diff --git a/addon.gradle b/addon.gradle new file mode 100644 index 000000000..629490719 --- /dev/null +++ b/addon.gradle @@ -0,0 +1,14 @@ +configurations { + lsp4j + implementation.extendsFrom(lsp4j) +} + +jar { + from provider { + configurations.lsp4j.collect { + it.isDirectory() ? it : zipTree(it) + } + }, { + exclude 'META-INF', 'META-INF/**', 'about*.html' + } +} diff --git a/dependencies.gradle b/dependencies.gradle index 5a1211f19..8ca1a41f6 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -24,6 +24,18 @@ dependencies { embed "org.apache.groovy:groovy:${project.groovy_version}" + // TODO: check if there is anything to exclude + embed "io.github.classgraph:classgraph:4.8.165" + + lsp4j("org.eclipse.lsp4j:org.eclipse.lsp4j:0.20.1") { + exclude group: 'com.google.guava', module: 'guava' + exclude group: 'com.google.code.gson', module: 'gson' + } + lsp4j("org.eclipse.lsp4j:org.eclipse.lsp4j.jsonrpc:0.20.1") { + exclude group: 'com.google.guava', module: 'guava' + exclude group: 'com.google.code.gson', module: 'gson' + } + compileOnly rfg.deobf('curse.maven:packmode-278398:2567799') if (project.debug_packmode.toBoolean()) { runtimeOnly rfg.deobf('curse.maven:packmode-278398:2567799') @@ -36,6 +48,15 @@ dependencies { runtimeOnly rfg.deobf('curse.maven:avaritia_1_10-261348:3143349') } + compileOnly rfg.deobf("curse.maven:shadowfacts-forgelin-248453:2785465") + compileOnly rfg.deobf("curse.maven:alchemylib-293426:2761706") + compileOnly rfg.deobf("curse.maven:alchemistry-293425:3186612") + if (project.debug_alchemistry.toBoolean()) { + runtimeOnly rfg.deobf('curse.maven:shadowfacts-forgelin-248453:2785465') + runtimeOnly rfg.deobf('curse.maven:alchemylib-293426:2761706') + runtimeOnly rfg.deobf('curse.maven:alchemistry-293425:3186612') + } + compileOnly rfg.deobf("curse.maven:ctm-267602:2915363") compileOnly rfg.deobf("curse.maven:chisel-235279:2915375") if (project.debug_chisel.toBoolean()) { @@ -43,6 +64,13 @@ dependencies { runtimeOnly rfg.deobf('curse.maven:chisel-235279:2915375') } + compileOnly rfg.deobf('curse.maven:bwm-suite-246760:3289033') + compileOnly rfg.deobf('curse.maven:bwm-core-294335:2624990') + if (project.debug_better_with_mods.toBoolean()) { + runtimeOnly rfg.deobf('curse.maven:bwm-suite-246760:3289033') + runtimeOnly rfg.deobf('curse.maven:bwm-core-294335:2624990') + } + compileOnly rfg.deobf('curse.maven:mantle-74924:2713386') if (project.debug_inspirations.toBoolean() || project.debug_tinkers.toBoolean()) { runtimeOnly rfg.deobf('curse.maven:mantle-74924:2713386') @@ -214,14 +242,10 @@ dependencies { } compileOnly 'com.enderio:endercore:0.5.78' - compileOnly('crazypants:enderio:5.3.72') { - transitive = false - } + compileOnly 'crazypants:enderio:5.3.72' if (project.debug_enderio.toBoolean()) { runtimeOnly 'com.enderio:endercore:0.5.78' - runtimeOnly('crazypants:enderio:5.3.72') { - transitive = false - } + runtimeOnly 'crazypants:enderio:5.3.72' } compileOnly rfg.deobf('curse.maven:forestry-59751:2918418') @@ -235,6 +259,11 @@ dependencies { runtimeOnly rfg.deobf('curse.maven:advancedmortars-283777:2780626') } + compileOnly rfg.deobf('curse.maven:aether-255308:3280119') + if (project.debug_aether.toBoolean()) { + runtimeOnly rfg.deobf('curse.maven:aether-255308:3280119') + } + compileOnly rfg.deobf('curse.maven:pyrotech-306676:4956838') compileOnly rfg.deobf('curse.maven:athenaeum-284350:4633750') @@ -243,8 +272,6 @@ dependencies { runtimeOnly rfg.deobf('curse.maven:pyrotech-306676:4956838') runtimeOnly rfg.deobf("curse.maven:dropt-284973:3758733") } - - } minecraft { @@ -253,4 +280,7 @@ minecraft { if (project.debug_use_examples_folder.toBoolean()) { extraRunJvmArguments.add('-Dgroovyscript.use_examples_folder=true') } + if (project.debug_run_ls.toBoolean()) { + extraRunJvmArguments.add('-Dgroovyscript.run_ls=true') + } } diff --git a/editors/README.md b/editors/README.md new file mode 100644 index 000000000..9fb13fd92 --- /dev/null +++ b/editors/README.md @@ -0,0 +1,9 @@ +# Supported editors + +- Visual Studio Code + +# TODO: + +- IntelliJ IDEA via [lsp4intellij](https://github.com/ballerina-platform/lsp4intellij) + or [lsp4ij](https://github.com/redhat-developer/lsp4ij) +- helix + vim via `netcat` \ No newline at end of file diff --git a/editors/vscode/.gitignore b/editors/vscode/.gitignore new file mode 100755 index 000000000..843fbca65 --- /dev/null +++ b/editors/vscode/.gitignore @@ -0,0 +1,5 @@ +out +dist +node_modules +.vscode-test/ +*.vsix \ No newline at end of file diff --git a/editors/vscode/.vscode/launch.json b/editors/vscode/.vscode/launch.json new file mode 100644 index 000000000..c84536b77 --- /dev/null +++ b/editors/vscode/.vscode/launch.json @@ -0,0 +1,21 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}" + ], + "name": "Launch Extension", + "outFiles": [ + "${workspaceFolder}/out/**/*.js" + ], + "preLaunchTask": "npm", + "request": "launch", + "type": "extensionHost" + } + + ] +} \ No newline at end of file diff --git a/editors/vscode/.vscode/settings.json b/editors/vscode/.vscode/settings.json new file mode 100644 index 000000000..9c7c15c3c --- /dev/null +++ b/editors/vscode/.vscode/settings.json @@ -0,0 +1,10 @@ +{ + "json.schemas": [ + { + "fileMatch": [ + "*.snippets.json" + ], + "url": "vscode://schemas/global-snippets" + } + ] +} \ No newline at end of file diff --git a/editors/vscode/.vscode/tasks.json b/editors/vscode/.vscode/tasks.json new file mode 100644 index 000000000..aa4cdca69 --- /dev/null +++ b/editors/vscode/.vscode/tasks.json @@ -0,0 +1,13 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "build", + "group": "build", + "problemMatcher": [], + "label": "npm", + "detail": "npm run build-base -- --sourcemap" + } + ] +} \ No newline at end of file diff --git a/editors/vscode/.vscodeignore b/editors/vscode/.vscodeignore new file mode 100755 index 000000000..ceb7c3308 --- /dev/null +++ b/editors/vscode/.vscodeignore @@ -0,0 +1,6 @@ +** +!out/main.js +!package-lock.json +!package.json +!README.md +!logo.png \ No newline at end of file diff --git a/editors/vscode/README.md b/editors/vscode/README.md new file mode 100755 index 000000000..df8323c15 --- /dev/null +++ b/editors/vscode/README.md @@ -0,0 +1,32 @@ +# GroovyScript + +This is the official VSC extension for the Minecraft 1.12.2 mod [GroovyScript](https://github.com/CleanroomMC/GroovyScript). +It allows VSC to connect to a running minecraft instance with a groovy folder and provide various tools for writing +scripts like auto-completion and hover info. + +## Getting Started +First you need to start the language server. Then you can connect VSC. +### Starting the language Server +There are two ways to do that. +1. Adding `-Dgroovyscript.run_ls=true` to the JVM arguments in your preferred launcher and start Minecraft with GroovyScript. + You will be able to connect VSC as soon as GroovyScript start running `preInit` scripts. +2. By starting Minecraft and GroovyScript and running `/grs runLS` command. Obviously you need to load into a world for that. + +You can check if the server started by checking for a `Starting Language server` message in the groovy log. + +By default, the language server is started with a port of `25564`. It must be the same as the port in the VSC extension, +which is configurable. + +### Connect VSC +1. Open VSC (skip if already open) +2. Install GroovyScript extension (skip if already installed) +3. Open the instance folder or groovy folder of a modpack in VSC +4. If you just opened VSC, it should auto connect. Otherwise, run the `GroovyScript: Reconnect` command. +5. Done + +At the bottom right in the status bar is a thumbs up/down emoji. This indicates the server connection status. + +That's it. The important part is that Minecraft needs to be running and the language server is started. +Note that minecraft has higher memory and cpu usage while the language server is running. + +Happy scripting :) diff --git a/editors/vscode/logo.png b/editors/vscode/logo.png new file mode 100644 index 000000000..3bf43f6bc Binary files /dev/null and b/editors/vscode/logo.png differ diff --git a/editors/vscode/package-lock.json b/editors/vscode/package-lock.json new file mode 100644 index 000000000..71dfc7c2a --- /dev/null +++ b/editors/vscode/package-lock.json @@ -0,0 +1,539 @@ +{ + "name": "groovyscript", + "version": "1.0.2", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "groovyscript", + "version": "1.0.2", + "dependencies": { + "vscode-languageclient": "^9.0.1" + }, + "devDependencies": { + "@types/node": "^20.11.17", + "@types/vscode": "^1.81.0", + "esbuild": "^0.20.0" + }, + "engines": { + "vscode": "^1.81.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.0.tgz", + "integrity": "sha512-fGFDEctNh0CcSwsiRPxiaqX0P5rq+AqE0SRhYGZ4PX46Lg1FNR6oCxJghf8YgY0WQEgQuh3lErUFE4KxLeRmmw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.0.tgz", + "integrity": "sha512-3bMAfInvByLHfJwYPJRlpTeaQA75n8C/QKpEaiS4HrFWFiJlNI0vzq/zCjBrhAYcPyVPG7Eo9dMrcQXuqmNk5g==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.0.tgz", + "integrity": "sha512-aVpnM4lURNkp0D3qPoAzSG92VXStYmoVPOgXveAUoQBWRSuQzt51yvSju29J6AHPmwY1BjH49uR29oyfH1ra8Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.0.tgz", + "integrity": "sha512-uK7wAnlRvjkCPzh8jJ+QejFyrP8ObKuR5cBIsQZ+qbMunwR8sbd8krmMbxTLSrDhiPZaJYKQAU5Y3iMDcZPhyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.0.tgz", + "integrity": "sha512-AjEcivGAlPs3UAcJedMa9qYg9eSfU6FnGHJjT8s346HSKkrcWlYezGE8VaO2xKfvvlZkgAhyvl06OJOxiMgOYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.0.tgz", + "integrity": "sha512-bsgTPoyYDnPv8ER0HqnJggXK6RyFy4PH4rtsId0V7Efa90u2+EifxytE9pZnsDgExgkARy24WUQGv9irVbTvIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.0.tgz", + "integrity": "sha512-kQ7jYdlKS335mpGbMW5tEe3IrQFIok9r84EM3PXB8qBFJPSc6dpWfrtsC/y1pyrz82xfUIn5ZrnSHQQsd6jebQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.0.tgz", + "integrity": "sha512-uG8B0WSepMRsBNVXAQcHf9+Ko/Tr+XqmK7Ptel9HVmnykupXdS4J7ovSQUIi0tQGIndhbqWLaIL/qO/cWhXKyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.0.tgz", + "integrity": "sha512-2ezuhdiZw8vuHf1HKSf4TIk80naTbP9At7sOqZmdVwvvMyuoDiZB49YZKLsLOfKIr77+I40dWpHVeY5JHpIEIg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.0.tgz", + "integrity": "sha512-uTtyYAP5veqi2z9b6Gr0NUoNv9F/rOzI8tOD5jKcCvRUn7T60Bb+42NDBCWNhMjkQzI0qqwXkQGo1SY41G52nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.0.tgz", + "integrity": "sha512-c88wwtfs8tTffPaoJ+SQn3y+lKtgTzyjkD8NgsyCtCmtoIC8RDL7PrJU05an/e9VuAke6eJqGkoMhJK1RY6z4w==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.0.tgz", + "integrity": "sha512-lR2rr/128/6svngnVta6JN4gxSXle/yZEZL3o4XZ6esOqhyR4wsKyfu6qXAL04S4S5CgGfG+GYZnjFd4YiG3Aw==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.0.tgz", + "integrity": "sha512-9Sycc+1uUsDnJCelDf6ZNqgZQoK1mJvFtqf2MUz4ujTxGhvCWw+4chYfDLPepMEvVL9PDwn6HrXad5yOrNzIsQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.0.tgz", + "integrity": "sha512-CoWSaaAXOZd+CjbUTdXIJE/t7Oz+4g90A3VBCHLbfuc5yUQU/nFDLOzQsN0cdxgXd97lYW/psIIBdjzQIwTBGw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.0.tgz", + "integrity": "sha512-mlb1hg/eYRJUpv8h/x+4ShgoNLL8wgZ64SUr26KwglTYnwAWjkhR2GpoKftDbPOCnodA9t4Y/b68H4J9XmmPzA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.0.tgz", + "integrity": "sha512-fgf9ubb53xSnOBqyvWEY6ukBNRl1mVX1srPNu06B6mNsNK20JfH6xV6jECzrQ69/VMiTLvHMicQR/PgTOgqJUQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.0.tgz", + "integrity": "sha512-H9Eu6MGse++204XZcYsse1yFHmRXEWgadk2N58O/xd50P9EvFMLJTQLg+lB4E1cF2xhLZU5luSWtGTb0l9UeSg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.0.tgz", + "integrity": "sha512-lCT675rTN1v8Fo+RGrE5KjSnfY0x9Og4RN7t7lVrN3vMSjy34/+3na0q7RIfWDAj0e0rCh0OL+P88lu3Rt21MQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.0.tgz", + "integrity": "sha512-HKoUGXz/TOVXKQ+67NhxyHv+aDSZf44QpWLa3I1lLvAwGq8x1k0T+e2HHSRvxWhfJrFxaaqre1+YyzQ99KixoA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.0.tgz", + "integrity": "sha512-GDwAqgHQm1mVoPppGsoq4WJwT3vhnz/2N62CzhvApFD1eJyTroob30FPpOZabN+FgCjhG+AgcZyOPIkR8dfD7g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.0.tgz", + "integrity": "sha512-0vYsP8aC4TvMlOQYozoksiaxjlvUcQrac+muDqj1Fxy6jh9l9CZJzj7zmh8JGfiV49cYLTorFLxg7593pGldwQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.0.tgz", + "integrity": "sha512-p98u4rIgfh4gdpV00IqknBD5pC84LCub+4a3MO+zjqvU5MVXOc3hqR2UgT2jI2nh3h8s9EQxmOsVI3tyzv1iFg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.0.tgz", + "integrity": "sha512-NgJnesu1RtWihtTtXGFMU5YSE6JyyHPMxCwBZK7a6/8d31GuSo9l0Ss7w1Jw5QnKUawG6UEehs883kcXf5fYwg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@types/node": { + "version": "20.11.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.17.tgz", + "integrity": "sha512-QmgQZGWu1Yw9TDyAP9ZzpFJKynYNeOvwMJmaxABfieQoVoiVOS6MN1WSpqpRcbeA5+RW82kraAVxCCJg+780Qw==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/vscode": { + "version": "1.86.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.86.0.tgz", + "integrity": "sha512-DnIXf2ftWv+9LWOB5OJeIeaLigLHF7fdXF6atfc7X5g2w/wVZBgk0amP7b+ub5xAuW1q7qP5YcFvOcit/DtyCQ==", + "dev": true + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/esbuild": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.0.tgz", + "integrity": "sha512-6iwE3Y2RVYCME1jLpBqq7LQWK3MW6vjV2bZy6gt/WrqkY+WE74Spyc0ThAOYpMtITvnjX09CrC6ym7A/m9mebA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.20.0", + "@esbuild/android-arm": "0.20.0", + "@esbuild/android-arm64": "0.20.0", + "@esbuild/android-x64": "0.20.0", + "@esbuild/darwin-arm64": "0.20.0", + "@esbuild/darwin-x64": "0.20.0", + "@esbuild/freebsd-arm64": "0.20.0", + "@esbuild/freebsd-x64": "0.20.0", + "@esbuild/linux-arm": "0.20.0", + "@esbuild/linux-arm64": "0.20.0", + "@esbuild/linux-ia32": "0.20.0", + "@esbuild/linux-loong64": "0.20.0", + "@esbuild/linux-mips64el": "0.20.0", + "@esbuild/linux-ppc64": "0.20.0", + "@esbuild/linux-riscv64": "0.20.0", + "@esbuild/linux-s390x": "0.20.0", + "@esbuild/linux-x64": "0.20.0", + "@esbuild/netbsd-x64": "0.20.0", + "@esbuild/openbsd-x64": "0.20.0", + "@esbuild/sunos-x64": "0.20.0", + "@esbuild/win32-arm64": "0.20.0", + "@esbuild/win32-ia32": "0.20.0", + "@esbuild/win32-x64": "0.20.0" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/vscode-jsonrpc": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vscode-languageclient": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-9.0.1.tgz", + "integrity": "sha512-JZiimVdvimEuHh5olxhxkht09m3JzUGwggb5eRUkzzJhZ2KjCN0nh55VfiED9oez9DyF8/fz1g1iBV3h+0Z2EA==", + "dependencies": { + "minimatch": "^5.1.0", + "semver": "^7.3.7", + "vscode-languageserver-protocol": "3.17.5" + }, + "engines": { + "vscode": "^1.82.0" + } + }, + "node_modules/vscode-languageclient/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/vscode-languageclient/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", + "dependencies": { + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" + } + }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==" + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } +} diff --git a/editors/vscode/package.json b/editors/vscode/package.json new file mode 100644 index 000000000..e04b51e98 --- /dev/null +++ b/editors/vscode/package.json @@ -0,0 +1,67 @@ +{ + "name": "groovyscript", + "displayName": "GroovyScript", + "description": "GroovyScript language client for Visual Studio Code", + "icon": "./logo.png", + "version": "1.0.2", + "repository": { + "type": "git", + "url": "https://github.com/CleanroomMC/GroovyScript.git", + "directory": "editors/vscode" + }, + "publisher": "CleanroomMC", + "engines": { + "vscode": "^1.81.0" + }, + "categories": [ + "Other" + ], + "activationEvents": [ + "workspaceContains:**/runConfig.json" + ], + "main": "./out/main.js", + "contributes": { + "commands": [ + { + "command": "groovyscript.reconnect", + "title": "Reconnect", + "category": "GroovyScript" + } + ], + "configuration": { + "title": "GroovyScript", + "properties": { + "groovyscript.port": { + "type": "number", + "default": 25564, + "title": "Language server port", + "description": "Port to connect to GroovyScript mod" + } + } + }, + "snippets": [ + { + "language": "groovy", + "path": "./snippets/groovyscript.snippets.json" + } + ] + }, + "scripts": { + "vscode:prepublish": "npm run build-base -- --minify", + "package": "vsce package -o groovy-analyzer.vsix", + "build-base": "esbuild ./src/main.ts --bundle --outfile=out/main.js --external:vscode --format=cjs --platform=node --target=node16", + "build": "npm run build-base -- --sourcemap", + "watch": "npm run build-base -- --sourcemap --watch", + "pretest": "npm run compile && npm run lint", + "lint": "eslint src --ext ts", + "test": "node ./out/test/runTest.js" + }, + "dependencies": { + "vscode-languageclient": "^9.0.1" + }, + "devDependencies": { + "@types/node": "^20.11.17", + "@types/vscode": "^1.81.0", + "esbuild": "^0.20.0" + } +} diff --git a/editors/vscode/snippets/groovyscript.snippets.json b/editors/vscode/snippets/groovyscript.snippets.json new file mode 100644 index 000000000..12fb83626 --- /dev/null +++ b/editors/vscode/snippets/groovyscript.snippets.json @@ -0,0 +1,54 @@ +{ + "Check is mod loaded preprocessor": { + "scope": "groovy", + "prefix": "is mod loaded", + "description": "Check if the mod is loaded", + "body": [ + "// MODS_LOADED: ${1:modname}" + ] + }, + "Check for Packmode": { + "scope": "groovy", + "prefix": "is packmode", + "description": "Check if the mod is loaded", + "body": [ + "// PACKMODE: ${1:packmode}" + ] + }, + "SideOnly annotation": { + "scope": "groovy", + "prefix": "client only", + "description": "@SideOnly(Side.CLIENT)", + "body": [ + "@SideOnly(Side.CLIENT)" + ] + }, + "Listen for event": { + "scope": "groovy", + "prefix": "listen forge event", + "description": "Listen for a forge event", + "body": [ + "event_manager.listen { ${1:eventtype} event ->", + "\t$2", + "}" + ] + }, + "Shaped crafting recipe": { + "scope": "groovy", + "prefix": "shaped crafting", + "description": "Create a shaped crafting recipe", + "body": [ + "crafting.addShaped('${1:recipe_name}', item('${2:output}', [", + "\t[$3]", + "])" + ] + }, + "Shapeless crafting recipe": { + "scope": "groovy", + "prefix": "shapeless crafting", + "description": "Create a shapeless crafting recipe", + "body": [ + "crafting.addShapeless('${1:recipe_name}', item('${2:output}', [$3]))" + ] + } +} \ No newline at end of file diff --git a/editors/vscode/src/gui/extensionStatusBarProvider.ts b/editors/vscode/src/gui/extensionStatusBarProvider.ts new file mode 100644 index 000000000..cf4e502d9 --- /dev/null +++ b/editors/vscode/src/gui/extensionStatusBarProvider.ts @@ -0,0 +1,74 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2021 DontShaveTheYak +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License +// +// Author: DontShaveTheYak +// No warranty of merchantability or fitness of any kind. +// Use this software at your own risk. +//////////////////////////////////////////////////////////////////////////////// + +import { StatusBarItem, window, StatusBarAlignment } from "vscode"; +import { Disposable } from "vscode-languageclient"; + +class ExtensionStatusBarProvider implements Disposable { + private statusBarItem: StatusBarItem; + + constructor() { + this.statusBarItem = window.createStatusBarItem(StatusBarAlignment.Right, Number.MIN_VALUE); + } + + public startUp(): void { + this.statusBarItem.text = StatusIcon.launching; + this.statusBarItem.tooltip = 'GroovyScript: Connecting'; + this.statusBarItem.show(); + } + + public running(): void { + this.statusBarItem.text = StatusIcon.ready; + this.statusBarItem.tooltip = 'GroovyScript: Ready'; + } + + public updateText(text: string): void { + this.statusBarItem.text = text; + } + + public setBusy(): void { + this.statusBarItem.text = StatusIcon.busy; + } + + public setError(): void { + this.statusBarItem.text = StatusIcon.error; + } + + public setReady(): void { + this.statusBarItem.text = StatusIcon.ready; + } + + public updateTooltip(tooltip: string): void { + this.statusBarItem.tooltip = tooltip; + } + + public dispose(): void { + this.statusBarItem.dispose(); + } +} + +enum StatusIcon { + busy = "$(sync~spin)", + ready = "$(thumbsup)", + error = "$(thumbsdown)", + launching = "$(rocket)", +} + +export const extensionStatusBar: ExtensionStatusBarProvider = new ExtensionStatusBarProvider(); diff --git a/editors/vscode/src/main.ts b/editors/vscode/src/main.ts new file mode 100755 index 000000000..323e17564 --- /dev/null +++ b/editors/vscode/src/main.ts @@ -0,0 +1,80 @@ +import * as net from "net"; +import * as lc from "vscode-languageclient/node"; +import * as vscode from "vscode"; +import { extensionStatusBar } from "./gui/extensionStatusBarProvider"; + +let client: lc.LanguageClient; +let outputChannel = vscode.window.createOutputChannel("GroovyScript Language Server"); +let traceOutputChannel = vscode.window.createOutputChannel("GroovyScript Language Server Trace"); + +async function startClient() { + const serverOptions = () => { + const configuration = vscode.workspace.getConfiguration("groovyscript"); + let port = configuration.get("port", 25564); + outputChannel.appendLine(`Connecting to GroovyScript Language Server at port ${port}`); + let socket = net.connect({ port: port }); + socket.on("error", (err) => { + extensionStatusBar.setError(); + outputChannel.appendLine(err.toString()); + stopClient(); + }); + let result: lc.StreamInfo = { + writer: socket, + reader: socket + }; + return Promise.resolve(result); + }; + + const clientOptions: lc.LanguageClientOptions = { + documentSelector: [ + { scheme: "file", language: "groovy" }, + { scheme: "file", pattern: "*.groovy" }, + { scheme: "file", pattern: "*.gvy" }, + { scheme: "file", pattern: "*.gy" }, + { scheme: "file", pattern: "*.gsh" }, + ], + outputChannel, + traceOutputChannel, + }; + + client = new lc.LanguageClient("groovyscript", "GroovyScript", serverOptions, clientOptions) + + try { + await client.start(); + } catch (e) { + extensionStatusBar.setError(); + outputChannel.appendLine(e.toString()); + return; + } + + extensionStatusBar.running(); + outputChannel.appendLine("Connected to GroovyScript Language Server"); +} + +async function stopClient() { + if (!client || !client.isRunning()) return; + try { + await client.stop(); + } catch { + } +} + +export async function activate(context: vscode.ExtensionContext) { + let disposable = vscode.commands.registerCommand("groovyscript.reconnect", async () => { + outputChannel.appendLine("Reconnecting..."); + await stopClient(); + extensionStatusBar.startUp(); + await startClient(); + }); + + context.subscriptions.push(disposable); + + context.subscriptions.push(extensionStatusBar); + extensionStatusBar.startUp(); + + await startClient(); +} + +export function deactivate(): Thenable | undefined { + return stopClient(); +} diff --git a/examples/postInit/actuallyadditions.groovy b/examples/postInit/actuallyadditions.groovy index 1e3823de9..708092683 100644 --- a/examples/postInit/actuallyadditions.groovy +++ b/examples/postInit/actuallyadditions.groovy @@ -1,38 +1,53 @@ +// Auto generated groovyscript example file // MODS_LOADED: actuallyadditions println 'mod \'actuallyadditions\' detected, running script' - -// Atomic Reconstructor +// Atomic Reconstructor: // The Atomic Reconstructor is a block which uses energy to convert a block or item in front of it into other items. -mods.actuallyadditions.atomicreconstructor.recipeBuilder() + +mods.actuallyadditions.atomic_reconstructor.removeByInput(item('minecraft:diamond')) +mods.actuallyadditions.atomic_reconstructor.removeByOutput(item('actuallyadditions:block_crystal')) +// mods.actuallyadditions.atomic_reconstructor.removeAll() + +mods.actuallyadditions.atomic_reconstructor.recipeBuilder() + .input(item('minecraft:clay')) + .output(item('minecraft:diamond')) + .energyUse(1000) + .register() + +mods.actuallyadditions.atomic_reconstructor.recipeBuilder() .input(item('minecraft:clay')) .output(item('minecraft:diamond')) - .energyUse(1000) // Optional, int + .energy(1000) .register() -mods.actuallyadditions.atomicreconstructor.recipeBuilder() +mods.actuallyadditions.atomic_reconstructor.recipeBuilder() .input(item('minecraft:gold_ingot')) .output(item('minecraft:clay') * 2) .register() -mods.actuallyadditions.atomicreconstructor.removeByInput(item('minecraft:diamond')) -mods.actuallyadditions.atomicreconstructor.removeByOutput(item('actuallyadditions:block_crystal')) -//mods.actuallyadditions.atomicreconstructor.removeAll() -// Ball of Fur +// Ball of Fur: // A weighted itemstack output for using a Ball of Fur, dropped by a cat. -mods.actuallyadditions.balloffur.recipeBuilder() + +mods.actuallyadditions.ball_of_fur.removeByOutput(item('minecraft:feather')) +// mods.actuallyadditions.ball_of_fur.removeAll() + +mods.actuallyadditions.ball_of_fur.recipeBuilder() .output(item('minecraft:clay') * 32) .weight(15) .register() -mods.actuallyadditions.balloffur.removeByOutput(item('minecraft:feather')) -//mods.actuallyadditions.balloffur.removeAll() -// Compost +// Compost: // Converts an input item into an output item after 150 seconds. Requires an input and output display blockstate. + +mods.actuallyadditions.compost.removeByInput(item('actuallyadditions:item_canola_seed')) +mods.actuallyadditions.compost.removeByOutput(item('actuallyadditions:item_fertilizer')) +// mods.actuallyadditions.compost.removeAll() + mods.actuallyadditions.compost.recipeBuilder() .input(item('minecraft:clay')) .output(item('minecraft:diamond')) @@ -40,16 +55,18 @@ mods.actuallyadditions.compost.recipeBuilder() .outputDisplay(blockstate('minecraft:diamond_block')) .register() -mods.actuallyadditions.compost.removeByInput(item('actuallyadditions:item_canola_seed')) -mods.actuallyadditions.compost.removeByOutput(item('actuallyadditions:item_fertilizer')) -//mods.actuallyadditions.compost.removeAll() -// Crusher -// Converts an input itemstack into an output itemstack with a chance of a second itemstack +// Crusher: +// Converts an input itemstack into an output itemstack with a chance of a second itemstack. + +mods.actuallyadditions.crusher.removeByInput(item('minecraft:bone')) +mods.actuallyadditions.crusher.removeByOutput(item('minecraft:sugar')) +// mods.actuallyadditions.crusher.removeAll() + mods.actuallyadditions.crusher.recipeBuilder() .input(item('minecraft:clay')) - .output(item('minecraft:diamond'), item('minecraft:diamond')) // Second output is optional, and modified by chance - .chance(100) // Optional, int + .output(item('minecraft:diamond'), item('minecraft:diamond')) + .chance(100) .register() mods.actuallyadditions.crusher.recipeBuilder() @@ -57,21 +74,23 @@ mods.actuallyadditions.crusher.recipeBuilder() .output(item('minecraft:diamond') * 12) .register() -mods.actuallyadditions.crusher.removeByInput(item('minecraft:bone')) -mods.actuallyadditions.crusher.removeByOutput(item('minecraft:sugar')) -//mods.actuallyadditions.crusher.removeAll() -// Empowerer -// Turns 5 input items into an output item at the cost of power and time. Has a configurable color +// Empowerer: +// Turns 5 input items into an output item at the cost of power and time. Has a configurable color. + +mods.actuallyadditions.empowerer.removeByInput(item('actuallyadditions:item_crystal')) +mods.actuallyadditions.empowerer.removeByOutput(item('actuallyadditions:item_misc:24')) +// mods.actuallyadditions.empowerer.removeAll() + mods.actuallyadditions.empowerer.recipeBuilder() - .mainInput(item('minecraft:clay')) // Optional, itemstack. if undefined and input has 5 items, mainInput uses the first itemstack input. Otherwise, errors. + .mainInput(item('minecraft:clay')) .input(item('minecraft:clay'),item('minecraft:clay'),item('minecraft:clay'),item('minecraft:clay')) .output(item('minecraft:diamond')) .time(50) - .energy(1000) // Optional, int - .red(0.5) // Optional, float (default 0) - .green(0.3) // Optional, float (default 0) - .blue(0.2) // Optional, float (default 0) + .energy(1000) + .red(0.5) + .green(0.3) + .blue(0.2) .register() mods.actuallyadditions.empowerer.recipeBuilder() @@ -79,7 +98,7 @@ mods.actuallyadditions.empowerer.recipeBuilder() .input(item('minecraft:diamond'),item('minecraft:clay'),item('minecraft:clay'),item('minecraft:clay')) .output(item('minecraft:diamond') * 2) .time(50) - .color(0.5, 0.3, 0.2) // Optional, float... shorthand for (red, green, blue). Must contain exactly 3 entries. "particleColor" and "color" are aliases + .color(0.5, 0.3, 0.2) .register() mods.actuallyadditions.empowerer.recipeBuilder() @@ -87,7 +106,7 @@ mods.actuallyadditions.empowerer.recipeBuilder() .input(item('minecraft:diamond'),item('minecraft:gold_ingot'),item('minecraft:diamond'),item('minecraft:gold_ingot')) .output(item('minecraft:dirt') * 8) .time(50) - .particleColor(0x00FF88) // Optional, int. Hexadecimal color. "particleColor" and "color" are aliases + .particleColor(0x00FF88) .register() mods.actuallyadditions.empowerer.recipeBuilder() @@ -96,74 +115,77 @@ mods.actuallyadditions.empowerer.recipeBuilder() .time(50) .register() -mods.actuallyadditions.empowerer.removeByInput(item('actuallyadditions:item_crystal')) -mods.actuallyadditions.empowerer.removeByOutput(item('actuallyadditions:item_misc:24')) -//mods.actuallyadditions.empowerer.removeAll() -// Nether Mining Lens -// A weighted oredict for the block obtained via firing a Mining Lens at a block of Netherrack. The oredict must have a block, or the world will hang. -mods.actuallyadditions.nethermininglens.recipeBuilder() +// Nether Mining Lens: +// A weighted oredict for the block obtained via firing a Mining Lens at a block of Netherrack. The oredict must have a +// block, or the world will hang. + +mods.actuallyadditions.nether_mining_lens.removeByOre(ore('oreQuartz')) +mods.actuallyadditions.nether_mining_lens.removeByOre('oreQuartz') +// mods.actuallyadditions.nether_mining_lens.removeAll() + +mods.actuallyadditions.nether_mining_lens.recipeBuilder() .ore(ore('blockDiamond')) .weight(100) .register() -mods.actuallyadditions.nethermininglens.recipeBuilder() +mods.actuallyadditions.nether_mining_lens.recipeBuilder() .ore('blockGold') .weight(100) .register() -mods.actuallyadditions.nethermininglens.removeByOre(ore('oreQuartz')) -mods.actuallyadditions.nethermininglens.removeByOre('oreQuartz') -//mods.actuallyadditions.nethermininglens.removeAll() -// Oil Gen -// Turns a fluid into power at a rate -mods.actuallyadditions.oilgen.recipeBuilder() +// Oil Gen: +// Turns a fluid into power at a rate. + +mods.actuallyadditions.oil_gen.removeByInput(fluid('canolaoil').getFluid()) +mods.actuallyadditions.oil_gen.removeByInput(fluid('canolaoil')) +mods.actuallyadditions.oil_gen.removeByInput('refinedcanolaoil') +// mods.actuallyadditions.oil_gen.removeAll() + +mods.actuallyadditions.oil_gen.recipeBuilder() .fluidInput(fluid('water')) - .amount(1000) // Optional, uses the FluidStack amount if not defined. + .amount(1000) .time(50) .register() -mods.actuallyadditions.oilgen.recipeBuilder() +mods.actuallyadditions.oil_gen.recipeBuilder() .fluidInput(fluid('lava') * 50) .time(100) .register() -mods.actuallyadditions.oilgen.removeByInput(fluid('canolaoil').getFluid()) -mods.actuallyadditions.oilgen.removeByInput('refinedcanolaoil') -//mods.actuallyadditions.oilgen.removeAll() +// Stone Mining Lens: +// A weighted oredict for the block obtained via firing a Mining Lens at a block of Stone. The oredict must have a block, +// or the world will hang. + +mods.actuallyadditions.stone_mining_lens.removeByOre(ore('oreCoal')) +mods.actuallyadditions.stone_mining_lens.removeByOre('oreLapis') +// mods.actuallyadditions.stone_mining_lens.removeAll() -// Stone Mining Lens -// A weighted oredict for the block obtained via firing a Mining Lens at a block of Stone. The oredict must have a block, or the world will hang. -mods.actuallyadditions.stonemininglens.recipeBuilder() +mods.actuallyadditions.stone_mining_lens.recipeBuilder() .ore(ore('blockDiamond')) .weight(100) .register() -mods.actuallyadditions.stonemininglens.recipeBuilder() +mods.actuallyadditions.stone_mining_lens.recipeBuilder() .ore('blockGold') .weight(100) .register() -mods.actuallyadditions.stonemininglens.removeByOre(ore('oreCoal')) -mods.actuallyadditions.stonemininglens.removeByOre('oreLapis') -//mods.actuallyadditions.stonemininglens.removeAll() -// Treasure Chest -// A weighted item, with a weight to obtain and a minimum and maximum amount. Obtained via right clicking a Treasure Chest spawning randomly on the sea floor. -mods.actuallyadditions.treasurechest.recipeBuilder() +// Treasure Chest: +// A weighted item, with a weight to obtain and a minimum and maximum amount. Obtained via right-clicking a Treasure Chest +// spawning randomly on the sea floor. + +mods.actuallyadditions.treasure_chest.removeByOutput(item('minecraft:iron_ingot')) +// mods.actuallyadditions.treasure_chest.removeAll() + +mods.actuallyadditions.treasure_chest.recipeBuilder() .output(item('minecraft:clay')) .weight(50) .min(16) .max(32) .register() -mods.actuallyadditions.treasurechest.removeByOutput(item('minecraft:iron_ingot')) -//mods.actuallyadditions.treasurechest.removeAll() - - - - - diff --git a/examples/postInit/advancedmortars.groovy b/examples/postInit/advancedmortars.groovy index cf7507240..ee411c385 100644 --- a/examples/postInit/advancedmortars.groovy +++ b/examples/postInit/advancedmortars.groovy @@ -1,54 +1,38 @@ +// Auto generated groovyscript example file // MODS_LOADED: advancedmortars + println 'mod \'advancedmortars\' detected, running script' -// Mortar - -// add(List types, ItemStack output, int duration, List inputs) -mods.advancedmortars.Mortar.add( - ['stone'], - item('minecraft:diamond') * 4, - 4, - [ore('ingotGold')] -) - -mods.advancedmortars.Mortar.add( - ['stone'], - item('minecraft:tnt'), - 4, - [ore('ingotGold')] -) - -// add(List types, ItemStack output, int duration, ItemStack secondaryOutput, float secondaryOutputChance, List inputs) -mods.advancedmortars.Mortar.add( - ['iron', 'wood'], - item('minecraft:tnt') * 5, - 4, - item('minecraft:tnt'), - 0.7, - [ore('ingotIron'), ore('ingotIron'), ore('ingotIron'), ore('ingotIron'), // max 8 - ore('ingotIron'), ore('ingotIron'), ore('ingotIron'), ore('ingotIron')] -) - -mods.advancedmortars.Mortar.recipeBuilder() - .type('stone') // EnumMortarType ('wood', 'stone', 'iron', 'diamond', 'gold', 'obsidian', 'emerald') - .duration(2) // int - .output(item('minecraft:grass')) // ItemStack - .input(item('minecraft:dirt')) // IIngredient - .register() - -mods.advancedmortars.Mortar.recipeBuilder() - .type('emerald') - .duration(4) - .output(item('minecraft:wheat_seeds') * 16) - .secondaryOutput(item('minecraft:melon_seeds')) // ItemStack - .input(ore('cropWheat')) - .register() - -mods.advancedmortars.Mortar.recipeBuilder() - .type('obsidian') - .duration(8) - .output(item('minecraft:wheat_seeds') * 16) - .secondaryOutput(item('minecraft:melon_seeds'), 0.5) // ItemStack, float - .input(ore('cropWheat')) - .register() \ No newline at end of file +// Mortar: +// Uses any number of specific types of Mortars to convert multiple items into a single output with a possible chance +// output after a number of player interactions. + +mods.advancedmortars.mortar.recipeBuilder() + .type('stone') + .duration(2) + .output(item('minecraft:grass')) + .input(item('minecraft:dirt')) + .register() + +mods.advancedmortars.mortar.recipeBuilder() + .type('emerald') + .duration(4) + .output(item('minecraft:wheat_seeds') * 16) + .secondaryOutput(item('minecraft:melon_seeds')) + .input(ore('cropWheat')) + .register() + +mods.advancedmortars.mortar.recipeBuilder() + .type('obsidian') + .duration(8) + .output(item('minecraft:wheat_seeds') * 16) + .secondaryOutput(item('minecraft:melon_seeds'), 0.5) + .input(ore('cropWheat')) + .register() + + +mods.advancedmortars.mortar.add(['iron', 'wood'], item('minecraft:tnt') * 5, 4, item('minecraft:tnt'), 0.7, [ore('ingotIron'), ore('ingotIron'), ore('ingotIron'), ore('ingotIron'),ore('ingotIron'), ore('ingotIron'), ore('ingotIron'), ore('ingotIron')]) +mods.advancedmortars.mortar.add(['stone'], item('minecraft:tnt'), 4, [ore('ingotGold')]) +mods.advancedmortars.mortar.add(['stone'], item('minecraft:diamond') * 4, 4, [ore('ingotGold')]) + diff --git a/examples/postInit/aether_legacy.groovy b/examples/postInit/aether_legacy.groovy new file mode 100644 index 000000000..5d4bdb7ea --- /dev/null +++ b/examples/postInit/aether_legacy.groovy @@ -0,0 +1,63 @@ + +// Auto generated groovyscript example file +// MODS_LOADED: aether_legacy + +println 'mod \'aether_legacy\' detected, running script' + +// Accessory: +// The Aether Accessory system. + +mods.aether_legacy.accessory.removeByInput(item('aether_legacy:iron_pendant')) +// mods.aether_legacy.accessory.removeAll() + +mods.aether_legacy.accessory.recipeBuilder() + .input(item('minecraft:shield')) + .accessoryType('shield') + .register() + + + +// Enchanter: +// Enchanting is a mechanic used to create new items, as well as repair tools, armor, and weapons, using the Altar block. + +mods.aether_legacy.enchanter.removeByOutput(item('aether_legacy:enchanted_gravitite')) +// mods.aether_legacy.enchanter.removeAll() + +mods.aether_legacy.enchanter.recipeBuilder() + .input(item('minecraft:clay')) + .output(item('minecraft:diamond')) + .time(200) + .register() + + + +// Enchanter Fuel: +// By default, the Enchantar (Altar) takes Ambrosium Shards as fuel. Using GroovyScript, custom fuels can be added. + +mods.aether_legacy.enchanter_fuel.removeByItem(item('aether_legacy:ambrosium_shard')) +// mods.aether_legacy.enchanter_fuel.removeAll() + +mods.aether_legacy.enchanter_fuel.add(item('minecraft:blaze_rod'), 1000) + +// Freezer: +// The Freezer is used to turn certain items into frozen versions. + +mods.aether_legacy.freezer.removeByOutput(item('minecraft:obsidian')) +// mods.aether_legacy.freezer.removeAll() + +mods.aether_legacy.freezer.recipeBuilder() + .input(item('minecraft:clay')) + .output(item('minecraft:dirt')) + .time(200) + .register() + + + +// Freezer: +// By default, the Freezer takes Icestone as fuel. Using GroovyScript, custom fuels can be added. + +mods.aether_legacy.freezer_fuel.removeByItem(item('aether_legacy:icestone')) +// mods.aether_legacy.freezer_fuel.removeAll() + +mods.aether_legacy.freezer_fuel.add(item('minecraft:packed_ice'), 1000) + diff --git a/examples/postInit/alchemistry.groovy b/examples/postInit/alchemistry.groovy new file mode 100644 index 000000000..131ad7c52 --- /dev/null +++ b/examples/postInit/alchemistry.groovy @@ -0,0 +1,127 @@ + +// Auto generated groovyscript example file +// MODS_LOADED: alchemistry + +println 'mod \'alchemistry\' detected, running script' + +// groovyscript.wiki.alchemistry.atomizer.title: +// groovyscript.wiki.alchemistry.atomizer.description + +mods.alchemistry.atomizer.removeByInput(fluid('water')) +// mods.alchemistry.atomizer.removeByOutput(item('alchemistry:compound:7')) +// mods.alchemistry.atomizer.removeAll() + +mods.alchemistry.atomizer.recipeBuilder() + .fluidInput(fluid('water') * 125) + .output(item('minecraft:clay')) + .register() + +mods.alchemistry.atomizer.recipeBuilder() + .fluidInput(fluid('lava') * 500) + .output(item('minecraft:gold_ingot')) + .reversible() + .register() + + +// groovyscript.wiki.alchemistry.combiner.title: +// groovyscript.wiki.alchemistry.combiner.description + +mods.alchemistry.combiner.removeByInput(element('carbon')) +mods.alchemistry.combiner.removeByOutput(item('minecraft:glowstone')) +// mods.alchemistry.combiner.removeAll() + +mods.alchemistry.combiner.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, item('minecraft:gold_ingot') * 2) + .output(item('minecraft:gold_block') * 2) + .register() + +mods.alchemistry.combiner.recipeBuilder() + .input(ItemStack.EMPTY, ItemStack.EMPTY, item('minecraft:clay')) + .output(item('minecraft:gold_ingot')) + .register() + + +// groovyscript.wiki.alchemistry.dissolver.title: +// groovyscript.wiki.alchemistry.dissolver.description + +mods.alchemistry.dissolver.removeByInput(item('alchemistry:compound:1')) +// mods.alchemistry.dissolver.removeAll() + +mods.alchemistry.dissolver.recipeBuilder() + .input(item('minecraft:gold_ingot')) + .probabilityOutput(item('minecraft:clay')) + .reversible() + .rolls(1) + .register() + +mods.alchemistry.dissolver.recipeBuilder() + .input(item('minecraft:diamond')) + .probabilityOutput(30, item('minecraft:clay')) + .probabilityOutput(30, item('minecraft:clay')) + .probabilityOutput(30, item('minecraft:clay')) + .rolls(10) + .register() + + +// groovyscript.wiki.alchemistry.electrolyzer.title: +// groovyscript.wiki.alchemistry.electrolyzer.description + +// mods.alchemistry.electrolyzer.removeByInput(fluid('water')) +mods.alchemistry.electrolyzer.removeByInput(element('calcium_carbonate')) +mods.alchemistry.electrolyzer.removeByOutput(element('chlorine')) +// mods.alchemistry.electrolyzer.removeAll() + +mods.alchemistry.electrolyzer.recipeBuilder() + .fluidInput(fluid('lava') * 100) + .output(item('minecraft:clay')) + .register() + +mods.alchemistry.electrolyzer.recipeBuilder() + .fluidInput(fluid('water') * 100) + .input(item('minecraft:gold_ingot')) + .consumptionChance(100) + .output(item('minecraft:gold_nugget') * 4) + .output(item('minecraft:gold_nugget') * 4) + .output(item('minecraft:gold_nugget') * 4) + .output(item('minecraft:gold_nugget') * 4) + .chance(50) + .chance(50) + .register() + + +// groovyscript.wiki.alchemistry.evaporator.title: +// groovyscript.wiki.alchemistry.evaporator.description + +mods.alchemistry.evaporator.removeByInput(fluid('lava')) +mods.alchemistry.evaporator.removeByOutput(item('alchemistry:mineral_salt')) +// mods.alchemistry.evaporator.removeAll() + +mods.alchemistry.evaporator.recipeBuilder() + .fluidInput(fluid('lava') * 100) + .output(item('minecraft:redstone') * 8) + .register() + +mods.alchemistry.evaporator.recipeBuilder() + .fluidInput(fluid('water') * 10) + .output(item('minecraft:clay')) + .register() + + +// groovyscript.wiki.alchemistry.liquifier.title: +// groovyscript.wiki.alchemistry.liquifier.description + +mods.alchemistry.liquifier.removeByInput(element('water')) +// mods.alchemistry.liquifier.removeByOutput(fluid('water')) +// mods.alchemistry.liquifier.removeAll() + +mods.alchemistry.liquifier.recipeBuilder() + .input(element('carbon') * 32) + .fluidOutput(fluid('water') * 1000) + .register() + +mods.alchemistry.liquifier.recipeBuilder() + .input(item('minecraft:magma')) + .fluidOutput(fluid('lava') * 750) + .register() + + diff --git a/examples/postInit/appliedenergistics2.groovy b/examples/postInit/appliedenergistics2.groovy index b16cf2449..0db8a35eb 100644 --- a/examples/postInit/appliedenergistics2.groovy +++ b/examples/postInit/appliedenergistics2.groovy @@ -1,58 +1,46 @@ -/* -NO_RUN -NO_RELOAD -MODS_LOADED: appliedenergistics2 -SIDE: client -*/ +// Auto generated groovyscript example file // MODS_LOADED: appliedenergistics2 -println 'mod \'appliedenergistics2\' detected, running script' -// Can be access via either `appliedenergistics2` or `ae2` +import appeng.capabilities.Capabilities -// Bracket Handlers +println 'mod \'appliedenergistics2\' detected, running script' -// Get the P2P Tunnel type from the enum. Case insensitive. -tunnel('me') -tunnel('ic2_power') -tunnel('fe_power') -tunnel('gteu_power') -tunnel('redstone') -tunnel('fluid') -tunnel('item') -tunnel('light') -tunnel('bundled_redstone') -tunnel('computer_message') +// P2P Attunement: +// Controls using specific items, any items from a mod, or any items with a Capability to convert a P2P into a specific +// tunnel type. +mods.appliedenergistics2.attunement.remove(Capabilities.FORGE_ENERGY, tunnel('fe_power')) +mods.appliedenergistics2.attunement.remove(item('minecraft:lever'), tunnel('redstone')) +mods.appliedenergistics2.attunement.remove('thermaldynamics', tunnel('fe_power')) +mods.appliedenergistics2.attunement.removeByTunnel(tunnel('item')) +// mods.appliedenergistics2.attunement.removeAll() -// Inscriber: -// Converts an item into another item, requiring either one or two additional items as either catalysts or ingredients. -mods.appliedenergistics2.inscriber.recipeBuilder() - .input(ore('blockGlass')) - .output(item('minecraft:diamond')) - .top(item('minecraft:diamond')) // Optional, ItemStack. Either top or bottom must be defined. - .bottom(item('minecraft:diamond')) // Optional, ItemStack. Either top or bottom must be defined. - .inscribe() // Disable consumption of the top/bottom items. - .register() +mods.appliedenergistics2.attunement.add(Capabilities.FORGE_ENERGY, tunnel('item')) +mods.appliedenergistics2.attunement.add(item('minecraft:clay'), tunnel('item')) +mods.appliedenergistics2.attunement.add('thermaldynamics', tunnel('redstone')) -mods.appliedenergistics2.inscriber.recipeBuilder() - .input(item('minecraft:gold_ingot')) - .output(item('minecraft:diamond')) - .top(item('minecraft:diamond')) - .register() +// Cannon Ammo: +// Item and weight, where weight is a factor in how much damage is dealt. -mods.appliedenergistics2.inscriber.removeByOutput(item('appliedenergistics2:material:59')) -//mods.appliedenergistics2.inscriber.removeAll() +mods.appliedenergistics2.cannon_ammo.remove(item('minecraft:gold_nugget')) +// mods.appliedenergistics2.cannon_ammo.removeAll() +mods.appliedenergistics2.cannon_ammo.add(item('minecraft:clay'), 10000) // Grinder: // Converts an item into one item, with up to two additional items as chance byproducts after a number of turns. + +mods.appliedenergistics2.grinder.removeByInput(item('minecraft:gold_ingot')) +mods.appliedenergistics2.grinder.removeByOutput(item('minecraft:quartz')) +// mods.appliedenergistics2.grinder.removeAll() + mods.appliedenergistics2.grinder.recipeBuilder() .input(item('minecraft:clay')) - .output(item('minecraft:diamond'), item('minecraft:gold_ingot'), item('minecraft:diamond')) // Up to 3 outputs can be defined. The first is 100%, the second is chance1, the third is chance2. + .output(item('minecraft:diamond'), item('minecraft:gold_ingot'), item('minecraft:diamond')) .turns(1) - .chance1(0.5) // Optional float, defaults to 1.0f. - .chance2(0.3) // Optional float, defaults to 1.0f. + .chance1(0.5) + .chance2(0.3) .register() mods.appliedenergistics2.grinder.recipeBuilder() @@ -61,39 +49,33 @@ mods.appliedenergistics2.grinder.recipeBuilder() .turns(10) .register() -mods.appliedenergistics2.grinder.removeByInput(item('minecraft:gold_ingot')) -mods.appliedenergistics2.grinder.removeByOutput(item('minecraft:quartz')) -//mods.appliedenergistics2.grinder.removeAll() - - -// Spatial Storage Allowed Tile Entities: -// Either the class itself or its String name to add or remove from the Tile Entities allowed in Spatial Storage. -mods.appliedenergistics2.spatial.add('net.minecraft.tileentity.TileEntityStructure') // Adds the Structure Block to the allowed Tile Entities - -mods.appliedenergistics2.spatial.remove('net.minecraft.tileentity.TileEntityChest') // Removes the vanilla Chest from the allowed Tile Entities -//mods.appliedenergistics2.spatial.removeAll() +// Inscriber: +// Converts an item into another item, requiring either one or two additional items as either catalysts or ingredients. -// Cannon Ammo: (alias: Cannon) -// Item and weight, where weight is a factor in how much damage is dealt. -mods.appliedenergistics2.cannonammo.add(item('minecraft:clay'), 10000) +mods.appliedenergistics2.inscriber.removeByOutput(item('appliedenergistics2:material:59')) +// mods.appliedenergistics2.inscriber.removeAll() -mods.appliedenergistics2.cannonammo.remove(item('minecraft:gold_nugget')) -//mods.appliedenergistics2.cannonammo.removeAll() +mods.appliedenergistics2.inscriber.recipeBuilder() + .input(ore('blockGlass')) + .output(item('minecraft:diamond')) + .top(item('minecraft:diamond')) + .bottom(item('minecraft:diamond')) + .inscribe() + .register() +mods.appliedenergistics2.inscriber.recipeBuilder() + .input(item('minecraft:gold_ingot')) + .output(item('minecraft:diamond')) + .top(item('minecraft:diamond')) + .register() -// P2P Attunement: -// Item and tunnel type, modid and tunnel type, or capability and tunnel type to add to allowed items to convert P2P tunnels. -mods.appliedenergistics2.attunement.removeByTunnel(tunnel('item')) // Remove all ways to create the given tunnel -mods.appliedenergistics2.attunement.add(item('minecraft:clay'), tunnel('item')) // item + tunnel -mods.appliedenergistics2.attunement.remove(item('minecraft:lever'), tunnel('redstone')) // item + tunnel +// Spatial Storage Allowed Tile Entities: +// Either the class itself or its String name to add or remove from the Tile Entities allowed in Spatial Storage. -//mods.appliedenergistics2.attunement.remove('thermaldynamics', tunnel('fe_power')) // modid + tunnel -//mods.appliedenergistics2.attunement.add('thermaldynamics', tunnel('redstone')) // modid + tunnel +mods.appliedenergistics2.spatial.remove('net.minecraft.tileentity.TileEntityChest') +// mods.appliedenergistics2.spatial.removeAll() -// Must be imported via `appeng.capabilities.Capabilities` -//mods.appliedenergistics2.attunement.remove(Capabilities.FORGE_ENERGY, tunnel('fe_power')) // capability + tunnel -//mods.appliedenergistics2.attunement.add(Capabilities.FORGE_ENERGY, tunnel('item')) // capability + tunnel +mods.appliedenergistics2.spatial.add('net.minecraft.tileentity.TileEntityStructure') -//mods.appliedenergistics2.attunement.removeAll() diff --git a/examples/postInit/astral.groovy b/examples/postInit/astral.groovy deleted file mode 100644 index 4e7bcce57..000000000 --- a/examples/postInit/astral.groovy +++ /dev/null @@ -1,325 +0,0 @@ - -// MODS_LOADED: astralsorcery -println 'mod \'astralsorcery\' detected, running script' - -import net.minecraft.util.math.MathHelper - -// Constellation bracket handler: -// Major (Bright) Constellations: -constellation('aevitas') -constellation('armara') -constellation('discidia') -constellation('evorsio') -constellation('vicio') -// Weak (Dim) Constellations: -constellation('bootes') -constellation('fornax') -constellation('horologium') -constellation('lucerna') -constellation('mineralis') -constellation('octans') -constellation('pelotrio') -// Minor (Faint) Constellations: -constellation('alcara') -constellation('gelu') -constellation('ulteria') -constellation('vorux') - - -// ItemStack Mixin: -// When interacting with a rock crystal item, you can use the following methods to specify data about it. -item('astralsorcery:itemrockcrystalsimple') - .setSize(300) // Size is increased via sitting in liquid starlight and decreased by increasing cutting. From 0 - 400 for normal and 900 for Celestial. - .setPurity(50) // Purity is increased when at max size in liquid starlight, consuming the starlight and creating a new crystal. From 0-100. - .setCutting(50) // Cutting is increased by using the Grindstone with a rock crystal and decreased by increasing size. From 0-100. - .setFracturation(0) // Fracturation is increased by being used on a Ritual Pedestal, breaking at 100, and decreased by increasing size. From 0-100. - .setSizeOverride(-1) // Overrides the size value if anything other than -1 - .tuneTo(constellation('discidia')) // Major or Weak constellation, relevant for the Ritual Pedestal. - .setTrait(constellation('vorux')) // Dim constellation, relevant for the Ritual Pedestal. - - -// Perk Tree Config: -// Control the Perk level cap (between 1 and 100) and the XP formula per level. -mods.astralsorcery.perktreeconfig.setLevelCap(50) -// i = level number, prev = prior level xp cost. -mods.astralsorcery.perktreeconfig.setXpFunction{ int i, long prev -> prev + 1000L + MathHelper.lfloor(Math.pow(2.0, i / 2.0F + 3)) } - - -// Constellation: -// Create a custom constellation. -mods.astralsorcery.constellation.constellationBuilder() - .major() // Mutually exclusive, designates the constellation as major. - //.weak() // Mutually exclusive, designates the constellation as weak. - //.minor() // Mutually exclusive, designates the constellation as minor. - .name('square') - .color(0xE01903) // Optional int, color of the constellation. (Default based on major/weak/minor) - .connection(12, 2, 2, 2) // Any number of connections, draws the constellation - .connection(12, 12, 12, 2) - .connection(2, 12, 12, 12) - .connection(2, 2, 2, 12) - //.phase(MoonPhase.FULL) // Only required for minor constellations, MoonPhase... or [MoonPhase] - .register() - -mods.astralsorcery.constellation.remove(constellation('bootes')) -//mods.astralsorcery.constellation.removeAll() - - -// Adjust the enchantment or potion effects related to a constellation. -mods.astralsorcery.constellation.constellationMapEffectBuilder() - .constellation(constellation('square')) - .enchantmentEffect(enchantment('luck_of_the_sea'), 1, 3) - .potionEffect(potion('luck'), 1,2) - .register() - -mods.astralsorcery.constellation.removeConstellationMapEffect(constellation('discidia')) -//mods.astralsorcery.constellation.removeAllConstellationMapEffect() - -// Set the signature items used for a constellation's mantle or paper. -mods.astralsorcery.constellation.signatureItems() - .constellation(constellation('square')) - .addItem(ore('gemDiamond')) - .addItem(item('minecraft:water_bucket')) - .addItem(item('minecraft:rabbit_foot')) - .addItem(item('minecraft:fish')) - .register() - -mods.astralsorcery.constellation.removeSignatureItems(constellation('discidia')) -//mods.astralsorcery.constellation.removeAllSignatureItems() - - -// Perk Tree: -// Create a custom perk with a custom effect, at a given location -mods.astralsorcery.perktree.attributePerkBuilder() - .name("this_is_fun") - .point(28, 28) - .connection("astralsorcery:magnet_ats_reach") - .modifier(mods.astralsorcery.perktree.effectBuilder() - .modifier(3) - .mode(2) - .type("ATTR_TYPE_REACH")) - .register() - -mods.astralsorcery.perktree.movePerk(mods.astralsorcery.perktree.getPerk("astralsorcery:magnet_ats_reach"), 30, 30) - -mods.astralsorcery.perktree.remove("astralsorcery:magnet_ats_reach") - - -// Add custom research pages -mods.astralsorcery.research.researchBuilder() - .name('MY_TEST_RESEARCH') - .point(5,5) - .icon(item('minecraft:pumpkin')) - .discovery() - .page(mods.astralsorcery.research.pageBuilder().textPage('GROOVYSCRIPT.RESEARCH.PAGE.TEST')) - .page(mods.astralsorcery.research.pageBuilder().emptyPage()) - .connectionFrom('ALTAR1') - .register() - -mods.astralsorcery.research.researchBuilder() - .name('MY_TEST_RESEARCH2') - .point(5,5) - .icon(item('minecraft:pumpkin')) - .constellation() - .page(mods.astralsorcery.research.pageBuilder().textPage('GROOVYSCRIPT.RESEARCH.PAGE.TEST2')) - .page(mods.astralsorcery.research.pageBuilder().constellationRecipePage(item('minecraft:pumpkin'))) - .register() - -mods.astralsorcery.research.connectNodes('MY_TEST_RESEARCH2', 'ENHANCED_COLLECTOR') - -mods.astralsorcery.research.moveNode('SOOTYMARBLE', 5, 6) - -mods.astralsorcery.research.removeNode('CPAPER') - -mods.astralsorcery.research.disconnectNodes('MY_TEST_RESEARCH', 'ALTAR1') - - - -// Starlight Altar: -// Allows creation of shaped recipes in the Astral Sorcery Crafting Altar chain. -mods.astralsorcery.starlightaltar.discoveryRecipeBuilder() - .output(item('minecraft:water_bucket')) - .row(' ') - .row(' B ') - .row(' ') - .key('B', item('minecraft:bucket')) - .starlight(1) // Optional int, amount of starlight the recipe requires. Has a cap per tier of table at 1000, 2000, 4000, 8000 for Luminous, Starlight, Celestial, and Iridescent respectively. (Default 0) - .craftTime(10) // Optional int, how long the craft will take to complete in ticks. (Default 1) - .register() - -mods.astralsorcery.starlightaltar.constellationRecipeBuilder() - .output(item('minecraft:pumpkin')) - .matrix('ss ss', - 's s', - ' d ', - 's s', - 'ss ss') - .key('s', item('minecraft:pumpkin_seeds')) - .key('d', ore('dirt')) - .register() - -mods.astralsorcery.starlightaltar.traitRecipeBuilder() - .output(item('astralsorcery:itemrockcrystalsimple').setSize(300).setPurity(50).setCutting(50)) - .matrix('sssss', - 'sgggs', - 'sgdgs', - 'sgggs', - 'sssss') - .key('s', item('minecraft:pumpkin')) - .key('g', ore('treeLeaves')) - .key('d', item('minecraft:diamond_block')) - .outerInput(item('astralsorcery:blockmarble')) // Items which are places on nearby relays once the craft has started. - .outerInput(ore('ingotAstralStarmetal')) - .outerInput(fluid('astralsorcery.liquidstarlight') * 1000) - .outerInput(ore('treeSapling')) - .constellation(constellation('discidia')) - .register() - -mods.astralsorcery.starlightaltar.removeByOutput(item('astralsorcery:itemarchitectwand')) -//mods.astralsorcery.starlightaltar.removeAll() - - -// Lightwell: -// Converts an input item into fluid, with a chance at breaking every time fluid is produced. The amount of fluid produced per interval can be increased via starlight. -mods.astralsorcery.lightwell.recipeBuilder() - .catalyst(item('minecraft:stone')) - .output(fluid('astralsorcery.liquidstarlight')) - .productionMultiplier(1.0F) // Optional float, base amount of output produced per tick. (Default 0) - .shatterMultiplier(15.0F) // Optional float, how likely the catalyst is to shatter when producing fluid, with higher being less likely but never 0%. (Default 0) - .catalystColor(16725260) // Optional int, color code for particles. (Default null) - .register() - -mods.astralsorcery.lightwell.recipeBuilder() - .catalyst(item('minecraft:obsidian')) - .output(fluid('astralsorcery.liquidstarlight')) - .productionMultiplier(1.0F) - .shatterMultiplier(15.0F) - .register() - -mods.astralsorcery.lightwell.removeByCatalyst(item('minecraft:ice')) -mods.astralsorcery.lightwell.removeByInput(item('minecraft:packed_ice')) -mods.astralsorcery.lightwell.removeByOutput(fluid('lava')) -//mods.astralsorcery.lightwell.removeAll() - - -// Infusion Altar: -// Consumes buckets of Liquid Starlight when interacted with by a Resonating Wand to convert input items into output itemstacks after a time. -mods.astralsorcery.infusionaltar.recipeBuilder() - .input(item('minecraft:diamond')) - .output(item('minecraft:clay')) - .consumption(1f) // Optional float, chance to consume a bucket of Liquid Starlight from around the altar. (Default 0.05f) - .chalice(false) // Optional boolean, allows the recipe time to be reduced by *0.3 if a chalice is nearby if true. (Default true) - .consumeMultiple(true) // Optional boolean, when consuming liquid starlight, consume all 12 source blocks instead of just one. (Default false) - .time(10) // Optional integer, how long the recipe takes to complete in ticks. (Default 200) - .register() - -mods.astralsorcery.infusionaltar.recipeBuilder() - .input(item('minecraft:gold_ingot')) - .output(item('minecraft:clay')) - .register() - -mods.astralsorcery.infusionaltar.removeByInput(item('minecraft:diamond_ore')) -mods.astralsorcery.infusionaltar.removeByOutput(item('minecraft:iron_ingot')) -//mods.astralsorcery.infusionaltar.removeAll() - - -// Grindstone: -// Converts an item into an itemstack with a chance of getting twice the amount after right clicking the grindstone based on weight. -mods.astralsorcery.grindstone.recipeBuilder() - .input(ore('blockDiamond')) - .output(item('minecraft:clay')) - .weight(1) // Optional int, how likely to craft the recipe per right click (1/weight chance). (Default 0) - .secondaryChance(1.0F) // Optional int, how likely to double the output. (Default 0) - .register() - -mods.astralsorcery.grindstone.recipeBuilder() - .input(item('minecraft:stone')) - .output(item('minecraft:cobblestone')) - .weight(5) - .register() - -mods.astralsorcery.grindstone.removeByInput(item('minecraft:redstone_ore')) -mods.astralsorcery.grindstone.removeByOutput(ore('dustIron')) -//mods.astralsorcery.grindstone.removeAll() - - -// Light Transmutation: -// Converts an input Block or IBlockState into an output IBlockState after being sent a given amount of starlight, with the ability to require a specific constellation of starlight. -mods.astralsorcery.lighttransmutation.recipeBuilder() - .input(block('minecraft:stone')) - .output(block('astralsorcery:blockmarble')) - .cost(100.0) - .constellation(constellation('armara')) // Optional IWeakConstellation, a Major or Weak Constellation that the celestial crystal sending starlight must be attuned to. (Default: any) - .inputDisplayStack(item('minecraft:stone')) // Optional itemstack, what item to be displayed as input in JEI. (Default: input) - .outputDisplayStack(item('minecraft:dye:15').withNbt([display:[Name:'Marble']])) // Optional itemstack, what item to be displayed as output in JEI. (Default: output) - .register() - -mods.astralsorcery.lighttransmutation.recipeBuilder() - .input(blockstate('minecraft:pumpkin')) // Will only convert a pumpkin facing north. Block should be used instead if this behavior is undesired. - .output(blockstate('minecraft:diamond_block')) - .cost(0) - .register() - -mods.astralsorcery.lighttransmutation.removeByInput(blockstate('minecraft:sandstone')) -mods.astralsorcery.lighttransmutation.removeByInput(block('minecraft:netherrack')) -mods.astralsorcery.lighttransmutation.removeByOutput(blockstate('minecraft:cake')) -mods.astralsorcery.lighttransmutation.removeByOutput(block('minecraft:lapis_block')) -//mods.astralsorcery.lighttransmutation.removeAll() - - -// Chalice Interaction: -// When two chalices containing different fluids are placed nearby, fluid may be consumed to produce an output itemstack. -mods.astralsorcery.chaliceinteraction.recipeBuilder() - .output(item('astralsorcery:blockmarble')) - .fluidInput(fluid('water') * 10) - .fluidInput(fluid('astralsorcery.liquidstarlight') * 30) - .register() - -mods.astralsorcery.chaliceinteraction.removeByInput(fluid('water'), fluid('lava')) -//mods.astralsorcery.chaliceinteraction.removeByInput(fluid('astralsorcery.liquidstarlight')) -mods.astralsorcery.chaliceinteraction.removeByOutput(item('minecraft:ice')) -//mods.astralsorcery.chaliceinteraction.removeAll() - - -// Fountian: -// Adds virtual aquifers that can be accessed via the Evershifting Fountain's Necromantic Prime -mods.astralsorcery.fountain.chanceHelper() - .fluid(fluid('astralsorcery.liquidstarlight')) - .rarity(10000000) - .minimumAmount(4000000) - .variance(1000000) - .register() - -mods.astralsorcery.fountain.remove(fluid('lava')) -//mods.astralsorcery.fountain.removeAll() - - -// Mineralis Ritual Registry: -// Using a mineralis ritual will convert nearby stone blocks into random ores. -mods.astralsorcery.mineralisRitualRegistry.add(ore('blockDiamond'), 10000) - -mods.astralsorcery.mineralisRitualRegistry.remove(ore('oreDiamond')) -//mods.astralsorcery.mineralisRitualRegistry.removeAll() - - -// Aevitas Perk Registry: -// Having the Stone Enrichment perk will convert nearby stone blocks into random ores. -mods.astralsorcery.aevitasPerkRegistry.add(ore('blockDiamond'), 10000) - -mods.astralsorcery.aevitasPerkRegistry.remove(ore('oreDiamond')) -//mods.astralsorcery.aevitasPerkRegistry.removeAll() - - -// Trash Perk Registry: -// Having the Trash to Treasure perk turns items the player drops in the list defined in the config at 'perks/key_void_trash/DropList' into a chance at random ores. -mods.astralsorcery.trashPerkRegistry.add(ore('blockDiamond'), 10000) - -mods.astralsorcery.trashPerkRegistry.remove(ore('oreDiamond')) -//mods.astralsorcery.trashPerkRegistry.removeAll() - - -// Treasure Shrine Registry: -// When the block in the middle of a Treasure Shrine structure is broken, a random ore from this list will replace it until the Treasure Shrine is exhausted. -mods.astralsorcery.treasureShrineRegistry.add(ore('blockDiamond'), 10000) - -mods.astralsorcery.treasureShrineRegistry.remove(ore('oreDiamond')) -//mods.astralsorcery.treasureShrineRegistry.removeAll() diff --git a/examples/postInit/astralsorcery.groovy b/examples/postInit/astralsorcery.groovy new file mode 100644 index 000000000..18a777fc4 --- /dev/null +++ b/examples/postInit/astralsorcery.groovy @@ -0,0 +1,304 @@ + +// Auto generated groovyscript example file +// MODS_LOADED: astralsorcery + +import hellfirepvp.astralsorcery.common.constellation.MoonPhase +import net.minecraft.util.math.MathHelper + +println 'mod \'astralsorcery\' detected, running script' + +// Chalice Interaction: +// When two chalices containing different fluids are placed nearby, fluid may be consumed to produce an output itemstack. + +// mods.astralsorcery.chalice_interaction.removeByInput(fluid('astralsorcery.liquidstarlight')) +mods.astralsorcery.chalice_interaction.removeByInput(fluid('water'), fluid('lava')) +mods.astralsorcery.chalice_interaction.removeByOutput(item('minecraft:ice')) +// mods.astralsorcery.chalice_interaction.removeAll() + +mods.astralsorcery.chalice_interaction.recipeBuilder() + .output(item('astralsorcery:blockmarble')) + .fluidInput(fluid('water') * 10) + .fluidInput(fluid('astralsorcery.liquidstarlight') * 30) + .register() + + + +// Constellation: +// Create a custom Constellation. + +mods.astralsorcery.constellation.remove(constellation('bootes')) +mods.astralsorcery.constellation.removeConstellationMapEffect(constellation('discidia')) +mods.astralsorcery.constellation.removeSignatureItems(constellation('discidia')) +// mods.astralsorcery.constellation.removeAll() +// mods.astralsorcery.constellation.removeAllConstellationMapEffect() +// mods.astralsorcery.constellation.removeAllSignatureItems() + +mods.astralsorcery.constellation.constellationBuilder() + .major() + .name('square') + .color(0xE01903) + .connection(12, 2, 2, 2) + .connection(12, 12, 12, 2) + .connection(2, 12, 12, 12) + .connection(2, 2, 2, 12) + .register() + +mods.astralsorcery.constellation.constellationBuilder() + .minor() + .name('slow') + .connection(10, 5, 5, 5) + .connection(5, 10, 5, 5) + .connection(3, 3, 3, 3) + .phase(MoonPhase.FULL) + .register() + +mods.astralsorcery.constellation.constellationMapEffectBuilder() + .constellation(constellation('square')) + .enchantmentEffect(enchantment('minecraft:luck_of_the_sea'), 1, 3) + .potionEffect(potion('minecraft:luck'), 1, 2) + .register() + +mods.astralsorcery.constellation.signatureItems() + .constellation(constellation('square')) + .addItem(ore('gemDiamond')) + .addItem(item('minecraft:water_bucket')) + .addItem(item('minecraft:rabbit_foot')) + .addItem(item('minecraft:fish')) + .register() + + + +// Fountain: +// Adds virtual aquifers that can be accessed via the Evershifting Fountain's Necromantic Prime. + +mods.astralsorcery.fountain.remove(fluid('lava')) +// mods.astralsorcery.fountain.removeAll() + +mods.astralsorcery.fountain.chanceHelper() + .fluid(fluid('astralsorcery.liquidstarlight')) + .rarity(10000000) + .minimumAmount(4000000) + .variance(1000000) + .register() + + + +// Grindstone: +// Converts an item into an itemstack with a chance of getting twice the amount after right clicking the grindstone based +// on weight. + +mods.astralsorcery.grindstone.removeByInput(item('minecraft:redstone_ore')) +mods.astralsorcery.grindstone.removeByOutput(ore('dustIron')) +// mods.astralsorcery.grindstone.removeAll() + +mods.astralsorcery.grindstone.recipeBuilder() + .input(ore('blockDiamond')) + .output(item('minecraft:clay')) + .weight(1) + .secondaryChance(1.0F) + .register() + +mods.astralsorcery.grindstone.recipeBuilder() + .input(item('minecraft:stone')) + .output(item('minecraft:cobblestone')) + .weight(5) + .register() + + + +// Infusion Altar: +// Consumes buckets of Liquid Starlight when interacted with by a Resonating Wand to convert input items into output +// itemstacks after a time. + +mods.astralsorcery.infusion_altar.removeByInput(item('minecraft:diamond_ore')) +mods.astralsorcery.infusion_altar.removeByOutput(item('minecraft:iron_ingot')) +// mods.astralsorcery.infusion_altar.removeAll() + +mods.astralsorcery.infusion_altar.recipeBuilder() + .input(item('minecraft:diamond')) + .output(item('minecraft:clay')) + .consumption(1f) + .chalice(false) + .consumeMultiple(true) + .time(10) + .register() + +mods.astralsorcery.infusion_altar.recipeBuilder() + .input(item('minecraft:gold_ingot')) + .output(item('minecraft:clay')) + .register() + + +// Light Transmutation: +// Converts an input Block or IBlockState into an output IBlockState after being sent a given amount of starlight, with the +// ability to require a specific constellation of starlight. + +mods.astralsorcery.light_transmutation.removeByInput(block('minecraft:netherrack')) +mods.astralsorcery.light_transmutation.removeByInput(blockstate('minecraft:sandstone')) +mods.astralsorcery.light_transmutation.removeByOutput(block('minecraft:lapis_block')) +mods.astralsorcery.light_transmutation.removeByOutput(blockstate('minecraft:cake')) +// mods.astralsorcery.light_transmutation.removeAll() + +mods.astralsorcery.light_transmutation.recipeBuilder() + .input(block('minecraft:stone')) + .output(block('astralsorcery:blockmarble')) + .cost(100.0) + .constellation(constellation('armara')) + .inputDisplayStack(item('minecraft:stone')) + .outputDisplayStack(item('minecraft:dye:15').withNbt([display:[Name:'Marble']])) + .register() + +mods.astralsorcery.light_transmutation.recipeBuilder() + .input(blockstate('minecraft:pumpkin')) + .output(blockstate('minecraft:diamond_block')) + .cost(0) + .register() + + +// Lightwell: +// Converts an input item into fluid, with a chance at breaking every time fluid is produced. The amount of fluid produced +// per interval can be increased via starlight. + +mods.astralsorcery.lightwell.removeByCatalyst(item('minecraft:ice')) +mods.astralsorcery.lightwell.removeByInput(item('minecraft:packed_ice')) +mods.astralsorcery.lightwell.removeByOutput(fluid('lava')) +// mods.astralsorcery.lightwell.removeAll() + +mods.astralsorcery.lightwell.recipeBuilder() + .catalyst(item('minecraft:stone')) + .output(fluid('astralsorcery.liquidstarlight')) + .productionMultiplier(1.0F) + .shatterMultiplier(15.0F) + .catalystColor(16725260) + .register() + +mods.astralsorcery.lightwell.recipeBuilder() + .catalyst(item('minecraft:obsidian')) + .output(fluid('astralsorcery.liquidstarlight')) + .productionMultiplier(1.0F) + .shatterMultiplier(15.0F) + .register() + + + +// Perk Tree: +// Create a custom perk with a custom effect, at a given location. + +mods.astralsorcery.perk_tree.remove('astralsorcery:mec_inc_ms_2') + +mods.astralsorcery.perk_tree.movePerk(mods.astralsorcery.perktree.getPerk('astralsorcery:magnet_ats_reach'), 30, 30) + +// Perk Tree Config: +// Control the Perk level cap and XP formula. + +mods.astralsorcery.perk_tree_config.setLevelCap(50) +mods.astralsorcery.perk_tree_config.setXpFunction({ int i, long prev -> prev + 1000L + MathHelper.lfloor(Math.pow(2.0, i / 2.0F + 3)) }) + +// Research Pages: +// Add custom Research Pages to the Astral Sorcery Book. + +mods.astralsorcery.research.disconnectNodes('MY_TEST_RESEARCH', 'ALTAR1') +mods.astralsorcery.research.removeNode('CPAPER') + +mods.astralsorcery.research.researchBuilder() + .name('MY_TEST_RESEARCH') + .point(5,5) + .icon(item('minecraft:pumpkin')) + .discovery() + .page(mods.astralsorcery.research.pageBuilder().textPage('GROOVYSCRIPT.RESEARCH.PAGE.TEST')) + .page(mods.astralsorcery.research.pageBuilder().emptyPage()) + .connectionFrom('ALTAR1') + .register() + +mods.astralsorcery.research.researchBuilder() + .name('MY_TEST_RESEARCH2') + .point(5,5) + .icon(item('minecraft:pumpkin')) + .constellation() + .page(mods.astralsorcery.research.pageBuilder().textPage('GROOVYSCRIPT.RESEARCH.PAGE.TEST2')) + .page(mods.astralsorcery.research.pageBuilder().constellationRecipePage(item('minecraft:pumpkin'))) + .register() + + +mods.astralsorcery.research.connectNodes('MY_TEST_RESEARCH2', 'ENHANCED_COLLECTOR') +mods.astralsorcery.research.moveNode('SOOTYMARBLE', 5, 6) + +// Starlight Altar: +// Allows creation of shaped recipes in the Astral Sorcery Crafting Altar chain. + +// mods.astralsorcery.starlight_altar.removeAll() + +mods.astralsorcery.starlight_altar.discoveryRecipeBuilder() + .output(item('minecraft:water_bucket')) + .row(' ') + .row(' B ') + .row(' ') + .key('B', item('minecraft:bucket')) + .starlight(1) + .craftTime(10) + .register() + + +mods.astralsorcery.starlight_altar.constellationRecipeBuilder() + .output(item('minecraft:pumpkin')) + .matrix('ss ss', + 's s', + ' d ', + 's s', + 'ss ss') + .key('s', item('minecraft:pumpkin_seeds')) + .key('d', ore('dirt')) + .register() + +mods.astralsorcery.starlight_altar.traitRecipeBuilder() + .output(item('astralsorcery:itemrockcrystalsimple').setSize(300).setPurity(50).setCutting(50)) + .matrix('sssss', + 'sgggs', + 'sgdgs', + 'sgggs', + 'sssss') + .key('s', item('minecraft:pumpkin')) + .key('g', ore('treeLeaves')) + .key('d', item('minecraft:diamond_block')) + .outerInput(item('astralsorcery:blockmarble')) + .outerInput(ore('ingotAstralStarmetal')) + .outerInput(fluid('astralsorcery.liquidstarlight') * 1000) + .outerInput(ore('treeSapling')) + .constellation(constellation('discidia')) + .register() + + +// Aevitas Perk Registry: +// Having the Stone Enrichment perk will convert nearby stone blocks into random ores. + +mods.astralsorcery.aevitas_perk_registry.remove(ore('oreDiamond')) +// mods.astralsorcery.aevitas_perk_registry.removeAll() + +mods.astralsorcery.aevitas_perk_registry.add(ore('blockDiamond'), 10000) + +// Mineralis Ritual Registry: +// Using a mineralis ritual will convert nearby stone blocks into random ores. + +mods.astralsorcery.mineralis_ritual_registry.remove(ore('oreDiamond')) +// mods.astralsorcery.mineralis_ritual_registry.removeAll() + +mods.astralsorcery.mineralis_ritual_registry.add(ore('blockDiamond'), 10000) + +// Trash Perk Registry: +// Having the Trash to Treasure perk turns items the player drops in the list defined in the config at +// 'perks/key_void_trash/DropList' into a chance at random ores. + +mods.astralsorcery.trash_perk_registry.remove(ore('oreDiamond')) +// mods.astralsorcery.trash_perk_registry.removeAll() + +mods.astralsorcery.trash_perk_registry.add(ore('blockDiamond'), 10000) + +// Treasure Shrine Registry: +// When the block in the middle of a Treasure Shrine structure is broken, a random ore from this list will replace it until +// the Treasure Shrine is exhausted. + +mods.astralsorcery.treasure_shrine_registry.remove(ore('oreDiamond')) +// mods.astralsorcery.treasure_shrine_registry.removeAll() + +mods.astralsorcery.treasure_shrine_registry.add(ore('blockDiamond'), 10000) + diff --git a/examples/postInit/avaritia.groovy b/examples/postInit/avaritia.groovy index df543325d..c49104b6d 100644 --- a/examples/postInit/avaritia.groovy +++ b/examples/postInit/avaritia.groovy @@ -1,59 +1,58 @@ +// Auto generated groovyscript example file // MODS_LOADED: avaritia + println 'mod \'avaritia\' detected, running script' -// extreme crafting - -// remove by output -mods.avaritia.ExtremeCrafting.removeByOutput(item('avaritia:resource', 6)) - -// add shaped recipe with nested ingredient list -mods.avaritia.ExtremeCrafting.shapedBuilder() - .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'),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'),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'),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'),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'),item('minecraft:gold_ingot'),item('minecraft:gold_ingot'),item('minecraft:gold_ingot'),item('minecraft:gold_ingot')]]) - .output(item('minecraft:gold_block')) - .register() - -// add shaped recipe with key based input -mods.avaritia.ExtremeCrafting.shapedBuilder() - .output(item('minecraft:stone') * 64) - .matrix( - 'DLLLLLDDD', - ' DNIGIND', - 'DDDNIGIND', - ' DLLLLLD') - .key('D', item('minecraft:diamond')) - .key('L', item('minecraft:redstone')) - .key('N', item('minecraft:stone').reuse()) // stone will not be consumed in the recipe - .key('I', item('minecraft:iron_ingot')) - .key('G', item('minecraft:gold_ingot')) - .register() - -// add shapeless recipe with ingredient list -mods.avaritia.ExtremeCrafting.shapelessBuilder() - .output(item('minecraft:stone') * 64) - .input(item('minecraft:stone'), - item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'), - item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'), - item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'), - item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'), - item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone')) - .register() - -// Compressor - -// remove by output -mods.avaritia.Compressor.removeByOutput(item('avaritia:singularity', 0)) - -// add -mods.avaritia.Compressor.add(item('minecraft:nether_star'), item('minecraft:clay_ball'), 100) // the last number is the required input amount -mods.avaritia.Compressor.recipeBuilder() - .input(item('minecraft:clay_ball') * 100) // this also specifies the input amount (you should only specify 1) - .output(item('minecraft:nether_star')) - .inputCount(100) // required input amount - .register() +// Compressor: +// Converts any number of a single item into an output itemstack. + +mods.avaritia.compressor.removeByOutput(item('avaritia:singularity', 0)) +// mods.avaritia.compressor.removeAll() + +mods.avaritia.compressor.recipeBuilder() + .input(item('minecraft:clay_ball') * 100) + .output(item('minecraft:nether_star')) + .inputCount(100) + .register() + + +mods.avaritia.compressor.add(item('minecraft:nether_star'), item('minecraft:clay_ball'), 100) + +// Extreme Crafting: +// A normal crafting table, by 9x9 instead. + +mods.avaritia.extreme_crafting.removeByOutput(item('avaritia:resource', 6)) +// mods.avaritia.extreme_crafting.removeAll() + +mods.avaritia.extreme_crafting.shapedBuilder() + .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'),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'),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'),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'),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'),item('minecraft:gold_ingot'),item('minecraft:gold_ingot'),item('minecraft:gold_ingot'),item('minecraft:gold_ingot')]]) + .output(item('minecraft:gold_block')) + .register() + +mods.avaritia.extreme_crafting.shapedBuilder() + .output(item('minecraft:stone') * 64) + .matrix('DLLLLLDDD', + ' DNIGIND', + 'DDDNIGIND', + ' DLLLLLD') + .key('D', item('minecraft:diamond')) + .key('L', item('minecraft:redstone')) + .key('N', item('minecraft:stone').reuse()) + .key('I', item('minecraft:iron_ingot')) + .key('G', item('minecraft:gold_ingot')) + .register() + +mods.avaritia.extreme_crafting.shapelessBuilder() + .output(item('minecraft:stone') * 64) + .input(item('minecraft:stone'), item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'), item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'), item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'), item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'), item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone')) + .register() + + + diff --git a/examples/postInit/betterwithmods.groovy b/examples/postInit/betterwithmods.groovy new file mode 100644 index 000000000..3a4b5bc4f --- /dev/null +++ b/examples/postInit/betterwithmods.groovy @@ -0,0 +1,194 @@ + +// Auto generated groovyscript example file +// MODS_LOADED: betterwithmods + +println 'mod \'betterwithmods\' detected, running script' + +// Anvil Crafting: +// Similar to a normal crafting table, but 4x4 instead. + +mods.betterwithmods.anvil_crafting.removeByInput(item('minecraft:redstone')) +mods.betterwithmods.anvil_crafting.removeByOutput(item('betterwithmods:steel_block')) +// mods.betterwithmods.anvil_crafting.removeAll() + +mods.betterwithmods.anvil_crafting.shapedBuilder() + .output(item('minecraft:diamond') * 32) + .matrix([[item('minecraft:gold_ingot'),item('minecraft:gold_ingot'),item('minecraft:gold_ingot'),null], + [item('minecraft:gold_ingot'),item('minecraft:gold_ingot'),item('minecraft:gold_ingot'),null], + [item('minecraft:gold_ingot'),item('minecraft:gold_ingot'),item('minecraft:gold_ingot'),null], + [null,null,null,item('minecraft:gold_ingot').transform({ _ -> item('minecraft:diamond') })]]) + .register() + +mods.betterwithmods.anvil_crafting.shapedBuilder() + .output(item('minecraft:diamond')) + .matrix('BXXX') + .mirrored() + .key('B', item('minecraft:stone')) + .key('X', item('minecraft:gold_ingot')) + .register() + +mods.betterwithmods.anvil_crafting.shapelessBuilder() + .name(resource('example:anvil_clay')) + .output(item('minecraft:clay')) + .input([item('minecraft:cobblestone'), item('minecraft:gold_ingot')]) + .register() + + +// Cauldron: +// Converts a large number of items into other items, with the ability to require specific amounts of heat. + +mods.betterwithmods.cauldron.removeByInput(item('minecraft:gunpowder')) +mods.betterwithmods.cauldron.removeByOutput(item('minecraft:gunpowder')) +// mods.betterwithmods.cauldron.removeAll() + +mods.betterwithmods.cauldron.recipeBuilder() + .input(item('minecraft:clay')) + .output(item('minecraft:diamond')) + .heat(2) + .register() + +mods.betterwithmods.cauldron.recipeBuilder() + .input(item('minecraft:diamond')) + .output(item('minecraft:gold_ingot') * 16) + .ignoreHeat() + .register() + + +// Crucible: +// Converts a large number of items into other items, with the ability to require specific amounts of heat. + +mods.betterwithmods.crucible.removeByInput(item('minecraft:gunpowder')) +mods.betterwithmods.crucible.removeByOutput(item('minecraft:gunpowder')) +// mods.betterwithmods.crucible.removeAll() + +mods.betterwithmods.crucible.recipeBuilder() + .input(item('minecraft:clay')) + .output(item('minecraft:diamond')) + .heat(2) + .register() + +mods.betterwithmods.crucible.recipeBuilder() + .input(item('minecraft:diamond')) + .output(item('minecraft:gold_ingot') * 16) + .ignoreHeat() + .register() + + +// Heat: +// Creates new levels or adds new blocks to old heat levels. + +mods.betterwithmods.heat.add(4, item('minecraft:redstone_block'), item('minecraft:redstone_torch')) +mods.betterwithmods.heat.add(3, 'torch') + +// Filtered Hopper: +// Recipes for the Filtered Hopper to process. The filter targeted must allow the input item in to function. + +mods.betterwithmods.hopper.removeByInput(item('minecraft:gunpowder')) +mods.betterwithmods.hopper.removeByOutput(item('minecraft:gunpowder')) +// mods.betterwithmods.hopper.removeAll() + +mods.betterwithmods.hopper.recipeBuilder() + .name('betterwithmods:iron_bar') + .input(ore('sand')) + .output(item('minecraft:clay')) + .inWorldItemOutput(item('minecraft:gold_ingot')) + .register() + +mods.betterwithmods.hopper.recipeBuilder() + .name('betterwithmods:wicker') + .input(item('minecraft:clay')) + .inWorldItemOutput(item('minecraft:gold_ingot')) + .register() + + +// Hopper Filters: +// Items placed in the middle slot of the Filtered Hopper to restrict what is capable of passing through. + +mods.betterwithmods.hopper_filters.removeByFilter(item('minecraft:trapdoor')) +mods.betterwithmods.hopper_filters.removeByName('betterwithmods:ladder') +// mods.betterwithmods.hopper_filters.removeAll() + +mods.betterwithmods.hopper_filters.recipeBuilder() + .name('too_weak_to_stop') + .filter(item('minecraft:string')) + .register() + +mods.betterwithmods.hopper_filters.recipeBuilder() + .name('groovyscript:clay_only') + .filter(item('minecraft:clay')) + .input(item('minecraft:clay')) + .register() + + +// Kiln: +// Converts a block into up to three output itemstacks, with the ability to require specific amounts of heat. + +mods.betterwithmods.kiln.removeByInput(item('minecraft:end_stone')) +mods.betterwithmods.kiln.removeByOutput(item('minecraft:brick')) +// mods.betterwithmods.kiln.removeAll() + +mods.betterwithmods.kiln.recipeBuilder() + .input(item('minecraft:clay')) + .output(item('minecraft:diamond')) + .heat(2) + .register() + +mods.betterwithmods.kiln.recipeBuilder() + .input(item('minecraft:diamond_block')) + .output(item('minecraft:gold_ingot') * 16) + .ignoreHeat() + .register() + + +// Mill Stone: +// Converts input itemstacks into output itemstacks after being ground via rotation power for a given time. + +mods.betterwithmods.mill_stone.removeByInput(item('minecraft:netherrack')) +mods.betterwithmods.mill_stone.removeByOutput(item('minecraft:blaze_powder')) +// mods.betterwithmods.mill_stone.removeAll() + +mods.betterwithmods.mill_stone.recipeBuilder() + .input(item('minecraft:diamond')) + .output(item('minecraft:gold_ingot') * 16) + .register() + +mods.betterwithmods.mill_stone.recipeBuilder() + .input(item('minecraft:diamond_block')) + .output(item('minecraft:gold_ingot'), item('minecraft:gold_block'), item('minecraft:clay')) + .register() + + +// Saw: +// Converts a block into output itemstacks after being powered via rotation power. + +mods.betterwithmods.saw.removeByInput(item('minecraft:vine')) +mods.betterwithmods.saw.removeByOutput(item('minecraft:pumpkin')) +// mods.betterwithmods.saw.removeAll() + +mods.betterwithmods.saw.recipeBuilder() + .input(item('minecraft:diamond_block')) + .output(item('minecraft:gold_ingot') * 16) + .register() + + +// Turntable: +// Converts a block into an output block and up to two itemstacks after being powered via rotation power. + +mods.betterwithmods.turntable.removeByInput(item('betterwithmods:unfired_pottery')) +mods.betterwithmods.turntable.removeByOutput(item('minecraft:clay_ball')) +// mods.betterwithmods.turntable.removeAll() + +mods.betterwithmods.turntable.recipeBuilder() + .input(item('minecraft:gold_block')) + .outputBlock(blockstate('minecraft:clay')) + .output(item('minecraft:gold_ingot') * 5) + .rotations(5) + .register() + +mods.betterwithmods.turntable.recipeBuilder() + .input(item('minecraft:clay')) + .output(item('minecraft:gold_ingot')) + .rotations(2) + .register() + + diff --git a/examples/postInit/bloodmagic.groovy b/examples/postInit/bloodmagic.groovy index dc829d3f8..900b95491 100644 --- a/examples/postInit/bloodmagic.groovy +++ b/examples/postInit/bloodmagic.groovy @@ -1,102 +1,164 @@ +// Auto generated groovyscript example file // MODS_LOADED: bloodmagic + println 'mod \'bloodmagic\' detected, running script' +// Alchemy Array: +// Converts two items into an output itemstack by using Arcane Ashes in-world. Has a configurable texture for the +// animation. + +mods.bloodmagic.alchemy_array.removeByCatalyst(item('bloodmagic:slate:2')) +mods.bloodmagic.alchemy_array.removeByInput(item('bloodmagic:component:13')) +mods.bloodmagic.alchemy_array.removeByInputAndCatalyst(item('bloodmagic:component:7'), item('bloodmagic:slate:1')) +mods.bloodmagic.alchemy_array.removeByOutput(item('bloodmagic:sigil_void')) +// mods.bloodmagic.alchemy_array.removeAll() + +mods.bloodmagic.alchemy_array.recipeBuilder() + .input(item('minecraft:diamond')) + .catalyst(item('bloodmagic:slate:1')) + .output(item('minecraft:gold_ingot')) + .texture('bloodmagic:textures/models/AlchemyArrays/LightSigil.png') + .register() + +mods.bloodmagic.alchemy_array.recipeBuilder() + .input(item('minecraft:clay')) + .catalyst(item('minecraft:gold_ingot')) + .output(item('minecraft:diamond')) + .register() + + + +// Alchemy Table: +// Converts up to 6 input items into an output itemstack, with configurable time, minimum tier of Blood Orb required, and +// Life Essence drained from the Orb network. + +mods.bloodmagic.alchemy_table.removeByInput(item('minecraft:nether_wart'), item('minecraft:gunpowder')) +mods.bloodmagic.alchemy_table.removeByOutput(item('minecraft:sand')) +// mods.bloodmagic.alchemy_table.removeAll() + +mods.bloodmagic.alchemy_table.recipeBuilder() + .input(item('minecraft:diamond'), item('minecraft:diamond')) + .output(item('minecraft:clay')) + .ticks(100) + .minimumTier(2) + .syphon(500) + .register() + +mods.bloodmagic.alchemy_table.recipeBuilder() + .input(item('minecraft:diamond'), item('minecraft:diamond'), item('minecraft:gold_ingot'), item('minecraft:gold_ingot'), item('bloodmagic:slate'), item('bloodmagic:slate')) + .output(item('minecraft:clay')) + .time(2000) + .tier(5) + .drain(25000) + .register() + + // Blood Altar: -// Converts an input item into an output itemstack, draining life essence from the altar at a base rate and requiring at least a specific tier. -mods.bloodmagic.bloodaltar.recipeBuilder() +// Converts an input item into an output itemstack, draining life essence from the altar at a base rate and requiring at +// least a specific tier. + +mods.bloodmagic.blood_altar.removeByInput(item('minecraft:ender_pearl')) +mods.bloodmagic.blood_altar.removeByOutput(item('bloodmagic:slate:4')) +// mods.bloodmagic.blood_altar.removeAll() + +mods.bloodmagic.blood_altar.recipeBuilder() .input(item('minecraft:clay')) .output(item('minecraft:gold_ingot')) - .tier(0) // Optional int, required tier of the altar. Maximum of either 5 or 6 depending on the config general/enableTierSixEvenThoughThereIsNoContent. (Default 0) + .minimumTier(0) .drainRate(5) .syphon(10) .consumeRate(5) .register() -mods.bloodmagic.bloodaltar.recipeBuilder() +mods.bloodmagic.blood_altar.recipeBuilder() .input(item('minecraft:gold_ingot')) .output(item('minecraft:diamond')) - .minimumTier(3) + .tier(3) .drainRate(100) .syphon(50000) .consumeRate(500) .register() -mods.bloodmagic.bloodaltar.removeByInput(item('minecraft:ender_pearl')) -mods.bloodmagic.bloodaltar.removeByOutput(item('bloodmagic:slate:4')) -//mods.bloodmagic.bloodaltar.removeAll() -// Alchemy Array: -// Converts two items into an output itemstack by using Arcane Ashes in-world. Has a configurable texture for the animation. -mods.bloodmagic.alchemyarray.recipeBuilder() - .input(item('minecraft:diamond')) - .catalyst(item('bloodmagic:slate:1')) - .output(item('minecraft:gold_ingot')) - .texture('bloodmagic:textures/models/AlchemyArrays/LightSigil.png') // Optional String/ResourceLocation of a texture to use as the animation. (Default 'bloodmagic:textures/models/AlchemyArrays/WIPArray.png') - .register() +// Meteor: +// Throwing an input catalyst atop an activated Mark of the Falling Tower Ritual will spawn a meteor made of the given +// components, size, explosion strength, and Life Essence cost. -mods.bloodmagic.alchemyarray.recipeBuilder() - .input(item('minecraft:clay')) +mods.bloodmagic.meteor.remove(item('minecraft:diamond_block')) +mods.bloodmagic.meteor.removeByCatalyst(item('minecraft:iron_block')) +mods.bloodmagic.meteor.removeByInput(item('minecraft:gold_block')) +// mods.bloodmagic.meteor.removeAll() + +mods.bloodmagic.meteor.recipeBuilder() .catalyst(item('minecraft:gold_ingot')) - .output(item('minecraft:diamond')) + .component(ore('oreIron'), 10) + .component(ore('oreDiamond'), 10) + .component(ore('stone'), 70) + .radius(7) + .explosionStrength(10) + .cost(1000) + .register() + +mods.bloodmagic.meteor.recipeBuilder() + .catalyst(item('minecraft:clay')) + .component('blockClay', 10) + .radius(20) + .explosionStrength(20) + .register() + + + +// Sacrificial: +// How much Life Essence is gained when using the Sacrificial Dagger on a mob. + +mods.bloodmagic.sacrificial.remove(entity('minecraft:villager')) +mods.bloodmagic.sacrificial.remove(resource('minecraft:villager')) +mods.bloodmagic.sacrificial.remove('minecraft:villager') +// mods.bloodmagic.sacrificial.removeAll() + +mods.bloodmagic.sacrificial.recipeBuilder() + .entity('minecraft:enderman') + .value(1000) .register() -mods.bloodmagic.alchemyarray.removeByInput(item('bloodmagic:component:13')) -mods.bloodmagic.alchemyarray.removeByCatalyst(item('bloodmagic:slate:2')) -mods.bloodmagic.alchemyarray.removeByInputAndCatalyst(item('bloodmagic:component:7'), item('bloodmagic:slate:1')) -mods.bloodmagic.alchemyarray.removeByOutput(item('bloodmagic:sigil_void')) -//mods.bloodmagic.alchemyarray.removeAll() // Tartaric Forge: -// Converts up to 4 input items into an output itemstack, requiring a Tartaric gem with a minimum amount of souls, and consuming some. -mods.bloodmagic.tartaricforge.recipeBuilder() +// Converts up to 4 input items into an output itemstack, requiring a Tartaric gem with a minimum amount of souls, and +// consuming some. + +mods.bloodmagic.tartaric_forge.removeByInput(item('minecraft:gunpowder'), item('minecraft:redstone')) +mods.bloodmagic.tartaric_forge.removeByInput(item('minecraft:cauldron'), item('minecraft:stone'), item('minecraft:dye:4'), item('minecraft:diamond')) +mods.bloodmagic.tartaric_forge.removeByOutput(item('bloodmagic:demon_crystal')) +// mods.bloodmagic.tartaric_forge.removeAll() + +mods.bloodmagic.tartaric_forge.recipeBuilder() .input(item('minecraft:clay'), item('minecraft:clay'), item('minecraft:clay'), item('minecraft:clay')) .output(item('minecraft:gold_ingot')) - .drain(5) + .soulDrain(5) .minimumSouls(10) .register() -mods.bloodmagic.tartaricforge.recipeBuilder() +mods.bloodmagic.tartaric_forge.recipeBuilder() .input(item('minecraft:gold_ingot'), item('minecraft:clay')) .output(item('minecraft:diamond')) - .soulDrain(200) // Alias to drain + .drain(200) .minimumSouls(500) .register() -mods.bloodmagic.tartaricforge.removeByInput(item('minecraft:cauldron'), item('minecraft:stone'), item('minecraft:dye:4'), item('minecraft:diamond')) -mods.bloodmagic.tartaricforge.removeByInput(item('minecraft:gunpowder'), item('minecraft:redstone')) -mods.bloodmagic.tartaricforge.removeByOutput(item('bloodmagic:demon_crystal')) -//mods.bloodmagic.tartaricforge.removeAll() - - -// Alchemy Table: -// Converts up to 6 input items into an output itemstack, with configurable time, minimum tier of Blood Orb required, and Life Essence drained from the Orb network. -mods.bloodmagic.alchemytable.recipeBuilder() - .input(item('minecraft:diamond'), item('minecraft:diamond')) - .output(item('minecraft:clay')) - .ticks(100) - .minimumTier(2) // Optional int, tier of the Blood Orb inside the table. Maximum of either 5 or 6 depending on the config general/enableTierSixEvenThoughThereIsNoContent. (Default 0) - .syphon(500) - .register() -mods.bloodmagic.alchemytable.recipeBuilder() - .input(item('minecraft:diamond'), item('minecraft:diamond'), item('minecraft:gold_ingot'), item('minecraft:gold_ingot'), item('bloodmagic:slate'), item('bloodmagic:slate')) - .output(item('minecraft:clay')) - .time(2000) // Alias to ticks - .tier(5) // Alias to minimumTier - .drain(25000) // Alias to syphon - .register() -mods.bloodmagic.alchemytable.removeByInput(item('minecraft:nether_wart'), item('minecraft:gunpowder')) -mods.bloodmagic.alchemytable.removeByOutput(item('minecraft:sand')) -//mods.bloodmagic.alchemytable.removeAll() +// Tranquility: +// Blocks in the area around the Tranquility Altar provide tranquility up to the Altar's cap, with reduced effect the more +// of a particular type of Tranquility is provided. +mods.bloodmagic.tranquility.remove(block('minecraft:dirt'), 'EARTHEN') +mods.bloodmagic.tranquility.remove(blockstate('minecraft:netherrack'), 'FIRE') +// mods.bloodmagic.tranquility.removeAll() -// Tranquility: -// Blocks in the area around the Tranquility Altar provide tranquility up to the Altar's cap, with reduced effect the more of a particular type of Tranquility is provided. mods.bloodmagic.tranquility.recipeBuilder() .block(block('minecraft:obsidian')) .tranquility('LAVA') @@ -115,42 +177,5 @@ mods.bloodmagic.tranquility.recipeBuilder() .value(500) .register() -mods.bloodmagic.tranquility.remove(blockstate('minecraft:netherrack'), 'FIRE') -mods.bloodmagic.tranquility.remove(block('minecraft:dirt'), 'EARTHEN') -//mods.bloodmagic.tranquility.removeAll() -// Sacrificial: -// How much Life Essence is gained when using the Sacrificial Dagger on a mob. -mods.bloodmagic.sacrificial.recipeBuilder() - .entity('minecraft:enderman') - .value(1000) - .register() - -mods.bloodmagic.sacrificial.remove('minecraft:villager') -//mods.bloodmagic.sacrificial.removeAll() - - -// Meteor: -// Throwing an input catalyst atop an activated Mark of the Falling Tower Ritual will spawn a meteor made of the given components, size, explosion strength, and Life Essence cost. -mods.bloodmagic.meteor.recipeBuilder() - .catalyst(item('minecraft:gold_ingot')) - .component(ore('oreIron'), 10) - .component(ore('oreDiamond'), 10) - .component(ore('stone'), 70) - .radius(7) - .explosionStrength(10) - .cost(1000) // Optional int, Life Essence cost of the ritual. (Default 1000000) - .register() - -mods.bloodmagic.meteor.recipeBuilder() - .catalyst(item('minecraft:clay')) - .component('blockClay', 10) - .radius(20) - .explosionStrength(20) - .register() - -mods.bloodmagic.meteor.remove(item('minecraft:diamond_block')) -mods.bloodmagic.meteor.removeByInput(item('minecraft:gold_block')) -mods.bloodmagic.meteor.removeByCatalyst(item('minecraft:iron_block')) -//mods.bloodmagic.meteor.removeAll() diff --git a/examples/postInit/botania.groovy b/examples/postInit/botania.groovy index 19236bd21..05620fb4d 100644 --- a/examples/postInit/botania.groovy +++ b/examples/postInit/botania.groovy @@ -1,193 +1,198 @@ +// Auto generated groovyscript example file // MODS_LOADED: botania -println 'mod \'botania\' detected, running script' import net.minecraft.potion.PotionEffect import net.minecraft.util.text.TextFormatting -// Bracket Handlers -// Brew: -// Gets one of botania's unique brews. Default options: -brew('speed') -brew('strength') -brew('haste') -brew('healing') -brew('jumpBoost') -brew('regen') -brew('regenWeak') -brew('resistance') -brew('fireResistance') -brew('waterBreathing') -brew('invisibility') -brew('nightVision') -brew('absorption') - -brew('allure') -brew('soulCross') -brew('featherfeet') -brew('emptiness') -brew('bloodthirst') -brew('overload') -brew('clear') - -brew('warpWard') // Only if Thaumcraft is installed +println 'mod \'botania\' detected, running script' + +// Petal Apothecary: +// Converts item inputs into an item output consuming water and a seed. + +mods.botania.apothecary.removeByInput(ore('runeFireB')) +mods.botania.apothecary.removeByInputs(ore('petalYellow'), ore('petalBrown')) +mods.botania.apothecary.removeByOutput(item('botania:specialflower').withNbt(['type': 'puredaisy'])) +// mods.botania.apothecary.removeAll() + +mods.botania.apothecary.recipeBuilder() + .input(ore('blockGold'), ore('ingotIron'), item('minecraft:apple')) + .output(item('minecraft:golden_apple')) + .register() +// Brew Effect: +// Creates a custom brew, but not a recipe for the brew. + +// mods.botania.brew.removeAll() + +mods.botania.brew.brewBuilder() + .key('groovy_example_brew') + .name('Groovy Brew') + .color(0x00FFFF) + .cost(100) + .effect(new PotionEffect(potion('minecraft:strength'), 1800, 3), new PotionEffect(potion('minecraft:speed'), 1800, 2), new PotionEffect(potion('minecraft:weakness'), 3600, 1)) + .incense(true) + .bloodPendant(true) + .register() + + +// Brew Recipe: +// Converts a non-infused Managlass Vial, Alfglass Flask, Incense Stick, or Tainted Blood Pendant into one infused to hold +// the given brew at the cost of item inputs and mana. + +mods.botania.brew_recipe.removeByInput(item('minecraft:iron_ingot')) +mods.botania.brew_recipe.removeByOutput(brew('allure')) +mods.botania.brew_recipe.removeByOutput('speed') +// mods.botania.brew_recipe.removeAll() + +mods.botania.brew_recipe.recipeBuilder() + .input(item('minecraft:clay'), ore('ingotGold'), ore('gemDiamond')) + .brew(brew('absorption')) + .register() + + // Elven Trade: // Convert in any number of item inputs into an item output. -def recipeElvenTrade = mods.botania.elventrade.recipeBuilder() + +mods.botania.elven_trade.removeByInputs(ore('ingotManasteel')) +mods.botania.elven_trade.removeByOutputs(item('botania:dreamwood')) +// mods.botania.elven_trade.removeAll() + +mods.botania.elven_trade.recipeBuilder() .input(ore('ingotGold'), ore('ingotIron')) .output(item('botania:manaresource:7')) .register() -mods.botania.elventrade.removeByInputs(ore('ingotManasteel')) -mods.botania.elventrade.removeByOutputs(item('botania:dreamwood')) -//mods.botania.elventrade.removeAll() -// Mana Infusion -// Toss an item into a mana pool with an optional catalyst blockstate below the pool. -def recipeInfusion = mods.botania.manainfusion.recipeBuilder() - .input(ore('ingotGold')) - .output(item('botania:manaresource', 1)) - .mana(500) - .catalyst(blockstate('minecraft:stone')) - .register() -mods.botania.manainfusion.removeByInput(item('minecraft:ender_pearl')) -mods.botania.manainfusion.removeByCatalyst(blockstate('botania:alchemycatalyst')) -mods.botania.manainfusion.removeByOutput(item('botania:managlass')) -//mods.botania.manainfusion.removeAll() +// Magnet: +// Add or remove items from the magnet blacklist -// Pure Daisy: -// Convert a given block to another blockstate after a period of time -mods.botania.puredaisy.recipeBuilder() - .input(ore('plankWood')) // input must be a Block, IBlockState, Oredict, or a String representing an oredict - .output(blockstate('minecraft:clay')) - .time(5) - .register() -mods.botania.puredaisy.add(blockstate('minecraft:iron_block'), blockstate('minecraft:gold_block'), 20) +mods.botania.magnet.addToBlacklist(item('minecraft:diamond')) -mods.botania.puredaisy.removeByInput(blockstate('minecraft:water')) -mods.botania.puredaisy.removeByInput(ore('logWood')) -mods.botania.puredaisy.removeByOutput(blockstate('botania:livingrock')) -//mods.botania.puredaisy.removeAll() +// Mana Infusion: +// Toss an item into a mana pool with an optional catalyst blockstate below the pool. +mods.botania.mana_infusion.removeByCatalyst(blockstate('botania:alchemycatalyst')) +mods.botania.mana_infusion.removeByInput(item('minecraft:ender_pearl')) +mods.botania.mana_infusion.removeByOutput(item('botania:managlass')) +// mods.botania.mana_infusion.removeAll() -// Petal Apothecary -// Converts item inputs into an item output consuming water and a seed. -def recipePetal = mods.botania.apothecary.recipeBuilder() - .input(ore('blockGold'), ore('ingotIron'), item('minecraft:apple')) - .output(item('minecraft:golden_apple')) +mods.botania.mana_infusion.recipeBuilder() + .input(ore('ingotGold')) + .output(item('botania:manaresource', 1)) + .mana(500) + .catalyst(blockstate('minecraft:stone')) .register() -mods.botania.apothecary.removeByInput(ore('runeFireB')) -mods.botania.apothecary.removeByInputs(ore('petalYellow'), ore('petalBrown')) -mods.botania.apothecary.removeByOutput(item('botania:specialflower').withNbt(["type": "puredaisy"])) -//mods.botania.apothecary.removeAll() + // Orechid: -// Converts stone blocks into one of a few ore blocks at the cost of mana -mods.botania.orechid.add(ore('oreEmerald'), 1350) +// Converts stone blocks into one of a few ore blocks at the cost of mana. -mods.botania.orechid.removeByOutput(ore('oreEmerald')) +// mods.botania.orechid.removeByOutput(ore('oreQuartz')) +// mods.botania.orechid.removeByOutput(ore('oreEmerald')) mods.botania.orechid.removeByOutput('oreCoal') -//mods.botania.orechid.removeAll() +// mods.botania.orechid.removeAll() + +mods.botania.orechid.add(ore('blockGold'), 1800) +mods.botania.orechid.add(ore('oreEmerald'), 1350) // Orechid Ignem: -// Converts netherrack blocks into one of a few ore blocks at the cost of mana -mods.botania.orechidignem.add(ore('blockGold'), 1800) +// Converts netherrack blocks into one of a few ore blocks at the cost of mana. -mods.botania.orechidignem.removeByOutput(ore('oreQuartz')) -//mods.botania.orechidignem.removeByOutput('oreQuartz') -//mods.botania.orechidignem.removeAll() +// mods.botania.orechid_ignem.removeByOutput(ore('oreQuartz')) +// mods.botania.orechid_ignem.removeByOutput(ore('oreEmerald')) +mods.botania.orechid_ignem.removeByOutput('oreQuartz') +// mods.botania.orechid_ignem.removeAll() -// Magnet: -// Add or remove items from the magnet blacklist. -mods.botania.magnet.addToBlacklist(item('minecraft:diamond')) +mods.botania.orechid_ignem.add(ore('blockGold'), 1800) +mods.botania.orechid_ignem.add(ore('oreEmerald'), 1350) -// Brew Effect: -// Creates a custom brew, but not a recipe for the brew. -mods.botania.brew.brewBuilder() - .key('groovy_example_brew') // Must be a unique key - .name('Groovy Brew') - .color(0x00FFFF) // Optional, default 0xFFFFFF - .cost(100) // Alias 'mana' - .effect(new PotionEffect(potion('strength'), 1800, 3), - new PotionEffect(potion('speed'), 1800, 2), - new PotionEffect(potion('weakness'), 3600, 1)) - .incense(true) // Optional, default true. Controls if the Incense Stick can be infused - .bloodPendant(true) // Optional, default true. Controls if the Tainted Blood Pendant can be infused - .register() +// Pure Daisy: +// Convert a given block to another blockstate after a period of time. -// Brew Recipe: -// Converts a non-infused Managlass Vial, Alfglass Flask, Incense Stick, or Tainted Blood Pendant into one infused to hold the given brew at the cost of item inputs and mana. -def recipeBrewing = mods.botania.brewrecipe.recipeBuilder() - .input(item('minecraft:clay'), ore('ingotGold'), ore('gemDiamond')) - .brew(brew('absorption')) // Alias 'output' +mods.botania.pure_daisy.removeByInput(blockstate('minecraft:water')) +mods.botania.pure_daisy.removeByInput(ore('logWood')) +mods.botania.pure_daisy.removeByOutput(blockstate('botania:livingrock')) +// mods.botania.pure_daisy.removeAll() + +mods.botania.pure_daisy.recipeBuilder() + .input(ore('plankWood')) + .output(blockstate('minecraft:clay')) + .time(5) .register() -mods.botania.brewrecipe.removeByInput(item('minecraft:iron_ingot')) -mods.botania.brewrecipe.removeByOutput('speed') -mods.botania.brewrecipe.removeByOutput(brew('allure')) -//mods.botania.brewrecipe.removeAll() + // Rune Altar: -// Converts a items inputs into an item ouput at the cost of mana when a Livingrock item is thrown atop the altar and right clicked with a Wand of the Forest -def recipeRune = mods.botania.runealtar.recipeBuilder() +// Converts a items inputs into an item ouput at the cost of mana when a Livingrock item is thrown atop the altar and right +// clicked with a Wand of the Forest. + +mods.botania.rune_altar.removeByInput(ore('runeEarthB')) +mods.botania.rune_altar.removeByInputs(ore('feather'), ore('string')) +mods.botania.rune_altar.removeByOutput(item('botania:rune:1')) +// mods.botania.rune_altar.removeAll() + +mods.botania.rune_altar.recipeBuilder() .input(ore('gemEmerald'), item('minecraft:apple')) .output(item('minecraft:diamond')) .mana(500) .register() -mods.botania.runealtar.removeByInput(ore('runeEarthB')) -mods.botania.runealtar.removeByInputs(ore('feather'), ore('string')) -mods.botania.runealtar.removeByOutput(item('botania:rune:1')) -//mods.botania.runealtar.removeAll() -// Knowledge: -// Creates a new type of knowledge that Lexica Botania entries may be gated with. -// Can only be created, format id, color, autoUnlock -def newType = mods.botania.knowledge.add('newType', TextFormatting.RED, true) +// Lexicon Knowledge: +// Creates a new type of knowledge that Lexica Botania entries may be gated with. Can only be created. -// Lexicon: -// Manipulate the Lexica Botania. +def newType = mods.botania.knowledge.add('newType', TextFormatting.RED, true) +// Lexicon Category: // Category creates a new entry on the front page of the Lexica Botania. -mods.botania.lexicon.category.add('test', resource('minecraft:textures/items/apple.png')) -mods.botania.lexicon.category.add('first', resource('minecraft:textures/items/clay_ball.png'), 100) -mods.botania.lexicon.category.remove('botania.category.alfhomancy') -mods.botania.lexicon.category.removeCategory('botania.category.misc') -//mods.botania.lexicon.category.removeAll() +mods.botania.category.remove('botania.category.alfhomancy') +mods.botania.category.removeCategory('botania.category.misc') +// mods.botania.category.removeAll() + +mods.botania.category.add('test', resource('minecraft:textures/items/apple.png')) +mods.botania.category.add('first', resource('minecraft:textures/items/clay_ball.png'), 100) + +// Lexicon Page: +// Page creates a new page to be used in entries. + +mods.botania.page.removeByEntry('botania.entry.runeAltar') +// mods.botania.page.removeAll() +// mods.botania.page.createBrewingPage('groovy.exampleBrewingPage', 'bottomText', 'bottomText', mods.botania.brewrecipe.recipeBuilder().input(item('minecraft:clay'), ore('ingotGold'), ore('gemDiamond')).brew(brew('absorption')).register()) +mods.botania.page.createCraftingPage('groovy.exampleCraftingPage', 'minecraft:clay') +// mods.botania.page.createElvenTradePage('groovy.exampleElvenTradePage', mods.botania.elventrade.recipeBuilder().input(ore('ingotGold'), ore('ingotIron')).output(item('botania:manaresource:7')).register()) +mods.botania.page.createEntityPage('groovy.exampleEntityPage', 5, entity('minecraft:wither_skeleton')) +mods.botania.page.createEntityPage('groovy.exampleEntityPage', 100, 'minecraft:wither_skeleton') +mods.botania.page.createImagePage('groovy.exampleImagePage', 'minecraft:textures/items/apple.png') +// mods.botania.page.createInfusionPage('groovy.exampleInfusionPage', mods.botania.manainfusion.recipeBuilder().input(ore('ingotGold')).output(item('botania:manaresource', 1)).mana(500).catalyst(blockstate('minecraft:stone')).register()) +mods.botania.page.createLoreTextPage('groovy.exampleLoreTextPage') +// mods.botania.page.createPetalPage('groovy.examplePetalPage', mods.botania.apothecary.recipeBuilder().input(ore('blockGold'), ore('ingotIron'), item('minecraft:apple')).output(item('minecraft:golden_apple')).register()) +// mods.botania.page.createRunePage('groovy.exampleRunePage', mods.botania.runealtar.recipeBuilder().input(ore('gemEmerald'), item('minecraft:apple')).output(item('minecraft:diamond')).mana(500).register()) +mods.botania.page.createTextPage('groovy.exampleTextPage') + +// Lexicon Entry: // Entry creates a new entry in a given category. -mods.botania.lexicon.entry.entryBuilder() + +mods.botania.entry.remove('botania.entry.flowers') +mods.botania.entry.removeEntry('botania.entry.apothecary') +// mods.botania.entry.removeAll() + +mods.botania.entry.entryBuilder() .name('test_entry') .icon(ore('blockIron')) .category('test') - .knowledgeType(newType) // Locks entry behind the given knowledge type. Also colors the entry name. - .page( - // Page creates a new page to be used in entries. - mods.botania.lexicon.page.createTextPage('groovy.exampleTextPage'), // looks for localization at "groovy.exampleTextPage" - mods.botania.lexicon.page.createLoreTextPage('groovy.exampleLoreTextPage'), - mods.botania.lexicon.page.createImagePage('groovy.exampleImagePage', 'minecraft:textures/items/apple.png'), - mods.botania.lexicon.page.createEntityPage('groovy.exampleEntityPage', 100, 'minecraft:wither_skeleton'), - mods.botania.lexicon.page.createEntityPage('groovy.exampleEntityPage', 5, entity('minecraft:wither_skeleton')), - mods.botania.lexicon.page.createCraftingPage('groovy.exampleCraftingPage', 'minecraft:clay'), - mods.botania.lexicon.page.createBrewingPage('groovy.exampleBrewingPage', 'bottomText', recipeBrewing), - mods.botania.lexicon.page.createInfusionPage('groovy.exampleInfusionPage', recipeInfusion), - mods.botania.lexicon.page.createRunePage('groovy.exampleRunePage', recipeRune), - mods.botania.lexicon.page.createPetalPage('groovy.examplePetalPage', recipePetal), - mods.botania.lexicon.page.createElvenTradePage('groovy.exampleElvenTradePage', recipeElvenTrade)) + .knowledgeType(newType) + .page(mods.botania.lexicon.page.createTextPage('groovy.exampleTextPage')) .register() -mods.botania.lexicon.entry.remove('botania.entry.flowers') -mods.botania.lexicon.entry.removeEntry('botania.entry.apothecary') -//mods.botania.lexicon.entry.removeAll() -// cont. from Page -mods.botania.lexicon.page.removeByEntry('botania.entry.runeAltar') -//mods.botania.lexicon.page.removeAll() + + diff --git a/examples/postInit/chisel.groovy b/examples/postInit/chisel.groovy index eb7dfd689..aa3284ac5 100644 --- a/examples/postInit/chisel.groovy +++ b/examples/postInit/chisel.groovy @@ -1,26 +1,21 @@ +// Auto generated groovyscript example file // MODS_LOADED: chisel + println 'mod \'chisel\' detected, running script' -// Carving -mods.chisel.carving.addGroup('demo') -mods.chisel.carving.removeGroup('blockDiamond') +// Carving: +// Sets a group of items any item can be converted between freely, in world and in a GUI +// mods.chisel.carving.removeAll() +mods.chisel.carving.removeGroup('blockDiamond') mods.chisel.carving.removeVariation('antiblock', item('chisel:antiblock:3')) mods.chisel.carving.removeVariation('antiblock', item('chisel:antiblock:15')) -mods.chisel.carving.addVariation('demo', item('minecraft:diamond_block')) + +mods.chisel.carving.addGroup('demo') mods.chisel.carving.addVariation('demo', item('chisel:antiblock:3')) mods.chisel.carving.addVariation('demo', item('minecraft:sea_lantern')) +mods.chisel.carving.addVariation('demo', item('minecraft:diamond_block')) -// Set the sound of the Variation -mods.chisel.carving.setSound('demo', sound('block.glass.break')) - -// You cannot addVariation/removeVariation to chisel groups based on the oredict, you have to modify the oredict directly. -oredict.add('blockCoal', item('chisel:antiblock:15')) -oredict.remove('blockCoal', item('minecraft:coal_block')) +mods.chisel.carving.setSound('demo', sound('minecraft:block.glass.break')) -// Can also run multiple operations on a group, creating the group if it didnt exist prior: -mods.chisel.carving.carvingGroup('valentines') - .remove(item('chisel:valentines'), item('chisel:valentines:1'), item('chisel:valentines:2'), item('chisel:valentines:3')) - .add(item('minecraft:grass'), item('minecraft:diamond_ore')) - .sound(sound('block.anvil.destroy')) diff --git a/examples/postInit/compactmachines.groovy b/examples/postInit/compactmachines.groovy deleted file mode 100644 index e1edc04df..000000000 --- a/examples/postInit/compactmachines.groovy +++ /dev/null @@ -1,51 +0,0 @@ - -// MODS_LOADED: compactmachines3 -println 'mod \'compactmachines3\' detected, running script' - -// Miniaturization: -// Consumes a 3d structure in-world based on keys when an item is thrown into the field. -mods.compactmachines.miniaturization.recipeBuilder() - .name('diamond_rectangle') // Optional, String - .input(item('minecraft:clay')) - .output(item('minecraft:clay')) - .symmetrical() // Indicates that the recipe does not have to test all 4 rotations to determine if the multiblock is valid - .ticks(10) // Alias: duration, default 100 - .shape([['www', 'www']]) // Shape is a List> - .key('w', blockstate('minecraft:diamond_block')) - // character, blockstate, nbt, metadata-sensitive (default true), display item - //.key('w', blockstate('minecraft:diamond_block'), null, true, item('minecraft:diamond') * 7) - .register() - -mods.compactmachines.miniaturization.recipeBuilder() - .name('groovy_rocket') // Optional, String - .input(item('minecraft:diamond')) - .output(item('minecraft:clay') * 64) - .symmetrical() - .ticks(5400) - // both ` ` and `_` are reserved for empty space, and cannot be used as keys - .key('a', blockstate('minecraft:stained_glass:0')) - .key('b', blockstate('minecraft:stained_glass:1')) - .key('c', blockstate('minecraft:stained_glass:2')) - .key('d', blockstate('minecraft:stained_glass:3')) - .key('e', blockstate('minecraft:diamond_block')) - .key('f', blockstate('minecraft:stained_glass:5')) - .key('g', blockstate('minecraft:stained_glass:6')) // Note: More than 6 keys results in incorrect displays in JEI - .layer(" ", " ", " a ", " aaa ", " a ", " ", " ") // layer adds String... to the shape structure - .layer(" ", " b ", " aaa ", " baaab ", " aaa ", " b ", " ") // adds layers in descending Y order - .layer(" ", " c ", " cac ", " caeac ", " cac ", " c ", " ") - .layer(" ", " a ", " aaa ", " aaeaa ", " aaa ", " a ", " ") - .layer(" ", " a ", " aaa ", " aaeaa ", " aaa ", " a ", " ") - .layer(" ", " a ", " aaa ", " aaeaa ", " aaa ", " a ", " ") - .layer(" ", " g ", " cac ", " caeac ", " cac ", " f ", " ") - .layer(" ", " a ", " aaa ", " aaeaa ", " aaa ", " a ", " ") - .layer(" ", " a ", " aaa ", " aaeaa ", " aaa ", " a ", " ") - .layer(" ", " a ", " aaa ", " aaeaa ", " aaa ", " a ", " ") - .layer(" ", " c ", " cac ", " caeac ", " cac ", " c ", " ") - .layer(" ", " a ", " aaa ", " aaaaa ", " aaa ", " a ", " ") - .layer(" a ", " ccc ", " cdddc ", "acdddca", " cdddc ", " ccc ", " a ") - .register() - -mods.compactmachines.miniaturization.removeByInput(item('minecraft:ender_pearl')) -mods.compactmachines.miniaturization.removeByCatalyst(item('minecraft:redstone')) -mods.compactmachines.miniaturization.removeByOutput(item('compactmachines3:machine:3')) -//mods.compactmachines.miniaturization.removeAll() diff --git a/examples/postInit/compactmachines3.groovy b/examples/postInit/compactmachines3.groovy new file mode 100644 index 000000000..de9baf75a --- /dev/null +++ b/examples/postInit/compactmachines3.groovy @@ -0,0 +1,132 @@ + +// Auto generated groovyscript example file +// MODS_LOADED: compactmachines3 + +println 'mod \'compactmachines3\' detected, running script' + +// Miniaturization: +// Consumes a 3d structure in-world based on keys when an item is thrown into the field. + +mods.compactmachines3.miniaturization.removeByCatalyst(item('minecraft:redstone')) +mods.compactmachines3.miniaturization.removeByInput(item('minecraft:ender_pearl')) +mods.compactmachines3.miniaturization.removeByOutput(item('compactmachines3:machine:3')) +// mods.compactmachines3.miniaturization.removeAll() + +mods.compactmachines3.miniaturization.recipeBuilder() + .name('diamond_rectangle') + .input(item('minecraft:clay')) + .output(item('minecraft:clay')) + .symmetrical() + .ticks(10) + .shape([['www', + 'www']]) + .key('w', blockstate('minecraft:diamond_block')) + .register() + +mods.compactmachines3.miniaturization.recipeBuilder() + .name('groovy_rocket') + .input(item('minecraft:diamond')) + .output(item('minecraft:clay') * 64) + .symmetrical() + .ticks(5400) + .key('a', blockstate('minecraft:stained_glass:0')) + .key('b', blockstate('minecraft:stained_glass:1')) + .key('c', blockstate('minecraft:stained_glass:2')) + .key('d', blockstate('minecraft:stained_glass:3')) + .key('e', blockstate('minecraft:diamond_block')) + .key('f', blockstate('minecraft:stained_glass:5')) + .key('g', blockstate('minecraft:stained_glass:6')) + .layer(' ', + ' ', + ' a ', + ' aaa ', + ' a ', + ' ', + ' ') + .layer(' ', + ' b ', + ' aaa ', + ' baaab ', + ' aaa ', + ' b ', + ' ') + .layer(' ', + ' c ', + ' cac ', + ' caeac ', + ' cac ', + ' c ', + ' ') + .layer(' ', + ' a ', + ' aaa ', + ' aaeaa ', + ' aaa ', + ' a ', + ' ') + .layer(' ', + ' a ', + ' aaa ', + ' aaeaa ', + ' aaa ', + ' a ', + ' ') + .layer(' ', + ' a ', + ' aaa ', + ' aaeaa ', + ' aaa ', + ' a ', + ' ') + .layer(' ', + ' g ', + ' cac ', + ' caeac ', + ' cac ', + ' f ', + ' ') + .layer(' ', + ' a ', + ' aaa ', + ' aaeaa ', + ' aaa ', + ' a ', + ' ') + .layer(' ', + ' a ', + ' aaa ', + ' aaeaa ', + ' aaa ', + ' a ', + ' ') + .layer(' ', + ' a ', + ' aaa ', + ' aaeaa ', + ' aaa ', + ' a ', + ' ') + .layer(' ', + ' c ', + ' cac ', + ' caeac ', + ' cac ', + ' c ', + ' ') + .layer(' ', + ' a ', + ' aaa ', + ' aaaaa ', + ' aaa ', + ' a ', + ' ') + .layer(' a ', + ' ccc ', + ' cdddc ', + 'acdddca', + ' cdddc ', + ' ccc ', + ' a ') + .register() + + diff --git a/examples/postInit/draconicevolution.groovy b/examples/postInit/draconicevolution.groovy index 96b64dfe7..670714baf 100644 --- a/examples/postInit/draconicevolution.groovy +++ b/examples/postInit/draconicevolution.groovy @@ -1,15 +1,22 @@ +// Auto generated groovyscript example file // MODS_LOADED: draconicevolution + println 'mod \'draconicevolution\' detected, running script' // Fusion: -// Consumes items and power from up to 54 pedestals of at least a given tier pointing towards a Fusion Crafting Core containing a catalyst to produce an output item. +// Consumes items and power from up to 54 pedestals of at least a given tier pointing towards a Fusion Crafting Core +// containing a catalyst to produce an output item. + +mods.draconicevolution.fusion.removeByCatalyst(item('draconicevolution:chaos_shard')) +// mods.draconicevolution.fusion.removeAll() + mods.draconicevolution.fusion.recipeBuilder() .catalyst(item('minecraft:diamond')) .input(ore('ingotIron'), ore('ingotIron'), item('minecraft:dirt'), item('minecraft:grass'), item('minecraft:grass'), item('minecraft:dirt'), ore('ingotGold'), ore('ingotGold')) .output(item('minecraft:nether_star')) - .energy(10) // Energy cost per item. Optional, default 1000000 (1 million) - .tier(1) // Optional, default 0 (basic) + .energy(10) + .tier(1) .register() mods.draconicevolution.fusion.recipeBuilder() @@ -17,12 +24,7 @@ mods.draconicevolution.fusion.recipeBuilder() .input(item('minecraft:clay'), item('minecraft:clay'), item('minecraft:clay'), item('minecraft:clay')) .output(item('minecraft:nether_star')) .energy(100000) - //.tierNormal() // Alias for tier(0) - //.tierBasic() // Alias for tier(0) - //.tierWyvern() // Alias for tier(1) - //.tierDraconic() // Alias for tier(2) - .tierChaotic() // Alias for tier(3) + .tierChaotic() .register() -mods.draconicevolution.fusion.removeByCatalyst(item('draconicevolution:chaos_shard')) -//mods.draconicevolution.fusion.removeAll() + diff --git a/examples/postInit/enderio.groovy b/examples/postInit/enderio.groovy index 752e25e9d..d21f7893d 100644 --- a/examples/postInit/enderio.groovy +++ b/examples/postInit/enderio.groovy @@ -1,178 +1,195 @@ +// Auto generated groovyscript example file // MODS_LOADED: enderio + println 'mod \'enderio\' detected, running script' -// Alloy Smelter (Alloying): -// Convert up to 3 itemstack inputs into an itemstack output, using energy and giving XP. Can be restricted to require a given tier of machine. -// Can be set to require at least SIMPLE, NORMAL, or ENHANCED tiers, or to IGNORE the tier. SIMPLE and IGNORE are effectively the same. -mods.enderio.alloysmelter.recipeBuilder() +// Alloy Smelter: +// Convert up to 3 itemstack inputs into an itemstack output, using energy and giving XP. Can be restricted to require a +// given tier of machine. Can be set to require at least SIMPLE, NORMAL, or ENHANCED tiers, or to IGNORE the tier. SIMPLE +// and IGNORE are effectively the same. + +mods.enderio.alloy_smelter.remove(item('enderio:item_material:70')) +// mods.enderio.alloy_smelter.removeAll() + +mods.enderio.alloy_smelter.recipeBuilder() .input(item('minecraft:diamond') * 4, item('minecraft:clay') * 32) .output(item('minecraft:nether_star')) - .energy(100000) // Optional int, how much energy is required for the recipe. Default of 5000. - .xp(500) // Optional int, how much xp is output when the recipe is finished. Default of 0. - .tierEnhanced() // Optional, sets the minimum tier of machine recipe for recipe to ENHANCED. Default IGNORE + .energy(100000) + .xp(500) + .tierEnhanced() .register() -mods.enderio.alloysmelter.recipeBuilder() +mods.enderio.alloy_smelter.recipeBuilder() .input(item('minecraft:clay') * 4, item('minecraft:diamond')) .output(item('minecraft:obsidian')) - .tierNormal() // Optional, sets the minimum tier of machine recipe for recipe to NORMAL. Default IGNORE + .tierNormal() .register() -mods.enderio.alloysmelter.recipeBuilder() +mods.enderio.alloy_smelter.recipeBuilder() .input(item('minecraft:diamond') * 4, item('minecraft:gold_ingot') * 2) .output(item('minecraft:clay') * 4) - .tierSimple() // Optional, sets the minimum tier of machine recipe for recipe to SIMPLE. Default IGNORE + .tierSimple() .register() -mods.enderio.alloysmelter.recipeBuilder() +mods.enderio.alloy_smelter.recipeBuilder() .input(item('minecraft:diamond') * 2, item('minecraft:gold_nugget') * 2) .output(item('minecraft:clay') * 4) - .tierAny() // Optional, sets the minimum tier of machine recipe for recipe to IGNORE. Default IGNORE + .tierAny() .register() -mods.enderio.alloysmelter.remove(item('enderio:item_material:70')) // Remove by ItemStack output -//mods.enderio.alloysmelter.removeAll() - // Enchanter: -// Convert an input itemstack, player xp, and either a written book and lapis or a custom alternative into an enchanted book. +// Convert an input itemstack, player xp, and either a written book and lapis or a custom alternative into an enchanted +// book. + +mods.enderio.enchanter.remove(enchantment('minecraft:mending')) +// mods.enderio.enchanter.removeAll() + mods.enderio.enchanter.recipeBuilder() - .enchantment(enchantment('sharpness')) - .input(item('minecraft:clay')) - .amountPerLevel(3) // Optional int, multiplier per enchantment level. Defaults to amount of the input ItemStack - .xpCostMultiplier(2) // Optional double, base XP multiplier. Default 1. - .customBook(item('minecraft:book')) // Optional ItemStack, item that would otherwise be a written book. Default item('minecraft:writable_book') - .customLapis(item('minecraft:diamond')) // Optional OreDict, item that would otherwise be lapis, consumes 3 per output enchantment level. Default ore('gemLapis') + .enchantment(enchantment('minecraft:unbreaking')) + .input(item('minecraft:diamond')) .register() mods.enderio.enchanter.recipeBuilder() - .enchantment(enchantment('unbreaking')) - .input(item('minecraft:diamond')) + .enchantment(enchantment('minecraft:sharpness')) + .input(item('minecraft:clay')) + .amountPerLevel(3) + .xpCostMultiplier(2) + .customBook(item('minecraft:book')) + .customLapis(item('minecraft:diamond')) .register() -mods.enderio.enchanter.remove(enchantment('mending')) -mods.enderio.enchanter.removeAll() -// Fluid Coolant (Combustion Coolant): +// Fluid Coolant: // Create a Coolant with a given coolant rate that produces power with a Fuel while in a Combustion Generator. -mods.enderio.fluidcoolant.addCoolant(fluid('xpjuice'), 1000) -mods.enderio.fluidcoolant.remove(fluid('water')) -//mods.enderio.fluidcoolant.removeAll() +mods.enderio.fluid_coolant.remove(fluid('water')) +// mods.enderio.fluid_coolant.removeAll() +mods.enderio.fluid_coolant.addCoolant(fluid('xpjuice'), 1000) -// Fluid Fuel (Combustion Fuel): -// Create a Fuel with a given power per tick and total burn time that produces power with a Coolant while in a Combustion Generator. -mods.enderio.fluidfuel.addFuel(fluid('lava'), 500, 1000) +// Fluid Fuel: +// Create a Fuel with a given power per tick and total burn time that produces power with a Coolant while in a Combustion +// Generator. -mods.enderio.fluidfuel.remove(fluid('fire_water')) -//mods.enderio.fluidfuel.removeAll() +mods.enderio.fluid_fuel.remove(fluid('fire_water')) +// mods.enderio.fluid_fuel.removeAll() +mods.enderio.fluid_fuel.addFuel(fluid('lava'), 500, 1000) -// Sag Mill (SAGMill, Sag): -// Convert an input itemstack into up to 4 output itemstacks with chances, using energy. Output can be boosted by Grinding Balls based on set bonusType. -// Can be set to require at least SIMPLE, NORMAL, or ENHANCED tiers, or to IGNORE the tier. SIMPLE and IGNORE are effectively the same. -mods.enderio.sagmill.recipeBuilder() +// Sag Mill: +// Convert an input itemstack into up to 4 output itemstacks with chances, using energy. Output can be boosted by Grinding +// Balls based on set bonusType. Can be set to require at least SIMPLE, NORMAL, or ENHANCED tiers, or to IGNORE the tier. +// SIMPLE and IGNORE are effectively the same. + +mods.enderio.sag_mill.removeByInput(item('minecraft:wheat')) +// mods.enderio.sag_mill.removeAll() + +mods.enderio.sag_mill.recipeBuilder() .input(item('minecraft:diamond_block')) - .output(item('minecraft:diamond') * 4) // Has an 100% chance. + .output(item('minecraft:diamond') * 4) .output(item('minecraft:clay_ball') * 2, 0.7) .output(item('minecraft:gold_ingot'), 0.1) - .output(item('minecraft:gold_ingot'), 0.1) // Between 1 and 4 outputs. - .bonusTypeMultiply() // Optional, controls the effect Grinding Balls have on outputs. Default bonusTypeNone. - .energy(1000) // Optional int, how much energy is required for the recipe. Default of 5000. - .tierEnhanced() // Optional, sets the minimum tier of machine recipe for recipe to ENHANCED. Default IGNORE + .output(item('minecraft:gold_ingot'), 0.1) + .bonusTypeMultiply() + .energy(1000) + .tierEnhanced() .register() -mods.enderio.sagmill.recipeBuilder() +mods.enderio.sag_mill.recipeBuilder() .input(item('minecraft:clay_ball')) .output(item('minecraft:diamond') * 4) .output(item('minecraft:gold_ingot'), 0.1) - .bonusTypeChance() // Optional, controls the effect Grinding Balls have on outputs. Default bonusTypeNone. - .tierNormal() // Optional, sets the minimum tier of machine recipe for recipe to NORMAL. Default IGNORE + .bonusTypeChance() + .tierNormal() .register() -mods.enderio.sagmill.recipeBuilder() +mods.enderio.sag_mill.recipeBuilder() .input(item('minecraft:diamond')) .output(item('minecraft:gold_ingot'), 0.1) - .bonusTypeNone() // Optional, controls the effect Grinding Balls have on outputs. Default bonusTypeNone. - .tierSimple() // Optional, sets the minimum tier of machine recipe for recipe to SIMPLE. Default IGNORE + .bonusTypeNone() + .tierSimple() .register() -mods.enderio.sagmill.recipeBuilder() +mods.enderio.sag_mill.recipeBuilder() .input(item('minecraft:nether_star')) .output(item('minecraft:clay_ball') * 2, 0.7) .output(item('minecraft:gold_ingot'), 0.1) - .tierAny() // Optional, sets the minimum tier of machine recipe for recipe to IGNORE. Default IGNORE + .tierAny() .register() -mods.enderio.sagmill.removeByInput(item('minecraft:wheat')) -//mods.enderio.sagmill.removeAll() +// Sag Mill Grinding: +// Add a new Griding Ball for use in a Sag Mill with the given output multiplier, power multiplier, chance multiplier, and +// duration (in base power used). -// Sag Mill Grinding (Grinding): -// Add a new Griding Ball for use in a Sag Mill with the given output multiplier, power multiplier, chance multiplier, and duration (in base power used). -mods.enderio.sagmillgrinding.recipeBuilder() +mods.enderio.sag_mill_grinding.remove(item('minecraft:flint')) +// mods.enderio.sag_mill_grinding.removeAll() + +mods.enderio.sag_mill_grinding.recipeBuilder() .input(item('minecraft:clay_ball')) - .chance(6.66) // Optional int, chance to double all outputs. Default 1. - .power(0.001) // Optional int, multiplier to required power for recipes. Default 1. - .grinding(3.33) // Optional int, increases the chances of outputs up to 100%. Default 1. - .duration(10000) // Amount of power used in recipes before it is consumed. + .chance(6.66) + .power(0.001) + .grinding(3.33) + .duration(10000) .register() -mods.enderio.sagmillgrinding.remove(item('minecraft:flint')) -//mods.enderio.sagmillgrinding.removeAll() - -// Slice N Splice (SliceAndSplice): +// Slice N Splice: // Convert up to 6 input itemstacks into an output itemstack, using energy and giving XP. -mods.enderio.slicensplice.recipeBuilder() + +mods.enderio.slice_n_splice.remove(item('enderio:item_material:40')) +mods.enderio.slice_n_splice.removeByInput([item('enderio:item_alloy_ingot:7'), item('enderio:block_enderman_skull'), item('enderio:item_alloy_ingot:7'), item('minecraft:potion').withNbt(['Potion': 'minecraft:water']), item('enderio:item_basic_capacitor'), item('minecraft:potion').withNbt(['Potion': 'minecraft:water'])]) +// mods.enderio.slice_n_splice.removeAll() + +mods.enderio.slice_n_splice.recipeBuilder() .input(item('minecraft:clay'), null, item('minecraft:clay')) .input(null, item('minecraft:clay'), null) .output(item('minecraft:gold_ingot')) - .energy(1000) // Optional int, how much energy is required for the recipe. Default of 5000. - .xp(5) // Optional int, how much xp is output when the recipe is finished. Default of 0. + .energy(1000) + .xp(5) .register() -mods.enderio.slicensplice.remove(item('enderio:item_material:40')) // Remove by ItemStack output -mods.enderio.slicensplice.removeByInput( // Remove by List input - [item('enderio:item_alloy_ingot:7'), item('enderio:block_enderman_skull'), item('enderio:item_alloy_ingot:7'), - item('minecraft:potion').withNbt(["Potion": "minecraft:water"]), item('enderio:item_basic_capacitor'), item('minecraft:potion').withNbt(["Potion": "minecraft:water"])] -) -//mods.enderio.slicensplice.removeAll() // Soulbinder: -// Converts an input itemstack into an output itemstack, requiring one of several entities in soul vials, using energy and giving XP. Must have a unique name. -// To function properly, the input entities must be allowed in Soul Vials. -mods.enderio.soulbinder.recipeBuilder() +// Converts an input itemstack into an output itemstack, requiring one of several entities in soul vials, using energy and +// giving XP. Must have a unique name. To function properly, the input entities must be allowed in Soul Vials. + +mods.enderio.soul_binder.remove(item('enderio:item_material:17')) +// mods.enderio.soul_binder.removeAll() + +mods.enderio.soul_binder.recipeBuilder() .input(item('minecraft:diamond')) .output(item('minecraft:clay')) .entity(entity('minecraft:zombie'), entity('minecraft:enderman')) .name('groovy_example') - .energy(1000) // Optional int, how much energy is required for the recipe. Default of 5000. - .xp(5) // Optional int, how much xp is output when the recipe is finished. Default of 0. + .energy(1000) + .xp(5) .register() -mods.enderio.soulbinder.remove(item('enderio:item_material:17')) // Remove by ItemStack output -//mods.enderio.soulbinder.removeAll() - // Tank: -// Converts an input itemstack into an output fluidstack with an optional output itemstack in drain mode, -// or converts an input itemstack and fluidstack into an output itemstack in fill mode. +// Converts an input itemstack into an output fluidstack with an optional output itemstack in drain mode, or converts an +// input itemstack and fluidstack into an output itemstack in fill mode. + +mods.enderio.tank.removeDrain(item('minecraft:experience_bottle'), fluid('xpjuice')) +mods.enderio.tank.removeFill(item('minecraft:glass_bottle'), fluid('xpjuice')) +// mods.enderio.tank.removeAll() + mods.enderio.tank.recipeBuilder() - .drain() // puts fluid into tank + .drain() .input(item('minecraft:clay')) - .output(item('minecraft:diamond')) // Optional ItemStack. + .output(item('minecraft:diamond')) .fluidInput(fluid('water') * 500) .register() mods.enderio.tank.recipeBuilder() - .fill() // takes fluid out of tank + .fill() .input(item('minecraft:diamond')) - .output(item('minecraft:clay')) // Optional ItemStack. + .output(item('minecraft:clay')) .fluidOutput(fluid('water') * 500) .register() @@ -188,18 +205,19 @@ mods.enderio.tank.recipeBuilder() .fluidOutput(fluid('fire_water') * 8000) .register() -mods.enderio.tank.removeDrain(item('minecraft:experience_bottle'), fluid('xpjuice')) -mods.enderio.tank.removeFill(item('minecraft:glass_bottle'), fluid('xpjuice')) -//mods.enderio.tank.removeAll() // Vat: -// Converts an input fluidstack into an output itemstack at a rate based on up 2 itemstack inputs, and using power. -// Can be set to require at least NORMAL or ENHANCED tiers, or to IGNORE the tier. NORMAL and IGNORE are effectively the same. +// Converts an input fluidstack into an output itemstack at a rate based on up 2 itemstack inputs, and using power. Can be +// set to require at least NORMAL or ENHANCED tiers, or to IGNORE the tier. NORMAL and IGNORE are effectively the same. + +mods.enderio.vat.remove(fluid('nutrient_distillation')) +// mods.enderio.vat.removeAll() + mods.enderio.vat.recipeBuilder() .input(fluid('lava')) .output(fluid('hootch')) - .baseMultiplier(2) // Optional int, determines the base fluid output relative to fluid input + .baseMultiplier(2) .itemInputLeft(item('minecraft:clay'), 2) .itemInputLeft(item('minecraft:clay_ball'), 0.5) .itemInputRight(item('minecraft:diamond'), 5) @@ -208,7 +226,7 @@ mods.enderio.vat.recipeBuilder() .itemInputRight(item('minecraft:gold_ingot'), 1) .itemInputRight(item('minecraft:gold_nugget'), 0.1) .energy(1000) - .tierEnhanced() // Optional, sets the minimum tier of machine recipe for recipe to ENHANCED (the higher of the two tiers). Default IGNORE + .tierEnhanced() .register() mods.enderio.vat.recipeBuilder() @@ -217,7 +235,7 @@ mods.enderio.vat.recipeBuilder() .itemInputLeft(item('minecraft:clay_ball'), 1) .itemInputRight(item('minecraft:diamond'), 1) .energy(1000) - .tierNormal() // Optional, sets the minimum tier of machine recipe for recipe to NORMAL (the lower of the two tiers). Default IGNORE + .tierNormal() .register() mods.enderio.vat.recipeBuilder() @@ -228,8 +246,7 @@ mods.enderio.vat.recipeBuilder() .itemInputRight(item('minecraft:diamond'), 5) .itemInputRight(item('minecraft:gold_ingot'), 1) .energy(1000) - .tierAny() // Optional, sets the minimum tier of machine recipe for recipe to IGNORE. Default IGNORE + .tierAny() .register() -mods.enderio.vat.remove(fluid('nutrient_distillation')) -//mods.enderio.vat.removeAll() + diff --git a/examples/postInit/evilcraft.groovy b/examples/postInit/evilcraft.groovy index 133fe6fdb..b89bf9f98 100644 --- a/examples/postInit/evilcraft.groovy +++ b/examples/postInit/evilcraft.groovy @@ -1,65 +1,67 @@ +// Auto generated groovyscript example file // MODS_LOADED: evilcraft + println 'mod \'evilcraft\' detected, running script' -// Weather Bracket Handler -weather('clear') -weather('rain') -weather('lightning') +// Blood Infuser: +// Consumes an item, some fluid, and requires a given tier of Promise of Tenacity to produce the output and some experience +// after a duration. +mods.evilcraft.blood_infuser.removeByInput(item('evilcraft:dark_gem')) +mods.evilcraft.blood_infuser.removeByOutput(item('minecraft:leather')) +// mods.evilcraft.blood_infuser.removeAll() -// Blood Infuser: -// Consumes an item, some fluid, and requires a given tier of Promise of Tenacity to produce the output and some experience after a duration. -mods.evilcraft.bloodinfuser.recipeBuilder() +mods.evilcraft.blood_infuser.recipeBuilder() .input(item('minecraft:clay')) .output(item('minecraft:clay')) .fluidInput(fluid('evilcraftblood') * 1000) - .tier(3) // Optional integer. Requires at least this tier of Promise of Tenacity to craft. Defaults to 0. - .duration(100) // Optional integer. Time in ticks for the recipe to complete. Defaults to 0. - .xp(10000) // Optional float. Experience gained when completing the recipe. Defaults to 0. + .tier(3) + .duration(100) + .xp(10000) .register() -mods.evilcraft.bloodinfuser.recipeBuilder() +mods.evilcraft.blood_infuser.recipeBuilder() .input(item('minecraft:gold_ingot')) .output(item('minecraft:clay')) - .fluidInput(100000) // Calling `fluidInput` with just an integer will automatically consider the fluid as "evilcraftblood". + .fluidInput(100000) .register() -mods.evilcraft.bloodinfuser.recipeBuilder() +mods.evilcraft.blood_infuser.recipeBuilder() .input(item('minecraft:diamond')) .output(item('minecraft:clay') * 4) - .fluidInput(5000) // `blood` can also be used as an alias for `fluidInput` when only an integer is used. + .fluidInput(5000) .tier(1) .register() -mods.evilcraft.bloodinfuser.removeByInput(item('evilcraft:dark_gem')) -mods.evilcraft.bloodinfuser.removeByOutput(item('minecraft:leather')) -//mods.evilcraft.bloodinfuser.removeAll() - // Environmental Accumulator: // Consumes an item to give an output, possibly changing the weather. Has a cooldown time or a blood cost. -mods.evilcraft.environmentalaccumulator.recipeBuilder() + +mods.evilcraft.environmental_accumulator.removeByInput(item('evilcraft:exalted_crafter:1')) +mods.evilcraft.environmental_accumulator.removeByOutput(item('evilcraft:exalted_crafter:2')) +// mods.evilcraft.environmental_accumulator.removeAll() + +mods.evilcraft.environmental_accumulator.recipeBuilder() .input(item('minecraft:clay')) .output(item('minecraft:clay') * 2) .inputWeather(weather('clear')) .outputWeather(weather('rain')) - .processingspeed(1) // Optional doube. Controls the visual rotation of the item while crafting. Defaults to the amount set in the config. - .cooldowntime(1000) // Optional integer. Time it takes before another recipe can be run. Defaults to the time set in the config. - // cooldowntime also controls the amount of evilcraftblood consumed by the Sanguinary Environmental Accumulator - .duration(10) // Optional integer. Time it takes to complete the recipe. Defaults to the time set in the config. + .processingspeed(1) + .cooldowntime(1000) + .duration(10) .register() -mods.evilcraft.environmentalaccumulator.recipeBuilder() +mods.evilcraft.environmental_accumulator.recipeBuilder() .input(item('minecraft:gold_ingot')) .output(item('minecraft:diamond')) .inputWeather(weather('rain')) .outputWeather(weather('lightning')) - .speed(10) // Short for processingspeed. - .cooldown(1) // Short for cooldowntime. + .speed(10) + .cooldown(1) .register() -mods.evilcraft.environmentalaccumulator.recipeBuilder() +mods.evilcraft.environmental_accumulator.recipeBuilder() .input(item('minecraft:diamond')) .output(item('minecraft:clay') * 16) .inputWeather(weather('lightning')) @@ -67,7 +69,3 @@ mods.evilcraft.environmentalaccumulator.recipeBuilder() .register() -mods.evilcraft.environmentalaccumulator.removeByInput(item('evilcraft:exalted_crafter:1')) -mods.evilcraft.environmentalaccumulator.removeByOutput(item('evilcraft:exalted_crafter:2')) -//mods.evilcraft.environmentalaccumulator.removeAll() - diff --git a/examples/postInit/extendedcrafting.groovy b/examples/postInit/extendedcrafting.groovy index 1d0456650..b8c1f2372 100644 --- a/examples/postInit/extendedcrafting.groovy +++ b/examples/postInit/extendedcrafting.groovy @@ -1,151 +1,159 @@ +// Auto generated groovyscript example file // MODS_LOADED: extendedcrafting + println 'mod \'extendedcrafting\' detected, running script' -// Combination Crafting (Combination): -// Converts one main item and any number of additional items into an output itemstack, with a configurable rf cost and consumption per tick amount. -mods.extendedcrafting.combination.recipeBuilder() +// Combination Crafting: +// Converts one main item and any number of additional items into an output itemstack, with a configurable rf cost and +// consumption per tick amount. + +// mods.extendedcrafting.combination_crafting.removeByInput(item('minecraft:pumpkin')) +// mods.extendedcrafting.combination_crafting.removeByOutput(item('minecraft:gold_ingot')) +// mods.extendedcrafting.combination_crafting.removeAll() + +mods.extendedcrafting.combination_crafting.recipeBuilder() .input(item('minecraft:pumpkin')) .pedestals(item('minecraft:pumpkin') * 8) .output(item('minecraft:diamond') * 2) .cost(100) - .perTick(100) // Optional int, maximum amount of RF consumed per tick until the cost is paid. (Default ModConfig.confCraftingCoreRFRate, 500) + .perTick(100) .register() -mods.extendedcrafting.combinationcrafting.recipeBuilder() +mods.extendedcrafting.combination_crafting.recipeBuilder() .input(item('minecraft:pumpkin')) .pedestals(item('minecraft:pumpkin'), item('minecraft:clay'), item('minecraft:clay'), item('minecraft:pumpkin')) .output(item('minecraft:gold_ingot') * 2) .cost(10000) .register() -// there are no combination recipes by default, and so none can be removed -//mods.extendedcrafting.combination.removeByInput(item('minecraft:pumpkin')) -//mods.extendedcrafting.combination.removeByOutput(item('minecraft:gold_ingot')) -//mods.extendedcrafting.combination.removeAll() -// Compression Crafting (Compression): -// Converts any number of a single item into an output itemstack, with a configurable rf cost, consumption per tick amount, catalyst, and if the catalyst is consumed. -mods.extendedcrafting.compressioncrafting.recipeBuilder() +// Compression Crafting: +// Converts any number of a single item into an output itemstack, with a configurable rf cost, consumption per tick amount, +// catalyst, and if the catalyst is consumed. + +mods.extendedcrafting.compression_crafting.removeByCatalyst(item('extendedcrafting:material:11')) +mods.extendedcrafting.compression_crafting.removeByInput(item('minecraft:gold_ingot')) +mods.extendedcrafting.compression_crafting.removeByOutput(item('extendedcrafting:singularity:6')) +// mods.extendedcrafting.compression_crafting.removeAll() + +mods.extendedcrafting.compression_crafting.recipeBuilder() .input(item('minecraft:clay')) .inputCount(100) .output(item('minecraft:gold_ingot') * 7) - .catalyst(item('minecraft:diamond')) // Optional IIngredient, the item in the catalyst slot. (Default ModConfig.confSingularityCatalyst, ItemMaterial.itemUltimateCatalyst) - .consumeCatalyst(true) // Optional boolean, if the catalyst stack is consumed when the recipe completes. (Default false) + .catalyst(item('minecraft:diamond')) + .consumeCatalyst(true) .powerCost(10000) - .powerRate(1000) // Optional int, maximum amount of RF consumed per tick until the cost is paid. (Default ModConfig.confCompressorRFRate, 5000) + .powerRate(1000) .register() -mods.extendedcrafting.compression.recipeBuilder() - .input(item('minecraft:clay') * 10) // Input count can also be defined like this. +mods.extendedcrafting.compression_crafting.recipeBuilder() + .input(item('minecraft:clay') * 10) .output(item('minecraft:diamond') * 2) .powerCost(1000) .register() -mods.extendedcrafting.compression.removeByInput(item('minecraft:gold_ingot')) -mods.extendedcrafting.compression.removeByCatalyst(item('extendedcrafting:material:11')) -mods.extendedcrafting.compression.removeByOutput(item('extendedcrafting:singularity:6')) -//mods.extendedcrafting.compression.removeAll() + // Ender Crafting: // A normal crafting recipe, with the recipe being slowly crafted based on nearby Ender Alternators. -mods.extendedcrafting.endercrafting.shapelessBuilder() - .output(item('minecraft:clay') * 8) - .input(item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone')) - .register() - -mods.extendedcrafting.endercrafting.shapedBuilder() - .output(item('minecraft:stone')) - .matrix('BXX', - 'X B') - .key('B', item('minecraft:stone')) - .key('X', item('minecraft:gold_ingot')) - .time(1) - .mirrored() - .register() - -mods.extendedcrafting.endercrafting.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')) - .time(1) - .register() - -mods.extendedcrafting.endercrafting.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')]]) - .time(1) - .register() - -mods.extendedcrafting.endercrafting.removeByOutput(item('extendedcrafting:material:40')) -//mods.extendedcrafting.endercrafting.removeAll() - -// Table Crafting + +mods.extendedcrafting.ender_crafting.removeByOutput(item('extendedcrafting:material:40')) +// mods.extendedcrafting.ender_crafting.removeAll() + +mods.extendedcrafting.ender_crafting.shapedBuilder() + .output(item('minecraft:stone')) + .matrix('BXX', + 'X B') + .key('B', item('minecraft:stone')) + .key('X', item('minecraft:gold_ingot')) + .time(1) + .mirrored() + .register() + +mods.extendedcrafting.ender_crafting.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')]]) + .time(1) + .register() + +mods.extendedcrafting.ender_crafting.shapelessBuilder() + .output(item('minecraft:clay') * 8) + .input(item('minecraft:stone'), item('minecraft:stone'), item('minecraft:stone')) + .register() + +mods.extendedcrafting.ender_crafting.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')) + .time(1) + .register() + + + +// Table Crafting: // A normal crafting recipe, but requiring either a specific tier, or at least a given tier, from 3x3 to 9x9. -mods.extendedcrafting.tablecrafting.shapedBuilder() - .output(item('minecraft:stone') * 64) - .matrix( - 'DLLLLLDDD', + +mods.extendedcrafting.table_crafting.removeByOutput(item('extendedcrafting:singularity_ultimate')) +// mods.extendedcrafting.table_crafting.removeAll() + +mods.extendedcrafting.table_crafting.shapedBuilder() + .output(item('minecraft:stone') * 64) + .matrix('DLLLLLDDD', ' DNIGIND', 'DDDNIGIND', ' DLLLLLD') - .key('D', item('minecraft:diamond')) - .key('L', item('minecraft:redstone')) - .key('N', item('minecraft:stone')) - .key('I', item('minecraft:iron_ingot')) - .key('G', item('minecraft:gold_ingot')) - .tierUltimate() - .register() - -mods.extendedcrafting.tablecrafting.shapedBuilder() - .tierAdvanced() - .output(item('minecraft:stone') * 8) - .matrix('BXX') - .mirrored() - .key('B', item('minecraft:stone')) - .key('X', item('minecraft:gold_ingot')) - .register() - -mods.extendedcrafting.tablecrafting.shapedBuilder() - .tierAny() - .output(item('minecraft:diamond')) - .matrix('BXXXBX') - .mirrored() - .key('B', item('minecraft:stone')) - .key('X', item('minecraft:gold_ingot')) - .register() - -mods.extendedcrafting.tablecrafting.shapedBuilder() - .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'),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'),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'),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'),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'),item('minecraft:gold_ingot'),item('minecraft:gold_ingot'),item('minecraft:gold_ingot'),item('minecraft:gold_ingot')]]) + .key('D', item('minecraft:diamond')) + .key('L', item('minecraft:redstone')) + .key('N', item('minecraft:stone')) + .key('I', item('minecraft:iron_ingot')) + .key('G', item('minecraft:gold_ingot')) + .tierUltimate() + .register() + +mods.extendedcrafting.table_crafting.shapedBuilder() + .tierAdvanced() + .output(item('minecraft:stone') * 8) + .matrix('BXX') + .mirrored() + .key('B', item('minecraft:stone')) + .key('X', item('minecraft:gold_ingot')) + .register() + +mods.extendedcrafting.table_crafting.shapedBuilder() + .tierAny() + .output(item('minecraft:diamond')) + .matrix('BXXXBX') + .mirrored() + .key('B', item('minecraft:stone')) + .key('X', item('minecraft:gold_ingot')) + .register() + +mods.extendedcrafting.table_crafting.shapedBuilder() + .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'), 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'), 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'), 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'), 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'), item('minecraft:gold_ingot'), item('minecraft:gold_ingot'), item('minecraft:gold_ingot'), item('minecraft:gold_ingot')]]) .output(item('minecraft:gold_ingot') * 64) - .tier(4) // while we only have a 7x7 of gold ingots in the recipe, specifically requiring tier 4 (ultimate) locks us in + .tier(4) .register() -mods.extendedcrafting.tablecrafting.shapedBuilder() // can be crafted in any tier of table - .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'),]]) +mods.extendedcrafting.table_crafting.shapedBuilder() + .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')]]) .output(item('minecraft:gold_ingot') * 64) .register() -mods.extendedcrafting.tablecrafting.shapelessBuilder() - .output(item('minecraft:stone') * 64) - .input(item('minecraft:stone'), // 26 stone = can be crafted in either elite or ultimate - item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'), - item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'), - item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'), - item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'), - item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone')) - .register() - -mods.extendedcrafting.tablecrafting.removeByOutput(item('extendedcrafting:singularity_ultimate')) -//mods.extendedcrafting.tablecrafting.removeAll() +mods.extendedcrafting.table_crafting.shapelessBuilder() + .output(item('minecraft:stone') * 64) + .input(item('minecraft:stone'), item('minecraft:stone'), item('minecraft:stone'), item('minecraft:stone'), item('minecraft:stone'), item('minecraft:stone'), item('minecraft:stone'), item('minecraft:stone'), item('minecraft:stone'), item('minecraft:stone'), item('minecraft:stone'), item('minecraft:stone'), item('minecraft:stone'), item('minecraft:stone'), item('minecraft:stone'), item('minecraft:stone'), item('minecraft:stone'), item('minecraft:stone'), item('minecraft:stone'), item('minecraft:stone'), item('minecraft:stone'), item('minecraft:stone'), item('minecraft:stone'), item('minecraft:stone'), item('minecraft:stone'), item('minecraft:stone')) + .register() + + diff --git a/examples/postInit/immersiveengineering.groovy b/examples/postInit/immersiveengineering.groovy index 8adb7c510..ad9895b28 100644 --- a/examples/postInit/immersiveengineering.groovy +++ b/examples/postInit/immersiveengineering.groovy @@ -1,22 +1,32 @@ +// Auto generated groovyscript example file // MODS_LOADED: immersiveengineering + println 'mod \'immersiveengineering\' detected, running script' // Alloy Kiln: // Converts two input itemstacks into an output itemstack, consuming fuel (based on burn time). -mods.immersiveengineering.alloykiln.recipeBuilder() + +mods.immersiveengineering.alloy_kiln.removeByInput(item('minecraft:gold_ingot'), item('immersiveengineering:metal:3')) +mods.immersiveengineering.alloy_kiln.removeByOutput(item('immersiveengineering:metal:6')) +// mods.immersiveengineering.alloy_kiln.removeAll() + +mods.immersiveengineering.alloy_kiln.recipeBuilder() .input(item('minecraft:diamond'), ore('ingotGold')) .output(item('minecraft:clay')) .register() -mods.immersiveengineering.alloykiln.removeByInput(item('minecraft:gold_ingot'), item('immersiveengineering:metal:3')) -mods.immersiveengineering.alloykiln.removeByOutput(item('immersiveengineering:metal:6')) -//mods.immersiveengineering.alloykiln.removeAll() // Arc Furnace: -// Converts 1 input itemstack with up to 4 additional inputs into an output itemstack and an optional 'slag' itemstack, taking time and using rf power. -mods.immersiveengineering.arcfurnace.recipeBuilder() +// Converts 1 input itemstack with up to 4 additional inputs into an output itemstack and an optional 'slag' itemstack, +// taking time and using rf power. + +mods.immersiveengineering.arc_furnace.removeByInput(item('immersiveengineering:metal:18'), item('immersiveengineering:material:17')) +mods.immersiveengineering.arc_furnace.removeByOutput(item('immersiveengineering:metal:7')) +// mods.immersiveengineering.arc_furnace.removeAll() + +mods.immersiveengineering.arc_furnace.recipeBuilder() .mainInput(item('minecraft:diamond')) .input(item('minecraft:diamond'), ore('ingotGold')) .output(item('minecraft:clay')) @@ -25,94 +35,109 @@ mods.immersiveengineering.arcfurnace.recipeBuilder() .slag(item('minecraft:gold_nugget')) .register() -mods.immersiveengineering.arcfurnace.removeByInput(item('immersiveengineering:metal:18'), item('immersiveengineering:material:17')) -mods.immersiveengineering.arcfurnace.removeByOutput(item('immersiveengineering:metal:7')) -//mods.immersiveengineering.arcfurnace.removeAll() // Blast Furnace: -// Converts an input itemstack into an output itemstack and an optional 'slag' itemstack, taking time and consuming fuel (based on Blast Furnace Fuels). -mods.immersiveengineering.blastfurnace.recipeBuilder() +// Converts an input itemstack into an output itemstack and an optional 'slag' itemstack, taking time and consuming fuel +// (based on Blast Furnace Fuels). + +mods.immersiveengineering.blast_furnace.removeByInput(item('minecraft:iron_block')) +mods.immersiveengineering.blast_furnace.removeByOutput(item('immersiveengineering:metal:8')) +// mods.immersiveengineering.blast_furnace.removeAll() + +mods.immersiveengineering.blast_furnace.recipeBuilder() .input(item('minecraft:diamond')) .output(item('minecraft:clay')) .time(100) .slag(item('minecraft:gold_nugget')) .register() -mods.immersiveengineering.blastfurnace.removeByInput(item('minecraft:iron_block')) -mods.immersiveengineering.blastfurnace.removeByOutput(item('immersiveengineering:metal:8')) -//mods.immersiveengineering.blastfurnace.removeAll() // Blast Furnace Fuel: // Allows an item to be used in the Blast Furnace as a fuel for the given number of ticks. -mods.immersiveengineering.blastfurnacefuel.recipeBuilder() + +mods.immersiveengineering.blast_furnace_fuel.removeByInput(item('immersiveengineering:material:6')) +// mods.immersiveengineering.blast_furnace_fuel.removeAll() + +mods.immersiveengineering.blast_furnace_fuel.recipeBuilder() .input(item('minecraft:clay')) .time(100) .register() -mods.immersiveengineering.blastfurnacefuel.removeByInput(item('immersiveengineering:material:6')) -//mods.immersiveengineering.blastfurnacefuel.removeAll() -// Blueprint Crafting (Blueprint): -// Converts any number of input itemstacks into an output itemstack, using a blueprint with the category nbt tag as a catalyst. -mods.immersiveengineering.blueprint.recipeBuilder() +// Blueprint Crafting: +// Converts any number of input itemstacks into an output itemstack, using a blueprint with the category nbt tag as a +// catalyst. + +mods.immersiveengineering.blueprint_crafting.removeByCategory('electrode') +mods.immersiveengineering.blueprint_crafting.removeByInput('components', item('immersiveengineering:metal:38'), item('immersiveengineering:metal:38'), item('immersiveengineering:metal')) +mods.immersiveengineering.blueprint_crafting.removeByOutput('components', item('immersiveengineering:material:8')) +// mods.immersiveengineering.blueprint_crafting.removeAll() + +mods.immersiveengineering.blueprint_crafting.recipeBuilder() .input(item('minecraft:diamond'), ore('ingotGold')) .output(item('minecraft:clay')) - .category('groovy') // Default blueprint options: components, molds, bullet, specialBullet, electrode. + .category('groovy') .register() -mods.immersiveengineering.blueprint.removeByCategory('electrode') -mods.immersiveengineering.blueprint.removeByInput('components', item('immersiveengineering:metal:38'), item('immersiveengineering:metal:38'), item('immersiveengineering:metal')) -mods.immersiveengineering.blueprint.removeByOutput('components', item('immersiveengineering:material:8')) -//mods.immersiveengineering.blueprint.removeAll() -// Bottling Machine (Bottling): +// Bottling Machine: // Converts an input itemstack and fluidstack into an output itemstack. -mods.immersiveengineering.bottling.recipeBuilder() + +mods.immersiveengineering.bottling_machine.removeByInput(item('minecraft:sponge'), fluid('water') * 1000) +mods.immersiveengineering.bottling_machine.removeByOutput(item('minecraft:potion').withNbt([Potion:'minecraft:mundane'])) +// mods.immersiveengineering.bottling_machine.removeAll() + +mods.immersiveengineering.bottling_machine.recipeBuilder() .input(item('minecraft:diamond')) .fluidInput(fluid('water')) .output(item('minecraft:clay')) .register() -mods.immersiveengineering.bottling.removeByInput(item('minecraft:sponge'), fluid('water') * 1000) -mods.immersiveengineering.bottling.removeByOutput(item('minecraft:potion').withNbt([Potion:'minecraft:mundane'])) -//mods.immersiveengineering.bottling.removeAll() // Coke Oven: // Converts an input itemstack into an output itemstack over time, producing a given amount of creosote oil. -mods.immersiveengineering.cokeoven.recipeBuilder() + +mods.immersiveengineering.coke_oven.removeByInput(item('minecraft:log')) +mods.immersiveengineering.coke_oven.removeByOutput(item('immersiveengineering:material:6')) +// mods.immersiveengineering.coke_oven.removeAll() + +mods.immersiveengineering.coke_oven.recipeBuilder() .input(item('minecraft:diamond')) .output(item('minecraft:clay')) .time(100) .creosote(50) .register() -mods.immersiveengineering.cokeoven.removeByInput(item('minecraft:log')) -mods.immersiveengineering.cokeoven.removeByOutput(item('immersiveengineering:material:6')) -//mods.immersiveengineering.cokeoven.removeAll() // Crusher: // Converts an input itemstack into an output itemstack, consuming energy. + +mods.immersiveengineering.crusher.removeByInput(item('immersiveengineering:material:7')) +mods.immersiveengineering.crusher.removeByOutput(item('minecraft:sand')) +// mods.immersiveengineering.crusher.removeAll() + mods.immersiveengineering.crusher.recipeBuilder() .input(item('minecraft:diamond')) .output(item('minecraft:clay')) .energy(100) .register() -mods.immersiveengineering.crusher.removeByInput(item('immersiveengineering:material:7')) -mods.immersiveengineering.crusher.removeByOutput(item('minecraft:sand')) -//mods.immersiveengineering.crusher.removeAll() // Excavator: -// Adds a Mineral Mix with the given name, weight, fail chance, ores, and allowed dimensions. A Mineral Mix can be mined -// by an Excavator Multiblock. -// WARNING: reloading will not change chunks already 'discovered' +// Adds a Mineral Mix with the given name, weight, fail chance, ores, and allowed dimensions. A Mineral Mix can be mined by +// an Excavator Multiblock and scanned via a Core Sample Drill. + +mods.immersiveengineering.excavator.removeByMineral('silt') +mods.immersiveengineering.excavator.removeByOres(ore('oreAluminum')) +// mods.immersiveengineering.excavator.removeAll() + mods.immersiveengineering.excavator.recipeBuilder() .name('demo') .weight(20000) @@ -131,44 +156,51 @@ mods.immersiveengineering.excavator.recipeBuilder() .blacklist() .register() -mods.immersiveengineering.excavator.removeByOres(ore('oreAluminum')) -mods.immersiveengineering.excavator.removeByMineral('silt') -//mods.immersiveengineering.excavator.removeAll() // Fermenter: // Converts an input itemstack into an output fluidstack with an optional output itemstack, consuming power. + +mods.immersiveengineering.fermenter.removeByInput(item('minecraft:reeds')) +mods.immersiveengineering.fermenter.removeByOutput(fluid('ethanol')) +// mods.immersiveengineering.fermenter.removeAll() + mods.immersiveengineering.fermenter.recipeBuilder() .input(item('minecraft:diamond')) - .output(item('minecraft:clay')) // Optional + .output(item('minecraft:clay')) .fluidOutput(fluid('water')) .energy(100) .register() -mods.immersiveengineering.fermenter.removeByInput(item('minecraft:reeds')) -mods.immersiveengineering.fermenter.removeByOutput(fluid('ethanol')) -//mods.immersiveengineering.fermenter.removeAll() // Metal Press: // Converts an input itemstack into an output itemstack, with a mold catalyst, consuming power. -mods.immersiveengineering.metalpress.recipeBuilder() + +mods.immersiveengineering.metal_press.removeByInput(item('minecraft:iron_ingot')) +mods.immersiveengineering.metal_press.removeByInput(item('immersiveengineering:mold'), item('immersiveengineering:metal:8')) +mods.immersiveengineering.metal_press.removeByMold(item('immersiveengineering:mold:4')) +mods.immersiveengineering.metal_press.removeByOutput(item('immersiveengineering:material:2')) +mods.immersiveengineering.metal_press.removeByOutput(item('immersiveengineering:mold'), item('immersiveengineering:metal:31')) +// mods.immersiveengineering.metal_press.removeAll() + +mods.immersiveengineering.metal_press.recipeBuilder() .mold(item('minecraft:diamond')) .input(ore('ingotGold')) .output(item('minecraft:clay')) .energy(100) .register() -mods.immersiveengineering.metalpress.removeByInput(item('minecraft:iron_ingot')) -mods.immersiveengineering.metalpress.removeByInput(item('immersiveengineering:mold'), item('immersiveengineering:metal:8')) -mods.immersiveengineering.metalpress.removeByOutput(item('immersiveengineering:material:23')) -mods.immersiveengineering.metalpress.removeByOutput(item('immersiveengineering:mold'), item('immersiveengineering:metal:31')) -mods.immersiveengineering.metalpress.removeByMold(item('immersiveengineering:mold:4')) -//mods.immersiveengineering.metalpress.removeAll() // Mixer: // Converts any number of input itemstacks and a fluidstack into an output fluidstack, consuming power. + +mods.immersiveengineering.mixer.removeByInput(fluid('water'), item('minecraft:speckled_melon')) +mods.immersiveengineering.mixer.removeByInput(item('minecraft:sand'), item('minecraft:sand'), item('minecraft:clay_ball'), item('minecraft:gravel')) +mods.immersiveengineering.mixer.removeByOutput(fluid('potion').withNbt([Potion:'minecraft:night_vision'])) +// mods.immersiveengineering.mixer.removeAll() + mods.immersiveengineering.mixer.recipeBuilder() .input(item('minecraft:diamond'), ore('ingotGold'), ore('ingotGold'), ore('ingotGold')) .fluidInput(fluid('water')) @@ -176,48 +208,49 @@ mods.immersiveengineering.mixer.recipeBuilder() .energy(100) .register() -mods.immersiveengineering.mixer.removeByInput(item('minecraft:sand'), item('minecraft:sand'), item('minecraft:clay_ball'), item('minecraft:gravel')) -mods.immersiveengineering.mixer.removeByInput(fluid('water'), item('minecraft:speckled_melon')) -mods.immersiveengineering.mixer.removeByOutput(fluid('potion').withNbt([Potion:'minecraft:night_vision'])) -//mods.immersiveengineering.mixer.removeAll() // Refinery: // Converts 2 input fluidstacks into an output fluidstack, consuming power. + +mods.immersiveengineering.refinery.removeByInput(fluid('plantoil'), fluid('ethanol')) +// mods.immersiveengineering.refinery.removeByOutput(fluid('biodiesel')) +// mods.immersiveengineering.refinery.removeAll() + mods.immersiveengineering.refinery.recipeBuilder() .fluidInput(fluid('water'), fluid('water')) .fluidOutput(fluid('lava')) .energy(100) .register() -mods.immersiveengineering.refinery.removeByInput(fluid('plantoil'), fluid('ethanol')) -//mods.immersiveengineering.refinery.removeByOutput(fluid('biodiesel')) // <- already removed by 'removeByInput' line -//mods.immersiveengineering.refinery.removeAll() // Squeezer: // Converts an input itemstack into either an output itemstack, fluidstack, or both, using energy. + +mods.immersiveengineering.squeezer.removeByInput(item('minecraft:wheat_seeds')) +mods.immersiveengineering.squeezer.removeByOutput(fluid('plantoil')) +mods.immersiveengineering.squeezer.removeByOutput(item('immersiveengineering:material:18')) +// mods.immersiveengineering.squeezer.removeAll() + mods.immersiveengineering.squeezer.recipeBuilder() .input(item('minecraft:diamond')) - .output(item('minecraft:clay')) // Either an output itemstack or output fluidstack must be defined - .fluidOutput(fluid('lava')) // Either an output itemstack or output fluidstack must be defined + .output(item('minecraft:clay')) + .fluidOutput(fluid('lava')) .energy(100) .register() -// WARNING: If only an output itemstack is defined, the itemstack will not display in JEI. mods.immersiveengineering.squeezer.recipeBuilder() .input(item('minecraft:gold_ingot')) - .output(item('minecraft:clay')) // Either an output itemstack or output fluidstack must be defined + .output(item('minecraft:clay')) .energy(100) .register() mods.immersiveengineering.squeezer.recipeBuilder() .input(item('minecraft:clay')) - .fluidOutput(fluid('water')) // Either an output itemstack or output fluidstack must be defined + .fluidOutput(fluid('water')) .energy(100) .register() -mods.immersiveengineering.squeezer.removeByInput(item('minecraft:wheat_seeds')) -mods.immersiveengineering.squeezer.removeByOutput(fluid('plantoil')) -mods.immersiveengineering.squeezer.removeByOutput(item('immersiveengineering:material:18')) -//mods.immersiveengineering.squeezer.removeAll() + + diff --git a/examples/postInit/inspirations.groovy b/examples/postInit/inspirations.groovy index 3cf15a6d0..e434d54c0 100644 --- a/examples/postInit/inspirations.groovy +++ b/examples/postInit/inspirations.groovy @@ -1,91 +1,93 @@ +// Auto generated groovyscript example file // MODS_LOADED: inspirations + println '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. -// Cauldrons have a cap of either 3 or 4 levels, depending on the config. +// 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() // Optional, one type is required, it can either be done in a normal recipeBuilder or preset via a recipeBuilder variant. + .standard() .input(item('minecraft:gold_ingot')) .fluidInput(fluid('lava')) .output(item('minecraft:clay')) .boiling() - .sound(sound('block.anvil.destroy')) + .sound(sound('minecraft:block.anvil.destroy')) .levels(3) .register() -mods.inspirations.cauldron.recipeBuilderStandard() // Requires 1 input, 1 output, 1 fluid input, 0 < levels < 3/4, and a sound - .input(item('minecraft:diamond')) - .output(item('minecraft:clay')) - .fluidInput(fluid('lava')) - .levels(3) - .sound(sound('block.anvil.destroy')) +mods.inspirations.cauldron.recipeBuilderBrewing() + .input(item('minecraft:diamond_block')) + .inputPotion(potionType('minecraft:fire_resistance')) + .outputPotion(potionType('minecraft:strength')) .register() -mods.inspirations.cauldron.recipeBuilderTransform() // Requires 1 input, 1 fluid input, 1 fluid output, 0 < levels < 3/4 - .input(item('minecraft:stone:3')) - .fluidInput(fluid('water')) - .fluidOutput(fluid('milk')) +mods.inspirations.cauldron.recipeBuilderDye() + .input(item('minecraft:gold_block')) + .output(item('minecraft:diamond_block')) + .dye('blue') .levels(2) .register() -mods.inspirations.cauldron.recipeBuilderMix() // Requires 1 output and 2 fluid inputs - .output(item('minecraft:clay')) - .fluidInput(fluid('milk'), fluid('lava')) - .register() - -mods.inspirations.cauldron.recipeBuilderFill() // Requires 1 input, 1 output, 1 fluid input, and a sound +mods.inspirations.cauldron.recipeBuilderFill() .input(item('minecraft:gold_ingot')) .output(item('minecraft:clay')) .fluidInput(fluid('milk')) - .sound(sound('block.anvil.destroy')) - .register() - -mods.inspirations.cauldron.recipeBuilderBrewing() // Requires 1 input, 1 input potion, and 1 output potion - .input(item('minecraft:diamond_block')) - .inputPotion(potionType('fire_resistance')) - .outputPotion(potionType('strength')) + .sound(sound('minecraft:block.anvil.destroy')) .register() -mods.inspirations.cauldron.recipeBuilderPotion() // Requires 1 input, 1 output, 1 input potion, and 0 < levels < 3/4 - .input(item('minecraft:gold_block')) - .output(item('minecraft:diamond_block')) - .inputPotion(potionType('fire_resistance')) - .levels(2) +mods.inspirations.cauldron.recipeBuilderMix() + .output(item('minecraft:clay')) + .fluidInput(fluid('milk'), fluid('lava')) .register() -mods.inspirations.cauldron.recipeBuilderDye() // Requires 1 input, 1 output, 1 dye, and 0 < levels < 3/4 +mods.inspirations.cauldron.recipeBuilderPotion() .input(item('minecraft:gold_block')) .output(item('minecraft:diamond_block')) - .dye('blue') + .inputPotion(potionType('minecraft:fire_resistance')) .levels(2) .register() - -// Note: some recipes (banners, potions) cannot be removed. -mods.inspirations.cauldron.removeByInput(item('minecraft:ghast_tear')) -mods.inspirations.cauldron.removeByOutput(item('minecraft:piston')) -mods.inspirations.cauldron.removeByFluidOutput(fluid('beetroot_soup')) -mods.inspirations.cauldron.removeByFluidInput(fluid('mushroom_stew')) - -//mods.inspirations.cauldron.removeAll() - - -// Anvil Smashing: -// Converts a Block or IBlockState into an IBlockState when an anvil falls on top of it (from any height). -mods.inspirations.anvilsmashing.recipeBuilder() - .input(blockstate('minecraft:diamond_block')) - .output(blockstate('minecraft:clay')) +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.anvilsmashing.recipeBuilder() - .input(blockstate('minecraft:clay')) - .output(blockstate('minecraft:air')) +mods.inspirations.cauldron.recipeBuilderTransform() + .input(item('minecraft:stone:3')) + .fluidInput(fluid('water')) + .fluidOutput(fluid('milk')) + .levels(2) .register() -mods.inspirations.anvilsmashing.removeByInput(blockstate('minecraft:packed_ice')) -mods.inspirations.anvilsmashing.removeByOutput(blockstate('minecraft:cobblestone')) - -//mods.inspirations.anvilsmashing.removeAll() diff --git a/examples/postInit/integrateddynamics.groovy b/examples/postInit/integrateddynamics.groovy index 09020d8b0..1cebdde96 100644 --- a/examples/postInit/integrateddynamics.groovy +++ b/examples/postInit/integrateddynamics.groovy @@ -1,40 +1,66 @@ +// Auto generated groovyscript example file // MODS_LOADED: integrateddynamics + println 'mod \'integrateddynamics\' detected, running script' -// Drying Basin and Mechanical Drying Basin: +// Drying Basin: // Takes either an item or fluid input and gives either an item or fluid output after a duration. -mods.integrateddynamics.dryingbasin.recipeBuilder() // Defaults to basic-only, and mechanical must be enabled via `mechanical()` - .input(item('minecraft:gold_ingot')) // Either an item input or fluid input must be defined, or both - .output(item('minecraft:clay')) // Either an item output or fluid output must be defined, or both - .fluidInput(fluid('water') * 500) // Either an item input or fluid input must be defined, or both - .fluidOutput(fluid('lava') * 2000) // Either an item output or fluid output must be defined, or both - .mechanical() // By default, the recipeBuilder is set to basic-only. This also adds the recipe to the Mechanical Drying Basin - .duration(5) // Optional integer. Defaults to 10 + +// 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.dryingbasin.recipeBuilder() +mods.integrateddynamics.drying_basin.recipeBuilder() .output(item('minecraft:clay')) .fluidInput(fluid('water') * 2000) .register() -mods.integrateddynamics.mechanicaldryingbasin.recipeBuilder() // Defaults to mechanical-only, and basic must be enabled via `basic()` + +// 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() -// Squeezer and Mechanical Squeezer: -// Takes an item and can give up to 3 chanced item outputs and a fluid. -mods.integrateddynamics.squeezer.recipeBuilder() // Defaults to basic-only, and mechanical must be enabled via `mechanical()` + +// 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) // Between 0 and 3 item outputs with chances can be added. + .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) // An output fluid may be defined. - .mechanical()// By default, the recipeBuilder is set to basic-only. This also adds the recipe to the Mechanical Drying Basin - .duration(5) // Optional integer. Defaults to 10. Only applies if mechanical is set to true + .fluidOutput(fluid('lava') * 2000) + .mechanical() + .duration(5) .register() mods.integrateddynamics.squeezer.recipeBuilder() @@ -47,7 +73,4 @@ mods.integrateddynamics.squeezer.recipeBuilder() .fluidOutput(fluid('lava') * 10) .register() -mods.integrateddynamics.mechanicalsqueezer.recipeBuilder() // Defaults to mechanical-only, and basic must be enabled via `basic()` - .input(item('minecraft:diamond')) - .output(item('minecraft:clay') * 16, 0.9F) - .register() + diff --git a/examples/postInit/mekanism.groovy b/examples/postInit/mekanism.groovy index 7ae720388..8a65b0dad 100644 --- a/examples/postInit/mekanism.groovy +++ b/examples/postInit/mekanism.groovy @@ -1,276 +1,282 @@ +// Auto generated groovyscript example file // MODS_LOADED: mekanism -println 'mod \'mekanism\' detected, running script' - -// Bracket Handlers -// Gas -gas('gold') -gas('liquidosmium') - -// Infusion -infusion('carbon') -infusion('redstone') -infusion('diamond') -infusion('obsidian') -infusion('fungi') -infusion('bio') -infusion('tin') +println 'mod \'mekanism\' detected, running script' // Infusion: -// Add new infusion types and itemstacks to those types - -mods.mekanism.infusion.infusion(infusion('diamond')) - .add(100, item('minecraft:clay')) - .remove(ore('dustDiamond')) +// Add new infusion types and itemstacks to those types. -mods.mekanism.infusion.infusion(infusion('carbon')) - .removeAll() // Must occur before anything is added - .add(100, ore('ingotGold')) +mods.mekanism.infusion.remove(ore('dustDiamond')) +mods.mekanism.infusion.removeByType(infusion('carbon')) +// mods.mekanism.infusion.removeByType(infusion('diamond')) +// mods.mekanism.infusion.removeAll() -// NOTE: -// To register the texture used, you have to add the following event listen to a PreInit file. -// event_manager.listen { TextureStitchEvent.Pre event -> event.getMap().registerSprite(resource('placeholdername:blocks/example')) } -// Where 'assets/placeholdername/textures/blocks/example.png' is the location of the desired texture. -mods.mekanism.infusion.infusion('groovy_example', resource('placeholdername:blocks/example')) - .add(10, item('minecraft:ice')) - .add(20, item('minecraft:packed_ice')) +mods.mekanism.infusion.addType('groovy_example', resource('placeholdername:blocks/example')) +mods.mekanism.infusion.add(infusion('diamond'), 100, item('minecraft:clay')) +mods.mekanism.infusion.add(infusion('carbon'), 100, item('minecraft:gold_ingot')) +mods.mekanism.infusion.add('groovy_example', 10, item('minecraft:ice')) +mods.mekanism.infusion.add('groovy_example', 20, item('minecraft:packed_ice')) +// Chemical Infuser: +// Combines two input gas stacks into a output gas stack. -//mods.mekanism.infusion.removeByType(infusion('diamond')) -//mods.mekanism.infusion.removeAll() - +mods.mekanism.chemical_infuser.removeByInput(gas('hydrogen'), gas('chlorine')) +// mods.mekanism.chemical_infuser.removeAll() -// Chemical Infuser: -// Combines two input gas stacks into a output gas stack -mods.mekanism.chemicalinfuser.recipeBuilder() +mods.mekanism.chemical_infuser.recipeBuilder() .gasInput(gas('copper') * 10, gas('iron')) .gasOutput(gas('gold') * 15) .register() -//mods.mekanism.chemicalinfuser.add(gas('copper') * 10, gas('iron'), gas('gold') * 15) -mods.mekanism.chemicalinfuser.removeByInput(gas('hydrogen'), gas('chlorine')) -//mods.mekanism.chemicalinfuser.removeAll() +// mods.mekanism.chemical_infuser.add(gas('copper') * 10, gas('iron'), gas('gold') * 15) + +// Chemical Oxidizer: +// Converts an input itemstack into an output gasstack. -// Chemical Oxidizer (Oxidizer): -// Converts an input itemstack into an output gasstack -mods.mekanism.chemicaloxidizer.recipeBuilder() +mods.mekanism.chemical_oxidizer.removeByInput(ore('dustSulfur')) +// mods.mekanism.chemical_oxidizer.removeAll() + +mods.mekanism.chemical_oxidizer.recipeBuilder() .input(ore('dustGold')) .gasOutput(gas('gold')) .register() -//mods.mekanism.chemicaloxidizer.add(ore('dustGold'), gas('gold')) -mods.mekanism.chemicaloxidizer.removeByInput(ore('dustSulfur')) -//mods.mekanism.chemicaloxidizer.removeAll() + +// mods.mekanism.chemical_oxidizer.add(ore('dustGold'), gas('gold')) // Combiner: -// Combines an input itemstack with an extra itemstack to create an output itemstack +// Combines an input itemstack with an extra itemstack to create an output itemstack. + +mods.mekanism.combiner.removeByInput(item('minecraft:flint'), item('minecraft:cobblestone')) +// mods.mekanism.combiner.removeAll() + mods.mekanism.combiner.recipeBuilder() .input(ore('gemQuartz') * 8) .extra(item('minecraft:netherrack')) .output(item('minecraft:quartz_ore')) .register() -//mods.mekanism.combiner.add(ore('gemQuartz') * 8, item('minecraft:netherrack'), item('minecraft:quartz_ore')) -mods.mekanism.combiner.removeByInput(item('minecraft:flint'), item('minecraft:cobblestone')) -//mods.mekanism.combiner.removeAll() +// mods.mekanism.combiner.add(ore('gemQuartz') * 8, item('minecraft:netherrack'), item('minecraft:quartz_ore')) // Crusher: // Converts an input itemstack into an output itemstack. + +mods.mekanism.crusher.removeByInput(ore('ingotTin')) +// mods.mekanism.crusher.removeAll() + mods.mekanism.crusher.recipeBuilder() .input(item('minecraft:clay_ball')) .output(item('minecraft:gold_ingot')) .register() -//mods.mekanism.crusher.add(item('minecraft:clay_ball'), item('minecraft:gold_ingot')) -mods.mekanism.crusher.removeByInput(ore('ingotTin')) -//mods.mekanism.crusher.removeAll() +// mods.mekanism.crusher.add(item('minecraft:clay_ball'), item('minecraft:gold_ingot')) // Crystallizer: // Converts an input gasstack into an output itemstack. + +mods.mekanism.crystallizer.removeByInput(gas('cleanGold')) +// mods.mekanism.crystallizer.removeAll() + mods.mekanism.crystallizer.recipeBuilder() .gasInput(gas('cleanGold')) .output(item('minecraft:gold_ingot')) .register() -//mods.mekanism.crystallizer.add(gas('cleanGold'), item('minecraft:gold_ingot')) -mods.mekanism.crystallizer.removeByInput(gas('cleanGold')) // either remove first or don't remove at all. the recipe below should overwrite the original recipe -//mods.mekanism.crystallizer.removeAll() +// mods.mekanism.crystallizer.add(gas('cleanGold'), item('minecraft:gold_ingot')) + +// Dissolution Chamber: +// Converts an input itemstack into an output gasstack at the cost of 100mb of Sulfuric Acid. -// Dissolution Chamber (Dissolver): -// Converts an input itemstack into an output gasstack at the cost of 100mb of Sulfuric Acid -mods.mekanism.dissolutionchamber.recipeBuilder() +mods.mekanism.dissolution_chamber.removeByInput(item('mekanism:oreblock:0')) +// mods.mekanism.dissolution_chamber.removeAll() + +mods.mekanism.dissolution_chamber.recipeBuilder() .input(item('minecraft:packed_ice')) .gasOutput(gas('water') * 2000) .register() -//mods.mekanism.dissolutionchamber.add(item('minecraft:packed_ice'), gas('water')) -mods.mekanism.dissolutionchamber.removeByInput(item('mekanism:oreblock:0')) -//mods.mekanism.dissolutionchamber.removeAll() +// mods.mekanism.dissolution_chamber.add(item('minecraft:packed_ice'), gas('water')) -// Enrichment Chamber (Enricher): -// Converts an input itemstack into an output itemstack. -mods.mekanism.enrichmentchamber.recipeBuilder() - .input(item('minecraft:clay_ball')) - .output(item('minecraft:nether_star')) - .register() -//mods.mekanism.enrichmentchamber.add(item('minecraft:clay_ball'), item('minecraft:nether_star')) - -mods.mekanism.enrichmentchamber.removeByInput(item('minecraft:diamond')) -//mods.mekanism.enrichmentchamber.removeAll() +// Electrolytic Separator: +// Converts an input fluid into two output gasstacks at the cost of power. +mods.mekanism.electrolytic_separator.removeByInput(fluid('water')) +// mods.mekanism.electrolytic_separator.removeAll() -// Electrolytic Separator (Separator): -// Converts an input fluid into two output gasstacks at the cost of power. -mods.mekanism.electrolyticseparator.recipeBuilder() +mods.mekanism.electrolytic_separator.recipeBuilder() .fluidInput(fluid('lava') * 10) .gasOutput(gas('cleanGold') * 5, gas('cleanCopper') * 3) .energy(3000) .register() -//mods.mekanism.electrolyticseparator.add(fluid('lava') * 10, gas('cleanGold') * 5, gas('cleanCopper') * 3, 3000) -mods.mekanism.electrolyticseparator.removeByInput(fluid('water')) -//mods.mekanism.electrolyticseparator.removeAll() +// mods.mekanism.electrolytic_separator.add(fluid('lava') * 10, gas('cleanGold') * 5, gas('cleanCopper') * 3, 3000) + +// Enrichment Chamber: +// Converts an input itemstack into an output itemstack. + +mods.mekanism.enrichment_chamber.removeByInput(item('minecraft:diamond')) +// mods.mekanism.enrichment_chamber.removeAll() + +mods.mekanism.enrichment_chamber.recipeBuilder() + .input(item('minecraft:clay_ball')) + .output(item('minecraft:nether_star')) + .register() + + +// mods.mekanism.enrichment_chamber.add(item('minecraft:clay_ball'), item('minecraft:nether_star')) -// Injection Chamber (Injector): +// Injection Chamber: // Converts an input itemstack and 200 of a gasstack into an output itemstack. -mods.mekanism.injectionchamber.recipeBuilder() + +mods.mekanism.injection_chamber.removeByInput(item('minecraft:hardened_clay'), gas('water')) +// mods.mekanism.injection_chamber.removeAll() + +mods.mekanism.injection_chamber.recipeBuilder() .input(item('minecraft:diamond')) - .gasInput(gas('water')) // Always uses 200 + .gasInput(gas('water')) // Always uses 200 gas .output(item('minecraft:nether_star')) .register() -//mods.mekanism.injectionchamber.add(item('minecraft:diamond'), gas('water'), item('minecraft:nether_star')) -mods.mekanism.injectionchamber.removeByInput(item('minecraft:hardened_clay'), gas('water')) -//mods.mekanism.injectionchamber.removeAll() +// mods.mekanism.injection_chamber.add(item('minecraft:diamond'), gas('water'), item('minecraft:nether_star')) // Metallurgic Infuser: // Converts and input itemstack and a varible amount of an infusion type into an output itemstack. -mods.mekanism.metallurgicinfuser.recipeBuilder() + +mods.mekanism.metallurgic_infuser.removeByInput(ore('dustObsidian'), 'DIAMOND') +// mods.mekanism.metallurgic_infuser.removeAll() + +mods.mekanism.metallurgic_infuser.recipeBuilder() .input(item('minecraft:nether_star')) .infuse(infusion('groovy_example')) .amount(50) .output(item('minecraft:clay')) .register() -//mods.mekanism.metallurgicinfuser.add(item('minecraft:nether_star'), infusion('groovy_example'), 50, item('minecraft:clay')) -mods.mekanism.metallurgicinfuser.removeByInput(ore('dustObsidian'), 'DIAMOND') -//mods.mekanism.metallurgicinfuser.removeAll() +// mods.mekanism.metallurgic_infuser.add(item('minecraft:nether_star'), infusion('groovy_example'), 50, item('minecraft:clay')) // Osmium Compressor: -// Converts an input itemstack and 200 of a gasstack into an output itemstack. By default, will use Liquid Osmium as the gasstack -mods.mekanism.osmiumcompressor.recipeBuilder() +// Converts an input itemstack and 200 of a gasstack into an output itemstack. By default, will use Liquid Osmium as the +// gasstack. + +mods.mekanism.osmium_compressor.removeByInput(ore('dustRefinedObsidian'), gas('liquidosmium')) +// mods.mekanism.osmium_compressor.removeAll() + +mods.mekanism.osmium_compressor.recipeBuilder() .input(item('minecraft:diamond')) - .gasInput(gas('hydrogen')) // Optional GasStack, default liquidosmium. Always uses 200 + .gasInput(gas('hydrogen')) // Always uses 200 gas .output(item('minecraft:nether_star')) .register() -//mods.mekanism.osmiumcompressor.add(item('minecraft:diamond'), gas('hydrogen'), item('minecraft:nether_star')) -mods.mekanism.osmiumcompressor.removeByInput(ore('dustRefinedObsidian'), gas('liquidosmium')) -//mods.mekanism.osmiumcompressor.removeAll() +// mods.mekanism.osmium_compressor.add(item('minecraft:diamond'), gas('hydrogen'), item('minecraft:nether_star')) - -// Pressurized Reaction Chamber (PRC): +// Pressurized Reaction Chamber: // Converts an input fluidstack, gasstack, and optional itemstack into an output gasstack and optional itemstack. -mods.mekanism.pressurizedreactionchamber.recipeBuilder() + +mods.mekanism.pressurized_reaction_chamber.removeByInput(ore('logWood'), fluid('water'), gas('oxygen')) +// mods.mekanism.pressurized_reaction_chamber.removeAll() + +mods.mekanism.pressurized_reaction_chamber.recipeBuilder() .fluidInput(fluid('water')) .gasInput(gas('water')) - .input(item('minecraft:clay_ball')) // Optional IIngredient - .output(item('minecraft:diamond')) // Optional ItemStack + .input(item('minecraft:clay_ball')) .gasOutput(gas('ethene')) .register() -mods.mekanism.pressurizedreactionchamber.recipeBuilder() - .fluidInput(fluid('lava')) - .gasInput(gas('water') * 100) - .gasOutput(gas('sulfuricacid') * 5) - .register() -mods.mekanism.pressurizedreactionchamber.removeByInput(ore('logWood'), fluid('water'), gas('oxygen')) -//mods.mekanism.pressurizedreactionchamber.removeAll() +// Purification Chamber: +// Converts an input itemstack and gasstack into an output itemstack. +mods.mekanism.purification_chamber.removeByInput(item('mekanism:oreblock:0'), gas('oxygen')) +// mods.mekanism.purification_chamber.removeAll() -// Purification Chamber (Purifier): -// Converts an input itemstack and gasstack into an output itemstack. -mods.mekanism.purificationchamber.recipeBuilder() +mods.mekanism.purification_chamber.recipeBuilder() .input(item('minecraft:diamond')) .gasInput(gas('deuterium')) .output(item('minecraft:nether_star')) .register() -//mods.mekanism.purificationchamber.add(item('minecraft:diamond'), gas('oxygen'), item('minecraft:nether_star')) -mods.mekanism.purificationchamber.removeByInput(item('mekanism:oreblock:0'), gas('oxygen')) -//mods.mekanism.purificationchamber.removeAll() +// mods.mekanism.purification_chamber.add(item('minecraft:diamond'), gas('oxygen'), item('minecraft:nether_star')) // Sawmill: // Converts an input itemstack into an output itemstack, with an optional additional output. + +mods.mekanism.sawmill.removeByInput(item('minecraft:ladder')) +// mods.mekanism.sawmill.removeAll() + mods.mekanism.sawmill.recipeBuilder() .input(item('minecraft:diamond_block')) .output(item('minecraft:diamond') * 9) - .extra(item('minecraft:clay_ball')) // Optional ItemStack, adds an extra chanced output - .chance(0.7) // Optional double, defaults to 1.0 + .extra(item('minecraft:clay_ball')) .register() -//mods.mekanism.sawmill.add(item('minecraft:diamond_block'), item('minecraft:diamond') * 9, item('minecraft:clay_ball'), 0.7) -mods.mekanism.sawmill.removeByInput(item('minecraft:ladder')) -//mods.mekanism.sawmill.removeAll() +// mods.mekanism.sawmill.add(item('minecraft:diamond_block'), item('minecraft:diamond') * 9, item('minecraft:clay_ball'), 0.7) + +// Smelting: +// Converts an input itemstack into an output itemstack in a recipe exclusive to the Smelter. Overrides the default furnace +// recipe, if applicable. + +// mods.mekanism.smelting.removeByInput(item('minecraft:clay')) +// mods.mekanism.smelting.removeAll() -// Smelting (Smelter): -// Converts an input itemstack into an output itemstack in a recipe exclusive to the Smelter. Overrides the default furnace recipe, if applicable. -// WARNING: Exclusive recipes are not displayed in JEI. mods.mekanism.smelting.recipeBuilder() .input(item('minecraft:clay_ball')) .output(item('minecraft:clay')) .register() -//mods.mekanism.smelting.add(item('minecraft:diamond_block'), item('minecraft:clay')) -// No recipes are exclusive to the Energised Smelter by default -//mods.mekanism.smelting.removeByInput(item('minecraft:clay')) -//mods.mekanism.smelting.removeAll() +// mods.mekanism.smelting.add(item('minecraft:diamond_block'), item('minecraft:clay')) -// Solar Neutron Activator (SNA): +// Solar Neutron Activator: // Converts an input gasstack into an output gasstack while exposed to the sun. -mods.mekanism.solarneutronactivator.recipeBuilder() + +mods.mekanism.solar_neutron_activator.removeByInput(gas('lithium')) +// mods.mekanism.solar_neutron_activator.removeAll() + +mods.mekanism.solar_neutron_activator.recipeBuilder() .gasInput(gas('water')) .gasOutput(gas('hydrogen')) .register() -//mods.mekanism.solarneutronactivator.add(gas('water'), gas('hydrogen')) -mods.mekanism.solarneutronactivator.removeByInput(gas('lithium')) -//mods.mekanism.solarneutronactivator.removeAll() +// mods.mekanism.solar_neutron_activator.add(gas('water'), gas('hydrogen')) -// Thermal Evaporation Plant (Thermal Evaporation, TEP): +// Thermal Evaporation Plant: // Converts an input fluidstack into an output fluidstack over time based on multiblock temperature. -mods.mekanism.thermalevaporationplant.recipeBuilder() + +mods.mekanism.thermal_evaporation_plant.removeByInput(fluid('water')) +// mods.mekanism.thermal_evaporation_plant.removeAll() + +mods.mekanism.thermal_evaporation_plant.recipeBuilder() .fluidInput(fluid('water')) .fluidOutput(fluid('steam')) .register() -//mods.mekanism.thermalevaporationplant.add(fluid('water'), fluid('steam')) -mods.mekanism.thermalevaporationplant.removeByInput(fluid('water')) -//mods.mekanism.thermalevaporationplant.removeAll() +// mods.mekanism.thermal_evaporation_plant.add(fluid('water'), fluid('steam')) // Washer: // Converts an input gasstack into an output gasstack at the cost of 5mb of water. + +mods.mekanism.washer.removeByInput(gas('iron')) +// mods.mekanism.washer.removeAll() + mods.mekanism.washer.recipeBuilder() .gasInput(gas('water') * 10) .gasOutput(gas('hydrogen') * 20) .register() -//mods.mekanism.washer.add(gas('water'), gas('hydrogen')) -mods.mekanism.washer.removeByInput(gas('iron')) -//mods.mekanism.washer.removeAll() + +// mods.mekanism.washer.add(gas('water'), gas('hydrogen')) + diff --git a/examples/postInit/roots.groovy b/examples/postInit/roots.groovy index cf6524951..23012d0b1 100644 --- a/examples/postInit/roots.groovy +++ b/examples/postInit/roots.groovy @@ -1,107 +1,85 @@ +// Auto generated groovyscript example file // MODS_LOADED: roots + println 'mod \'roots\' detected, running script' -// Bracket Handlers - -// Cost Bracket Handler -cost('no_cost') -cost('additional_cost') -cost('all_cost_multiplier') -cost('specific_cost_adjustment') -cost('specific_cost_multiplier') - -// Herb Bracket Handler -herb('spirit_herb') -herb('baffle_cap') -herb('moonglow_leaf') -herb('pereskia') -herb('terra_moss') -herb('wildroot') -herb('wildewheet') -herb('infernal_bulb') -herb('dewgonia') -herb('stalicripe') -herb('cloud_berry') - -// Spell Bracket Handler -spell('roots:spell_geas') -spell('spell_geas') // Automatically adds `roots:` -spell('geas') // Automatically adds `roots:spell_` - -// Modifier Bracket Handler -modifier('roots:extended_geas') - -// Ritual Bracket Handler -ritual('ritual_summon_creatures') -ritual('summon_creatures') // Automatically prefixes `ritual_` +// Animal Harvest: +// Animal Harvest is a ritual that drops items from nearby mob's based on that mobs loottable without harming the mob. Only +// applies to allowed mobs. +mods.roots.animal_harvest.removeByEntity(entity('minecraft:pig')) +mods.roots.animal_harvest.removeByName(resource('roots:chicken')) +// mods.roots.animal_harvest.removeAll() -// Animal Harvest: -// Animal Harvest is a ritual that drops items from nearby mobs based on that mob's loottable without harming the mob. Only applies to allowed mobs. -mods.roots.animalharvest.recipeBuilder() - .name('wither_skeleton_harvest') // Optional, either a ResourceLocation or a String - .entity(entity('minecraft:wither_skeleton')) // Target Entity must extend EntityLivingBase +mods.roots.animal_harvest.recipeBuilder() + .name('wither_skeleton_harvest') + .entity(entity('minecraft:wither_skeleton')) .register() -mods.roots.animalharvest.recipeBuilder() +mods.roots.animal_harvest.recipeBuilder() .entity(entity('minecraft:enderman')) .register() -mods.roots.animalharvest.removeByName(resource('roots:chicken')) -mods.roots.animalharvest.removeByEntity(entity('minecraft:pig')) -//mods.roots.animalharvest.removeAll() - // Animal Harvest Fish: -// Animal Harvest Fish is another effect of the Animal Harvest ritual that applies if there are water source blocks within the ritual range. -mods.roots.animalharvestfish.recipeBuilder() - .name('clay_fish') // Optional, either a ResourceLocation or a String +// Animal Harvest Fish is another effect of the Animal Harvest ritual that applies if there are water source blocks within +// the ritual range. + +mods.roots.animal_harvest_fish.removeByFish(item('minecraft:fish:2')) +mods.roots.animal_harvest_fish.removeByName(resource('roots:cod')) +mods.roots.animal_harvest_fish.removeByOutput(item('minecraft:fish:1')) +// mods.roots.animal_harvest_fish.removeAll() + +mods.roots.animal_harvest_fish.recipeBuilder() + .name('clay_fish') .weight(50) .output(item('minecraft:clay')) .register() -mods.roots.animalharvestfish.recipeBuilder() +mods.roots.animal_harvest_fish.recipeBuilder() .weight(13) - .fish(item('minecraft:gold_ingot')) // fish is an alternative to output + .fish(item('minecraft:gold_ingot')) .register() -mods.roots.animalharvestfish.removeByName(resource('roots:cod')) -mods.roots.animalharvestfish.removeByOutput(item('minecraft:fish:1')) -mods.roots.animalharvestfish.removeByFish(item('minecraft:fish:2')) -//mods.roots.animalharvestfish.removeAll() - // Bark Carving: -// Bark Carving is a special set of alternate drops for blocks when broken with an item containing the tool type 'knife'. Amount dropped is up to 2 + fortune/looting level higher than the set amount. -// bark or barkcarving -mods.roots.bark.recipeBuilder() // also accessible via BarkCarving - .name('gold_bark') // Optional, either a ResourceLocation or a String - .input(item('minecraft:clay')) // An item that would be dropped by a block when broken +// Bark Carving is a special set of alternate drops for blocks when broken with an item containing the tool type 'knife'. +// Amount dropped is up to 2 + fortune/looting level higher than the set amount. + +mods.roots.bark_carving.removeByBlock(item('minecraft:log:1')) +mods.roots.bark_carving.removeByInput(item('minecraft:log')) +mods.roots.bark_carving.removeByName(resource('roots:wildwood')) +mods.roots.bark_carving.removeByOutput(item('roots:bark_dark_oak')) +// mods.roots.bark_carving.removeAll() + +mods.roots.bark_carving.recipeBuilder() + .name('gold_bark') + .input(item('minecraft:clay')) .output(item('minecraft:gold_ingot')) .register() -mods.roots.bark.recipeBuilder() +mods.roots.bark_carving.recipeBuilder() .blockstate(blockstate('minecraft:gold_block')) .output(item('minecraft:diamond')) .register() -mods.roots.bark.recipeBuilder() +mods.roots.bark_carving.recipeBuilder() .input(blockstate('minecraft:diamond_block')) .output(item('minecraft:clay') * 10) .register() -mods.roots.bark.removeByName(resource('roots:wildwood')) -mods.roots.bark.removeByInput(item('minecraft:log')) -mods.roots.bark.removeByBlock(item('minecraft:log:1')) -mods.roots.bark.removeByOutput(item('roots:bark_dark_oak')) -//mods.roots.bark.removeAll() - // Chrysopoeia: // Chrysopoeia is a spell that transmutes items held in the main hand. + +mods.roots.chrysopoeia.removeByInput(item('minecraft:rotten_flesh')) +mods.roots.chrysopoeia.removeByName(resource('roots:gold_from_silver')) +mods.roots.chrysopoeia.removeByOutput(item('minecraft:iron_nugget')) +// mods.roots.chrysopoeia.removeAll() + mods.roots.chrysopoeia.recipeBuilder() - .name('clay_transmute') // Optional, either a ResourceLocation or a String + .name('clay_transmute') .input(item('minecraft:gold_ingot')) .output(item('minecraft:clay')) .register() @@ -111,352 +89,290 @@ mods.roots.chrysopoeia.recipeBuilder() .output(item('minecraft:gold_ingot') * 3) .register() -mods.roots.chrysopoeia.removeByName(resource('roots:gold_from_silver')) -mods.roots.chrysopoeia.removeByInput(item('minecraft:rotten_flesh')) -mods.roots.chrysopoeia.removeByOutput(item('minecraft:iron_nugget')) -//mods.roots.chrysopoeia.removeAll() - // Fey Crafter: -// The Fey Crafter is a crafting mechanism that requires an activated Grove Stone nearby to take 5 item inputs and return an item output. -mods.roots.feycrafter.recipeBuilder() - .name('clay_craft') // Optional, either a ResourceLocation or a String +// The Fey Crafter is a crafting mechanism that requires an activated Grove Stone nearby to take 5 item inputs and return +// an item output. + +mods.roots.fey_crafter.removeByName(resource('roots:unending_bowl')) +mods.roots.fey_crafter.removeByOutput(item('minecraft:gravel')) +// mods.roots.fey_crafter.removeAll() + +mods.roots.fey_crafter.recipeBuilder() + .name('clay_craft') .input(item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone')) // Must be exactly 5 .output(item('minecraft:clay')) - .xp(100) // Optional, int + .xp(100) .register() -mods.roots.feycrafter.removeByName(resource('roots:unending_bowl')) // WARNING: When reloading recipes with the Fey Crafter, you may encounter a ConcurrentModificationException! -mods.roots.feycrafter.removeByOutput(item('minecraft:gravel')) -//mods.roots.feycrafter.removeAll() - // Flower Generation: -// When running the Flower Growth Ritual, allowed flowers will generate in the area. Additionally, using the spell Growth Infusion's Floral Reproduction modifier will duplicate the flower. -mods.roots.flowergeneration.recipeBuilder() - .name('clay_flower') // Optional, either a ResourceLocation or a String +// When running the Flower Growth Ritual, allowed flowers will generate in the area. Additionally, using the spell Growth +// Infusion's Floral Reproduction modifier will duplicate the flower. + +mods.roots.flower_generation.removeByFlower(block('minecraft:red_flower')) +mods.roots.flower_generation.removeByFlower(block('minecraft:red_flower'), 1) +mods.roots.flower_generation.removeByFlower(blockstate('minecraft:red_flower:2')) +mods.roots.flower_generation.removeByFlower(item('minecraft:red_flower:3')) +mods.roots.flower_generation.removeByName(resource('roots:dandelion')) +// mods.roots.flower_generation.removeAll() + +mods.roots.flower_generation.recipeBuilder() + .name('clay_flower') .flower(blockstate('minecraft:clay')) .register() -mods.roots.flowergeneration.removeByName(resource('roots:dandelion')) -//mods.roots.flowergeneration.removeByFlower(block('minecraft:red_flower')) // Removes by all blockstates of the block -mods.roots.flowergeneration.removeByFlower(block('minecraft:red_flower'), 1) -mods.roots.flowergeneration.removeByFlower(blockstate('minecraft:red_flower:2')) -mods.roots.flowergeneration.removeByFlower(item('minecraft:red_flower:3')) -//mods.roots.flowergeneration.removeAll() - // Life Essence: -// When shift right clicking a mob in the Life Essence Pool with Runic Shears, it will drop a Life-Essence, which allows that mob to be spawned via the Creature Summoning ritual. -mods.roots.lifeessence.add(entity('minecraft:wither_skeleton')) +// When shift right clicking a mob in the Life Essence Pool with Runic Shears, it will drop a Life-Essence, which allows +// that mob to be spawned via the Creature Summoning ritual. + +mods.roots.life_essence.remove(entity('minecraft:sheep')) +// mods.roots.life_essence.removeAll() + +mods.roots.life_essence.add(entity('minecraft:wither_skeleton')) -mods.roots.lifeessence.remove(entity('minecraft:sheep')) -//mods.roots.lifeessence.removeAll() +// Modifiers: +// Controls what spell modifiers are enabled and can be used. +mods.roots.modifiers.disable(spell('spell_geas')) +// mods.roots.modifiers.disableAll() + +mods.roots.modifiers.enable(modifier('roots:weakened_response')) +mods.roots.modifiers.enable(resource('roots:animal_savior')) +mods.roots.modifiers.enable('extended_geas') // Mortar And Pestle: -// When right clicking a mortar containing the input items with a pestle, it will display a few colored sparkles, consume the inputs, and drop the item output. -mods.roots.mortar.recipeBuilder() // also accessible via MortarAndPestle - .name('clay_mortar') // Optional, either a ResourceLocation or a String - .input(item('minecraft:stone'),item('minecraft:gold_ingot'),item('minecraft:stone'),item('minecraft:gold_ingot'),item('minecraft:stone')) // Between 1 and 5 - .generate(false) // Optional, when inputs = 3 and generate isnt disabled, creates a recipe for each amount of items +// When right clicking a mortar containing the input items with a pestle, it will display a few colored sparkles, consume +// the inputs, and drop the item output. + +mods.roots.mortar.removeByName(resource('roots:wheat_flour')) +mods.roots.mortar.removeByOutput(item('minecraft:string')) +// mods.roots.mortar.removeAll() + +mods.roots.mortar.recipeBuilder() + .name('clay_mortar') + .input(item('minecraft:stone'),item('minecraft:gold_ingot'),item('minecraft:stone'),item('minecraft:gold_ingot'),item('minecraft:stone')) + .generate(false) .output(item('minecraft:clay')) - .color(1, 0, 0.1, 1, 0, 0.1) // Optional, sets color as red1, green1, blue1, red2, green2, blue2. All values must be a float between 0 and 1 + .color(1, 0, 0.1, 1, 0, 0.1) .register() -mods.roots.mortarandpestle.recipeBuilder() - .input(item('minecraft:clay')) // With generate being true and only 1 input, this will generate a recipe for each amount of inputs to 5. Without this, only 1 clay could be converted at a time +mods.roots.mortar.recipeBuilder() + .input(item('minecraft:clay')) .output(item('minecraft:diamond')) - .color(0, 0, 0.1) // Optional, sets the color as red, green, blue. All values must be a float between 0 and 1 + .color(0, 0, 0.1) .register() mods.roots.mortar.recipeBuilder() .input(item('minecraft:diamond'), item('minecraft:diamond')) .output(item('minecraft:gold_ingot') * 16) - .red(0) // Optional, sets red1 and red2. All values must be a float between 0 and 1 - .green1(0.5) // Optional. The value must be a float between 0 and 1 - .green2(1) // Optional. The value must be a float between 0 and 1 + .red(0) + .green1(0.5) + .green2(1) .register() -mods.roots.mortar.removeByName(resource('roots:wheat_flour')) // Many Mortar recipes are generated and have the resource location of [base]_x, where x is the output. They can all be removed by running removeByName(base) -mods.roots.mortar.removeByOutput(item('minecraft:string')) -//mods.roots.mortar.removeAll() - // Moss: -// Moss indicates a pair of items that can right click the input with a knife to turn it into the output and give a Terra Moss and right click the output with moss spores to turn it into the input. +// Moss indicates a pair of items that can right click the input with a knife to turn it into the output and give a Terra +// Moss and right click the output with moss spores to turn it into the input. + +mods.roots.moss.remove(item('minecraft:cobblestone')) +// mods.roots.moss.removeAll() + mods.roots.moss.recipeBuilder() .input(item('minecraft:gold_block')) .output(item('minecraft:clay')) .register() -mods.roots.moss.add(item('minecraft:stained_glass:3'), item('minecraft:stained_glass:4')) - -mods.roots.moss.remove(item('minecraft:cobblestone')) -//mods.roots.moss.removeAll() +mods.roots.moss.add(item('minecraft:stained_glass:3'), item('minecraft:stained_glass:4')) // Pacifist: // Pacifist is a list of entities which killing will give the player the advancement 'Untrue Pacifist'. + +mods.roots.pacifist.removeByEntity(entity('minecraft:cow')) +mods.roots.pacifist.removeByName(resource('minecraft:chicken')) +// mods.roots.pacifist.removeAll() + mods.roots.pacifist.recipeBuilder() - .name('wither_skeleton_pacifist') // Optional, either a ResourceLocation or a String + .name('wither_skeleton_pacifist') .entity(entity('minecraft:wither_skeleton')) .register() -mods.roots.pacifist.removeByName(resource('minecraft:chicken')) -mods.roots.pacifist.removeByEntity(entity('minecraft:cow')) -//mods.roots.pacifist.removeAll() + +// Predicates: +// Predicates are used in Transmution and RunicShearBlock. They either match all blockstates of a block, or all blockstates +// that have the given properties that match the input blockstate. + +mods.roots.predicates.stateBuilder() + .blockstate(blockstate('minecraft:red_flower')) + .register() + +mods.roots.predicates.stateBuilder() + .block(block('minecraft:red_flower')) + .register() + +mods.roots.predicates.stateBuilder() + .blockstate(blockstate('minecraft:red_flower:type=poppy')) + .properties('type') + .register() + +mods.roots.predicates.stateBuilder() + .blockstate(blockstate('minecraft:log:axis=z:variant=oak')) + .properties('axis') + .above() + .register() + +mods.roots.predicates.stateBuilder() + .blockstate(blockstate('minecraft:log')) + .below() + .register() // Pyre: // Converts 5 input items into the ouput after a period of time when the Pyre is lit on fire. + +mods.roots.pyre.removeByName(resource('roots:infernal_bulb')) +mods.roots.pyre.removeByOutput(item('minecraft:gravel')) +// mods.roots.pyre.removeAll() + mods.roots.pyre.recipeBuilder() - .name('clay_from_fire') // Optional, either a ResourceLocation or a String + .name('clay_from_fire') .input(item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone'),item('minecraft:stone')) .output(item('minecraft:clay')) - .xp(5) // Optional, XP given when the recipe finishes in levels. Default 0 - .time(1) // Optional, time in ticks for the recipe to procress. Default 200 + .xp(5) + .time(1) .register() mods.roots.pyre.recipeBuilder() .input(item('minecraft:gold_ingot'),item('minecraft:clay'),item('minecraft:clay'),item('minecraft:stone'),item('minecraft:stone')) .output(item('minecraft:diamond') * 32) - .levels(5) // Optional, XP given when the recipe finishes in levels. Default 0 - .burnTime(1000) // Optional, time in ticks for the recipe to procress. Default 200 + .levels(5) + .burnTime(1000) .register() -mods.roots.pyre.removeByName(resource('roots:infernal_bulb')) -mods.roots.pyre.removeByOutput(item('minecraft:gravel')) -//mods.roots.pyre.removeAll() + +// Rituals: +// Set the Pyre Ritual recipe and control all stats. Dump the modifiable stats into `roots.log` by running `/roots +// rituals`. + +mods.roots.rituals.recipeBuilder() + .ritual(ritual('ritual_healing_aura')) + .input(item('minecraft:clay'),item('minecraft:gold_ingot'),item('minecraft:gold_ingot'),item('minecraft:gold_ingot'),item('minecraft:gold_ingot')) + .register() // Runic Shear Block: -// Right clicking a Runic Shear on a block to convert it into a replacement block and drop items -mods.roots.runicshearblock.recipeBuilder() - .name('clay_from_runic_diamond') // Optional, either a ResourceLocation or a String - .state(blockstate('minecraft:diamond_block')) // Either an IBlockState or BlockStatePredicate - .replacementState(blockstate('minecraft:air')) // NOTE: Not displayed in JEI +// Right clicking a Runic Shear on a block to convert it into a replacement block and drop items. + +mods.roots.runic_shear_block.removeByName(resource('roots:wildewheet')) +mods.roots.runic_shear_block.removeByOutput(item('roots:spirit_herb')) +mods.roots.runic_shear_block.removeByState(blockstate('minecraft:beetroots:age=3')) +// mods.roots.runic_shear_block.removeAll() + +mods.roots.runic_shear_block.recipeBuilder() + .name('clay_from_runic_diamond') + .state(blockstate('minecraft:diamond_block')) + .replacementState(blockstate('minecraft:air')) .output(item('minecraft:clay') * 64) - .displayItem(item('minecraft:diamond') * 9) // Optional, represents the state. Otherwise, is the first itemstack found from the given state + .displayItem(item('minecraft:diamond') * 9) .register() -mods.roots.runicshearblock.recipeBuilder() +mods.roots.runic_shear_block.recipeBuilder() .state(mods.roots.predicates.stateBuilder().blockstate(blockstate('minecraft:yellow_flower:type=dandelion')).properties('type').register()) .replacementState(blockstate('minecraft:red_flower:type=poppy')) .output(item('minecraft:gold_ingot')) .register() -mods.roots.runicshearblock.removeByName(resource('roots:wildewheet')) -mods.roots.runicshearblock.removeByOutput(item('roots:spirit_herb')) -mods.roots.runicshearblock.removeByState(blockstate('minecraft:beetroots:age=3')) -//mods.roots.runicshearblock.removeAll() - // Runic Shear Entity: // Right clicking a Runic Shear on an entity. The entity will have a cooldown, preventing spamming. -// === WARNING: Not Reloadable === -mods.roots.runicshearentity.recipeBuilder() - .name('clay_from_wither_skeletons') // Optional, either a ResourceLocation or a String + +mods.roots.runic_shear_entity.removeByEntity(entity('minecraft:chicken')) +mods.roots.runic_shear_entity.removeByName(resource('roots:slime_strange_ooze')) +mods.roots.runic_shear_entity.removeByOutput(item('roots:fey_leather')) +// mods.roots.runic_shear_entity.removeAll() + +mods.roots.runic_shear_entity.recipeBuilder() + .name('clay_from_wither_skeletons') .entity(entity('minecraft:wither_skeleton')) .output(item('minecraft:clay')) - .cooldown(1000) // Optional, time in ticks between harvesting. Default of 0 + .cooldown(1000) .register() -mods.roots.runicshearentity.recipeBuilder() - .name('creeper_at_the_last_moment') // Optional, either a ResourceLocation or a String +mods.roots.runic_shear_entity.recipeBuilder() + .name('creeper_at_the_last_moment') .entity(entity('minecraft:creeper')) - .output(item('minecraft:diamond'), item('minecraft:nether_star')) // WARNING: JEI will not display this properly. Add a tooltip to the first item informing about all options. - .functionMap({ entityLivingBase -> - // If the creeper has ignited (been right clicked with Flint and Steel), it will drop a Nether Star. Otherwise, its just dirt. - if (entityLivingBase.hasIgnited()) return item('minecraft:nether_star') - return item('minecraft:dirt') - }) + .output(item('minecraft:diamond'), item('minecraft:nether_star')) + .functionMap({ entityLivingBase -> entityLivingBase.hasIgnited() ? item('minecraft:nether_star') : item('minecraft:dirt') }) .register() -mods.roots.runicshearentity.recipeBuilder() +mods.roots.runic_shear_entity.recipeBuilder() .entity(entity('minecraft:witch')) .output(item('minecraft:clay')) .register() -mods.roots.runicshearentity.removeByName(resource('roots:slime_strange_ooze')) -mods.roots.runicshearentity.removeByOutput(item('roots:fey_leather')) -mods.roots.runicshearentity.removeByEntity(entity('minecraft:chicken')) -//mods.roots.runicshearentity.removeAll() - - -// Summon Creature: -// When running a Summon Creature Ritual, the input items placed on Catalyst Plate will summon the target entity -mods.roots.summoncreature.recipeBuilder() - .name('wither_skeleton_from_clay') // Optional, either a ResourceLocation or a String - .input(item('minecraft:clay'), item('minecraft:clay'), item('minecraft:clay'), item('minecraft:clay'), item('minecraft:clay')) // Between 1 and 10 - .entity(entity('minecraft:wither_skeleton')) - .register() - -mods.roots.summoncreature.removeByName(resource('roots:cow')) -mods.roots.summoncreature.removeByEntity(entity('minecraft:chicken')) -//mods.roots.summoncreature.removeAll() +// Spells: +// Controls the recipe for the given spell, the sound, all properties, the base cost, and each modifier's cost. -// Transmutation: -// When running the Transmutation, convert nearby blocks that match a set of conditions into either a block or items. -mods.roots.transmutation.recipeBuilder() - .name('clay_duping') // Optional, either a ResourceLocation or a String - .start(blockstate('minecraft:clay')) // Either an IBlockState or BlockStatePredicate - .output(item('minecraft:clay_ball') * 30) // Either a state or output must be defined - .condition(mods.roots.predicates.stateBuilder().blockstate(blockstate('minecraft:gold_block')).below().register()) // Optional, must be a WorldBlockStatePredicate (BlockStateAbove, or BlockStateBelow) +mods.roots.spells.costBuilder() .register() -mods.roots.transmutation.recipeBuilder() - .start(mods.roots.predicates.stateBuilder().blockstate(blockstate('minecraft:yellow_flower:type=dandelion')).properties('type').register()) // Either an IBlockState or BlockStatePredicate - .state(blockstate('minecraft:gold_block')) - .condition(mods.roots.predicates.above(mods.roots.predicates.LEAVES)) +mods.roots.spells.costBuilder() + .cost(cost('additional_cost'), herb('dewgonia'), 0.25) .register() -mods.roots.transmutation.recipeBuilder() - .start(blockstate('minecraft:diamond_block')) - .state(blockstate('minecraft:gold_block')) +mods.roots.spells.costBuilder() + .cost(cost('additional_cost'), herb('spirit_herb'), 0.1) + .cost(cost('all_cost_multiplier'), null, -0.125) .register() -mods.roots.transmutation.removeByName(resource('roots:redstone_block_to_glowstone')) -mods.roots.transmutation.removeByOutput(item('minecraft:dye:3')) -mods.roots.transmutation.removeByOutput(blockstate('minecraft:log:variant=jungle')) -//mods.roots.transmutation.removeAll() - - -// Predicates: -// Predicates are used in Transmution and RunicShearBlock. They either match all blockstates of a block, or all blockstates that have the given properties that match the input blockstate. -// When used in Transmutation, they may require a direction (above or below) to be set. -mods.roots.predicates.stateBuilder() - .blockstate(blockstate('minecraft:red_flower')) // Because this is has no 'properties' set, any blockstate of the base block will work. +mods.roots.spells.recipeBuilder() + .spell(spell('spell_fey_light')) + .input(item('minecraft:clay'), item('minecraft:diamond'), item('minecraft:gold_ingot')) .register() -mods.roots.predicates.stateBuilder() - .block(block('minecraft:red_flower')) // This is identical to the prior predicate. - .register() -mods.roots.predicates.stateBuilder() - .blockstate(blockstate('minecraft:red_flower:type=poppy')) - .properties('type') // Optional, contains a String..., String[], or List. Controls what properties are matched against with the provided blockstate - .register() +// Summon Creature: +// When running a Summon Creature Ritual, the input items placed on Catalyst Plate will summon the target entity. -mods.roots.predicates.stateBuilder() // Matches any log on the z-axis, as 'properties' only checks 'axis'. Matches above the target in Transmutation. - .blockstate(blockstate('minecraft:log:axis=z:variant=oak')) - .properties('axis') - .above() // Optional, used for Transmution to indicate the required direction relative to the primary block. - .register() +mods.roots.summon_creature.removeByEntity(entity('minecraft:chicken')) +mods.roots.summon_creature.removeByName(resource('roots:cow')) +// mods.roots.summon_creature.removeAll() -mods.roots.predicates.stateBuilder() // Matches any log, regardless of axis or variant, below the target in Transmutation. - .blockstate(blockstate('minecraft:log')) - .below() // Optional, used for Transmution to indicate the required direction relative to the primary block. +mods.roots.summon_creature.recipeBuilder() + .name('wither_skeleton_from_clay') + .input(item('minecraft:clay'), item('minecraft:clay'), item('minecraft:clay'), item('minecraft:clay'), item('minecraft:clay')) + .entity(entity('minecraft:wither_skeleton')) .register() -// A few constant values are provided: -mods.roots.predicates.ANY // Matches everything -mods.roots.predicates.TRUE // Matches everything -mods.roots.predicates.LAVA // Matches a Lava source block. Required to render properly in JEI -mods.roots.predicates.WATER // Matches a Water source block. Required to render properly in JEI -mods.roots.predicates.LEAVES // Matches any leaf block - -// Converts a BlockStatePredicate into a WorldBlockStatePredicate (BlockStateAbove, or BlockStateBelow) -mods.roots.predicates.above(mods.roots.predicates.LAVA) -mods.roots.predicates.below(mods.roots.predicates.LAVA) - -mods.roots.predicates.create(blockstate('minecraft:red_flower')) -mods.roots.predicates.create(blockstate('minecraft:log:axis=z:variant=oak'), 'axis', 'variant') +// Transmutation: +// When running the Transmutation, convert nearby blocks that match a set of conditions into either a block or items. +mods.roots.transmutation.removeByName(resource('roots:redstone_block_to_glowstone')) +mods.roots.transmutation.removeByOutput(blockstate('minecraft:log:variant=jungle')) +mods.roots.transmutation.removeByOutput(item('minecraft:dye:3')) +// mods.roots.transmutation.removeAll() -// Rituals: -// Set the Pyre Ritual recipe and control all stats. Dump the modifiable stats into `roots.log` by running `/roots rituals`. -// WARNING: When reloading scripts, changes made are never undone. -mods.roots.rituals.recipeBuilder() - .ritual(ritual('ritual_healing_aura')) - .input(item('minecraft:clay'),item('minecraft:gold_ingot'),item('minecraft:gold_ingot'),item('minecraft:gold_ingot'),item('minecraft:gold_ingot')) // Exactly 5 input items +mods.roots.transmutation.recipeBuilder() + .name('clay_duping') + .start(blockstate('minecraft:clay')) + .output(item('minecraft:clay_ball') * 30) + .condition(mods.roots.predicates.stateBuilder().blockstate(blockstate('minecraft:gold_block')).below().register()) .register() -mods.roots.rituals.ritual(ritual('ritual_summon_creatures')) - .recipe(item('minecraft:diamond'),item('minecraft:diamond'),item('minecraft:diamond'),item('minecraft:clay'),item('minecraft:clay')) - .setDuration(10) - .setProperty('tries', 10) - .setProperty('radius_z', 1) - .setProperty('radius_y', 10) - .setProperty('glow_duration', 100) - .setProperty('interval', 1) - .setProperty('radius_x', 1) - -mods.roots.rituals.ritual(ritual('ritual_spreading_forest')) - .setDisabled() - -//mods.roots.rituals.disableAll() +mods.roots.transmutation.recipeBuilder() + .start(mods.roots.predicates.stateBuilder().blockstate(blockstate('minecraft:yellow_flower:type=dandelion')).properties('type').register()) + .state(blockstate('minecraft:gold_block')) + .condition(mods.roots.predicates.above(mods.roots.predicates.LEAVES)) + .register() -// Cost: -// A helper method to generate modifier costs for spells. Output should be used in a setModifierCost of a spell. -def exampleCost = mods.roots.spells.costBuilder() - .cost(cost('additional_cost'), herb('spirit_herb'), 0.1) - .cost(cost('all_cost_multiplier'), null, -0.125) +mods.roots.transmutation.recipeBuilder() + .start(blockstate('minecraft:diamond_block')) + .state(blockstate('minecraft:gold_block')) .register() -// Spells: -// Controls the recipe for the given spell, the sound, all properties, the base cost, and each modifier's cost. -// WARNING: When reloading scripts, changes made are never undone. -mods.roots.spells.recipeBuilder() - .spell(spell('spell_fey_light')) - .input(item('minecraft:clay'),item('minecraft:diamond'),item('minecraft:gold_ingot')) // Up to 5 input items, but < 5 generates an error in the Patchouli book - .register() - -mods.roots.spells.spell(spell('spell_acid_cloud')) - .recipe(item('minecraft:diamond'),item('minecraft:diamond'),item('minecraft:diamond'),item('minecraft:clay'),item('minecraft:clay')) - .setSound(5) // Change the volume of the sound played - .setCooldown(10) - .setDamage(10) - .setProperty('regeneration', 10) // An example list of the modifiable properties - .setProperty('radius_general', 10) - .setProperty('damage_count', 10) - .setProperty('poison_amplification', 10) - .setProperty('healing', 10) - .setProperty('slow_duration', 10) - .setProperty('weakness_duration', 10) - .setProperty('regeneration_amplifier', 10) - .setProperty('weakness_amplifier', 10) - .setProperty('radius_boost', 10) - .setProperty('healing_count', 10) - .setProperty('night_modifier_high', 0.6) - .setProperty('fire_duration', 10) - .setProperty('undead_damage', 10) - .setProperty('slow_amplifier', 10) - .setProperty('underwater_boost', 10) - .setProperty('night_modifier_low', 0.05) - .clearCost() // clearCost or clearSpellCost - .addCost(herb('baffle_cap'), 1.0) // addCost or addSpellCost - .addSpellCost(herb('dewgonia'), 0.25) - .setModifierCost(modifier('roots:moonfall'), exampleCost) - .setModifierCost(modifier('roots:radius_boost'), exampleCost) - .setModifierCost(modifier('roots:peaceful_cloud'), exampleCost) - .setModifierCost(modifier('roots:weakening_cloud'), exampleCost) - .setModifierCost(modifier('roots:moonfall'), exampleCost) - .setModifierCost(modifier('roots:unholy_vanquisher'), exampleCost) - .setModifierCost(modifier('roots:healing_cloud'), exampleCost) - .setModifierCost(modifier('roots:increased_speed'), exampleCost) - .setModifierCost(modifier('roots:fire_cloud'), exampleCost) - .setModifierCost(modifier('roots:slowing'), exampleCost) - .setModifierCost(modifier('roots:underwater_increase'), exampleCost) - -mods.roots.spells.spell(spell('spell_harvest')) - .disableSound() - .setSpellCost(herb('baffle_cap'), 0.01) // setCost or setSpellCost - -mods.roots.spells.spell(spell('spell_aqua_bubble')) - .addSpellCost(herb('baffle_cap'), 0.01) - .setSound(0.1) - -mods.roots.spells.spell(spell('spell_geas')) - .setDisabled() - -//mods.roots.spells.disableAll() - -// Modifiers -mods.roots.modifiers.disable(spell('spell_geas')) // Disable all Modifiers for the spell Geas -mods.roots.modifiers.enable(modifier('roots:extended_geas')) // Reenable these three modifiers -mods.roots.modifiers.enable(modifier('roots:animal_savior')) -mods.roots.modifiers.enable(modifier('roots:weakened_response')) -//mods.roots.modifiers.disableAll() + diff --git a/examples/postInit/thaumcraft.groovy b/examples/postInit/thaumcraft.groovy index 65a1d635b..670789bb5 100644 --- a/examples/postInit/thaumcraft.groovy +++ b/examples/postInit/thaumcraft.groovy @@ -1,137 +1,204 @@ +// Auto generated groovyscript example file // MODS_LOADED: thaumcraft + println 'mod \'thaumcraft\' detected, running script' -mods.thaumcraft.Crucible.removeByOutput(item('minecraft:gunpowder')) - -mods.thaumcraft.Crucible.recipeBuilder() - .researchKey('UNLOCKALCHEMY@3') - .catalyst(item('minecraft:rotten_flesh')) - .output(item('minecraft:gold_ingot')) - .aspect(aspect('metallum') * 10) - .register() - -mods.thaumcraft.InfusionCrafting.removeByOutput(item('thaumcraft:crystal_terra')) - -mods.thaumcraft.InfusionCrafting.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() - -mods.thaumcraft.ArcaneWorkbench.removeByOutput(item('thaumcraft:mechanism_simple')) - -mods.thaumcraft.ArcaneWorkbench.shapelessBuilder() - .researchKey('UNLOCKALCHEMY@3') - .input(item('minecraft:pumpkin')) - .input(item('minecraft:stick')) - .input(item('minecraft:stick')) - .output(item('thaumcraft:void_hoe')) - .vis(0) - .register() - -mods.thaumcraft.ArcaneWorkbench.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.Aspect.aspectBuilder() -// .tag('humor') -// .chatColor(14013676) -// .component(aspect('cognito')) -// .component('perditio') -// .image(resource('thaumcraft:textures/aspects/humor.png')) -// .register() - -mods.thaumcraft.AspectHelper.aspectBuilder() - .object(item('minecraft:stone')) - .stripAspects() - .aspect(aspect('ignis') * 20) - .aspect('ordo', 5) - .register() - -mods.thaumcraft.AspectHelper.aspectBuilder() - .object(ore('cropPumpkin')) - .stripAspects() - .aspect(aspect('herba') * 20) - .register() - -mods.thaumcraft.AspectHelper.aspectBuilder() - .entity(entity('minecraft:chicken')) - .stripAspects() - .aspect('bestia', 20) - .register() - -mods.thaumcraft.Warp.addWarp(item('minecraft:pumpkin'), 3) -mods.thaumcraft.Warp.removeWarp(item('thaumcraft:void_hoe')) - -mods.thaumcraft.DustTrigger.removeByOutput(item('thaumcraft:arcane_workbench')) - -mods.thaumcraft.DustTrigger.triggerBuilder() - .researchKey('UNLOCKALCHEMY@3') - .target(block('minecraft:obsidian')) - .output(item('minecraft:enchanting_table')) - .register() - -mods.thaumcraft.DustTrigger.triggerBuilder() - .researchKey('UNLOCKALCHEMY@3') - .target(ore('cropPumpkin')) - .output(item('minecraft:lit_pumpkin')) - .register() - -mods.thaumcraft.LootBag.getRare().removeAll() -mods.thaumcraft.LootBag.getRare().addItem(item('minecraft:diamond_block'), 100) -mods.thaumcraft.LootBag.getCommon().removeItem(item('minecraft:ender_pearl')) -mods.thaumcraft.LootBag.getCommon().addItem(item('minecraft:dirt'), 100) - -mods.thaumcraft.SmeltingBonus.recipeBuilder() - .input(item('minecraft:cobblestone')) - .output(item('minecraft:stone_button')) - .chance(0.2F) - .register() - -mods.thaumcraft.SmeltingBonus.recipeBuilder() - .input(ore('stone')) - .output(item('minecraft:obsidian')) - .register() - -mods.thaumcraft.SmeltingBonus.removeByOutput(item('minecraft:gold_nugget')) - -//mods.thaumcraft.SmeltingBonus.stream() -// .filter{ bonus -> bonus.in == 'oreDiamond' } -// .removeAll() - -//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')) - -//mods.thaumcraft.Research.removeCategory('BASICS'); \ No newline at end of file +// 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('humor') + .chatColor(14013676) + .component(aspect('cognitio')) + .component('perditio') + .image(resource('thaumcraft:textures/aspects/humor.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/woot.groovy b/examples/postInit/woot.groovy index f94742d10..0ec5bea5a 100644 --- a/examples/postInit/woot.groovy +++ b/examples/postInit/woot.groovy @@ -1,47 +1,22 @@ +// Auto generated groovyscript example file // MODS_LOADED: woot -println 'mod \'woot\' detected, running script' -import ipsis.woot.configuration.EnumConfigKey import ipsis.woot.util.WootMobName -// Note: -// Drops, Spawning, Policy, and Mob Config can also be controlled via .json config file -// Drops can also be modified via `custom_drops.json`, -// Spawning can also be modified via `factory_config.json`, -// Policy and Mob Config can also be modified via `factory_config.json`. - - -// Stygian Iron Anvil: -// Has a catalyst (which may or may not be consumed) placed on the anvil, with the input items thrown atop the base. -// The anvil must be above a Magma Block and then right clicked with a Hammer, converting the input items into the output item. -mods.woot.stygianironanvil.recipeBuilder() - .input(item('minecraft:diamond'),item('minecraft:diamond'),item('minecraft:diamond')) - .base(item('minecraft:gold_ingot')) - .output(item('minecraft:clay')) - .preserveBase(true) // Optional, boolean. Defaults to false - .register() - -mods.woot.anvil.recipeBuilder() - .input(item('minecraft:diamond'), - item('minecraft:gold_ingot'), - item('minecraft:iron_ingot'), - item('minecraft:diamond_block'), - item('minecraft:gold_block'), - item('minecraft:iron_bars'), - item('minecraft:magma')) // Accepts more than 6 items, but JEI only displays the first 6. - .base(item('minecraft:clay')) - .output(item('minecraft:clay')) - .preserveBase() // Toggle preserveBase - .register() +println 'mod \'woot\' detected, running script' -mods.woot.anvil.removeByBase(item('minecraft:iron_bars')) -mods.woot.anvil.removeByOutput(item('woot:stygianironplate')) -//mods.woot.anvil.removeAll() +// Drops: +// Controls extra drops given by mobs. Chance and Size are both arrays 4 long, containing the values for levels 0/1/2/3 +// levels of Looting. +mods.woot.drops.removeByEntity(entity('minecraft:ender_dragon')) +mods.woot.drops.removeByEntity('minecraft:ender_dragon') +mods.woot.drops.removeByEntity('minecraft:ender_dragon', '') +mods.woot.drops.removeByEntity(new WootMobName('minecraft:ender_dragon')) +mods.woot.drops.removeByOutput(item('minecraft:dragon_breath')) +// mods.woot.drops.removeAll() -// Drops: -// Controls extra drops given by mobs. Chance and Size are both arrays 4 long, containing the values for levels 0/1/2/3 levels of Looting. mods.woot.drops.recipeBuilder() .name('minecraft:zombie') .output(item('minecraft:clay')) @@ -49,149 +24,87 @@ mods.woot.drops.recipeBuilder() .size(5, 10, 20, 50) .register() -mods.woot.drops.removeByEntity(new WootMobName('minecraft:ender_dragon')) -mods.woot.drops.removeByEntity(entity('minecraft:ender_dragon')) -mods.woot.drops.removeByEntity('minecraft:ender_dragon') -mods.woot.drops.removeByEntity('minecraft:ender_dragon', '') // NBT tag -mods.woot.drops.removeByOutput(item('minecraft:dragon_breath')) -//mods.woot.drops.removeAll() +// Mob Config: +// Control the default values or mob-specific values for a large number of effects, a full list can be found at +// `ipsis.woot.configuration.EnumConfigKey`. A full list can be viewed on +// [Github](https://github.com/Ipsis/Woot/blob/55e88f5a15d66cc987e676d665d20f4afbe008b8/src/main/java/ipsis/woot/configuration/EnumConfigKey.java#L14) + +mods.woot.mob_config.remove('minecraft:wither_skeleton', 'spawn_units') +mods.woot.mob_config.remove('minecraft:wither') +// mods.woot.mob_config.removeAll() + +mods.woot.mob_config.add('spawn_ticks', 100) +mods.woot.mob_config.add('minecraft:zombie', 'spawn_ticks', 1) + +// Policy: +// Controls what entities can be farmed for what items via an entity blacklist, mod blacklist, item output blacklist, item +// output mod blacklist, and a mob whitelist. + +mods.woot.policy.removeFromEntityBlacklist('twilightforest:naga') +mods.woot.policy.removeFromEntityModBlacklist('botania') +// mods.woot.policy.removeFromEntityWhitelist('minecraft:wither_skeleton') +// mods.woot.policy.removeFromGenerateOnlyList('minecraft:wither_skeleton') +// mods.woot.policy.removeFromItemBlacklist(item('minecraft:sugar')) +mods.woot.policy.removeFromItemModBlacklist('minecraft') +// mods.woot.policy.removeAllFromEntityBlacklist() +// mods.woot.policy.removeAllFromEntityModBlacklist() +// mods.woot.policy.removeAllFromEntityWhitelist() +// mods.woot.policy.removeAllFromGenerateOnlyList() +// mods.woot.policy.removeAllFromItemBlacklist() +// mods.woot.policy.removeAllFromItemModBlacklist() +// mods.woot.policy.removeAll() + +mods.woot.policy.addToEntityBlacklist('minecraft:witch') +// mods.woot.policy.addToEntityModBlacklist('minecraft') +// mods.woot.policy.addToEntityWhitelist('minecraft:zombie') +mods.woot.policy.addToGenerateOnlyList('minecraft:skeleton') +mods.woot.policy.addToItemBlacklist(item('minecraft:gunpowder')) +mods.woot.policy.addToItemModBlacklist('woot') + // Spawning: // Controls item/fluid costs of a given mob or the default cost. + +mods.woot.spawning.remove(new WootMobName('minecraft:ender_dragon')) +mods.woot.spawning.removeByEntity(entity('minecraft:ender_dragon')) +mods.woot.spawning.removeByEntity('minecraft:ender_dragon') +mods.woot.spawning.removeByEntity('minecraft:ender_dragon', '') +mods.woot.spawning.removeByEntity(new WootMobName('minecraft:ender_dragon')) +// mods.woot.spawning.removeAll() + mods.woot.spawning.recipeBuilder() - .name('minecraft:zombie') // Optional, either a name must be defined or the recipe must be the "defaultSpawnRecipe" - .input(item('minecraft:clay')) // up to 6 input items - .fluidInput(fluid('water')) // up to 6 input fluids + .name('minecraft:zombie') + .input(item('minecraft:clay')) + .fluidInput(fluid('water')) .register() mods.woot.spawning.recipeBuilder() - .defaultSpawnRecipe(true) // Optional, either a name must be defined or the recipe must be the "defaultSpawnRecipe" + .defaultSpawnRecipe(true) .input(item('minecraft:gold_ingot'), item('minecraft:diamond')) .register() -mods.woot.spawning.remove(new WootMobName('minecraft:ender_dragon')) -mods.woot.spawning.removeByEntity(new WootMobName('minecraft:ender_dragon')) -mods.woot.spawning.removeByEntity(entity('minecraft:ender_dragon')) -mods.woot.spawning.removeByEntity('minecraft:ender_dragon') -mods.woot.spawning.removeByEntity('minecraft:ender_dragon', '') // NBT tag -//mods.woot.spawning.removeAll() - -// Policy: -// Controls what entities can be farmed for what items via an entity blacklist, mod blacklist, item output blacklist, item output mod blacklist, and a mob whitelist. -// Note: if the whitelist contains any entities, any entities not in the whitelist are banned (rendering EntityModBlacklist and EntityBlacklist superflous). -// GenerateOnlyList contains all entities which cannot be captured via shard, meaning the controller would need to be obtained a different way. -//mods.woot.policy.addToEntityModBlacklist('minecraft') -mods.woot.policy.addToEntityBlacklist('minecraft:witch') // Also takes a WootMobName -mods.woot.policy.addToItemModBlacklist('woot') -mods.woot.policy.addToItemBlacklist(item('minecraft:gunpowder')) -//mods.woot.policy.addToEntityWhitelist('minecraft:zombie') // Also takes a WootMobName -mods.woot.policy.addToGenerateOnlyList('minecraft:skeleton') // Also takes a WootMobName +// Stygian Iron Anvil: +// Has a catalyst (which may or may not be consumed) placed on the anvil, with the input items thrown atop the base. -mods.woot.policy.removeFromEntityModBlacklist('botania') -mods.woot.policy.removeFromEntityBlacklist('twilightforest:naga') // Also takes a WootMobName -//mods.woot.policy.removeFromItemModBlacklist('minecraft') // Note: has no default entries -//mods.woot.policy.removeFromItemBlacklist(item('minecraft:sugar')) // Note: has no default entries -//mods.woot.policy.removeFromEntityWhitelist('minecraft:wither_skeleton') // Note: has no default entries. Also takes a WootMobName -//mods.woot.policy.removeFromGenerateOnlyList('minecraft:wither_skeleton') // Note: has no default entries. Also takes a WootMobName +mods.woot.stygian_iron_anvil.removeByBase(item('minecraft:iron_bars')) +mods.woot.stygian_iron_anvil.removeByOutput(item('woot:stygianironplate')) +// mods.woot.stygian_iron_anvil.removeAll() -//mods.woot.policy.removeAllFromEntityModBlacklist() -//mods.woot.policy.removeAllFromEntityBlacklist() -//mods.woot.policy.removeAllFromItemModBlacklist() // Note: has no default entries -//mods.woot.policy.removeAllFromItemBlacklist() // Note: has no default entries -//mods.woot.policy.removeAllFromEntityWhitelist() // Note: has no default entries -//mods.woot.policy.removeAllFromGenerateOnlyList() // Note: has no default entries +mods.woot.stygian_iron_anvil.recipeBuilder() + .input(item('minecraft:diamond'),item('minecraft:diamond'),item('minecraft:diamond')) + .base(item('minecraft:gold_ingot')) + .output(item('minecraft:clay')) + .preserveBase(true) + .register() -//mods.woot.policy.removeAll() +mods.woot.stygian_iron_anvil.recipeBuilder() + .input(item('minecraft:diamond'), item('minecraft:gold_ingot'), item('minecraft:iron_ingot'), item('minecraft:diamond_block'), item('minecraft:gold_block'), item('minecraft:iron_bars'), item('minecraft:magma')) + .base(item('minecraft:clay')) + .output(item('minecraft:clay')) + .preserveBase() + .register() -// Mob Config: -// Control the default values or mob-specific values for a large number of effects, a full list can be found at -// ipsis.woot.configuration.EnumConfigKey. View on Github via the link: -// https://github.com/Ipsis/Woot/blob/55e88f5a15d66cc987e676d665d20f4afbe008b8/src/main/java/ipsis/woot/configuration/EnumConfigKey.java#L14 - -// Change the default Spawn Ticks interval to 100 (default 320) -mods.woot.mobconfig.add('spawn_ticks', 100) -// Change the Spawn Ticks interval for Zombies to 1 (default the global Spawn Ticks) -mods.woot.mobconfig.add('minecraft:zombie', 'spawn_ticks', 1) - -// Remove the unique cost for Wither Skeletons, making it fallback to the default (default 1) -mods.woot.mobconfig.remove('minecraft:wither_skeleton', 'spawn_units') - -// Remove all config values set for the Wither -mods.woot.mobconfig.remove('minecraft:wither') - -//mods.woot.mobconfig.removeAll() - - -/* Mob-specific overrides for EnumConfigKey: - * SPAWN_TICKS - * KILL_COUNT - * SPAWN_UNITS - * DEATH_XP - * MASS_FX - * FACTORY_TIER - * POWER_PER_UNIT - * T1_POWER_TICK, T2_POWER_TICK, T3_POWER_TICK, T4_POWER_TICK - * RATE_1_POWER_TICK, RATE_2_POWER_TICK, RATE_3_POWER_TICK - * MASS_1_POWER_TICK, MASS_2_POWER_TICK, MASS_3_POWER_TICK - * LOOTING_1_POWER_TICK, LOOTING_2_POWER_TICK, LOOTING_3_POWER_TICK - * DECAP_1_POWER_TICK, DECAP_2_POWER_TICK, DECAP_3_POWER_TICK - * XP_1_POWER_TICK, XP_2_POWER_TICK, XP_3_POWER_TICK - * EFF_1_POWER_TICK, EFF_2_POWER_TICK, EFF_3_POWER_TICK - * BM_LE_TANK_1_POWER_TICK, BM_LE_TANK_2_POWER_TICK, BM_LE_TANK_3_POWER_TICK - * BM_LE_ALTAR_1_POWER_TICK, BM_LE_ALTAR_2_POWER_TICK, BM_LE_ALTAR_3_POWER_TICK - * BM_CRYSTAL_1_POWER_TICK, BM_CRYSTAL_2_POWER_TICK, BM_CRYSTAL_3_POWER_TICK - * EC_BLOOD_1_POWER_TICK, EC_BLOOD_2_POWER_TICK, EC_BLOOD_3_POWER_TICK - * RATE_1_PARAM, RATE_2_PARAM, RATE_3_PARAM - * MASS_1_PARAM, MASS_2_PARAM, MASS_3_PARAM - * DECAP_1_PARAM, DECAP_2_PARAM, DECAP_3_PARAM - * XP_1_PARAM, XP_2_PARAM, XP_3_PARAM - * EFF_1_PARAM, EFF_2_PARAM, EFF_3_PARAM - * BM_LE_TANK_1_PARAM, BM_LE_TANK_2_PARAM, BM_LE_TANK_3_PARAM - * BM_LE_ALTAR_1_PARAM, BM_LE_ALTAR_2_PARAM, BM_LE_ALTAR_3_PARAM - * EC_BLOOD_1_PARAM, EC_BLOOD_2_PARAM, EC_BLOOD_3_PARAM - * BM_CRYSTAL_1_PARAM, BM_CRYSTAL_2_PARAM, BM_CRYSTAL_3_PARAM - */ - -/* Default options for EnumConfigKey (global): - * TARTARUS_ID - * SAMPLE_SIZE - * LEARN_TICKS - * SPAWN_TICKS - * HEADHUNTER_1_CHANCE, HEADHUNTER_2_CHANCE, HEADHUNTER_3_CHANCE - * NUM_MOBS - * KILL_COUNT - * SPAWN_UNITS - * DEATH_XP - * MASS_FX - * FACTORY_TIER - * T1_UNITS_MAX, T2_UNITS_MAX, T3_UNITS_MAX, T4_UNITS_MAX - * POWER_PER_UNIT - * T1_POWER_MAX, T2_POWER_MAX, T3_POWER_MAX - * T1_POWER_RX_TICK, T2_POWER_RX_TICK, T3_POWER_RX_TICK - * T1_POWER_TICK, T2_POWER_TICK, T3_POWER_TICK, T4_POWER_TICK - * T2_SHARD_GEN, T3_SHARD_GEN, T4_SHARD_GEN - * RATE_1_POWER_TICK, RATE_2_POWER_TICK, RATE_3_POWER_TICK - * MASS_1_POWER_TICK, MASS_2_POWER_TICK, MASS_3_POWER_TICK - * LOOTING_1_POWER_TICK, LOOTING_2_POWER_TICK, LOOTING_3_POWER_TICK - * DECAP_1_POWER_TICK, DECAP_2_POWER_TICK, DECAP_3_POWER_TICK - * XP_1_POWER_TICK, XP_2_POWER_TICK, XP_3_POWER_TICK - * EFF_1_POWER_TICK, EFF_2_POWER_TICK, EFF_3_POWER_TICK - * BM_LE_TANK_1_POWER_TICK, BM_LE_TANK_2_POWER_TICK, BM_LE_TANK_3_POWER_TICK - * BM_LE_ALTAR_1_POWER_TICK, BM_LE_ALTAR_2_POWER_TICK, BM_LE_ALTAR_3_POWER_TICK - * BM_CRYSTAL_1_POWER_TICK, BM_CRYSTAL_2_POWER_TICK, BM_CRYSTAL_3_POWER_TICK - * EC_BLOOD_1_POWER_TICK, EC_BLOOD_2_POWER_TICK, EC_BLOOD_3_POWER_TICK - * RATE_1_PARAM, RATE_2_PARAM, RATE_3_PARAM - * MASS_1_PARAM, MASS_2_PARAM, MASS_3_PARAM - * LOOTING_1_PARAM, LOOTING_2_PARAM, LOOTING_3_PARAM - * DECAP_1_PARAM, DECAP_2_PARAM, DECAP_3_PARAM - * XP_1_PARAM, XP_2_PARAM, XP_3_PARAM - * EFF_1_PARAM, EFF_2_PARAM, EFF_3_PARAM - * BM_LE_TANK_1_PARAM, BM_LE_TANK_2_PARAM, BM_LE_TANK_3_PARAM - * BM_LE_ALTAR_1_PARAM, BM_LE_ALTAR_2_PARAM, BM_LE_ALTAR_3_PARAM - * EC_BLOOD_1_PARAM, EC_BLOOD_2_PARAM, EC_BLOOD_3_PARAM - * BM_CRYSTAL_1_PARAM, BM_CRYSTAL_2_PARAM, BM_CRYSTAL_3_PARAM - */ \ No newline at end of file diff --git a/examples/preInit/vanilla.groovy b/examples/preInit/vanilla.groovy index 35d719291..cc253601b 100644 --- a/examples/preInit/vanilla.groovy +++ b/examples/preInit/vanilla.groovy @@ -52,8 +52,8 @@ content.createFluid('amongium') content.registerItem('snack', (new ItemFood(20, 10, false) { protected void onFoodEaten(ItemStack stack, World worldIn, EntityPlayer player) { if (!worldIn.isRemote) { - player.addPotionEffect(new PotionEffect(potion('regeneration'), 240000, 3, false, false)) - player.addPotionEffect(new PotionEffect(potion('resistance'), 240000, 3, false, false)) + player.addPotionEffect(new PotionEffect(potion('minecraft:regeneration'), 240000, 3, false, false)) + player.addPotionEffect(new PotionEffect(potion('minecraft:resistance'), 240000, 3, false, false)) } } }).setAlwaysEdible()) diff --git a/gradle.properties b/gradle.properties index da7ce1cb1..d5e2cef55 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,9 +1,12 @@ # Debug mod compat debug_actually_additions = false debug_adv_mortars = false +debug_aether = false +debug_alchemistry = false debug_applied_energistics_2 = false debug_astral = false debug_avaritia = false +debug_better_with_mods = false debug_blood_magic = false debug_botania = false debug_chisel = false @@ -39,6 +42,7 @@ modGroup = com.cleanroommc.groovyscript modVersion = 0.8.0 groovy_version = 4.0.13 debug_use_examples_folder = true +debug_run_ls = true # Whether to use the old jar naming structure (modid-mcversion-version) instead of the new version (modid-version) includeMCVersionJar = false diff --git a/src/main/java/com/cleanroommc/groovyscript/GroovyScript.java b/src/main/java/com/cleanroommc/groovyscript/GroovyScript.java index be42fe252..785bfffde 100644 --- a/src/main/java/com/cleanroommc/groovyscript/GroovyScript.java +++ b/src/main/java/com/cleanroommc/groovyscript/GroovyScript.java @@ -21,6 +21,8 @@ import com.cleanroommc.groovyscript.sandbox.*; import com.cleanroommc.groovyscript.sandbox.mapper.GroovyDeobfMapper; import com.cleanroommc.groovyscript.sandbox.security.GrSMetaClassCreationHandle; +import com.cleanroommc.groovyscript.server.GroovyScriptLanguageServer; +import com.google.common.base.Joiner; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import groovy.lang.GroovySystem; @@ -89,6 +91,7 @@ public class GroovyScript { private static RunConfig runConfig; private static GroovyScriptSandbox sandbox; private static ModContainer scriptMod; + private static Thread languageServerThread; private static KeyBinding reloadKey; private static long timeSinceLastUse = 0; @@ -97,6 +100,10 @@ public class GroovyScript { @Mod.EventHandler public void onConstruction(FMLConstructionEvent event) { + if (Boolean.parseBoolean(System.getProperty("groovyscript.run_ls"))) { + runLanguageServer(); + } + MinecraftForge.EVENT_BUS.register(this); MinecraftForge.EVENT_BUS.register(EventHandler.class); NetworkHandler.init(); @@ -297,4 +304,11 @@ public static void postScriptRunResult(ICommandSender sender, boolean onlyLogFai GSCommand.postLogFiles(sender); } } + + public static boolean runLanguageServer() { + if (languageServerThread != null) return false; + languageServerThread = new Thread(GroovyScriptLanguageServer::listen); + languageServerThread.start(); + return true; + } } diff --git a/src/main/java/com/cleanroommc/groovyscript/GroovyScriptConfig.java b/src/main/java/com/cleanroommc/groovyscript/GroovyScriptConfig.java index 75bd50635..1fafc60e2 100644 --- a/src/main/java/com/cleanroommc/groovyscript/GroovyScriptConfig.java +++ b/src/main/java/com/cleanroommc/groovyscript/GroovyScriptConfig.java @@ -9,4 +9,7 @@ public class GroovyScriptConfig { @ApiStatus.Internal @Config.Comment("The current set packmode") public static String packmode = ""; + + @Config.Comment("Port for the VSC connection. Default: 25564") + public static int languageServerPort = 25564; } diff --git a/src/main/java/com/cleanroommc/groovyscript/api/IDynamicGroovyProperty.java b/src/main/java/com/cleanroommc/groovyscript/api/IDynamicGroovyProperty.java index 3bf989534..a0898b917 100644 --- a/src/main/java/com/cleanroommc/groovyscript/api/IDynamicGroovyProperty.java +++ b/src/main/java/com/cleanroommc/groovyscript/api/IDynamicGroovyProperty.java @@ -2,6 +2,8 @@ import org.jetbrains.annotations.Nullable; +import java.util.Map; + /** * When this is implemented on a class, {@link #getProperty(String)} will be called when groovy tries to get a field from this class */ @@ -26,4 +28,11 @@ public interface IDynamicGroovyProperty { default boolean setProperty(String name, @Nullable Object value) { return false; } + + /** + * Returns all properties stored in this object. + * + * @return all properties + */ + Map getProperties(); } diff --git a/src/main/java/com/cleanroommc/groovyscript/api/IGameObjectHandler.java b/src/main/java/com/cleanroommc/groovyscript/api/IGameObjectParser.java similarity index 78% rename from src/main/java/com/cleanroommc/groovyscript/api/IGameObjectHandler.java rename to src/main/java/com/cleanroommc/groovyscript/api/IGameObjectParser.java index 8f2fec9bd..1d833588d 100644 --- a/src/main/java/com/cleanroommc/groovyscript/api/IGameObjectHandler.java +++ b/src/main/java/com/cleanroommc/groovyscript/api/IGameObjectParser.java @@ -22,7 +22,7 @@ * The first argument is always a string. The other can be anything. */ @FunctionalInterface -public interface IGameObjectHandler { +public interface IGameObjectParser { /** * Parses a object based on input arguments @@ -33,7 +33,7 @@ public interface IGameObjectHandler { @NotNull Result parse(String mainArg, Object[] args); - static > IGameObjectHandler wrapForgeRegistry(IForgeRegistry forgeRegistry) { + static > IGameObjectParser wrapForgeRegistry(IForgeRegistry forgeRegistry) { return (s, args) -> { Result rl = GameObjectHandlers.parseResourceLocation(s, args); if (rl.hasError()) return Result.error(rl.getError()); @@ -42,7 +42,7 @@ static > IGameObjectHandler wrapForgeRegistr }; } - static > IGameObjectHandler wrapEnum(Class enumClass, boolean caseSensitive) { + static > IGameObjectParser wrapEnum(Class enumClass, boolean caseSensitive) { Map map = new Object2ObjectOpenHashMap<>(); for (T t : enumClass.getEnumConstants()) { map.put(caseSensitive ? t.name() : t.name().toUpperCase(Locale.ROOT), t); @@ -53,11 +53,11 @@ static > IGameObjectHandler wrapEnum(Class enumClass, bo }; } - static IGameObjectHandler wrapStringGetter(Function getter) { + static IGameObjectParser wrapStringGetter(Function getter) { return wrapStringGetter(getter, false); } - static IGameObjectHandler wrapStringGetter(Function getter, boolean isUpperCase) { + static IGameObjectParser wrapStringGetter(Function getter, boolean isUpperCase) { return (s, args) -> { if (args.length > 0) { return Result.error("extra arguments are not allowed"); @@ -67,11 +67,11 @@ static IGameObjectHandler wrapStringGetter(Function getter, bo }; } - static IGameObjectHandler wrapStringGetter(Function getter, Function trueTypeFunction) { + static IGameObjectParser wrapStringGetter(Function getter, Function trueTypeFunction) { return wrapStringGetter(getter, trueTypeFunction, false); } - static IGameObjectHandler wrapStringGetter(Function getter, Function trueTypeFunction, boolean isUpperCase) { + static IGameObjectParser wrapStringGetter(Function getter, Function trueTypeFunction, boolean isUpperCase) { return (s, args) -> { if (args.length > 0) { return Result.error("extra arguments are not allowed"); diff --git a/src/main/java/com/cleanroommc/groovyscript/command/GSCommand.java b/src/main/java/com/cleanroommc/groovyscript/command/GSCommand.java index 7b0e62cfc..43e00c863 100644 --- a/src/main/java/com/cleanroommc/groovyscript/command/GSCommand.java +++ b/src/main/java/com/cleanroommc/groovyscript/command/GSCommand.java @@ -183,6 +183,14 @@ public GSCommand() { GroovyScript.getSandbox().deleteClassCache(); })); + addSubcommand(new SimpleCommand("runLS", (server, sender, args) -> { + if (GroovyScript.runLanguageServer()) { + sender.sendMessage(new TextComponentString("Starting language server")); + } else { + sender.sendMessage(new TextComponentString("Language server is already running")); + } + })); + if (ModSupport.MEKANISM.isLoaded()) { addSubcommand(new GSMekanismCommand()); } diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/ModPropertyContainer.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/ModPropertyContainer.java index 7816fa372..8f37ce36e 100644 --- a/src/main/java/com/cleanroommc/groovyscript/compat/mods/ModPropertyContainer.java +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/ModPropertyContainer.java @@ -6,6 +6,7 @@ import org.jetbrains.annotations.Nullable; import java.util.Collection; +import java.util.HashMap; import java.util.Map; public class ModPropertyContainer implements IDynamicGroovyProperty { @@ -41,6 +42,11 @@ public Collection getRegistries() { return registry; } + @Override + public Map getProperties() { + return new HashMap<>(registries); + } + /** * Register bracket handlers, bindings, expansions etc. here */ diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/ModSupport.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/ModSupport.java index 452634f99..4cc11b271 100644 --- a/src/main/java/com/cleanroommc/groovyscript/compat/mods/ModSupport.java +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/ModSupport.java @@ -6,9 +6,12 @@ import com.cleanroommc.groovyscript.api.IDynamicGroovyProperty; import com.cleanroommc.groovyscript.compat.mods.actuallyadditions.ActuallyAdditions; import com.cleanroommc.groovyscript.compat.mods.advancedmortars.AdvancedMortars; +import com.cleanroommc.groovyscript.compat.mods.aetherlegacy.Aether; +import com.cleanroommc.groovyscript.compat.mods.alchemistry.Alchemistry; import com.cleanroommc.groovyscript.compat.mods.appliedenergistics2.AppliedEnergistics2; import com.cleanroommc.groovyscript.compat.mods.astralsorcery.AstralSorcery; import com.cleanroommc.groovyscript.compat.mods.avaritia.Avaritia; +import com.cleanroommc.groovyscript.compat.mods.betterwithmods.BetterWithMods; import com.cleanroommc.groovyscript.compat.mods.bloodmagic.BloodMagic; import com.cleanroommc.groovyscript.compat.mods.botania.Botania; import com.cleanroommc.groovyscript.compat.mods.chisel.Chisel; @@ -40,6 +43,7 @@ import org.jetbrains.annotations.Nullable; import java.util.*; +import java.util.stream.Collectors; public class ModSupport implements IDynamicGroovyProperty { @@ -52,9 +56,12 @@ public class ModSupport implements IDynamicGroovyProperty { public static final GroovyContainer ACTUALLY_ADDITIONS = new InternalModContainer<>("actuallyadditions", "Actually Additions", ActuallyAdditions::new, "aa"); public static final GroovyContainer ADVANCED_MORTARS = new InternalModContainer<>("advancedmortars", "Advanced Mortars", AdvancedMortars::new); + public static final GroovyContainer AETHER = new InternalModContainer<>("aether_legacy", "The Aether", Aether::new, "aether"); + public static final GroovyContainer ALCHEMISTRY = new InternalModContainer<>("alchemistry", "Alchemistry", Alchemistry::new); public static final GroovyContainer APPLIED_ENERGISTICS_2 = new InternalModContainer<>("appliedenergistics2", "Applied Energistics 2", AppliedEnergistics2::new, "ae2"); public static final GroovyContainer ASTRAL_SORCERY = new InternalModContainer<>("astralsorcery", "Astral Sorcery", AstralSorcery::new, "astral"); public static final GroovyContainer AVARITIA = new InternalModContainer<>("avaritia", "Avaritia", Avaritia::new); + public static final GroovyContainer BETTER_WITH_MODS = new InternalModContainer<>("betterwithmods", "Better With Mods", BetterWithMods::new); public static final GroovyContainer BLOOD_MAGIC = new InternalModContainer<>("bloodmagic", "Blood Magic: Alchemical Wizardry", BloodMagic::new, "bm"); public static final GroovyContainer BOTANIA = new InternalModContainer<>("botania", "Botania", Botania::new); public static final GroovyContainer CHISEL = new InternalModContainer<>("chisel", "Chisel", Chisel::new); @@ -150,6 +157,15 @@ public Object getProperty(String name) { return container != null ? container.get() : null; } + @Override + public Map getProperties() { + Map properties = new HashMap<>(); + for (var entry : containers.entrySet()) { + properties.put(entry.getKey(), entry.getValue().get()); + } + return properties; + } + @GroovyBlacklist @ApiStatus.Internal public static void init() { diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/aetherlegacy/Accessory.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/aetherlegacy/Accessory.java new file mode 100644 index 000000000..78c78e608 --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/aetherlegacy/Accessory.java @@ -0,0 +1,94 @@ +package com.cleanroommc.groovyscript.compat.mods.aetherlegacy; + +import com.cleanroommc.groovyscript.api.GroovyLog; +import com.cleanroommc.groovyscript.api.IIngredient; +import com.cleanroommc.groovyscript.api.documentation.annotations.*; +import com.cleanroommc.groovyscript.helper.Alias; +import com.cleanroommc.groovyscript.helper.EnumHelper; +import com.cleanroommc.groovyscript.helper.recipe.AbstractRecipeBuilder; +import com.cleanroommc.groovyscript.registry.ForgeRegistryWrapper; +import com.gildedgames.the_aether.api.accessories.AccessoryType; +import com.gildedgames.the_aether.api.accessories.AetherAccessory; +import net.minecraft.item.ItemStack; +import net.minecraftforge.fml.common.registry.GameRegistry; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; + +@RegistryDescription +public class Accessory extends ForgeRegistryWrapper { + + public Accessory() { + super(GameRegistry.findRegistry(AetherAccessory.class), Alias.generateOfClass(Accessory.class)); + } + + @MethodDescription(type = MethodDescription.Type.ADDITION) + public void add(ItemStack item, String type) { + AccessoryType accessoryType = EnumHelper.valueOfNullable(AccessoryType.class, type, false); + if (accessoryType == null) { + GroovyLog.msg("Error adding Aether accessory") + .add(accessoryType == null, "type with name {} does not exist. Valid values are {}.", type, Arrays.toString(AccessoryType.values())) + .error() + .post(); + return; + } + AetherAccessory accessory = new AetherAccessory(item, accessoryType); + add(accessory); + } + + @MethodDescription(description = "groovyscript.wiki.removeByInput", example = @Example("item('aether_legacy:iron_pendant')")) + public void removeByInput(IIngredient input) { + this.getRegistry().getValuesCollection().forEach(accessory -> { + if (input.test(accessory.getAccessoryStack())) { + remove(accessory); + } + }); + } + + @RecipeBuilderDescription(example = @Example(".input(item('minecraft:shield')).accessoryType('shield')")) + public RecipeBuilder recipeBuilder() { + return new RecipeBuilder(); + } + + @Property(property = "input", valid = @Comp("1")) + public static class RecipeBuilder extends AbstractRecipeBuilder { + + @Property + private AccessoryType accessoryType; + + @RecipeBuilderMethodDescription + public RecipeBuilder accessoryType(String type) { + accessoryType = EnumHelper.valueOfNullable(AccessoryType.class, type, false); + return this; + } + + @RecipeBuilderMethodDescription + public RecipeBuilder accessoryType(AccessoryType type) { + accessoryType = type; + return this; + } + + @Override + public String getErrorMsg() { + return "Error adding Aether Accessory"; + } + + @Override + public void validate(GroovyLog.Msg msg) { + validateItems(msg, 1, 1, 0, 0); + validateFluids(msg); + msg.add(accessoryType == null, "type with name {} does not exist. Valid values are {}.", accessoryType.toString(), Arrays.toString(AccessoryType.values())); + } + + @RecipeBuilderRegistrationMethod + @Override + public @Nullable AetherAccessory register() { + if (!validate()) return null; + + AetherAccessory accessory = new AetherAccessory(input.get(0).getMatchingStacks()[0], accessoryType); + Aether.accessory.add(accessory); + return accessory; + } + } + +} diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/aetherlegacy/Aether.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/aetherlegacy/Aether.java new file mode 100644 index 000000000..f4eac9d8f --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/aetherlegacy/Aether.java @@ -0,0 +1,21 @@ +package com.cleanroommc.groovyscript.compat.mods.aetherlegacy; + +import com.cleanroommc.groovyscript.compat.mods.ModPropertyContainer; + +public class Aether extends ModPropertyContainer { + + public static final Enchanter enchanter = new Enchanter(); + public static final EnchanterFuel enchanterFuel = new EnchanterFuel(); + public static final Freezer freezer = new Freezer(); + public static final FreezerFuel freezerFuel = new FreezerFuel(); + public static final Accessory accessory = new Accessory(); + + public Aether() { + addRegistry(enchanter); + addRegistry(enchanterFuel); + addRegistry(freezer); + addRegistry(freezerFuel); + addRegistry(accessory); + } + +} diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/aetherlegacy/Enchanter.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/aetherlegacy/Enchanter.java new file mode 100644 index 000000000..9f76a4f89 --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/aetherlegacy/Enchanter.java @@ -0,0 +1,77 @@ +package com.cleanroommc.groovyscript.compat.mods.aetherlegacy; + +import com.cleanroommc.groovyscript.api.GroovyLog; +import com.cleanroommc.groovyscript.api.IIngredient; +import com.cleanroommc.groovyscript.api.documentation.annotations.*; +import com.cleanroommc.groovyscript.helper.Alias; +import com.cleanroommc.groovyscript.helper.recipe.AbstractRecipeBuilder; +import com.cleanroommc.groovyscript.registry.ForgeRegistryWrapper; +import com.gildedgames.the_aether.api.enchantments.AetherEnchantment; +import net.minecraft.item.ItemStack; +import net.minecraftforge.fml.common.registry.GameRegistry; +import org.jetbrains.annotations.Nullable; + +@RegistryDescription +public class Enchanter extends ForgeRegistryWrapper { + + public Enchanter() { + super(GameRegistry.findRegistry(AetherEnchantment.class), Alias.generateOfClass(Enchanter.class)); + } + + @RecipeBuilderDescription(example = @Example(".input(item('minecraft:clay')).output(item('minecraft:diamond')).time(200)")) + public RecipeBuilder recipeBuilder() { + return new RecipeBuilder(); + } + + @MethodDescription(type = MethodDescription.Type.ADDITION) + public void add(ItemStack input, ItemStack output, int timeRequired) { + AetherEnchantment enchantment = new AetherEnchantment(input, output, timeRequired); + add(enchantment); + } + + @MethodDescription(description = "groovyscript.wiki.removeByOutput", example = @Example("item('aether_legacy:enchanted_gravitite')")) + public void removeByOutput(IIngredient output) { + this.getRegistry().getValuesCollection().forEach(enchantment -> { + if (output.test(enchantment.getOutput())) { + remove(enchantment); + } + }); + } + + @Property(property = "input", valid = @Comp("1")) + @Property(property = "output", valid = @Comp("1")) + public static class RecipeBuilder extends AbstractRecipeBuilder { + + @Property(valid = @Comp(type = Comp.Type.GTE, value = "0")) + private int time; + + @RecipeBuilderMethodDescription + public RecipeBuilder time(int time) { + this.time = time; + return this; + } + + @Override + public String getErrorMsg() { + return "Error adding Aether Enchanter Recipe"; + } + + @Override + public void validate(GroovyLog.Msg msg) { + validateItems(msg, 1, 1, 1, 1); + validateFluids(msg); + msg.add(time < 0, "time must be a non-negative integer, yet it was {}", time); + } + + @RecipeBuilderRegistrationMethod + @Override + public @Nullable AetherEnchantment register() { + if (!validate()) return null; + + AetherEnchantment enchantment = new AetherEnchantment(input.get(0).getMatchingStacks()[0], output.get(0), time); + Aether.enchanter.add(enchantment); + return enchantment; + } + } + +} diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/aetherlegacy/EnchanterFuel.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/aetherlegacy/EnchanterFuel.java new file mode 100644 index 000000000..3f41a1d11 --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/aetherlegacy/EnchanterFuel.java @@ -0,0 +1,34 @@ +package com.cleanroommc.groovyscript.compat.mods.aetherlegacy; + +import com.cleanroommc.groovyscript.api.IIngredient; +import com.cleanroommc.groovyscript.api.documentation.annotations.Example; +import com.cleanroommc.groovyscript.api.documentation.annotations.MethodDescription; +import com.cleanroommc.groovyscript.api.documentation.annotations.RegistryDescription; +import com.cleanroommc.groovyscript.helper.Alias; +import com.cleanroommc.groovyscript.registry.ForgeRegistryWrapper; +import com.gildedgames.the_aether.api.enchantments.AetherEnchantmentFuel; +import net.minecraft.item.ItemStack; +import net.minecraftforge.fml.common.registry.GameRegistry; + +@RegistryDescription +public class EnchanterFuel extends ForgeRegistryWrapper { + + public EnchanterFuel() { + super(GameRegistry.findRegistry(AetherEnchantmentFuel.class), Alias.generateOfClass(EnchanterFuel.class)); + } + + @MethodDescription(type = MethodDescription.Type.ADDITION, example = @Example("item('minecraft:blaze_rod'), 1000")) + public void add(ItemStack fuel, int timeGiven) { + AetherEnchantmentFuel enchantmentFuel = new AetherEnchantmentFuel(fuel, timeGiven); + add(enchantmentFuel); + } + + @MethodDescription(description = "groovyscript.wiki.removeByInput", example = @Example("item('aether_legacy:ambrosium_shard')")) + public void removeByItem(IIngredient fuel) { + this.getRegistry().getValuesCollection().forEach(enchantmentFuel -> { + if (fuel.test(enchantmentFuel.getFuelStack())) { + remove(enchantmentFuel); + } + }); + } +} diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/aetherlegacy/Freezer.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/aetherlegacy/Freezer.java new file mode 100644 index 000000000..8af5080be --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/aetherlegacy/Freezer.java @@ -0,0 +1,76 @@ +package com.cleanroommc.groovyscript.compat.mods.aetherlegacy; + +import com.cleanroommc.groovyscript.api.GroovyLog; +import com.cleanroommc.groovyscript.api.IIngredient; +import com.cleanroommc.groovyscript.api.documentation.annotations.*; +import com.cleanroommc.groovyscript.helper.Alias; +import com.cleanroommc.groovyscript.helper.recipe.AbstractRecipeBuilder; +import com.cleanroommc.groovyscript.registry.ForgeRegistryWrapper; +import com.gildedgames.the_aether.api.freezables.AetherFreezable; +import net.minecraft.item.ItemStack; +import net.minecraftforge.fml.common.registry.GameRegistry; +import org.jetbrains.annotations.Nullable; + +@RegistryDescription +public class Freezer extends ForgeRegistryWrapper { + + public Freezer() { + super(GameRegistry.findRegistry(AetherFreezable.class), Alias.generateOfClass(Freezer.class)); + } + + @RecipeBuilderDescription(example = @Example(".input(item('minecraft:clay')).output(item('minecraft:dirt')).time(200)")) + public RecipeBuilder recipeBuilder() { + return new RecipeBuilder(); + } + + @MethodDescription(type = MethodDescription.Type.ADDITION) + public void add(ItemStack input, ItemStack output, int timeRequired) { + AetherFreezable freezable = new AetherFreezable(input, output, timeRequired); + add(freezable); + } + + @MethodDescription(description = "groovyscript.wiki.removeByOutput", example = @Example("item('minecraft:obsidian')")) + public void removeByOutput(IIngredient output) { + this.getRegistry().getValuesCollection().forEach(freezable -> { + if (output.test(freezable.getOutput())) { + remove(freezable); + } + }); + } + + @Property(property = "input", valid = @Comp("1")) + @Property(property = "output", valid = @Comp("1")) + public static class RecipeBuilder extends AbstractRecipeBuilder { + + @Property(valid = @Comp(type = Comp.Type.GTE, value = "0")) + private int time; + + @RecipeBuilderMethodDescription + public RecipeBuilder time(int time) { + this.time = time; + return this; + } + + @Override + public String getErrorMsg() { + return "Error adding Aether Freezer Recipe"; + } + + @Override + public void validate(GroovyLog.Msg msg) { + validateItems(msg, 1, 1, 1, 1); + validateFluids(msg); + msg.add(time < 0, "time must be a non-negative integer, yet it was {}", time); + } + + @RecipeBuilderRegistrationMethod + @Override + public @Nullable AetherFreezable register() { + if (!validate()) return null; + AetherFreezable freezable = new AetherFreezable(input.get(0).getMatchingStacks()[0], output.get(0), time); + Aether.freezer.add(freezable); + return freezable; + } + } + +} diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/aetherlegacy/FreezerFuel.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/aetherlegacy/FreezerFuel.java new file mode 100644 index 000000000..ce6c5ba38 --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/aetherlegacy/FreezerFuel.java @@ -0,0 +1,34 @@ +package com.cleanroommc.groovyscript.compat.mods.aetherlegacy; + +import com.cleanroommc.groovyscript.api.IIngredient; +import com.cleanroommc.groovyscript.api.documentation.annotations.Example; +import com.cleanroommc.groovyscript.api.documentation.annotations.MethodDescription; +import com.cleanroommc.groovyscript.api.documentation.annotations.RegistryDescription; +import com.cleanroommc.groovyscript.helper.Alias; +import com.cleanroommc.groovyscript.registry.ForgeRegistryWrapper; +import com.gildedgames.the_aether.api.freezables.AetherFreezableFuel; +import net.minecraft.item.ItemStack; +import net.minecraftforge.fml.common.registry.GameRegistry; + +@RegistryDescription +public class FreezerFuel extends ForgeRegistryWrapper { + + public FreezerFuel() { + super(GameRegistry.findRegistry(AetherFreezableFuel.class), Alias.generateOfClass(FreezerFuel.class)); + } + + @MethodDescription(type = MethodDescription.Type.ADDITION, example = @Example("item('minecraft:packed_ice'), 1000")) + public void add(ItemStack fuel, int timeGiven) { + AetherFreezableFuel freezableFuel = new AetherFreezableFuel(fuel, timeGiven); + add(freezableFuel); + } + + @MethodDescription(description = "groovyscript.wiki.removeByInput", example = @Example("item('aether_legacy:icestone')")) + public void removeByItem(IIngredient fuel) { + this.getRegistry().getValuesCollection().forEach(freezableFuel -> { + if (fuel.test(freezableFuel.getFuelStack())) { + remove(freezableFuel); + } + }); + } +} diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/alchemistry/Alchemistry.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/alchemistry/Alchemistry.java new file mode 100644 index 000000000..9ba86f131 --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/alchemistry/Alchemistry.java @@ -0,0 +1,53 @@ +package com.cleanroommc.groovyscript.compat.mods.alchemistry; + +import al132.alchemistry.chemistry.ChemicalCompound; +import al132.alchemistry.chemistry.ChemicalElement; +import al132.alchemistry.chemistry.CompoundRegistry; +import al132.alchemistry.chemistry.ElementRegistry; +import com.cleanroommc.groovyscript.api.Result; +import com.cleanroommc.groovyscript.compat.mods.ModPropertyContainer; +import com.cleanroommc.groovyscript.gameobjects.GameObjectHandler; +import net.minecraft.item.ItemStack; + +public class Alchemistry extends ModPropertyContainer { + + public final Atomizer atomizer = new Atomizer(); + public final Combiner combiner = new Combiner(); + public final Dissolver dissolver = new Dissolver(); + public final Electrolyzer electrolyzer = new Electrolyzer(); + public final Evaporator evaporator = new Evaporator(); + public final Liquifier liquifier = new Liquifier(); + + public Alchemistry() { + addRegistry(atomizer); + addRegistry(combiner); + addRegistry(dissolver); + addRegistry(electrolyzer); + addRegistry(evaporator); + addRegistry(liquifier); + // TODO: + // Compound Creation and Element Creation + } + + @Override + public void initialize() { + GameObjectHandler.builder("element", ItemStack.class) + .mod("alchemistry") + .parser((s, args) -> { + String parsedName = s.trim().toLowerCase().replace(" ", "_"); + ChemicalCompound compound = CompoundRegistry.INSTANCE.get(parsedName); + if (compound == null || compound.toItemStack(1).isEmpty()) { + ChemicalElement element = ElementRegistry.INSTANCE.get(parsedName); + if (element == null || element.toItemStack(1).isEmpty()) { + return Result.error(); + } + return Result.some(element.toItemStack(1)); + } + return Result.some(compound.toItemStack(1)); + }) + .defaultValue(() -> ItemStack.EMPTY) + .completerOfNamed(CompoundRegistry.INSTANCE::compounds, ChemicalCompound::getName) + .completerOfNamed(ElementRegistry.INSTANCE::getAllElements, ChemicalElement::getName) + .register(); + } +} diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/alchemistry/Atomizer.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/alchemistry/Atomizer.java new file mode 100644 index 000000000..f08e76b9e --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/alchemistry/Atomizer.java @@ -0,0 +1,130 @@ +package com.cleanroommc.groovyscript.compat.mods.alchemistry; + +import al132.alchemistry.recipes.AtomizerRecipe; +import al132.alchemistry.recipes.LiquifierRecipe; +import al132.alchemistry.recipes.ModRecipes; +import com.cleanroommc.groovyscript.api.GroovyLog; +import com.cleanroommc.groovyscript.api.IIngredient; +import com.cleanroommc.groovyscript.api.documentation.annotations.*; +import com.cleanroommc.groovyscript.compat.mods.ModSupport; +import com.cleanroommc.groovyscript.helper.SimpleObjectStream; +import com.cleanroommc.groovyscript.helper.recipe.AbstractRecipeBuilder; +import com.cleanroommc.groovyscript.registry.VirtualizedRegistry; +import net.minecraft.item.ItemStack; +import net.minecraftforge.fluids.FluidStack; +import org.jetbrains.annotations.Nullable; + +@RegistryDescription +public class Atomizer extends VirtualizedRegistry { + + @Override + public void onReload() { + removeScripted().forEach(recipe -> ModRecipes.INSTANCE.getAtomizerRecipes().removeIf(r -> r == recipe)); + ModRecipes.INSTANCE.getAtomizerRecipes().addAll(restoreFromBackup()); + } + + @RecipeBuilderDescription(example = { + @Example(".fluidInput(fluid('water') * 125).output(item('minecraft:clay'))"), + @Example(".fluidInput(fluid('lava') * 500).output(item('minecraft:gold_ingot')).reversible()") + }) + public RecipeBuilder recipeBuilder() { + return new RecipeBuilder(); + } + + @MethodDescription(type = MethodDescription.Type.ADDITION) + public AtomizerRecipe add(FluidStack input, ItemStack output) { + return new RecipeBuilder().fluidInput(input).output(output).register(); + } + + public AtomizerRecipe add(AtomizerRecipe recipe) { + if (recipe != null) { + addScripted(recipe); + ModRecipes.INSTANCE.getAtomizerRecipes().add(recipe); + } + return recipe; + } + + public boolean remove(AtomizerRecipe recipe) { + if (ModRecipes.INSTANCE.getAtomizerRecipes().removeIf(r -> r == recipe)) { + addBackup(recipe); + return true; + } + return false; + } + + @MethodDescription(description = "groovyscript.wiki.removeByOutput", example = @Example(value = "item('alchemistry:compound:7')", commented = true)) + public boolean removeByOutput(IIngredient output) { + return ModRecipes.INSTANCE.getAtomizerRecipes().removeIf(r -> { + if (output.test(r.getOutput())) { + addBackup(r); + return true; + } + return false; + }); + } + + @MethodDescription(description = "groovyscript.wiki.removeByInput", example = @Example("fluid('water')")) + public boolean removeByInput(FluidStack input) { + return ModRecipes.INSTANCE.getAtomizerRecipes().removeIf(r -> { + if (r.getInput().isFluidEqual(input)) { + addBackup(r); + return true; + } + return false; + }); + } + + @MethodDescription(description = "groovyscript.wiki.streamRecipes", type = MethodDescription.Type.QUERY) + public SimpleObjectStream streamRecipes() { + return new SimpleObjectStream<>(ModRecipes.INSTANCE.getAtomizerRecipes()).setRemover(this::remove); + } + + @MethodDescription(description = "groovyscript.wiki.removeAll", priority = 2000, example = @Example(commented = true)) + public void removeAll() { + ModRecipes.INSTANCE.getAtomizerRecipes().forEach(this::addBackup); + ModRecipes.INSTANCE.getAtomizerRecipes().clear(); + } + + @Property(property = "fluidInput", valid = @Comp("1")) + @Property(property = "output", valid = @Comp("1")) + public static class RecipeBuilder extends AbstractRecipeBuilder { + + @Property + private boolean reversible; + + @RecipeBuilderMethodDescription + public RecipeBuilder reversible(boolean reversible) { + this.reversible = reversible; + return this; + } + + @RecipeBuilderMethodDescription + public RecipeBuilder reversible() { + this.reversible = !reversible; + return this; + } + + @Override + public String getErrorMsg() { + return "Error adding Alchemistry Atomizer recipe"; + } + + @Override + public void validate(GroovyLog.Msg msg) { + validateItems(msg, 0, 0, 1, 1); + validateFluids(msg, 1, 1, 0, 0); + } + + @Nullable + @Override + @RecipeBuilderRegistrationMethod + public AtomizerRecipe register() { + if (!validate()) return null; + + AtomizerRecipe recipe = new AtomizerRecipe(false, fluidInput.get(0), output.get(0)); + if (reversible) ModSupport.ALCHEMISTRY.get().liquifier.add(new LiquifierRecipe(output.get(0), fluidInput.get(0))); + ModSupport.ALCHEMISTRY.get().atomizer.add(recipe); + return recipe; + } + } +} diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/alchemistry/Combiner.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/alchemistry/Combiner.java new file mode 100644 index 000000000..4c7d7aba2 --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/alchemistry/Combiner.java @@ -0,0 +1,130 @@ +package com.cleanroommc.groovyscript.compat.mods.alchemistry; + +import al132.alchemistry.recipes.CombinerRecipe; +import al132.alchemistry.recipes.ModRecipes; +import com.cleanroommc.groovyscript.api.GroovyLog; +import com.cleanroommc.groovyscript.api.IIngredient; +import com.cleanroommc.groovyscript.api.documentation.annotations.*; +import com.cleanroommc.groovyscript.compat.mods.ModSupport; +import com.cleanroommc.groovyscript.helper.Alias; +import com.cleanroommc.groovyscript.helper.SimpleObjectStream; +import com.cleanroommc.groovyscript.helper.ingredient.IngredientHelper; +import com.cleanroommc.groovyscript.helper.recipe.AbstractRecipeBuilder; +import com.cleanroommc.groovyscript.registry.VirtualizedRegistry; +import net.minecraft.item.ItemStack; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.stream.Collectors; + +@RegistryDescription +public class Combiner extends VirtualizedRegistry { + + public Combiner() { + super(Alias.generateOfClass(Combiner.class).andGenerate("ChemicalCombiner")); + } + + @Override + public void onReload() { + removeScripted().forEach(recipe -> ModRecipes.INSTANCE.getCombinerRecipes().removeIf(r -> r == recipe)); + ModRecipes.INSTANCE.getCombinerRecipes().addAll(restoreFromBackup()); + } + + @RecipeBuilderDescription(example = { + @Example(".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, item('minecraft:gold_ingot') * 2).output(item('minecraft:gold_block') * 2)"), + @Example(".input(ItemStack.EMPTY, ItemStack.EMPTY, item('minecraft:clay')).output(item('minecraft:gold_ingot'))") + }) + public RecipeBuilder recipeBuilder() { + return new RecipeBuilder(); + } + + public CombinerRecipe add(CombinerRecipe recipe) { + if (recipe != null) { + addScripted(recipe); + ModRecipes.INSTANCE.getCombinerRecipes().add(recipe); + } + return recipe; + } + + public boolean remove(CombinerRecipe recipe) { + if (ModRecipes.INSTANCE.getCombinerRecipes().removeIf(r -> r == recipe)) { + addBackup(recipe); + return true; + } + return false; + } + + @MethodDescription(description = "groovyscript.wiki.removeByOutput", example = @Example("item('minecraft:glowstone')")) + public boolean removeByOutput(IIngredient output) { + return ModRecipes.INSTANCE.getCombinerRecipes().removeIf(r -> { + if (output.test(r.getOutput())) { + addBackup(r); + return true; + } + return false; + }); + } + + @MethodDescription(description = "groovyscript.wiki.removeByInput", example = @Example("element('carbon')")) + public boolean removeByInput(IIngredient input) { + return ModRecipes.INSTANCE.getCombinerRecipes().removeIf(r -> { + for (ItemStack itemstack : r.getInputs()) { + if (input.test(itemstack)) { + addBackup(r); + return true; + } + } + return false; + }); + } + + @MethodDescription(description = "groovyscript.wiki.streamRecipes", type = MethodDescription.Type.QUERY) + public SimpleObjectStream streamRecipes() { + return new SimpleObjectStream<>(ModRecipes.INSTANCE.getCombinerRecipes()).setRemover(this::remove); + } + + @MethodDescription(description = "groovyscript.wiki.removeAll", priority = 2000, example = @Example(commented = true)) + public void removeAll() { + ModRecipes.INSTANCE.getCombinerRecipes().forEach(this::addBackup); + ModRecipes.INSTANCE.getCombinerRecipes().clear(); + } + + @Property(property = "input", valid = {@Comp(type = Comp.Type.GTE, value = "1"), @Comp(type = Comp.Type.LTE, value = "9")}) + @Property(property = "output", valid = @Comp("1")) + public static class RecipeBuilder extends AbstractRecipeBuilder { + + @Property + protected String gamestage = ""; + + @RecipeBuilderMethodDescription + public RecipeBuilder gamestage(String gamestage) { + this.gamestage = gamestage; + return this; + } + + @Override + public String getErrorMsg() { + return "Error adding Alchemistry Combiner recipe"; + } + + @Override + public void validate(GroovyLog.Msg msg) { + int inputSize = input.getRealSize(); + output.trim(); + msg.add(inputSize < 1 || inputSize > 9, () -> "Must have 1 - 9 inputs, but found " + input.size()); + msg.add(output.size() != 1, () -> "Must have exactly 1 output, but found " + output.size()); + validateFluids(msg); + } + + @Override + @RecipeBuilderRegistrationMethod + public @Nullable CombinerRecipe register() { + if (!validate()) return null; + + List inputs = input.stream().map(x -> x.isEmpty() ? ItemStack.EMPTY : IngredientHelper.toItemStack(x)).collect(Collectors.toList()); + CombinerRecipe recipe = new CombinerRecipe(output.get(0), inputs, gamestage); + ModSupport.ALCHEMISTRY.get().combiner.add(recipe); + return recipe; + } + } +} diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/alchemistry/Dissolver.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/alchemistry/Dissolver.java new file mode 100644 index 000000000..dc64fcc9b --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/alchemistry/Dissolver.java @@ -0,0 +1,184 @@ +package com.cleanroommc.groovyscript.compat.mods.alchemistry; + +import al132.alchemistry.recipes.*; +import com.cleanroommc.groovyscript.api.GroovyLog; +import com.cleanroommc.groovyscript.api.IIngredient; +import com.cleanroommc.groovyscript.api.documentation.annotations.*; +import com.cleanroommc.groovyscript.compat.mods.ModSupport; +import com.cleanroommc.groovyscript.helper.Alias; +import com.cleanroommc.groovyscript.helper.SimpleObjectStream; +import com.cleanroommc.groovyscript.helper.recipe.AbstractRecipeBuilder; +import com.cleanroommc.groovyscript.registry.VirtualizedRegistry; +import net.minecraft.item.ItemStack; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +@RegistryDescription +public class Dissolver extends VirtualizedRegistry { + + public Dissolver() { + super(Alias.generateOfClass(Dissolver.class).andGenerate("ChemicalDissolver")); + } + + @Override + public void onReload() { + removeScripted().forEach(recipe -> ModRecipes.INSTANCE.getDissolverRecipes().removeIf(r -> r == recipe)); + ModRecipes.INSTANCE.getDissolverRecipes().addAll(restoreFromBackup()); + } + + @RecipeBuilderDescription(example = { + @Example(".input(item('minecraft:gold_ingot')).probabilityOutput(item('minecraft:clay')).reversible().rolls(1)"), + @Example(".input(item('minecraft:diamond')).probabilityOutput(30, item('minecraft:clay')).probabilityOutput(30, item('minecraft:clay')).probabilityOutput(30, item('minecraft:clay')).rolls(10)") + }) + public RecipeBuilder recipeBuilder() { + return new RecipeBuilder(); + } + + public DissolverRecipe add(DissolverRecipe recipe) { + if (recipe != null) { + addScripted(recipe); + ModRecipes.INSTANCE.getDissolverRecipes().add(recipe); + } + return recipe; + } + + public boolean remove(DissolverRecipe recipe) { + if (ModRecipes.INSTANCE.getDissolverRecipes().removeIf(r -> r == recipe)) { + addBackup(recipe); + return true; + } + return false; + } + + @MethodDescription(description = "groovyscript.wiki.removeByInput", example = @Example("item('alchemistry:compound:1')")) + public boolean removeByInput(IIngredient input) { + return ModRecipes.INSTANCE.getDissolverRecipes().removeIf(r -> { + for (ItemStack itemstack : r.getInputs()) { + if (input.test(itemstack)) { + addBackup(r); + return true; + } + } + return false; + }); + } + + @MethodDescription(description = "groovyscript.wiki.streamRecipes", type = MethodDescription.Type.QUERY) + public SimpleObjectStream streamRecipes() { + return new SimpleObjectStream<>(ModRecipes.INSTANCE.getDissolverRecipes()).setRemover(this::remove); + } + + @MethodDescription(description = "groovyscript.wiki.removeAll", priority = 2000, example = @Example(commented = true)) + public void removeAll() { + ModRecipes.INSTANCE.getDissolverRecipes().forEach(this::addBackup); + ModRecipes.INSTANCE.getDissolverRecipes().clear(); + } + + @Property(property = "input", valid = @Comp("1")) + @Property(property = "output", valid = @Comp("1")) + public static class RecipeBuilder extends AbstractRecipeBuilder { + + @Property(valid = @Comp(value = "1", type = Comp.Type.GTE)) + private final ArrayList probabilityGroup = new ArrayList<>(); + @Property + private boolean reversible; + @Property + private boolean relativeProbability; + @Property(defaultValue = "1", valid = @Comp(value = "1", type = Comp.Type.GTE)) + private int rolls = 1; + + @RecipeBuilderMethodDescription + public RecipeBuilder probabilityOutput(double probability, ItemStack... probabilityOutputs) { + probabilityGroup.add(new ProbabilityGroup(Arrays.asList(probabilityOutputs), probability)); + return this; + } + + @RecipeBuilderMethodDescription + public RecipeBuilder probabilityOutput(ItemStack... probabilityOutputs) { + return this.probabilityOutput(100, probabilityOutputs); + } + + @RecipeBuilderMethodDescription + public RecipeBuilder probabilityOutput(double probability, Collection probabilityOutputs) { + probabilityGroup.add(new ProbabilityGroup((List) probabilityOutputs, probability)); + return this; + } + + @RecipeBuilderMethodDescription + public RecipeBuilder probabilityOutput(Collection probabilityOutputs) { + return this.probabilityOutput(100, probabilityOutputs); + } + + @RecipeBuilderMethodDescription + public RecipeBuilder output(ItemStack... probabilityOutputs) { + return this.probabilityOutput(100, probabilityOutputs); + } + + @RecipeBuilderMethodDescription + public RecipeBuilder output(Collection probabilityOutputs) { + return this.probabilityOutput(100, probabilityOutputs); + } + + @RecipeBuilderMethodDescription + public RecipeBuilder reversible(boolean reversible) { + this.reversible = reversible; + return this; + } + + @RecipeBuilderMethodDescription + public RecipeBuilder reversible() { + this.reversible = !reversible; + return this; + } + + @RecipeBuilderMethodDescription + public RecipeBuilder relativeProbability(boolean relativeProbability) { + this.relativeProbability = relativeProbability; + return this; + } + + @RecipeBuilderMethodDescription + public RecipeBuilder relativeProbability() { + this.relativeProbability = !relativeProbability; + return this; + } + + @RecipeBuilderMethodDescription + public RecipeBuilder rolls(int rolls) { + this.rolls = rolls; + return this; + } + + @Override + public String getErrorMsg() { + return "Error adding Alchemistry Dissolver recipe"; + } + + @Override + public void validate(GroovyLog.Msg msg) { + validateItems(msg, 1, 1, 0, 0); + validateFluids(msg); + validateCustom(msg, probabilityGroup, 1, Integer.MAX_VALUE, "probability group"); + msg.add(rolls < 1, "rolls must be greater than or equal to 1, yet it was {}", rolls); + } + + @Nullable + @Override + @RecipeBuilderRegistrationMethod + public DissolverRecipe register() { + if (!validate()) return null; + + DissolverRecipe recipe = new DissolverRecipe(input.get(0).toMcIngredient(), false, new ProbabilitySet(probabilityGroup, relativeProbability, rolls)); + if (reversible) { + ModSupport.ALCHEMISTRY.get().combiner.add(new CombinerRecipe(recipe.getInputs().get(0), probabilityGroup.stream().map(ProbabilityGroup::getOutput).flatMap(Collection::stream).collect(Collectors.toList()), "")); + } + ModSupport.ALCHEMISTRY.get().dissolver.add(recipe); + return recipe; + } + } +} diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/alchemistry/Electrolyzer.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/alchemistry/Electrolyzer.java new file mode 100644 index 000000000..19bedf907 --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/alchemistry/Electrolyzer.java @@ -0,0 +1,173 @@ +package com.cleanroommc.groovyscript.compat.mods.alchemistry; + +import al132.alchemistry.recipes.ElectrolyzerRecipe; +import al132.alchemistry.recipes.ModRecipes; +import com.cleanroommc.groovyscript.api.GroovyLog; +import com.cleanroommc.groovyscript.api.IIngredient; +import com.cleanroommc.groovyscript.api.documentation.annotations.*; +import com.cleanroommc.groovyscript.compat.mods.ModSupport; +import com.cleanroommc.groovyscript.helper.SimpleObjectStream; +import com.cleanroommc.groovyscript.helper.recipe.AbstractRecipeBuilder; +import com.cleanroommc.groovyscript.registry.VirtualizedRegistry; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import net.minecraft.item.ItemStack; +import net.minecraft.item.crafting.Ingredient; +import net.minecraftforge.fluids.FluidStack; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; + +@RegistryDescription +public class Electrolyzer extends VirtualizedRegistry { + + @Override + public void onReload() { + removeScripted().forEach(recipe -> ModRecipes.INSTANCE.getElectrolyzerRecipes().removeIf(r -> r == recipe)); + ModRecipes.INSTANCE.getElectrolyzerRecipes().addAll(restoreFromBackup()); + } + + @RecipeBuilderDescription(example = { + @Example(".fluidInput(fluid('lava') * 100).output(item('minecraft:clay'))"), + @Example(".fluidInput(fluid('water') * 100).input(item('minecraft:gold_ingot')).consumptionChance(100).output(item('minecraft:gold_nugget') * 4).output(item('minecraft:gold_nugget') * 4).output(item('minecraft:gold_nugget') * 4).output(item('minecraft:gold_nugget') * 4).chance(50).chance(50)") + }) + public RecipeBuilder recipeBuilder() { + return new RecipeBuilder(); + } + + public ElectrolyzerRecipe add(ElectrolyzerRecipe recipe) { + if (recipe != null) { + addScripted(recipe); + ModRecipes.INSTANCE.getElectrolyzerRecipes().add(recipe); + } + return recipe; + } + + public boolean remove(ElectrolyzerRecipe recipe) { + if (ModRecipes.INSTANCE.getElectrolyzerRecipes().removeIf(r -> r == recipe)) { + addBackup(recipe); + return true; + } + return false; + } + + @MethodDescription(description = "groovyscript.wiki.removeByOutput", example = @Example("element('chlorine')")) + public boolean removeByOutput(IIngredient output) { + return ModRecipes.INSTANCE.getElectrolyzerRecipes().removeIf(r -> { + for (ItemStack itemstack : r.getOutputs()) { + if (output.test(itemstack)) { + addBackup(r); + return true; + } + } + return false; + }); + } + + @MethodDescription(description = "groovyscript.wiki.removeByInput", example = @Example(value = "fluid('water')", commented = true)) + public boolean removeByInput(FluidStack input) { + return ModRecipes.INSTANCE.getElectrolyzerRecipes().removeIf(r -> { + if (r.getInput().isFluidEqual(input)) { + addBackup(r); + return true; + } + return false; + }); + } + + @MethodDescription(description = "groovyscript.wiki.removeByInput", example = @Example("element('calcium_carbonate')")) + public boolean removeByInput(IIngredient input) { + return ModRecipes.INSTANCE.getElectrolyzerRecipes().removeIf(r -> { + for (ItemStack itemstack : r.getElectrolytes()) { + if (input.test(itemstack)) { + addBackup(r); + return true; + } + } + return false; + }); + } + + @MethodDescription(description = "groovyscript.wiki.streamRecipes", type = MethodDescription.Type.QUERY) + public SimpleObjectStream streamRecipes() { + return new SimpleObjectStream<>(ModRecipes.INSTANCE.getElectrolyzerRecipes()).setRemover(this::remove); + } + + @MethodDescription(description = "groovyscript.wiki.removeAll", priority = 2000, example = @Example(commented = true)) + public void removeAll() { + ModRecipes.INSTANCE.getElectrolyzerRecipes().forEach(this::addBackup); + ModRecipes.INSTANCE.getElectrolyzerRecipes().clear(); + } + + @Property(property = "input", valid = {@Comp(value = "0", type = Comp.Type.GTE), @Comp(value = "1", type = Comp.Type.LTE)}) + @Property(property = "output", valid = {@Comp(value = "1", type = Comp.Type.GTE), @Comp(value = "4", type = Comp.Type.LTE)}) + public static class RecipeBuilder extends AbstractRecipeBuilder { + + @Property(valid = {@Comp(value = "0", type = Comp.Type.GTE), @Comp(value = "2", type = Comp.Type.LTE)}) + private final IntArrayList chance = new IntArrayList(); + @Property(valid = {@Comp(value = "0", type = Comp.Type.GTE), @Comp(value = "100", type = Comp.Type.LTE)}) + private int consumptionChance; + + @RecipeBuilderMethodDescription + public RecipeBuilder input(IIngredient ingredient, int chance) { + this.input.add(ingredient); + this.chance.add(chance); + return this; + } + + @RecipeBuilderMethodDescription + public RecipeBuilder chance(int chance) { + this.chance.add(chance); + return this; + } + + @RecipeBuilderMethodDescription + public RecipeBuilder chance(int... chances) { + for (int chance : chances) { + chance(chance); + } + return this; + } + + @RecipeBuilderMethodDescription + public RecipeBuilder chance(Collection chances) { + for (int chance : chances) { + chance(chance); + } + return this; + } + + @RecipeBuilderMethodDescription + public RecipeBuilder consumptionChance(int consumptionChance) { + this.consumptionChance = consumptionChance; + return this; + } + + @Override + public String getErrorMsg() { + return "Error adding Alchemistry Electrolyzer recipe"; + } + + @Override + public void validate(GroovyLog.Msg msg) { + validateItems(msg, 0, 1, 1, 4); + validateFluids(msg, 1, 1, 0, 0); + validateCustom(msg, chance, 0, 2, "chance"); + msg.add(!chance.isEmpty() && chance.size() > (output.size() - 2), "chance only applies to output items after the second, cannot have more chance than output items above 2, had {} chance and {} output", chance.size(), output.size()); + msg.add(consumptionChance < 0 || consumptionChance > 100, "consumption chance must be between 0 and 100, yet it was {}", consumptionChance); + } + + @Nullable + @Override + @RecipeBuilderRegistrationMethod + public ElectrolyzerRecipe register() { + if (!validate()) return null; + ElectrolyzerRecipe recipe = new ElectrolyzerRecipe(fluidInput.get(0), + input.size() >= 1 ? input.get(0).toMcIngredient() : Ingredient.EMPTY, consumptionChance, + output.get(0), output.getOrEmpty(1), + output.getOrEmpty(2), chance.size() >= 1 ? chance.getInt(0) : 0, + output.getOrEmpty(3), chance.size() >= 2 ? chance.getInt(1) : 0); + ModSupport.ALCHEMISTRY.get().electrolyzer.add(recipe); + return recipe; + } + } +} diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/alchemistry/Evaporator.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/alchemistry/Evaporator.java new file mode 100644 index 000000000..68533c44d --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/alchemistry/Evaporator.java @@ -0,0 +1,112 @@ +package com.cleanroommc.groovyscript.compat.mods.alchemistry; + +import al132.alchemistry.recipes.EvaporatorRecipe; +import al132.alchemistry.recipes.ModRecipes; +import com.cleanroommc.groovyscript.api.GroovyLog; +import com.cleanroommc.groovyscript.api.IIngredient; +import com.cleanroommc.groovyscript.api.documentation.annotations.*; +import com.cleanroommc.groovyscript.compat.mods.ModSupport; +import com.cleanroommc.groovyscript.helper.SimpleObjectStream; +import com.cleanroommc.groovyscript.helper.recipe.AbstractRecipeBuilder; +import com.cleanroommc.groovyscript.registry.VirtualizedRegistry; +import net.minecraft.item.ItemStack; +import net.minecraftforge.fluids.FluidStack; +import org.jetbrains.annotations.Nullable; + +@RegistryDescription +public class Evaporator extends VirtualizedRegistry { + + @Override + public void onReload() { + removeScripted().forEach(recipe -> ModRecipes.INSTANCE.getEvaporatorRecipes().removeIf(r -> r == recipe)); + ModRecipes.INSTANCE.getEvaporatorRecipes().addAll(restoreFromBackup()); + } + + @RecipeBuilderDescription(example = { + @Example(".fluidInput(fluid('lava') * 100).output(item('minecraft:redstone') * 8)"), + @Example(".fluidInput(fluid('water') * 10).output(item('minecraft:clay'))") + }) + public RecipeBuilder recipeBuilder() { + return new RecipeBuilder(); + } + + @MethodDescription(type = MethodDescription.Type.ADDITION) + public EvaporatorRecipe add(FluidStack input, ItemStack output) { + return new RecipeBuilder().fluidInput(input).output(output).register(); + } + + public EvaporatorRecipe add(EvaporatorRecipe recipe) { + if (recipe != null) { + addScripted(recipe); + ModRecipes.INSTANCE.getEvaporatorRecipes().add(recipe); + } + return recipe; + } + + public boolean remove(EvaporatorRecipe recipe) { + if (ModRecipes.INSTANCE.getEvaporatorRecipes().removeIf(r -> r == recipe)) { + addBackup(recipe); + return true; + } + return false; + } + + @MethodDescription(description = "groovyscript.wiki.removeByOutput", example = @Example("item('alchemistry:mineral_salt')")) + public boolean removeByOutput(IIngredient output) { + return ModRecipes.INSTANCE.getEvaporatorRecipes().removeIf(r -> { + if (output.test(r.getOutput())) { + addBackup(r); + return true; + } + return false; + }); + } + + @MethodDescription(description = "groovyscript.wiki.removeByInput", example = @Example("fluid('lava')")) + public boolean removeByInput(FluidStack input) { + return ModRecipes.INSTANCE.getEvaporatorRecipes().removeIf(r -> { + if (r.getInput().isFluidEqual(input)) { + addBackup(r); + return true; + } + return false; + }); + } + + @MethodDescription(description = "groovyscript.wiki.streamRecipes", type = MethodDescription.Type.QUERY) + public SimpleObjectStream streamRecipes() { + return new SimpleObjectStream<>(ModRecipes.INSTANCE.getEvaporatorRecipes()).setRemover(this::remove); + } + + @MethodDescription(description = "groovyscript.wiki.removeAll", priority = 2000, example = @Example(commented = true)) + public void removeAll() { + ModRecipes.INSTANCE.getEvaporatorRecipes().forEach(this::addBackup); + ModRecipes.INSTANCE.getEvaporatorRecipes().clear(); + } + + @Property(property = "fluidInput", valid = @Comp("1")) + @Property(property = "output", valid = @Comp("1")) + public static class RecipeBuilder extends AbstractRecipeBuilder { + + @Override + public String getErrorMsg() { + return "Error adding Alchemistry Evaporator recipe"; + } + + @Override + public void validate(GroovyLog.Msg msg) { + validateItems(msg, 0, 0, 1, 1); + validateFluids(msg, 1, 1, 0, 0); + } + + @Nullable + @Override + @RecipeBuilderRegistrationMethod + public EvaporatorRecipe register() { + if (!validate()) return null; + EvaporatorRecipe recipe = new EvaporatorRecipe(fluidInput.get(0), output.get(0)); + ModSupport.ALCHEMISTRY.get().evaporator.add(recipe); + return recipe; + } + } +} diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/alchemistry/Liquifier.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/alchemistry/Liquifier.java new file mode 100644 index 000000000..d0c44c569 --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/alchemistry/Liquifier.java @@ -0,0 +1,112 @@ +package com.cleanroommc.groovyscript.compat.mods.alchemistry; + +import al132.alchemistry.recipes.LiquifierRecipe; +import al132.alchemistry.recipes.ModRecipes; +import com.cleanroommc.groovyscript.api.GroovyLog; +import com.cleanroommc.groovyscript.api.IIngredient; +import com.cleanroommc.groovyscript.api.documentation.annotations.*; +import com.cleanroommc.groovyscript.compat.mods.ModSupport; +import com.cleanroommc.groovyscript.helper.SimpleObjectStream; +import com.cleanroommc.groovyscript.helper.ingredient.IngredientHelper; +import com.cleanroommc.groovyscript.helper.recipe.AbstractRecipeBuilder; +import com.cleanroommc.groovyscript.registry.VirtualizedRegistry; +import net.minecraftforge.fluids.FluidStack; +import org.jetbrains.annotations.Nullable; + +@RegistryDescription +public class Liquifier extends VirtualizedRegistry { + + @Override + public void onReload() { + removeScripted().forEach(recipe -> ModRecipes.INSTANCE.getLiquifierRecipes().removeIf(r -> r == recipe)); + ModRecipes.INSTANCE.getLiquifierRecipes().addAll(restoreFromBackup()); + } + + @RecipeBuilderDescription(example = { + @Example(".input(element('carbon') * 32).fluidOutput(fluid('water') * 1000)"), + @Example(".input(item('minecraft:magma')).fluidOutput(fluid('lava') * 750)") + }) + public RecipeBuilder recipeBuilder() { + return new RecipeBuilder(); + } + + @MethodDescription(type = MethodDescription.Type.ADDITION) + public LiquifierRecipe add(IIngredient input, FluidStack output) { + return new RecipeBuilder().input(input).fluidOutput(output).register(); + } + + public LiquifierRecipe add(LiquifierRecipe recipe) { + if (recipe != null) { + addScripted(recipe); + ModRecipes.INSTANCE.getLiquifierRecipes().add(recipe); + } + return recipe; + } + + public boolean remove(LiquifierRecipe recipe) { + if (ModRecipes.INSTANCE.getLiquifierRecipes().removeIf(r -> r == recipe)) { + addBackup(recipe); + return true; + } + return false; + } + + @MethodDescription(description = "groovyscript.wiki.removeByOutput", example = @Example(value = "fluid('water')", commented = true)) + public boolean removeByOutput(FluidStack output) { + return ModRecipes.INSTANCE.getLiquifierRecipes().removeIf(r -> { + if (r.getOutput().isFluidEqual(output)) { + addBackup(r); + return true; + } + return false; + }); + } + + @MethodDescription(description = "groovyscript.wiki.removeByInput", example = @Example("element('water')")) + public boolean removeByInput(IIngredient input) { + return ModRecipes.INSTANCE.getLiquifierRecipes().removeIf(r -> { + if (input.test(r.getInput())) { + addBackup(r); + return true; + } + return false; + }); + } + + @MethodDescription(description = "groovyscript.wiki.streamRecipes", type = MethodDescription.Type.QUERY) + public SimpleObjectStream streamRecipes() { + return new SimpleObjectStream<>(ModRecipes.INSTANCE.getLiquifierRecipes()).setRemover(this::remove); + } + + @MethodDescription(description = "groovyscript.wiki.removeAll", priority = 2000, example = @Example(commented = true)) + public void removeAll() { + ModRecipes.INSTANCE.getLiquifierRecipes().forEach(this::addBackup); + ModRecipes.INSTANCE.getLiquifierRecipes().clear(); + } + + @Property(property = "input", valid = @Comp("1")) + @Property(property = "fluidOutput", valid = @Comp("1")) + public static class RecipeBuilder extends AbstractRecipeBuilder { + + @Override + public String getErrorMsg() { + return "Error adding Alchemistry Liquifier recipe"; + } + + @Override + public void validate(GroovyLog.Msg msg) { + validateItems(msg, 1, 1, 0, 0); + validateFluids(msg, 0, 0, 1, 1); + } + + @Nullable + @Override + @RecipeBuilderRegistrationMethod + public LiquifierRecipe register() { + if (!validate()) return null; + LiquifierRecipe recipe = new LiquifierRecipe(IngredientHelper.toItemStack(input.get(0)), fluidOutput.get(0)); + ModSupport.ALCHEMISTRY.get().liquifier.add(recipe); + return recipe; + } + } +} diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/appliedenergistics2/AppliedEnergistics2.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/appliedenergistics2/AppliedEnergistics2.java index ec9c984a9..12a2ef3ca 100644 --- a/src/main/java/com/cleanroommc/groovyscript/compat/mods/appliedenergistics2/AppliedEnergistics2.java +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/appliedenergistics2/AppliedEnergistics2.java @@ -1,9 +1,12 @@ package com.cleanroommc.groovyscript.compat.mods.appliedenergistics2; import appeng.api.config.TunnelType; -import com.cleanroommc.groovyscript.api.IGameObjectHandler; +import com.cleanroommc.groovyscript.api.IGameObjectParser; import com.cleanroommc.groovyscript.compat.mods.ModPropertyContainer; -import com.cleanroommc.groovyscript.gameobjects.GameObjectHandlerManager; +import com.cleanroommc.groovyscript.gameobjects.GameObjectHandler; + +import java.util.Arrays; +import java.util.Locale; public class AppliedEnergistics2 extends ModPropertyContainer { @@ -23,6 +26,10 @@ public AppliedEnergistics2() { @Override public void initialize() { - GameObjectHandlerManager.registerGameObjectHandler("appliedenergistics2", "tunnel", IGameObjectHandler.wrapEnum(TunnelType.class, false)); + GameObjectHandler.builder("tunnel", TunnelType.class) + .mod("appliedenergistics2") + .parser(IGameObjectParser.wrapEnum(TunnelType.class, false)) + .completerOfNamed(() -> Arrays.asList(TunnelType.values()), v -> v.name().toUpperCase(Locale.ROOT)) + .register(); } } diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/astralsorcery/AstralSorcery.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/astralsorcery/AstralSorcery.java index f2c0912c1..31ce68ec1 100644 --- a/src/main/java/com/cleanroommc/groovyscript/compat/mods/astralsorcery/AstralSorcery.java +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/astralsorcery/AstralSorcery.java @@ -8,7 +8,7 @@ import com.cleanroommc.groovyscript.compat.mods.astralsorcery.perktree.PerkTreeConfig; import com.cleanroommc.groovyscript.compat.mods.astralsorcery.starlightaltar.StarlightAltar; import com.cleanroommc.groovyscript.core.mixin.astralsorcery.ConstellationRegistryAccessor; -import com.cleanroommc.groovyscript.gameobjects.GameObjectHandlerManager; +import com.cleanroommc.groovyscript.gameobjects.GameObjectHandler; import com.cleanroommc.groovyscript.helper.ingredient.OreDictIngredient; import com.cleanroommc.groovyscript.sandbox.expand.ExpansionHelper; import hellfirepvp.astralsorcery.common.constellation.IConstellation; @@ -54,14 +54,18 @@ public AstralSorcery() { @Override public void initialize() { - GameObjectHandlerManager.registerGameObjectHandler("astralsorcery", "constellation", (s, args) -> { - for (IConstellation constellation : ConstellationRegistryAccessor.getConstellationList()) { - if (constellation.getSimpleName().equalsIgnoreCase(s)) { - return Result.some(constellation); - } - } - return Result.error(); - }); + GameObjectHandler.builder("constellation", IConstellation.class) + .mod("astralsorcery") + .parser((s, args) -> { + for (IConstellation constellation : ConstellationRegistryAccessor.getConstellationList()) { + if (constellation.getSimpleName().equalsIgnoreCase(s)) { + return Result.some(constellation); + } + } + return Result.error(); + }) + .completerOfNamed(ConstellationRegistryAccessor::getConstellationList, IConstellation::getSimpleName) + .register(); ExpansionHelper.mixinClass(ItemStack.class, CrystalItemStackExpansion.class); } diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/astralsorcery/Constellation.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/astralsorcery/Constellation.java index 398a43902..739f7e8f7 100644 --- a/src/main/java/com/cleanroommc/groovyscript/compat/mods/astralsorcery/Constellation.java +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/astralsorcery/Constellation.java @@ -317,9 +317,9 @@ private boolean validate() { if (this.name == null || "".equals(this.name)) errors.add("name must be provided"); - if (this.connections.equals(new ArrayList<>())) + if (this.connections.isEmpty()) errors.add("connections must not be empty"); - if (!this.phases.isEmpty()) + if (type == Type.MINOR && this.phases.isEmpty()) errors.add("minor constellations require at least one moon phase"); if (!errors.isEmpty()) { diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/betterwithmods/AnvilCrafting.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/betterwithmods/AnvilCrafting.java new file mode 100644 index 000000000..164a5f810 --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/betterwithmods/AnvilCrafting.java @@ -0,0 +1,96 @@ +package com.cleanroommc.groovyscript.compat.mods.betterwithmods; + +import betterwithmods.common.registry.anvil.AnvilCraftingManager; +import com.cleanroommc.groovyscript.api.IIngredient; +import com.cleanroommc.groovyscript.api.documentation.annotations.Example; +import com.cleanroommc.groovyscript.api.documentation.annotations.MethodDescription; +import com.cleanroommc.groovyscript.api.documentation.annotations.RecipeBuilderDescription; +import com.cleanroommc.groovyscript.api.documentation.annotations.RegistryDescription; +import com.cleanroommc.groovyscript.helper.Alias; +import com.cleanroommc.groovyscript.helper.SimpleObjectStream; +import com.cleanroommc.groovyscript.helper.ingredient.IngredientHelper; +import com.cleanroommc.groovyscript.registry.VirtualizedRegistry; +import net.minecraft.item.ItemStack; +import net.minecraft.item.crafting.IRecipe; +import net.minecraft.item.crafting.Ingredient; + +@RegistryDescription +public class AnvilCrafting extends VirtualizedRegistry { + + public AnvilCrafting() { + super(Alias.generateOfClass(AnvilCrafting.class).andGenerate("SoulforgedSteelAnvil")); + } + + @RecipeBuilderDescription(example = { + @Example(".output(item('minecraft:diamond') * 32).matrix([[item('minecraft:gold_ingot'),item('minecraft:gold_ingot'),item('minecraft:gold_ingot'),null],[item('minecraft:gold_ingot'),item('minecraft:gold_ingot'),item('minecraft:gold_ingot'),null],[item('minecraft:gold_ingot'),item('minecraft:gold_ingot'),item('minecraft:gold_ingot'),null],[null,null,null,item('minecraft:gold_ingot').transform({ _ -> item('minecraft:diamond') })]])"), + @Example(".output(item('minecraft:diamond')).matrix('BXXX').mirrored().key('B', item('minecraft:stone')).key('X', item('minecraft:gold_ingot'))") + }) + public AnvilRecipeBuilder.Shaped shapedBuilder() { + return new AnvilRecipeBuilder.Shaped(); + } + + @RecipeBuilderDescription(example = @Example(".name(resource('example:anvil_clay')).output(item('minecraft:clay')).input([item('minecraft:cobblestone'), item('minecraft:gold_ingot')])")) + public AnvilRecipeBuilder.Shapeless shapelessBuilder() { + return new AnvilRecipeBuilder.Shapeless(); + } + + @Override + public void onReload() { + removeScripted().forEach(recipe -> AnvilCraftingManager.ANVIL_CRAFTING.removeIf(r -> r == recipe)); + AnvilCraftingManager.ANVIL_CRAFTING.addAll(restoreFromBackup()); + } + + public IRecipe add(IRecipe recipe) { + if (recipe != null) { + addScripted(recipe); + AnvilCraftingManager.ANVIL_CRAFTING.add(recipe); + } + return recipe; + } + + public boolean remove(IRecipe recipe) { + if (AnvilCraftingManager.ANVIL_CRAFTING.removeIf(r -> r == recipe)) { + addBackup(recipe); + return true; + } + return false; + } + + @MethodDescription(description = "groovyscript.wiki.removeByOutput", example = @Example("item('betterwithmods:steel_block')")) + public boolean removeByOutput(IIngredient output) { + return AnvilCraftingManager.ANVIL_CRAFTING.removeIf(r -> { + if (output.test(r.getRecipeOutput())) { + addBackup(r); + return true; + } + return false; + }); + } + + @MethodDescription(description = "groovyscript.wiki.removeByInput", example = @Example("item('minecraft:redstone')")) + public boolean removeByInput(IIngredient input) { + return AnvilCraftingManager.ANVIL_CRAFTING.removeIf(r -> { + for (Ingredient ingredient : r.getIngredients()) { + for (ItemStack item : ingredient.getMatchingStacks()) { + if (input.test(item)) { + addBackup(r); + return true; + } + } + } + return false; + }); + } + + @MethodDescription(description = "groovyscript.wiki.streamRecipes", type = MethodDescription.Type.QUERY) + public SimpleObjectStream streamRecipes() { + return new SimpleObjectStream<>(AnvilCraftingManager.ANVIL_CRAFTING).setRemover(this::remove); + } + + @MethodDescription(description = "groovyscript.wiki.removeAll", priority = 2000, example = @Example(commented = true)) + public void removeAll() { + AnvilCraftingManager.ANVIL_CRAFTING.forEach(this::addBackup); + AnvilCraftingManager.ANVIL_CRAFTING.clear(); + } + +} diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/betterwithmods/AnvilRecipeBuilder.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/betterwithmods/AnvilRecipeBuilder.java new file mode 100644 index 000000000..fef2cb3a6 --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/betterwithmods/AnvilRecipeBuilder.java @@ -0,0 +1,186 @@ +package com.cleanroommc.groovyscript.compat.mods.betterwithmods; + +import betterwithmods.common.registry.anvil.ShapedAnvilRecipe; +import betterwithmods.common.registry.anvil.ShapelessAnvilRecipe; +import com.cleanroommc.groovyscript.api.GroovyLog; +import com.cleanroommc.groovyscript.api.IIngredient; +import com.cleanroommc.groovyscript.api.documentation.annotations.Comp; +import com.cleanroommc.groovyscript.api.documentation.annotations.Property; +import com.cleanroommc.groovyscript.api.documentation.annotations.RecipeBuilderMethodDescription; +import com.cleanroommc.groovyscript.api.documentation.annotations.RecipeBuilderRegistrationMethod; +import com.cleanroommc.groovyscript.compat.mods.ModSupport; +import com.cleanroommc.groovyscript.compat.vanilla.CraftingRecipeBuilder; +import com.cleanroommc.groovyscript.helper.ingredient.IngredientHelper; +import it.unimi.dsi.fastutil.chars.Char2ObjectOpenHashMap; +import net.minecraft.item.crafting.IRecipe; +import org.apache.commons.lang3.ArrayUtils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +public abstract class AnvilRecipeBuilder extends CraftingRecipeBuilder { + + public AnvilRecipeBuilder() { + super(4, 4); + } + + public static class Shaped extends AnvilRecipeBuilder { + + @Property(value = "groovyscript.wiki.craftingrecipe.keyMap.value", defaultValue = "' ' = IIngredient.EMPTY", priority = 200) + private final Char2ObjectOpenHashMap keyMap = new Char2ObjectOpenHashMap<>(); + private final List errors = new ArrayList<>(); + @Property("groovyscript.wiki.craftingrecipe.mirrored.value") + protected boolean mirrored = false; + @Property(value = "groovyscript.wiki.craftingrecipe.keyBasedMatrix.value", requirement = "groovyscript.wiki.craftingrecipe.matrix.required", priority = 210) + private String[] keyBasedMatrix; + @Property(value = "groovyscript.wiki.craftingrecipe.ingredientMatrix.value", requirement = "groovyscript.wiki.craftingrecipe.matrix.required", valid = { + @Comp(value = "1", type = Comp.Type.GTE), @Comp(value = "81", type = Comp.Type.LTE)}, priority = 250) + private List> ingredientMatrix; + + public Shaped() { + keyMap.put(' ', IIngredient.EMPTY); + } + + @RecipeBuilderMethodDescription + public AnvilRecipeBuilder.Shaped mirrored(boolean mirrored) { + this.mirrored = mirrored; + return this; + } + + @RecipeBuilderMethodDescription + public AnvilRecipeBuilder.Shaped mirrored() { + return mirrored(true); + } + + @RecipeBuilderMethodDescription(field = "keyBasedMatrix") + public AnvilRecipeBuilder.Shaped matrix(String... matrix) { + this.keyBasedMatrix = matrix; + return this; + } + + @RecipeBuilderMethodDescription(field = "keyBasedMatrix") + public AnvilRecipeBuilder.Shaped shape(String... matrix) { + this.keyBasedMatrix = matrix; + return this; + } + + @RecipeBuilderMethodDescription(field = "keyBasedMatrix") + public AnvilRecipeBuilder.Shaped row(String row) { + if (this.keyBasedMatrix == null) { + this.keyBasedMatrix = new String[]{row}; + } else { + this.keyBasedMatrix = ArrayUtils.add(this.keyBasedMatrix, row); + } + return this; + } + + @RecipeBuilderMethodDescription(field = "keyMap") + public AnvilRecipeBuilder.Shaped key(char c, IIngredient ingredient) { + this.keyMap.put(c, ingredient); + return this; + } + + @RecipeBuilderMethodDescription(field = "keyMap") + public AnvilRecipeBuilder.Shaped key(String c, IIngredient ingredient) { + if (c == null || c.length() != 1) { + errors.add("key must be a single char, but found '" + c + "'"); + return this; + } + this.keyMap.put(c.charAt(0), ingredient); + return this; + } + + @RecipeBuilderMethodDescription(field = "keyMap") + public AnvilRecipeBuilder.Shaped key(Map map) { + for (Map.Entry x : map.entrySet()) { + key(x.getKey(), x.getValue()); + } + return this; + } + + @RecipeBuilderMethodDescription(field = "ingredientMatrix") + public AnvilRecipeBuilder.Shaped matrix(List> matrix) { + this.ingredientMatrix = matrix; + return this; + } + + @RecipeBuilderMethodDescription(field = "ingredientMatrix") + public AnvilRecipeBuilder.Shaped shape(List> matrix) { + this.ingredientMatrix = matrix; + return this; + } + + @Override + @RecipeBuilderRegistrationMethod + public IRecipe register() { + GroovyLog.Msg msg = GroovyLog.msg("Error adding shaped Better With Mods Anvil recipe").error() + .add((keyBasedMatrix == null || keyBasedMatrix.length == 0) && (ingredientMatrix == null || ingredientMatrix.isEmpty()), () -> "No matrix was defined") + .add(keyBasedMatrix != null && ingredientMatrix != null, () -> "A key based matrix AND a ingredient based matrix was defined. This is not allowed!"); + if (msg.postIfNotEmpty()) return null; + msg.add(IngredientHelper.isEmpty(this.output), () -> "Output must not be empty"); + ShapedAnvilRecipe recipe = null; + if (keyBasedMatrix != null) { + recipe = validateShape(msg, errors, keyBasedMatrix, keyMap, ((width1, height1, ingredients) -> AnvilShapedRecipe.make(name, output, ingredients, width1, height1, mirrored, recipeFunction, recipeAction))); + } else if (ingredientMatrix != null) { + recipe = validateShape(msg, ingredientMatrix, ((width1, height1, ingredients) -> AnvilShapedRecipe.make(name, output.copy(), ingredients, width1, height1, mirrored, recipeFunction, recipeAction))); + } + if (msg.postIfNotEmpty()) return null; + if (recipe != null) { + ModSupport.BETTER_WITH_MODS.get().anvilCrafting.add(recipe); + } + return recipe; + } + } + + public static class Shapeless extends AnvilRecipeBuilder { + + @Property(value = "groovyscript.wiki.craftingrecipe.ingredients.value", valid = {@Comp(value = "1", type = Comp.Type.GTE), + @Comp(value = "81", type = Comp.Type.LTE)}, priority = 250) + private final List ingredients = new ArrayList<>(); + + @RecipeBuilderMethodDescription(field = "ingredients") + public AnvilRecipeBuilder.Shapeless input(IIngredient ingredient) { + ingredients.add(ingredient); + return this; + } + + @RecipeBuilderMethodDescription(field = "ingredients") + public AnvilRecipeBuilder.Shapeless input(IIngredient... ingredients) { + if (ingredients != null) { + for (IIngredient ingredient : ingredients) { + input(ingredient); + } + } + return this; + } + + @RecipeBuilderMethodDescription(field = "ingredients") + public AnvilRecipeBuilder.Shapeless input(Collection ingredients) { + if (ingredients != null && !ingredients.isEmpty()) { + for (IIngredient ingredient : ingredients) { + input(ingredient); + } + } + return this; + } + + public boolean validate() { + GroovyLog.Msg msg = GroovyLog.msg("Error adding shapeless Better With Mods Anvil recipe").error(); + msg.add(IngredientHelper.isEmpty(this.output), () -> "Output must not be empty"); + msg.add(ingredients.isEmpty(), () -> "inputs must not be empty"); + msg.add(ingredients.size() > width * height, () -> "maximum inputs are " + (width * height) + " but found " + ingredients.size()); + return !msg.postIfNotEmpty(); + } + + @Override + @RecipeBuilderRegistrationMethod + public IRecipe register() { + if (!validate()) return null; + ShapelessAnvilRecipe recipe = AnvilShapelessRecipe.make(name, output.copy(), ingredients, recipeFunction, recipeAction); + ModSupport.BETTER_WITH_MODS.get().anvilCrafting.add(recipe); + return recipe; + } + } +} diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/betterwithmods/AnvilShapedRecipe.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/betterwithmods/AnvilShapedRecipe.java new file mode 100644 index 000000000..696318835 --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/betterwithmods/AnvilShapedRecipe.java @@ -0,0 +1,56 @@ +package com.cleanroommc.groovyscript.compat.mods.betterwithmods; + +import betterwithmods.common.registry.anvil.ShapedAnvilRecipe; +import com.cleanroommc.groovyscript.api.IIngredient; +import com.cleanroommc.groovyscript.compat.vanilla.ShapedCraftingRecipe; +import groovy.lang.Closure; +import net.minecraft.inventory.InventoryCrafting; +import net.minecraft.item.ItemStack; +import net.minecraft.util.NonNullList; +import net.minecraft.util.ResourceLocation; +import net.minecraft.world.World; +import net.minecraftforge.common.crafting.CraftingHelper; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class AnvilShapedRecipe extends ShapedAnvilRecipe { + + private final ShapedCraftingRecipe groovyRecipe; + + private AnvilShapedRecipe(ResourceLocation resourceLocation, ShapedCraftingRecipe groovyRecipe, CraftingHelper.ShapedPrimer primer) { + super(resourceLocation, groovyRecipe.getRecipeOutput(), primer); + this.groovyRecipe = groovyRecipe; + setMirrored(this.groovyRecipe.isMirrored()); + } + +// public static AnvilShapedRecipe make(ItemStack output, List input, int width, int height, boolean mirrored, @Nullable Closure recipeFunction, @Nullable Closure recipeAction) { +// return make(null, output, input, width, height, mirrored, recipeFunction, recipeAction); +// } + + public static AnvilShapedRecipe make(ResourceLocation resourceLocation, ItemStack output, List input, int width, int height, boolean mirrored, @Nullable Closure recipeFunction, @Nullable Closure recipeAction) { + ShapedCraftingRecipe recipe = new ShapedCraftingRecipe(output, input, width, height, mirrored, recipeFunction, recipeAction); + CraftingHelper.ShapedPrimer primer = new CraftingHelper.ShapedPrimer(); + primer.width = width; + primer.height = height; + primer.mirrored = mirrored; + primer.input = recipe.getIngredients(); + return new AnvilShapedRecipe(resourceLocation, recipe, primer); + } + + @Override + public @NotNull ItemStack getCraftingResult(@NotNull InventoryCrafting inv) { + return this.groovyRecipe.getCraftingResult(inv); + } + + @Override + public boolean matches(@NotNull InventoryCrafting inv, @NotNull World world) { + return this.groovyRecipe.matches(inv, world); + } + + @Override + public @NotNull NonNullList getRemainingItems(@NotNull InventoryCrafting inv) { + return this.groovyRecipe.getRemainingItems(inv); + } +} diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/betterwithmods/AnvilShapelessRecipe.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/betterwithmods/AnvilShapelessRecipe.java new file mode 100644 index 000000000..191e88830 --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/betterwithmods/AnvilShapelessRecipe.java @@ -0,0 +1,49 @@ +package com.cleanroommc.groovyscript.compat.mods.betterwithmods; + +import betterwithmods.common.registry.anvil.ShapelessAnvilRecipe; +import com.cleanroommc.groovyscript.api.IIngredient; +import com.cleanroommc.groovyscript.compat.vanilla.ShapelessCraftingRecipe; +import groovy.lang.Closure; +import net.minecraft.inventory.InventoryCrafting; +import net.minecraft.item.ItemStack; +import net.minecraft.util.NonNullList; +import net.minecraft.util.ResourceLocation; +import net.minecraft.world.World; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class AnvilShapelessRecipe extends ShapelessAnvilRecipe { + + private final ShapelessCraftingRecipe groovyRecipe; + + public AnvilShapelessRecipe(ResourceLocation resourceLocation, ShapelessCraftingRecipe groovyRecipe) { + super(resourceLocation, groovyRecipe.getIngredients(), groovyRecipe.getRecipeOutput()); + this.groovyRecipe = groovyRecipe; + } + +// public static AnvilShapelessRecipe make(ItemStack output, List input, @Nullable Closure recipeFunction, @Nullable Closure recipeAction) { +// return make(null, output, input, recipeFunction, recipeAction); +// } + + public static AnvilShapelessRecipe make(ResourceLocation resourceLocation, ItemStack output, List input, @Nullable Closure recipeFunction, @Nullable Closure recipeAction) { + ShapelessCraftingRecipe recipe = new ShapelessCraftingRecipe(output, input, recipeFunction, recipeAction); + return new AnvilShapelessRecipe(resourceLocation, recipe); + } + + @Override + public @NotNull ItemStack getCraftingResult(@NotNull InventoryCrafting inv) { + return this.groovyRecipe.getCraftingResult(inv); + } + + @Override + public boolean matches(@NotNull InventoryCrafting inv, @NotNull World world) { + return this.groovyRecipe.matches(inv, world); + } + + @Override + public @NotNull NonNullList getRemainingItems(@NotNull InventoryCrafting inv) { + return this.groovyRecipe.getRemainingItems(inv); + } +} diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/betterwithmods/BetterWithMods.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/betterwithmods/BetterWithMods.java new file mode 100644 index 000000000..82273951b --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/betterwithmods/BetterWithMods.java @@ -0,0 +1,31 @@ +package com.cleanroommc.groovyscript.compat.mods.betterwithmods; + +import com.cleanroommc.groovyscript.compat.mods.ModPropertyContainer; + +public class BetterWithMods extends ModPropertyContainer { + + public final AnvilCrafting anvilCrafting = new AnvilCrafting(); + public final Cauldron cauldron = new Cauldron(); + public final Crucible crucible = new Crucible(); + public final Kiln kiln = new Kiln(); + public final MillStone millStone = new MillStone(); + public final Saw saw = new Saw(); + public final Turntable turntable = new Turntable(); + public final Heat heat = new Heat(); + public final Hopper hopper = new Hopper(); + public final HopperFilters hopperFilters = new HopperFilters(); + + public BetterWithMods() { + addRegistry(anvilCrafting); + addRegistry(cauldron); + addRegistry(crucible); + addRegistry(kiln); + addRegistry(millStone); + addRegistry(saw); + addRegistry(turntable); + addRegistry(heat); + addRegistry(hopper); + addRegistry(hopperFilters); + } + +} diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/betterwithmods/Cauldron.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/betterwithmods/Cauldron.java new file mode 100644 index 000000000..1732ded53 --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/betterwithmods/Cauldron.java @@ -0,0 +1,145 @@ +package com.cleanroommc.groovyscript.compat.mods.betterwithmods; + +import betterwithmods.common.BWRegistry; +import betterwithmods.common.registry.bulk.recipes.CookingPotRecipe; +import com.cleanroommc.groovyscript.api.GroovyLog; +import com.cleanroommc.groovyscript.api.IIngredient; +import com.cleanroommc.groovyscript.api.documentation.annotations.*; +import com.cleanroommc.groovyscript.compat.mods.ModSupport; +import com.cleanroommc.groovyscript.helper.SimpleObjectStream; +import com.cleanroommc.groovyscript.helper.recipe.AbstractRecipeBuilder; +import com.cleanroommc.groovyscript.registry.VirtualizedRegistry; +import net.minecraft.item.ItemStack; +import net.minecraft.item.crafting.Ingredient; +import org.jetbrains.annotations.Nullable; + +import java.util.stream.Collectors; + +@RegistryDescription +public class Cauldron extends VirtualizedRegistry { + + @RecipeBuilderDescription(example = { + @Example(".input(item('minecraft:clay')).output(item('minecraft:diamond')).heat(2)"), + @Example(".input(item('minecraft:diamond')).output(item('minecraft:gold_ingot') * 16).ignoreHeat()") + }) + public RecipeBuilder recipeBuilder() { + return new RecipeBuilder(); + } + + @Override + public void onReload() { + removeScripted().forEach(recipe -> BWRegistry.CAULDRON.getRecipes().removeIf(r -> r == recipe)); + BWRegistry.CAULDRON.getRecipes().addAll(restoreFromBackup()); + } + + public CookingPotRecipe add(CookingPotRecipe recipe) { + if (recipe != null) { + addScripted(recipe); + BWRegistry.CAULDRON.getRecipes().add(recipe); + } + return recipe; + } + + public boolean remove(CookingPotRecipe recipe) { + if (BWRegistry.CAULDRON.getRecipes().removeIf(r -> r == recipe)) { + addBackup(recipe); + return true; + } + return false; + } + + @MethodDescription(description = "groovyscript.wiki.removeByOutput", example = @Example("item('minecraft:gunpowder')")) + public boolean removeByOutput(IIngredient output) { + return BWRegistry.CAULDRON.getRecipes().removeIf(r -> { + for (ItemStack itemstack : r.getOutputs()) { + if (output.test(itemstack)) { + addBackup(r); + return true; + } + } + return false; + }); + } + + @MethodDescription(description = "groovyscript.wiki.removeByInput", example = @Example("item('minecraft:gunpowder')")) + public boolean removeByInput(IIngredient input) { + return BWRegistry.CAULDRON.getRecipes().removeIf(r -> { + for (Ingredient ingredient : r.getInputs()) { + for (ItemStack item : ingredient.getMatchingStacks()) { + if (input.test(item)) { + addBackup(r); + return true; + } + } + } + return false; + }); + } + + @MethodDescription(description = "groovyscript.wiki.streamRecipes", type = MethodDescription.Type.QUERY) + public SimpleObjectStream streamRecipes() { + return new SimpleObjectStream<>(BWRegistry.CAULDRON.getRecipes()).setRemover(this::remove); + } + + @MethodDescription(description = "groovyscript.wiki.removeAll", priority = 2000, example = @Example(commented = true)) + public void removeAll() { + BWRegistry.CAULDRON.getRecipes().forEach(this::addBackup); + BWRegistry.CAULDRON.getRecipes().clear(); + } + + @Property(property = "input", valid = {@Comp(value = "1", type = Comp.Type.GTE), @Comp(value = "9", type = Comp.Type.LTE)}) + @Property(property = "output", valid = {@Comp(value = "1", type = Comp.Type.GTE), @Comp(value = "9", type = Comp.Type.LTE)}) + public static class RecipeBuilder extends AbstractRecipeBuilder { + + @Property(defaultValue = "1") + private int heat = 1; + @Property + private boolean ignoreHeat; + @Property(defaultValue = "1") + private int priority = 1; + + public RecipeBuilder heat(int heat) { + this.heat = heat; + return this; + } + + public RecipeBuilder ignoreHeat(boolean ignoreHeat) { + this.ignoreHeat = ignoreHeat; + return this; + } + + public RecipeBuilder ignoreHeat() { + this.ignoreHeat = !ignoreHeat; + return this; + } + + public RecipeBuilder priority(int priority) { + this.priority = priority; + return this; + } + + @Override + public String getErrorMsg() { + return "Error adding Better With Mods Cauldron recipe"; + } + + @Override + public void validate(GroovyLog.Msg msg) { + validateItems(msg, 1, 9, 1, 9); + validateFluids(msg); + } + + @Override + @RecipeBuilderRegistrationMethod + public @Nullable CookingPotRecipe register() { + if (!validate()) return null; + + CookingPotRecipe recipe = new CookingPotRecipe(input.stream().map(IIngredient::toMcIngredient).collect(Collectors.toList()), output, heat); + recipe.setIgnoreHeat(ignoreHeat); + recipe.setPriority(priority); + ModSupport.BETTER_WITH_MODS.get().cauldron.add(recipe); + return recipe; + } + } + +} diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/betterwithmods/Crucible.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/betterwithmods/Crucible.java new file mode 100644 index 000000000..f69f512cf --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/betterwithmods/Crucible.java @@ -0,0 +1,150 @@ +package com.cleanroommc.groovyscript.compat.mods.betterwithmods; + +import betterwithmods.common.BWRegistry; +import betterwithmods.common.registry.bulk.recipes.CookingPotRecipe; +import com.cleanroommc.groovyscript.api.GroovyLog; +import com.cleanroommc.groovyscript.api.IIngredient; +import com.cleanroommc.groovyscript.api.documentation.annotations.*; +import com.cleanroommc.groovyscript.compat.mods.ModSupport; +import com.cleanroommc.groovyscript.helper.SimpleObjectStream; +import com.cleanroommc.groovyscript.helper.ingredient.IngredientHelper; +import com.cleanroommc.groovyscript.helper.recipe.AbstractRecipeBuilder; +import com.cleanroommc.groovyscript.registry.VirtualizedRegistry; +import net.minecraft.item.ItemStack; +import net.minecraft.item.crafting.Ingredient; +import net.minecraftforge.items.ItemHandlerHelper; +import org.jetbrains.annotations.Nullable; + +import java.util.stream.Collectors; + +@RegistryDescription +public class Crucible extends VirtualizedRegistry { + + @RecipeBuilderDescription(example = { + @Example(".input(item('minecraft:clay')).output(item('minecraft:diamond')).heat(2)"), + @Example(".input(item('minecraft:diamond')).output(item('minecraft:gold_ingot') * 16).ignoreHeat()") + }) + public RecipeBuilder recipeBuilder() { + return new RecipeBuilder(); + } + + @Override + public void onReload() { + removeScripted().forEach(recipe -> BWRegistry.CRUCIBLE.getRecipes().removeIf(r -> r == recipe)); + BWRegistry.CRUCIBLE.getRecipes().addAll(restoreFromBackup()); + } + + public CookingPotRecipe add(CookingPotRecipe recipe) { + if (recipe != null) { + addScripted(recipe); + BWRegistry.CRUCIBLE.getRecipes().add(recipe); + } + return recipe; + } + + public boolean remove(CookingPotRecipe recipe) { + if (BWRegistry.CRUCIBLE.getRecipes().removeIf(r -> r == recipe)) { + addBackup(recipe); + return true; + } + return false; + } + + @MethodDescription(description = "groovyscript.wiki.removeByOutput", example = @Example("item('minecraft:gunpowder')")) + public boolean removeByOutput(ItemStack output) { + return BWRegistry.CRUCIBLE.getRecipes().removeIf(r -> { + for (ItemStack itemstack : r.getOutputs()) { + if (ItemHandlerHelper.canItemStacksStack(itemstack, output)) { + addBackup(r); + return true; + } + } + return false; + }); + } + + @MethodDescription(description = "groovyscript.wiki.removeByInput", example = @Example("item('minecraft:gunpowder')")) + public boolean removeByInput(ItemStack input) { + return BWRegistry.CRUCIBLE.getRecipes().removeIf(r -> { + for (Ingredient ingredient : r.getInputs()) { + if (ingredient.test(input)) { + addBackup(r); + return true; + } + } + return false; + }); + } + + @MethodDescription(description = "groovyscript.wiki.removeByInput") + public boolean removeByInput(IIngredient input) { + return removeByInput(IngredientHelper.toItemStack(input)); + } + + @MethodDescription(description = "groovyscript.wiki.streamRecipes", type = MethodDescription.Type.QUERY) + public SimpleObjectStream streamRecipes() { + return new SimpleObjectStream<>(BWRegistry.CRUCIBLE.getRecipes()).setRemover(this::remove); + } + + @MethodDescription(description = "groovyscript.wiki.removeAll", priority = 2000, example = @Example(commented = true)) + public void removeAll() { + BWRegistry.CRUCIBLE.getRecipes().forEach(this::addBackup); + BWRegistry.CRUCIBLE.getRecipes().clear(); + } + + @Property(property = "input", valid = {@Comp(value = "1", type = Comp.Type.GTE), @Comp(value = "9", type = Comp.Type.LTE)}) + @Property(property = "output", valid = {@Comp(value = "1", type = Comp.Type.GTE), @Comp(value = "9", type = Comp.Type.LTE)}) + public static class RecipeBuilder extends AbstractRecipeBuilder { + + @Property(defaultValue = "1") + private int heat = 1; + @Property + private boolean ignoreHeat; + @Property(defaultValue = "1") + private int priority = 1; + + public RecipeBuilder heat(int heat) { + this.heat = heat; + return this; + } + + public RecipeBuilder ignoreHeat(boolean ignoreHeat) { + this.ignoreHeat = ignoreHeat; + return this; + } + + public RecipeBuilder ignoreHeat() { + this.ignoreHeat = !ignoreHeat; + return this; + } + + public RecipeBuilder priority(int priority) { + this.priority = priority; + return this; + } + + @Override + public String getErrorMsg() { + return "Error adding Better With Mods Cauldron recipe"; + } + + @Override + public void validate(GroovyLog.Msg msg) { + validateItems(msg, 1, 9, 1, 9); + validateFluids(msg); + } + + @Override + @RecipeBuilderRegistrationMethod + public @Nullable CookingPotRecipe register() { + if (!validate()) return null; + + CookingPotRecipe recipe = new CookingPotRecipe(input.stream().map(IIngredient::toMcIngredient).collect(Collectors.toList()), output, heat); + recipe.setIgnoreHeat(ignoreHeat); + recipe.setPriority(priority); + ModSupport.BETTER_WITH_MODS.get().crucible.add(recipe); + return recipe; + } + } + +} diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/betterwithmods/Heat.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/betterwithmods/Heat.java new file mode 100644 index 000000000..ee4c17748 --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/betterwithmods/Heat.java @@ -0,0 +1,64 @@ +package com.cleanroommc.groovyscript.compat.mods.betterwithmods; + +import betterwithmods.common.registry.block.recipe.BlockIngredient; +import betterwithmods.common.registry.heat.BWMHeatRegistry; +import com.cleanroommc.groovyscript.api.GroovyBlacklist; +import com.cleanroommc.groovyscript.api.IIngredient; +import com.cleanroommc.groovyscript.api.documentation.annotations.Admonition; +import com.cleanroommc.groovyscript.api.documentation.annotations.Example; +import com.cleanroommc.groovyscript.api.documentation.annotations.MethodDescription; +import com.cleanroommc.groovyscript.api.documentation.annotations.RegistryDescription; +import com.cleanroommc.groovyscript.core.mixin.betterwithmods.BWMHeatRegistryAccessor; +import com.cleanroommc.groovyscript.registry.VirtualizedRegistry; +import net.minecraft.item.ItemStack; + +import java.util.List; + +@RegistryDescription( + admonition = @Admonition("groovyscript.wiki.betterwithmods.heat.note0") +) +public class Heat extends VirtualizedRegistry { + + @Override + @GroovyBlacklist + public void onReload() { + removeScripted().forEach(recipe -> BWMHeatRegistryAccessor.getHEAT_SOURCES().removeIf(r -> r == recipe)); + BWMHeatRegistryAccessor.getHEAT_SOURCES().addAll(restoreFromBackup()); + } + + public void add(BWMHeatRegistry.HeatSource heatSource) { + BWMHeatRegistryAccessor.getHEAT_SOURCES().add(heatSource); + addScripted(heatSource); + } + + public boolean remove(BWMHeatRegistry.HeatSource heatSource) { + if (!BWMHeatRegistryAccessor.getHEAT_SOURCES().remove(heatSource)) return false; + addBackup(heatSource); + return true; + } + + @MethodDescription(type = MethodDescription.Type.VALUE) + public void add(int heat, BlockIngredient ingredient) { + add(new BWMHeatRegistry.HeatSource(ingredient, heat)); + } + + @MethodDescription(type = MethodDescription.Type.VALUE, example = @Example("3, 'torch'")) + public void add(int heat, String input) { + add(heat, new BlockIngredient(input)); + } + + @MethodDescription(type = MethodDescription.Type.VALUE) + public void add(int heat, List input) { + add(heat, new BlockIngredient(input)); + } + + @MethodDescription(type = MethodDescription.Type.VALUE, example = @Example("4, item('minecraft:redstone_block'), item('minecraft:redstone_torch')")) + public void add(int heat, ItemStack... input) { + add(heat, new BlockIngredient(input)); + } + + @MethodDescription(type = MethodDescription.Type.VALUE) + public void add(int heat, IIngredient input) { + add(heat, new BlockIngredient(input.toMcIngredient())); + } +} diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/betterwithmods/Hopper.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/betterwithmods/Hopper.java new file mode 100644 index 000000000..d4c57c93e --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/betterwithmods/Hopper.java @@ -0,0 +1,141 @@ +package com.cleanroommc.groovyscript.compat.mods.betterwithmods; + +import betterwithmods.common.registry.HopperInteractions; +import com.cleanroommc.groovyscript.api.GroovyLog; +import com.cleanroommc.groovyscript.api.IIngredient; +import com.cleanroommc.groovyscript.api.documentation.annotations.*; +import com.cleanroommc.groovyscript.compat.mods.ModSupport; +import com.cleanroommc.groovyscript.helper.SimpleObjectStream; +import com.cleanroommc.groovyscript.helper.ingredient.ItemStackList; +import com.cleanroommc.groovyscript.helper.recipe.AbstractRecipeBuilder; +import com.cleanroommc.groovyscript.registry.VirtualizedRegistry; +import net.minecraft.item.ItemStack; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; + +@RegistryDescription +public class Hopper extends VirtualizedRegistry { + + @RecipeBuilderDescription(example = { + @Example(".name('betterwithmods:iron_bar').input(ore('sand')).output(item('minecraft:clay')).inWorldItemOutput(item('minecraft:gold_ingot'))"), + @Example(".name('betterwithmods:wicker').input(item('minecraft:clay')).inWorldItemOutput(item('minecraft:gold_ingot'))") + }) + public RecipeBuilder recipeBuilder() { + return new RecipeBuilder(); + } + + @Override + public void onReload() { + removeScripted().forEach(recipe -> HopperInteractions.RECIPES.removeIf(r -> r == recipe)); + HopperInteractions.RECIPES.addAll(restoreFromBackup()); + } + + public HopperInteractions.HopperRecipe add(HopperInteractions.HopperRecipe recipe) { + if (recipe != null) { + addScripted(recipe); + HopperInteractions.RECIPES.add(recipe); + } + return recipe; + } + + public boolean remove(HopperInteractions.HopperRecipe recipe) { + if (HopperInteractions.RECIPES.removeIf(r -> r == recipe)) { + addBackup(recipe); + return true; + } + return false; + } + + @MethodDescription(description = "groovyscript.wiki.removeByOutput", example = @Example("item('minecraft:gunpowder')")) + public boolean removeByOutput(IIngredient output) { + return HopperInteractions.RECIPES.removeIf(r -> { + for (ItemStack itemstack : r.getOutputs()) { + if (output.test(itemstack)) { + addBackup(r); + return true; + } + } + return false; + }); + } + + @MethodDescription(description = "groovyscript.wiki.removeByInput", example = @Example("item('minecraft:gunpowder')")) + public boolean removeByInput(IIngredient input) { + return HopperInteractions.RECIPES.removeIf(r -> { + for (ItemStack item : r.getInputs().getMatchingStacks()) { + if (input.test(item)) { + addBackup(r); + return true; + } + } + return false; + }); + } + + @MethodDescription(description = "groovyscript.wiki.streamRecipes", type = MethodDescription.Type.QUERY) + public SimpleObjectStream streamRecipes() { + return new SimpleObjectStream<>(HopperInteractions.RECIPES).setRemover(this::remove); + } + + @MethodDescription(description = "groovyscript.wiki.removeAll", priority = 2000, example = @Example(commented = true)) + public void removeAll() { + HopperInteractions.RECIPES.forEach(this::addBackup); + HopperInteractions.RECIPES.clear(); + } + + @Property(property = "name", value = "groovyscript.wiki.betterwithmods.hopper.name.value", valid = @Comp(value = "null", type = Comp.Type.NOT)) + @Property(property = "input", valid = @Comp("1")) + @Property(property = "output", valid = {@Comp(value = "0", type = Comp.Type.GTE), @Comp(value = "2", type = Comp.Type.LTE)}) + public static class RecipeBuilder extends AbstractRecipeBuilder { + + @Property(valid = {@Comp(value = "0", type = Comp.Type.GTE), @Comp(value = "2", type = Comp.Type.LTE)}) + protected final ItemStackList inWorldItemOutput = new ItemStackList(); + + @RecipeBuilderMethodDescription + public RecipeBuilder inWorldItemOutput(ItemStack inWorldItemOutput) { + this.inWorldItemOutput.add(inWorldItemOutput); + return this; + } + + @RecipeBuilderMethodDescription + public RecipeBuilder inWorldItemOutput(ItemStack... inWorldItemOutputs) { + for (ItemStack inWorldItemOutput : inWorldItemOutputs) { + inWorldItemOutput(inWorldItemOutput); + } + return this; + } + + @RecipeBuilderMethodDescription + public RecipeBuilder inWorldItemOutput(Collection inWorldItemOutputs) { + for (ItemStack inWorldItemOutput : inWorldItemOutputs) { + inWorldItemOutput(inWorldItemOutput); + } + return this; + } + + @Override + public String getErrorMsg() { + return "Error adding Better With Mods Filtered Hopper recipe"; + } + + @Override + public void validate(GroovyLog.Msg msg) { + msg.add(name == null, "name cannot be null"); + validateItems(msg, 1, 1, 0, 2); + validateCustom(msg, inWorldItemOutput, 0, 2, "item in world output"); + validateFluids(msg); + } + + @Override + @RecipeBuilderRegistrationMethod + public @Nullable HopperInteractions.HopperRecipe register() { + if (!validate()) return null; + + HopperInteractions.HopperRecipe recipe = new HopperInteractions.HopperRecipe(name.toString(), input.get(0).toMcIngredient(), output, inWorldItemOutput); + ModSupport.BETTER_WITH_MODS.get().hopper.add(recipe); + return recipe; + } + } + +} diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/betterwithmods/HopperFilters.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/betterwithmods/HopperFilters.java new file mode 100644 index 000000000..4863b109b --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/betterwithmods/HopperFilters.java @@ -0,0 +1,141 @@ +package com.cleanroommc.groovyscript.compat.mods.betterwithmods; + +import betterwithmods.api.tile.IHopperFilter; +import betterwithmods.common.BWRegistry; +import betterwithmods.common.registry.HopperFilter; +import com.cleanroommc.groovyscript.api.GroovyLog; +import com.cleanroommc.groovyscript.api.IIngredient; +import com.cleanroommc.groovyscript.api.documentation.annotations.*; +import com.cleanroommc.groovyscript.compat.mods.ModSupport; +import com.cleanroommc.groovyscript.core.mixin.betterwithmods.HopperFiltersAccessor; +import com.cleanroommc.groovyscript.helper.SimpleObjectStream; +import com.cleanroommc.groovyscript.helper.ingredient.IngredientHelper; +import com.cleanroommc.groovyscript.helper.recipe.AbstractRecipeBuilder; +import com.cleanroommc.groovyscript.registry.VirtualizedRegistry; +import net.minecraft.item.ItemStack; +import net.minecraft.item.crafting.Ingredient; +import org.jetbrains.annotations.Nullable; + +import java.util.stream.Collectors; + +@RegistryDescription +public class HopperFilters extends VirtualizedRegistry { + + @RecipeBuilderDescription(example = { + @Example(".name('too_weak_to_stop').filter(item('minecraft:string'))"), + @Example(".name('groovyscript:clay_only').filter(item('minecraft:clay')).input(item('minecraft:clay'))") + }) + public RecipeBuilder recipeBuilder() { + return new RecipeBuilder(); + } + + @Override + public void onReload() { + removeScripted().forEach(recipe -> ((HopperFiltersAccessor) BWRegistry.HOPPER_FILTERS).getFILTERS().remove(recipe.getName())); + restoreFromBackup().forEach(recipe -> ((HopperFiltersAccessor) BWRegistry.HOPPER_FILTERS).getFILTERS().put(recipe.getName(), recipe)); + } + + public IHopperFilter add(IHopperFilter recipe) { + if (recipe != null) { + addScripted(recipe); + ((HopperFiltersAccessor) BWRegistry.HOPPER_FILTERS).getFILTERS().put(recipe.getName(), recipe); + } + return recipe; + } + + public boolean remove(IHopperFilter recipe) { + if (((HopperFiltersAccessor) BWRegistry.HOPPER_FILTERS).getFILTERS().remove(recipe.getName(), recipe)) { + addBackup(recipe); + return true; + } + return false; + } + + @MethodDescription(example = @Example("'betterwithmods:ladder'")) + public boolean removeByName(String name) { + return ((HopperFiltersAccessor) BWRegistry.HOPPER_FILTERS).getFILTERS().remove(name) != null; + } + + @MethodDescription(example = @Example("item('minecraft:trapdoor')")) + public boolean removeByFilter(IIngredient input) { + return ((HopperFiltersAccessor) BWRegistry.HOPPER_FILTERS).getFILTERS().values().removeIf(r -> { + for (ItemStack item : r.getFilter().getMatchingStacks()) { + if (input.test(item)) { + addBackup(r); + return true; + } + } + return false; + }); + } + + @MethodDescription + public boolean removeByFiltered(IIngredient output) { + return ((HopperFiltersAccessor) BWRegistry.HOPPER_FILTERS).getFILTERS().values().removeIf(r -> { + if (!(r instanceof HopperFilter)) return false; + for (Ingredient ingredient : ((HopperFilter) r).getFiltered()) { + for (ItemStack item : ingredient.getMatchingStacks()) { + if (output.test(item)) { + addBackup(r); + return true; + } + } + } + return false; + }); + } + + @MethodDescription(description = "groovyscript.wiki.streamRecipes", type = MethodDescription.Type.QUERY) + public SimpleObjectStream streamRecipes() { + return new SimpleObjectStream<>(((HopperFiltersAccessor) BWRegistry.HOPPER_FILTERS).getFILTERS().values()) + .setRemover(this::remove); + } + + @MethodDescription(description = "groovyscript.wiki.removeAll", priority = 2000, example = @Example(commented = true)) + public void removeAll() { + ((HopperFiltersAccessor) BWRegistry.HOPPER_FILTERS).getFILTERS().values().forEach(this::addBackup); + ((HopperFiltersAccessor) BWRegistry.HOPPER_FILTERS).getFILTERS().values().clear(); + } + + @Property(property = "input", value = "groovyscript.wiki.betterwithmods.hopper_filters.filtered.value", valid = {@Comp(value = "0", type = Comp.Type.GTE), @Comp(value = "Integer.MAX_VALUE", type = Comp.Type.LTE)}) + public static class RecipeBuilder extends AbstractRecipeBuilder { + + @Property(valid = @Comp(value = "null", type = Comp.Type.NOT)) + private IIngredient filter; + + @RecipeBuilderMethodDescription + public RecipeBuilder filter(IIngredient filter) { + this.filter = filter; + return this; + } + + @Override + public String getRecipeNamePrefix() { + return "groovyscript_hopper_filter_"; + } + + @Override + public String getErrorMsg() { + return "Error adding Better With Mods Hopper Filter recipe"; + } + + @Override + public void validate(GroovyLog.Msg msg) { + validateName(); + validateItems(msg, 0, Integer.MAX_VALUE, 0, 0); + validateFluids(msg); + msg.add(IngredientHelper.isEmpty(filter), "filter must be defined"); + } + + @Override + @RecipeBuilderRegistrationMethod + public @Nullable IHopperFilter register() { + if (!validate()) return null; + + IHopperFilter recipe = new HopperFilter(name.toString(), filter.toMcIngredient(), input.stream().map(IIngredient::toMcIngredient).collect(Collectors.toList())); + ModSupport.BETTER_WITH_MODS.get().hopperFilters.add(recipe); + return recipe; + } + } + +} diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/betterwithmods/Kiln.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/betterwithmods/Kiln.java new file mode 100644 index 000000000..f7d32d36c --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/betterwithmods/Kiln.java @@ -0,0 +1,161 @@ +package com.cleanroommc.groovyscript.compat.mods.betterwithmods; + +import betterwithmods.common.BWRegistry; +import betterwithmods.common.registry.block.recipe.BlockIngredient; +import betterwithmods.common.registry.block.recipe.KilnRecipe; +import com.cleanroommc.groovyscript.api.GroovyLog; +import com.cleanroommc.groovyscript.api.IIngredient; +import com.cleanroommc.groovyscript.api.documentation.annotations.*; +import com.cleanroommc.groovyscript.compat.mods.ModSupport; +import com.cleanroommc.groovyscript.helper.SimpleObjectStream; +import com.cleanroommc.groovyscript.helper.recipe.AbstractRecipeBuilder; +import com.cleanroommc.groovyscript.registry.VirtualizedRegistry; +import net.minecraft.item.ItemStack; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +@RegistryDescription +public class Kiln extends VirtualizedRegistry { + + @RecipeBuilderDescription(example = { + @Example(".input(item('minecraft:clay')).output(item('minecraft:diamond')).heat(2)"), + @Example(".input(item('minecraft:diamond_block')).output(item('minecraft:gold_ingot') * 16).ignoreHeat()") + }) + public RecipeBuilder recipeBuilder() { + return new RecipeBuilder(); + } + + @Override + public void onReload() { + removeScripted().forEach(recipe -> BWRegistry.KILN.getRecipes().removeIf(r -> r == recipe)); + BWRegistry.KILN.getRecipes().addAll(restoreFromBackup()); + } + + public KilnRecipe add(KilnRecipe recipe) { + if (recipe != null) { + addScripted(recipe); + BWRegistry.KILN.getRecipes().add(recipe); + } + return recipe; + } + + public boolean remove(KilnRecipe recipe) { + if (BWRegistry.KILN.getRecipes().removeIf(r -> r == recipe)) { + addBackup(recipe); + return true; + } + return false; + } + + @MethodDescription(description = "groovyscript.wiki.removeByOutput", example = @Example("item('minecraft:brick')")) + public boolean removeByOutput(IIngredient output) { + return BWRegistry.KILN.getRecipes().removeIf(r -> { + for (ItemStack itemstack : r.getOutputs()) { + if (output.test(itemstack)) { + addBackup(r); + return true; + } + } + return false; + }); + } + + @MethodDescription(description = "groovyscript.wiki.removeByInput", example = @Example("item('minecraft:end_stone')")) + public boolean removeByInput(IIngredient input) { + return BWRegistry.KILN.getRecipes().removeIf(r -> { + for (ItemStack itemstack : r.getInput().getMatchingStacks()) { + if (input.test(itemstack)) { + addBackup(r); + return true; + } + } + return false; + }); + } + + @MethodDescription(description = "groovyscript.wiki.streamRecipes", type = MethodDescription.Type.QUERY) + public SimpleObjectStream streamRecipes() { + return new SimpleObjectStream<>(BWRegistry.KILN.getRecipes()).setRemover(this::remove); + } + + @MethodDescription(description = "groovyscript.wiki.removeAll", priority = 2000, example = @Example(commented = true)) + public void removeAll() { + BWRegistry.KILN.getRecipes().forEach(this::addBackup); + BWRegistry.KILN.getRecipes().clear(); + } + + @Property(property = "output", valid = {@Comp(value = "1", type = Comp.Type.GTE), @Comp(value = "3", type = Comp.Type.LTE)}) + public static class RecipeBuilder extends AbstractRecipeBuilder { + + @Property + private BlockIngredient input; + @Property(defaultValue = "1") + private int heat = 1; + @Property + private boolean ignoreHeat; + + public RecipeBuilder input(BlockIngredient input) { + this.input = input; + return this; + } + + public RecipeBuilder input(String input) { + this.input = new BlockIngredient(input); + return this; + } + + public RecipeBuilder input(List input) { + this.input = new BlockIngredient(input); + return this; + } + + public RecipeBuilder input(ItemStack... input) { + this.input = new BlockIngredient(input); + return this; + } + + public RecipeBuilder input(IIngredient input) { + this.input = new BlockIngredient(input.toMcIngredient()); + return this; + } + + public RecipeBuilder heat(int heat) { + this.heat = heat; + return this; + } + + public RecipeBuilder ignoreHeat(boolean ignoreHeat) { + this.ignoreHeat = ignoreHeat; + return this; + } + + public RecipeBuilder ignoreHeat() { + this.ignoreHeat = !ignoreHeat; + return this; + } + + @Override + public String getErrorMsg() { + return "Error adding Better With Mods Kiln recipe"; + } + + @Override + public void validate(GroovyLog.Msg msg) { + validateItems(msg, 0, 0, 1, 3); + validateFluids(msg); + } + + @Override + @RecipeBuilderRegistrationMethod + public @Nullable KilnRecipe register() { + if (!validate()) return null; + + KilnRecipe recipe = new KilnRecipe(input, output, heat); + recipe.setIgnoreHeat(ignoreHeat); + ModSupport.BETTER_WITH_MODS.get().kiln.add(recipe); + return recipe; + } + } + +} diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/betterwithmods/MillStone.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/betterwithmods/MillStone.java new file mode 100644 index 000000000..cab7da798 --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/betterwithmods/MillStone.java @@ -0,0 +1,153 @@ +package com.cleanroommc.groovyscript.compat.mods.betterwithmods; + +import betterwithmods.common.BWRegistry; +import betterwithmods.common.registry.bulk.recipes.MillRecipe; +import com.cleanroommc.groovyscript.api.GroovyLog; +import com.cleanroommc.groovyscript.api.IIngredient; +import com.cleanroommc.groovyscript.api.documentation.annotations.*; +import com.cleanroommc.groovyscript.compat.mods.ModSupport; +import com.cleanroommc.groovyscript.helper.Alias; +import com.cleanroommc.groovyscript.helper.SimpleObjectStream; +import com.cleanroommc.groovyscript.helper.recipe.AbstractRecipeBuilder; +import com.cleanroommc.groovyscript.registry.VirtualizedRegistry; +import net.minecraft.item.ItemStack; +import net.minecraft.item.crafting.Ingredient; +import net.minecraft.util.SoundEvent; +import org.jetbrains.annotations.Nullable; + +import java.util.stream.Collectors; + +@RegistryDescription +public class MillStone extends VirtualizedRegistry { + + public MillStone() { + super(Alias.generateOfClass(MillStone.class).andGenerate("Mill")); + } + + @RecipeBuilderDescription(example = { + @Example(".input(item('minecraft:diamond')).output(item('minecraft:gold_ingot') * 16)"), + @Example(".input(item('minecraft:diamond_block')).output(item('minecraft:gold_ingot'), item('minecraft:gold_block'), item('minecraft:clay'))") + }) + public RecipeBuilder recipeBuilder() { + return new RecipeBuilder(); + } + + @Override + public void onReload() { + removeScripted().forEach(recipe -> BWRegistry.MILLSTONE.getRecipes().removeIf(r -> r == recipe)); + BWRegistry.MILLSTONE.getRecipes().addAll(restoreFromBackup()); + } + + public MillRecipe add(MillRecipe recipe) { + if (recipe != null) { + addScripted(recipe); + BWRegistry.MILLSTONE.getRecipes().add(recipe); + } + return recipe; + } + + public boolean remove(MillRecipe recipe) { + if (BWRegistry.MILLSTONE.getRecipes().removeIf(r -> r == recipe)) { + addBackup(recipe); + return true; + } + return false; + } + + @MethodDescription(description = "groovyscript.wiki.removeByOutput", example = @Example("item('minecraft:blaze_powder')")) + public boolean removeByOutput(IIngredient output) { + return BWRegistry.MILLSTONE.getRecipes().removeIf(r -> { + for (ItemStack itemstack : r.getOutputs()) { + if (output.test(itemstack)) { + addBackup(r); + return true; + } + } + return false; + }); + } + + @MethodDescription(description = "groovyscript.wiki.removeByInput", example = @Example("item('minecraft:netherrack')")) + public boolean removeByInput(IIngredient input) { + return BWRegistry.MILLSTONE.getRecipes().removeIf(r -> { + for (Ingredient ingredient : r.getInputs()) { + for (ItemStack item : ingredient.getMatchingStacks()) { + if (input.test(item)) { + addBackup(r); + return true; + } + } + } + return false; + }); + } + + @MethodDescription(description = "groovyscript.wiki.streamRecipes", type = MethodDescription.Type.QUERY) + public SimpleObjectStream streamRecipes() { + return new SimpleObjectStream<>(BWRegistry.MILLSTONE.getRecipes()).setRemover(this::remove); + } + + @MethodDescription(description = "groovyscript.wiki.removeAll", priority = 2000, example = @Example(commented = true)) + public void removeAll() { + BWRegistry.MILLSTONE.getRecipes().forEach(this::addBackup); + BWRegistry.MILLSTONE.getRecipes().clear(); + } + + @Property(property = "input", valid = {@Comp(value = "1", type = Comp.Type.GTE), @Comp(value = "3", type = Comp.Type.LTE)}) + @Property(property = "output", valid = {@Comp(value = "1", type = Comp.Type.GTE), @Comp(value = "3", type = Comp.Type.LTE)}) + public static class RecipeBuilder extends AbstractRecipeBuilder { + + @Property + private SoundEvent soundEvent; + @Property(defaultValue = "1", valid = @Comp(value = "1", type = Comp.Type.GTE)) + private int ticks = 1; + @Property(defaultValue = "1") + private int priority = 1; + + public RecipeBuilder ticks(int ticks) { + this.ticks = ticks; + return this; + } + + public RecipeBuilder time(int ticks) { + this.ticks = ticks; + return this; + } + + public RecipeBuilder soundEvent(SoundEvent soundEvent) { + this.soundEvent = soundEvent; + return this; + } + + public RecipeBuilder priority(int priority) { + this.priority = priority; + return this; + } + + @Override + public String getErrorMsg() { + return "Error adding Better With Mods Mill recipe"; + } + + @Override + public void validate(GroovyLog.Msg msg) { + validateItems(msg, 1, 3, 1, 3); + validateFluids(msg); + msg.add(ticks <= 0, "ticks must be a positive integer greater than 0, yet it was {}", ticks); + } + + @Override + @RecipeBuilderRegistrationMethod + public @Nullable MillRecipe register() { + if (!validate()) return null; + + MillRecipe recipe = new MillRecipe(input.stream().map(IIngredient::toMcIngredient).collect(Collectors.toList()), output); + recipe.setSound(soundEvent); + recipe.setTicks(ticks); + recipe.setPriority(priority); + ModSupport.BETTER_WITH_MODS.get().millStone.add(recipe); + return recipe; + } + } + +} diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/betterwithmods/Saw.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/betterwithmods/Saw.java new file mode 100644 index 000000000..b7e92cac2 --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/betterwithmods/Saw.java @@ -0,0 +1,138 @@ +package com.cleanroommc.groovyscript.compat.mods.betterwithmods; + +import betterwithmods.common.BWRegistry; +import betterwithmods.common.registry.block.recipe.BlockIngredient; +import betterwithmods.common.registry.block.recipe.SawRecipe; +import com.cleanroommc.groovyscript.api.GroovyLog; +import com.cleanroommc.groovyscript.api.IIngredient; +import com.cleanroommc.groovyscript.api.documentation.annotations.*; +import com.cleanroommc.groovyscript.compat.mods.ModSupport; +import com.cleanroommc.groovyscript.helper.SimpleObjectStream; +import com.cleanroommc.groovyscript.helper.recipe.AbstractRecipeBuilder; +import com.cleanroommc.groovyscript.registry.VirtualizedRegistry; +import net.minecraft.item.ItemStack; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +@RegistryDescription +public class Saw extends VirtualizedRegistry { + + @RecipeBuilderDescription(example = @Example(".input(item('minecraft:diamond_block')).output(item('minecraft:gold_ingot') * 16)")) + public RecipeBuilder recipeBuilder() { + return new RecipeBuilder(); + } + + @Override + public void onReload() { + removeScripted().forEach(recipe -> BWRegistry.WOOD_SAW.getRecipes().removeIf(r -> r == recipe)); + BWRegistry.WOOD_SAW.getRecipes().addAll(restoreFromBackup()); + } + + public SawRecipe add(SawRecipe recipe) { + if (recipe != null) { + addScripted(recipe); + BWRegistry.WOOD_SAW.getRecipes().add(recipe); + } + return recipe; + } + + public boolean remove(SawRecipe recipe) { + if (BWRegistry.WOOD_SAW.getRecipes().removeIf(r -> r == recipe)) { + addBackup(recipe); + return true; + } + return false; + } + + @MethodDescription(description = "groovyscript.wiki.removeByOutput", example = @Example("item('minecraft:pumpkin')")) + public boolean removeByOutput(IIngredient output) { + return BWRegistry.WOOD_SAW.getRecipes().removeIf(r -> { + for (ItemStack itemstack : r.getOutputs()) { + if (output.test(itemstack)) { + addBackup(r); + return true; + } + } + return false; + }); + } + + @MethodDescription(description = "groovyscript.wiki.removeByInput", example = @Example("item('minecraft:vine')")) + public boolean removeByInput(IIngredient input) { + return BWRegistry.WOOD_SAW.getRecipes().removeIf(r -> { + for (ItemStack itemstack : r.getInput().getMatchingStacks()) { + if (input.test(itemstack)) { + addBackup(r); + return true; + } + } + return false; + }); + } + + @MethodDescription(description = "groovyscript.wiki.streamRecipes", type = MethodDescription.Type.QUERY) + public SimpleObjectStream streamRecipes() { + return new SimpleObjectStream<>(BWRegistry.WOOD_SAW.getRecipes()).setRemover(this::remove); + } + + @MethodDescription(description = "groovyscript.wiki.removeAll", priority = 2000, example = @Example(commented = true)) + public void removeAll() { + BWRegistry.WOOD_SAW.getRecipes().forEach(this::addBackup); + BWRegistry.WOOD_SAW.getRecipes().clear(); + } + + @Property(property = "output", valid = {@Comp(value = "1", type = Comp.Type.GTE), @Comp(value = "9", type = Comp.Type.LTE)}) + public static class RecipeBuilder extends AbstractRecipeBuilder { + + @Property + private BlockIngredient input; + + public RecipeBuilder input(BlockIngredient input) { + this.input = input; + return this; + } + + public RecipeBuilder input(String input) { + this.input = new BlockIngredient(input); + return this; + } + + public RecipeBuilder input(List input) { + this.input = new BlockIngredient(input); + return this; + } + + public RecipeBuilder input(ItemStack... input) { + this.input = new BlockIngredient(input); + return this; + } + + public RecipeBuilder input(IIngredient input) { + this.input = new BlockIngredient(input.toMcIngredient()); + return this; + } + + @Override + public String getErrorMsg() { + return "Error adding Better With Mods Saw recipe"; + } + + @Override + public void validate(GroovyLog.Msg msg) { + validateItems(msg, 0, 0, 1, 3); + validateFluids(msg); + } + + @Override + @RecipeBuilderRegistrationMethod + public @Nullable SawRecipe register() { + if (!validate()) return null; + + SawRecipe recipe = new SawRecipe(input, output); + ModSupport.BETTER_WITH_MODS.get().saw.add(recipe); + return recipe; + } + } + +} diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/betterwithmods/Turntable.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/betterwithmods/Turntable.java new file mode 100644 index 000000000..75c7106df --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/betterwithmods/Turntable.java @@ -0,0 +1,157 @@ +package com.cleanroommc.groovyscript.compat.mods.betterwithmods; + +import betterwithmods.common.BWRegistry; +import betterwithmods.common.registry.block.recipe.BlockIngredient; +import betterwithmods.common.registry.block.recipe.TurntableRecipe; +import com.cleanroommc.groovyscript.api.GroovyLog; +import com.cleanroommc.groovyscript.api.IIngredient; +import com.cleanroommc.groovyscript.api.documentation.annotations.*; +import com.cleanroommc.groovyscript.compat.mods.ModSupport; +import com.cleanroommc.groovyscript.helper.SimpleObjectStream; +import com.cleanroommc.groovyscript.helper.recipe.AbstractRecipeBuilder; +import com.cleanroommc.groovyscript.registry.VirtualizedRegistry; +import net.minecraft.block.state.IBlockState; +import net.minecraft.init.Blocks; +import net.minecraft.item.ItemStack; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +@RegistryDescription +public class Turntable extends VirtualizedRegistry { + + @RecipeBuilderDescription(example = { + @Example(".input(item('minecraft:gold_block')).outputBlock(blockstate('minecraft:clay')).output(item('minecraft:gold_ingot') * 5).rotations(5)"), + @Example(".input(item('minecraft:clay')).output(item('minecraft:gold_ingot')).rotations(2)") + }) + public RecipeBuilder recipeBuilder() { + return new RecipeBuilder(); + } + + @Override + public void onReload() { + removeScripted().forEach(recipe -> BWRegistry.TURNTABLE.getRecipes().removeIf(r -> r == recipe)); + BWRegistry.TURNTABLE.getRecipes().addAll(restoreFromBackup()); + } + + public TurntableRecipe add(TurntableRecipe recipe) { + if (recipe != null) { + addScripted(recipe); + BWRegistry.TURNTABLE.getRecipes().add(recipe); + } + return recipe; + } + + public boolean remove(TurntableRecipe recipe) { + if (BWRegistry.TURNTABLE.getRecipes().removeIf(r -> r == recipe)) { + addBackup(recipe); + return true; + } + return false; + } + + @MethodDescription(description = "groovyscript.wiki.removeByOutput", example = @Example("item('minecraft:clay_ball')")) + public boolean removeByOutput(IIngredient output) { + return BWRegistry.TURNTABLE.getRecipes().removeIf(r -> { + for (ItemStack itemstack : r.getOutputs()) { + if (output.test(itemstack)) { + addBackup(r); + return true; + } + } + return false; + }); + } + + @MethodDescription(description = "groovyscript.wiki.removeByInput", example = @Example("item('betterwithmods:unfired_pottery')")) + public boolean removeByInput(IIngredient input) { + return BWRegistry.TURNTABLE.getRecipes().removeIf(r -> { + for (ItemStack itemstack : r.getInput().getMatchingStacks()) { + if (input.test(itemstack)) { + addBackup(r); + return true; + } + } + return false; + }); + } + + @MethodDescription(description = "groovyscript.wiki.streamRecipes", type = MethodDescription.Type.QUERY) + public SimpleObjectStream streamRecipes() { + return new SimpleObjectStream<>(BWRegistry.TURNTABLE.getRecipes()).setRemover(this::remove); + } + + @MethodDescription(description = "groovyscript.wiki.removeAll", priority = 2000, example = @Example(commented = true)) + public void removeAll() { + BWRegistry.TURNTABLE.getRecipes().forEach(this::addBackup); + BWRegistry.TURNTABLE.getRecipes().clear(); + } + + @Property(property = "output", valid = {@Comp(value = "0", type = Comp.Type.GTE), @Comp(value = "2", type = Comp.Type.LTE)}) + public static class RecipeBuilder extends AbstractRecipeBuilder { + + @Property + private BlockIngredient input; + @Property(defaultValue = "Blocks.AIR.getDefaultState()") + private IBlockState outputBlock = Blocks.AIR.getDefaultState(); + @Property(defaultValue = "1") + private int rotations = 1; + + public RecipeBuilder input(BlockIngredient input) { + this.input = input; + return this; + } + + public RecipeBuilder input(String input) { + this.input = new BlockIngredient(input); + return this; + } + + public RecipeBuilder input(List input) { + this.input = new BlockIngredient(input); + return this; + } + + public RecipeBuilder input(ItemStack... input) { + this.input = new BlockIngredient(input); + return this; + } + + public RecipeBuilder input(IIngredient input) { + this.input = new BlockIngredient(input.toMcIngredient()); + return this; + } + + public RecipeBuilder outputBlock(IBlockState outputBlock) { + this.outputBlock = outputBlock; + return this; + } + + public RecipeBuilder rotations(int rotations) { + this.rotations = rotations; + return this; + } + + @Override + public String getErrorMsg() { + return "Error adding Better With Mods Turntable recipe"; + } + + @Override + public void validate(GroovyLog.Msg msg) { + validateItems(msg, 0, 0, 0, 2); + validateFluids(msg); + } + + @Override + @RecipeBuilderRegistrationMethod + public @Nullable TurntableRecipe register() { + if (!validate()) return null; + + TurntableRecipe recipe = new TurntableRecipe(input, output, outputBlock, rotations); + ModSupport.BETTER_WITH_MODS.get().turntable.add(recipe); + return recipe; + } + } + +} diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/botania/Botania.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/botania/Botania.java index a10647033..e4c38ab6f 100644 --- a/src/main/java/com/cleanroommc/groovyscript/compat/mods/botania/Botania.java +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/botania/Botania.java @@ -1,14 +1,12 @@ package com.cleanroommc.groovyscript.compat.mods.botania; -import com.cleanroommc.groovyscript.api.IGameObjectHandler; +import com.cleanroommc.groovyscript.api.IGameObjectParser; import com.cleanroommc.groovyscript.compat.mods.ModPropertyContainer; -import com.cleanroommc.groovyscript.gameobjects.GameObjectHandlerManager; +import com.cleanroommc.groovyscript.gameobjects.GameObjectHandler; import vazkii.botania.api.BotaniaAPI; import vazkii.botania.api.lexicon.LexiconCategory; import vazkii.botania.api.lexicon.LexiconEntry; -import java.util.function.Supplier; - public class Botania extends ModPropertyContainer { public final ElvenTrade elvenTrade = new ElvenTrade(); @@ -58,6 +56,11 @@ public static LexiconEntry getEntry(String name) { @SuppressWarnings("Convert2MethodRef") @Override public void initialize() { - GameObjectHandlerManager.registerGameObjectHandler("botania", "brew", (IGameObjectHandler) IGameObjectHandler.wrapStringGetter(val -> BotaniaAPI.brewMap.get(val), false), (Supplier) () -> BotaniaAPI.fallbackBrew); + GameObjectHandler.builder("brew", vazkii.botania.api.brew.Brew.class) + .mod("botania") + .parser(IGameObjectParser.wrapStringGetter(val -> BotaniaAPI.brewMap.get(val), false)) + .completerOfNames(() -> BotaniaAPI.brewMap.keySet()) + .defaultValue(() -> BotaniaAPI.fallbackBrew) + .register(); } } diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/evilcraft/EvilCraft.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/evilcraft/EvilCraft.java index 08bda8cd5..baf1b501d 100644 --- a/src/main/java/com/cleanroommc/groovyscript/compat/mods/evilcraft/EvilCraft.java +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/evilcraft/EvilCraft.java @@ -1,10 +1,13 @@ package com.cleanroommc.groovyscript.compat.mods.evilcraft; -import com.cleanroommc.groovyscript.api.IGameObjectHandler; +import com.cleanroommc.groovyscript.api.IGameObjectParser; import com.cleanroommc.groovyscript.compat.mods.ModPropertyContainer; -import com.cleanroommc.groovyscript.gameobjects.GameObjectHandlerManager; +import com.cleanroommc.groovyscript.gameobjects.GameObjectHandler; import org.cyclops.evilcraft.core.weather.WeatherType; +import java.util.Arrays; +import java.util.List; + public class EvilCraft extends ModPropertyContainer { public final BloodInfuser bloodInfuser = new BloodInfuser(); @@ -17,6 +20,12 @@ public EvilCraft() { @Override public void initialize() { - GameObjectHandlerManager.registerGameObjectHandler("evilcraft", "weather", IGameObjectHandler.wrapStringGetter(WeatherType::valueOf, true)); + final List weatherTypes = Arrays.asList("any", "clear", "rain", "lightning"); + GameObjectHandler.builder("weather", WeatherType.class) + .mod("evilcraft") + .parser(IGameObjectParser.wrapStringGetter(WeatherType::valueOf, true)) + .completerOfNames(() -> weatherTypes) // elements don't have names + .defaultValue(() -> WeatherType.ANY) + .register(); } } diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/forestry/Forestry.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/forestry/Forestry.java index 8097503b9..da95bb57a 100644 --- a/src/main/java/com/cleanroommc/groovyscript/compat/mods/forestry/Forestry.java +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/forestry/Forestry.java @@ -2,7 +2,7 @@ import com.cleanroommc.groovyscript.api.Result; import com.cleanroommc.groovyscript.compat.mods.ModPropertyContainer; -import com.cleanroommc.groovyscript.gameobjects.GameObjectHandlerManager; +import com.cleanroommc.groovyscript.gameobjects.GameObjectHandler; import forestry.api.apiculture.IAlleleBeeSpecies; import forestry.api.core.ForestryAPI; import forestry.api.genetics.AlleleManager; @@ -44,7 +44,11 @@ public static Result parseSpecies(String mainArg, Object... ar } String[] parts = mainArg.split(":"); if (parts.length < 2) { - Result.error("Can't find bee species for '{}'", mainArg); + if (args.length > 0 && args[0] instanceof String s) { + parts = new String[]{parts[0], s}; + } else { + Result.error("Can't find bee species for '{}'", mainArg); + } } IAlleleBeeSpecies species = (IAlleleBeeSpecies) AlleleManager.alleleRegistry.getAllele(parts[0] + "." + parts[1]); if (species instanceof AlleleBeeSpecies) return Result.some((AlleleBeeSpecies) species); @@ -61,6 +65,10 @@ protected static String getNormalName(String name) { @Override public void initialize() { - GameObjectHandlerManager.registerGameObjectHandler("forestry", "species", Forestry::parseSpecies); + GameObjectHandler.builder("species", AlleleBeeSpecies.class) + .mod("forestry") + .parser(Forestry::parseSpecies) + .completerOfNamed(() -> AlleleManager.alleleRegistry.getRegisteredAlleles().keySet(), s -> s.replace('.', ':')) // elements don't have names + .register(); } } diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/mekanism/Mekanism.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/mekanism/Mekanism.java index b0002ebef..7d5913589 100644 --- a/src/main/java/com/cleanroommc/groovyscript/compat/mods/mekanism/Mekanism.java +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/mekanism/Mekanism.java @@ -1,14 +1,15 @@ package com.cleanroommc.groovyscript.compat.mods.mekanism; -import com.cleanroommc.groovyscript.api.IGameObjectHandler; +import com.cleanroommc.groovyscript.api.IGameObjectParser; import com.cleanroommc.groovyscript.api.IIngredient; import com.cleanroommc.groovyscript.api.Result; import com.cleanroommc.groovyscript.compat.mods.ModPropertyContainer; -import com.cleanroommc.groovyscript.gameobjects.GameObjectHandlerManager; +import com.cleanroommc.groovyscript.gameobjects.GameObjectHandler; import mekanism.api.gas.Gas; import mekanism.api.gas.GasRegistry; import mekanism.api.gas.GasStack; import mekanism.api.infuse.InfuseRegistry; +import mekanism.api.infuse.InfuseType; import net.minecraft.util.text.TextFormatting; import net.minecraftforge.fml.common.Loader; import net.minecraftforge.fml.common.Optional; @@ -62,11 +63,19 @@ public Mekanism() { @Override public void initialize() { - GameObjectHandlerManager.registerGameObjectHandler("mekanism", "gas", (s, args) -> { - Gas gas = GasRegistry.getGas(s); - return gas == null ? Result.error() : Result.some(new GasStack(gas, 1)); - }); - GameObjectHandlerManager.registerGameObjectHandler("mekanism", "infusion", IGameObjectHandler.wrapStringGetter(InfuseRegistry::get, true)); + GameObjectHandler.builder("gas", GasStack.class) + .mod("mekanism") + .parser((s, args) -> { + Gas gas = GasRegistry.getGas(s); + return gas == null ? Result.error() : Result.some(new GasStack(gas, 1)); + }) + .completerOfNamed(GasRegistry::getRegisteredGasses, Gas::getName) + .register(); + GameObjectHandler.builder("infusion", InfuseType.class) + .mod("mekanism") + .parser(IGameObjectParser.wrapStringGetter(InfuseRegistry::get, true)) + .completerOfNames(InfuseRegistry.getInfuseMap()::keySet) + .register(); } @Optional.Method(modid = "mekanism") diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/roots/Roots.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/roots/Roots.java index f6210faff..954a6154e 100644 --- a/src/main/java/com/cleanroommc/groovyscript/compat/mods/roots/Roots.java +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/roots/Roots.java @@ -1,15 +1,18 @@ package com.cleanroommc.groovyscript.compat.mods.roots; -import com.cleanroommc.groovyscript.api.IGameObjectHandler; +import com.cleanroommc.groovyscript.api.IGameObjectParser; import com.cleanroommc.groovyscript.api.Result; import com.cleanroommc.groovyscript.compat.mods.ModPropertyContainer; -import com.cleanroommc.groovyscript.gameobjects.GameObjectHandlerManager; +import com.cleanroommc.groovyscript.gameobjects.GameObjectHandler; import com.cleanroommc.groovyscript.gameobjects.GameObjectHandlers; +import epicsquid.roots.api.Herb; import epicsquid.roots.init.HerbRegistry; import epicsquid.roots.modifiers.CostType; import epicsquid.roots.modifiers.Modifier; import epicsquid.roots.modifiers.ModifierRegistry; +import epicsquid.roots.ritual.RitualBase; import epicsquid.roots.ritual.RitualRegistry; +import epicsquid.roots.spell.FakeSpell; import epicsquid.roots.spell.SpellBase; import epicsquid.roots.spell.SpellRegistry; import net.minecraft.util.ResourceLocation; @@ -61,11 +64,32 @@ public Roots() { @Override public void initialize() { - GameObjectHandlerManager.registerGameObjectHandler("roots", "ritual", IGameObjectHandler.wrapStringGetter(RitualRegistry::getRitual)); - GameObjectHandlerManager.registerGameObjectHandler("roots", "herb", IGameObjectHandler.wrapStringGetter(HerbRegistry::getHerbByName)); - GameObjectHandlerManager.registerGameObjectHandler("roots", "cost", IGameObjectHandler.wrapEnum(CostType.class, false)); - GameObjectHandlerManager.registerGameObjectHandler("roots", "spell", Roots::getSpell); - GameObjectHandlerManager.registerGameObjectHandler("roots", "modifier", Roots::getModifier); + GameObjectHandler.builder("ritual", RitualBase.class) + .mod("roots") + .parser(IGameObjectParser.wrapStringGetter(RitualRegistry::getRitual)) + .completerOfNames(RitualRegistry.ritualRegistry::keySet) + .register(); + GameObjectHandler.builder("herb", Herb.class) + .mod("roots") + .parser(IGameObjectParser.wrapStringGetter(HerbRegistry::getHerbByName)) + .completerOfNames(HerbRegistry.registry::keySet) + .register(); + GameObjectHandler.builder("cost", CostType.class) + .mod("roots") + .parser(IGameObjectParser.wrapEnum(CostType.class, false)) + .completerOfEnum(CostType.class, false) + .register(); + GameObjectHandler.builder("spell", SpellBase.class) + .mod("roots") + .parser(Roots::getSpell) + .completer(SpellRegistry.spellRegistry::keySet) + .defaultValue(() -> FakeSpell.INSTANCE) + .register(); + GameObjectHandler.builder("modifier", Modifier.class) + .mod("roots") + .parser(Roots::getModifier) + .completerOfNamed(ModifierRegistry::getModifiers, v -> v.getRegistryName().toString()) + .register(); } private static Result getSpell(String s, Object... args) { diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/thaumcraft/Thaumcraft.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/thaumcraft/Thaumcraft.java index fe347bc46..ca42efc62 100644 --- a/src/main/java/com/cleanroommc/groovyscript/compat/mods/thaumcraft/Thaumcraft.java +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/thaumcraft/Thaumcraft.java @@ -1,7 +1,7 @@ package com.cleanroommc.groovyscript.compat.mods.thaumcraft; import com.cleanroommc.groovyscript.api.GroovyLog; -import com.cleanroommc.groovyscript.api.IGameObjectHandler; +import com.cleanroommc.groovyscript.api.IGameObjectParser; import com.cleanroommc.groovyscript.compat.mods.ModPropertyContainer; import com.cleanroommc.groovyscript.compat.mods.thaumcraft.arcane.ArcaneWorkbench; import com.cleanroommc.groovyscript.compat.mods.thaumcraft.aspect.Aspect; @@ -10,7 +10,7 @@ import com.cleanroommc.groovyscript.compat.mods.thaumcraft.aspect.AspectStack; import com.cleanroommc.groovyscript.compat.mods.thaumcraft.warp.Warp; import com.cleanroommc.groovyscript.compat.mods.thaumcraft.warp.WarpItemStackExpansion; -import com.cleanroommc.groovyscript.gameobjects.GameObjectHandlerManager; +import com.cleanroommc.groovyscript.gameobjects.GameObjectHandler; import com.cleanroommc.groovyscript.sandbox.expand.ExpansionHelper; import net.minecraft.item.ItemStack; import thaumcraft.api.ThaumcraftApiHelper; @@ -47,8 +47,17 @@ public Thaumcraft() { @Override public void initialize() { - GameObjectHandlerManager.registerGameObjectHandler("thaumcraft", "aspect", IGameObjectHandler.wrapStringGetter(Thaumcraft::getAspect, AspectStack::new)); - GameObjectHandlerManager.registerGameObjectHandler("thaumcraft", "crystal", IGameObjectHandler.wrapStringGetter(Thaumcraft::getAspect, ThaumcraftApiHelper::makeCrystal)); + GameObjectHandler.builder("aspect", AspectStack.class) + .mod("thaumcraft") + .parser(IGameObjectParser.wrapStringGetter(Thaumcraft::getAspect, AspectStack::new)) + .completerOfNames(thaumcraft.api.aspects.Aspect.aspects::keySet) + .register(); + GameObjectHandler.builder("crystal", ItemStack.class) + .mod("thaumcraft") + .parser(IGameObjectParser.wrapStringGetter(Thaumcraft::getAspect, ThaumcraftApiHelper::makeCrystal)) + .completerOfNames(thaumcraft.api.aspects.Aspect.aspects::keySet) + .defaultValue(() -> ItemStack.EMPTY) + .register(); ExpansionHelper.mixinClass(ItemStack.class, AspectItemStackExpansion.class); ExpansionHelper.mixinClass(ItemStack.class, WarpItemStackExpansion.class); } diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/tinkersconstruct/Casting.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/tinkersconstruct/Casting.java index 13e29a495..c019c803b 100644 --- a/src/main/java/com/cleanroommc/groovyscript/compat/mods/tinkersconstruct/Casting.java +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/tinkersconstruct/Casting.java @@ -37,6 +37,11 @@ public Casting() { return properties.get(name); } + @Override + public Map getProperties() { + return properties; + } + public static class Table extends VirtualizedRegistry { public RecipeBuilder recipeBuilder() { diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/mods/tinkersconstruct/TinkersConstruct.java b/src/main/java/com/cleanroommc/groovyscript/compat/mods/tinkersconstruct/TinkersConstruct.java index c0e24ff8f..278873038 100644 --- a/src/main/java/com/cleanroommc/groovyscript/compat/mods/tinkersconstruct/TinkersConstruct.java +++ b/src/main/java/com/cleanroommc/groovyscript/compat/mods/tinkersconstruct/TinkersConstruct.java @@ -1,14 +1,16 @@ package com.cleanroommc.groovyscript.compat.mods.tinkersconstruct; -import com.cleanroommc.groovyscript.api.IGameObjectHandler; +import com.cleanroommc.groovyscript.api.IGameObjectParser; import com.cleanroommc.groovyscript.compat.mods.ModPropertyContainer; import com.cleanroommc.groovyscript.compat.mods.tinkersconstruct.material.GroovyMaterial; import com.cleanroommc.groovyscript.compat.mods.tinkersconstruct.material.MaterialRegistryEvent; import com.cleanroommc.groovyscript.compat.mods.tinkersconstruct.material.ToolMaterialBuilder; import com.cleanroommc.groovyscript.compat.mods.tinkersconstruct.material.traits.TraitRegistryEvent; import com.cleanroommc.groovyscript.core.mixin.tconstruct.TinkerRegistryAccessor; -import com.cleanroommc.groovyscript.gameobjects.GameObjectHandlerManager; +import com.cleanroommc.groovyscript.gameobjects.GameObjectHandler; import net.minecraftforge.common.MinecraftForge; +import slimeknights.tconstruct.library.materials.Material; +import slimeknights.tconstruct.library.traits.ITrait; public class TinkersConstruct extends ModPropertyContainer { @@ -31,9 +33,22 @@ public TinkersConstruct() { @Override public void initialize() { - GameObjectHandlerManager.registerGameObjectHandler("tconstruct", "toolMaterial", IGameObjectHandler.wrapStringGetter(TinkerRegistryAccessor.getMaterials()::get)); - GameObjectHandlerManager.registerGameObjectHandler("tconstruct", "toolTrait", IGameObjectHandler.wrapStringGetter(TinkerRegistryAccessor.getTraits()::get)); - GameObjectHandlerManager.registerGameObjectHandler("tconstruct", "armorTrait", IGameObjectHandler.wrapStringGetter(s -> TinkerRegistryAccessor.getTraits().get(s + "_armor"))); + GameObjectHandler.builder("toolMaterial", Material.class) + .mod("tconstruct") + .parser(IGameObjectParser.wrapStringGetter(TinkerRegistryAccessor.getMaterials()::get)) + .completerOfNames(TinkerRegistryAccessor.getMaterials()::keySet) + .register(); + GameObjectHandler.builder("toolTrait", ITrait.class) + .mod("tconstruct") + .parser(IGameObjectParser.wrapStringGetter(TinkerRegistryAccessor.getTraits()::get)) + .completerOfNamed(TinkerRegistryAccessor.getTraits()::keySet, v -> v.endsWith("_armor") ? null : v) // only suggest non armor traits + .register(); + GameObjectHandler.builder("armorTrait", ITrait.class) + .mod("tconstruct") + .parser(IGameObjectParser.wrapStringGetter(s -> TinkerRegistryAccessor.getTraits().get(s + "_armor"))) + .completerOfNamed(TinkerRegistryAccessor.getTraits()::keySet, v -> v.endsWith("_armor") ? v.substring(0, v.length() - 6) + : null) // only suggest armor traits + .register(); } public static void init() { diff --git a/src/main/java/com/cleanroommc/groovyscript/core/LateMixin.java b/src/main/java/com/cleanroommc/groovyscript/core/LateMixin.java index 9a9cdd29c..6e6a92b5f 100644 --- a/src/main/java/com/cleanroommc/groovyscript/core/LateMixin.java +++ b/src/main/java/com/cleanroommc/groovyscript/core/LateMixin.java @@ -14,6 +14,7 @@ public class LateMixin implements ILateMixinLoader { "advancedmortars", "appliedenergistics2", "astralsorcery", + "betterwithmods", "bloodmagic", "botania", "draconicevolution", diff --git a/src/main/java/com/cleanroommc/groovyscript/core/mixin/betterwithmods/BWMHeatRegistryAccessor.java b/src/main/java/com/cleanroommc/groovyscript/core/mixin/betterwithmods/BWMHeatRegistryAccessor.java new file mode 100644 index 000000000..5db2ceede --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/core/mixin/betterwithmods/BWMHeatRegistryAccessor.java @@ -0,0 +1,17 @@ +package com.cleanroommc.groovyscript.core.mixin.betterwithmods; + +import betterwithmods.common.registry.heat.BWMHeatRegistry; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.List; + +@Mixin(value = BWMHeatRegistry.class, remap = false) +public interface BWMHeatRegistryAccessor { + + @Accessor + static List getHEAT_SOURCES() { + throw new UnsupportedOperationException(); + } + +} diff --git a/src/main/java/com/cleanroommc/groovyscript/core/mixin/betterwithmods/HopperFiltersAccessor.java b/src/main/java/com/cleanroommc/groovyscript/core/mixin/betterwithmods/HopperFiltersAccessor.java new file mode 100644 index 000000000..977686b8b --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/core/mixin/betterwithmods/HopperFiltersAccessor.java @@ -0,0 +1,16 @@ +package com.cleanroommc.groovyscript.core.mixin.betterwithmods; + +import betterwithmods.api.tile.IHopperFilter; +import betterwithmods.common.registry.HopperFilters; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.Map; + +@Mixin(value = HopperFilters.class, remap = false) +public interface HopperFiltersAccessor { + + @Accessor + Map getFILTERS(); + +} diff --git a/src/main/java/com/cleanroommc/groovyscript/documentation/Registry.java b/src/main/java/com/cleanroommc/groovyscript/documentation/Registry.java index f5d750ab8..f24ce02c9 100644 --- a/src/main/java/com/cleanroommc/groovyscript/documentation/Registry.java +++ b/src/main/java/com/cleanroommc/groovyscript/documentation/Registry.java @@ -257,11 +257,11 @@ public String documentationBlock() { return out.toString(); } - private String documentMethods(List methods) { + public String documentMethods(List methods) { return documentMethods(methods, false); } - private String documentMethods(List methods, boolean preventExamples) { + public String documentMethods(List methods, boolean preventExamples) { StringBuilder out = new StringBuilder(); List exampleLines = new ArrayList<>(); List annotations = new ArrayList<>(); diff --git a/src/main/java/com/cleanroommc/groovyscript/gameobjects/Completer.java b/src/main/java/com/cleanroommc/groovyscript/gameobjects/Completer.java new file mode 100644 index 000000000..4d8afdbf1 --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/gameobjects/Completer.java @@ -0,0 +1,41 @@ +package com.cleanroommc.groovyscript.gameobjects; + +import com.cleanroommc.groovyscript.server.Completions; +import org.eclipse.lsp4j.CompletionItem; +import org.eclipse.lsp4j.CompletionItemKind; + +import java.util.function.Function; +import java.util.function.Supplier; + +@FunctionalInterface +public interface Completer { + + void complete(int paramIndex, Completions items); + + static Completer ofNamed(Supplier> values, Function toString, int preferredParamIndex) { + return ofValues(values, v -> { + String s = toString.apply(v); + if (s != null) { + var item = new CompletionItem(toString.apply(v)); + item.setKind(CompletionItemKind.Constant); + return item; + } + return null; + }, preferredParamIndex); + } + + static Completer ofValues(Supplier> values, Function toCompletionItem, int preferredParamIndex) { + return (paramIndex, items) -> { + if (preferredParamIndex < 0 || preferredParamIndex == paramIndex) { + items.addAll(values.get(), toCompletionItem); + } + }; + } + + default Completer and(Completer other) { + return (paramIndex, items) -> { + complete(paramIndex, items); + other.complete(paramIndex, items); + }; + } +} diff --git a/src/main/java/com/cleanroommc/groovyscript/gameobjects/GameObjectHandler.java b/src/main/java/com/cleanroommc/groovyscript/gameobjects/GameObjectHandler.java new file mode 100644 index 000000000..829ffba19 --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/gameobjects/GameObjectHandler.java @@ -0,0 +1,159 @@ +package com.cleanroommc.groovyscript.gameobjects; + +import com.cleanroommc.groovyscript.api.GroovyLog; +import com.cleanroommc.groovyscript.api.IGameObjectParser; +import com.cleanroommc.groovyscript.api.Result; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.fml.common.Loader; +import net.minecraftforge.registries.IForgeRegistry; +import net.minecraftforge.registries.IForgeRegistryEntry; +import org.eclipse.lsp4j.CompletionItem; +import org.eclipse.lsp4j.CompletionItemKind; + +import java.util.*; +import java.util.function.Function; +import java.util.function.Supplier; + +public class GameObjectHandler { + + public static Builder builder(String name, Class returnTpe) { + return new Builder<>(name, returnTpe); + } + + private final String name; + private final String mod; + private final IGameObjectParser handler; + private final Supplier defaultValue; + private final Class returnType; + private final List[]> paramTypes; + private final Completer completer; + + private GameObjectHandler(String name, String mod, IGameObjectParser handler, Supplier defaultValue, Class returnType, List[]> paramTypes, Completer completer) { + this.name = name; + this.mod = mod; + this.handler = handler; + this.defaultValue = defaultValue; + this.returnType = returnType; + this.paramTypes = paramTypes; + this.completer = completer; + } + + T invoke(String s, Object... args) { + Result t = Objects.requireNonNull(handler.parse(s, args), "Bracket handlers must return a non null result!"); + if (t.hasError()) { + if (this.mod == null) { + GroovyLog.get().error("Can't find {} for name {}!", name, s); + } else { + GroovyLog.get().error("Can't find {} {} for name {}!", mod, name, s); + } + if (t.getError() != null && !t.getError().isEmpty()) { + GroovyLog.get().error(" - reason: {}", t.getError()); + } + return this.defaultValue.get(); + } + return Objects.requireNonNull(t.getValue(), "Bracket handler result must contain a non-null value!"); + } + + public String getMod() { + return mod; + } + + public String getName() { + return name; + } + + public List[]> getParamTypes() { + return this.paramTypes; + } + + public Class getReturnType() { + return returnType; + } + + public Completer getCompleter() { + return completer; + } + + public static class Builder { + + private final String name; + private String mod; + private IGameObjectParser handler; + private Supplier defaultValue; + private final Class returnType; + private final List[]> paramTypes = new ArrayList<>(); + private Completer completer; + + public Builder(String name, Class returnType) { + this.name = name; + this.returnType = returnType; + } + + public Builder mod(String mod) { + this.mod = mod; + return this; + } + + public Builder parser(IGameObjectParser handler) { + this.handler = handler; + return this; + } + + public Builder completer(Completer completer) { + if (this.completer == null) { + this.completer = completer; + } else { + this.completer = this.completer.and(completer); + } + return this; + } + + public Builder completerOfNames(Supplier> values) { + return completer(Completer.ofNamed(values, Function.identity(), 0)); + } + + public Builder completerOfNamed(Supplier> values, Function toString) { + return completer(Completer.ofNamed(values, toString, 0)); + } + + public > Builder completerOfEnum(Class values, boolean caseSensitive) { + return completerOfNamed(() -> Arrays.asList(values.getEnumConstants()), s -> caseSensitive ? s.name() : s.name().toLowerCase(Locale.ROOT)); + } + + public Builder completer(Supplier> values) { + return completer(Completer.ofValues(values, v -> { + CompletionItem item = new CompletionItem(v.toString()); + item.setKind(CompletionItemKind.Constant); + return item; + }, 0)); + } + + public > Builder completer(IForgeRegistry values) { + return completer(values::getKeys); + } + + public Builder defaultValue(Supplier defaultValue) { + this.defaultValue = defaultValue; + return this; + } + + public Builder addSignature(Class... paramTypes) { + this.paramTypes.add(paramTypes); + return this; + } + + public void register() { + if (this.name == null || this.name.isEmpty()) throw new IllegalArgumentException("Name must not be empty"); + if (GameObjectHandlerManager.hasGameObjectHandler(this.name)) + throw new IllegalArgumentException("GameObjectHandler with name " + this.name + " already exists"); + if (this.mod != null && !Loader.isModLoaded(this.mod)) + throw new IllegalArgumentException("Tried to register GameObjectHandler for mod " + this.mod + ", but it's not loaded"); + Objects.requireNonNull(this.handler, () -> "The GameObjectHandler function must no be null"); + Objects.requireNonNull(this.returnType, () -> "The GameObjectHandler return type must not be null"); + if (this.paramTypes.isEmpty()) this.paramTypes.add(new Class[]{String.class}); + if (this.defaultValue == null) this.defaultValue = () -> null; + GameObjectHandlerManager.registerGameObjectHandler(new GameObjectHandler<>(this.name, this.mod, this.handler, this.defaultValue, + this.returnType, this.paramTypes, this.completer)); + } + } +} diff --git a/src/main/java/com/cleanroommc/groovyscript/gameobjects/GameObjectHandlerManager.java b/src/main/java/com/cleanroommc/groovyscript/gameobjects/GameObjectHandlerManager.java index 3aced6a24..4f11a1233 100644 --- a/src/main/java/com/cleanroommc/groovyscript/gameobjects/GameObjectHandlerManager.java +++ b/src/main/java/com/cleanroommc/groovyscript/gameobjects/GameObjectHandlerManager.java @@ -1,71 +1,121 @@ package com.cleanroommc.groovyscript.gameobjects; -import com.cleanroommc.groovyscript.api.GroovyLog; -import com.cleanroommc.groovyscript.api.IGameObjectHandler; +import com.cleanroommc.groovyscript.api.IGameObjectParser; +import com.cleanroommc.groovyscript.api.IIngredient; import com.cleanroommc.groovyscript.api.Result; +import com.cleanroommc.groovyscript.core.mixin.OreDictionaryAccessor; import com.cleanroommc.groovyscript.helper.ingredient.OreDictIngredient; import com.cleanroommc.groovyscript.helper.ingredient.OreDictWildcardIngredient; +import com.cleanroommc.groovyscript.server.Completions; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import net.minecraft.block.Block; +import net.minecraft.block.state.IBlockState; +import net.minecraft.creativetab.CreativeTabs; +import net.minecraft.enchantment.Enchantment; import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.potion.Potion; +import net.minecraft.potion.PotionType; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.SoundEvent; +import net.minecraft.util.text.TextFormatting; +import net.minecraft.world.biome.Biome; +import net.minecraftforge.fluids.FluidRegistry; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fml.common.registry.EntityEntry; import net.minecraftforge.fml.common.registry.ForgeRegistries; -import org.apache.commons.lang3.StringUtils; +import net.minecraftforge.fml.common.registry.VillagerRegistry; import org.jetbrains.annotations.Nullable; +import java.util.Arrays; +import java.util.Collection; +import java.util.Locale; import java.util.Map; -import java.util.Objects; -import java.util.function.Supplier; public class GameObjectHandlerManager { - private static final Map> bracketHandlers = new Object2ObjectOpenHashMap<>(); + private static final Map> handlers = new Object2ObjectOpenHashMap<>(); public static final String EMPTY = "empty", WILDCARD = "*", SPLITTER = ":"; - public static void registerGameObjectHandler(@Nullable String mod, String key, IGameObjectHandler handler, Supplier defaultValue) { - if (StringUtils.isEmpty(key) || handler == null || defaultValue == null) throw new NullPointerException(); - if (bracketHandlers.containsKey(key)) { - throw new IllegalArgumentException("Bracket handler already exists for key " + key); - } - bracketHandlers.put(key, new GameObjectHandler<>(key, mod, handler, defaultValue)); - } - - public static void registerGameObjectHandler(@Nullable String mod, String key, IGameObjectHandler handler, T defaultValue) { - registerGameObjectHandler(mod, key, handler, (Supplier) () -> defaultValue); - } - - public static void registerGameObjectHandler(@Nullable String mod, String key, IGameObjectHandler handler) { - registerGameObjectHandler(mod, key, handler, (Supplier) () -> null); - } - - public static void registerGameObjectHandler(String key, IGameObjectHandler handler, Supplier defaultValue) { - registerGameObjectHandler(null, key, handler, defaultValue); - } - - public static void registerGameObjectHandler(String key, IGameObjectHandler handler) { - registerGameObjectHandler(null, key, handler); - } - - public static boolean hasGameObjectHandler(String key) { - return bracketHandlers.containsKey(key); + static void registerGameObjectHandler(GameObjectHandler goh) { + handlers.put(goh.getName(), goh); } public static void init() { - registerGameObjectHandler("resource", GameObjectHandlers::parseResourceLocation); - registerGameObjectHandler("ore", (s, args) -> s.contains("*") ? Result.some(OreDictWildcardIngredient.of(s)) : Result.some(new OreDictIngredient(s))); - registerGameObjectHandler("item", GameObjectHandlers::parseItemStack, () -> ItemStack.EMPTY); - registerGameObjectHandler("liquid", GameObjectHandlers::parseFluidStack); - registerGameObjectHandler("fluid", GameObjectHandlers::parseFluidStack); - registerGameObjectHandler("block", IGameObjectHandler.wrapForgeRegistry(ForgeRegistries.BLOCKS)); - registerGameObjectHandler("blockstate", GameObjectHandlers::parseBlockState); - registerGameObjectHandler("enchantment", IGameObjectHandler.wrapForgeRegistry(ForgeRegistries.ENCHANTMENTS)); - registerGameObjectHandler("potion", IGameObjectHandler.wrapForgeRegistry(ForgeRegistries.POTIONS)); - registerGameObjectHandler("potionType", IGameObjectHandler.wrapForgeRegistry(ForgeRegistries.POTION_TYPES)); - registerGameObjectHandler("sound", IGameObjectHandler.wrapForgeRegistry(ForgeRegistries.SOUND_EVENTS)); - registerGameObjectHandler("entity", IGameObjectHandler.wrapForgeRegistry(ForgeRegistries.ENTITIES)); - registerGameObjectHandler("biome", IGameObjectHandler.wrapForgeRegistry(ForgeRegistries.BIOMES)); - registerGameObjectHandler("profession", IGameObjectHandler.wrapForgeRegistry(ForgeRegistries.VILLAGER_PROFESSIONS)); - registerGameObjectHandler("creativeTab", GameObjectHandlers::parseCreativeTab); - registerGameObjectHandler("textformat", GameObjectHandlers::parseTextFormatting); - registerGameObjectHandler("nbt", GameObjectHandlers::parseNBT); + GameObjectHandler.builder("resource", ResourceLocation.class) + .parser(GameObjectHandlers::parseResourceLocation) + .addSignature(String.class) + .addSignature(String.class, String.class) + .register(); + GameObjectHandler.builder("ore", IIngredient.class) + .parser((s, args) -> s.contains(WILDCARD) ? Result.some(OreDictWildcardIngredient.of(s)) : Result.some(new OreDictIngredient(s))) + .completerOfNames(OreDictionaryAccessor::getIdToName) + .register(); + GameObjectHandler.builder("item", ItemStack.class) + .parser(GameObjectHandlers::parseItemStack) + .addSignature(String.class) + .addSignature(String.class, int.class) + .defaultValue(() -> ItemStack.EMPTY) + .completer(ForgeRegistries.ITEMS) + .register(); + GameObjectHandler.builder("liquid", FluidStack.class) + .parser(GameObjectHandlers::parseFluidStack) + .completerOfNames(FluidRegistry.getRegisteredFluids()::keySet) + .register(); + GameObjectHandler.builder("fluid", FluidStack.class) + .parser(GameObjectHandlers::parseFluidStack) + .completerOfNames(FluidRegistry.getRegisteredFluids()::keySet) + .register(); + GameObjectHandler.builder("block", Block.class) + .parser(IGameObjectParser.wrapForgeRegistry(ForgeRegistries.BLOCKS)) + .completer(ForgeRegistries.BLOCKS) + .register(); + GameObjectHandler.builder("blockstate", IBlockState.class) + .parser(GameObjectHandlers::parseBlockState) + .addSignature(String.class) + .addSignature(String.class, int.class) + .addSignature(String.class, String[].class) + .completer(ForgeRegistries.BLOCKS) + .register(); + GameObjectHandler.builder("enchantment", Enchantment.class) + .parser(IGameObjectParser.wrapForgeRegistry(ForgeRegistries.ENCHANTMENTS)) + .completer(ForgeRegistries.ENCHANTMENTS) + .register(); + GameObjectHandler.builder("potion", Potion.class) + .parser(IGameObjectParser.wrapForgeRegistry(ForgeRegistries.POTIONS)) + .completer(ForgeRegistries.POTIONS) + .register(); + GameObjectHandler.builder("potionType", PotionType.class) + .parser(IGameObjectParser.wrapForgeRegistry(ForgeRegistries.POTION_TYPES)) + .completer(ForgeRegistries.POTION_TYPES) + .register(); + GameObjectHandler.builder("sound", SoundEvent.class) + .parser(IGameObjectParser.wrapForgeRegistry(ForgeRegistries.SOUND_EVENTS)) + .completer(ForgeRegistries.SOUND_EVENTS) + .register(); + GameObjectHandler.builder("entity", EntityEntry.class) + .parser(IGameObjectParser.wrapForgeRegistry(ForgeRegistries.ENTITIES)) + .completer(ForgeRegistries.ENTITIES) + .register(); + GameObjectHandler.builder("biome", Biome.class) + .parser(IGameObjectParser.wrapForgeRegistry(ForgeRegistries.BIOMES)) + .completer(ForgeRegistries.BIOMES) + .register(); + GameObjectHandler.builder("profession", VillagerRegistry.VillagerProfession.class) + .parser(IGameObjectParser.wrapForgeRegistry(ForgeRegistries.VILLAGER_PROFESSIONS)) + .completer(ForgeRegistries.VILLAGER_PROFESSIONS) + .register(); + GameObjectHandler.builder("creativeTab", CreativeTabs.class) + .parser(GameObjectHandlers::parseCreativeTab) + .completerOfNamed(() -> Arrays.asList(CreativeTabs.CREATIVE_TAB_ARRAY), CreativeTabs::getTabLabel) + .register(); + GameObjectHandler.builder("textformat", TextFormatting.class) + .parser(GameObjectHandlers::parseTextFormatting) + .completerOfNamed(() -> Arrays.asList(TextFormatting.values()), format -> format.name().toLowerCase(Locale.ROOT).replaceAll("[^a-z]", "")) + .register(); + GameObjectHandler.builder("nbt", NBTTagCompound.class) + .parser(GameObjectHandlers::parseNBT) + .register(); } /** @@ -78,41 +128,29 @@ public static void init() { */ @Nullable public static Object getGameObject(String name, String mainArg, Object... args) { - GameObjectHandler gameObjectHandler = bracketHandlers.get(name); + GameObjectHandler gameObjectHandler = handlers.get(name); if (gameObjectHandler != null) { return gameObjectHandler.invoke(mainArg, args); } return null; } - public static class GameObjectHandler { + public static boolean hasGameObjectHandler(String key) { + return handlers.containsKey(key); + } - private final String name; - private final String mod; - private final IGameObjectHandler handler; - private final Supplier defaultValue; + public static Collection> getGameObjectHandlers() { + return handlers.values(); + } - private GameObjectHandler(String name, String mod, IGameObjectHandler handler, Supplier defaultValue) { - this.name = name; - this.mod = mod; - this.handler = handler; - this.defaultValue = defaultValue; - } + public static Class getReturnTypeOf(String name) { + GameObjectHandler goh = handlers.get(name); + return goh == null ? null : goh.getReturnType(); + } - private T invoke(String s, Object... args) { - Result t = Objects.requireNonNull(handler.parse(s, args), "Bracket handlers must return a non null result!"); - if (t.hasError()) { - if (this.mod == null) { - GroovyLog.get().error("Can't find {} for name {}!", name, s); - } else { - GroovyLog.get().error("Can't find {} {} for name {}!", mod, name, s); - } - if (t.getError() != null && !t.getError().isEmpty()) { - GroovyLog.get().error(" - reason: {}", t.getError()); - } - return this.defaultValue.get(); - } - return Objects.requireNonNull(t.getValue(), "Bracket handler result must contain a non-null value!"); - } + public static void provideCompletion(String name, int index, Completions items) { + Completer completer = handlers.get(name).getCompleter(); + if (completer == null) return; + completer.complete(index, items); } } diff --git a/src/main/java/com/cleanroommc/groovyscript/helper/BetterList.java b/src/main/java/com/cleanroommc/groovyscript/helper/BetterList.java new file mode 100644 index 000000000..520e9cc8b --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/helper/BetterList.java @@ -0,0 +1,68 @@ +package com.cleanroommc.groovyscript.helper; + +import it.unimi.dsi.fastutil.objects.ObjectArrayList; + +import java.util.Objects; + +public class BetterList extends ObjectArrayList { + + public void addFirst(K k) { + add(0, k); + } + + public void addLast(K k) { + add(k); + } + + public K removeFirst() { + return remove(0); + } + + public K removeLast() { + return remove(size() - 1); + } + + public K pollFirst() { + return isEmpty() ? null : removeFirst(); + } + + public K pollLast() { + return isEmpty() ? null : removeLast(); + } + + public K getFirst() { + return get(0); + } + + public K getLast() { + return get(size() - 1); + } + + public K peekFirst() { + return isEmpty() ? null : get(0); + } + + public K peekLast() { + return isEmpty() ? null : get(size() - 1); + } + + public boolean removeFirstOccurrence(Object o) { + for (int i = 0; i < size(); i++) { + if (Objects.equals(get(i), o)) { + remove(i); + return true; + } + } + return false; + } + + public boolean removeLastOccurrence(Object o) { + for (int i = size() - 1; i >= 0; i--) { + if (Objects.equals(get(i), o)) { + remove(i); + return true; + } + } + return false; + } +} diff --git a/src/main/java/com/cleanroommc/groovyscript/helper/EnumHelper.java b/src/main/java/com/cleanroommc/groovyscript/helper/EnumHelper.java index e33fdeb41..821b0ef1b 100644 --- a/src/main/java/com/cleanroommc/groovyscript/helper/EnumHelper.java +++ b/src/main/java/com/cleanroommc/groovyscript/helper/EnumHelper.java @@ -1,6 +1,6 @@ package com.cleanroommc.groovyscript.helper; -import com.cleanroommc.groovyscript.api.IGameObjectHandler; +import com.cleanroommc.groovyscript.api.IGameObjectParser; import com.cleanroommc.groovyscript.api.Result; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import org.jetbrains.annotations.NotNull; @@ -12,15 +12,15 @@ public class EnumHelper { private static final Object[] EMPTY = new Object[0]; - private static final Map>, IGameObjectHandler> cs = new Object2ObjectOpenHashMap<>(); - private static final Map>, IGameObjectHandler> ncs = new Object2ObjectOpenHashMap<>(); + private static final Map>, IGameObjectParser> cs = new Object2ObjectOpenHashMap<>(); + private static final Map>, IGameObjectParser> ncs = new Object2ObjectOpenHashMap<>(); @NotNull public static > Result valueOf(Class clazz, String s, boolean caseSensitive) { var map = caseSensitive ? cs : ncs; - IGameObjectHandler goh = map.get(clazz); + IGameObjectParser goh = map.get(clazz); if (goh == null) { - goh = IGameObjectHandler.wrapEnum(clazz, caseSensitive); + goh = IGameObjectParser.wrapEnum(clazz, caseSensitive); map.put(clazz, goh); } return (Result) goh.parse(s, EMPTY); diff --git a/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyLogImpl.java b/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyLogImpl.java index ceb0523b5..f2e5bcee3 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 @@ private String formatLine(String level, String msg) { } private String getSource() { - String source = GroovyScript.getSandbox().getCurrentScript(); + String source = GroovyScript.isSandboxLoaded() ? GroovyScript.getSandbox().getCurrentScript() : null; if (source == null) { ModContainer mod = Loader.instance().activeModContainer(); return mod != null ? mod.getModId() : GroovyScript.ID; diff --git a/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovySandbox.java b/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovySandbox.java index d8f98f89f..b1f1457ce 100644 --- a/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovySandbox.java +++ b/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovySandbox.java @@ -44,6 +44,7 @@ public static GroovySandbox getCurrentSandbox() { private final URL[] scriptEnvironment; private final ThreadLocal running = ThreadLocal.withInitial(() -> false); private final Map bindings = new Object2ObjectOpenHashMap<>(); + private final Set> staticImports = new HashSet<>(); protected GroovySandbox(URL[] scriptEnvironment) { if (scriptEnvironment == null || scriptEnvironment.length == 0) { @@ -71,6 +72,15 @@ public void registerBinding(INamed named) { } } + protected void registerStaticImports(Class... classes) { + Objects.requireNonNull(classes); + if (classes.length == 0) { + throw new IllegalArgumentException("Static imports must not be empty!"); + } + + Collections.addAll(staticImports, classes); + } + protected void startRunning() { currentSandbox.set(this); this.running.set(true); @@ -218,6 +228,10 @@ public Map getBindings() { return bindings; } + public Set> getStaticImports() { + return staticImports; + } + public String getCurrentScript() { return currentScript; } diff --git a/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptSandbox.java b/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptSandbox.java index 3c6f86625..2af615f35 100644 --- a/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptSandbox.java +++ b/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptSandbox.java @@ -61,7 +61,10 @@ public GroovyScriptSandbox(File scriptRoot, File cacheRoot) throws MalformedURLE registerBinding("Mods", ModSupport.INSTANCE); registerBinding("Log", GroovyLog.get()); registerBinding("EventManager", GroovyEventManager.INSTANCE); + this.importCustomizer.addStaticStars(GroovyHelper.class.getName(), MathHelper.class.getName()); + registerStaticImports(GroovyHelper.class, MathHelper.class); + this.importCustomizer.addImports("net.minecraft.world.World", "net.minecraft.block.state.IBlockState", "net.minecraft.block.Block", @@ -89,7 +92,9 @@ public GroovyScriptSandbox(File scriptRoot, File cacheRoot) throws MalformedURLE "net.minecraft.util.EnumFacing", "net.minecraft.util.ResourceLocation", "net.minecraftforge.fml.common.eventhandler.EventPriority", - "com.cleanroommc.groovyscript.event.EventBusType"); + "com.cleanroommc.groovyscript.event.EventBusType", + "net.minecraftforge.fml.relauncher.Side", + "net.minecraftforge.fml.relauncher.SideOnly"); this.storedExceptions = new Object2ObjectOpenHashMap<>(); readIndex(); } diff --git a/src/main/java/com/cleanroommc/groovyscript/sandbox/security/GroovySecurityManager.java b/src/main/java/com/cleanroommc/groovyscript/sandbox/security/GroovySecurityManager.java index 62901951a..8a898b5e0 100644 --- a/src/main/java/com/cleanroommc/groovyscript/sandbox/security/GroovySecurityManager.java +++ b/src/main/java/com/cleanroommc/groovyscript/sandbox/security/GroovySecurityManager.java @@ -61,6 +61,7 @@ public void initDefaults() { banPackage("com.cleanroommc.groovyscript.core"); banPackage("com.cleanroommc.groovyscript.registry"); banPackage("com.cleanroommc.groovyscript.sandbox"); + banPackage("com.cleanroommc.groovyscript.server"); } public void unBanClass(Class clazz) { @@ -127,4 +128,20 @@ public boolean isValidMethod(Class receiver, String method) { Set methods = bannedMethods.get(receiver); return methods == null || !methods.contains(method); } + + public List getBannedPackages() { + return Collections.unmodifiableList(bannedPackages); + } + + public Set> getBannedClasses() { + return Collections.unmodifiableSet(bannedClasses); + } + + public Map, Set> getBannedMethods() { + return Collections.unmodifiableMap(bannedMethods); + } + + public Set> getWhiteListedClasses() { + return Collections.unmodifiableSet(whiteListedClasses); + } } diff --git a/src/main/java/com/cleanroommc/groovyscript/sandbox/transformer/GroovyScriptTransformer.java b/src/main/java/com/cleanroommc/groovyscript/sandbox/transformer/GroovyScriptTransformer.java index 057298550..cb82be2bd 100644 --- a/src/main/java/com/cleanroommc/groovyscript/sandbox/transformer/GroovyScriptTransformer.java +++ b/src/main/java/com/cleanroommc/groovyscript/sandbox/transformer/GroovyScriptTransformer.java @@ -28,8 +28,7 @@ protected SourceUnit getSourceUnit() { } private static Expression makeCheckedCall(ClassNode classNode, String name, Expression... arguments) { - return new StaticMethodCallExpression(classNode, name, - new ArgumentListExpression(arguments)); + return new StaticMethodCallExpression(classNode, name, new ArgumentListExpression(arguments)); } @Override diff --git a/src/main/java/com/cleanroommc/groovyscript/server/Completions.java b/src/main/java/com/cleanroommc/groovyscript/server/Completions.java new file mode 100644 index 000000000..dd8da7420 --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/server/Completions.java @@ -0,0 +1,50 @@ +package com.cleanroommc.groovyscript.server; + +import org.eclipse.lsp4j.CompletionItem; +import org.eclipse.lsp4j.CompletionList; +import org.eclipse.lsp4j.jsonrpc.messages.Either; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +public class Completions extends ArrayList { + + private final int limit; + + public Completions(int limit) { + this.limit = limit; + } + + public int getLimit() { + return limit; + } + + public boolean reachedLimit() { + return size() >= this.limit; + } + + public void addAll(Iterable values, Function toCompletionItem) { + for (V v : values) { + if (reachedLimit()) break; + CompletionItem item = toCompletionItem.apply(v); + if (item != null) { + add(item); + } + } + } + + public void addAll(V[] values, Function toCompletionItem) { + for (V v : values) { + if (reachedLimit()) break; + CompletionItem item = toCompletionItem.apply(v); + if (item != null) { + add(item); + } + } + } + + public Either, CompletionList> getResult(boolean incomplete) { + return incomplete || reachedLimit() ? Either.forRight(new CompletionList(true, this)) : Either.forLeft(this); + } +} diff --git a/src/main/java/com/cleanroommc/groovyscript/server/GroovyScriptCompilationUnitFactory.java b/src/main/java/com/cleanroommc/groovyscript/server/GroovyScriptCompilationUnitFactory.java new file mode 100644 index 000000000..6fdb490bb --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/server/GroovyScriptCompilationUnitFactory.java @@ -0,0 +1,114 @@ +package com.cleanroommc.groovyscript.server; + +import com.cleanroommc.groovyscript.GroovyScript; +import com.cleanroommc.groovyscript.sandbox.LoadStage; +import com.cleanroommc.groovyscript.sandbox.transformer.GroovyScriptCompiler; +import groovy.lang.GroovyClassLoader; +import net.minecraft.launchwrapper.Launch; +import net.prominic.groovyls.compiler.control.GroovyLSCompilationUnit; +import net.prominic.groovyls.config.CompilationUnitFactoryBase; +import org.codehaus.groovy.control.CompilerConfiguration; +import org.codehaus.groovy.control.SourceUnit; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.net.URI; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; +import java.util.stream.Stream; + +public class GroovyScriptCompilationUnitFactory extends CompilationUnitFactoryBase { + + private final GroovyScriptLanguageServerContext languageServerContext; + + private final Map compilationUnitsByScript = new HashMap<>(); + + public GroovyScriptCompilationUnitFactory(GroovyScriptLanguageServerContext languageServerContext) { + this.languageServerContext = languageServerContext; + } + + @Override + protected GroovyClassLoader getClassLoader() { + return new GroovyClassLoader(Launch.classLoader, config, true); + } + + @Override + protected CompilerConfiguration getConfiguration() { + var config = super.getConfiguration(); + + config.setSourceEncoding("UTF-8"); + + config.addCompilationCustomizers(GroovyScriptCompiler.transformer()); + config.addCompilationCustomizers(languageServerContext.getSandbox().getImportCustomizer()); + + return config; + } + + @Override + public GroovyLSCompilationUnit create(Path workspaceRoot, @Nullable URI context) { + if (context == null || isInClassesContext(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 changedUris = languageServerContext.getFileContentsTracker().getChangedURIs(); + + removeSources(unit, changedUris); + + // add open classes + languageServerContext.getFileContentsTracker().getOpenURIs().stream() + .filter(uri -> { + Path openPath = Paths.get(uri); + return openPath.normalize().startsWith(workspaceRoot.normalize()); + }) + .filter(this::isInClassesContext) + .forEach(uri -> { + addOpenFileToCompilationUnit(uri, languageServerContext.getFileContentsTracker().getContents(uri), unit); + }); + + // add all other classes too + 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); + + // we're in script context so only classes and the script itself + addOpenFileToCompilationUnit(context, contents, unit); + } + + return unit; + } + + protected boolean isInClassesContext(URI uri) { + var file = Paths.get(uri).getParent(); + + return getAllClasses().anyMatch(file::startsWith); + } + + protected Stream getAllClasses() { + return LoadStage.getLoadStages().stream().map(LoadStage::getName) + .flatMap(loader -> GroovyScript.getRunConfig().getClassFiles(loader).stream()) + .map(File::toPath) + .map(path -> GroovyScript.getScriptFile().toPath().resolve(path)); + } + + protected void removeSources(GroovyLSCompilationUnit unit, Set urisToRemove) { + List sourcesToRemove = new ArrayList<>(); + unit.iterator().forEachRemaining(sourceUnit -> { + URI uri = sourceUnit.getSource().getURI(); + if (urisToRemove.contains(uri)) { + sourcesToRemove.add(sourceUnit); + } + }); + + // if an 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); + } +} diff --git a/src/main/java/com/cleanroommc/groovyscript/server/GroovyScriptDocumentationProvider.java b/src/main/java/com/cleanroommc/groovyscript/server/GroovyScriptDocumentationProvider.java new file mode 100644 index 000000000..ca37dba06 --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/server/GroovyScriptDocumentationProvider.java @@ -0,0 +1,45 @@ +package com.cleanroommc.groovyscript.server; + +import com.cleanroommc.groovyscript.api.IGroovyContainer; +import com.cleanroommc.groovyscript.api.IScriptReloadable; +import com.cleanroommc.groovyscript.api.documentation.annotations.MethodDescription; +import com.cleanroommc.groovyscript.compat.mods.ModSupport; +import com.cleanroommc.groovyscript.documentation.Registry; +import net.prominic.groovyls.compiler.ast.ASTContext; +import net.prominic.groovyls.compiler.documentation.IDocumentationProvider; +import net.prominic.groovyls.compiler.util.GroovyReflectionUtils; +import org.codehaus.groovy.ast.AnnotatedNode; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.MethodNode; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.Objects; + +public class GroovyScriptDocumentationProvider implements IDocumentationProvider { + + @Override + public @Nullable String getDocumentation(AnnotatedNode node, ASTContext context) { + var builder = new StringBuilder(); + + if (node instanceof MethodNode methodNode && methodNode.getDeclaringClass().implementsInterface(new ClassNode(IScriptReloadable.class))) { + ModSupport.getAllContainers().stream() + .filter(IGroovyContainer::isLoaded) + .map(groovyContainer -> { + var methodRegistry = groovyContainer.get().getRegistries().stream().filter(registry -> registry.getClass().equals(methodNode.getDeclaringClass().getTypeClass())).findFirst(); + + if (methodRegistry.isPresent()) { + var method = GroovyReflectionUtils.resolveMethodFromMethodNode(methodNode, context); + + if (method.isPresent() && method.get().isAnnotationPresent(MethodDescription.class)) { + return new Registry(groovyContainer, methodRegistry.get(), Collections.emptyList()).documentMethods(Collections.singletonList(method.get()), true); + } + } + + return null; + }).filter(Objects::nonNull).forEach(builder::append); + } + + return builder.length() == 0 ? null : builder.toString(); + } +} diff --git a/src/main/java/com/cleanroommc/groovyscript/server/GroovyScriptLanguageServer.java b/src/main/java/com/cleanroommc/groovyscript/server/GroovyScriptLanguageServer.java new file mode 100644 index 000000000..dcedcb2db --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/server/GroovyScriptLanguageServer.java @@ -0,0 +1,39 @@ +package com.cleanroommc.groovyscript.server; + +import com.cleanroommc.groovyscript.GroovyScript; +import com.cleanroommc.groovyscript.GroovyScriptConfig; +import com.cleanroommc.groovyscript.api.GroovyLog; +import net.prominic.groovyls.GroovyLanguageServer; +import org.eclipse.lsp4j.jsonrpc.Launcher; +import org.eclipse.lsp4j.services.LanguageClient; + +import java.net.ServerSocket; + +public class GroovyScriptLanguageServer extends GroovyLanguageServer { + + @SuppressWarnings("InfiniteLoopStatement") + public static void listen() { + GroovyLog.get().infoMC("Starting Language server"); + var languageServerContext = new GroovyScriptLanguageServerContext(); + + while (true) { + var server = new GroovyScriptLanguageServer(languageServerContext); + try (var serverSocket = new ServerSocket(GroovyScriptConfig.languageServerPort); + var socket = serverSocket.accept()) { + + GroovyScript.LOGGER.info("Accepted connection from: {}", socket.getInetAddress()); + + var launcher = Launcher.createLauncher(server, LanguageClient.class, socket.getInputStream(), socket.getOutputStream()); + server.connect(launcher.getRemoteProxy()); + + launcher.startListening().get(); + } catch (Exception e) { + GroovyScript.LOGGER.error("Connection failed", e); + } + } + } + + public GroovyScriptLanguageServer(GroovyScriptLanguageServerContext languageServerContext) { + super(new GroovyScriptCompilationUnitFactory(languageServerContext), languageServerContext); + } +} diff --git a/src/main/java/com/cleanroommc/groovyscript/server/GroovyScriptLanguageServerContext.java b/src/main/java/com/cleanroommc/groovyscript/server/GroovyScriptLanguageServerContext.java new file mode 100644 index 000000000..cc21c2a17 --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/server/GroovyScriptLanguageServerContext.java @@ -0,0 +1,48 @@ +package com.cleanroommc.groovyscript.server; + +import com.cleanroommc.groovyscript.GroovyScript; +import com.cleanroommc.groovyscript.sandbox.GroovyScriptSandbox; +import com.cleanroommc.groovyscript.sandbox.security.GroovySecurityManager; +import io.github.classgraph.ClassGraph; +import io.github.classgraph.ScanResult; +import net.minecraft.launchwrapper.Launch; +import net.prominic.groovyls.compiler.ILanguageServerContext; +import net.prominic.groovyls.compiler.documentation.DocumentationFactory; +import net.prominic.groovyls.compiler.documentation.GroovydocProvider; +import net.prominic.groovyls.util.FileContentsTracker; + +public class GroovyScriptLanguageServerContext implements ILanguageServerContext { + + private final FileContentsTracker fileContentsTracker = new FileContentsTracker(); + + private ScanResult scanResult = new ClassGraph() + .enableClassInfo() + .enableMethodInfo() + .enableSystemJarsAndModules() + .overrideClassLoaders(Launch.classLoader) + .acceptPaths("*") + .rejectClasses(GroovySecurityManager.INSTANCE.getBannedClasses().stream().map(Class::getName).toArray(String[]::new)) + .rejectPackages(GroovySecurityManager.INSTANCE.getBannedPackages().stream().toArray(String[]::new)) + .acceptClasses(GroovySecurityManager.INSTANCE.getWhiteListedClasses().stream().map(Class::getName).toArray(String[]::new)) + .scan(); + + private final DocumentationFactory documentationFactory = new DocumentationFactory(new GroovyScriptDocumentationProvider(), new GroovydocProvider()); + + public GroovyScriptSandbox getSandbox() { + return GroovyScript.getSandbox(); + } + + public ScanResult getScanResult() { + return this.scanResult; + } + + @Override + public FileContentsTracker getFileContentsTracker() { + return fileContentsTracker; + } + + @Override + public DocumentationFactory getDocumentationFactory() { + return documentationFactory; + } +} diff --git a/src/main/java/net/prominic/groovyls/GroovyLanguageServer.java b/src/main/java/net/prominic/groovyls/GroovyLanguageServer.java new file mode 100644 index 000000000..281f5ba43 --- /dev/null +++ b/src/main/java/net/prominic/groovyls/GroovyLanguageServer.java @@ -0,0 +1,112 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2022 Prominic.NET, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License +// +// Author: Prominic.NET, Inc. +// No warranty of merchantability or fitness of any kind. +// Use this software at your own risk. +//////////////////////////////////////////////////////////////////////////////// +package net.prominic.groovyls; + +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.net.ServerSocket; +import java.net.URI; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; + +import net.prominic.groovyls.compiler.ILanguageServerContext; +import org.eclipse.lsp4j.*; +import org.eclipse.lsp4j.jsonrpc.Launcher; +import org.eclipse.lsp4j.launch.LSPLauncher; +import org.eclipse.lsp4j.services.LanguageClient; +import org.eclipse.lsp4j.services.LanguageClientAware; +import org.eclipse.lsp4j.services.LanguageServer; +import org.eclipse.lsp4j.services.TextDocumentService; +import org.eclipse.lsp4j.services.WorkspaceService; + +import com.cleanroommc.groovyscript.GroovyScript; +import com.cleanroommc.groovyscript.GroovyScriptConfig; + +import net.prominic.groovyls.config.CompilationUnitFactory; +import net.prominic.groovyls.config.ICompilationUnitFactory; + +public class GroovyLanguageServer implements LanguageServer, LanguageClientAware { + + private final GroovyServices groovyServices; + + public GroovyLanguageServer(ICompilationUnitFactory compilationUnitFactory, ILanguageServerContext languageServerContext) { + this.groovyServices = new GroovyServices(compilationUnitFactory, languageServerContext); + } + + @Override + public CompletableFuture initialize(InitializeParams params) { + String rootUriString = params.getRootUri(); + if (rootUriString != null) { + URI uri = URI.create(params.getRootUri()); + Path workspaceRoot = Paths.get(uri); + groovyServices.setWorkspaceRoot(workspaceRoot); + } + + CompletionOptions completionOptions = new CompletionOptions(false, Arrays.asList(".", "'", "\"")); + ServerCapabilities serverCapabilities = new ServerCapabilities(); + serverCapabilities.setCompletionProvider(completionOptions); + serverCapabilities.setTextDocumentSync(TextDocumentSyncKind.Full); + serverCapabilities.setDocumentSymbolProvider(true); + serverCapabilities.setWorkspaceSymbolProvider(true); + serverCapabilities.setDocumentSymbolProvider(true); + serverCapabilities.setReferencesProvider(true); + serverCapabilities.setDefinitionProvider(true); + serverCapabilities.setTypeDefinitionProvider(true); + serverCapabilities.setHoverProvider(true); + serverCapabilities.setRenameProvider(true); + SignatureHelpOptions signatureHelpOptions = new SignatureHelpOptions(); + signatureHelpOptions.setTriggerCharacters(Arrays.asList("(", ",")); + serverCapabilities.setSignatureHelpProvider(signatureHelpOptions); + + InitializeResult initializeResult = new InitializeResult(serverCapabilities); + return CompletableFuture.completedFuture(initializeResult); + } + + @Override + public CompletableFuture shutdown() { + return CompletableFuture.completedFuture(new Object()); + } + + @Override + public void exit() {} + + @Override + public TextDocumentService getTextDocumentService() { + return groovyServices; + } + + @Override + public WorkspaceService getWorkspaceService() { + return groovyServices; + } + + @Override + public void setTrace(SetTraceParams params) { + + } + + @Override + public void connect(LanguageClient client) { + groovyServices.connect(client); + } +} diff --git a/src/main/java/net/prominic/groovyls/GroovyServices.java b/src/main/java/net/prominic/groovyls/GroovyServices.java new file mode 100644 index 000000000..1fdd063fc --- /dev/null +++ b/src/main/java/net/prominic/groovyls/GroovyServices.java @@ -0,0 +1,394 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2022 Prominic.NET, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License +// +// Author: Prominic.NET, Inc. +// No warranty of merchantability or fitness of any kind. +// Use this software at your own risk. +//////////////////////////////////////////////////////////////////////////////// +package net.prominic.groovyls; + +import com.cleanroommc.groovyscript.GroovyScript; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import net.prominic.groovyls.compiler.ILanguageServerContext; +import net.prominic.groovyls.compiler.ast.ASTContext; +import net.prominic.groovyls.compiler.ast.ASTNodeVisitor; +import net.prominic.groovyls.compiler.control.GroovyLSCompilationUnit; +import net.prominic.groovyls.config.ICompilationUnitFactory; +import net.prominic.groovyls.providers.*; +import net.prominic.groovyls.util.GroovyLanguageServerUtils; +import net.prominic.groovyls.util.URIUtils; +import net.prominic.lsp.utils.Positions; +import org.codehaus.groovy.GroovyBugError; +import org.codehaus.groovy.ast.ASTNode; +import org.codehaus.groovy.control.ErrorCollector; +import org.codehaus.groovy.control.messages.Message; +import org.codehaus.groovy.control.messages.SyntaxErrorMessage; +import org.codehaus.groovy.syntax.SyntaxException; +import org.eclipse.lsp4j.*; +import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.eclipse.lsp4j.services.LanguageClient; +import org.eclipse.lsp4j.services.LanguageClientAware; +import org.eclipse.lsp4j.services.TextDocumentService; +import org.eclipse.lsp4j.services.WorkspaceService; +import org.jetbrains.annotations.Nullable; + +import java.net.URI; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class GroovyServices implements TextDocumentService, WorkspaceService, LanguageClientAware { + + private static final Pattern PATTERN_CONSTRUCTOR_CALL = Pattern.compile(".*new \\w*$"); + + private LanguageClient languageClient; + + private Path workspaceRoot; + private ICompilationUnitFactory compilationUnitFactory; + private final ILanguageServerContext languageServerContext; + private Map> prevDiagnosticsByFile; + + public GroovyServices(ICompilationUnitFactory factory, ILanguageServerContext languageServerContext) { + compilationUnitFactory = factory; + this.languageServerContext = languageServerContext; + } + + public void setWorkspaceRoot(Path workspaceRoot) { + this.workspaceRoot = workspaceRoot; + compilationUnitFactory.invalidateCompilationUnit(); + } + + @Override + public void connect(LanguageClient client) { + languageClient = client; + } + + // --- NOTIFICATIONS + + @Override + public void didOpen(DidOpenTextDocumentParams params) { + languageServerContext.getFileContentsTracker().didOpen(params); + URI uri = URIUtils.toUri(params.getTextDocument().getUri()); + + var unit = compilationUnitFactory.create(workspaceRoot, uri); + + compileAndVisitAST(unit, uri); + } + + @Override + public void didChange(DidChangeTextDocumentParams params) { + languageServerContext.getFileContentsTracker().didChange(params); + URI uri = URIUtils.toUri(params.getTextDocument().getUri()); + + var unit = compilationUnitFactory.create(workspaceRoot, uri); + + compileAndVisitAST(unit, uri); + } + + @Override + public void didClose(DidCloseTextDocumentParams params) { + languageServerContext.getFileContentsTracker().didClose(params); + URI uri = URIUtils.toUri(params.getTextDocument().getUri()); + + var unit = compilationUnitFactory.create(workspaceRoot, uri); + + compileAndVisitAST(unit, uri); + } + + @Override + public void didSave(DidSaveTextDocumentParams params) { + // nothing to handle on save at this time + } + + @Override + public void didChangeWatchedFiles(DidChangeWatchedFilesParams params) { + Set urisWithChanges = params.getChanges().stream().map(fileEvent -> URIUtils.toUri(fileEvent.getUri())) + .collect(Collectors.toSet()); + + for (URI uri : urisWithChanges) { + var unit = compilationUnitFactory.create(workspaceRoot, uri); + + compileAndVisitAST(unit, uri); + } + } + + @Override + public void didChangeConfiguration(DidChangeConfigurationParams params) { + if (!(params.getSettings() instanceof JsonObject)) { + return; + } + JsonObject settings = (JsonObject) params.getSettings(); + this.updateClasspath(settings); + } + + private void updateClasspath(JsonObject settings) { + List classpathList = new ArrayList<>(); + + if (settings.has("groovy") && settings.get("groovy").isJsonObject()) { + JsonObject groovy = settings.get("groovy").getAsJsonObject(); + if (groovy.has("classpath") && groovy.get("classpath").isJsonArray()) { + JsonArray classpath = groovy.get("classpath").getAsJsonArray(); + classpath.forEach(element -> { + classpathList.add(element.getAsString()); + }); + } + } + + if (!classpathList.equals(compilationUnitFactory.getAdditionalClasspathList())) { + compilationUnitFactory.setAdditionalClasspathList(classpathList); + } + } + + // --- REQUESTS + + @Override + public CompletableFuture hover(HoverParams params) { + URI uri = URIUtils.toUri(params.getTextDocument().getUri()); + var unit = compilationUnitFactory.create(workspaceRoot, uri); + + var visitor = compileAndVisitAST(unit, uri); + + if (visitor == null) { + return CompletableFuture.completedFuture(null); + } + + HoverProvider provider = new HoverProvider(new ASTContext(visitor, languageServerContext)); + return provider.provideHover(params.getTextDocument(), params.getPosition()); + } + + @Override + public CompletableFuture, CompletionList>> completion(CompletionParams params) { + TextDocumentIdentifier textDocument = params.getTextDocument(); + Position position = params.getPosition(); + URI uri = URIUtils.toUri(textDocument.getUri()); + var unit = compilationUnitFactory.create(workspaceRoot, uri); + + var visitor = compileAndVisitAST(unit, uri); + + String originalSource = null; + ASTNode offsetNode = visitor.getNodeAtLineAndColumn(uri, position.getLine(), position.getCharacter()); + if (offsetNode == null) { + originalSource = languageServerContext.getFileContentsTracker().getContents(uri); + VersionedTextDocumentIdentifier versionedTextDocument = new VersionedTextDocumentIdentifier( + textDocument.getUri(), 1); + int offset = Positions.getOffset(originalSource, position); + String lineBeforeOffset = originalSource.substring(offset - position.getCharacter(), offset); + Matcher matcher = PATTERN_CONSTRUCTOR_CALL.matcher(lineBeforeOffset); + TextDocumentContentChangeEvent changeEvent = null; + if (matcher.matches()) { + changeEvent = new TextDocumentContentChangeEvent(new Range(position, position), 0, "a()"); + } else { + changeEvent = new TextDocumentContentChangeEvent(new Range(position, position), 0, "a"); + } + DidChangeTextDocumentParams didChangeParams = new DidChangeTextDocumentParams(versionedTextDocument, + Collections.singletonList(changeEvent)); + // if the offset node is null, there is probably a syntax error. + // a completion request is usually triggered by the . character, and + // if there is no property name after the dot, it will cause a syntax + // error. + // this hack adds a placeholder property name in the hopes that it + // will correctly create a PropertyExpression to use for completion. + // we'll restore the original text after we're done handling the + // completion request. + didChange(didChangeParams); + } + + CompletableFuture, CompletionList>> result = null; + try { + CompletionProvider provider = new CompletionProvider(new ASTContext(visitor, languageServerContext)); + result = provider.provideCompletion(params.getTextDocument(), params.getPosition(), params.getContext()); + } finally { + if (originalSource != null) { + VersionedTextDocumentIdentifier versionedTextDocument = new VersionedTextDocumentIdentifier( + textDocument.getUri(), 1); + TextDocumentContentChangeEvent changeEvent = new TextDocumentContentChangeEvent(null, 0, + originalSource); + DidChangeTextDocumentParams didChangeParams = new DidChangeTextDocumentParams(versionedTextDocument, + Collections.singletonList(changeEvent)); + didChange(didChangeParams); + } + } + + return result; + } + + @Override + public CompletableFuture, List>> definition( + DefinitionParams params) { + URI uri = URIUtils.toUri(params.getTextDocument().getUri()); + var unit = compilationUnitFactory.create(workspaceRoot, uri); + + var visitor = compileAndVisitAST(unit, uri); + + DefinitionProvider provider = new DefinitionProvider(new ASTContext(visitor, languageServerContext)); + return provider.provideDefinition(params.getTextDocument(), params.getPosition()); + } + + @Override + public CompletableFuture signatureHelp(SignatureHelpParams params) { + TextDocumentIdentifier textDocument = params.getTextDocument(); + Position position = params.getPosition(); + URI uri = URIUtils.toUri(textDocument.getUri()); + var unit = compilationUnitFactory.create(workspaceRoot, uri); + + var visitor = compileAndVisitAST(unit, uri); + + String originalSource = null; + ASTNode offsetNode = visitor.getNodeAtLineAndColumn(uri, position.getLine(), position.getCharacter()); + if (offsetNode == null) { + originalSource = languageServerContext.getFileContentsTracker().getContents(uri); + VersionedTextDocumentIdentifier versionedTextDocument = new VersionedTextDocumentIdentifier( + textDocument.getUri(), 1); + TextDocumentContentChangeEvent changeEvent = new TextDocumentContentChangeEvent( + new Range(position, position), 0, ")"); + DidChangeTextDocumentParams didChangeParams = new DidChangeTextDocumentParams(versionedTextDocument, + Collections.singletonList(changeEvent)); + // if the offset node is null, there is probably a syntax error. + // a signature help request is usually triggered by the ( character, + // and if there is no matching ), it will cause a syntax error. + // this hack adds a placeholder ) character in the hopes that it + // will correctly create a ArgumentListExpression to use for + // signature help. + // we'll restore the original text after we're done handling the + // signature help request. + didChange(didChangeParams); + } + + try { + SignatureHelpProvider provider = new SignatureHelpProvider(new ASTContext(visitor, languageServerContext)); + return provider.provideSignatureHelp(params.getTextDocument(), params.getPosition()); + } finally { + if (originalSource != null) { + VersionedTextDocumentIdentifier versionedTextDocument = new VersionedTextDocumentIdentifier( + textDocument.getUri(), 1); + TextDocumentContentChangeEvent changeEvent = new TextDocumentContentChangeEvent(null, 0, + originalSource); + DidChangeTextDocumentParams didChangeParams = new DidChangeTextDocumentParams(versionedTextDocument, + Collections.singletonList(changeEvent)); + didChange(didChangeParams); + } + } + } + + @Override + public CompletableFuture, List>> typeDefinition( + TypeDefinitionParams params) { + URI uri = URIUtils.toUri(params.getTextDocument().getUri()); + var unit = compilationUnitFactory.create(workspaceRoot, uri); + + var visitor = compileAndVisitAST(unit, uri); + + TypeDefinitionProvider provider = new TypeDefinitionProvider(new ASTContext(visitor, languageServerContext)); + return provider.provideTypeDefinition(params.getTextDocument(), params.getPosition()); + } + + @Override + public CompletableFuture> references(ReferenceParams params) { + URI uri = URIUtils.toUri(params.getTextDocument().getUri()); + var unit = compilationUnitFactory.create(workspaceRoot, uri); + + var visitor = compileAndVisitAST(unit, uri); + + ReferenceProvider provider = new ReferenceProvider(new ASTContext(visitor, languageServerContext)); + return provider.provideReferences(params.getTextDocument(), params.getPosition()); + } + + @Override + public CompletableFuture>> documentSymbol( + DocumentSymbolParams params) { + URI uri = URIUtils.toUri(params.getTextDocument().getUri()); + var unit = compilationUnitFactory.create(workspaceRoot, uri); + + var visitor = compileAndVisitAST(unit, uri); + + DocumentSymbolProvider provider = new DocumentSymbolProvider(new ASTContext(visitor, languageServerContext)); + return provider.provideDocumentSymbols(params.getTextDocument()); + } + + @Override + public CompletableFuture, List>> symbol(WorkspaceSymbolParams params) { + var unit = compilationUnitFactory.create(workspaceRoot, null); + + var visitor = compileAndVisitAST(unit, null); + + WorkspaceSymbolProvider provider = new WorkspaceSymbolProvider(new ASTContext(visitor, languageServerContext)); + return provider.provideWorkspaceSymbols(params.getQuery()).thenApply(Either::forLeft); + } + + @Override + public CompletableFuture rename(RenameParams params) { + URI uri = URIUtils.toUri(params.getTextDocument().getUri()); + var unit = compilationUnitFactory.create(workspaceRoot, uri); + + var visitor = compileAndVisitAST(unit, uri); + + RenameProvider provider = new RenameProvider(new ASTContext(visitor, languageServerContext), languageServerContext.getFileContentsTracker()); + return provider.provideRename(params); + } + + private @Nullable ASTNodeVisitor compileAndVisitAST(GroovyLSCompilationUnit compilationUnit, URI context) { + try { + return compilationUnit.recompileAndVisitASTIfContextChanged(context); + } catch (GroovyBugError | Exception e) { + GroovyScript.LOGGER.error("Unexpected exception in language server when compiling Groovy.", e); + } finally { + Set diagnostics = handleErrorCollector(compilationUnit.getErrorCollector()); + diagnostics.stream().forEach(languageClient::publishDiagnostics); + } + + return null; + } + + private Set handleErrorCollector(ErrorCollector collector) { + Map> diagnosticsByFile = new HashMap<>(); + + List errors = collector.getErrors(); + if (errors != null) { + errors.stream().filter((Object message) -> message instanceof SyntaxErrorMessage) + .forEach((Object message) -> { + SyntaxErrorMessage syntaxErrorMessage = (SyntaxErrorMessage) message; + SyntaxException cause = syntaxErrorMessage.getCause(); + Range range = GroovyLanguageServerUtils.syntaxExceptionToRange(cause); + Diagnostic diagnostic = new Diagnostic(); + diagnostic.setRange(range); + diagnostic.setSeverity(DiagnosticSeverity.Error); + diagnostic.setMessage(cause.getMessage()); + URI uri = Paths.get(cause.getSourceLocator()).toUri(); + diagnosticsByFile.computeIfAbsent(uri, (key) -> new ArrayList<>()).add(diagnostic); + }); + } + + Set result = diagnosticsByFile.entrySet().stream() + .map(entry -> new PublishDiagnosticsParams(entry.getKey().toString(), entry.getValue())) + .collect(Collectors.toSet()); + + if (prevDiagnosticsByFile != null) { + for (URI key : prevDiagnosticsByFile.keySet()) { + if (!diagnosticsByFile.containsKey(key)) { + // send an empty list of diagnostics for files that had + // diagnostics previously or they won't be cleared + result.add(new PublishDiagnosticsParams(key.toString(), new ArrayList<>())); + } + } + } + prevDiagnosticsByFile = diagnosticsByFile; + return result; + } +} \ No newline at end of file diff --git a/src/main/java/net/prominic/groovyls/compiler/ILanguageServerContext.java b/src/main/java/net/prominic/groovyls/compiler/ILanguageServerContext.java new file mode 100644 index 000000000..60470c34d --- /dev/null +++ b/src/main/java/net/prominic/groovyls/compiler/ILanguageServerContext.java @@ -0,0 +1,17 @@ +package net.prominic.groovyls.compiler; + +import com.cleanroommc.groovyscript.sandbox.GroovySandbox; +import io.github.classgraph.ScanResult; +import net.prominic.groovyls.compiler.documentation.DocumentationFactory; +import net.prominic.groovyls.util.FileContentsTracker; + +public interface ILanguageServerContext { + + GroovySandbox getSandbox(); + + ScanResult getScanResult(); + + FileContentsTracker getFileContentsTracker(); + + DocumentationFactory getDocumentationFactory(); +} diff --git a/src/main/java/net/prominic/groovyls/compiler/ast/ASTContext.java b/src/main/java/net/prominic/groovyls/compiler/ast/ASTContext.java new file mode 100644 index 000000000..e973cb5c4 --- /dev/null +++ b/src/main/java/net/prominic/groovyls/compiler/ast/ASTContext.java @@ -0,0 +1,22 @@ +package net.prominic.groovyls.compiler.ast; + +import net.prominic.groovyls.compiler.ILanguageServerContext; + +public class ASTContext { + + private final ASTNodeVisitor astVisitor; + private final ILanguageServerContext languageServerContext; + + public ASTContext(ASTNodeVisitor astVisitor, ILanguageServerContext languageServerContext) { + this.astVisitor = astVisitor; + this.languageServerContext = languageServerContext; + } + + public ASTNodeVisitor getVisitor() { + return astVisitor; + } + + public ILanguageServerContext getLanguageServerContext() { + return languageServerContext; + } +} diff --git a/src/main/java/net/prominic/groovyls/compiler/ast/ASTNodeVisitor.java b/src/main/java/net/prominic/groovyls/compiler/ast/ASTNodeVisitor.java new file mode 100644 index 000000000..c1d974827 --- /dev/null +++ b/src/main/java/net/prominic/groovyls/compiler/ast/ASTNodeVisitor.java @@ -0,0 +1,830 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2022 Prominic.NET, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License +// +// Author: Prominic.NET, Inc. +// No warranty of merchantability or fitness of any kind. +// Use this software at your own risk. +//////////////////////////////////////////////////////////////////////////////// +package net.prominic.groovyls.compiler.ast; + +import com.cleanroommc.groovyscript.helper.BetterList; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import net.prominic.groovyls.util.GroovyLanguageServerUtils; +import net.prominic.lsp.utils.Positions; +import net.prominic.lsp.utils.Ranges; +import org.codehaus.groovy.ast.*; +import org.codehaus.groovy.ast.expr.*; +import org.codehaus.groovy.ast.stmt.*; +import org.codehaus.groovy.classgen.BytecodeExpression; +import org.codehaus.groovy.control.CompilationUnit; +import org.codehaus.groovy.control.SourceUnit; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.Range; + +import java.net.URI; +import java.util.*; +import java.util.stream.Collectors; + +public class ASTNodeVisitor extends ClassCodeVisitorSupport { + + private static class ASTLookupKey { + + public ASTLookupKey(ASTNode node) { + this.node = node; + } + + private final ASTNode node; + + @Override + public boolean equals(Object o) { + // some ASTNode subclasses, like ClassNode, override equals() with + // comparisons that are not strict. we need strict. + return o instanceof ASTLookupKey other && node == other.node; + } + + @Override + public int hashCode() { + return node.hashCode(); + } + } + + private static class ASTNodeLookupData { + + public ASTNode parent; + public URI uri; + } + + private SourceUnit sourceUnit; + + @Override + protected SourceUnit getSourceUnit() { + return sourceUnit; + } + + private final BetterList stack = new BetterList<>(); + private final Map> nodesByURI = new Object2ObjectOpenHashMap<>(); + private final Map> classNodesByURI = new Object2ObjectOpenHashMap<>(); + private final Map lookup = new Object2ObjectOpenHashMap<>(); + + private void pushASTNode(ASTNode node) { + if (!(node instanceof AnnotatedNode an && an.isSynthetic())) { + URI uri = sourceUnit.getSource().getURI(); + nodesByURI.get(uri).add(node); + + ASTNodeLookupData data = new ASTNodeLookupData(); + data.uri = uri; + data.parent = stack.peekLast(); + lookup.put(new ASTLookupKey(node), data); + } + stack.add(node); + } + + private void popASTNode() { + stack.pop(); + } + + public List getClassNodes() { + List result = new ArrayList<>(); + for (List nodes : classNodesByURI.values()) { + result.addAll(nodes); + } + return result; + } + + public List getNodes() { + List result = new ArrayList<>(); + for (List nodes : nodesByURI.values()) { + result.addAll(nodes); + } + return result; + } + + public List getNodes(URI uri) { + List nodes = nodesByURI.get(uri); + if (nodes == null) { + return Collections.emptyList(); + } + return nodes; + } + + public ASTNode getNodeAtLineAndColumn(URI uri, int line, int column) { + Position position = new Position(line, column); + Map nodeToRange = new HashMap<>(); + List nodes = nodesByURI.get(uri); + if (nodes == null) { + return null; + } + List foundNodes = nodes.stream().filter(node -> { + if (node.getLineNumber() == -1) { + // can't be the offset node if it has no position + // also, do this first because it's the fastest comparison + return false; + } + Range range = GroovyLanguageServerUtils.astNodeToRange(node); + if (range == null) { + return false; + } + boolean result = Ranges.contains(range, position); + if (result) { + // save the range object to avoid creating it again when we + // sort the nodes + nodeToRange.put(node, range); + } + return result; + }).sorted((n1, n2) -> { + int result = Positions.COMPARATOR.reversed().compare(nodeToRange.get(n1).getStart(), + nodeToRange.get(n2).getStart()); + if (result != 0) { + return result; + } + result = Positions.COMPARATOR.compare(nodeToRange.get(n1).getEnd(), nodeToRange.get(n2).getEnd()); + if (result != 0) { + return result; + } + // n1 and n2 have the same range + if (contains(n1, n2)) { + if (n1 instanceof ClassNode && n2 instanceof ConstructorNode) { + return -1; + } + return 1; + } else if (contains(n2, n1)) { + if (n2 instanceof ClassNode && n1 instanceof ConstructorNode) { + return 1; + } + return -1; + } + return 0; + }).collect(Collectors.toList()); + if (foundNodes.isEmpty()) { + return null; + } + return foundNodes.get(0); + } + + public ASTNode getParent(ASTNode child) { + if (child == null) { + return null; + } + ASTNodeLookupData data = lookup.get(new ASTLookupKey(child)); + if (data == null) { + return null; + } + return data.parent; + } + + public boolean contains(ASTNode ancestor, ASTNode descendant) { + ASTNode current = getParent(descendant); + while (current != null) { + if (current.equals(ancestor)) { + return true; + } + current = getParent(current); + } + return false; + } + + public URI getURI(ASTNode node) { + ASTNodeLookupData data = lookup.get(new ASTLookupKey(node)); + if (data == null) { + return null; + } + return data.uri; + } + + public void visitCompilationUnit(CompilationUnit unit) { + nodesByURI.clear(); + classNodesByURI.clear(); + lookup.clear(); + unit.iterator().forEachRemaining(this::visitSourceUnit); + } + + public void visitCompilationUnit(CompilationUnit unit, Collection uris) { + uris.forEach(uri -> { + // clear all old nodes so that they may be replaced + List nodes = nodesByURI.remove(uri); + if (nodes != null) { + nodes.forEach(node -> { + lookup.remove(new ASTLookupKey(node)); + }); + } + classNodesByURI.remove(uri); + }); + unit.iterator().forEachRemaining(sourceUnit -> { + URI uri = sourceUnit.getSource().getURI(); + if (!uris.contains(uri)) { + return; + } + visitSourceUnit(sourceUnit); + }); + } + + public void visitSourceUnit(SourceUnit unit) { + sourceUnit = unit; + URI uri = sourceUnit.getSource().getURI(); + nodesByURI.put(uri, new ArrayList<>()); + classNodesByURI.put(uri, new ArrayList<>()); + stack.clear(); + ModuleNode moduleNode = unit.getAST(); + if (moduleNode != null) { + visitModule(moduleNode); + } + sourceUnit = null; + stack.clear(); + } + + public void visitModule(ModuleNode node) { + pushASTNode(node); + try { + node.getClasses().forEach(this::visitClass); + } finally { + popASTNode(); + } + } + + // GroovyClassVisitor + + public void visitClass(ClassNode node) { + URI uri = sourceUnit.getSource().getURI(); + classNodesByURI.get(uri).add(node); + pushASTNode(node); + try { + ClassNode unresolvedSuperClass = node.getUnresolvedSuperClass(); + if (unresolvedSuperClass != null && unresolvedSuperClass.getLineNumber() != -1) { + pushASTNode(unresolvedSuperClass); + popASTNode(); + } + for (ClassNode unresolvedInterface : node.getUnresolvedInterfaces()) { + if (unresolvedInterface.getLineNumber() == -1) { + continue; + } + pushASTNode(unresolvedInterface); + popASTNode(); + } + super.visitClass(node); + } finally { + popASTNode(); + } + } + + @Override + public void visitImports(ModuleNode node) { + if (node != null) { + for (ImportNode importNode : node.getImports()) { + pushASTNode(importNode); + visitAnnotations(importNode); + importNode.visit(this); + popASTNode(); + } + for (ImportNode importStarNode : node.getStarImports()) { + pushASTNode(importStarNode); + visitAnnotations(importStarNode); + importStarNode.visit(this); + popASTNode(); + } + for (ImportNode importStaticNode : node.getStaticImports().values()) { + pushASTNode(importStaticNode); + visitAnnotations(importStaticNode); + importStaticNode.visit(this); + popASTNode(); + } + for (ImportNode importStaticStarNode : node.getStaticStarImports().values()) { + pushASTNode(importStaticStarNode); + visitAnnotations(importStaticStarNode); + importStaticStarNode.visit(this); + popASTNode(); + } + } + } + + public void visitConstructor(ConstructorNode node) { + pushASTNode(node); + try { + super.visitConstructor(node); + for (Parameter parameter : node.getParameters()) { + visitParameter(parameter); + } + } finally { + popASTNode(); + } + } + + public void visitMethod(MethodNode node) { + pushASTNode(node); + try { + super.visitMethod(node); + for (Parameter parameter : node.getParameters()) { + visitParameter(parameter); + } + } finally { + popASTNode(); + } + } + + protected void visitParameter(Parameter node) { + // only add node to lookup map + pushASTNode(node); + popASTNode(); + } + + public void visitField(FieldNode node) { + pushASTNode(node); + try { + super.visitField(node); + } finally { + popASTNode(); + } + } + + public void visitProperty(PropertyNode node) { + pushASTNode(node); + try { + super.visitProperty(node); + } finally { + popASTNode(); + } + } + + // GroovyCodeVisitor + + public void visitBlockStatement(BlockStatement node) { + pushASTNode(node); + try { + super.visitBlockStatement(node); + } finally { + popASTNode(); + } + } + + public void visitForLoop(ForStatement node) { + pushASTNode(node); + try { + super.visitForLoop(node); + } finally { + popASTNode(); + } + } + + public void visitWhileLoop(WhileStatement node) { + pushASTNode(node); + try { + super.visitWhileLoop(node); + } finally { + popASTNode(); + } + } + + public void visitDoWhileLoop(DoWhileStatement node) { + pushASTNode(node); + try { + super.visitDoWhileLoop(node); + } finally { + popASTNode(); + } + } + + public void visitIfElse(IfStatement node) { + pushASTNode(node); + try { + super.visitIfElse(node); + } finally { + popASTNode(); + } + } + + public void visitExpressionStatement(ExpressionStatement node) { + pushASTNode(node); + try { + super.visitExpressionStatement(node); + } finally { + popASTNode(); + } + } + + public void visitReturnStatement(ReturnStatement node) { + pushASTNode(node); + try { + super.visitReturnStatement(node); + } finally { + popASTNode(); + } + } + + public void visitAssertStatement(AssertStatement node) { + pushASTNode(node); + try { + super.visitAssertStatement(node); + } finally { + popASTNode(); + } + } + + public void visitTryCatchFinally(TryCatchStatement node) { + pushASTNode(node); + try { + super.visitTryCatchFinally(node); + } finally { + popASTNode(); + } + } + + public void visitEmptyStatement(EmptyStatement node) { + pushASTNode(node); + try { + super.visitEmptyStatement(node); + } finally { + popASTNode(); + } + } + + public void visitSwitch(SwitchStatement node) { + pushASTNode(node); + try { + super.visitSwitch(node); + } finally { + popASTNode(); + } + } + + public void visitCaseStatement(CaseStatement node) { + pushASTNode(node); + try { + super.visitCaseStatement(node); + } finally { + popASTNode(); + } + } + + public void visitBreakStatement(BreakStatement node) { + pushASTNode(node); + try { + super.visitBreakStatement(node); + } finally { + popASTNode(); + } + } + + public void visitContinueStatement(ContinueStatement node) { + pushASTNode(node); + try { + super.visitContinueStatement(node); + } finally { + popASTNode(); + } + } + + public void visitSynchronizedStatement(SynchronizedStatement node) { + pushASTNode(node); + try { + super.visitSynchronizedStatement(node); + } finally { + popASTNode(); + } + } + + public void visitThrowStatement(ThrowStatement node) { + pushASTNode(node); + try { + super.visitThrowStatement(node); + } finally { + popASTNode(); + } + } + + public void visitMethodCallExpression(MethodCallExpression node) { + pushASTNode(node); + try { + super.visitMethodCallExpression(node); + } finally { + popASTNode(); + } + } + + public void visitStaticMethodCallExpression(StaticMethodCallExpression node) { + pushASTNode(node); + try { + super.visitStaticMethodCallExpression(node); + } finally { + popASTNode(); + } + } + + public void visitConstructorCallExpression(ConstructorCallExpression node) { + pushASTNode(node); + try { + super.visitConstructorCallExpression(node); + } finally { + popASTNode(); + } + } + + public void visitBinaryExpression(BinaryExpression node) { + pushASTNode(node); + try { + super.visitBinaryExpression(node); + } finally { + popASTNode(); + } + } + + public void visitTernaryExpression(TernaryExpression node) { + pushASTNode(node); + try { + super.visitTernaryExpression(node); + } finally { + popASTNode(); + } + } + + public void visitShortTernaryExpression(ElvisOperatorExpression node) { + pushASTNode(node); + try { + super.visitShortTernaryExpression(node); + } finally { + popASTNode(); + } + } + + public void visitPostfixExpression(PostfixExpression node) { + pushASTNode(node); + try { + super.visitPostfixExpression(node); + } finally { + popASTNode(); + } + } + + public void visitPrefixExpression(PrefixExpression node) { + pushASTNode(node); + try { + super.visitPrefixExpression(node); + } finally { + popASTNode(); + } + } + + public void visitBooleanExpression(BooleanExpression node) { + pushASTNode(node); + try { + super.visitBooleanExpression(node); + } finally { + popASTNode(); + } + } + + public void visitNotExpression(NotExpression node) { + pushASTNode(node); + try { + super.visitNotExpression(node); + } finally { + popASTNode(); + } + } + + public void visitClosureExpression(ClosureExpression node) { + pushASTNode(node); + try { + super.visitClosureExpression(node); + } finally { + popASTNode(); + } + } + + public void visitTupleExpression(TupleExpression node) { + pushASTNode(node); + try { + super.visitTupleExpression(node); + } finally { + popASTNode(); + } + } + + public void visitListExpression(ListExpression node) { + pushASTNode(node); + try { + super.visitListExpression(node); + } finally { + popASTNode(); + } + } + + public void visitArrayExpression(ArrayExpression node) { + pushASTNode(node); + try { + super.visitArrayExpression(node); + } finally { + popASTNode(); + } + } + + public void visitMapExpression(MapExpression node) { + pushASTNode(node); + try { + super.visitMapExpression(node); + } finally { + popASTNode(); + } + } + + public void visitMapEntryExpression(MapEntryExpression node) { + pushASTNode(node); + try { + super.visitMapEntryExpression(node); + } finally { + popASTNode(); + } + } + + public void visitRangeExpression(RangeExpression node) { + pushASTNode(node); + try { + super.visitRangeExpression(node); + } finally { + popASTNode(); + } + } + + public void visitSpreadExpression(SpreadExpression node) { + pushASTNode(node); + try { + super.visitSpreadExpression(node); + } finally { + popASTNode(); + } + } + + public void visitSpreadMapExpression(SpreadMapExpression node) { + pushASTNode(node); + try { + super.visitSpreadMapExpression(node); + } finally { + popASTNode(); + } + } + + public void visitMethodPointerExpression(MethodPointerExpression node) { + pushASTNode(node); + try { + super.visitMethodPointerExpression(node); + } finally { + popASTNode(); + } + } + + public void visitUnaryMinusExpression(UnaryMinusExpression node) { + pushASTNode(node); + try { + super.visitUnaryMinusExpression(node); + } finally { + popASTNode(); + } + } + + public void visitUnaryPlusExpression(UnaryPlusExpression node) { + pushASTNode(node); + try { + super.visitUnaryPlusExpression(node); + } finally { + popASTNode(); + } + } + + public void visitBitwiseNegationExpression(BitwiseNegationExpression node) { + pushASTNode(node); + try { + super.visitBitwiseNegationExpression(node); + } finally { + popASTNode(); + } + } + + public void visitCastExpression(CastExpression node) { + pushASTNode(node); + try { + super.visitCastExpression(node); + } finally { + popASTNode(); + } + } + + public void visitConstantExpression(ConstantExpression node) { + pushASTNode(node); + try { + super.visitConstantExpression(node); + } finally { + popASTNode(); + } + } + + public void visitClassExpression(ClassExpression node) { + pushASTNode(node); + try { + super.visitClassExpression(node); + } finally { + popASTNode(); + } + } + + public void visitVariableExpression(VariableExpression node) { + pushASTNode(node); + try { + super.visitVariableExpression(node); + } finally { + popASTNode(); + } + } + + // this calls visitBinaryExpression() + // public void visitDeclarationExpression(DeclarationExpression node) { + // pushASTNode(node); + // try { + // super.visitDeclarationExpression(node); + // } finally { + // popASTNode(); + // } + // } + + public void visitPropertyExpression(PropertyExpression node) { + pushASTNode(node); + try { + super.visitPropertyExpression(node); + } finally { + popASTNode(); + } + } + + public void visitAttributeExpression(AttributeExpression node) { + pushASTNode(node); + try { + super.visitAttributeExpression(node); + } finally { + popASTNode(); + } + } + + public void visitFieldExpression(FieldExpression node) { + pushASTNode(node); + try { + super.visitFieldExpression(node); + } finally { + popASTNode(); + } + } + + public void visitGStringExpression(GStringExpression node) { + pushASTNode(node); + try { + super.visitGStringExpression(node); + } finally { + popASTNode(); + } + } + + public void visitCatchStatement(CatchStatement node) { + pushASTNode(node); + try { + super.visitCatchStatement(node); + } finally { + popASTNode(); + } + } + + // this calls visitTupleListExpression() + // public void visitArgumentlistExpression(ArgumentListExpression node) { + // pushASTNode(node); + // try { + // super.visitArgumentlistExpression(node); + // } finally { + // popASTNode(); + // } + // } + + public void visitClosureListExpression(ClosureListExpression node) { + pushASTNode(node); + try { + super.visitClosureListExpression(node); + } finally { + popASTNode(); + } + } + + public void visitBytecodeExpression(BytecodeExpression node) { + pushASTNode(node); + try { + super.visitBytecodeExpression(node); + } finally { + popASTNode(); + } + } +} \ No newline at end of file diff --git a/src/main/java/net/prominic/groovyls/compiler/control/GroovyLSCompilationUnit.java b/src/main/java/net/prominic/groovyls/compiler/control/GroovyLSCompilationUnit.java new file mode 100644 index 000000000..10ccff0b1 --- /dev/null +++ b/src/main/java/net/prominic/groovyls/compiler/control/GroovyLSCompilationUnit.java @@ -0,0 +1,140 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2022 Prominic.NET, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License +// +// Author: Prominic.NET, Inc. +// No warranty of merchantability or fitness of any kind. +// Use this software at your own risk. +//////////////////////////////////////////////////////////////////////////////// +package net.prominic.groovyls.compiler.control; + +import groovy.lang.GroovyClassLoader; +import net.prominic.groovyls.compiler.ILanguageServerContext; +import net.prominic.groovyls.compiler.ast.ASTNodeVisitor; +import org.codehaus.groovy.ast.CompileUnit; +import org.codehaus.groovy.ast.ModuleNode; +import org.codehaus.groovy.control.*; +import org.codehaus.groovy.tools.GroovyClass; +import org.jetbrains.annotations.Nullable; + +import java.net.URI; +import java.security.CodeSource; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +public class GroovyLSCompilationUnit extends CompilationUnit { + + private final ILanguageServerContext languageServerContext; + @Nullable + private ASTNodeVisitor visitor; + + @Nullable + private URI previousContext; + + public GroovyLSCompilationUnit(CompilerConfiguration config, CodeSource security, GroovyClassLoader loader, ILanguageServerContext languageServerContext) { + super(config, security, loader); + this.languageServerContext = languageServerContext; + this.errorCollector = new LanguageServerErrorCollector(config); + } + + public void setErrorCollector(LanguageServerErrorCollector errorCollector) { + this.errorCollector = errorCollector; + } + + public void removeSources(Collection sourceUnitsToRemove) { + for (SourceUnit sourceUnit : sourceUnitsToRemove) { + if (sourceUnit.getAST() != null) { + List sourceUnitClassNames = sourceUnit.getAST().getClasses().stream() + .map(classNode -> classNode.getName()).collect(Collectors.toList()); + final List generatedClasses = getClasses(); + generatedClasses.removeIf(groovyClass -> sourceUnitClassNames.contains(groovyClass.getName())); + } + sources.remove(sourceUnit.getName()); + } + // keep existing modules from other source units + List modules = ast.getModules(); + ast = new CompileUnit(this.classLoader, null, this.configuration); + for (ModuleNode module : modules) { + if (!sourceUnitsToRemove.contains(module.getContext())) { + ast.addModule(module); + } + } + LanguageServerErrorCollector lsErrorCollector = (LanguageServerErrorCollector) errorCollector; + lsErrorCollector.clear(); + } + + public void removeSource(SourceUnit sourceUnit) { + removeSources(Collections.singletonList(sourceUnit)); + } + + @Override + public void compile() throws CompilationFailedException { + // AST is completely built after the canonicalization phase + // for code intelligence, we shouldn't need to go further + // http://groovy-lang.org/metaprogramming.html#_compilation_phases_guide + try { + compile(Phases.CANONICALIZATION); + } catch (CompilationFailedException e) { + // ignore + } + } + + private ASTNodeVisitor visitAST(Set uris) { + if (visitor == null || uris.isEmpty()) { + visitor = new ASTNodeVisitor(); + visitor.visitCompilationUnit(this); + } else { + visitor.visitCompilationUnit(this, uris); + } + + return visitor; + } + + private ASTNodeVisitor compileAndVisitAST() { + previousContext = null; + + compile(); + return visitAST(Collections.emptySet()); + } + + private ASTNodeVisitor compileAndVisitAST(@Nullable URI context) { + if (context == null) { + return compileAndVisitAST(); + } + + previousContext = context; + + compile(); + return visitAST(Collections.singleton(context)); + } + + public ASTNodeVisitor recompileAndVisitASTIfContextChanged(@Nullable URI context) { + var isChanged = context == null || languageServerContext.getFileContentsTracker().getChangedURIs().contains(context); + + languageServerContext.getFileContentsTracker().resetChangedFiles(); + + if ((previousContext == null || previousContext.equals(context)) && visitor != null && !isChanged) { + return visitor; + } + + if (context != null) { + languageServerContext.getFileContentsTracker().forceChanged(context); + } + + return compileAndVisitAST(context); + } +} \ No newline at end of file diff --git a/src/main/java/net/prominic/groovyls/compiler/control/LanguageServerErrorCollector.java b/src/main/java/net/prominic/groovyls/compiler/control/LanguageServerErrorCollector.java new file mode 100644 index 000000000..ec137ebc8 --- /dev/null +++ b/src/main/java/net/prominic/groovyls/compiler/control/LanguageServerErrorCollector.java @@ -0,0 +1,50 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2022 Prominic.NET, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License +// +// Author: Prominic.NET, Inc. +// No warranty of merchantability or fitness of any kind. +// Use this software at your own risk. +//////////////////////////////////////////////////////////////////////////////// +package net.prominic.groovyls.compiler.control; + +import org.codehaus.groovy.control.CompilationFailedException; +import org.codehaus.groovy.control.CompilerConfiguration; +import org.codehaus.groovy.control.ErrorCollector; + +/** + * A special ErrorCollector for language servers that can clear all errors and + * does not throw exceptions. + */ +public class LanguageServerErrorCollector extends ErrorCollector { + private static final long serialVersionUID = 1L; + + public LanguageServerErrorCollector(CompilerConfiguration configuration) { + super(configuration); + } + + public void clear() { + if (errors != null) { + errors.clear(); + } + if (warnings != null) { + warnings.clear(); + } + } + + @Override + protected void failIfErrors() throws CompilationFailedException { + // don't fail + } +} diff --git a/src/main/java/net/prominic/groovyls/compiler/control/io/StringReaderSourceWithURI.java b/src/main/java/net/prominic/groovyls/compiler/control/io/StringReaderSourceWithURI.java new file mode 100644 index 000000000..1b1a96e0e --- /dev/null +++ b/src/main/java/net/prominic/groovyls/compiler/control/io/StringReaderSourceWithURI.java @@ -0,0 +1,38 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2022 Prominic.NET, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License +// +// Author: Prominic.NET, Inc. +// No warranty of merchantability or fitness of any kind. +// Use this software at your own risk. +//////////////////////////////////////////////////////////////////////////////// +package net.prominic.groovyls.compiler.control.io; + +import java.net.URI; + +import org.codehaus.groovy.control.CompilerConfiguration; +import org.codehaus.groovy.control.io.StringReaderSource; + +public class StringReaderSourceWithURI extends StringReaderSource { + private URI uri; + + public StringReaderSourceWithURI(String string, URI uri, CompilerConfiguration configuration) { + super(string, configuration); + this.uri = uri; + } + + public URI getURI() { + return uri; + } +} \ No newline at end of file diff --git a/src/main/java/net/prominic/groovyls/compiler/documentation/DocumentationFactory.java b/src/main/java/net/prominic/groovyls/compiler/documentation/DocumentationFactory.java new file mode 100644 index 000000000..31299fac7 --- /dev/null +++ b/src/main/java/net/prominic/groovyls/compiler/documentation/DocumentationFactory.java @@ -0,0 +1,24 @@ +package net.prominic.groovyls.compiler.documentation; + +import net.prominic.groovyls.compiler.ast.ASTContext; +import org.codehaus.groovy.ast.AnnotatedNode; +import org.jetbrains.annotations.Nullable; + +public class DocumentationFactory { + + private final IDocumentationProvider[] providers; + + public DocumentationFactory(IDocumentationProvider... providers) { + this.providers = providers; + } + + public @Nullable String getDocumentation(AnnotatedNode node, ASTContext context) { + for (IDocumentationProvider provider : providers) { + String documentation = provider.getDocumentation(node, context); + if (documentation != null) { + return documentation; + } + } + return null; + } +} diff --git a/src/main/java/net/prominic/groovyls/compiler/documentation/GroovydocProvider.java b/src/main/java/net/prominic/groovyls/compiler/documentation/GroovydocProvider.java new file mode 100644 index 000000000..208955346 --- /dev/null +++ b/src/main/java/net/prominic/groovyls/compiler/documentation/GroovydocProvider.java @@ -0,0 +1,72 @@ +package net.prominic.groovyls.compiler.documentation; + +import net.prominic.groovyls.compiler.ast.ASTContext; +import org.codehaus.groovy.ast.AnnotatedNode; +import org.jetbrains.annotations.Nullable; + +public class GroovydocProvider implements IDocumentationProvider { + + @Override + public @Nullable String getDocumentation(AnnotatedNode node, ASTContext context) { + var groovydoc = node.getGroovydoc(); + + if (groovydoc == null || !groovydoc.isPresent()) { + return null; + } + String content = groovydoc.getContent(); + String[] lines = content.split("\n"); + StringBuilder markdownBuilder = new StringBuilder(); + int n = lines.length; + if (n == 1) { + // strip end of groovydoc comment + int c = lines[0].indexOf("*/"); + if (c != -1) { + lines[0] = lines[0].substring(0, c); + } + } + // strip start of groovydoc coment + String line = lines[0]; + int lengthToRemove = Math.min(line.length(), 3); + line = line.substring(lengthToRemove); + appendLine(markdownBuilder, line); + for (int i = 1; i < n - 1; i++) { + line = lines[i]; + int star = line.indexOf("*"); + int at = line.indexOf("@"); + if (at == -1) { + if (star > -1) // line starts with a * + { + appendLine(markdownBuilder, line.substring(star + 1)); + } + } + } + return markdownBuilder.toString().trim(); + } + + private static void appendLine(StringBuilder markdownBuilder, String line) { + line = reformatLine(line); + if (line.length() == 0) { + return; + } + markdownBuilder.append(line); + markdownBuilder.append("\n"); + } + + private static String reformatLine(String line) { + // remove all attributes (including namespaced) + line = line.replaceAll("<(\\w+)(?:\\s+\\w+(?::\\w+)?=(\"|\')[^\"\']*\\2)*\\s*(\\/{0,1})>", "<$1$3>"); + line = line.replaceAll("
", "\n\n```\n");
+        line = line.replaceAll("
", "\n```\n"); + line = line.replaceAll("", "_"); + line = line.replaceAll("", "**"); + line = line.replaceAll("", "`"); + line = line.replaceAll("
", "\n\n---\n\n"); + line = line.replaceAll("<(p|ul|ol|dl|li|dt|table|tr|div|blockquote)>", "\n\n"); + + // to add a line break to markdown, there needs to be at least two + // spaces at the end of the line + line = line.replaceAll("\\s*", " \n"); + line = line.replaceAll("<\\/{0,1}\\w+\\/{0,1}>", ""); + return line; + } +} diff --git a/src/main/java/net/prominic/groovyls/compiler/documentation/IDocumentationProvider.java b/src/main/java/net/prominic/groovyls/compiler/documentation/IDocumentationProvider.java new file mode 100644 index 000000000..85ae10649 --- /dev/null +++ b/src/main/java/net/prominic/groovyls/compiler/documentation/IDocumentationProvider.java @@ -0,0 +1,10 @@ +package net.prominic.groovyls.compiler.documentation; + +import net.prominic.groovyls.compiler.ast.ASTContext; +import org.codehaus.groovy.ast.AnnotatedNode; +import org.jetbrains.annotations.Nullable; + +public interface IDocumentationProvider { + + @Nullable String getDocumentation(AnnotatedNode node, ASTContext context); +} diff --git a/src/main/java/net/prominic/groovyls/compiler/util/GroovyASTUtils.java b/src/main/java/net/prominic/groovyls/compiler/util/GroovyASTUtils.java new file mode 100644 index 000000000..a9fd1e3cd --- /dev/null +++ b/src/main/java/net/prominic/groovyls/compiler/util/GroovyASTUtils.java @@ -0,0 +1,487 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2022 Prominic.NET, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License +// +// Author: Prominic.NET, Inc. +// No warranty of merchantability or fitness of any kind. +// Use this software at your own risk. +//////////////////////////////////////////////////////////////////////////////// +package net.prominic.groovyls.compiler.util; + +import com.cleanroommc.groovyscript.api.IDynamicGroovyProperty; +import com.cleanroommc.groovyscript.gameobjects.GameObjectHandlerManager; +import net.prominic.groovyls.compiler.ast.ASTContext; +import net.prominic.groovyls.util.ClassGraphUtils; +import net.prominic.groovyls.util.GroovyLanguageServerUtils; +import org.codehaus.groovy.ast.*; +import org.codehaus.groovy.ast.expr.*; +import org.codehaus.groovy.ast.stmt.ExpressionStatement; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.Range; +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.Opcodes; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +public class GroovyASTUtils { + + public static ASTNode getEnclosingNodeOfType(ASTNode offsetNode, Class nodeType, + ASTContext context) { + ASTNode current = offsetNode; + while (current != null) { + if (nodeType.isInstance(current)) { + return current; + } + current = context.getVisitor().getParent(current); + } + return null; + } + + public static ASTNode getDefinition(ASTNode node, boolean strict, ASTContext context) { + if (node == null) { + return null; + } + ASTNode parentNode = context.getVisitor().getParent(node); + if (node instanceof ExpressionStatement) { + ExpressionStatement statement = (ExpressionStatement) node; + node = statement.getExpression(); + } + if (node instanceof ClassNode) { + return tryToResolveOriginalClassNode((ClassNode) node, strict, context); + } else if (node instanceof ConstructorCallExpression) { + ConstructorCallExpression callExpression = (ConstructorCallExpression) node; + return GroovyASTUtils.getMethodFromCallExpression(callExpression, context); + } else if (node instanceof DeclarationExpression) { + DeclarationExpression declExpression = (DeclarationExpression) node; + if (!declExpression.isMultipleAssignmentDeclaration()) { + ClassNode originType = declExpression.getVariableExpression().getOriginType(); + return tryToResolveOriginalClassNode(originType, strict, context); + } + } else if (node instanceof ClassExpression) { + ClassExpression classExpression = (ClassExpression) node; + return tryToResolveOriginalClassNode(classExpression.getType(), strict, context); + } else if (node instanceof ImportNode) { + ImportNode importNode = (ImportNode) node; + return tryToResolveOriginalClassNode(importNode.getType(), strict, context); + } else if (node instanceof MethodNode) { + return node; + } else if (node instanceof ConstantExpression && parentNode != null) { + if (parentNode instanceof MethodCallExpression) { + MethodCallExpression methodCallExpression = (MethodCallExpression) parentNode; + return GroovyASTUtils.getMethodFromCallExpression(methodCallExpression, context); + } else if (parentNode instanceof PropertyExpression) { + PropertyExpression propertyExpression = (PropertyExpression) parentNode; + PropertyNode propNode = GroovyASTUtils.getPropertyFromExpression(propertyExpression, context); + if (propNode != null) { + return propNode; + } + return GroovyASTUtils.getFieldFromExpression(propertyExpression, context); + } + } else if (node instanceof VariableExpression) { + VariableExpression variableExpression = (VariableExpression) node; + Variable accessedVariable = variableExpression.getAccessedVariable(); + if (accessedVariable instanceof ASTNode) { + return (ASTNode) accessedVariable; + } + + Object binding = context.getLanguageServerContext().getSandbox().getBindings().get(variableExpression.getName()); + if (binding == null) { + // DynamicVariable is not an ASTNode, so skip it + return null; + } + + return new VariableExpression(variableExpression.getName(), new ClassNode(binding.getClass())); + } else if (node instanceof Variable) { + return node; + } else if (node instanceof MethodCallExpression methodCallExpression) { + return getDefinition(methodCallExpression.getObjectExpression(), strict, context); + } else if (node instanceof StaticMethodCallExpression staticMethodCallExpression) { + var gameObjectName = getAccessedGameObjectName(staticMethodCallExpression); + + if (gameObjectName != null) { + var gameObjectReturnType = GameObjectHandlerManager.getReturnTypeOf(gameObjectName); + + if (gameObjectReturnType == null) { + return null; + } + + var methodNode = new MethodNode(gameObjectName, Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, + ClassHelper.makeCached(gameObjectReturnType), + new Parameter[]{ + new Parameter(ClassHelper.makeCached(String.class), "mainArg"), + new Parameter(ClassHelper.makeCached(Object[].class), "args") + }, + null, + null + ); + + methodNode.setDeclaringClass(ClassHelper.makeCached(GameObjectHandlerManager.class)); + + return methodNode; + } + + return GroovyASTUtils.getMethodFromCallExpression(staticMethodCallExpression, context); + } + return null; + } + + private static @Nullable String getAccessedGameObjectName(StaticMethodCallExpression staticMethodCallExpression) { + return staticMethodCallExpression.getOwnerType().equals(ClassHelper.makeCached(GameObjectHandlerManager.class)) && + staticMethodCallExpression.getMethod().equals("getGameObject") && + staticMethodCallExpression.getArguments() instanceof ArgumentListExpression argumentListExpression && + !argumentListExpression.getExpressions().isEmpty() && + argumentListExpression.getExpression(0) instanceof ConstantExpression objectNameConstantExpression && + GameObjectHandlerManager.hasGameObjectHandler(objectNameConstantExpression.getText()) ? + objectNameConstantExpression.getText() : + null; + } + + public static ASTNode getTypeDefinition(ASTNode node, ASTContext context) { + ASTNode definitionNode = getDefinition(node, false, context); + if (definitionNode == null) { + return null; + } + if (definitionNode instanceof MethodNode) { + MethodNode method = (MethodNode) definitionNode; + return tryToResolveOriginalClassNode(method.getReturnType(), true, context); + } else if (definitionNode instanceof Variable) { + Variable variable = (Variable) definitionNode; + return tryToResolveOriginalClassNode(variable.getOriginType(), true, context); + } + return null; + } + + public static List getReferences(ASTNode node, ASTContext context) { + ASTNode definitionNode = getDefinition(node, true, context); + if (definitionNode == null) { + return Collections.emptyList(); + } + return context.getVisitor().getNodes().stream().filter(otherNode -> { + ASTNode otherDefinition = getDefinition(otherNode, false, context); + return definitionNode.equals(otherDefinition) && node.getLineNumber() != -1 && node.getColumnNumber() != -1; + }).collect(Collectors.toList()); + } + + private static ClassNode tryToResolveOriginalClassNode(ClassNode node, boolean strict, ASTContext context) { + for (ClassNode originalNode : context.getVisitor().getClassNodes()) { + if (originalNode.equals(node)) { + return originalNode; + } + } + if (strict) { + return null; + } + return node; + } + + public static PropertyNode getPropertyFromExpression(PropertyExpression node, ASTContext context) { + ClassNode classNode = getTypeOfNode(node.getObjectExpression(), context); + if (classNode != null && classNode.implementsInterface(new ClassNode(IDynamicGroovyProperty.class))) { + var value = resolveDynamicValue(node, context); + + if (value != null) { + return new PropertyNode(node.getProperty().getText(), Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL, + new ClassNode(value.getClass()), + classNode, + null, null, null); + } + } + if (classNode != null) { + var prop = classNode.getProperty(node.getProperty().getText()); + var field = classNode.getField(node.getProperty().getText()); + + if (prop == null && field != null) { + prop = new PropertyNode(field, Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL, null, null); + } + + return prop; + } + return null; + } + + public static Object resolveDynamicValue(ASTNode node, ASTContext context) { + if (node instanceof PropertyExpression propertyExpression) { + var value = resolveDynamicValue(propertyExpression.getObjectExpression(), context); + + Object result = null; + if (value instanceof IDynamicGroovyProperty dynamicValue) { + result = dynamicValue.getProperty(propertyExpression.getProperty().getText()); + } + + if (value != null && result == null) { + try { + result = value.getClass().getDeclaredField(propertyExpression.getProperty().getText()).get(value); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (NoSuchFieldException e) { + return null; + } + } + + return result; + } else if (node instanceof VariableExpression variableExpression) { + return context.getLanguageServerContext().getSandbox().getBindings().get(variableExpression.getName()); + } + return null; + } + + public static FieldNode getFieldFromExpression(PropertyExpression node, ASTContext context) { + ClassNode classNode = getTypeOfNode(node.getObjectExpression(), context); + if (classNode != null) { + return classNode.getField(node.getProperty().getText()); + } + return null; + } + + public static List getFieldsForLeftSideOfPropertyExpression(Expression node, ASTContext context) { + ClassNode classNode = getTypeOfNode(node, context); + + if (classNode != null && node instanceof VariableExpression) { + var binding = context.getLanguageServerContext().getSandbox().getBindings().get(((VariableExpression) node).getName()); + var classInfo = ClassGraphUtils.resolveAllowedClassInfo(classNode, context); + + if (classInfo != null && binding != null && (classInfo.loadClass().equals(IDynamicGroovyProperty.class) || classInfo.implementsInterface(IDynamicGroovyProperty.class))) { + final ClassNode finalClassNode = classNode; + return ((IDynamicGroovyProperty) binding).getProperties().entrySet().stream() + .filter(entry -> entry.getValue() != null) + .map(entry -> new FieldNode(entry.getKey(), Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL, + new ClassNode(entry.getValue().getClass()), + finalClassNode, + new ConstantExpression(entry.getValue()))) + .collect(Collectors.toList()); + } + } + + if (classNode != null) { + boolean statics = node instanceof ClassExpression; + return classNode.getFields().stream().filter(fieldNode -> { + return statics ? fieldNode.isStatic() : !fieldNode.isStatic(); + }).collect(Collectors.toList()); + } + return Collections.emptyList(); + } + + public static List getPropertiesForLeftSideOfPropertyExpression(Expression node, + ASTContext context) { + ClassNode classNode = getTypeOfNode(node, context); + if (classNode != null) { + boolean statics = node instanceof ClassExpression; + return classNode.getProperties().stream().filter(propNode -> { + return statics ? propNode.isStatic() : !propNode.isStatic(); + }).collect(Collectors.toList()); + } + return Collections.emptyList(); + } + + public static List getMethodsForLeftSideOfPropertyExpression(Expression node, + ASTContext context) { + ClassNode classNode = getTypeOfNode(node, context); + if (classNode != null) { + boolean statics = node instanceof ClassExpression; + return classNode.getMethods().stream().filter(methodNode -> { + return statics ? methodNode.isStatic() : !methodNode.isStatic(); + }).collect(Collectors.toList()); + } + return Collections.emptyList(); + } + + public static ClassNode getTypeOfNode(ASTNode node, ASTContext context) { + if (node instanceof BinaryExpression) { + BinaryExpression binaryExpr = (BinaryExpression) node; + Expression leftExpr = binaryExpr.getLeftExpression(); + if (binaryExpr.getOperation().getText().equals("[") && leftExpr.getType().isArray()) { + return leftExpr.getType().getComponentType(); + } + } else if (node instanceof ClassExpression) { + ClassExpression expression = (ClassExpression) node; + // This means it's an expression like this: SomeClass.someProp + return expression.getType(); + } else if (node instanceof ConstructorCallExpression) { + ConstructorCallExpression expression = (ConstructorCallExpression) node; + return expression.getType(); + } else if (node instanceof MethodCallExpression) { + MethodCallExpression expression = (MethodCallExpression) node; + MethodNode methodNode = GroovyASTUtils.getMethodFromCallExpression(expression, context); + if (methodNode != null) { + return methodNode.getReturnType(); + } + return expression.getType(); + } else if (node instanceof StaticMethodCallExpression expr) { + var gameObjectName = getAccessedGameObjectName(expr); + if (gameObjectName != null) { + return ClassHelper.makeCached(GameObjectHandlerManager.getReturnTypeOf(gameObjectName)); + } + + MethodNode methodNode = GroovyASTUtils.getMethodFromCallExpression(expr, context); + if (methodNode != null) { + return methodNode.getReturnType(); + } + return expr.getType(); + } else if (node instanceof PropertyExpression) { + PropertyExpression expression = (PropertyExpression) node; + + PropertyNode propNode = GroovyASTUtils.getPropertyFromExpression(expression, context); + if (propNode != null) { + return getTypeOfNode(propNode, context); + } + return expression.getType(); + } else if (node instanceof Variable) { + Variable var = (Variable) node; + if (var.getName().equals("this")) { + ClassNode enclosingClass = (ClassNode) getEnclosingNodeOfType(node, ClassNode.class, context); + if (enclosingClass != null) { + return enclosingClass; + } + } else if (var.isDynamicTyped()) { + ASTNode defNode = GroovyASTUtils.getDefinition(node, false, context); + if (defNode instanceof Variable) { + Variable defVar = (Variable) defNode; + if (defVar.hasInitialExpression()) { + return getTypeOfNode(defVar.getInitialExpression(), context); + } else if (!defVar.isDynamicTyped()) { + return defVar.getType(); + } else { + ASTNode declNode = context.getVisitor().getParent(defNode); + if (declNode instanceof DeclarationExpression) { + DeclarationExpression decl = (DeclarationExpression) declNode; + return getTypeOfNode(decl.getRightExpression(), context); + } + } + } + } + if (var.getOriginType() != null) { + return var.getOriginType(); + } + } + if (node instanceof Expression) { + Expression expression = (Expression) node; + return expression.getType(); + } + return null; + } + + public static List getMethodOverloadsFromCallExpression(MethodCall node, ASTContext context) { + if (node instanceof MethodCallExpression) { + MethodCallExpression methodCallExpr = (MethodCallExpression) node; + ClassNode leftType = getTypeOfNode(methodCallExpr.getObjectExpression(), context); + if (leftType != null) { + return leftType.getMethods(methodCallExpr.getMethod().getText()); + } + } else if (node instanceof ConstructorCallExpression) { + ConstructorCallExpression constructorCallExpr = (ConstructorCallExpression) node; + ClassNode constructorType = constructorCallExpr.getType(); + if (constructorType != null) { + return constructorType.getDeclaredConstructors().stream().map(constructor -> (MethodNode) constructor) + .collect(Collectors.toList()); + } + } else if (node instanceof StaticMethodCallExpression staticMethodCallExpression) { + var ownerType = staticMethodCallExpression.getOwnerType(); + if (ownerType != null) { + return ownerType.getMethods(staticMethodCallExpression.getMethod()); + } + } + return Collections.emptyList(); + } + + public static MethodNode getMethodFromCallExpression(MethodCall node, ASTContext context) { + return getMethodFromCallExpression(node, context, -1); + } + + public static MethodNode getMethodFromCallExpression(MethodCall node, ASTContext context, int argIndex) { + List possibleMethods = getMethodOverloadsFromCallExpression(node, context); + if (!possibleMethods.isEmpty() && node.getArguments() instanceof ArgumentListExpression) { + ArgumentListExpression actualArguments = (ArgumentListExpression) node.getArguments(); + MethodNode foundMethod = possibleMethods.stream().max(new Comparator() { + public int compare(MethodNode m1, MethodNode m2) { + Parameter[] p1 = m1.getParameters(); + Parameter[] p2 = m2.getParameters(); + int m1Value = calculateArgumentsScore(p1, actualArguments, argIndex); + int m2Value = calculateArgumentsScore(p2, actualArguments, argIndex); + if (m1Value > m2Value) { + return 1; + } else if (m1Value < m2Value) { + return -1; + } + return 0; + } + }).orElse(null); + return foundMethod; + } + return null; + } + + private static int calculateArgumentsScore(Parameter[] parameters, ArgumentListExpression arguments, int argIndex) { + int score = 0; + int paramCount = parameters.length; + int expressionsCount = arguments.getExpressions().size(); + int argsCount = expressionsCount; + if (argIndex >= argsCount) { + argsCount = argIndex + 1; + } + int minCount = Math.min(paramCount, argsCount); + if (minCount == 0 && paramCount == argsCount) { + score++; + } + for (int i = 0; i < minCount; i++) { + ClassNode argType = (i < expressionsCount) ? arguments.getExpression(i).getType() : null; + ClassNode paramType = (i < paramCount) ? parameters[i].getType() : null; + if (argType != null && paramType != null) { + if (argType.equals(paramType)) { + // equal types are preferred + score += 1000; + } else if (argType.isDerivedFrom(paramType)) { + // subtypes are nice, but less important + score += 100; + } else { + // if a type doesn't match at all, it's not worth much + score++; + } + } else if (paramType != null) { + // extra parameters are like a type not matching + score++; + } + } + return score; + } + + public static Range findAddImportRange(ASTNode offsetNode, ASTContext context) { + ModuleNode moduleNode = (ModuleNode) GroovyASTUtils.getEnclosingNodeOfType(offsetNode, ModuleNode.class, + context); + if (moduleNode == null) { + return new Range(new Position(0, 0), new Position(0, 0)); + } + ASTNode afterNode = null; + if (afterNode == null) { + List importNodes = moduleNode.getImports(); + if (importNodes.size() > 0) { + afterNode = importNodes.get(importNodes.size() - 1); + } + } + if (afterNode == null) { + afterNode = moduleNode.getPackage(); + } + if (afterNode == null) { + return new Range(new Position(0, 0), new Position(0, 0)); + } + Range nodeRange = GroovyLanguageServerUtils.astNodeToRange(afterNode); + if (nodeRange == null) { + return new Range(new Position(0, 0), new Position(0, 0)); + } + Position position = new Position(nodeRange.getEnd().getLine() + 1, 0); + return new Range(position, position); + } +} \ No newline at end of file diff --git a/src/main/java/net/prominic/groovyls/compiler/util/GroovyReflectionUtils.java b/src/main/java/net/prominic/groovyls/compiler/util/GroovyReflectionUtils.java new file mode 100644 index 000000000..17b1ee162 --- /dev/null +++ b/src/main/java/net/prominic/groovyls/compiler/util/GroovyReflectionUtils.java @@ -0,0 +1,35 @@ +package net.prominic.groovyls.compiler.util; + +import com.cleanroommc.groovyscript.sandbox.security.GroovySecurityManager; +import com.google.common.collect.Iterators; +import io.github.classgraph.MethodInfo; +import net.prominic.groovyls.compiler.ast.ASTContext; +import org.codehaus.groovy.ast.MethodNode; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Optional; + +public class GroovyReflectionUtils { + + public static Optional resolveMethodFromMethodNode(MethodNode methodNode, ASTContext context) { + return Arrays.stream(methodNode.getDeclaringClass().getTypeClass().getMethods()) + .filter(GroovySecurityManager.INSTANCE::isValid) + .filter(method -> method.getName().equals(methodNode.getName()) && + method.getParameterTypes().length == methodNode.getParameters().length && + Iterators.elementsEqual(Arrays.stream(method.getParameterTypes()).iterator(), + Arrays.stream(methodNode.getParameters()).map(parameter -> parameter.getType().getTypeClass()).iterator())) + .findFirst(); + } + + public static Optional resolveMethodFromMethodInfo(MethodInfo methodInfo, ASTContext context) { + return Arrays.stream(methodInfo.getClassInfo().loadClass().getMethods()) + .filter(GroovySecurityManager.INSTANCE::isValid) + .filter(method -> method.getName().equals(methodInfo.getName()) && + method.getParameterTypes().length == methodInfo.getParameterInfo().length && + Iterators.elementsEqual(Arrays.stream(method.getParameterTypes()).iterator(), + Arrays.stream(methodInfo.getParameterInfo()).map(parameter -> context.getLanguageServerContext().getScanResult() + .loadClass(parameter.getTypeSignatureOrTypeDescriptor().toString(), true)).iterator())) + .findFirst(); + } +} diff --git a/src/main/java/net/prominic/groovyls/config/CompilationUnitFactory.java b/src/main/java/net/prominic/groovyls/config/CompilationUnitFactory.java new file mode 100644 index 000000000..02e380342 --- /dev/null +++ b/src/main/java/net/prominic/groovyls/config/CompilationUnitFactory.java @@ -0,0 +1,143 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2022 Prominic.NET, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License +// +// Author: Prominic.NET, Inc. +// No warranty of merchantability or fitness of any kind. +// Use this software at your own risk. +//////////////////////////////////////////////////////////////////////////////// +package net.prominic.groovyls.config; + +import com.cleanroommc.groovyscript.GroovyScript; +import net.prominic.groovyls.compiler.ILanguageServerContext; +import net.prominic.groovyls.compiler.control.GroovyLSCompilationUnit; +import net.prominic.groovyls.util.FileContentsTracker; +import org.codehaus.groovy.control.SourceUnit; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class CompilationUnitFactory extends CompilationUnitFactoryBase { + + protected static final String FILE_EXTENSION_GROOVY = ".groovy"; + private final ILanguageServerContext languageServerContext; + + protected GroovyLSCompilationUnit compilationUnit; + + public CompilationUnitFactory(ILanguageServerContext languageServerContext) { + this.languageServerContext = languageServerContext; + } + + @Override + public void invalidateCompilationUnit() { + super.invalidateCompilationUnit(); + compilationUnit = null; + } + + public GroovyLSCompilationUnit create(Path workspaceRoot, @Nullable URI context) { + if (config == null) { + config = getConfiguration(); + } + + if (classLoader == null) { + classLoader = getClassLoader(); + } + + Set changedUris = null; + if (compilationUnit == null) { + compilationUnit = new GroovyLSCompilationUnit(config, null, classLoader, languageServerContext); + // we don't care about changed URIs if there's no compilation unit yet + } else { + changedUris = languageServerContext.getFileContentsTracker().getChangedURIs(); + } + + var fileContentsTracker = languageServerContext.getFileContentsTracker(); + + if (changedUris != null && !changedUris.isEmpty()) { + compilationUnit.setClassLoader(classLoader); + final Set urisToRemove = changedUris; + List sourcesToRemove = new ArrayList<>(); + compilationUnit.iterator().forEachRemaining(sourceUnit -> { + URI uri = sourceUnit.getSource().getURI(); + if (urisToRemove.contains(uri)) { + sourcesToRemove.add(sourceUnit); + } + }); + // if an URI has changed, we remove it from the compilation unit so + // that a new version can be built from the updated source file + compilationUnit.removeSources(sourcesToRemove); + } + + if (workspaceRoot != null) { + addDirectoryToCompilationUnit(workspaceRoot, compilationUnit, fileContentsTracker, changedUris); + } else { + final Set urisToAdd = changedUris; + fileContentsTracker.getOpenURIs().forEach(uri -> { + // if we're only tracking changes, skip all files that haven't + // actually changed + if (urisToAdd != null && !urisToAdd.contains(uri)) { + return; + } + String contents = fileContentsTracker.getContents(uri); + addOpenFileToCompilationUnit(uri, contents, compilationUnit); + }); + } + + return compilationUnit; + } + + protected void addDirectoryToCompilationUnit(Path dirPath, GroovyLSCompilationUnit compilationUnit, + FileContentsTracker fileContentsTracker, Set changedUris) { + try { + if (Files.exists(dirPath)) { + Files.walk(dirPath).forEach((filePath) -> { + if (!filePath.toString().endsWith(FILE_EXTENSION_GROOVY)) { + return; + } + URI fileURI = filePath.toUri(); + if (!fileContentsTracker.isOpen(fileURI)) { + File file = filePath.toFile(); + if (file.isFile()) { + if (changedUris == null || changedUris.contains(fileURI)) { + compilationUnit.addSource(file); + } + } + } + }); + } + + } catch (IOException e) { + GroovyScript.LOGGER.error("Failed to walk directory for source files: {}", dirPath); + } + fileContentsTracker.getOpenURIs().forEach(uri -> { + Path openPath = Paths.get(uri); + if (!openPath.normalize().startsWith(dirPath.normalize())) { + return; + } + if (changedUris != null && !changedUris.contains(uri)) { + return; + } + String contents = fileContentsTracker.getContents(uri); + addOpenFileToCompilationUnit(uri, contents, compilationUnit); + }); + } +} \ No newline at end of file diff --git a/src/main/java/net/prominic/groovyls/config/CompilationUnitFactoryBase.java b/src/main/java/net/prominic/groovyls/config/CompilationUnitFactoryBase.java new file mode 100644 index 000000000..5b76c4a32 --- /dev/null +++ b/src/main/java/net/prominic/groovyls/config/CompilationUnitFactoryBase.java @@ -0,0 +1,95 @@ +package net.prominic.groovyls.config; + +import groovy.lang.GroovyClassLoader; +import net.prominic.groovyls.compiler.control.GroovyLSCompilationUnit; +import net.prominic.groovyls.compiler.control.io.StringReaderSourceWithURI; +import org.codehaus.groovy.control.CompilerConfiguration; +import org.codehaus.groovy.control.SourceUnit; + +import java.io.File; +import java.net.URI; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public abstract class CompilationUnitFactoryBase implements ICompilationUnitFactory { + + protected CompilerConfiguration config; + protected GroovyClassLoader classLoader; + protected List additionalClasspathList; + + public List getAdditionalClasspathList() { + return additionalClasspathList; + } + + public void setAdditionalClasspathList(List additionalClasspathList) { + this.additionalClasspathList = additionalClasspathList; + invalidateCompilationUnit(); + } + + public void invalidateCompilationUnit() { + config = null; + classLoader = null; + } + + protected GroovyClassLoader getClassLoader() { + return new GroovyClassLoader(ClassLoader.getSystemClassLoader().getParent(), config, true); + } + + protected CompilerConfiguration getConfiguration() { + CompilerConfiguration config = new CompilerConfiguration(); + + Map optimizationOptions = new HashMap<>(); + optimizationOptions.put(CompilerConfiguration.GROOVYDOC, true); + config.setOptimizationOptions(optimizationOptions); + + List classpathList = new ArrayList<>(); + getClasspathList(classpathList); + config.setClasspathList(classpathList); + + return config; + } + + protected void getClasspathList(List result) { + if (additionalClasspathList == null) { + return; + } + + for (String entry : additionalClasspathList) { + boolean mustBeDirectory = false; + if (entry.endsWith("*")) { + entry = entry.substring(0, entry.length() - 1); + mustBeDirectory = true; + } + + File file = new File(entry); + if (!file.exists()) { + continue; + } + if (file.isDirectory()) { + for (File child : file.listFiles()) { + if (!child.getName().endsWith(".jar") || !child.isFile()) { + continue; + } + result.add(child.getPath()); + } + } else if (!mustBeDirectory && file.isFile()) { + if (file.getName().endsWith(".jar")) { + result.add(entry); + } + } + } + } + + protected void addOpenFileToCompilationUnit(URI uri, String contents, GroovyLSCompilationUnit compilationUnit) { + Path filePath = Paths.get(uri); + SourceUnit sourceUnit = new SourceUnit(filePath.toString(), + new StringReaderSourceWithURI(contents, uri, compilationUnit.getConfiguration()), + compilationUnit.getConfiguration(), compilationUnit.getClassLoader(), + compilationUnit.getErrorCollector()); + compilationUnit.addSource(sourceUnit); + } +} diff --git a/src/main/java/net/prominic/groovyls/config/ICompilationUnitFactory.java b/src/main/java/net/prominic/groovyls/config/ICompilationUnitFactory.java new file mode 100644 index 000000000..231be27bc --- /dev/null +++ b/src/main/java/net/prominic/groovyls/config/ICompilationUnitFactory.java @@ -0,0 +1,44 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2022 Prominic.NET, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License +// +// Author: Prominic.NET, Inc. +// No warranty of merchantability or fitness of any kind. +// Use this software at your own risk. +//////////////////////////////////////////////////////////////////////////////// +package net.prominic.groovyls.config; + +import net.prominic.groovyls.compiler.control.GroovyLSCompilationUnit; +import org.jetbrains.annotations.Nullable; + +import java.net.URI; +import java.nio.file.Path; +import java.util.List; + +public interface ICompilationUnitFactory { + /** + * If this factory would normally reuse an existing compilation unit, forces + * the creation of a new one. + */ + public void invalidateCompilationUnit(); + + public List getAdditionalClasspathList(); + + public void setAdditionalClasspathList(List classpathList); + + /** + * Returns a compilation unit. + */ + GroovyLSCompilationUnit create(Path workspaceRoot, @Nullable URI context); +} \ No newline at end of file diff --git a/src/main/java/net/prominic/groovyls/providers/CompletionProvider.java b/src/main/java/net/prominic/groovyls/providers/CompletionProvider.java new file mode 100644 index 000000000..919b22d78 --- /dev/null +++ b/src/main/java/net/prominic/groovyls/providers/CompletionProvider.java @@ -0,0 +1,624 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2022 Prominic.NET, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License +// +// Author: Prominic.NET, Inc. +// No warranty of merchantability or fitness of any kind. +// Use this software at your own risk. +//////////////////////////////////////////////////////////////////////////////// +package net.prominic.groovyls.providers; + +import com.cleanroommc.groovyscript.gameobjects.GameObjectHandlerManager; +import com.cleanroommc.groovyscript.server.Completions; +import io.github.classgraph.*; +import net.prominic.groovyls.compiler.ast.ASTContext; +import net.prominic.groovyls.compiler.util.GroovyASTUtils; +import net.prominic.groovyls.compiler.util.GroovyReflectionUtils; +import net.prominic.groovyls.util.CompletionItemFactory; +import net.prominic.groovyls.util.GroovyLanguageServerUtils; +import net.prominic.groovyls.util.URIUtils; +import org.codehaus.groovy.ast.*; +import org.codehaus.groovy.ast.expr.*; +import org.codehaus.groovy.ast.stmt.BlockStatement; +import org.codehaus.groovy.ast.stmt.Statement; +import org.eclipse.lsp4j.*; +import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.jetbrains.annotations.NotNull; + +import java.net.URI; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +public class CompletionProvider { + + private final ASTContext astContext; + //private int maxItemCount = 1000; + private boolean isIncomplete = false; + + public CompletionProvider(ASTContext astContext) { + this.astContext = astContext; + } + + public CompletableFuture, CompletionList>> provideCompletion( + TextDocumentIdentifier textDocument, Position position, CompletionContext context) { + URI uri = URIUtils.toUri(textDocument.getUri()); + + Completions items = new Completions(1000); + + ASTNode offsetNode = astContext.getVisitor().getNodeAtLineAndColumn(uri, position.getLine(), position.getCharacter()); + if (offsetNode != null) { + populateItemsFromNode(position, offsetNode, items); + } + populateKeywords(items); + + return CompletableFuture.completedFuture(items.getResult(this.isIncomplete)); + } + + private void populateKeywords(Completions items) { + items.addAll(new String[]{"def", "assert", "if", "for", "else", "while", "switch", "case", "break", "continue", "return", + "transient", "import", "class", "extends", "implements", "enum", "try", "catch", "finally", "throw", "new", "in", "as", + "instanceof", "super", "this", "null", "true", "false", "void", "byte", "short", "int", "long", "float", "double", "boolean", + "private", "public", "protected"}, + s -> { + var item = new CompletionItem(s); + item.setKind(CompletionItemKind.Keyword); + item.setSortText("zzz" + s); + return item; + }); + } + + private void populateItemsFromNode(Position position, ASTNode offsetNode, Completions items) { + ASTNode parentNode = astContext.getVisitor().getParent(offsetNode); + + if (offsetNode instanceof PropertyExpression) { + populateItemsFromPropertyExpression((PropertyExpression) offsetNode, position, items); + } else if (parentNode instanceof PropertyExpression) { + populateItemsFromPropertyExpression((PropertyExpression) parentNode, position, items); + } else if (offsetNode instanceof MethodCallExpression) { + populateItemsFromMethodCallExpression((MethodCallExpression) offsetNode, position, items); + } else if (offsetNode instanceof ConstructorCallExpression) { + populateItemsFromConstructorCallExpression((ConstructorCallExpression) offsetNode, position, items); + } else if (offsetNode instanceof VariableExpression) { + populateItemsFromVariableExpression((VariableExpression) offsetNode, position, items); + } else if (parentNode instanceof MethodCallExpression) { + populateItemsFromMethodCallExpression((MethodCallExpression) parentNode, position, items); + } else if (offsetNode instanceof ImportNode) { + populateItemsFromImportNode((ImportNode) offsetNode, position, items); + } else if (offsetNode instanceof ClassNode) { + populateItemsFromClassNode((ClassNode) offsetNode, position, items); + } else if (offsetNode instanceof MethodNode) { + populateItemsFromScope(offsetNode, "", items); + } else if (offsetNode instanceof Statement) { + populateItemsFromScope(offsetNode, "", items); + } else if (offsetNode instanceof ClosureExpression) { + populateItemsFromScope(offsetNode, "", items); + } else if (offsetNode instanceof StaticMethodCallExpression) { + populateItemsFromStaticMethodCallExpression((StaticMethodCallExpression) offsetNode, position, items); + } else if (offsetNode instanceof ConstantExpression) { + populateItemsFromConstantExpression((ConstantExpression) offsetNode, parentNode, items); + } + } + + private void populateItemsFromConstantExpression(ConstantExpression node, ASTNode parent, Completions items) { + if (node.getType().getName().equals(String.class.getName())) { + ASTNode parentParent = astContext.getVisitor().getParent(parent); + if (parentParent instanceof StaticMethodCallExpression expr && + expr.getOwnerType().getName().equals(GameObjectHandlerManager.class.getName()) && + expr.getMethod().equals("getGameObject") && + expr.getArguments() instanceof ArgumentListExpression args && !args.getExpressions().isEmpty() && + args.getExpression(0) instanceof ConstantExpression expr1 && expr1.getValue() instanceof String name && + GameObjectHandlerManager.hasGameObjectHandler(name)) { + int index = -1; + if (args.getExpressions().size() > 1) { + for (int i = 1; i < args.getExpressions().size(); i++) { + if (args.getExpression(i) == node) { + index = i - 1; + break; + } + } + } + GameObjectHandlerManager.provideCompletion(name, index, items); + } + } + } + + private void populateItemsFromStaticMethodCallExpression(StaticMethodCallExpression methodCallExpr, Position position, Completions items) { + Set existingNames = new HashSet<>(); + + if (methodCallExpr.getOwnerType().getTypeClass().equals(GameObjectHandlerManager.class) && methodCallExpr.getMethod().equals("getGameObject")) { + // expression like item() + + var argumentsExpr = methodCallExpr.getArguments(); + if (argumentsExpr instanceof ArgumentListExpression) { + var firstArgumentExpr = ((ArgumentListExpression) argumentsExpr).getExpression(0); + + if (firstArgumentExpr instanceof ConstantExpression) { + var memberNamePrefix = ((ConstantExpression) firstArgumentExpr).getValue().toString(); + + populateItemsFromGameObjects(memberNamePrefix, existingNames, items); + } + } + } + + populateItemsFromGlobalScope(methodCallExpr.getMethod(), existingNames, items); + } + + private static void populateItemsFromGameObjects(String memberNamePrefix, + Set existingNames, Completions items) { + GameObjectHandlerManager.getGameObjectHandlers().stream() + .filter(handler -> { + if (handler.getName().startsWith(memberNamePrefix) && !existingNames.contains(handler.getName())) { + existingNames.add(handler.getName()); + return true; + } + return false; + }).forEach(handler -> { + for (Class[] paramTypes : handler.getParamTypes()) { + var completionItem = CompletionItemFactory.createCompletion(CompletionItemKind.Method, handler.getName()); + completionItem.setDetail("(global scope)"); + StringBuilder builder = new StringBuilder().append('('); + for (int i = 0; i < paramTypes.length; i++) { + var parameter = paramTypes[i]; + builder.append(parameter.getSimpleName()); + if (i < paramTypes.length - 1) { + builder.append(","); + } + } + builder.append(") -> "); + builder.append(handler.getReturnType().getSimpleName()); + CompletionItemLabelDetails details = new CompletionItemLabelDetails(); + details.setDetail(builder.toString()); + completionItem.setLabelDetails(details); + items.add(completionItem); + } + }); + } + + private void populateItemsFromPropertyExpression(PropertyExpression propExpr, Position position, + Completions items) { + Range propertyRange = GroovyLanguageServerUtils.astNodeToRange(propExpr.getProperty()); + if (propertyRange == null) { + return; + } + String memberName = getMemberName(propExpr.getPropertyAsString(), propertyRange, position); + populateItemsFromExpression(propExpr.getObjectExpression(), memberName, items); + } + + private void populateItemsFromMethodCallExpression(MethodCallExpression methodCallExpr, Position position, + Completions items) { + Range methodRange = GroovyLanguageServerUtils.astNodeToRange(methodCallExpr.getMethod()); + if (methodRange == null) { + return; + } + String memberName = getMemberName(methodCallExpr.getMethodAsString(), methodRange, position); + populateItemsFromExpression(methodCallExpr.getObjectExpression(), memberName, items); + } + + private void populateItemsFromImportNode(ImportNode importNode, Position position, Completions items) { + Range importRange = GroovyLanguageServerUtils.astNodeToRange(importNode); + if (importRange == null) { + return; + } + // skip the "import " at the beginning + importRange.setStart(new Position(importRange.getEnd().getLine(), + importRange.getEnd().getCharacter() - importNode.getType().getName().length())); + String importText = getMemberName(importNode.getType().getName(), importRange, position); + + ModuleNode enclosingModule = (ModuleNode) GroovyASTUtils.getEnclosingNodeOfType(importNode, ModuleNode.class, + astContext); + String enclosingPackageName = enclosingModule.getPackageName(); + List importNames = enclosingModule.getImports().stream() + .map(ImportNode::getClassName).collect(Collectors.toList()); + + List localClassItems = astContext.getVisitor().getClassNodes().stream().filter(classNode -> { + String packageName = classNode.getPackageName(); + if (packageName == null || packageName.length() == 0 || packageName.equals(enclosingPackageName)) { + return false; + } + String className = classNode.getName(); + String classNameWithoutPackage = classNode.getNameWithoutPackage(); + if (!className.startsWith(importText) && !classNameWithoutPackage.startsWith(importText)) { + return false; + } + if (importNames.contains(className)) { + return false; + } + return true; + }).map(classNode -> { + CompletionItem item = CompletionItemFactory.createCompletion(classNode, classNode.getName(), astContext); + item.setTextEdit(Either.forLeft(new TextEdit(importRange, classNode.getName()))); + if (classNode.getNameWithoutPackage().startsWith(importText)) { + item.setSortText(classNode.getNameWithoutPackage()); + } + return item; + }).collect(Collectors.toList()); + items.addAll(localClassItems); + + List classes = astContext.getLanguageServerContext().getScanResult().getAllClasses(); + List packages = astContext.getLanguageServerContext().getScanResult().getPackageInfo(); + + List packageItems = packages.stream().filter(packageInfo -> { + String packageName = packageInfo.getName(); + if (packageName.startsWith(importText)) { + return true; + } + return false; + }).map(packageInfo -> { + CompletionItem item = CompletionItemFactory.createCompletion(CompletionItemKind.Module, packageInfo.getName()); + item.setTextEdit(Either.forLeft(new TextEdit(importRange, packageInfo.getName()))); + return item; + }).collect(Collectors.toList()); + items.addAll(packageItems); + + List classItems = classes.stream().filter(classInfo -> { + String packageName = classInfo.getPackageName(); + if (packageName == null || packageName.length() == 0 || packageName.equals(enclosingPackageName)) { + return false; + } + String className = classInfo.getName(); + String classNameWithoutPackage = classInfo.getSimpleName(); + if (!className.startsWith(importText) && !classNameWithoutPackage.startsWith(importText)) { + return false; + } + if (importNames.contains(className)) { + return false; + } + return true; + }).map(classInfo -> { + CompletionItem item = CompletionItemFactory.createCompletion(classInfoToCompletionItemKind(classInfo), classInfo.getName()); + + item.setTextEdit(Either.forLeft(new TextEdit(importRange, classInfo.getName()))); + if (classInfo.getSimpleName().startsWith(importText)) { + item.setSortText(classInfo.getSimpleName()); + } + return item; + }).collect(Collectors.toList()); + items.addAll(classItems); + } + + private void populateItemsFromClassNode(ClassNode classNode, Position position, Completions items) { + ASTNode parentNode = astContext.getVisitor().getParent(classNode); + if (!(parentNode instanceof ClassNode)) { + return; + } + ClassNode parentClassNode = (ClassNode) parentNode; + Range classRange = GroovyLanguageServerUtils.astNodeToRange(classNode); + 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); + } else if (Arrays.asList(parentClassNode.getUnresolvedInterfaces()).contains(classNode)) { + populateTypes(classNode, className, new HashSet<>(), false, true, false, items); + } + } + + private void populateItemsFromConstructorCallExpression(ConstructorCallExpression constructorCallExpr, + Position position, Completions items) { + Range typeRange = GroovyLanguageServerUtils.astNodeToRange(constructorCallExpr.getType()); + if (typeRange == null) { + return; + } + String typeName = getMemberName(constructorCallExpr.getType().getNameWithoutPackage(), typeRange, position); + populateTypes(constructorCallExpr, typeName, new HashSet<>(), true, false, false, items); + } + + private void populateItemsFromVariableExpression(VariableExpression varExpr, Position position, + Completions items) { + Range varRange = GroovyLanguageServerUtils.astNodeToRange(varExpr); + if (varRange == null) { + return; + } + String memberName = getMemberName(varExpr.getName(), varRange, position); + populateItemsFromScope(varExpr, memberName, items); + } + + private void populateItemsFromPropertiesAndFields(List properties, List fields, + String memberNamePrefix, Set existingNames, Completions items) { + List propItems = properties.stream().filter(property -> { + String name = property.getName(); + // sometimes, a property and a field will have the same name + if (name.startsWith(memberNamePrefix) && !existingNames.contains(name)) { + existingNames.add(name); + return true; + } + return false; + }).map(property -> { + CompletionItem item = CompletionItemFactory.createCompletion(property, property.getName(), astContext); + + if (!property.isDynamicTyped()) { + item.setDetail(property.getType().getNameWithoutPackage()); + } + return item; + }).collect(Collectors.toList()); + items.addAll(propItems); + List fieldItems = fields.stream().filter(field -> { + String name = field.getName(); + // sometimes, a property and a field will have the same name + if (name.startsWith(memberNamePrefix) && !existingNames.contains(name)) { + existingNames.add(name); + return true; + } + return false; + }).map(field -> { + CompletionItem item = CompletionItemFactory.createCompletion(field, field.getName(), astContext); + + if (!field.isDynamicTyped()) { + item.setDetail(field.getType().getNameWithoutPackage()); + } + return item; + }).collect(Collectors.toList()); + items.addAll(fieldItems); + } + + private void populateItemsFromMethods(List methods, String memberNamePrefix, Set existingNames, + Completions items) { + List methodItems = methods.stream() + .filter(method -> { + String methodName = method.getName(); + // overloads can cause duplicates + if (methodName.startsWith(memberNamePrefix) && !existingNames.contains(methodName)) { + existingNames.add(methodName); + return !method.getDeclaringClass().isResolved() || GroovyReflectionUtils.resolveMethodFromMethodNode(method, astContext).isPresent(); + } + return false; + }).map(method -> { + CompletionItem item = CompletionItemFactory.createCompletion(method, method.getName(), astContext); + + var details = getMethodNodeDetails(method); + item.setLabelDetails(details); + + return item; + }).collect(Collectors.toList()); + items.addAll(methodItems); + } + + @NotNull + private static CompletionItemLabelDetails getMethodNodeDetails(MethodNode method) { + var detailBuilder = new StringBuilder(); + detailBuilder.append("("); + var parameters = method.getParameters(); + for (int i = 0; i < parameters.length; i++) { + var parameter = parameters[i]; + detailBuilder.append(parameter.isDynamicTyped() ? "?" : parameter.getType().getNameWithoutPackage()); + if (i < parameters.length - 1) { + detailBuilder.append(","); + } + } + detailBuilder.append(") -> "); + detailBuilder.append(method.getReturnType().getNameWithoutPackage()); + + var details = new CompletionItemLabelDetails(); + details.setDetail(detailBuilder.toString()); + return details; + } + + @NotNull + private static CompletionItemLabelDetails getMethodInfoDetails(MethodInfo methodInfo) { + var detailBuilder = new StringBuilder(); + detailBuilder.append("("); + MethodParameterInfo[] info = methodInfo.getParameterInfo(); + for (int i = 0; i < info.length; i++) { + var parameterInfo = info[i]; + detailBuilder.append(parameterInfo.getTypeSignatureOrTypeDescriptor().toStringWithSimpleNames()); + if (i < info.length - 1) { + detailBuilder.append(","); + } + } + detailBuilder.append(") -> "); + detailBuilder.append(methodInfo.getTypeSignatureOrTypeDescriptor().getResultType().toStringWithSimpleNames()); + + var details = new CompletionItemLabelDetails(); + details.setDetail(detailBuilder.toString()); + return details; + } + + private void populateItemsFromExpression(Expression leftSide, String memberNamePrefix, Completions items) { + Set existingNames = new HashSet<>(); + + List properties = GroovyASTUtils.getPropertiesForLeftSideOfPropertyExpression(leftSide, astContext); + List fields = GroovyASTUtils.getFieldsForLeftSideOfPropertyExpression(leftSide, astContext); + populateItemsFromPropertiesAndFields(properties, fields, memberNamePrefix, existingNames, items); + + List methods = GroovyASTUtils.getMethodsForLeftSideOfPropertyExpression(leftSide, astContext); + populateItemsFromMethods(methods, memberNamePrefix, existingNames, items); + } + + private void populateItemsFromGlobalScope(String memberNamePrefix, + Set existingNames, List items) { + astContext.getLanguageServerContext().getSandbox().getBindings().forEach((variableName, value) -> { + if (!variableName.startsWith(memberNamePrefix) || existingNames.contains(variableName)) { + return; + } + existingNames.add(variableName); + + var item = CompletionItemFactory.createCompletion(CompletionItemKind.Variable, variableName); + + item.setDetail("(global scope)"); + + items.add(item); + }); + + List staticMethodItems = astContext.getLanguageServerContext().getSandbox().getStaticImports().stream() + .map(staticImport -> astContext.getLanguageServerContext().getScanResult().getClassInfo(staticImport.getName())) + .filter(Objects::nonNull) + .flatMap(classInfo -> classInfo.getMethodInfo().stream().filter(ClassMemberInfo::isStatic)) + .filter(methodInfo -> { + String methodName = methodInfo.getName(); + if (methodName.startsWith(memberNamePrefix) && !existingNames.contains(methodName)) { + existingNames.add(methodName); + return GroovyReflectionUtils.resolveMethodFromMethodInfo(methodInfo, astContext).isPresent(); + } + return false; + }) + .map(methodInfo -> { + var item = CompletionItemFactory.createCompletion(CompletionItemKind.Method, methodInfo.getName()); + + var details = getMethodInfoDetails(methodInfo); + item.setLabelDetails(details); + return item; + }) + .collect(Collectors.toList()); + items.addAll(staticMethodItems); + } + + private void populateItemsFromVariableScope(VariableScope variableScope, String memberNamePrefix, + Set existingNames, Completions items) { + populateItemsFromGameObjects(memberNamePrefix, existingNames, items); + populateItemsFromGlobalScope(memberNamePrefix, existingNames, items); + + List variableItems = variableScope.getDeclaredVariables().values().stream().filter(variable -> { + + String variableName = variable.getName(); + // overloads can cause duplicates + if (variableName.startsWith(memberNamePrefix) && !existingNames.contains(variableName)) { + existingNames.add(variableName); + return true; + } + return false; + }).map(variable -> { + var item = CompletionItemFactory.createCompletion((ASTNode) variable, variable.getName(), astContext); + + if (!variable.isDynamicTyped()) { + item.setDetail(variable.getType().getName()); + } + return item; + }).collect(Collectors.toList()); + items.addAll(variableItems); + } + + private void populateItemsFromScope(ASTNode node, String namePrefix, Completions items) { + Set existingNames = new HashSet<>(); + ASTNode current = node; + while (current != null) { + if (current instanceof ClassNode) { + ClassNode classNode = (ClassNode) current; + populateItemsFromPropertiesAndFields(classNode.getProperties(), classNode.getFields(), namePrefix, + existingNames, items); + populateItemsFromMethods(classNode.getMethods(), namePrefix, existingNames, items); + } else if (current instanceof MethodNode) { + MethodNode methodNode = (MethodNode) current; + populateItemsFromVariableScope(methodNode.getVariableScope(), namePrefix, existingNames, items); + } else if (current instanceof BlockStatement) { + BlockStatement block = (BlockStatement) current; + populateItemsFromVariableScope(block.getVariableScope(), namePrefix, existingNames, items); + } else if (current instanceof VariableExpression || current instanceof StaticMethodCallExpression) { + populateItemsFromGameObjects(namePrefix, existingNames, items); + populateItemsFromGlobalScope(namePrefix, existingNames, items); + } + current = astContext.getVisitor().getParent(current); + } + populateTypes(node, namePrefix, existingNames, items); + } + + private void populateTypes(ASTNode offsetNode, String namePrefix, Set existingNames, + Completions items) { + populateTypes(offsetNode, namePrefix, existingNames, true, true, true, items); + } + + private void populateTypes(ASTNode offsetNode, String namePrefix, Set existingNames, boolean includeClasses, + boolean includeInterfaces, boolean includeEnums, Completions items) { + Range addImportRange = GroovyASTUtils.findAddImportRange(offsetNode, astContext); + + ModuleNode enclosingModule = (ModuleNode) GroovyASTUtils.getEnclosingNodeOfType(offsetNode, ModuleNode.class, + astContext); + String enclosingPackageName = enclosingModule.getPackageName(); + List importNames = enclosingModule.getImports().stream().map(importNode -> importNode.getClassName()) + .collect(Collectors.toList()); + + List localClassItems = astContext.getVisitor().getClassNodes().stream().filter(classNode -> { + if (items.reachedLimit()) return false; + String classNameWithoutPackage = classNode.getNameWithoutPackage(); + String className = classNode.getName(); + if (classNameWithoutPackage.startsWith(namePrefix) && !existingNames.contains(className)) { + existingNames.add(className); + return true; + } + return false; + }).map(classNode -> { + String className = classNode.getName(); + String packageName = classNode.getPackageName(); + CompletionItem item = CompletionItemFactory.createCompletion(classNode, classNode.getNameWithoutPackage(), astContext); + item.setDetail(packageName); + if (packageName != null && !packageName.equals(enclosingPackageName) && !importNames.contains(className)) { + List additionalTextEdits = new ArrayList<>(); + TextEdit addImportEdit = createAddImportTextEdit(className, addImportRange); + additionalTextEdits.add(addImportEdit); + item.setAdditionalTextEdits(additionalTextEdits); + } + return item; + }).collect(Collectors.toList()); + items.addAll(localClassItems); + + List classes = astContext.getLanguageServerContext().getScanResult().getAllClasses(); + + List classItems = classes.stream().filter(classInfo -> { + if (items.reachedLimit()) return false; + String className = classInfo.getName(); + String classNameWithoutPackage = classInfo.getSimpleName(); + if (classNameWithoutPackage.startsWith(namePrefix) && !existingNames.contains(className)) { + existingNames.add(className); + return true; + } + return false; + }).map(classInfo -> { + String className = classInfo.getName(); + String packageName = classInfo.getPackageName(); + CompletionItem item = CompletionItemFactory.createCompletion(classInfoToCompletionItemKind(classInfo), classInfo.getSimpleName()); + item.setDetail(packageName); + if (packageName != null && !packageName.equals(enclosingPackageName) && !importNames.contains(className)) { + List additionalTextEdits = new ArrayList<>(); + TextEdit addImportEdit = createAddImportTextEdit(className, addImportRange); + additionalTextEdits.add(addImportEdit); + item.setAdditionalTextEdits(additionalTextEdits); + } + return item; + }).collect(Collectors.toList()); + items.addAll(classItems); + } + + private String getMemberName(String memberName, Range range, Position position) { + if (position.getLine() == range.getStart().getLine() + && position.getCharacter() > range.getStart().getCharacter()) { + int length = position.getCharacter() - range.getStart().getCharacter(); + if (length > 0 && length <= memberName.length()) { + return memberName.substring(0, length).trim(); + } + } + return ""; + } + + private CompletionItemKind classInfoToCompletionItemKind(ClassInfo classInfo) { + if (classInfo.isInterface()) { + return CompletionItemKind.Interface; + } + if (classInfo.isEnum()) { + return CompletionItemKind.Enum; + } + return CompletionItemKind.Class; + } + + private TextEdit createAddImportTextEdit(String className, Range range) { + TextEdit edit = new TextEdit(); + StringBuilder builder = new StringBuilder(); + builder.append("import "); + builder.append(className); + builder.append("\n"); + edit.setNewText(builder.toString()); + edit.setRange(range); + return edit; + } +} \ No newline at end of file diff --git a/src/main/java/net/prominic/groovyls/providers/DefinitionProvider.java b/src/main/java/net/prominic/groovyls/providers/DefinitionProvider.java new file mode 100644 index 000000000..fc4d4e701 --- /dev/null +++ b/src/main/java/net/prominic/groovyls/providers/DefinitionProvider.java @@ -0,0 +1,73 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2022 Prominic.NET, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License +// +// Author: Prominic.NET, Inc. +// No warranty of merchantability or fitness of any kind. +// Use this software at your own risk. +//////////////////////////////////////////////////////////////////////////////// +package net.prominic.groovyls.providers; + +import java.net.URI; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import net.prominic.groovyls.compiler.ast.ASTContext; +import net.prominic.groovyls.util.URIUtils; +import org.codehaus.groovy.ast.ASTNode; +import org.eclipse.lsp4j.Location; +import org.eclipse.lsp4j.LocationLink; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.TextDocumentIdentifier; +import org.eclipse.lsp4j.jsonrpc.messages.Either; + +import net.prominic.groovyls.compiler.ast.ASTNodeVisitor; +import net.prominic.groovyls.compiler.util.GroovyASTUtils; +import net.prominic.groovyls.util.GroovyLanguageServerUtils; + +public class DefinitionProvider { + + private final ASTContext astContext; + + public DefinitionProvider(ASTContext astContext) { + this.astContext = astContext; + } + + public CompletableFuture, List>> provideDefinition( + TextDocumentIdentifier textDocument, Position position) { + URI uri = URIUtils.toUri(textDocument.getUri()); + ASTNode offsetNode = astContext.getVisitor().getNodeAtLineAndColumn(uri, position.getLine(), position.getCharacter()); + if (offsetNode == null) { + return CompletableFuture.completedFuture(Either.forLeft(Collections.emptyList())); + } + + ASTNode definitionNode = GroovyASTUtils.getDefinition(offsetNode, true, astContext); + if (definitionNode == null || definitionNode.getLineNumber() == -1 || definitionNode.getColumnNumber() == -1) { + return CompletableFuture.completedFuture(Either.forLeft(Collections.emptyList())); + } + + URI definitionURI = astContext.getVisitor().getURI(definitionNode); + if (definitionURI == null) { + definitionURI = uri; + } + + Location location = GroovyLanguageServerUtils.astNodeToLocation(definitionNode, definitionURI); + if (location == null) { + return CompletableFuture.completedFuture(Either.forLeft(Collections.emptyList())); + } + + return CompletableFuture.completedFuture(Either.forLeft(Collections.singletonList(location))); + } +} \ No newline at end of file diff --git a/src/main/java/net/prominic/groovyls/providers/DocumentSymbolProvider.java b/src/main/java/net/prominic/groovyls/providers/DocumentSymbolProvider.java new file mode 100644 index 000000000..1b0f2a22f --- /dev/null +++ b/src/main/java/net/prominic/groovyls/providers/DocumentSymbolProvider.java @@ -0,0 +1,77 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2022 Prominic.NET, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License +// +// Author: Prominic.NET, Inc. +// No warranty of merchantability or fitness of any kind. +// Use this software at your own risk. +//////////////////////////////////////////////////////////////////////////////// +package net.prominic.groovyls.providers; + +import net.prominic.groovyls.compiler.ast.ASTContext; +import net.prominic.groovyls.compiler.util.GroovyASTUtils; +import net.prominic.groovyls.util.GroovyLanguageServerUtils; +import net.prominic.groovyls.util.URIUtils; +import org.codehaus.groovy.ast.*; +import org.eclipse.lsp4j.DocumentSymbol; +import org.eclipse.lsp4j.SymbolInformation; +import org.eclipse.lsp4j.TextDocumentIdentifier; +import org.eclipse.lsp4j.jsonrpc.messages.Either; + +import java.net.URI; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +public class DocumentSymbolProvider { + + private final ASTContext astContext; + + public DocumentSymbolProvider(ASTContext astContext) { + this.astContext = astContext; + } + + public CompletableFuture>> provideDocumentSymbols( + TextDocumentIdentifier textDocument) { + URI uri = URIUtils.toUri(textDocument.getUri()); + List nodes = astContext.getVisitor().getNodes(uri); + List> symbols = nodes.stream().filter(node -> { + return node instanceof ClassNode || node instanceof MethodNode || node instanceof FieldNode + || node instanceof PropertyNode; + }).map(node -> { + if (node instanceof ClassNode) { + ClassNode classNode = (ClassNode) node; + return GroovyLanguageServerUtils.astNodeToSymbolInformation(classNode, uri, null); + } + ClassNode classNode = (ClassNode) GroovyASTUtils.getEnclosingNodeOfType(node, ClassNode.class, astContext); + if (node instanceof MethodNode) { + MethodNode methodNode = (MethodNode) node; + return GroovyLanguageServerUtils.astNodeToSymbolInformation(methodNode, uri, classNode.getName()); + } + if (node instanceof PropertyNode) { + PropertyNode propNode = (PropertyNode) node; + return GroovyLanguageServerUtils.astNodeToSymbolInformation(propNode, uri, classNode.getName()); + } + if (node instanceof FieldNode) { + FieldNode fieldNode = (FieldNode) node; + return GroovyLanguageServerUtils.astNodeToSymbolInformation(fieldNode, uri, classNode.getName()); + } + // this should never happen + return null; + }).filter(symbolInformation -> symbolInformation != null).map(node -> { + return Either.forLeft(node); + }).collect(Collectors.toList()); + return CompletableFuture.completedFuture(symbols); + } +} \ No newline at end of file diff --git a/src/main/java/net/prominic/groovyls/providers/HoverProvider.java b/src/main/java/net/prominic/groovyls/providers/HoverProvider.java new file mode 100644 index 000000000..1f145ea97 --- /dev/null +++ b/src/main/java/net/prominic/groovyls/providers/HoverProvider.java @@ -0,0 +1,95 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2022 Prominic.NET, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License +// +// Author: Prominic.NET, Inc. +// No warranty of merchantability or fitness of any kind. +// Use this software at your own risk. +//////////////////////////////////////////////////////////////////////////////// +package net.prominic.groovyls.providers; + +import com.cleanroommc.groovyscript.GroovyScript; +import net.prominic.groovyls.compiler.ast.ASTContext; +import net.prominic.groovyls.compiler.util.GroovyASTUtils; +import net.prominic.groovyls.util.GroovyNodeToStringUtils; +import net.prominic.groovyls.util.URIUtils; +import org.codehaus.groovy.ast.*; +import org.eclipse.lsp4j.*; + +import java.net.URI; +import java.util.concurrent.CompletableFuture; + +public class HoverProvider { + + private final ASTContext astContext; + + public HoverProvider(ASTContext astContext) { + this.astContext = astContext; + } + + public CompletableFuture provideHover(TextDocumentIdentifier textDocument, Position position) { + URI uri = URIUtils.toUri(textDocument.getUri()); + ASTNode offsetNode = astContext.getVisitor().getNodeAtLineAndColumn(uri, position.getLine(), position.getCharacter()); + if (offsetNode == null) { + return CompletableFuture.completedFuture(null); + } + + ASTNode definitionNode = GroovyASTUtils.getDefinition(offsetNode, false, astContext); + if (definitionNode == null) { + return CompletableFuture.completedFuture(null); + } + + String content = getContent(definitionNode); + if (content == null) { + return CompletableFuture.completedFuture(null); + } + + String documentation = null; + if (definitionNode instanceof AnnotatedNode annotatedNode) { + documentation = astContext.getLanguageServerContext().getDocumentationFactory().getDocumentation(annotatedNode, astContext); + } + + StringBuilder contentsBuilder = new StringBuilder(); + contentsBuilder.append("```groovy\n"); + contentsBuilder.append(content); + contentsBuilder.append("\n```"); + if (documentation != null) { + contentsBuilder.append("\n\n---\n\n"); + contentsBuilder.append(documentation); + } + + MarkupContent contents = new MarkupContent(); + contents.setKind(MarkupKind.MARKDOWN); + contents.setValue(contentsBuilder.toString()); + Hover hover = new Hover(); + hover.setContents(contents); + return CompletableFuture.completedFuture(hover); + } + + private String getContent(ASTNode hoverNode) { + if (hoverNode instanceof ClassNode) { + ClassNode classNode = (ClassNode) hoverNode; + return GroovyNodeToStringUtils.classToString(classNode, astContext); + } else if (hoverNode instanceof MethodNode) { + MethodNode methodNode = (MethodNode) hoverNode; + return GroovyNodeToStringUtils.methodToString(methodNode, astContext); + } else if (hoverNode instanceof Variable) { + Variable varNode = (Variable) hoverNode; + return GroovyNodeToStringUtils.variableToString(varNode, astContext); + } else { + GroovyScript.LOGGER.warn("*** hover not available for node: {}", hoverNode); + } + return null; + } +} \ No newline at end of file diff --git a/src/main/java/net/prominic/groovyls/providers/ReferenceProvider.java b/src/main/java/net/prominic/groovyls/providers/ReferenceProvider.java new file mode 100644 index 000000000..dd79f132e --- /dev/null +++ b/src/main/java/net/prominic/groovyls/providers/ReferenceProvider.java @@ -0,0 +1,63 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2022 Prominic.NET, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License +// +// Author: Prominic.NET, Inc. +// No warranty of merchantability or fitness of any kind. +// Use this software at your own risk. +//////////////////////////////////////////////////////////////////////////////// +package net.prominic.groovyls.providers; + +import java.net.URI; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +import net.prominic.groovyls.compiler.ast.ASTContext; +import net.prominic.groovyls.util.URIUtils; +import org.codehaus.groovy.ast.ASTNode; +import org.eclipse.lsp4j.Location; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.TextDocumentIdentifier; + +import net.prominic.groovyls.compiler.ast.ASTNodeVisitor; +import net.prominic.groovyls.compiler.util.GroovyASTUtils; +import net.prominic.groovyls.util.GroovyLanguageServerUtils; + +public class ReferenceProvider { + + private final ASTContext astContext; + + public ReferenceProvider(ASTContext astContext) { + this.astContext = astContext; + } + + public CompletableFuture> provideReferences(TextDocumentIdentifier textDocument, + Position position) { + URI documentURI = URIUtils.toUri(textDocument.getUri()); + ASTNode offsetNode = astContext.getVisitor().getNodeAtLineAndColumn(documentURI, position.getLine(), position.getCharacter()); + if (offsetNode == null) { + return CompletableFuture.completedFuture(Collections.emptyList()); + } + + List references = GroovyASTUtils.getReferences(offsetNode, astContext); + List locations = references.stream().map(node -> { + URI uri = astContext.getVisitor().getURI(node); + return GroovyLanguageServerUtils.astNodeToLocation(node, uri); + }).filter(location -> location != null).collect(Collectors.toList()); + + return CompletableFuture.completedFuture(locations); + } +} \ No newline at end of file diff --git a/src/main/java/net/prominic/groovyls/providers/RenameProvider.java b/src/main/java/net/prominic/groovyls/providers/RenameProvider.java new file mode 100644 index 000000000..b97c644a6 --- /dev/null +++ b/src/main/java/net/prominic/groovyls/providers/RenameProvider.java @@ -0,0 +1,235 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2022 Prominic.NET, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License +// +// Author: Prominic.NET, Inc. +// No warranty of merchantability or fitness of any kind. +// Use this software at your own risk. +//////////////////////////////////////////////////////////////////////////////// +package net.prominic.groovyls.providers; + +import java.net.URI; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import net.prominic.groovyls.compiler.ast.ASTContext; +import net.prominic.groovyls.util.URIUtils; +import org.codehaus.groovy.ast.ASTNode; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.PropertyNode; +import org.codehaus.groovy.ast.expr.ConstantExpression; +import org.codehaus.groovy.ast.expr.VariableExpression; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.Range; +import org.eclipse.lsp4j.RenameFile; +import org.eclipse.lsp4j.RenameParams; +import org.eclipse.lsp4j.ResourceOperation; +import org.eclipse.lsp4j.TextDocumentEdit; +import org.eclipse.lsp4j.TextDocumentIdentifier; +import org.eclipse.lsp4j.TextEdit; +import org.eclipse.lsp4j.VersionedTextDocumentIdentifier; +import org.eclipse.lsp4j.WorkspaceEdit; +import org.eclipse.lsp4j.jsonrpc.messages.Either; + +import net.prominic.groovyls.compiler.ast.ASTNodeVisitor; +import net.prominic.groovyls.compiler.util.GroovyASTUtils; +import net.prominic.groovyls.util.FileContentsTracker; +import net.prominic.groovyls.util.GroovyLanguageServerUtils; +import net.prominic.lsp.utils.Ranges; + +public class RenameProvider { + + private final ASTContext astContext; + private FileContentsTracker files; + + public RenameProvider(ASTContext astContext, FileContentsTracker files) { + this.astContext = astContext; + this.files = files; + } + + public CompletableFuture provideRename(RenameParams renameParams) { + TextDocumentIdentifier textDocument = renameParams.getTextDocument(); + Position position = renameParams.getPosition(); + String newName = renameParams.getNewName(); + + Map> textEditChanges = new HashMap<>(); + List> documentChanges = new ArrayList<>(); + WorkspaceEdit workspaceEdit = new WorkspaceEdit(documentChanges); + + URI documentURI = URIUtils.toUri(textDocument.getUri()); + ASTNode offsetNode = astContext.getVisitor().getNodeAtLineAndColumn(documentURI, position.getLine(), position.getCharacter()); + if (offsetNode == null) { + return CompletableFuture.completedFuture(workspaceEdit); + } + + List references = GroovyASTUtils.getReferences(offsetNode, astContext); + references.forEach(node -> { + URI uri = astContext.getVisitor().getURI(node); + if (uri == null) { + uri = documentURI; + } + + String contents = getPartialNodeText(uri, node); + if (contents == null) { + // can't find the text? skip it + return; + } + Range range = GroovyLanguageServerUtils.astNodeToRange(node); + if (range == null) { + // can't find the range? skip it + return; + } + Position start = range.getStart(); + Position end = range.getEnd(); + end.setLine(start.getLine()); + end.setCharacter(start.getCharacter() + contents.length()); + + TextEdit textEdit = null; + if (node instanceof ClassNode) { + ClassNode classNode = (ClassNode) node; + textEdit = createTextEditToRenameClassNode(classNode, newName, contents, range); + if (textEdit != null && astContext.getVisitor().getParent(classNode) == null) { + String newURI = uri.toString(); + int slashIndex = newURI.lastIndexOf("/"); + int dotIndex = newURI.lastIndexOf("."); + newURI = newURI.substring(0, slashIndex + 1) + newName + newURI.substring(dotIndex); + + RenameFile renameFile = new RenameFile(); + renameFile.setOldUri(uri.toString()); + renameFile.setNewUri(newURI); + documentChanges.add(Either.forRight(renameFile)); + } + } else if (node instanceof MethodNode) { + MethodNode methodNode = (MethodNode) node; + textEdit = createTextEditToRenameMethodNode(methodNode, newName, contents, range); + } else if (node instanceof PropertyNode) { + PropertyNode propNode = (PropertyNode) node; + textEdit = createTextEditToRenamePropertyNode(propNode, newName, contents, range); + } else if (node instanceof ConstantExpression || node instanceof VariableExpression) { + textEdit = new TextEdit(); + textEdit.setNewText(newName); + textEdit.setRange(range); + } + if (textEdit == null) { + return; + } + + if (!textEditChanges.containsKey(uri.toString())) { + textEditChanges.put(uri.toString(), new ArrayList<>()); + } + List textEdits = textEditChanges.get(uri.toString()); + textEdits.add(textEdit); + }); + + for (String uri : textEditChanges.keySet()) { + List textEdits = textEditChanges.get(uri); + + VersionedTextDocumentIdentifier versionedIdentifier = new VersionedTextDocumentIdentifier(uri, null); + TextDocumentEdit textDocumentEdit = new TextDocumentEdit(versionedIdentifier, textEdits); + documentChanges.add(0, Either.forLeft(textDocumentEdit)); + } + + return CompletableFuture.completedFuture(workspaceEdit); + } + + private String getPartialNodeText(URI uri, ASTNode node) { + Range range = GroovyLanguageServerUtils.astNodeToRange(node); + if (range == null) { + return null; + } + String contents = files.getContents(uri); + if (contents == null) { + return null; + } + return Ranges.getSubstring(contents, range, 1); + } + + private TextEdit createTextEditToRenameClassNode(ClassNode classNode, String newName, String text, Range range) { + // the AST doesn't give us access to the name location, so we + // need to find it manually + String className = classNode.getNameWithoutPackage(); + int dollarIndex = className.indexOf('$'); + if (dollarIndex != 01) { + // it's an inner class, so remove the outer name prefix + className = className.substring(dollarIndex + 1); + } + + Pattern classPattern = Pattern.compile("(class\\s+)" + className + "\\b"); + Matcher classMatcher = classPattern.matcher(text); + if (!classMatcher.find()) { + // couldn't find the name! + return null; + } + String prefix = classMatcher.group(1); + + Position start = range.getStart(); + Position end = range.getEnd(); + end.setCharacter(start.getCharacter() + classMatcher.end()); + start.setCharacter(start.getCharacter() + prefix.length() + classMatcher.start()); + + TextEdit textEdit = new TextEdit(); + textEdit.setRange(range); + textEdit.setNewText(newName); + return textEdit; + } + + private TextEdit createTextEditToRenameMethodNode(MethodNode methodNode, String newName, String text, Range range) { + // the AST doesn't give us access to the name location, so we + // need to find it manually + Pattern methodPattern = Pattern.compile("\\b" + methodNode.getName() + "\\b(?=\\s*\\()"); + Matcher methodMatcher = methodPattern.matcher(text); + if (!methodMatcher.find()) { + // couldn't find the name! + return null; + } + + Position start = range.getStart(); + Position end = range.getEnd(); + end.setCharacter(start.getCharacter() + methodMatcher.end()); + start.setCharacter(start.getCharacter() + methodMatcher.start()); + + TextEdit textEdit = new TextEdit(); + textEdit.setRange(range); + textEdit.setNewText(newName); + return textEdit; + } + + private TextEdit createTextEditToRenamePropertyNode(PropertyNode propNode, String newName, String text, + Range range) { + // the AST doesn't give us access to the name location, so we + // need to find it manually + Pattern propPattern = Pattern.compile("\\b" + propNode.getName() + "\\b"); + Matcher propMatcher = propPattern.matcher(text); + if (!propMatcher.find()) { + // couldn't find the name! + return null; + } + + Position start = range.getStart(); + Position end = range.getEnd(); + end.setCharacter(start.getCharacter() + propMatcher.end()); + start.setCharacter(start.getCharacter() + propMatcher.start()); + + TextEdit textEdit = new TextEdit(); + textEdit.setRange(range); + textEdit.setNewText(newName); + return textEdit; + } +} \ No newline at end of file diff --git a/src/main/java/net/prominic/groovyls/providers/SignatureHelpProvider.java b/src/main/java/net/prominic/groovyls/providers/SignatureHelpProvider.java new file mode 100644 index 000000000..50df7b35e --- /dev/null +++ b/src/main/java/net/prominic/groovyls/providers/SignatureHelpProvider.java @@ -0,0 +1,121 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2022 Prominic.NET, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License +// +// Author: Prominic.NET, Inc. +// No warranty of merchantability or fitness of any kind. +// Use this software at your own risk. +//////////////////////////////////////////////////////////////////////////////// +package net.prominic.groovyls.providers; + +import net.prominic.groovyls.compiler.ast.ASTContext; +import net.prominic.groovyls.compiler.util.GroovyASTUtils; +import net.prominic.groovyls.util.GroovyLanguageServerUtils; +import net.prominic.groovyls.util.GroovyNodeToStringUtils; +import net.prominic.groovyls.util.URIUtils; +import org.codehaus.groovy.ast.ASTNode; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.Parameter; +import org.codehaus.groovy.ast.expr.ArgumentListExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.MethodCall; +import org.eclipse.lsp4j.*; + +import java.net.URI; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +public class SignatureHelpProvider { + + private final ASTContext astContext; + + public SignatureHelpProvider(ASTContext astContext) { + this.astContext = astContext; + } + + public CompletableFuture provideSignatureHelp(TextDocumentIdentifier textDocument, + Position position) { + URI uri = URIUtils.toUri(textDocument.getUri()); + ASTNode offsetNode = astContext.getVisitor().getNodeAtLineAndColumn(uri, position.getLine(), position.getCharacter()); + if (offsetNode == null) { + return CompletableFuture.completedFuture(new SignatureHelp(Collections.emptyList(), -1, -1)); + } + int activeParamIndex = -1; + MethodCall methodCall = null; + ASTNode parentNode = astContext.getVisitor().getParent(offsetNode); + + if (offsetNode instanceof ArgumentListExpression) { + methodCall = (MethodCall) parentNode; + + ArgumentListExpression argsList = (ArgumentListExpression) offsetNode; + List expressions = argsList.getExpressions(); + activeParamIndex = getActiveParameter(position, expressions); + } + + if (methodCall == null) { + return CompletableFuture.completedFuture(new SignatureHelp(Collections.emptyList(), -1, -1)); + } + + List methods = GroovyASTUtils.getMethodOverloadsFromCallExpression(methodCall, astContext); + if (methods.isEmpty()) { + return CompletableFuture.completedFuture(new SignatureHelp(Collections.emptyList(), -1, -1)); + } + + List sigInfos = new ArrayList<>(); + for (MethodNode method : methods) { + List parameters = new ArrayList<>(); + Parameter[] methodParams = method.getParameters(); + for (int i = 0; i < methodParams.length; i++) { + Parameter methodParam = methodParams[i]; + ParameterInformation paramInfo = new ParameterInformation(); + paramInfo.setLabel(GroovyNodeToStringUtils.variableToString(methodParam, astContext)); + parameters.add(paramInfo); + } + SignatureInformation sigInfo = new SignatureInformation(); + sigInfo.setLabel(GroovyNodeToStringUtils.methodToString(method, astContext)); + sigInfo.setParameters(parameters); + String markdownDocs = astContext.getLanguageServerContext().getDocumentationFactory().getDocumentation(method, astContext); + + if (markdownDocs != null) { + sigInfo.setDocumentation(new MarkupContent(MarkupKind.MARKDOWN, markdownDocs)); + } + sigInfos.add(sigInfo); + } + + MethodNode bestMethod = GroovyASTUtils.getMethodFromCallExpression(methodCall, astContext, activeParamIndex); + int activeSignature = methods.indexOf(bestMethod); + + return CompletableFuture.completedFuture(new SignatureHelp(sigInfos, activeSignature, activeParamIndex)); + } + + private int getActiveParameter(Position position, List expressions) { + for (int i = 0; i < expressions.size(); i++) { + Expression expr = expressions.get(i); + Range exprRange = GroovyLanguageServerUtils.astNodeToRange(expr); + if (exprRange == null) { + continue; + } + if (position.getLine() < exprRange.getEnd().getLine()) { + return i; + } + if (position.getLine() == exprRange.getEnd().getLine() + && position.getCharacter() <= exprRange.getEnd().getCharacter()) { + return i; + } + } + return expressions.size(); + } +} \ No newline at end of file diff --git a/src/main/java/net/prominic/groovyls/providers/TypeDefinitionProvider.java b/src/main/java/net/prominic/groovyls/providers/TypeDefinitionProvider.java new file mode 100644 index 000000000..032172721 --- /dev/null +++ b/src/main/java/net/prominic/groovyls/providers/TypeDefinitionProvider.java @@ -0,0 +1,73 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2022 Prominic.NET, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License +// +// Author: Prominic.NET, Inc. +// No warranty of merchantability or fitness of any kind. +// Use this software at your own risk. +//////////////////////////////////////////////////////////////////////////////// +package net.prominic.groovyls.providers; + +import java.net.URI; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import net.prominic.groovyls.compiler.ast.ASTContext; +import net.prominic.groovyls.util.URIUtils; +import org.codehaus.groovy.ast.ASTNode; +import org.eclipse.lsp4j.Location; +import org.eclipse.lsp4j.LocationLink; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.TextDocumentIdentifier; +import org.eclipse.lsp4j.jsonrpc.messages.Either; + +import net.prominic.groovyls.compiler.ast.ASTNodeVisitor; +import net.prominic.groovyls.compiler.util.GroovyASTUtils; +import net.prominic.groovyls.util.GroovyLanguageServerUtils; + +public class TypeDefinitionProvider { + + private final ASTContext astContext; + + public TypeDefinitionProvider(ASTContext astContext) { + this.astContext = astContext; + } + + public CompletableFuture, List>> provideTypeDefinition( + TextDocumentIdentifier textDocument, Position position) { + URI uri = URIUtils.toUri(textDocument.getUri()); + ASTNode offsetNode = astContext.getVisitor().getNodeAtLineAndColumn(uri, position.getLine(), position.getCharacter()); + if (offsetNode == null) { + return CompletableFuture.completedFuture(Either.forLeft(Collections.emptyList())); + } + + ASTNode definitionNode = GroovyASTUtils.getTypeDefinition(offsetNode, astContext); + if (definitionNode == null || definitionNode.getLineNumber() == -1 || definitionNode.getColumnNumber() == -1) { + return CompletableFuture.completedFuture(Either.forLeft(Collections.emptyList())); + } + + URI definitionURI = astContext.getVisitor().getURI(definitionNode); + if (definitionURI == null) { + definitionURI = uri; + } + + Location location = GroovyLanguageServerUtils.astNodeToLocation(definitionNode, definitionURI); + if (location == null) { + return CompletableFuture.completedFuture(Either.forLeft(Collections.emptyList())); + } + + return CompletableFuture.completedFuture(Either.forLeft(Collections.singletonList(location))); + } +} \ No newline at end of file diff --git a/src/main/java/net/prominic/groovyls/providers/WorkspaceSymbolProvider.java b/src/main/java/net/prominic/groovyls/providers/WorkspaceSymbolProvider.java new file mode 100644 index 000000000..4289983c8 --- /dev/null +++ b/src/main/java/net/prominic/groovyls/providers/WorkspaceSymbolProvider.java @@ -0,0 +1,94 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2022 Prominic.NET, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License +// +// Author: Prominic.NET, Inc. +// No warranty of merchantability or fitness of any kind. +// Use this software at your own risk. +//////////////////////////////////////////////////////////////////////////////// +package net.prominic.groovyls.providers; + +import java.net.URI; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +import net.prominic.groovyls.compiler.ast.ASTContext; +import org.codehaus.groovy.ast.ASTNode; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.FieldNode; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.PropertyNode; +import org.eclipse.lsp4j.SymbolInformation; + +import net.prominic.groovyls.compiler.ast.ASTNodeVisitor; +import net.prominic.groovyls.compiler.util.GroovyASTUtils; +import net.prominic.groovyls.util.GroovyLanguageServerUtils; + +public class WorkspaceSymbolProvider { + + private final ASTContext astContext; + + public WorkspaceSymbolProvider(ASTContext astContext) { + this.astContext = astContext; + } + + public CompletableFuture> provideWorkspaceSymbols(String query) { + String lowerCaseQuery = query.toLowerCase(); + List nodes = astContext.getVisitor().getNodes(); + List symbols = nodes.stream().filter(node -> { + String name = null; + if (node instanceof ClassNode) { + ClassNode classNode = (ClassNode) node; + name = classNode.getName(); + } else if (node instanceof MethodNode) { + MethodNode methodNode = (MethodNode) node; + name = methodNode.getName(); + } else if (node instanceof FieldNode) { + FieldNode fieldNode = (FieldNode) node; + name = fieldNode.getName(); + } else if (node instanceof PropertyNode) { + PropertyNode propNode = (PropertyNode) node; + name = propNode.getName(); + } + if (name == null) { + return false; + } + return name.toLowerCase().contains(lowerCaseQuery); + }).map(node -> { + URI uri = astContext.getVisitor().getURI(node); + if (node instanceof ClassNode) { + ClassNode classNode = (ClassNode) node; + return GroovyLanguageServerUtils.astNodeToSymbolInformation(classNode, uri, null); + } + ClassNode classNode = (ClassNode) GroovyASTUtils.getEnclosingNodeOfType(node, ClassNode.class, astContext); + if (node instanceof MethodNode) { + MethodNode methodNode = (MethodNode) node; + return GroovyLanguageServerUtils.astNodeToSymbolInformation(methodNode, uri, classNode.getName()); + } + if (node instanceof PropertyNode) { + PropertyNode propNode = (PropertyNode) node; + return GroovyLanguageServerUtils.astNodeToSymbolInformation(propNode, uri, classNode.getName()); + } + if (node instanceof FieldNode) { + FieldNode fieldNode = (FieldNode) node; + return GroovyLanguageServerUtils.astNodeToSymbolInformation(fieldNode, uri, classNode.getName()); + } + // this should never happen + return null; + }).filter(symbolInformation -> symbolInformation != null).collect(Collectors.toList()); + return CompletableFuture.completedFuture(symbols); + } +} \ No newline at end of file diff --git a/src/main/java/net/prominic/groovyls/util/ClassGraphUtils.java b/src/main/java/net/prominic/groovyls/util/ClassGraphUtils.java new file mode 100644 index 000000000..2bf453960 --- /dev/null +++ b/src/main/java/net/prominic/groovyls/util/ClassGraphUtils.java @@ -0,0 +1,27 @@ +package net.prominic.groovyls.util; + +import io.github.classgraph.ClassInfo; +import net.prominic.groovyls.compiler.ast.ASTContext; +import org.codehaus.groovy.ast.ClassNode; +import org.jetbrains.annotations.Nullable; + +public class ClassGraphUtils { + + public static @Nullable ClassInfo resolveAllowedClassInfo(ClassNode node, ASTContext context) { + ClassInfo result = null; + while (result == null) { + if (node.equals(new ClassNode(Object.class))) + return null; + result = context.getLanguageServerContext().getScanResult().getClassInfo(node.getName()); + for (ClassNode anInterface : node.getInterfaces()) { + result = context.getLanguageServerContext().getScanResult().getClassInfo(anInterface.getName()); + if (result != null) { + break; + } + } + node = node.getSuperClass(); + } + + return result; + } +} diff --git a/src/main/java/net/prominic/groovyls/util/CompletionItemFactory.java b/src/main/java/net/prominic/groovyls/util/CompletionItemFactory.java new file mode 100644 index 000000000..faa827d90 --- /dev/null +++ b/src/main/java/net/prominic/groovyls/util/CompletionItemFactory.java @@ -0,0 +1,37 @@ +package net.prominic.groovyls.util; + +import net.prominic.groovyls.compiler.ast.ASTContext; +import org.codehaus.groovy.ast.ASTNode; +import org.codehaus.groovy.ast.AnnotatedNode; +import org.eclipse.lsp4j.*; + +public class CompletionItemFactory { + + public static CompletionItem createCompletion(ASTNode node, String label, ASTContext astContext) { + var completionItem = createCompletion(GroovyLanguageServerUtils.astNodeToCompletionItemKind(node), label); + + if (node instanceof AnnotatedNode annotatedNode) { + var documentation = astContext.getLanguageServerContext().getDocumentationFactory().getDocumentation(annotatedNode, astContext); + + if (documentation != null) { + completionItem.setDocumentation(new MarkupContent(MarkupKind.MARKDOWN, documentation)); + } + } + + return completionItem; + } + + public static CompletionItem createCompletion(CompletionItemKind kind, String label) { + var item = new CompletionItem(); + item.setKind(kind); + item.setLabel(label); + if (kind == CompletionItemKind.Method) { + item.setInsertTextFormat(InsertTextFormat.Snippet); + item.setInsertText(label + "($0)"); + } else if (kind == CompletionItemKind.Property || kind == CompletionItemKind.Field) { + item.setInsertTextFormat(InsertTextFormat.Snippet); + item.setInsertText(label + ".$0"); + } + return item; + } +} diff --git a/src/main/java/net/prominic/groovyls/util/FileContentsTracker.java b/src/main/java/net/prominic/groovyls/util/FileContentsTracker.java new file mode 100644 index 000000000..2c9fad625 --- /dev/null +++ b/src/main/java/net/prominic/groovyls/util/FileContentsTracker.java @@ -0,0 +1,119 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2022 Prominic.NET, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License +// +// Author: Prominic.NET, Inc. +// No warranty of merchantability or fitness of any kind. +// Use this software at your own risk. +//////////////////////////////////////////////////////////////////////////////// +package net.prominic.groovyls.util; + +import net.prominic.lsp.utils.Positions; +import org.eclipse.lsp4j.*; + +import java.io.BufferedReader; +import java.io.IOException; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class FileContentsTracker { + + private Map openFiles = new HashMap<>(); + private Set changedFiles = new HashSet<>(); + + public Set getOpenURIs() { + return openFiles.keySet(); + } + + public Set getChangedURIs() { + return changedFiles; + } + + public void resetChangedFiles() { + changedFiles = new HashSet<>(); + } + + public void forceChanged(URI uri) { + changedFiles.add(uri); + } + + public boolean isOpen(URI uri) { + return openFiles.containsKey(uri); + } + + public void didOpen(DidOpenTextDocumentParams params) { + URI uri = URIUtils.toUri(params.getTextDocument().getUri()); + openFiles.put(uri, params.getTextDocument().getText()); + changedFiles.add(uri); + } + + public void didChange(DidChangeTextDocumentParams params) { + URI uri = URIUtils.toUri(params.getTextDocument().getUri()); + String oldText = openFiles.get(uri); + TextDocumentContentChangeEvent change = params.getContentChanges().get(0); + Range range = change.getRange(); + if (range == null) { + openFiles.put(uri, change.getText()); + } else { + int offsetStart = Positions.getOffset(oldText, change.getRange().getStart()); + int offsetEnd = Positions.getOffset(oldText, change.getRange().getEnd()); + StringBuilder builder = new StringBuilder(); + builder.append(oldText.substring(0, offsetStart)); + builder.append(change.getText()); + builder.append(oldText.substring(offsetEnd)); + openFiles.put(uri, builder.toString()); + } + changedFiles.add(uri); + } + + public void didClose(DidCloseTextDocumentParams params) { + URI uri = URIUtils.toUri(params.getTextDocument().getUri()); + openFiles.remove(uri); + changedFiles.add(uri); + } + + public String getContents(URI uri) { + if (!openFiles.containsKey(uri)) { + BufferedReader reader = null; + try { + reader = Files.newBufferedReader(Paths.get(uri)); + StringBuilder builder = new StringBuilder(); + int next = -1; + while ((next = reader.read()) != -1) { + builder.append((char) next); + } + return builder.toString(); + } catch (IOException e) { + return ""; + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + } + } + } + } + return openFiles.getOrDefault(uri, ""); + } + + public void setContents(URI uri, String contents) { + openFiles.put(uri, contents); + } +} \ No newline at end of file diff --git a/src/main/java/net/prominic/groovyls/util/GroovyLanguageServerUtils.java b/src/main/java/net/prominic/groovyls/util/GroovyLanguageServerUtils.java new file mode 100644 index 000000000..a0e2aed85 --- /dev/null +++ b/src/main/java/net/prominic/groovyls/util/GroovyLanguageServerUtils.java @@ -0,0 +1,171 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2022 Prominic.NET, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License +// +// Author: Prominic.NET, Inc. +// No warranty of merchantability or fitness of any kind. +// Use this software at your own risk. +//////////////////////////////////////////////////////////////////////////////// +package net.prominic.groovyls.util; + +import java.net.URI; + +import org.codehaus.groovy.ast.ASTNode; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.FieldNode; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.PropertyNode; +import org.codehaus.groovy.ast.Variable; +import org.codehaus.groovy.syntax.SyntaxException; +import org.eclipse.lsp4j.CompletionItemKind; +import org.eclipse.lsp4j.Location; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.Range; +import org.eclipse.lsp4j.SymbolInformation; +import org.eclipse.lsp4j.SymbolKind; + +public class GroovyLanguageServerUtils { + /** + * Converts a Groovy position to a LSP position. + * + * May return null if the Groovy line is -1 + */ + public static Position createGroovyPosition(int groovyLine, int groovyColumn) { + if (groovyLine == -1) { + return null; + } + if (groovyColumn == -1) { + groovyColumn = 0; + } + int lspLine = groovyLine; + if (lspLine > 0) { + lspLine--; + } + int lspColumn = groovyColumn; + if (lspColumn > 0) { + lspColumn--; + } + return new Position(lspLine, lspColumn); + } + + public static Range syntaxExceptionToRange(SyntaxException exception) { + return new Range(createGroovyPosition(exception.getStartLine(), exception.getStartColumn()), + createGroovyPosition(exception.getEndLine(), exception.getEndColumn())); + } + + /** + * Converts a Groovy AST node to an LSP range. + * + * May return null if the node's start line is -1 + */ + public static Range astNodeToRange(ASTNode node) { + Position start = createGroovyPosition(node.getLineNumber(), node.getColumnNumber()); + if (start == null) { + return null; + } + Position end = createGroovyPosition(node.getLastLineNumber(), node.getLastColumnNumber()); + if (end == null) { + end = start; + } + return new Range(start, end); + } + + public static CompletionItemKind astNodeToCompletionItemKind(ASTNode node) { + if (node instanceof ClassNode) { + ClassNode classNode = (ClassNode) node; + if (classNode.isInterface()) { + return CompletionItemKind.Interface; + } else if (classNode.isEnum()) { + return CompletionItemKind.Enum; + } + return CompletionItemKind.Class; + } else if (node instanceof MethodNode) { + return CompletionItemKind.Method; + } else if (node instanceof Variable) { + if (node instanceof FieldNode || node instanceof PropertyNode) { + return CompletionItemKind.Field; + } + return CompletionItemKind.Variable; + } + return CompletionItemKind.Property; + } + + public static SymbolKind astNodeToSymbolKind(ASTNode node) { + if (node instanceof ClassNode) { + ClassNode classNode = (ClassNode) node; + if (classNode.isInterface()) { + return SymbolKind.Interface; + } else if (classNode.isEnum()) { + return SymbolKind.Enum; + } + return SymbolKind.Class; + } else if (node instanceof MethodNode) { + return SymbolKind.Method; + } else if (node instanceof Variable) { + if (node instanceof FieldNode || node instanceof PropertyNode) { + return SymbolKind.Field; + } + return SymbolKind.Variable; + } + return SymbolKind.Property; + } + + /** + * Converts a Groovy AST node to an LSP location. + * + * May return null if the node's start line is -1 + */ + public static Location astNodeToLocation(ASTNode node, URI uri) { + Range range = astNodeToRange(node); + if (range == null) { + return null; + } + return new Location(uri.toString(), range); + } + + public static SymbolInformation astNodeToSymbolInformation(ClassNode node, URI uri, String parentName) { + Location location = astNodeToLocation(node, uri); + if (location == null) { + return null; + } + SymbolKind symbolKind = astNodeToSymbolKind(node); + return new SymbolInformation(node.getName(), symbolKind, location, + parentName); + } + + public static SymbolInformation astNodeToSymbolInformation(MethodNode node, URI uri, String parentName) { + Location location = astNodeToLocation(node, uri); + if (location == null) { + return null; + } + SymbolKind symbolKind = astNodeToSymbolKind(node); + return new SymbolInformation(node.getName(), symbolKind, location, + parentName); + } + + public static SymbolInformation astNodeToSymbolInformation(Variable node, URI uri, String parentName) { + if (!(node instanceof ASTNode)) { + // DynamicVariable isn't an ASTNode + return null; + } + ASTNode astVar = (ASTNode) node; + Location location = astNodeToLocation(astVar, uri); + if (location == null) { + return null; + } + SymbolKind symbolKind = astNodeToSymbolKind(astVar); + return new SymbolInformation(node.getName(), symbolKind, location, + parentName); + } +} \ No newline at end of file diff --git a/src/main/java/net/prominic/groovyls/util/GroovyNodeToStringUtils.java b/src/main/java/net/prominic/groovyls/util/GroovyNodeToStringUtils.java new file mode 100644 index 000000000..498a5d82d --- /dev/null +++ b/src/main/java/net/prominic/groovyls/util/GroovyNodeToStringUtils.java @@ -0,0 +1,160 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2022 Prominic.NET, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License +// +// Author: Prominic.NET, Inc. +// No warranty of merchantability or fitness of any kind. +// Use this software at your own risk. +//////////////////////////////////////////////////////////////////////////////// +package net.prominic.groovyls.util; + +import net.prominic.groovyls.compiler.ast.ASTContext; +import org.codehaus.groovy.ast.ASTNode; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.ConstructorNode; +import org.codehaus.groovy.ast.FieldNode; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.Parameter; +import org.codehaus.groovy.ast.Variable; + +import net.prominic.groovyls.compiler.ast.ASTNodeVisitor; +import net.prominic.groovyls.compiler.util.GroovyASTUtils; + +public class GroovyNodeToStringUtils { + + private static final String JAVA_OBJECT = "java.lang.Object"; + + public static String classToString(ClassNode classNode, ASTContext astContext) { + StringBuilder builder = new StringBuilder(); + String packageName = classNode.getPackageName(); + if (packageName != null && packageName.length() > 0) { + builder.append("package "); + builder.append(packageName); + builder.append("\n"); + } + if (!classNode.isSyntheticPublic()) { + builder.append("public "); + } + if (classNode.isAbstract()) { + builder.append("abstract "); + } + if (classNode.isInterface()) { + builder.append("interface "); + } else if (classNode.isEnum()) { + builder.append("enum "); + } else { + builder.append("class "); + } + builder.append(classNode.getNameWithoutPackage()); + + ClassNode superClass = null; + try { + superClass = classNode.getSuperClass(); + } catch (NoClassDefFoundError e) { + // this is fine, we'll just treat it as null + } + if (superClass != null && !superClass.getName().equals(JAVA_OBJECT)) { + builder.append(" extends "); + builder.append(superClass.getNameWithoutPackage()); + } + return builder.toString(); + } + + public static String constructorToString(ConstructorNode constructorNode, ASTContext astContext) { + StringBuilder builder = new StringBuilder(); + builder.append(constructorNode.getDeclaringClass().getName()); + builder.append("("); + builder.append(parametersToString(constructorNode.getParameters(), astContext)); + builder.append(")"); + return builder.toString(); + } + + public static String methodToString(MethodNode methodNode, ASTContext astContext) { + if (methodNode instanceof ConstructorNode) { + return constructorToString((ConstructorNode) methodNode, astContext); + } + StringBuilder builder = new StringBuilder(); + if (methodNode.isPublic()) { + if (!methodNode.isSyntheticPublic()) { + builder.append("public "); + } + } else if (methodNode.isProtected()) { + builder.append("protected "); + } else if (methodNode.isPrivate()) { + builder.append("private "); + } + + if (methodNode.isStatic()) { + builder.append("static "); + } + + if (methodNode.isFinal()) { + builder.append("final "); + } + ClassNode returnType = methodNode.getReturnType(); + builder.append(returnType.getNameWithoutPackage()); + builder.append(" "); + builder.append(methodNode.getName()); + builder.append("("); + builder.append(parametersToString(methodNode.getParameters(), astContext)); + builder.append(")"); + return builder.toString(); + } + + public static String parametersToString(Parameter[] params, ASTContext astContext) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < params.length; i++) { + if (i > 0) { + builder.append(", "); + } + Parameter paramNode = params[i]; + builder.append(variableToString(paramNode, astContext)); + } + return builder.toString(); + } + + public static String variableToString(Variable variable, ASTContext astContext) { + StringBuilder builder = new StringBuilder(); + if (variable instanceof FieldNode) { + FieldNode fieldNode = (FieldNode) variable; + if (fieldNode.isPublic()) { + builder.append("public "); + } + if (fieldNode.isProtected()) { + builder.append("protected "); + } + if (fieldNode.isPrivate()) { + builder.append("private "); + } + + if (fieldNode.isFinal()) { + builder.append("final "); + } + + if (fieldNode.isStatic()) { + builder.append("static "); + } + } + ClassNode varType = null; + if (variable instanceof ASTNode) { + varType = GroovyASTUtils.getTypeOfNode((ASTNode) variable, astContext); + } else { + varType = variable.getType(); + } + builder.append(varType.getNameWithoutPackage()); + builder.append(" "); + builder.append(variable.getName()); + return builder.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/net/prominic/groovyls/util/URIUtils.java b/src/main/java/net/prominic/groovyls/util/URIUtils.java new file mode 100644 index 000000000..1eec0f08b --- /dev/null +++ b/src/main/java/net/prominic/groovyls/util/URIUtils.java @@ -0,0 +1,22 @@ +package net.prominic.groovyls.util; + +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URLDecoder; + +public final class URIUtils { + + public static URI toUri(String uriString) { + try { + // for some reason vscode like to output garbage like file:///c%3A/Users/.. + var decodedUriString = URLDecoder.decode(uriString, "UTF-8"); + + if (decodedUriString.matches("^file:///[a-z]:/.*$")) { + decodedUriString = "file:///" + decodedUriString.substring(8, 9).toUpperCase() + decodedUriString.substring(9); + } + return URI.create(decodedUriString); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/net/prominic/lsp/utils/Positions.java b/src/main/java/net/prominic/lsp/utils/Positions.java new file mode 100644 index 000000000..b9716e6fc --- /dev/null +++ b/src/main/java/net/prominic/lsp/utils/Positions.java @@ -0,0 +1,72 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2022 Prominic.NET, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License +// +// Author: Prominic.NET, Inc. +// No warranty of merchantability or fitness of any kind. +// Use this software at your own risk. +//////////////////////////////////////////////////////////////////////////////// +package net.prominic.lsp.utils; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.StringReader; +import java.util.Comparator; + +import org.eclipse.lsp4j.Position; + +public class Positions { + public static final Comparator COMPARATOR = (Position p1, Position p2) -> { + if (p1.getLine() != p2.getLine()) { + return p1.getLine() - p2.getLine(); + } + return p1.getCharacter() - p2.getCharacter(); + }; + + public static boolean valid(Position p) { + return p.getLine() >= 0 || p.getCharacter() >= 0; + } + + public static int getOffset(String string, Position position) { + int line = position.getLine(); + int character = position.getCharacter(); + int currentIndex = 0; + if (line > 0) { + BufferedReader reader = new BufferedReader(new StringReader(string)); + try { + int readLines = 0; + while (true) { + char currentChar = (char) reader.read(); + if (currentChar == -1) { + return -1; + } + currentIndex++; + if (currentChar == '\n') { + readLines++; + if (readLines == line) { + break; + } + } + } + } catch (IOException e) { + return -1; + } + try { + reader.close(); + } catch (IOException e) { + } + } + return currentIndex + character; + } +} \ No newline at end of file diff --git a/src/main/java/net/prominic/lsp/utils/Ranges.java b/src/main/java/net/prominic/lsp/utils/Ranges.java new file mode 100644 index 000000000..69854748d --- /dev/null +++ b/src/main/java/net/prominic/lsp/utils/Ranges.java @@ -0,0 +1,92 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2022 Prominic.NET, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License +// +// Author: Prominic.NET, Inc. +// No warranty of merchantability or fitness of any kind. +// Use this software at your own risk. +//////////////////////////////////////////////////////////////////////////////// +package net.prominic.lsp.utils; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.StringReader; + +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.Range; + +public class Ranges { + public static boolean contains(Range range, Position position) { + return Positions.COMPARATOR.compare(position, range.getStart()) >= 0 + && Positions.COMPARATOR.compare(position, range.getEnd()) <= 0; + } + + public static boolean intersect(Range r1, Range r2) { + return contains(r1, r2.getStart()) || contains(r1, r2.getEnd()); + } + + public static String getSubstring(String string, Range range) { + return getSubstring(string, range, 0); + } + + public static String getSubstring(String string, Range range, int maxLines) { + BufferedReader reader = new BufferedReader(new StringReader(string)); + StringBuilder builder = new StringBuilder(); + Position start = range.getStart(); + Position end = range.getEnd(); + int startLine = start.getLine(); + int startChar = start.getCharacter(); + int endLine = end.getLine(); + int endChar = end.getCharacter(); + int lineCount = 1 + (endLine - startLine); + if (maxLines > 0 && lineCount > maxLines) { + endLine = startLine + maxLines - 1; + endChar = 0; + } + try { + for (int i = 0; i < startLine; i++) { + // ignore these lines + reader.readLine(); + } + for (int i = 0; i < startChar; i++) { + // ignore these characters + reader.read(); + } + int endCharStart = startChar; + int maxLineBreaks = endLine - startLine; + if (maxLineBreaks > 0) { + endCharStart = 0; + int readLines = 0; + while (readLines < maxLineBreaks) { + char character = (char) reader.read(); + if (character == '\n') { + readLines++; + } + builder.append(character); + } + } + // the remaining characters on the final line + for (int i = endCharStart; i < endChar; i++) { + builder.append((char) reader.read()); + } + } catch (IOException e) { + return null; + } + try { + reader.close(); + } catch (IOException e) { + } + return builder.toString(); + } +} \ No newline at end of file diff --git a/src/main/resources/assets/groovyscript/lang/en_us.lang b/src/main/resources/assets/groovyscript/lang/en_us.lang index 6d0b70f1f..2d806c1f2 100644 --- a/src/main/resources/assets/groovyscript/lang/en_us.lang +++ b/src/main/resources/assets/groovyscript/lang/en_us.lang @@ -145,6 +145,53 @@ groovyscript.wiki.advancedmortars.mortar.duration.value=Sets how many interactio groovyscript.wiki.advancedmortars.mortar.secondaryOutput.value=Sets the additional output itemstack groovyscript.wiki.advancedmortars.mortar.secondaryOutputChance.value=Sets the chance of the additional output itemstack being output +# Aether Legacy +groovyscript.wiki.aether_legacy.enchanter.title=Enchanter +groovyscript.wiki.aether_legacy.enchanter.description=Enchanting is a mechanic used to create new items, as well as repair tools, armor, and weapons, using the Altar block. +groovyscript.wiki.aether_legacy.enchanter.add=Adds an Enchanting recipe in the format `input`, `output`, `time`. +groovyscript.wiki.aether_legacy.enchanter_fuel.title=Enchanter Fuel +groovyscript.wiki.aether_legacy.enchanter_fuel.description=By default, the Enchantar (Altar) takes Ambrosium Shards as fuel. Using GroovyScript, custom fuels can be added. +groovyscript.wiki.aether_legacy.enchanter_fuel.add=Adds an Enchanting fuel in the format `item`, `timeGiven`. +groovyscript.wiki.aether_legacy.freezer.title=Freezer +groovyscript.wiki.aether_legacy.freezer.description=The Freezer is used to turn certain items into frozen versions. +groovyscript.wiki.aether_legacy.freezer.add=Adds a Freezer recipe in the format `input`, `output`, `time`. +groovyscript.wiki.aether_legacy.freezer_fuel.title=Freezer +groovyscript.wiki.aether_legacy.freezer_fuel.description= By default, the Freezer takes Icestone as fuel. Using GroovyScript, custom fuels can be added. +groovyscript.wiki.aether_legacy.freezer_fuel.add=Adds a Freezer fuel in the format `item`, `timeGiven`. +groovyscript.wiki.aether_legacy.accessory.title=Accessory +groovyscript.wiki.aether_legacy.accessory.description=The Aether Accessory system. +groovyscript.wiki.aether_legacy.accessory.add=Adds an Accessory in the format `item`, `type`, where type is one of the following: "Ring", "Pendant", "Cape", "Shield", "Glove", or "Misc". + +# Alchemistry +groovyscript.wiki.alchemistry.atomizer.title=Atomizer +groovyscript.wiki.alchemistry.atomizer.description=Converts a non-element into its component elements. +groovyscript.wiki.alchemistry.atomizer.add=Adds recipes in the format `input`, `output` +groovyscript.wiki.alchemistry.atomizer.reversible.value=Sets if the recipe will also create a Liquifier recipe to invert the process + +groovyscript.wiki.alchemistry.combiner.title=Chemical Combiner +groovyscript.wiki.alchemistry.combiner.description=Converts up to 9 input itemstacks into an output itemstack. +groovyscript.wiki.alchemistry.combiner.gamestage.value=Sets the required gamestage to add the recipe + +groovyscript.wiki.alchemistry.dissolver.title=Chemical Dissolver +groovyscript.wiki.alchemistry.dissolver.description=Converts an input itemstack into any number of output itemstacks, divided in any manner between different chances, with the ability to run multiple rolls to produce additional outputs. +groovyscript.wiki.alchemistry.dissolver.rolls.value=Sets the number of rolls to produce outputs +groovyscript.wiki.alchemistry.dissolver.reversible.value=Sets if the recipe will also create a Chemical Combiner to invert the process. Only properly works on some recipes +groovyscript.wiki.alchemistry.dissolver.probabilityGroup.value=Sets the probability sets rolled to produce output +groovyscript.wiki.alchemistry.dissolver.relativeProbability.value=Sets if there is guaranteed to always be one and exactly one set output, or if each set is rolled individually + +groovyscript.wiki.alchemistry.electrolyzer.title=Electrolyzer +groovyscript.wiki.alchemistry.electrolyzer.description=Converts an input fluidstack into up to 4 output itemstacks, with the 3rd and 4th output itemstacks being able to have chances applied to them. May require a catalyst input itemstack, which may also have a chance to be consumed. +groovyscript.wiki.alchemistry.electrolyzer.chance.value=Sets the output chance of the 3rd and 4th output itemstacks +groovyscript.wiki.alchemistry.electrolyzer.consumptionChance.value=Sets the chance the catalyst input itemstack has to be consumed + +groovyscript.wiki.alchemistry.evaporator.title=Evaporator +groovyscript.wiki.alchemistry.evaporator.add=Adds recipes in the format `input`, `output` +groovyscript.wiki.alchemistry.evaporator.description=Converts an input fluidstack into an output fluidstack, taking a set amount of time. + +groovyscript.wiki.alchemistry.liquifier.title=Liquifier +groovyscript.wiki.alchemistry.liquifier.add=Adds recipes in the format `input`, `output` +groovyscript.wiki.alchemistry.liquifier.description=Converts an input itemstack into an output fluidstack, consuming a set amount of power. + # Applied Energistics 2 groovyscript.wiki.appliedenergistics2.attunement.title=P2P Attunement groovyscript.wiki.appliedenergistics2.attunement.description=Controls using specific items, any items from a mod, or any items with a Capability to convert a P2P into a specific tunnel type. @@ -327,6 +374,63 @@ groovyscript.wiki.avaritia.extreme_crafting.description=A normal crafting table, groovyscript.wiki.avaritia.extreme_crafting.addShaped=Adds a shaped crafting recipe in the format `output`, `input` groovyscript.wiki.avaritia.extreme_crafting.addShapeless=Adds a shapeless crafting recipe in the format `output`, `input` +# Better With Mods +groovyscript.wiki.betterwithmods.anvil_crafting.title=Anvil Crafting +groovyscript.wiki.betterwithmods.anvil_crafting.description=Similar to a normal crafting table, but 4x4 instead. + +groovyscript.wiki.betterwithmods.cauldron.title=Cauldron +groovyscript.wiki.betterwithmods.cauldron.description=Converts a large number of items into other items, with the ability to require specific amounts of heat. +groovyscript.wiki.betterwithmods.cauldron.heat.value=Sets if the Cauldron requires a normal fire (1) or a Stoked Fire (2) below it +groovyscript.wiki.betterwithmods.cauldron.priority.value=Sets the priority of the recipe in relation to other valid recipes for the given items +groovyscript.wiki.betterwithmods.cauldron.ignoreHeat.value=Sets if the Cauldron requires any heat source below it + +groovyscript.wiki.betterwithmods.crucible.title=Crucible +groovyscript.wiki.betterwithmods.crucible.description=Converts a large number of items into other items, with the ability to require specific amounts of heat. +groovyscript.wiki.betterwithmods.crucible.heat.value=Sets if the Cauldron requires a normal fire (1) or a Stoked Fire (2) below it +groovyscript.wiki.betterwithmods.crucible.priority.value=Sets the priority of the recipe in relation to other valid recipes for the given items +groovyscript.wiki.betterwithmods.crucible.ignoreHeat.value=Sets if the Cauldron requires any heat source below it + +groovyscript.wiki.betterwithmods.heat.title=Heat +groovyscript.wiki.betterwithmods.heat.description=Creates new levels or adds new blocks to old heat levels. +groovyscript.wiki.betterwithmods.heat.note0=Anything using heat levels will create a new JEI tab for each heat level it has recipes for. This will have a lang key name. +groovyscript.wiki.betterwithmods.heat.add=Adds new heat levels in the format `heat`, `ingredient` + +groovyscript.wiki.betterwithmods.hopper.title=Filtered Hopper +groovyscript.wiki.betterwithmods.hopper.description=Recipes for the Filtered Hopper to process. The filter targeted must allow the input item in to function. +groovyscript.wiki.betterwithmods.hopper.name.value=Sets the name of the filter used for the recipe, the input item must be capable of passing through the filter to be processed +groovyscript.wiki.betterwithmods.hopper.inWorldItemOutput.value=Sets the items dropped in-world when the recipe is processed + +groovyscript.wiki.betterwithmods.hopper_filters.title=Hopper Filters +groovyscript.wiki.betterwithmods.hopper_filters.description=Items placed in the middle slot of the Filtered Hopper to restrict what is capable of passing through. +groovyscript.wiki.betterwithmods.hopper_filters.filter.value=Sets the filter itemstack +groovyscript.wiki.betterwithmods.hopper_filters.filtered.value=Sets the valid items allowed through the filter +groovyscript.wiki.betterwithmods.hopper_filters.removeByFilter=Removes all filters with the given filter item +groovyscript.wiki.betterwithmods.hopper_filters.removeByFiltered=Removes all filters with the given items allowed through the filter +groovyscript.wiki.betterwithmods.hopper_filters.removeByName=Removes the filter with the given name + +groovyscript.wiki.betterwithmods.kiln.title=Kiln +groovyscript.wiki.betterwithmods.kiln.description=Converts a block into up to three output itemstacks, with the ability to require specific amounts of heat. +groovyscript.wiki.betterwithmods.kiln.heat.value=Sets if the Kiln requires a normal fire (1) or a Stoked Fire (2) below it +groovyscript.wiki.betterwithmods.kiln.input.value=Sets the input block of the recipe +groovyscript.wiki.betterwithmods.kiln.ignoreHeat.value=Sets if the Kiln requires any heat source below it + +groovyscript.wiki.betterwithmods.mill_stone.title=Mill Stone +groovyscript.wiki.betterwithmods.mill_stone.description=Converts input itemstacks into output itemstacks after being ground via rotation power for a given time. +groovyscript.wiki.betterwithmods.mill_stone.ticks.value=Sets the time in ticks the recipe takes to complete +groovyscript.wiki.betterwithmods.mill_stone.priority.value=Sets the priority of the recipe in relation to other valid recipes for the given items +groovyscript.wiki.betterwithmods.mill_stone.soundEvent.value=Sets what sound is played by the mill during the recipe + +groovyscript.wiki.betterwithmods.saw.title=Saw +groovyscript.wiki.betterwithmods.saw.description=Converts a block into output itemstacks after being powered via rotation power. +groovyscript.wiki.betterwithmods.saw.input.value=Sets the input block of the recipe + +groovyscript.wiki.betterwithmods.turntable.title=Turntable +groovyscript.wiki.betterwithmods.turntable.description=Converts a block into an output block and up to two itemstacks after being powered via rotation power. +groovyscript.wiki.betterwithmods.turntable.input.value=Sets the input block +groovyscript.wiki.betterwithmods.turntable.rotations.value=Sets the number of rotations required to complete the recipe +groovyscript.wiki.betterwithmods.turntable.outputBlock.value=Sets the blockstate that replaces the input block + + # Blood Magic groovyscript.wiki.bloodmagic.alchemy_array.title=Alchemy Array groovyscript.wiki.bloodmagic.alchemy_array.description=Converts two items into an output itemstack by using Arcane Ashes in-world. Has a configurable texture for the animation. diff --git a/src/main/resources/mixin.groovyscript.betterwithmods.json b/src/main/resources/mixin.groovyscript.betterwithmods.json new file mode 100644 index 000000000..0a27c47d3 --- /dev/null +++ b/src/main/resources/mixin.groovyscript.betterwithmods.json @@ -0,0 +1,11 @@ +{ + "package": "com.cleanroommc.groovyscript.core.mixin.betterwithmods", + "refmap": "mixins.groovyscript.refmap.json", + "target": "@env(DEFAULT)", + "minVersion": "0.8", + "compatibilityLevel": "JAVA_8", + "mixins": [ + "BWMHeatRegistryAccessor", + "HopperFiltersAccessor" + ] +} \ No newline at end of file