From d41ef92b3a6759b198adea3f11f5063f0f8faa7d Mon Sep 17 00:00:00 2001 From: samszotkowski <94253934+samszotkowski@users.noreply.github.com> Date: Sat, 20 Dec 2025 22:30:28 -0500 Subject: [PATCH 01/13] loot tracker: include metadata in published event --- .../net/runelite/client/plugins/loottracker/LootReceived.java | 1 + .../runelite/client/plugins/loottracker/LootTrackerPlugin.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootReceived.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootReceived.java index ecaa869a2a0..7cfaae0f5f5 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootReceived.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootReceived.java @@ -42,4 +42,5 @@ public class LootReceived private LootRecordType type; private Collection items; private int amount; + private Object metadata; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java index 0086322f7ac..e00ba619928 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java @@ -681,7 +681,7 @@ void addLoot(@NonNull String name, int combatLevel, LootRecordType type, Object queuedLoots.add(lootRecord); } - eventBus.post(new LootReceived(name, combatLevel, type, items, amount)); + eventBus.post(new LootReceived(name, combatLevel, type, items, amount, metadata)); } private Integer getLootWorldId() From d2c99287a6970169d2ed22b26caacf968c08b660 Mon Sep 17 00:00:00 2001 From: Rhea Date: Sun, 21 Dec 2025 10:59:39 -0500 Subject: [PATCH 02/13] all: move to gradle --- .github/workflows/CI.yml | 25 +- .gitignore | 44 +- .mvn/jvm.config | 1 - build.gradle.kts | 43 ++ cache/build.gradle.kts | 117 ++++ cache/pom.xml | 178 ------ cache/settings.gradle.kts | 27 + ci/build.sh | 3 +- ci/settings.xml | 280 ---------- common.settings.gradle.kts | 107 ++++ .../checkstyle/checkstyle.xml | 3 +- .../checkstyle/suppressions.xml | 3 + gradle.properties | 33 ++ gradle/verification-metadata.xml | 468 ++++++++++++++++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 60756 bytes gradle/wrapper/gradle-wrapper.properties | 7 + gradlew | 234 ++++++++ gradlew.bat | 89 +++ libs.versions.toml | 85 +++ pom.xml | 332 ----------- runelite-api/build.gradle.kts | 146 +++++ runelite-api/pom.xml | 178 ------ runelite-api/settings.gradle.kts | 29 + runelite-client/build.gradle.kts | 199 +++++++ runelite-client/pom.xml | 517 ------------------ runelite-client/settings.gradle.kts | 30 + runelite-gradle-plugin/build.gradle.kts | 68 +++ .../pmd-ruleset.xml | 0 runelite-gradle-plugin/settings.gradle.kts | 29 + .../gradle/assemble/AssemblePlugin.java | 46 ++ .../gradle/assemble/AssembleTask.java | 77 +-- .../gradle/component/ComponentPlugin.java | 53 ++ .../gradle/component/ComponentTask.java | 103 ++-- .../runelite/gradle/index/IndexPlugin.java | 44 ++ .../net/runelite/gradle/index/IndexTask.java | 47 +- .../gradle/jarsign/JarsignExtension.java | 41 ++ .../gradle/jarsign/JarsignPlugin.java | 82 +++ .../runelite/gradle/jarsign/JarsignTask.java | 94 ++++ runelite-jshell/build.gradle.kts | 55 ++ runelite-jshell/pom.xml | 87 --- runelite-jshell/settings.gradle.kts | 27 + runelite-maven-plugin/pom.xml | 112 ---- settings.gradle.kts | 31 ++ 43 files changed, 2347 insertions(+), 1827 deletions(-) delete mode 100644 .mvn/jvm.config create mode 100644 build.gradle.kts create mode 100644 cache/build.gradle.kts delete mode 100644 cache/pom.xml create mode 100644 cache/settings.gradle.kts delete mode 100644 ci/settings.xml create mode 100644 common.settings.gradle.kts rename checkstyle.xml => config/checkstyle/checkstyle.xml (96%) rename suppressions.xml => config/checkstyle/suppressions.xml (87%) create mode 100644 gradle.properties create mode 100644 gradle/verification-metadata.xml create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 libs.versions.toml delete mode 100644 pom.xml create mode 100644 runelite-api/build.gradle.kts delete mode 100644 runelite-api/pom.xml create mode 100644 runelite-api/settings.gradle.kts create mode 100644 runelite-client/build.gradle.kts delete mode 100644 runelite-client/pom.xml create mode 100644 runelite-client/settings.gradle.kts create mode 100644 runelite-gradle-plugin/build.gradle.kts rename {runelite-maven-plugin => runelite-gradle-plugin}/pmd-ruleset.xml (100%) create mode 100644 runelite-gradle-plugin/settings.gradle.kts create mode 100644 runelite-gradle-plugin/src/main/java/net/runelite/gradle/assemble/AssemblePlugin.java rename runelite-maven-plugin/src/main/java/net/runelite/mvn/AssembleMojo.java => runelite-gradle-plugin/src/main/java/net/runelite/gradle/assemble/AssembleTask.java (68%) create mode 100644 runelite-gradle-plugin/src/main/java/net/runelite/gradle/component/ComponentPlugin.java rename runelite-maven-plugin/src/main/java/net/runelite/mvn/ComponentMojo.java => runelite-gradle-plugin/src/main/java/net/runelite/gradle/component/ComponentTask.java (62%) create mode 100644 runelite-gradle-plugin/src/main/java/net/runelite/gradle/index/IndexPlugin.java rename runelite-maven-plugin/src/main/java/net/runelite/mvn/IndexMojo.java => runelite-gradle-plugin/src/main/java/net/runelite/gradle/index/IndexTask.java (69%) create mode 100644 runelite-gradle-plugin/src/main/java/net/runelite/gradle/jarsign/JarsignExtension.java create mode 100644 runelite-gradle-plugin/src/main/java/net/runelite/gradle/jarsign/JarsignPlugin.java create mode 100644 runelite-gradle-plugin/src/main/java/net/runelite/gradle/jarsign/JarsignTask.java create mode 100644 runelite-jshell/build.gradle.kts delete mode 100644 runelite-jshell/pom.xml create mode 100644 runelite-jshell/settings.gradle.kts delete mode 100644 runelite-maven-plugin/pom.xml create mode 100644 settings.gradle.kts diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index ebf2f182577..b338871fc6c 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -9,27 +9,32 @@ jobs: permissions: contents: read - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - persist-credentials: false + env: + GRADLE_OPTS: "-Dfile:encoding=UTF-8" - - name: Cache - uses: actions/cache@v4 + steps: + - name: Cache glslang + uses: actions/cache@v5 with: path: | - ~/.m2/repository ~/.cache/runelite - key: ${{ runner.os }}-cache-${{ hashFiles('**/pom.xml', '**/build.sh', '**/pmd-ruleset.xml') }} + key: ${{ runner.os }}-cache-${{ hashFiles('ci/build.sh') }} restore-keys: | ${{ runner.os }}-cache- + - name: Checkout + uses: actions/checkout@v4 + - name: Set up JDK 11 uses: actions/setup-java@v4 with: distribution: temurin java-version: 11 - - name: Build + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + gradle-version: wrapper + + - name: Build and Test run: ./ci/build.sh diff --git a/.gitignore b/.gitignore index ff14386ce44..f35ca9ebe00 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,39 @@ -target -nbactions.xml -nb-configuration.xml -/nbproject/ -project.properties -*.iml +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### IntelliJ IDEA ### .idea/ -.project -.settings/ +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Eclipse ### +.apt_generated .classpath -.vscode .factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### .DS_Store \ No newline at end of file diff --git a/.mvn/jvm.config b/.mvn/jvm.config deleted file mode 100644 index 67bd169f319..00000000000 --- a/.mvn/jvm.config +++ /dev/null @@ -1 +0,0 @@ --Xmx512m diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 00000000000..a9e40b66a18 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2024, LlemonDuck + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +tasks.register("cleanAll") { + gradle.includedBuilds.forEach { build -> this@register.dependsOn(build.task(":clean")) } +} +tasks.register("buildAll") { + gradle.includedBuilds.forEach { build -> this@register.dependsOn(build.task(":build")) } +} +tasks.register("assembleAll") { + gradle.includedBuilds.forEach { build -> this@register.dependsOn(build.task(":assemble")) } +} +tasks.register("testAll") { + gradle.includedBuilds.forEach { build -> this@register.dependsOn(build.task(":test")) } +} +tasks.register("publishAll") { + this@register.dependsOn(gradle.includedBuild("cache").task(":publish")) + this@register.dependsOn(gradle.includedBuild("runelite-api").task(":publish")) + this@register.dependsOn(gradle.includedBuild("runelite-client").task(":publish")) + this@register.dependsOn(gradle.includedBuild("runelite-jshell").task(":publish")) +} diff --git a/cache/build.gradle.kts b/cache/build.gradle.kts new file mode 100644 index 00000000000..d9a04312a88 --- /dev/null +++ b/cache/build.gradle.kts @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2024, LlemonDuck + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +plugins { + java + `maven-publish` + antlr + alias(libs.plugins.lombok) +} + +lombok.version = libs.versions.lombok.get() + +java { + withJavadocJar() + withSourcesJar() +} + +dependencies { + antlr(libs.antlr.core) + implementation(libs.antlr.runtime) + + implementation(libs.guava) + implementation(libs.slf4j.api) + runtimeOnly(libs.slf4j.simple) + implementation(libs.commons.compress) + implementation(libs.gson) + implementation(libs.commons.cli) + implementation(libs.jna.core) + + testImplementation(libs.junit) + testImplementation(libs.rs.cache) +} + +// the gradle antlr plugin adds all of antlr to runtimeClasspath, +// workaround that https://github.com/gradle/gradle/issues/820 +configurations { + api { + setExtendsFrom(extendsFrom.filterNot { it == antlr.get() }) + } +} + +sourceSets { + main { + antlr { setSrcDirs(listOf("src/main/antlr4")) } + } +} + +publishing { + publications { + create("cache") { + from(components["java"]) + } + } +} + +tasks.processTestResources { + filesMatching("cache.properties") { + filter { it.replace("\${rs.version}", libs.versions.rs.get()) } + filter { it.replace("\${cache.version}", libs.versions.cache.get()) } + } +} + +tasks.test { + enabled = false + jvmArgs("-Xmx2048m") +} + +// everything from here down is accounting for antlr sources in varying ways +tasks.named("sourcesJar", Jar::class) { + dependsOn(tasks.generateGrammarSource) + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + exclude("net/runelite/cache/script/assembler/*.interp") + exclude("net/runelite/cache/script/assembler/*.tokens") +} + +tasks.javadoc { + exclude("net/runelite/cache/script/assembler/*.interp") + exclude("net/runelite/cache/script/assembler/*.tokens") +} + +tasks.checkstyleMain { + exclude("net/runelite/cache/script/assembler/*.java") +} + +tasks.generateGrammarSource { + arguments.addAll(listOf("-package", "net.runelite.cache.script.assembler")) +} + +afterEvaluate { + tasks.named("generateEffectiveLombokConfig") { + // lombok won't find anything in the antlr generated sources, but it looks in there regardless + // and gradle complains if you don't provide an explicit task dependency between the two + dependsOn(tasks.generateGrammarSource) + } +} diff --git a/cache/pom.xml b/cache/pom.xml deleted file mode 100644 index 5085ec783ef..00000000000 --- a/cache/pom.xml +++ /dev/null @@ -1,178 +0,0 @@ - - - - 4.0.0 - - - net.runelite - runelite-parent - 1.12.10-SNAPSHOT - - - cache - Cache - - - 165 - - 4.13.1 - - - - - com.google.guava - guava - - - org.slf4j - slf4j-api - - - org.slf4j - slf4j-simple - true - - - org.apache.commons - commons-compress - 1.10 - - - com.google.code.gson - gson - - - org.antlr - antlr4-runtime - ${antlr4.version} - - - commons-cli - commons-cli - 1.3.1 - - - org.projectlombok - lombok - provided - - - net.java.dev.jna - jna - 5.9.0 - - - - junit - junit - 4.12 - test - - - net.runelite.rs - cache - ${cache.version} - test - - - - - - - src/test/resources - true - - - - - org.apache.maven.plugins - maven-surefire-plugin - - true - -Xmx2048m - - ${cache.tmpdir} - - - - - maven-assembly-plugin - - - jar-with-dependencies - - - - - make-assembly - package - - single - - - - - - org.antlr - antlr4-maven-plugin - ${antlr4.version} - - - process-resources - - antlr4 - - - - - - - org.projectlombok - lombok-maven-plugin - - - org.apache.maven.plugins - maven-javadoc-plugin - - - ${project.build.directory}/delombok;${project.build.directory}/generated-sources/antlr4 - - - - org.apache.maven.plugins - maven-jar-plugin - - - - test-jar - - - - - - - diff --git a/cache/settings.gradle.kts b/cache/settings.gradle.kts new file mode 100644 index 00000000000..ccc997b18fe --- /dev/null +++ b/cache/settings.gradle.kts @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024, LlemonDuck + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +rootProject.name = "cache" +apply(from = "../common.settings.gradle.kts") diff --git a/ci/build.sh b/ci/build.sh index a8092698243..e863cdf7bf6 100755 --- a/ci/build.sh +++ b/ci/build.sh @@ -15,4 +15,5 @@ if [ ! -f "${GLSLANG_ARCHIVE}" ] || [ ! -d "${GLSLANG_DIR}" ] || ! echo "${GLSLA unzip -o -q "${GLSLANG_ARCHIVE}" -d "${GLSLANG_DIR}" fi -mvn verify --settings ci/settings.xml -Dglslang.path="${GLSLANG_DIR}/bin/glslangValidator" +export ORG_GRADLE_PROJECT_glslangPath="$GLSLANG_DIR/bin/glslangValidator" +./gradlew ':buildAll' diff --git a/ci/settings.xml b/ci/settings.xml deleted file mode 100644 index 44c93408311..00000000000 --- a/ci/settings.xml +++ /dev/null @@ -1,280 +0,0 @@ - - - - - - - - - - false - - - - - - - - - - - - - - - - - - - - - runelite - repo - ${env.REPO_PASSWORD} - - - - - - - - - - - - - - - - - runelite - - - false - false - false - ${user.home}/.cache/runelite/pmd.cache - true - - - - - - - runelite - - diff --git a/common.settings.gradle.kts b/common.settings.gradle.kts new file mode 100644 index 00000000000..dac40c65cf5 --- /dev/null +++ b/common.settings.gradle.kts @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2024, LlemonDuck + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +dependencyResolutionManagement { + repositories { + maven(uri("https://repo.runelite.net")) { + name = "rrn" + + content { + includeGroupAndSubgroups("net.runelite") + excludeModule("net.runelite.rs", "vanilla") + } + } + mavenCentral { + content { excludeGroupAndSubgroups("net.runelite") } + } + } + + versionCatalogs { + create("libs") { + from(files("./libs.versions.toml")) + } + } +} + +// set up some defaults +val rootProps = file("./gradle.properties").inputStream().use { stream -> + java.util.Properties().apply { load(stream) } +} +val checkstyleDir = file("./config/checkstyle/") +gradle.beforeProject { + apply(plugin = "idea") + apply(plugin = "checkstyle") + group = rootProps["project.build.group"] as String + version = rootProps["project.build.version"] as String + + tasks.withType { + options.encoding = "UTF-8" + options.release = 11 + } +} + +gradle.afterProject { + tasks.withType { + (this.options as StandardJavadocDocletOptions).apply { + quiet() + encoding("UTF-8") + use(true) + bottom("Copyright © 2014") + links( + "https://docs.oracle.com/en/java/javase/11/docs/api/" + ) + } + } + + // shared checkstyle config + extensions.findByType()?.run { + toolVersion = "8.3" + configDirectory = file("../config/checkstyle") + } + + // shared publishing config + tasks.withType { enabled = false } + extensions.findByType()?.run { + repositories { + maven(uri(providers.gradleProperty("rlMavenUrl").getOrElse("https://repo.runelite.net"))) { + name = "rlMaven" + if (url.scheme != "file") { credentials(PasswordCredentials::class) } + } + } + } + + // produce reproducible outputs + tasks.withType { + isPreserveFileTimestamps = false + isReproducibleFileOrder = true + } + + extensions.findByType()?.run { + module { + isDownloadSources = true + isDownloadJavadoc = true + } + } +} diff --git a/checkstyle.xml b/config/checkstyle/checkstyle.xml similarity index 96% rename from checkstyle.xml rename to config/checkstyle/checkstyle.xml index 97efb7194ea..c8287888985 100644 --- a/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -27,6 +27,7 @@ "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN" "https://checkstyle.org/dtds/configuration_1_3.dtd"> + @@ -57,6 +58,6 @@ - + diff --git a/suppressions.xml b/config/checkstyle/suppressions.xml similarity index 87% rename from suppressions.xml rename to config/checkstyle/suppressions.xml index 962c26ceb0d..004da9e22e2 100644 --- a/suppressions.xml +++ b/config/checkstyle/suppressions.xml @@ -28,5 +28,8 @@ "https://checkstyle.org/dtds/suppressions_1_1.dtd"> + + + diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 00000000000..e94a2c8e523 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,33 @@ +# +# Copyright (c) 2024, LlemonDuck +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +org.gradle.jvmargs=-Dfile.encoding=UTF-8 -Xmx1024m +org.gradle.parallel=true +org.gradle.caching=true + +project.build.group=net.runelite +project.build.version=1.12.10-SNAPSHOT + +glslang.path= diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml new file mode 100644 index 00000000000..fdf12ea6f85 --- /dev/null +++ b/gradle/verification-metadata.xml @@ -0,0 +1,468 @@ + + + + false + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..249e5832f090a2944b7473328c07c9755baa3196 GIT binary patch literal 60756 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 '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 00000000000..107acd32c4e --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/libs.versions.toml b/libs.versions.toml new file mode 100644 index 00000000000..ab8e298c7e7 --- /dev/null +++ b/libs.versions.toml @@ -0,0 +1,85 @@ +# +# Copyright (c) 2024, LlemonDuck +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +[versions] +rs = "235" +cache = "165" + +flatlaf = "3.2.5-rl4" +guice = "4.1.0" +lombok = "1.18.30" +logback = "1.2.9" +lwjgl = "3.3.2" +slf4j = "1.7.32" +antlr = "4.13.1" + + +[libraries] +rs-vanilla = { module = "net.runelite.rs:vanilla", version.ref = "rs" } +rs-cache = { module = "net.runelite.rs:cache", version.ref = "cache" } + +flatlaf-core = { module = "net.runelite:flatlaf", version.ref = "flatlaf" } +flatlaf-extras = { module = "net.runelite:flatlaf-extras", version.ref = "flatlaf" } +rl-http-api = "net.runelite.arn:http-api:1.2.21" +rl-awt = "net.runelite:rlawt:1.7" +rl-discord = "net.runelite:discord:1.4" +rl-orange = "net.runelite:orange-extensions:1.1" + +antlr-core = { module = "org.antlr:antlr4", version.ref="antlr" } +antlr-runtime = { module = "org.antlr:antlr4-runtime", version.ref="antlr" } +commons-compress = "org.apache.commons:commons-compress:1.10" +commons-text = "org.apache.commons:commons-text:1.2" +commons-cli = "commons-cli:commons-cli:1.3.1" +fife-rsyntaxtextarea = "com.fifesoft:rsyntaxtextarea:3.1.2" +fife-autocomplete = "com.fifesoft:autocomplete:3.1.1" +findbugs = "com.google.code.findbugs:jsr305:3.0.2" +gson = "com.google.code.gson:gson:2.8.5" +guava = "com.google.guava:guava:23.2-jre" +guice-core = { module = "com.google.inject:guice", version.ref = "guice" } +guice-testlib = { module = "com.google.inject.extensions:guice-testlib", version.ref = "guice" } +guice-grapher = { module = "com.google.inject.extensions:guice-grapher", version.ref = "guice" } +hamcrest = "org.hamcrest:hamcrest-library:1.3" +javapoet = "com.squareup:javapoet:1.13.0" +javax-inject = "javax.inject:javax.inject:1" +jetbrains-annotations = "org.jetbrains:annotations:23.0.0" +jna-core = "net.java.dev.jna:jna:5.9.0" +jna-platform = "net.java.dev.jna:jna-platform:5.9.0" +jopt = "net.sf.jopt-simple:jopt-simple:5.0.1" +junit = "junit:junit:4.12" +logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } +logback-core = { module = "ch.qos.logback:logback-core", version.ref = "logback" } +lwjgl-core = { module = "org.lwjgl:lwjgl", version.ref = "lwjgl" } +lwjgl-opengl = { module = "org.lwjgl:lwjgl-opengl", version.ref = "lwjgl" } +lwjgl-opencl = { module = "org.lwjgl:lwjgl-opencl", version.ref = "lwjgl" } +mockito = "org.mockito:mockito-core:3.1.0" +okhttp-mockserver = "com.squareup.okhttp3:mockwebserver:3.14.9" +protobuf = "com.google.protobuf:protobuf-javalite:3.21.12" +slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } +slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j" } +tomlj = "org.tomlj:tomlj:1.1.0" + + + +[plugins] +lombok = { id = "io.freefair.lombok", version = "8.10.2" } diff --git a/pom.xml b/pom.xml deleted file mode 100644 index fc5e0934cb0..00000000000 --- a/pom.xml +++ /dev/null @@ -1,332 +0,0 @@ - - - - 4.0.0 - - net.runelite - runelite-parent - 1.12.10-SNAPSHOT - pom - - RuneLite - Open source RuneScape client - http://runelite.net - - - UTF-8 - 1 - 1.18.30 - 1.2.9 - 1.7.25 - 3.2.5-rl4 - - true - true - - - - - - 2-Clause BSD License - https://opensource.org/licenses/BSD-2-Clause - - - - 2014 - - - https://github.com/runelite/runelite - scm:git:git://github.com/runelite/runelite - scm:git:git@github.com:runelite/runelite - HEAD - - - - - Adam- - Adam - Adam@sigterm.info - - - - - GitHub Issues - https://github.com/runelite/runelite/issues - - - - - central - Central Repository - https://repo.maven.apache.org/maven2 - - false - - - - runelite - RuneLite - https://repo.runelite.net - - true - always - - - - - - central - Central Repository - https://repo.maven.apache.org/maven2 - - never - - - false - - - - runelite-plugins - RuneLite Plugins - https://repo.runelite.net - - true - always - - - - - - cache - runelite-api - runelite-client - runelite-jshell - runelite-maven-plugin - - - - - - com.google.guava - guava - 23.2-jre - - - org.projectlombok - lombok - ${lombok.version} - provided - - - com.google.code.gson - gson - 2.8.5 - - - com.google.code.findbugs - jsr305 - 3.0.2 - - - ch.qos.logback - logback-classic - ${logback.version} - - - ch.qos.logback - logback-core - ${logback.version} - - - org.slf4j - slf4j-api - ${slf4j.version} - - - org.slf4j - slf4j-simple - ${slf4j.version} - - - com.google.inject - guice-bom - 4.1.0 - pom - import - - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - 11 - - - - org.apache.maven.plugins - maven-surefire-plugin - 2.22.2 - - true - -Xmx512m -Duser.language=en -Duser.region=US - - ${glslang.path} - - - - - org.apache.maven.plugins - maven-release-plugin - 2.5.3 - - - org.projectlombok - lombok-maven-plugin - 1.18.20.0 - - - ${maven.javadoc.skip} - ${project.basedir}/src/main/java - - ${project.build.directory}/delombok - false - - - - generate-sources - - delombok - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 3.5.0 - - - ${project.build.directory}/delombok - - Copyright © {inceptionYear} - - - - - attach-javadocs - - jar - - - - - - org.apache.maven.plugins - maven-checkstyle-plugin - 2.17 - - - com.puppycrawl.tools - checkstyle - 8.3 - - - - - verify-style - process-classes - - check - - - - - checkstyle.xml - - - ${project.build.sourceDirectory} - - true - - - - - - - org.apache.maven.plugins - maven-assembly-plugin - 3.2.0 - - - org.apache.maven.plugins - maven-jar-plugin - 3.2.0 - - - org.apache.maven.plugins - maven-compiler-plugin - 3.6.1 - - - org.apache.maven.plugins - maven-deploy-plugin - 2.8.2 - - - org.apache.maven.plugins - maven-plugin-plugin - 3.6.0 - - - org.apache.maven.plugins - maven-pmd-plugin - 3.22.0 - - - net.sourceforge.pmd - pmd-core - 7.2.0 - - - net.sourceforge.pmd - pmd-java - 7.2.0 - - - - - - - diff --git a/runelite-api/build.gradle.kts b/runelite-api/build.gradle.kts new file mode 100644 index 00000000000..0f0fc84ffc3 --- /dev/null +++ b/runelite-api/build.gradle.kts @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2024, LlemonDuck + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +plugins { + java + `maven-publish` + checkstyle + alias(libs.plugins.lombok) + + id("net.runelite.runelite-gradle-plugin.component") +} + +lombok.version = libs.versions.lombok.get() + +java { + withJavadocJar() + withSourcesJar() +} + +dependencies { + implementation(libs.slf4j.api) + compileOnly(libs.findbugs) + + compileOnly(libs.jetbrains.annotations) + + testImplementation(libs.junit) + testRuntimeOnly(libs.slf4j.simple) +} + +val runtimeJar = tasks.register("runtimeJar") { + this@register.group = BasePlugin.BUILD_GROUP + + from(sourceSets.main.get().output) + archiveClassifier = "runtime" + + /* from JLS 13.1.3: references to a static field that is a constant variable (§4.12.4) must be resolved + at compile time to the value denoted by the constant variable's initializer *and* no references to the + field should be present in the code in a binary file. + + This permits us to remove the classes containing only these types of fields at runtime */ + exclude("net/runelite/api/annotations/*.class") + exclude("net/runelite/api/clan/ClanID.class") + exclude("net/runelite/api/dbtable/DBTableID*.class") + exclude("net/runelite/api/widgets/ComponentID.class") + exclude("net/runelite/api/widgets/InterfaceID.class") + exclude("net/runelite/api/widgets/ItemQuantityMode.class") + exclude("net/runelite/api/widgets/WidgetID*.class") + exclude("net/runelite/api/widgets/WidgetModalMode.class") + exclude("net/runelite/api/widgets/WidgetModelType.class") + exclude("net/runelite/api/widgets/WidgetPositionMode.class") + exclude("net/runelite/api/widgets/WidgetSizeMode.class") + exclude("net/runelite/api/widgets/WidgetTextAlignment.class") + exclude("net/runelite/api/widgets/WidgetType.class") + exclude("net/runelite/api/AnimationID.class") + exclude("net/runelite/api/CollisionDataFlag.class") + exclude("net/runelite/api/EnumID.class") + exclude("net/runelite/api/FontID.class") + exclude("net/runelite/api/GraphicID.class") + exclude("net/runelite/api/HintArrowType.class") + exclude("net/runelite/api/HitsplatID.class") + exclude("net/runelite/api/ItemID.class") + exclude("net/runelite/api/KeyCode.class") + exclude("net/runelite/api/NpcID.class") + exclude("net/runelite/api/NullItemID.class") + exclude("net/runelite/api/NullNpcID.class") + exclude("net/runelite/api/NullObjectID.class") + exclude("net/runelite/api/ObjectID.class") + exclude("net/runelite/api/Opcodes.class") + exclude("net/runelite/api/ParamID.class") + exclude("net/runelite/api/ScriptID.class") + exclude("net/runelite/api/SettingID.class") + exclude("net/runelite/api/SkullIcon.class") + exclude("net/runelite/api/SoundEffectID.class") + exclude("net/runelite/api/SoundEffectVolume.class") + exclude("net/runelite/api/SpriteID.class") + exclude("net/runelite/api/StructID.class") + exclude("net/runelite/api/Varbits.class") + exclude("net/runelite/api/VarClientInt.class") + exclude("net/runelite/api/VarClientStr.class") + exclude("net/runelite/api/VarPlayer.class") + exclude("net/runelite/api/gameval/*.class") +} +tasks.assemble { dependsOn(runtimeJar) } + +publishing { + publications { + create("api") { + from(components["java"]) + if (!project.version.toString().endsWith("-SNAPSHOT")) { + artifact(runtimeJar) { classifier = "runtime" } + } + } + } +} +tasks.withType { + onlyIf { + publication == publishing.publications["api"] || !version.toString().endsWith("-SNAPSHOT") + } +} + +tasks.withType { + inputFile = file("src/main/interfaces/interfaces.toml") + outputDirectory = file("build/generated/sources/runelite/java/main") +} + +tasks.checkstyleMain { + exclude("net/runelite/api/widgets/ComponentID.java") + exclude("net/runelite/api/widgets/InterfaceID.java") +} + +tasks.javadoc { + title = "RuneLite API ${project.version} API" + + exclude( + "net/runelite/api/gameval/**", + "net/runelite/api/AnimationID.java", + "net/runelite/api/ItemID.java", + "net/runelite/api/NullItemID.java", + "net/runelite/api/ObjectID.java", + "net/runelite/api/NullObjectID.java", + "net/runelite/api/NpcID.java", + "net/runelite/api/NullNpcID.java", + ) +} diff --git a/runelite-api/pom.xml b/runelite-api/pom.xml deleted file mode 100644 index c9715db53c4..00000000000 --- a/runelite-api/pom.xml +++ /dev/null @@ -1,178 +0,0 @@ - - - - 4.0.0 - - - net.runelite - runelite-parent - 1.12.10-SNAPSHOT - - - runelite-api - RuneLite API - https://static.runelite.net/runelite-api - - - - org.slf4j - slf4j-api - - - org.projectlombok - lombok - provided - - - com.google.code.findbugs - jsr305 - provided - - - org.jetbrains - annotations - 23.0.0 - provided - - - - junit - junit - 4.12 - test - - - org.slf4j - slf4j-simple - test - - - - - - - org.apache.maven.plugins - maven-jar-plugin - - - - runtime-jar - package - - jar - - - runtime - - net/runelite/api/annotations/*.class - net/runelite/api/clan/ClanID.class - net/runelite/api/dbtable/DBTableID*.class - net/runelite/api/widgets/ComponentID.class - net/runelite/api/widgets/InterfaceID.class - net/runelite/api/widgets/ItemQuantityMode.class - net/runelite/api/widgets/WidgetID*.class - net/runelite/api/widgets/WidgetModalMode.class - net/runelite/api/widgets/WidgetModelType.class - net/runelite/api/widgets/WidgetPositionMode.class - net/runelite/api/widgets/WidgetSizeMode.class - net/runelite/api/widgets/WidgetTextAlignment.class - net/runelite/api/widgets/WidgetType.class - net/runelite/api/AnimationID.class - net/runelite/api/CollisionDataFlag.class - net/runelite/api/EnumID.class - net/runelite/api/FontID.class - net/runelite/api/GraphicID.class - net/runelite/api/HintArrowType.class - net/runelite/api/HitsplatID.class - net/runelite/api/ItemID.class - net/runelite/api/KeyCode.class - net/runelite/api/NpcID.class - net/runelite/api/NullItemID.class - net/runelite/api/NullNpcID.class - net/runelite/api/NullObjectID.class - net/runelite/api/ObjectID.class - net/runelite/api/Opcodes.class - net/runelite/api/ParamID.class - net/runelite/api/ScriptID.class - net/runelite/api/SettingID.class - net/runelite/api/SkullIcon.class - net/runelite/api/SoundEffectID.class - net/runelite/api/SoundEffectVolume.class - net/runelite/api/SpriteID.class - net/runelite/api/StructID.class - net/runelite/api/Varbits.class - net/runelite/api/VarClientInt.class - net/runelite/api/VarClientStr.class - net/runelite/api/VarPlayer.class - net/runelite/api/gameval/*.class - - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - - - ${project.build.directory}/delombok;${project.build.directory}/generated-sources - net.runelite.api.gameval - - net/runelite/api/AnimationID.java - net/runelite/api/ItemID.java - net/runelite/api/NullItemID.java - net/runelite/api/ObjectID.java - net/runelite/api/NullObjectID.java - net/runelite/api/NpcID.java - net/runelite/api/NullNpcID.java - - - - - net.runelite - runelite-maven-plugin - ${project.version} - - - pack-components - - pack-components - - - src/main/interfaces - ${project.build.directory}/generated-sources - - - - - - - diff --git a/runelite-api/settings.gradle.kts b/runelite-api/settings.gradle.kts new file mode 100644 index 00000000000..b640a5c2f81 --- /dev/null +++ b/runelite-api/settings.gradle.kts @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024, LlemonDuck + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +rootProject.name = "runelite-api" +apply(from = "../common.settings.gradle.kts") + +includeBuild("../runelite-gradle-plugin") diff --git a/runelite-client/build.gradle.kts b/runelite-client/build.gradle.kts new file mode 100644 index 00000000000..a24d361d61c --- /dev/null +++ b/runelite-client/build.gradle.kts @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2024, LlemonDuck + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import java.io.ByteArrayOutputStream + +plugins { + java + `java-library` + `maven-publish` + pmd + alias(libs.plugins.lombok) + + id("net.runelite.runelite-gradle-plugin.assemble") + id("net.runelite.runelite-gradle-plugin.index") + id("net.runelite.runelite-gradle-plugin.jarsign") +} + +lombok.version = libs.versions.lombok.get() + +java { + withJavadocJar() + withSourcesJar() +} + +dependencies { + api("net.runelite:runelite-api:${project.version}") + implementation("net.runelite:jshell:${project.version}") + runtimeOnly("net.runelite:injected-client:${project.version}") + + api(libs.rl.http.api) + implementation(libs.rl.discord) + implementation(libs.rl.awt) + compileOnly(libs.rl.orange) + + api(libs.slf4j.api) + implementation(libs.logback.classic) + implementation(libs.jopt) + api(libs.guava) { + exclude("com.google.code.findbugs", "jsr305") + exclude("com.google.errorprone", "error_prone_annotations") + exclude("com.google.j2objc", "j2objc-annotations") + exclude("org.codehaus.mojo", "animal-sniffer-annotations") + } + api(variantOf(libs.guice.core) { classifier("no_aop") }) + api(libs.gson) + implementation(libs.flatlaf.core) + implementation(libs.flatlaf.extras) + implementation(libs.commons.text) + implementation(libs.jna.core) + implementation(libs.jna.platform) + implementation(libs.findbugs) + compileOnly(libs.jetbrains.annotations) + implementation(libs.protobuf) + api(libs.lwjgl.core) + api(libs.lwjgl.opengl) + api(libs.lwjgl.opencl) + + for (platform in listOf( + "linux", + "linux-arm64", + "macos", + "macos-arm64", + "windows-x86", + "windows", + "windows-arm64", + )) { + runtimeOnly(variantOf(libs.lwjgl.core) { classifier("natives-$platform") }) + runtimeOnly(variantOf(libs.lwjgl.opengl) { classifier("natives-$platform") }) + } + + testImplementation(libs.junit) + testImplementation(libs.hamcrest) + testImplementation(libs.mockito) + testImplementation(libs.guice.testlib) + testImplementation(libs.guice.grapher) + testImplementation(libs.okhttp.mockserver) +} + +val shadowJar = tasks.register("shadowJar") { + dependsOn(configurations.runtimeClasspath) + manifest { + attributes["Main-Class"] = "net.runelite.client.RuneLite" + } + + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + + from(sourceSets.main.get().output) + from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) }) + + exclude( + "META-INF/INDEX.LIST", + "META-INF/*.SF", + "META-INF/*.DSA", + "META-INF/*.RSA", + "**/module-info.class" + ) + + group = BasePlugin.BUILD_GROUP + archiveClassifier = "shadow" + archiveFileName = project.name + "-" + project.version + "-shaded.jar" +} +tasks.assemble { dependsOn(shadowJar) } + +publishing { + publications { + create("runelite-client") { + from(components["java"]) + artifact(shadowJar) { classifier = "shaded" } + } + } +} + +val assemble = tasks.withType { + scriptDirectory = file("src/main/scripts") + outputDirectory = sourceSets.main.map { File(it.output.resourcesDir, "runelite") } + componentsFile = file("../runelite-api/src/main/interfaces/interfaces.toml") +} + +tasks.withType { + archiveOverlayDirectory = assemble.single().outputDirectory + indexFile = archiveOverlayDirectory.file("index") +} + +tasks.processResources { + val commit = ByteArrayOutputStream() + exec { + commandLine("git", "rev-parse", "--short=7", "HEAD") + standardOutput = commit + } + + val dirty = ByteArrayOutputStream() + exec { + commandLine("git", "status", "--short") + standardOutput = dirty + } + + filesMatching("net/runelite/client/runelite.properties") { + filter { it.replace("\${project.version}", project.version.toString()) } + filter { it.replace("\${git.commit.id.abbrev}", commit.toString().trim()) } + filter { it.replace("\${git.dirty}", dirty.toString().isNotBlank().toString()) } + } +} + +tasks.compileJava { + options.isFork = true +} + +tasks.jar { + exclude("**/.clang-format") +} + +pmd { + toolVersion = "7.2.0" + ruleSetFiles("./pmd-ruleset.xml") + isConsoleOutput = true + incrementalAnalysis = true + isIgnoreFailures = false + threads = Runtime.getRuntime().availableProcessors() +} +tasks.pmdMain { + exclude("**/RuntimeTypeAdapterFactory.java") + exclude("**/net/runelite/client/party/Party.java") +} +tasks.pmdTest { enabled = false } + +tasks.checkstyleMain { + exclude("net/runelite/client/util/RuntimeTypeAdapterFactory.java") // vendored + exclude("net/runelite/client/party/Party.java") // generated by protobuf +} + +tasks.withType { + systemProperty("glslang.path", providers.gradleProperty("glslangPath").getOrElse("")) +} + +tasks.javadoc { + title = "RuneLite Client ${project.version} API" +} diff --git a/runelite-client/pom.xml b/runelite-client/pom.xml deleted file mode 100644 index c99ff9e6048..00000000000 --- a/runelite-client/pom.xml +++ /dev/null @@ -1,517 +0,0 @@ - - - - 4.0.0 - - - net.runelite - runelite-parent - 1.12.10-SNAPSHOT - - - client - RuneLite Client - - - true - true - nogit - false - false - - - - - org.slf4j - slf4j-api - - - ch.qos.logback - logback-classic - - - net.sf.jopt-simple - jopt-simple - 5.0.1 - - - com.google.guava - guava - - - - com.google.code.findbugs - jsr305 - - - com.google.errorprone - error_prone_annotations - - - com.google.j2objc - j2objc-annotations - - - org.codehaus.mojo - animal-sniffer-annotations - - - - - com.google.inject - guice - no_aop - - - com.google.code.gson - gson - - - net.runelite - flatlaf - ${flatlaf.version} - - - org.projectlombok - lombok - provided - - - org.apache.commons - commons-text - 1.2 - - - net.java.dev.jna - jna - 5.9.0 - - - net.java.dev.jna - jna-platform - 5.9.0 - - - com.google.code.findbugs - jsr305 - - - org.jetbrains - annotations - 23.0.0 - provided - - - com.google.protobuf - protobuf-javalite - 3.21.12 - - - net.runelite - rlawt - 1.7 - - - - - org.lwjgl - lwjgl - - - org.lwjgl - lwjgl - natives-linux - runtime - - - org.lwjgl - lwjgl - natives-linux-arm64 - runtime - - - org.lwjgl - lwjgl - natives-macos - runtime - - - org.lwjgl - lwjgl - natives-macos-arm64 - runtime - - - org.lwjgl - lwjgl - natives-windows-x86 - runtime - - - org.lwjgl - lwjgl - natives-windows - runtime - - - org.lwjgl - lwjgl - natives-windows-arm64 - runtime - - - - - org.lwjgl - lwjgl-opengl - - - org.lwjgl - lwjgl-opengl - natives-linux - runtime - - - org.lwjgl - lwjgl-opengl - natives-linux-arm64 - runtime - - - org.lwjgl - lwjgl-opengl - natives-macos - runtime - - - org.lwjgl - lwjgl-opengl - natives-macos-arm64 - runtime - - - org.lwjgl - lwjgl-opengl - natives-windows-x86 - runtime - - - org.lwjgl - lwjgl-opengl - natives-windows - runtime - - - org.lwjgl - lwjgl-opengl - natives-windows-arm64 - runtime - - - - - org.lwjgl - lwjgl-opencl - - - - net.runelite - runelite-api - ${project.version} - - - net.runelite - jshell - ${project.version} - true - - - net.runelite - injected-client - ${project.version} - runtime - - - net.runelite.arn - http-api - 1.2.21 - - - net.runelite - discord - 1.4 - - - net.runelite - orange-extensions - 1.1 - provided - - - - junit - junit - 4.12 - test - - - org.hamcrest - hamcrest-library - 1.3 - test - - - org.mockito - mockito-core - 3.1.0 - test - - - com.google.inject.extensions - guice-testlib - test - - - com.google.inject.extensions - guice-grapher - test - - - com.squareup.okhttp3 - mockwebserver - 3.14.9 - test - - - - - - - org.lwjgl - lwjgl-bom - 3.3.2 - pom - import - - - - - - - - src/main/resources - - logback.xml - - true - - - src/main/resources - - logback.xml - - false - - - - - org.apache.maven.plugins - maven-jar-plugin - - - **/.clang-format - - - - - - test-jar - - - - - - org.apache.maven.plugins - maven-resources-plugin - 3.0.2 - - - ttf - png - gif - wav - - - - - org.apache.maven.plugins - maven-shade-plugin - 3.3.0 - - - package - - shade - - - ${shade.skip} - true - shaded - false - - - *:* - - META-INF/versions/**/module-info.class - - - - - - - - net.runelite.client.RuneLite - true - - - - - - - - - org.apache.maven.plugins - maven-jarsigner-plugin - 1.4 - - - sign - - sign - - - - - ${jarsigner.skip} - ${jarsigner.keystore} - ${jarsigner.alias} - ${jarsigner.storepass} - ${jarsigner.keypass} - - - - net.runelite - runelite-maven-plugin - ${project.version} - - - assemble - - assemble - - - src/main/scripts - ${project.build.outputDirectory}/runelite - ../runelite-api/src/main/interfaces/interfaces.toml - - - - build-index - - build-index - - - ${project.build.outputDirectory}/runelite - ${project.build.outputDirectory}/runelite/index - - - - - - org.apache.maven.plugins - maven-checkstyle-plugin - - - - ${project.build.sourceDirectory} - ${project.basedir}/src/main/java11 - - - - - org.apache.maven.plugins - maven-pmd-plugin - - true - true - - ${basedir}/pmd-ruleset.xml - - false - true - - **/RuntimeTypeAdapterFactory.java - net/runelite/client/party/Party.java - - - - - - check - - - - - - pl.project13.maven - git-commit-id-plugin - 2.2.6 - - - query-git-info - - revision - - - false - false - - true - - - git.commit.id.abbrev - git.dirty - - - - - - - - diff --git a/runelite-client/settings.gradle.kts b/runelite-client/settings.gradle.kts new file mode 100644 index 00000000000..9c78c29f5f8 --- /dev/null +++ b/runelite-client/settings.gradle.kts @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2024, LlemonDuck + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +rootProject.name = "client" +apply(from = "../common.settings.gradle.kts") + +includeBuild("../runelite-gradle-plugin") +includeBuild("../runelite-jshell") diff --git a/runelite-gradle-plugin/build.gradle.kts b/runelite-gradle-plugin/build.gradle.kts new file mode 100644 index 00000000000..0ce2746cd0d --- /dev/null +++ b/runelite-gradle-plugin/build.gradle.kts @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2024, LlemonDuck + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +plugins { + `java-gradle-plugin` + pmd +} + +dependencies { + implementation("net.runelite:cache:${project.version}") + + // todo needed? + implementation(libs.guava) + implementation(libs.tomlj) + implementation(libs.javapoet) +} + +gradlePlugin { + plugins { + create("rl-assemble") { + id = "net.runelite.runelite-gradle-plugin.assemble" + implementationClass = "net.runelite.gradle.assemble.AssemblePlugin" + } + create("rl-component") { + id = "net.runelite.runelite-gradle-plugin.component" + implementationClass = "net.runelite.gradle.component.ComponentPlugin" + } + create("rl-index") { + id = "net.runelite.runelite-gradle-plugin.index" + implementationClass = "net.runelite.gradle.index.IndexPlugin" + } + create("rl-jarsign") { + id = "net.runelite.runelite-gradle-plugin.jarsign" + implementationClass = "net.runelite.gradle.jarsign.JarsignPlugin" + } + } +} + +pmd { + toolVersion = "7.2.0" + ruleSetFiles("./pmd-ruleset.xml") + isConsoleOutput = true + incrementalAnalysis = true + isIgnoreFailures = false + threads = Runtime.getRuntime().availableProcessors() +} diff --git a/runelite-maven-plugin/pmd-ruleset.xml b/runelite-gradle-plugin/pmd-ruleset.xml similarity index 100% rename from runelite-maven-plugin/pmd-ruleset.xml rename to runelite-gradle-plugin/pmd-ruleset.xml diff --git a/runelite-gradle-plugin/settings.gradle.kts b/runelite-gradle-plugin/settings.gradle.kts new file mode 100644 index 00000000000..54a1fadafbf --- /dev/null +++ b/runelite-gradle-plugin/settings.gradle.kts @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024, LlemonDuck + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +rootProject.name = "runelite-gradle-plugin" +apply(from = "../common.settings.gradle.kts") + +includeBuild("../cache") diff --git a/runelite-gradle-plugin/src/main/java/net/runelite/gradle/assemble/AssemblePlugin.java b/runelite-gradle-plugin/src/main/java/net/runelite/gradle/assemble/AssemblePlugin.java new file mode 100644 index 00000000000..dfe30339389 --- /dev/null +++ b/runelite-gradle-plugin/src/main/java/net/runelite/gradle/assemble/AssemblePlugin.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024, LlemonDuck + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.runelite.gradle.assemble; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.tasks.TaskProvider; + +public abstract class AssemblePlugin implements Plugin +{ + + @Override + public void apply(Project project) + { + TaskProvider assembleRs2asm = project.getTasks() + .register("assembleRs2asm", AssembleTask.class, (task) -> task.setGroup("build")); + + project.getTasks() + .getByName("processResources") + .dependsOn(assembleRs2asm); + } + +} diff --git a/runelite-maven-plugin/src/main/java/net/runelite/mvn/AssembleMojo.java b/runelite-gradle-plugin/src/main/java/net/runelite/gradle/assemble/AssembleTask.java similarity index 68% rename from runelite-maven-plugin/src/main/java/net/runelite/mvn/AssembleMojo.java rename to runelite-gradle-plugin/src/main/java/net/runelite/gradle/assemble/AssembleTask.java index 22acaf4806b..9e35283fe95 100644 --- a/runelite-maven-plugin/src/main/java/net/runelite/mvn/AssembleMojo.java +++ b/runelite-gradle-plugin/src/main/java/net/runelite/gradle/assemble/AssembleTask.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Adam + * Copyright (c) 2024, LlemonDuck * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -22,11 +22,13 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.runelite.mvn; + +package net.runelite.gradle.assemble; import com.google.common.io.Files; import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; import java.util.HashMap; import java.util.Locale; @@ -36,38 +38,45 @@ import net.runelite.cache.definitions.savers.ScriptSaver; import net.runelite.cache.script.RuneLiteInstructions; import net.runelite.cache.script.assembler.Assembler; -import org.apache.maven.plugin.AbstractMojo; -import org.apache.maven.plugin.MojoExecutionException; -import org.apache.maven.plugin.MojoFailureException; -import org.apache.maven.plugin.logging.Log; -import org.apache.maven.plugins.annotations.LifecyclePhase; -import org.apache.maven.plugins.annotations.Mojo; -import org.apache.maven.plugins.annotations.Parameter; +import org.gradle.api.DefaultTask; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.logging.Logger; +import org.gradle.api.tasks.CacheableTask; +import org.gradle.api.tasks.InputDirectory; +import org.gradle.api.tasks.InputFile; +import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.PathSensitive; +import org.gradle.api.tasks.PathSensitivity; +import org.gradle.api.tasks.TaskAction; import org.tomlj.Toml; import org.tomlj.TomlParseError; import org.tomlj.TomlParseResult; import org.tomlj.TomlTable; -@Mojo( - name = "assemble", - defaultPhase = LifecyclePhase.GENERATE_RESOURCES -) -public class AssembleMojo extends AbstractMojo +@CacheableTask +public abstract class AssembleTask extends DefaultTask { - @Parameter(required = true) - private File scriptDirectory; + @InputDirectory + @PathSensitive(PathSensitivity.RELATIVE) + public abstract DirectoryProperty getScriptDirectory(); - @Parameter(required = true) - private File outputDirectory; + @OutputDirectory + public abstract DirectoryProperty getOutputDirectory(); - @Parameter(required = true) - private File componentsFile; + @InputFile + @PathSensitive(PathSensitivity.RELATIVE) + public abstract RegularFileProperty getComponentsFile(); - private final Log log = getLog(); + private final Logger log = getLogger(); - @Override - public void execute() throws MojoExecutionException, MojoFailureException + @TaskAction + public void assembleRs2Asm() throws IOException { + File scriptDirectory = getScriptDirectory().getAsFile().get(); + File outputDirectory = getOutputDirectory().getAsFile().get(); + File componentsFile = getComponentsFile().getAsFile().get(); + RuneLiteInstructions instructions = new RuneLiteInstructions(); instructions.init(); @@ -80,7 +89,7 @@ public void execute() throws MojoExecutionException, MojoFailureException for (File scriptFile : scriptDirectory.listFiles((dir, name) -> name.endsWith(".rs2asm"))) { - log.debug("Assembling " + scriptFile); + log.debug("Assembling {}", scriptFile); try (FileInputStream fin = new FileInputStream(scriptFile)) { @@ -99,21 +108,17 @@ public void execute() throws MojoExecutionException, MojoFailureException } else if (script.getId() < 10000) // Scripts >=10000 are RuneLite scripts, so they shouldn't have a .hash { - throw new MojoExecutionException("Unable to find hash file for " + scriptFile); + throw new FileNotFoundException("Unable to find hash file for " + scriptFile); } ++count; } - catch (IOException ex) - { - throw new MojoFailureException("unable to open file", ex); - } } - log.info("Assembled " + count + " scripts"); + log.lifecycle("Assembled {} scripts", count); } - private Map buildComponentSymbols(File file) throws MojoExecutionException + private Map buildComponentSymbols(File file) { TomlParseResult result; try @@ -122,7 +127,7 @@ private Map buildComponentSymbols(File file) throws MojoExecutio } catch (IOException e) { - throw new MojoExecutionException("unable to read component file " + file.getName(), e); + throw new RuntimeException("unable to read component file " + file.getName(), e); } if (result.hasErrors()) @@ -131,7 +136,7 @@ private Map buildComponentSymbols(File file) throws MojoExecutio { log.error(err.toString()); } - throw new MojoExecutionException("unable to parse component file " + file.getName()); + throw new RuntimeException("unable to parse component file " + file.getName()); } Map symbols = new HashMap<>(); @@ -142,13 +147,13 @@ private Map buildComponentSymbols(File file) throws MojoExecutio if (!tbl.contains("id")) { - throw new MojoExecutionException("interface " + interfaceName + " has no id"); + throw new RuntimeException("interface " + interfaceName + " has no id"); } int interfaceId = (int) (long) tbl.getLong("id"); if (interfaceId < 0 || interfaceId > 0xffff) { - throw new MojoExecutionException("interface id out of range for " + interfaceName); + throw new RuntimeException("interface id out of range for " + interfaceName); } for (var entry2 : tbl.entrySet()) @@ -162,7 +167,7 @@ private Map buildComponentSymbols(File file) throws MojoExecutio int id = (int) (long) entry2.getValue(); if (id < 0 || id > 0xffff) { - throw new MojoExecutionException("component id out of range for " + componentName); + throw new RuntimeException("component id out of range for " + componentName); } var fullName = interfaceName.toLowerCase(Locale.ENGLISH) + ":" + componentName.toLowerCase(Locale.ENGLISH); diff --git a/runelite-gradle-plugin/src/main/java/net/runelite/gradle/component/ComponentPlugin.java b/runelite-gradle-plugin/src/main/java/net/runelite/gradle/component/ComponentPlugin.java new file mode 100644 index 00000000000..a30a6a4c260 --- /dev/null +++ b/runelite-gradle-plugin/src/main/java/net/runelite/gradle/component/ComponentPlugin.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2024, LlemonDuck + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.gradle.component; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.SourceSetContainer; +import org.gradle.api.tasks.TaskProvider; + +public class ComponentPlugin implements Plugin +{ + + @Override + public void apply(Project project) + { + TaskProvider packComponents = project.getTasks() + .register("packComponents", ComponentTask.class, (task) -> task.setGroup("build")); + + project.getTasks() + .getByName("compileJava") + .dependsOn(packComponents); + + project.getExtensions() + .getByType(SourceSetContainer.class) + .getByName(SourceSet.MAIN_SOURCE_SET_NAME) + .getJava() + .srcDir(packComponents.map(ComponentTask::getOutputDirectory)); + } + +} diff --git a/runelite-maven-plugin/src/main/java/net/runelite/mvn/ComponentMojo.java b/runelite-gradle-plugin/src/main/java/net/runelite/gradle/component/ComponentTask.java similarity index 62% rename from runelite-maven-plugin/src/main/java/net/runelite/mvn/ComponentMojo.java rename to runelite-gradle-plugin/src/main/java/net/runelite/gradle/component/ComponentTask.java index 619fa273b65..e7458a962f5 100644 --- a/runelite-maven-plugin/src/main/java/net/runelite/mvn/ComponentMojo.java +++ b/runelite-gradle-plugin/src/main/java/net/runelite/gradle/component/ComponentTask.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Adam + * Copyright (c) 2024, LlemonDuck * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -22,7 +22,7 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.runelite.mvn; +package net.runelite.gradle.component; import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.JavaFile; @@ -33,74 +33,61 @@ import java.util.Locale; import java.util.Set; import javax.lang.model.element.Modifier; -import org.apache.maven.plugin.AbstractMojo; -import org.apache.maven.plugin.MojoExecutionException; -import org.apache.maven.plugin.MojoFailureException; -import org.apache.maven.plugin.logging.Log; -import org.apache.maven.plugins.annotations.LifecyclePhase; -import org.apache.maven.plugins.annotations.Mojo; -import org.apache.maven.plugins.annotations.Parameter; -import org.apache.maven.project.MavenProject; +import org.gradle.api.DefaultTask; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.logging.Logger; +import org.gradle.api.tasks.CacheableTask; +import org.gradle.api.tasks.InputFile; +import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.PathSensitive; +import org.gradle.api.tasks.PathSensitivity; +import org.gradle.api.tasks.TaskAction; import org.tomlj.Toml; import org.tomlj.TomlParseError; import org.tomlj.TomlParseResult; import org.tomlj.TomlTable; -@Mojo( - name = "pack-components", - defaultPhase = LifecyclePhase.GENERATE_SOURCES -) -public class ComponentMojo extends AbstractMojo +@CacheableTask +public abstract class ComponentTask extends DefaultTask { - @Parameter(defaultValue = "${project}") - private MavenProject project; - @Parameter(required = true) - private File inputDirectory; + @InputFile + @PathSensitive(PathSensitivity.RELATIVE) + public abstract RegularFileProperty getInputFile(); - @Parameter(required = true) - private File outputDirectory; + @OutputDirectory + public abstract DirectoryProperty getOutputDirectory(); - private final Log log = getLog(); + private final Logger log = getLogger(); private final Set seenInterfaces = new HashSet<>(); private final Set seenComponents = new HashSet<>(); - @Override - public void execute() throws MojoExecutionException, MojoFailureException + @TaskAction + public void packComponents() throws IOException { + File inputFile = getInputFile().getAsFile().get(); + File outputDirectory = getOutputDirectory().getAsFile().get(); + TypeSpec.Builder interfaceType = TypeSpec.classBuilder("InterfaceID") .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addAnnotation(Deprecated.class) - .addJavadoc("@deprecated Use {@link net.runelite.api.gameval.InterfaceID} instead"); + .addJavadoc("@deprecated Use {@link net.runelite.api.gameval.InterfaceID} instead");; TypeSpec.Builder componentType = TypeSpec.classBuilder("ComponentID") .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addAnnotation(Deprecated.class) .addJavadoc("@deprecated Use nested classes of {@link net.runelite.api.gameval.InterfaceID} instead"); - for (File file : inputDirectory.listFiles((dir, name) -> name.endsWith(".toml"))) - { - executeOne(file, interfaceType, componentType); - } - - writeClass("net.runelite.api.widgets", interfaceType.build()); - writeClass("net.runelite.api.widgets", componentType.build()); + executeOne(inputFile, interfaceType, componentType); - // https://stackoverflow.com/a/30760908 - project.addCompileSourceRoot(outputDirectory.getAbsolutePath()); + writeClass(outputDirectory, "net.runelite.api.widgets", interfaceType.build()); + writeClass(outputDirectory, "net.runelite.api.widgets", componentType.build()); } - private void executeOne(File file, TypeSpec.Builder interfaceType, TypeSpec.Builder componentType) throws MojoExecutionException + private void executeOne(File file, TypeSpec.Builder interfaceType, TypeSpec.Builder componentType) throws IOException { - TomlParseResult result; - try - { - result = Toml.parse(file.toPath()); - } - catch (IOException e) - { - throw new MojoExecutionException("unable to read component file " + file.getName(), e); - } + TomlParseResult result = Toml.parse(file.toPath()); if (result.hasErrors()) { @@ -108,7 +95,7 @@ private void executeOne(File file, TypeSpec.Builder interfaceType, TypeSpec.Buil { log.error(err.toString()); } - throw new MojoExecutionException("unable to parse component file " + file.getName()); + throw new RuntimeException("unable to parse component file " + file.getName()); } for (var entry : result.entrySet()) @@ -118,18 +105,18 @@ private void executeOne(File file, TypeSpec.Builder interfaceType, TypeSpec.Buil if (!tbl.contains("id")) { - throw new MojoExecutionException("interface " + interfaceName + " has no id"); + throw new RuntimeException("interface " + interfaceName + " has no id"); } int interfaceId = (int) (long) tbl.getLong("id"); if (interfaceId < 0 || interfaceId > 0xffff) { - throw new MojoExecutionException("interface id out of range for " + interfaceName); + throw new RuntimeException("interface id out of range for " + interfaceName); } if (seenInterfaces.contains(interfaceId)) { - throw new MojoExecutionException("duplicate interface id " + interfaceId); + throw new RuntimeException("duplicate interface id " + interfaceId); } seenInterfaces.add(interfaceId); @@ -146,7 +133,7 @@ private void executeOne(File file, TypeSpec.Builder interfaceType, TypeSpec.Buil int id = (int) (long) entry2.getValue(); if (id < 0 || id > 0xffff) { - throw new MojoExecutionException("component id out of range for " + componentName); + throw new RuntimeException("component id out of range for " + componentName); } var fullName = interfaceName.toUpperCase(Locale.ENGLISH) + "_" + componentName.toUpperCase(Locale.ENGLISH); @@ -155,7 +142,7 @@ private void executeOne(File file, TypeSpec.Builder interfaceType, TypeSpec.Buil if (seenComponents.contains(componentId)) { - throw new MojoExecutionException("duplicate component id " + comment); + throw new RuntimeException("duplicate component id " + comment); } seenComponents.add(componentId); @@ -176,18 +163,10 @@ private static void addField(TypeSpec.Builder type, String name, int value, Stri type.addField(field.build()); } - private void writeClass(String pkg, TypeSpec type) throws MojoExecutionException + private void writeClass(File outputDirectory, String pkg, TypeSpec type) throws IOException { - JavaFile javaFile = JavaFile.builder(pkg, type) - .build(); - - try - { - javaFile.writeTo(outputDirectory); - } - catch (IOException e) - { - throw new MojoExecutionException("unable to write java class", e); - } + JavaFile.builder(pkg, type) + .build() + .writeToFile(outputDirectory); } } diff --git a/runelite-gradle-plugin/src/main/java/net/runelite/gradle/index/IndexPlugin.java b/runelite-gradle-plugin/src/main/java/net/runelite/gradle/index/IndexPlugin.java new file mode 100644 index 00000000000..8e9de3e6f99 --- /dev/null +++ b/runelite-gradle-plugin/src/main/java/net/runelite/gradle/index/IndexPlugin.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024, LlemonDuck + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.gradle.index; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.tasks.TaskProvider; + +public abstract class IndexPlugin implements Plugin +{ + + @Override + public void apply(Project project) + { + TaskProvider buildRs2asmIndex = project.getTasks() + .register("buildRs2asmIndex", IndexTask.class, (task) -> task.setGroup("build")); + + project.getTasks() + .getByName("processResources") + .dependsOn(buildRs2asmIndex); + } +} diff --git a/runelite-maven-plugin/src/main/java/net/runelite/mvn/IndexMojo.java b/runelite-gradle-plugin/src/main/java/net/runelite/gradle/index/IndexTask.java similarity index 69% rename from runelite-maven-plugin/src/main/java/net/runelite/mvn/IndexMojo.java rename to runelite-gradle-plugin/src/main/java/net/runelite/gradle/index/IndexTask.java index 63cbb6dbcc0..494b0ec9d9e 100644 --- a/runelite-maven-plugin/src/main/java/net/runelite/mvn/IndexMojo.java +++ b/runelite-gradle-plugin/src/main/java/net/runelite/gradle/index/IndexTask.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Adam + * Copyright (c) 2024, LlemonDuck * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -22,35 +22,40 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.runelite.mvn; +package net.runelite.gradle.index; import java.io.DataOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import static java.lang.Integer.parseInt; -import org.apache.maven.plugin.AbstractMojo; -import org.apache.maven.plugin.MojoExecutionException; -import org.apache.maven.plugin.MojoFailureException; -import org.apache.maven.plugins.annotations.LifecyclePhase; -import org.apache.maven.plugins.annotations.Mojo; -import org.apache.maven.plugins.annotations.Parameter; +import org.gradle.api.DefaultTask; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.tasks.CacheableTask; +import org.gradle.api.tasks.InputDirectory; +import org.gradle.api.tasks.OutputFile; +import org.gradle.api.tasks.PathSensitive; +import org.gradle.api.tasks.PathSensitivity; +import org.gradle.api.tasks.TaskAction; -@Mojo( - name = "build-index", - defaultPhase = LifecyclePhase.GENERATE_RESOURCES -) -public class IndexMojo extends AbstractMojo +@CacheableTask +public abstract class IndexTask extends DefaultTask { - @Parameter(required = true) - private File archiveOverlayDirectory; - @Parameter(required = true) - private File indexFile; + @InputDirectory + @PathSensitive(PathSensitivity.RELATIVE) + public abstract DirectoryProperty getArchiveOverlayDirectory(); - @Override - public void execute() throws MojoExecutionException, MojoFailureException + @OutputFile + public abstract RegularFileProperty getIndexFile(); + + @TaskAction + public void buildRs2Index() throws IOException { + File archiveOverlayDirectory = getArchiveOverlayDirectory().getAsFile().get(); + File indexFile = getIndexFile().getAsFile().get(); + try (DataOutputStream fout = new DataOutputStream(new FileOutputStream(indexFile))) { for (File indexFolder : archiveOverlayDirectory.listFiles()) @@ -77,10 +82,6 @@ public void execute() throws MojoExecutionException, MojoFailureException fout.writeInt(-1); } - catch (IOException ex) - { - throw new MojoExecutionException("error build index file", ex); - } } } diff --git a/runelite-gradle-plugin/src/main/java/net/runelite/gradle/jarsign/JarsignExtension.java b/runelite-gradle-plugin/src/main/java/net/runelite/gradle/jarsign/JarsignExtension.java new file mode 100644 index 00000000000..ded3cbc88d3 --- /dev/null +++ b/runelite-gradle-plugin/src/main/java/net/runelite/gradle/jarsign/JarsignExtension.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024, LlemonDuck + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.gradle.jarsign; + +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.provider.Property; + +public interface JarsignExtension +{ + + RegularFileProperty getKeystore(); + + Property getStorePass(); + + Property getKeyPass(); + + Property getAlias(); + +} diff --git a/runelite-gradle-plugin/src/main/java/net/runelite/gradle/jarsign/JarsignPlugin.java b/runelite-gradle-plugin/src/main/java/net/runelite/gradle/jarsign/JarsignPlugin.java new file mode 100644 index 00000000000..f252fdf90e3 --- /dev/null +++ b/runelite-gradle-plugin/src/main/java/net/runelite/gradle/jarsign/JarsignPlugin.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2024, LlemonDuck + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.gradle.jarsign; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.plugins.BasePlugin; +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.TaskProvider; +import org.gradle.jvm.tasks.Jar; + +public abstract class JarsignPlugin implements Plugin +{ + + @Override + public void apply(Project project) + { + JarsignExtension ext = project.getExtensions() + .create(JarsignExtension.class, "jarsign", JarsignExtension.class); + ext.getKeystore().convention(toRegularFileProvider(project, propProvider(project, "jarsignerKeystore"))); + ext.getStorePass().convention(propProvider(project, "jarsignerStorepass")); + ext.getKeyPass().convention(propProvider(project, "jarsignerKeypass")); + ext.getAlias().convention(propProvider(project, "jarsignerAlias")); + + project.getTasks() + .withType(Jar.class, jarTask -> registerSignTask(project, jarTask, ext)); + } + + private void registerSignTask(Project project, Jar jarTask, JarsignExtension ext) + { + TaskProvider signTask = project.getTasks().register( + jarTask.getName() + "Sign", JarsignTask.class, (jarsignTask) -> + { + jarsignTask.setGroup(BasePlugin.BUILD_GROUP); + + jarsignTask.getBuildTask().convention(jarTask); + jarsignTask.getArchive().convention(jarTask.getArchiveFile()); + jarsignTask.getKeystore().convention(ext.getKeystore()); + jarsignTask.getStorePass().convention(ext.getStorePass()); + jarsignTask.getKeyPass().convention(ext.getKeyPass()); + jarsignTask.getAlias().convention(ext.getAlias()); + } + ); + jarTask.finalizedBy(signTask); + } + + private static Provider propProvider(Project project, String key) + { + return project.provider(() -> + (String) project.findProperty(key)); + } + + private static RegularFileProperty toRegularFileProvider(Project project, Provider propProvider) + { + return project.getObjects() + .fileProperty() + .fileProvider(propProvider.map(project::file)); + } +} diff --git a/runelite-gradle-plugin/src/main/java/net/runelite/gradle/jarsign/JarsignTask.java b/runelite-gradle-plugin/src/main/java/net/runelite/gradle/jarsign/JarsignTask.java new file mode 100644 index 00000000000..8acb891db71 --- /dev/null +++ b/runelite-gradle-plugin/src/main/java/net/runelite/gradle/jarsign/JarsignTask.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2024, LlemonDuck + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.gradle.jarsign; + +import org.gradle.api.DefaultTask; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.provider.Property; +import org.gradle.api.tasks.CacheableTask; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputFile; +import org.gradle.api.tasks.PathSensitive; +import org.gradle.api.tasks.PathSensitivity; +import org.gradle.api.tasks.TaskAction; +import org.gradle.jvm.tasks.Jar; + +@CacheableTask +public abstract class JarsignTask extends DefaultTask +{ + + public JarsignTask() + { + dependsOn(getBuildTask()); + onlyIf( + "target archive must be specified", + _t -> getArchive().getAsFile().get().exists() + ); + + onlyIf( + "keystore properties are set", + _t -> + getKeystore().isPresent() && + getStorePass().isPresent() && + getKeyPass().isPresent() && + getAlias().isPresent() + ); + } + + @Input + public abstract Property getBuildTask(); + + @InputFile + @PathSensitive(PathSensitivity.RELATIVE) + public abstract RegularFileProperty getArchive(); + + @InputFile + @PathSensitive(PathSensitivity.RELATIVE) + public abstract RegularFileProperty getKeystore(); + + @Input + public abstract Property getStorePass(); + + @Input + public abstract Property getKeyPass(); + + @Input + public abstract Property getAlias(); + + @TaskAction + public void signArtifact() + { + getProject().exec(exec -> + exec.commandLine( + "jarsigner", + "-keystore", getKeystore().getAsFile().get().getAbsolutePath(), + "-storepass", getStorePass().get(), + "-keypass", getKeyPass().get(), + getArchive().getAsFile().get().getAbsolutePath(), + getAlias().get() + )); + } + +} diff --git a/runelite-jshell/build.gradle.kts b/runelite-jshell/build.gradle.kts new file mode 100644 index 00000000000..f10bfe36fb5 --- /dev/null +++ b/runelite-jshell/build.gradle.kts @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024, LlemonDuck + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +plugins { + java + `maven-publish` + alias(libs.plugins.lombok) +} + +lombok.version = libs.versions.lombok.get() + +java { + withJavadocJar() + withSourcesJar() +} + +dependencies { + implementation(libs.slf4j.api) + implementation(libs.guava) + implementation(variantOf(libs.guice.core) { classifier("no_aop") }) + implementation(libs.findbugs) + implementation(libs.fife.rsyntaxtextarea) + implementation(libs.fife.autocomplete) + implementation(libs.flatlaf.extras) +} + +publishing { + publications { + create("jshell") { + from(components["java"]) + } + } +} diff --git a/runelite-jshell/pom.xml b/runelite-jshell/pom.xml deleted file mode 100644 index 171e91aa0f9..00000000000 --- a/runelite-jshell/pom.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - - 4.0.0 - - - net.runelite - runelite-parent - 1.12.10-SNAPSHOT - - - jshell - RuneLite JShell - - - - org.slf4j - slf4j-api - - - com.google.inject - guice - no_aop - - - org.projectlombok - lombok - provided - - - com.google.code.findbugs - jsr305 - - - com.fifesoft - rsyntaxtextarea - 3.1.2 - - - com.fifesoft - autocomplete - 3.1.1 - - - - net.runelite - flatlaf-extras - ${flatlaf.version} - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - 11 - - - - - diff --git a/runelite-jshell/settings.gradle.kts b/runelite-jshell/settings.gradle.kts new file mode 100644 index 00000000000..f0b83742986 --- /dev/null +++ b/runelite-jshell/settings.gradle.kts @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024, LlemonDuck + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +rootProject.name = "jshell" +apply(from = "../common.settings.gradle.kts") diff --git a/runelite-maven-plugin/pom.xml b/runelite-maven-plugin/pom.xml deleted file mode 100644 index d197cd61c67..00000000000 --- a/runelite-maven-plugin/pom.xml +++ /dev/null @@ -1,112 +0,0 @@ - - - - 4.0.0 - - - net.runelite - runelite-parent - 1.12.10-SNAPSHOT - - - runelite-maven-plugin - RuneLite Maven Plugin - maven-plugin - - - true - - - - - net.runelite - cache - ${project.version} - - - - org.apache.maven - maven-plugin-api - 3.0.5 - - - org.apache.maven.plugin-tools - maven-plugin-annotations - 3.4 - - - org.apache.maven - maven-core - 3.0.5 - - - - org.tomlj - tomlj - 1.1.0 - - - com.squareup - javapoet - 1.13.0 - - - - - - - org.apache.maven.plugins - maven-pmd-plugin - - true - true - - ${project.basedir}/pmd-ruleset.xml - - false - true - - - - - check - - - - - - org.apache.maven.plugins - maven-plugin-plugin - - - default-descriptor - process-classes - - - - - - diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 00000000000..549ce19f0fa --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024, LlemonDuck + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +rootProject.name = "runelite" +includeBuild("cache") +includeBuild("runelite-api") +includeBuild("runelite-client") +includeBuild("runelite-gradle-plugin") +includeBuild("runelite-jshell") From 5d0e20b79f02bc3a0085b54e4d5add2d8a8aee90 Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 21 Dec 2025 11:52:29 -0500 Subject: [PATCH 03/13] Revert "all: move to gradle" This reverts commit d2c99287a6970169d2ed22b26caacf968c08b660. --- .github/workflows/CI.yml | 25 +- .gitignore | 44 +- .mvn/jvm.config | 1 + build.gradle.kts | 43 -- cache/build.gradle.kts | 117 ---- cache/pom.xml | 178 ++++++ cache/settings.gradle.kts | 27 - .../checkstyle.xml => checkstyle.xml | 3 +- ci/build.sh | 3 +- ci/settings.xml | 280 ++++++++++ common.settings.gradle.kts | 107 ---- gradle.properties | 33 -- gradle/verification-metadata.xml | 468 ---------------- gradle/wrapper/gradle-wrapper.jar | Bin 60756 -> 0 bytes gradle/wrapper/gradle-wrapper.properties | 7 - gradlew | 234 -------- gradlew.bat | 89 --- libs.versions.toml | 85 --- pom.xml | 332 +++++++++++ runelite-api/build.gradle.kts | 146 ----- runelite-api/pom.xml | 178 ++++++ runelite-api/settings.gradle.kts | 29 - runelite-client/build.gradle.kts | 199 ------- runelite-client/pom.xml | 517 ++++++++++++++++++ runelite-client/settings.gradle.kts | 30 - runelite-gradle-plugin/build.gradle.kts | 68 --- runelite-gradle-plugin/settings.gradle.kts | 29 - .../gradle/assemble/AssemblePlugin.java | 46 -- .../gradle/component/ComponentPlugin.java | 53 -- .../runelite/gradle/index/IndexPlugin.java | 44 -- .../gradle/jarsign/JarsignExtension.java | 41 -- .../gradle/jarsign/JarsignPlugin.java | 82 --- .../runelite/gradle/jarsign/JarsignTask.java | 94 ---- runelite-jshell/build.gradle.kts | 55 -- runelite-jshell/pom.xml | 87 +++ runelite-jshell/settings.gradle.kts | 27 - .../pmd-ruleset.xml | 0 runelite-maven-plugin/pom.xml | 112 ++++ .../java/net/runelite/mvn/AssembleMojo.java | 77 ++- .../java/net/runelite/mvn/ComponentMojo.java | 103 ++-- .../main/java/net/runelite/mvn/IndexMojo.java | 47 +- settings.gradle.kts | 31 -- .../suppressions.xml => suppressions.xml | 3 - 43 files changed, 1827 insertions(+), 2347 deletions(-) create mode 100644 .mvn/jvm.config delete mode 100644 build.gradle.kts delete mode 100644 cache/build.gradle.kts create mode 100644 cache/pom.xml delete mode 100644 cache/settings.gradle.kts rename config/checkstyle/checkstyle.xml => checkstyle.xml (96%) create mode 100644 ci/settings.xml delete mode 100644 common.settings.gradle.kts delete mode 100644 gradle.properties delete mode 100644 gradle/verification-metadata.xml delete mode 100644 gradle/wrapper/gradle-wrapper.jar delete mode 100644 gradle/wrapper/gradle-wrapper.properties delete mode 100755 gradlew delete mode 100644 gradlew.bat delete mode 100644 libs.versions.toml create mode 100644 pom.xml delete mode 100644 runelite-api/build.gradle.kts create mode 100644 runelite-api/pom.xml delete mode 100644 runelite-api/settings.gradle.kts delete mode 100644 runelite-client/build.gradle.kts create mode 100644 runelite-client/pom.xml delete mode 100644 runelite-client/settings.gradle.kts delete mode 100644 runelite-gradle-plugin/build.gradle.kts delete mode 100644 runelite-gradle-plugin/settings.gradle.kts delete mode 100644 runelite-gradle-plugin/src/main/java/net/runelite/gradle/assemble/AssemblePlugin.java delete mode 100644 runelite-gradle-plugin/src/main/java/net/runelite/gradle/component/ComponentPlugin.java delete mode 100644 runelite-gradle-plugin/src/main/java/net/runelite/gradle/index/IndexPlugin.java delete mode 100644 runelite-gradle-plugin/src/main/java/net/runelite/gradle/jarsign/JarsignExtension.java delete mode 100644 runelite-gradle-plugin/src/main/java/net/runelite/gradle/jarsign/JarsignPlugin.java delete mode 100644 runelite-gradle-plugin/src/main/java/net/runelite/gradle/jarsign/JarsignTask.java delete mode 100644 runelite-jshell/build.gradle.kts create mode 100644 runelite-jshell/pom.xml delete mode 100644 runelite-jshell/settings.gradle.kts rename {runelite-gradle-plugin => runelite-maven-plugin}/pmd-ruleset.xml (100%) create mode 100644 runelite-maven-plugin/pom.xml rename runelite-gradle-plugin/src/main/java/net/runelite/gradle/assemble/AssembleTask.java => runelite-maven-plugin/src/main/java/net/runelite/mvn/AssembleMojo.java (68%) rename runelite-gradle-plugin/src/main/java/net/runelite/gradle/component/ComponentTask.java => runelite-maven-plugin/src/main/java/net/runelite/mvn/ComponentMojo.java (62%) rename runelite-gradle-plugin/src/main/java/net/runelite/gradle/index/IndexTask.java => runelite-maven-plugin/src/main/java/net/runelite/mvn/IndexMojo.java (69%) delete mode 100644 settings.gradle.kts rename config/checkstyle/suppressions.xml => suppressions.xml (87%) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index b338871fc6c..ebf2f182577 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -9,32 +9,27 @@ jobs: permissions: contents: read - env: - GRADLE_OPTS: "-Dfile:encoding=UTF-8" - steps: - - name: Cache glslang - uses: actions/cache@v5 + - name: Checkout + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Cache + uses: actions/cache@v4 with: path: | + ~/.m2/repository ~/.cache/runelite - key: ${{ runner.os }}-cache-${{ hashFiles('ci/build.sh') }} + key: ${{ runner.os }}-cache-${{ hashFiles('**/pom.xml', '**/build.sh', '**/pmd-ruleset.xml') }} restore-keys: | ${{ runner.os }}-cache- - - name: Checkout - uses: actions/checkout@v4 - - name: Set up JDK 11 uses: actions/setup-java@v4 with: distribution: temurin java-version: 11 - - name: Setup Gradle - uses: gradle/actions/setup-gradle@v4 - with: - gradle-version: wrapper - - - name: Build and Test + - name: Build run: ./ci/build.sh diff --git a/.gitignore b/.gitignore index f35ca9ebe00..ff14386ce44 100644 --- a/.gitignore +++ b/.gitignore @@ -1,39 +1,13 @@ -.gradle -build/ -!gradle/wrapper/gradle-wrapper.jar -!**/src/main/**/build/ -!**/src/test/**/build/ - -### IntelliJ IDEA ### -.idea/ -*.iws +target +nbactions.xml +nb-configuration.xml +/nbproject/ +project.properties *.iml -*.ipr -out/ -!**/src/main/**/out/ -!**/src/test/**/out/ - -### Eclipse ### -.apt_generated +.idea/ +.project +.settings/ .classpath +.vscode .factorypath -.project -.settings -.springBeans -.sts4-cache -bin/ -!**/src/main/**/bin/ -!**/src/test/**/bin/ - -### NetBeans ### -/nbproject/private/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ - -### VS Code ### -.vscode/ - -### Mac OS ### .DS_Store \ No newline at end of file diff --git a/.mvn/jvm.config b/.mvn/jvm.config new file mode 100644 index 00000000000..67bd169f319 --- /dev/null +++ b/.mvn/jvm.config @@ -0,0 +1 @@ +-Xmx512m diff --git a/build.gradle.kts b/build.gradle.kts deleted file mode 100644 index a9e40b66a18..00000000000 --- a/build.gradle.kts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2024, LlemonDuck - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -tasks.register("cleanAll") { - gradle.includedBuilds.forEach { build -> this@register.dependsOn(build.task(":clean")) } -} -tasks.register("buildAll") { - gradle.includedBuilds.forEach { build -> this@register.dependsOn(build.task(":build")) } -} -tasks.register("assembleAll") { - gradle.includedBuilds.forEach { build -> this@register.dependsOn(build.task(":assemble")) } -} -tasks.register("testAll") { - gradle.includedBuilds.forEach { build -> this@register.dependsOn(build.task(":test")) } -} -tasks.register("publishAll") { - this@register.dependsOn(gradle.includedBuild("cache").task(":publish")) - this@register.dependsOn(gradle.includedBuild("runelite-api").task(":publish")) - this@register.dependsOn(gradle.includedBuild("runelite-client").task(":publish")) - this@register.dependsOn(gradle.includedBuild("runelite-jshell").task(":publish")) -} diff --git a/cache/build.gradle.kts b/cache/build.gradle.kts deleted file mode 100644 index d9a04312a88..00000000000 --- a/cache/build.gradle.kts +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (c) 2024, LlemonDuck - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -plugins { - java - `maven-publish` - antlr - alias(libs.plugins.lombok) -} - -lombok.version = libs.versions.lombok.get() - -java { - withJavadocJar() - withSourcesJar() -} - -dependencies { - antlr(libs.antlr.core) - implementation(libs.antlr.runtime) - - implementation(libs.guava) - implementation(libs.slf4j.api) - runtimeOnly(libs.slf4j.simple) - implementation(libs.commons.compress) - implementation(libs.gson) - implementation(libs.commons.cli) - implementation(libs.jna.core) - - testImplementation(libs.junit) - testImplementation(libs.rs.cache) -} - -// the gradle antlr plugin adds all of antlr to runtimeClasspath, -// workaround that https://github.com/gradle/gradle/issues/820 -configurations { - api { - setExtendsFrom(extendsFrom.filterNot { it == antlr.get() }) - } -} - -sourceSets { - main { - antlr { setSrcDirs(listOf("src/main/antlr4")) } - } -} - -publishing { - publications { - create("cache") { - from(components["java"]) - } - } -} - -tasks.processTestResources { - filesMatching("cache.properties") { - filter { it.replace("\${rs.version}", libs.versions.rs.get()) } - filter { it.replace("\${cache.version}", libs.versions.cache.get()) } - } -} - -tasks.test { - enabled = false - jvmArgs("-Xmx2048m") -} - -// everything from here down is accounting for antlr sources in varying ways -tasks.named("sourcesJar", Jar::class) { - dependsOn(tasks.generateGrammarSource) - duplicatesStrategy = DuplicatesStrategy.EXCLUDE - exclude("net/runelite/cache/script/assembler/*.interp") - exclude("net/runelite/cache/script/assembler/*.tokens") -} - -tasks.javadoc { - exclude("net/runelite/cache/script/assembler/*.interp") - exclude("net/runelite/cache/script/assembler/*.tokens") -} - -tasks.checkstyleMain { - exclude("net/runelite/cache/script/assembler/*.java") -} - -tasks.generateGrammarSource { - arguments.addAll(listOf("-package", "net.runelite.cache.script.assembler")) -} - -afterEvaluate { - tasks.named("generateEffectiveLombokConfig") { - // lombok won't find anything in the antlr generated sources, but it looks in there regardless - // and gradle complains if you don't provide an explicit task dependency between the two - dependsOn(tasks.generateGrammarSource) - } -} diff --git a/cache/pom.xml b/cache/pom.xml new file mode 100644 index 00000000000..5085ec783ef --- /dev/null +++ b/cache/pom.xml @@ -0,0 +1,178 @@ + + + + 4.0.0 + + + net.runelite + runelite-parent + 1.12.10-SNAPSHOT + + + cache + Cache + + + 165 + + 4.13.1 + + + + + com.google.guava + guava + + + org.slf4j + slf4j-api + + + org.slf4j + slf4j-simple + true + + + org.apache.commons + commons-compress + 1.10 + + + com.google.code.gson + gson + + + org.antlr + antlr4-runtime + ${antlr4.version} + + + commons-cli + commons-cli + 1.3.1 + + + org.projectlombok + lombok + provided + + + net.java.dev.jna + jna + 5.9.0 + + + + junit + junit + 4.12 + test + + + net.runelite.rs + cache + ${cache.version} + test + + + + + + + src/test/resources + true + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + -Xmx2048m + + ${cache.tmpdir} + + + + + maven-assembly-plugin + + + jar-with-dependencies + + + + + make-assembly + package + + single + + + + + + org.antlr + antlr4-maven-plugin + ${antlr4.version} + + + process-resources + + antlr4 + + + + + + + org.projectlombok + lombok-maven-plugin + + + org.apache.maven.plugins + maven-javadoc-plugin + + + ${project.build.directory}/delombok;${project.build.directory}/generated-sources/antlr4 + + + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + + + + diff --git a/cache/settings.gradle.kts b/cache/settings.gradle.kts deleted file mode 100644 index ccc997b18fe..00000000000 --- a/cache/settings.gradle.kts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2024, LlemonDuck - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -rootProject.name = "cache" -apply(from = "../common.settings.gradle.kts") diff --git a/config/checkstyle/checkstyle.xml b/checkstyle.xml similarity index 96% rename from config/checkstyle/checkstyle.xml rename to checkstyle.xml index c8287888985..97efb7194ea 100644 --- a/config/checkstyle/checkstyle.xml +++ b/checkstyle.xml @@ -27,7 +27,6 @@ "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN" "https://checkstyle.org/dtds/configuration_1_3.dtd"> - @@ -58,6 +57,6 @@ - + diff --git a/ci/build.sh b/ci/build.sh index e863cdf7bf6..a8092698243 100755 --- a/ci/build.sh +++ b/ci/build.sh @@ -15,5 +15,4 @@ if [ ! -f "${GLSLANG_ARCHIVE}" ] || [ ! -d "${GLSLANG_DIR}" ] || ! echo "${GLSLA unzip -o -q "${GLSLANG_ARCHIVE}" -d "${GLSLANG_DIR}" fi -export ORG_GRADLE_PROJECT_glslangPath="$GLSLANG_DIR/bin/glslangValidator" -./gradlew ':buildAll' +mvn verify --settings ci/settings.xml -Dglslang.path="${GLSLANG_DIR}/bin/glslangValidator" diff --git a/ci/settings.xml b/ci/settings.xml new file mode 100644 index 00000000000..44c93408311 --- /dev/null +++ b/ci/settings.xml @@ -0,0 +1,280 @@ + + + + + + + + + + false + + + + + + + + + + + + + + + + + + + + + runelite + repo + ${env.REPO_PASSWORD} + + + + + + + + + + + + + + + + + runelite + + + false + false + false + ${user.home}/.cache/runelite/pmd.cache + true + + + + + + + runelite + + diff --git a/common.settings.gradle.kts b/common.settings.gradle.kts deleted file mode 100644 index dac40c65cf5..00000000000 --- a/common.settings.gradle.kts +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (c) 2024, LlemonDuck - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -dependencyResolutionManagement { - repositories { - maven(uri("https://repo.runelite.net")) { - name = "rrn" - - content { - includeGroupAndSubgroups("net.runelite") - excludeModule("net.runelite.rs", "vanilla") - } - } - mavenCentral { - content { excludeGroupAndSubgroups("net.runelite") } - } - } - - versionCatalogs { - create("libs") { - from(files("./libs.versions.toml")) - } - } -} - -// set up some defaults -val rootProps = file("./gradle.properties").inputStream().use { stream -> - java.util.Properties().apply { load(stream) } -} -val checkstyleDir = file("./config/checkstyle/") -gradle.beforeProject { - apply(plugin = "idea") - apply(plugin = "checkstyle") - group = rootProps["project.build.group"] as String - version = rootProps["project.build.version"] as String - - tasks.withType { - options.encoding = "UTF-8" - options.release = 11 - } -} - -gradle.afterProject { - tasks.withType { - (this.options as StandardJavadocDocletOptions).apply { - quiet() - encoding("UTF-8") - use(true) - bottom("Copyright © 2014") - links( - "https://docs.oracle.com/en/java/javase/11/docs/api/" - ) - } - } - - // shared checkstyle config - extensions.findByType()?.run { - toolVersion = "8.3" - configDirectory = file("../config/checkstyle") - } - - // shared publishing config - tasks.withType { enabled = false } - extensions.findByType()?.run { - repositories { - maven(uri(providers.gradleProperty("rlMavenUrl").getOrElse("https://repo.runelite.net"))) { - name = "rlMaven" - if (url.scheme != "file") { credentials(PasswordCredentials::class) } - } - } - } - - // produce reproducible outputs - tasks.withType { - isPreserveFileTimestamps = false - isReproducibleFileOrder = true - } - - extensions.findByType()?.run { - module { - isDownloadSources = true - isDownloadJavadoc = true - } - } -} diff --git a/gradle.properties b/gradle.properties deleted file mode 100644 index e94a2c8e523..00000000000 --- a/gradle.properties +++ /dev/null @@ -1,33 +0,0 @@ -# -# Copyright (c) 2024, LlemonDuck -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -org.gradle.jvmargs=-Dfile.encoding=UTF-8 -Xmx1024m -org.gradle.parallel=true -org.gradle.caching=true - -project.build.group=net.runelite -project.build.version=1.12.10-SNAPSHOT - -glslang.path= diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml deleted file mode 100644 index fdf12ea6f85..00000000000 --- a/gradle/verification-metadata.xml +++ /dev/null @@ -1,468 +0,0 @@ - - - - false - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 249e5832f090a2944b7473328c07c9755baa3196..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 60756 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 '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" -APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat deleted file mode 100644 index 107acd32c4e..00000000000 --- a/gradlew.bat +++ /dev/null @@ -1,89 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/libs.versions.toml b/libs.versions.toml deleted file mode 100644 index ab8e298c7e7..00000000000 --- a/libs.versions.toml +++ /dev/null @@ -1,85 +0,0 @@ -# -# Copyright (c) 2024, LlemonDuck -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -[versions] -rs = "235" -cache = "165" - -flatlaf = "3.2.5-rl4" -guice = "4.1.0" -lombok = "1.18.30" -logback = "1.2.9" -lwjgl = "3.3.2" -slf4j = "1.7.32" -antlr = "4.13.1" - - -[libraries] -rs-vanilla = { module = "net.runelite.rs:vanilla", version.ref = "rs" } -rs-cache = { module = "net.runelite.rs:cache", version.ref = "cache" } - -flatlaf-core = { module = "net.runelite:flatlaf", version.ref = "flatlaf" } -flatlaf-extras = { module = "net.runelite:flatlaf-extras", version.ref = "flatlaf" } -rl-http-api = "net.runelite.arn:http-api:1.2.21" -rl-awt = "net.runelite:rlawt:1.7" -rl-discord = "net.runelite:discord:1.4" -rl-orange = "net.runelite:orange-extensions:1.1" - -antlr-core = { module = "org.antlr:antlr4", version.ref="antlr" } -antlr-runtime = { module = "org.antlr:antlr4-runtime", version.ref="antlr" } -commons-compress = "org.apache.commons:commons-compress:1.10" -commons-text = "org.apache.commons:commons-text:1.2" -commons-cli = "commons-cli:commons-cli:1.3.1" -fife-rsyntaxtextarea = "com.fifesoft:rsyntaxtextarea:3.1.2" -fife-autocomplete = "com.fifesoft:autocomplete:3.1.1" -findbugs = "com.google.code.findbugs:jsr305:3.0.2" -gson = "com.google.code.gson:gson:2.8.5" -guava = "com.google.guava:guava:23.2-jre" -guice-core = { module = "com.google.inject:guice", version.ref = "guice" } -guice-testlib = { module = "com.google.inject.extensions:guice-testlib", version.ref = "guice" } -guice-grapher = { module = "com.google.inject.extensions:guice-grapher", version.ref = "guice" } -hamcrest = "org.hamcrest:hamcrest-library:1.3" -javapoet = "com.squareup:javapoet:1.13.0" -javax-inject = "javax.inject:javax.inject:1" -jetbrains-annotations = "org.jetbrains:annotations:23.0.0" -jna-core = "net.java.dev.jna:jna:5.9.0" -jna-platform = "net.java.dev.jna:jna-platform:5.9.0" -jopt = "net.sf.jopt-simple:jopt-simple:5.0.1" -junit = "junit:junit:4.12" -logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } -logback-core = { module = "ch.qos.logback:logback-core", version.ref = "logback" } -lwjgl-core = { module = "org.lwjgl:lwjgl", version.ref = "lwjgl" } -lwjgl-opengl = { module = "org.lwjgl:lwjgl-opengl", version.ref = "lwjgl" } -lwjgl-opencl = { module = "org.lwjgl:lwjgl-opencl", version.ref = "lwjgl" } -mockito = "org.mockito:mockito-core:3.1.0" -okhttp-mockserver = "com.squareup.okhttp3:mockwebserver:3.14.9" -protobuf = "com.google.protobuf:protobuf-javalite:3.21.12" -slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } -slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j" } -tomlj = "org.tomlj:tomlj:1.1.0" - - - -[plugins] -lombok = { id = "io.freefair.lombok", version = "8.10.2" } diff --git a/pom.xml b/pom.xml new file mode 100644 index 00000000000..fc5e0934cb0 --- /dev/null +++ b/pom.xml @@ -0,0 +1,332 @@ + + + + 4.0.0 + + net.runelite + runelite-parent + 1.12.10-SNAPSHOT + pom + + RuneLite + Open source RuneScape client + http://runelite.net + + + UTF-8 + 1 + 1.18.30 + 1.2.9 + 1.7.25 + 3.2.5-rl4 + + true + true + + + + + + 2-Clause BSD License + https://opensource.org/licenses/BSD-2-Clause + + + + 2014 + + + https://github.com/runelite/runelite + scm:git:git://github.com/runelite/runelite + scm:git:git@github.com:runelite/runelite + HEAD + + + + + Adam- + Adam + Adam@sigterm.info + + + + + GitHub Issues + https://github.com/runelite/runelite/issues + + + + + central + Central Repository + https://repo.maven.apache.org/maven2 + + false + + + + runelite + RuneLite + https://repo.runelite.net + + true + always + + + + + + central + Central Repository + https://repo.maven.apache.org/maven2 + + never + + + false + + + + runelite-plugins + RuneLite Plugins + https://repo.runelite.net + + true + always + + + + + + cache + runelite-api + runelite-client + runelite-jshell + runelite-maven-plugin + + + + + + com.google.guava + guava + 23.2-jre + + + org.projectlombok + lombok + ${lombok.version} + provided + + + com.google.code.gson + gson + 2.8.5 + + + com.google.code.findbugs + jsr305 + 3.0.2 + + + ch.qos.logback + logback-classic + ${logback.version} + + + ch.qos.logback + logback-core + ${logback.version} + + + org.slf4j + slf4j-api + ${slf4j.version} + + + org.slf4j + slf4j-simple + ${slf4j.version} + + + com.google.inject + guice-bom + 4.1.0 + pom + import + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 11 + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.2 + + true + -Xmx512m -Duser.language=en -Duser.region=US + + ${glslang.path} + + + + + org.apache.maven.plugins + maven-release-plugin + 2.5.3 + + + org.projectlombok + lombok-maven-plugin + 1.18.20.0 + + + ${maven.javadoc.skip} + ${project.basedir}/src/main/java + + ${project.build.directory}/delombok + false + + + + generate-sources + + delombok + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.5.0 + + + ${project.build.directory}/delombok + + Copyright © {inceptionYear} + + + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 2.17 + + + com.puppycrawl.tools + checkstyle + 8.3 + + + + + verify-style + process-classes + + check + + + + + checkstyle.xml + + + ${project.build.sourceDirectory} + + true + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.2.0 + + + org.apache.maven.plugins + maven-jar-plugin + 3.2.0 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.6.1 + + + org.apache.maven.plugins + maven-deploy-plugin + 2.8.2 + + + org.apache.maven.plugins + maven-plugin-plugin + 3.6.0 + + + org.apache.maven.plugins + maven-pmd-plugin + 3.22.0 + + + net.sourceforge.pmd + pmd-core + 7.2.0 + + + net.sourceforge.pmd + pmd-java + 7.2.0 + + + + + + + diff --git a/runelite-api/build.gradle.kts b/runelite-api/build.gradle.kts deleted file mode 100644 index 0f0fc84ffc3..00000000000 --- a/runelite-api/build.gradle.kts +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (c) 2024, LlemonDuck - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -plugins { - java - `maven-publish` - checkstyle - alias(libs.plugins.lombok) - - id("net.runelite.runelite-gradle-plugin.component") -} - -lombok.version = libs.versions.lombok.get() - -java { - withJavadocJar() - withSourcesJar() -} - -dependencies { - implementation(libs.slf4j.api) - compileOnly(libs.findbugs) - - compileOnly(libs.jetbrains.annotations) - - testImplementation(libs.junit) - testRuntimeOnly(libs.slf4j.simple) -} - -val runtimeJar = tasks.register("runtimeJar") { - this@register.group = BasePlugin.BUILD_GROUP - - from(sourceSets.main.get().output) - archiveClassifier = "runtime" - - /* from JLS 13.1.3: references to a static field that is a constant variable (§4.12.4) must be resolved - at compile time to the value denoted by the constant variable's initializer *and* no references to the - field should be present in the code in a binary file. - - This permits us to remove the classes containing only these types of fields at runtime */ - exclude("net/runelite/api/annotations/*.class") - exclude("net/runelite/api/clan/ClanID.class") - exclude("net/runelite/api/dbtable/DBTableID*.class") - exclude("net/runelite/api/widgets/ComponentID.class") - exclude("net/runelite/api/widgets/InterfaceID.class") - exclude("net/runelite/api/widgets/ItemQuantityMode.class") - exclude("net/runelite/api/widgets/WidgetID*.class") - exclude("net/runelite/api/widgets/WidgetModalMode.class") - exclude("net/runelite/api/widgets/WidgetModelType.class") - exclude("net/runelite/api/widgets/WidgetPositionMode.class") - exclude("net/runelite/api/widgets/WidgetSizeMode.class") - exclude("net/runelite/api/widgets/WidgetTextAlignment.class") - exclude("net/runelite/api/widgets/WidgetType.class") - exclude("net/runelite/api/AnimationID.class") - exclude("net/runelite/api/CollisionDataFlag.class") - exclude("net/runelite/api/EnumID.class") - exclude("net/runelite/api/FontID.class") - exclude("net/runelite/api/GraphicID.class") - exclude("net/runelite/api/HintArrowType.class") - exclude("net/runelite/api/HitsplatID.class") - exclude("net/runelite/api/ItemID.class") - exclude("net/runelite/api/KeyCode.class") - exclude("net/runelite/api/NpcID.class") - exclude("net/runelite/api/NullItemID.class") - exclude("net/runelite/api/NullNpcID.class") - exclude("net/runelite/api/NullObjectID.class") - exclude("net/runelite/api/ObjectID.class") - exclude("net/runelite/api/Opcodes.class") - exclude("net/runelite/api/ParamID.class") - exclude("net/runelite/api/ScriptID.class") - exclude("net/runelite/api/SettingID.class") - exclude("net/runelite/api/SkullIcon.class") - exclude("net/runelite/api/SoundEffectID.class") - exclude("net/runelite/api/SoundEffectVolume.class") - exclude("net/runelite/api/SpriteID.class") - exclude("net/runelite/api/StructID.class") - exclude("net/runelite/api/Varbits.class") - exclude("net/runelite/api/VarClientInt.class") - exclude("net/runelite/api/VarClientStr.class") - exclude("net/runelite/api/VarPlayer.class") - exclude("net/runelite/api/gameval/*.class") -} -tasks.assemble { dependsOn(runtimeJar) } - -publishing { - publications { - create("api") { - from(components["java"]) - if (!project.version.toString().endsWith("-SNAPSHOT")) { - artifact(runtimeJar) { classifier = "runtime" } - } - } - } -} -tasks.withType { - onlyIf { - publication == publishing.publications["api"] || !version.toString().endsWith("-SNAPSHOT") - } -} - -tasks.withType { - inputFile = file("src/main/interfaces/interfaces.toml") - outputDirectory = file("build/generated/sources/runelite/java/main") -} - -tasks.checkstyleMain { - exclude("net/runelite/api/widgets/ComponentID.java") - exclude("net/runelite/api/widgets/InterfaceID.java") -} - -tasks.javadoc { - title = "RuneLite API ${project.version} API" - - exclude( - "net/runelite/api/gameval/**", - "net/runelite/api/AnimationID.java", - "net/runelite/api/ItemID.java", - "net/runelite/api/NullItemID.java", - "net/runelite/api/ObjectID.java", - "net/runelite/api/NullObjectID.java", - "net/runelite/api/NpcID.java", - "net/runelite/api/NullNpcID.java", - ) -} diff --git a/runelite-api/pom.xml b/runelite-api/pom.xml new file mode 100644 index 00000000000..c9715db53c4 --- /dev/null +++ b/runelite-api/pom.xml @@ -0,0 +1,178 @@ + + + + 4.0.0 + + + net.runelite + runelite-parent + 1.12.10-SNAPSHOT + + + runelite-api + RuneLite API + https://static.runelite.net/runelite-api + + + + org.slf4j + slf4j-api + + + org.projectlombok + lombok + provided + + + com.google.code.findbugs + jsr305 + provided + + + org.jetbrains + annotations + 23.0.0 + provided + + + + junit + junit + 4.12 + test + + + org.slf4j + slf4j-simple + test + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + runtime-jar + package + + jar + + + runtime + + net/runelite/api/annotations/*.class + net/runelite/api/clan/ClanID.class + net/runelite/api/dbtable/DBTableID*.class + net/runelite/api/widgets/ComponentID.class + net/runelite/api/widgets/InterfaceID.class + net/runelite/api/widgets/ItemQuantityMode.class + net/runelite/api/widgets/WidgetID*.class + net/runelite/api/widgets/WidgetModalMode.class + net/runelite/api/widgets/WidgetModelType.class + net/runelite/api/widgets/WidgetPositionMode.class + net/runelite/api/widgets/WidgetSizeMode.class + net/runelite/api/widgets/WidgetTextAlignment.class + net/runelite/api/widgets/WidgetType.class + net/runelite/api/AnimationID.class + net/runelite/api/CollisionDataFlag.class + net/runelite/api/EnumID.class + net/runelite/api/FontID.class + net/runelite/api/GraphicID.class + net/runelite/api/HintArrowType.class + net/runelite/api/HitsplatID.class + net/runelite/api/ItemID.class + net/runelite/api/KeyCode.class + net/runelite/api/NpcID.class + net/runelite/api/NullItemID.class + net/runelite/api/NullNpcID.class + net/runelite/api/NullObjectID.class + net/runelite/api/ObjectID.class + net/runelite/api/Opcodes.class + net/runelite/api/ParamID.class + net/runelite/api/ScriptID.class + net/runelite/api/SettingID.class + net/runelite/api/SkullIcon.class + net/runelite/api/SoundEffectID.class + net/runelite/api/SoundEffectVolume.class + net/runelite/api/SpriteID.class + net/runelite/api/StructID.class + net/runelite/api/Varbits.class + net/runelite/api/VarClientInt.class + net/runelite/api/VarClientStr.class + net/runelite/api/VarPlayer.class + net/runelite/api/gameval/*.class + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + ${project.build.directory}/delombok;${project.build.directory}/generated-sources + net.runelite.api.gameval + + net/runelite/api/AnimationID.java + net/runelite/api/ItemID.java + net/runelite/api/NullItemID.java + net/runelite/api/ObjectID.java + net/runelite/api/NullObjectID.java + net/runelite/api/NpcID.java + net/runelite/api/NullNpcID.java + + + + + net.runelite + runelite-maven-plugin + ${project.version} + + + pack-components + + pack-components + + + src/main/interfaces + ${project.build.directory}/generated-sources + + + + + + + diff --git a/runelite-api/settings.gradle.kts b/runelite-api/settings.gradle.kts deleted file mode 100644 index b640a5c2f81..00000000000 --- a/runelite-api/settings.gradle.kts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2024, LlemonDuck - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -rootProject.name = "runelite-api" -apply(from = "../common.settings.gradle.kts") - -includeBuild("../runelite-gradle-plugin") diff --git a/runelite-client/build.gradle.kts b/runelite-client/build.gradle.kts deleted file mode 100644 index a24d361d61c..00000000000 --- a/runelite-client/build.gradle.kts +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright (c) 2024, LlemonDuck - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -import java.io.ByteArrayOutputStream - -plugins { - java - `java-library` - `maven-publish` - pmd - alias(libs.plugins.lombok) - - id("net.runelite.runelite-gradle-plugin.assemble") - id("net.runelite.runelite-gradle-plugin.index") - id("net.runelite.runelite-gradle-plugin.jarsign") -} - -lombok.version = libs.versions.lombok.get() - -java { - withJavadocJar() - withSourcesJar() -} - -dependencies { - api("net.runelite:runelite-api:${project.version}") - implementation("net.runelite:jshell:${project.version}") - runtimeOnly("net.runelite:injected-client:${project.version}") - - api(libs.rl.http.api) - implementation(libs.rl.discord) - implementation(libs.rl.awt) - compileOnly(libs.rl.orange) - - api(libs.slf4j.api) - implementation(libs.logback.classic) - implementation(libs.jopt) - api(libs.guava) { - exclude("com.google.code.findbugs", "jsr305") - exclude("com.google.errorprone", "error_prone_annotations") - exclude("com.google.j2objc", "j2objc-annotations") - exclude("org.codehaus.mojo", "animal-sniffer-annotations") - } - api(variantOf(libs.guice.core) { classifier("no_aop") }) - api(libs.gson) - implementation(libs.flatlaf.core) - implementation(libs.flatlaf.extras) - implementation(libs.commons.text) - implementation(libs.jna.core) - implementation(libs.jna.platform) - implementation(libs.findbugs) - compileOnly(libs.jetbrains.annotations) - implementation(libs.protobuf) - api(libs.lwjgl.core) - api(libs.lwjgl.opengl) - api(libs.lwjgl.opencl) - - for (platform in listOf( - "linux", - "linux-arm64", - "macos", - "macos-arm64", - "windows-x86", - "windows", - "windows-arm64", - )) { - runtimeOnly(variantOf(libs.lwjgl.core) { classifier("natives-$platform") }) - runtimeOnly(variantOf(libs.lwjgl.opengl) { classifier("natives-$platform") }) - } - - testImplementation(libs.junit) - testImplementation(libs.hamcrest) - testImplementation(libs.mockito) - testImplementation(libs.guice.testlib) - testImplementation(libs.guice.grapher) - testImplementation(libs.okhttp.mockserver) -} - -val shadowJar = tasks.register("shadowJar") { - dependsOn(configurations.runtimeClasspath) - manifest { - attributes["Main-Class"] = "net.runelite.client.RuneLite" - } - - duplicatesStrategy = DuplicatesStrategy.EXCLUDE - - from(sourceSets.main.get().output) - from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) }) - - exclude( - "META-INF/INDEX.LIST", - "META-INF/*.SF", - "META-INF/*.DSA", - "META-INF/*.RSA", - "**/module-info.class" - ) - - group = BasePlugin.BUILD_GROUP - archiveClassifier = "shadow" - archiveFileName = project.name + "-" + project.version + "-shaded.jar" -} -tasks.assemble { dependsOn(shadowJar) } - -publishing { - publications { - create("runelite-client") { - from(components["java"]) - artifact(shadowJar) { classifier = "shaded" } - } - } -} - -val assemble = tasks.withType { - scriptDirectory = file("src/main/scripts") - outputDirectory = sourceSets.main.map { File(it.output.resourcesDir, "runelite") } - componentsFile = file("../runelite-api/src/main/interfaces/interfaces.toml") -} - -tasks.withType { - archiveOverlayDirectory = assemble.single().outputDirectory - indexFile = archiveOverlayDirectory.file("index") -} - -tasks.processResources { - val commit = ByteArrayOutputStream() - exec { - commandLine("git", "rev-parse", "--short=7", "HEAD") - standardOutput = commit - } - - val dirty = ByteArrayOutputStream() - exec { - commandLine("git", "status", "--short") - standardOutput = dirty - } - - filesMatching("net/runelite/client/runelite.properties") { - filter { it.replace("\${project.version}", project.version.toString()) } - filter { it.replace("\${git.commit.id.abbrev}", commit.toString().trim()) } - filter { it.replace("\${git.dirty}", dirty.toString().isNotBlank().toString()) } - } -} - -tasks.compileJava { - options.isFork = true -} - -tasks.jar { - exclude("**/.clang-format") -} - -pmd { - toolVersion = "7.2.0" - ruleSetFiles("./pmd-ruleset.xml") - isConsoleOutput = true - incrementalAnalysis = true - isIgnoreFailures = false - threads = Runtime.getRuntime().availableProcessors() -} -tasks.pmdMain { - exclude("**/RuntimeTypeAdapterFactory.java") - exclude("**/net/runelite/client/party/Party.java") -} -tasks.pmdTest { enabled = false } - -tasks.checkstyleMain { - exclude("net/runelite/client/util/RuntimeTypeAdapterFactory.java") // vendored - exclude("net/runelite/client/party/Party.java") // generated by protobuf -} - -tasks.withType { - systemProperty("glslang.path", providers.gradleProperty("glslangPath").getOrElse("")) -} - -tasks.javadoc { - title = "RuneLite Client ${project.version} API" -} diff --git a/runelite-client/pom.xml b/runelite-client/pom.xml new file mode 100644 index 00000000000..c99ff9e6048 --- /dev/null +++ b/runelite-client/pom.xml @@ -0,0 +1,517 @@ + + + + 4.0.0 + + + net.runelite + runelite-parent + 1.12.10-SNAPSHOT + + + client + RuneLite Client + + + true + true + nogit + false + false + + + + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + + + net.sf.jopt-simple + jopt-simple + 5.0.1 + + + com.google.guava + guava + + + + com.google.code.findbugs + jsr305 + + + com.google.errorprone + error_prone_annotations + + + com.google.j2objc + j2objc-annotations + + + org.codehaus.mojo + animal-sniffer-annotations + + + + + com.google.inject + guice + no_aop + + + com.google.code.gson + gson + + + net.runelite + flatlaf + ${flatlaf.version} + + + org.projectlombok + lombok + provided + + + org.apache.commons + commons-text + 1.2 + + + net.java.dev.jna + jna + 5.9.0 + + + net.java.dev.jna + jna-platform + 5.9.0 + + + com.google.code.findbugs + jsr305 + + + org.jetbrains + annotations + 23.0.0 + provided + + + com.google.protobuf + protobuf-javalite + 3.21.12 + + + net.runelite + rlawt + 1.7 + + + + + org.lwjgl + lwjgl + + + org.lwjgl + lwjgl + natives-linux + runtime + + + org.lwjgl + lwjgl + natives-linux-arm64 + runtime + + + org.lwjgl + lwjgl + natives-macos + runtime + + + org.lwjgl + lwjgl + natives-macos-arm64 + runtime + + + org.lwjgl + lwjgl + natives-windows-x86 + runtime + + + org.lwjgl + lwjgl + natives-windows + runtime + + + org.lwjgl + lwjgl + natives-windows-arm64 + runtime + + + + + org.lwjgl + lwjgl-opengl + + + org.lwjgl + lwjgl-opengl + natives-linux + runtime + + + org.lwjgl + lwjgl-opengl + natives-linux-arm64 + runtime + + + org.lwjgl + lwjgl-opengl + natives-macos + runtime + + + org.lwjgl + lwjgl-opengl + natives-macos-arm64 + runtime + + + org.lwjgl + lwjgl-opengl + natives-windows-x86 + runtime + + + org.lwjgl + lwjgl-opengl + natives-windows + runtime + + + org.lwjgl + lwjgl-opengl + natives-windows-arm64 + runtime + + + + + org.lwjgl + lwjgl-opencl + + + + net.runelite + runelite-api + ${project.version} + + + net.runelite + jshell + ${project.version} + true + + + net.runelite + injected-client + ${project.version} + runtime + + + net.runelite.arn + http-api + 1.2.21 + + + net.runelite + discord + 1.4 + + + net.runelite + orange-extensions + 1.1 + provided + + + + junit + junit + 4.12 + test + + + org.hamcrest + hamcrest-library + 1.3 + test + + + org.mockito + mockito-core + 3.1.0 + test + + + com.google.inject.extensions + guice-testlib + test + + + com.google.inject.extensions + guice-grapher + test + + + com.squareup.okhttp3 + mockwebserver + 3.14.9 + test + + + + + + + org.lwjgl + lwjgl-bom + 3.3.2 + pom + import + + + + + + + + src/main/resources + + logback.xml + + true + + + src/main/resources + + logback.xml + + false + + + + + org.apache.maven.plugins + maven-jar-plugin + + + **/.clang-format + + + + + + test-jar + + + + + + org.apache.maven.plugins + maven-resources-plugin + 3.0.2 + + + ttf + png + gif + wav + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.3.0 + + + package + + shade + + + ${shade.skip} + true + shaded + false + + + *:* + + META-INF/versions/**/module-info.class + + + + + + + + net.runelite.client.RuneLite + true + + + + + + + + + org.apache.maven.plugins + maven-jarsigner-plugin + 1.4 + + + sign + + sign + + + + + ${jarsigner.skip} + ${jarsigner.keystore} + ${jarsigner.alias} + ${jarsigner.storepass} + ${jarsigner.keypass} + + + + net.runelite + runelite-maven-plugin + ${project.version} + + + assemble + + assemble + + + src/main/scripts + ${project.build.outputDirectory}/runelite + ../runelite-api/src/main/interfaces/interfaces.toml + + + + build-index + + build-index + + + ${project.build.outputDirectory}/runelite + ${project.build.outputDirectory}/runelite/index + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + + + ${project.build.sourceDirectory} + ${project.basedir}/src/main/java11 + + + + + org.apache.maven.plugins + maven-pmd-plugin + + true + true + + ${basedir}/pmd-ruleset.xml + + false + true + + **/RuntimeTypeAdapterFactory.java + net/runelite/client/party/Party.java + + + + + + check + + + + + + pl.project13.maven + git-commit-id-plugin + 2.2.6 + + + query-git-info + + revision + + + false + false + + true + + + git.commit.id.abbrev + git.dirty + + + + + + + + diff --git a/runelite-client/settings.gradle.kts b/runelite-client/settings.gradle.kts deleted file mode 100644 index 9c78c29f5f8..00000000000 --- a/runelite-client/settings.gradle.kts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2024, LlemonDuck - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -rootProject.name = "client" -apply(from = "../common.settings.gradle.kts") - -includeBuild("../runelite-gradle-plugin") -includeBuild("../runelite-jshell") diff --git a/runelite-gradle-plugin/build.gradle.kts b/runelite-gradle-plugin/build.gradle.kts deleted file mode 100644 index 0ce2746cd0d..00000000000 --- a/runelite-gradle-plugin/build.gradle.kts +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2024, LlemonDuck - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -plugins { - `java-gradle-plugin` - pmd -} - -dependencies { - implementation("net.runelite:cache:${project.version}") - - // todo needed? - implementation(libs.guava) - implementation(libs.tomlj) - implementation(libs.javapoet) -} - -gradlePlugin { - plugins { - create("rl-assemble") { - id = "net.runelite.runelite-gradle-plugin.assemble" - implementationClass = "net.runelite.gradle.assemble.AssemblePlugin" - } - create("rl-component") { - id = "net.runelite.runelite-gradle-plugin.component" - implementationClass = "net.runelite.gradle.component.ComponentPlugin" - } - create("rl-index") { - id = "net.runelite.runelite-gradle-plugin.index" - implementationClass = "net.runelite.gradle.index.IndexPlugin" - } - create("rl-jarsign") { - id = "net.runelite.runelite-gradle-plugin.jarsign" - implementationClass = "net.runelite.gradle.jarsign.JarsignPlugin" - } - } -} - -pmd { - toolVersion = "7.2.0" - ruleSetFiles("./pmd-ruleset.xml") - isConsoleOutput = true - incrementalAnalysis = true - isIgnoreFailures = false - threads = Runtime.getRuntime().availableProcessors() -} diff --git a/runelite-gradle-plugin/settings.gradle.kts b/runelite-gradle-plugin/settings.gradle.kts deleted file mode 100644 index 54a1fadafbf..00000000000 --- a/runelite-gradle-plugin/settings.gradle.kts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2024, LlemonDuck - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -rootProject.name = "runelite-gradle-plugin" -apply(from = "../common.settings.gradle.kts") - -includeBuild("../cache") diff --git a/runelite-gradle-plugin/src/main/java/net/runelite/gradle/assemble/AssemblePlugin.java b/runelite-gradle-plugin/src/main/java/net/runelite/gradle/assemble/AssemblePlugin.java deleted file mode 100644 index dfe30339389..00000000000 --- a/runelite-gradle-plugin/src/main/java/net/runelite/gradle/assemble/AssemblePlugin.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2024, LlemonDuck - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package net.runelite.gradle.assemble; - -import org.gradle.api.Plugin; -import org.gradle.api.Project; -import org.gradle.api.tasks.TaskProvider; - -public abstract class AssemblePlugin implements Plugin -{ - - @Override - public void apply(Project project) - { - TaskProvider assembleRs2asm = project.getTasks() - .register("assembleRs2asm", AssembleTask.class, (task) -> task.setGroup("build")); - - project.getTasks() - .getByName("processResources") - .dependsOn(assembleRs2asm); - } - -} diff --git a/runelite-gradle-plugin/src/main/java/net/runelite/gradle/component/ComponentPlugin.java b/runelite-gradle-plugin/src/main/java/net/runelite/gradle/component/ComponentPlugin.java deleted file mode 100644 index a30a6a4c260..00000000000 --- a/runelite-gradle-plugin/src/main/java/net/runelite/gradle/component/ComponentPlugin.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2024, LlemonDuck - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.runelite.gradle.component; - -import org.gradle.api.Plugin; -import org.gradle.api.Project; -import org.gradle.api.tasks.SourceSet; -import org.gradle.api.tasks.SourceSetContainer; -import org.gradle.api.tasks.TaskProvider; - -public class ComponentPlugin implements Plugin -{ - - @Override - public void apply(Project project) - { - TaskProvider packComponents = project.getTasks() - .register("packComponents", ComponentTask.class, (task) -> task.setGroup("build")); - - project.getTasks() - .getByName("compileJava") - .dependsOn(packComponents); - - project.getExtensions() - .getByType(SourceSetContainer.class) - .getByName(SourceSet.MAIN_SOURCE_SET_NAME) - .getJava() - .srcDir(packComponents.map(ComponentTask::getOutputDirectory)); - } - -} diff --git a/runelite-gradle-plugin/src/main/java/net/runelite/gradle/index/IndexPlugin.java b/runelite-gradle-plugin/src/main/java/net/runelite/gradle/index/IndexPlugin.java deleted file mode 100644 index 8e9de3e6f99..00000000000 --- a/runelite-gradle-plugin/src/main/java/net/runelite/gradle/index/IndexPlugin.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2024, LlemonDuck - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.runelite.gradle.index; - -import org.gradle.api.Plugin; -import org.gradle.api.Project; -import org.gradle.api.tasks.TaskProvider; - -public abstract class IndexPlugin implements Plugin -{ - - @Override - public void apply(Project project) - { - TaskProvider buildRs2asmIndex = project.getTasks() - .register("buildRs2asmIndex", IndexTask.class, (task) -> task.setGroup("build")); - - project.getTasks() - .getByName("processResources") - .dependsOn(buildRs2asmIndex); - } -} diff --git a/runelite-gradle-plugin/src/main/java/net/runelite/gradle/jarsign/JarsignExtension.java b/runelite-gradle-plugin/src/main/java/net/runelite/gradle/jarsign/JarsignExtension.java deleted file mode 100644 index ded3cbc88d3..00000000000 --- a/runelite-gradle-plugin/src/main/java/net/runelite/gradle/jarsign/JarsignExtension.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2024, LlemonDuck - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.runelite.gradle.jarsign; - -import org.gradle.api.file.RegularFileProperty; -import org.gradle.api.provider.Property; - -public interface JarsignExtension -{ - - RegularFileProperty getKeystore(); - - Property getStorePass(); - - Property getKeyPass(); - - Property getAlias(); - -} diff --git a/runelite-gradle-plugin/src/main/java/net/runelite/gradle/jarsign/JarsignPlugin.java b/runelite-gradle-plugin/src/main/java/net/runelite/gradle/jarsign/JarsignPlugin.java deleted file mode 100644 index f252fdf90e3..00000000000 --- a/runelite-gradle-plugin/src/main/java/net/runelite/gradle/jarsign/JarsignPlugin.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) 2024, LlemonDuck - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.runelite.gradle.jarsign; - -import org.gradle.api.Plugin; -import org.gradle.api.Project; -import org.gradle.api.file.RegularFileProperty; -import org.gradle.api.plugins.BasePlugin; -import org.gradle.api.provider.Provider; -import org.gradle.api.tasks.TaskProvider; -import org.gradle.jvm.tasks.Jar; - -public abstract class JarsignPlugin implements Plugin -{ - - @Override - public void apply(Project project) - { - JarsignExtension ext = project.getExtensions() - .create(JarsignExtension.class, "jarsign", JarsignExtension.class); - ext.getKeystore().convention(toRegularFileProvider(project, propProvider(project, "jarsignerKeystore"))); - ext.getStorePass().convention(propProvider(project, "jarsignerStorepass")); - ext.getKeyPass().convention(propProvider(project, "jarsignerKeypass")); - ext.getAlias().convention(propProvider(project, "jarsignerAlias")); - - project.getTasks() - .withType(Jar.class, jarTask -> registerSignTask(project, jarTask, ext)); - } - - private void registerSignTask(Project project, Jar jarTask, JarsignExtension ext) - { - TaskProvider signTask = project.getTasks().register( - jarTask.getName() + "Sign", JarsignTask.class, (jarsignTask) -> - { - jarsignTask.setGroup(BasePlugin.BUILD_GROUP); - - jarsignTask.getBuildTask().convention(jarTask); - jarsignTask.getArchive().convention(jarTask.getArchiveFile()); - jarsignTask.getKeystore().convention(ext.getKeystore()); - jarsignTask.getStorePass().convention(ext.getStorePass()); - jarsignTask.getKeyPass().convention(ext.getKeyPass()); - jarsignTask.getAlias().convention(ext.getAlias()); - } - ); - jarTask.finalizedBy(signTask); - } - - private static Provider propProvider(Project project, String key) - { - return project.provider(() -> - (String) project.findProperty(key)); - } - - private static RegularFileProperty toRegularFileProvider(Project project, Provider propProvider) - { - return project.getObjects() - .fileProperty() - .fileProvider(propProvider.map(project::file)); - } -} diff --git a/runelite-gradle-plugin/src/main/java/net/runelite/gradle/jarsign/JarsignTask.java b/runelite-gradle-plugin/src/main/java/net/runelite/gradle/jarsign/JarsignTask.java deleted file mode 100644 index 8acb891db71..00000000000 --- a/runelite-gradle-plugin/src/main/java/net/runelite/gradle/jarsign/JarsignTask.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (c) 2024, LlemonDuck - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.runelite.gradle.jarsign; - -import org.gradle.api.DefaultTask; -import org.gradle.api.file.RegularFileProperty; -import org.gradle.api.provider.Property; -import org.gradle.api.tasks.CacheableTask; -import org.gradle.api.tasks.Input; -import org.gradle.api.tasks.InputFile; -import org.gradle.api.tasks.PathSensitive; -import org.gradle.api.tasks.PathSensitivity; -import org.gradle.api.tasks.TaskAction; -import org.gradle.jvm.tasks.Jar; - -@CacheableTask -public abstract class JarsignTask extends DefaultTask -{ - - public JarsignTask() - { - dependsOn(getBuildTask()); - onlyIf( - "target archive must be specified", - _t -> getArchive().getAsFile().get().exists() - ); - - onlyIf( - "keystore properties are set", - _t -> - getKeystore().isPresent() && - getStorePass().isPresent() && - getKeyPass().isPresent() && - getAlias().isPresent() - ); - } - - @Input - public abstract Property getBuildTask(); - - @InputFile - @PathSensitive(PathSensitivity.RELATIVE) - public abstract RegularFileProperty getArchive(); - - @InputFile - @PathSensitive(PathSensitivity.RELATIVE) - public abstract RegularFileProperty getKeystore(); - - @Input - public abstract Property getStorePass(); - - @Input - public abstract Property getKeyPass(); - - @Input - public abstract Property getAlias(); - - @TaskAction - public void signArtifact() - { - getProject().exec(exec -> - exec.commandLine( - "jarsigner", - "-keystore", getKeystore().getAsFile().get().getAbsolutePath(), - "-storepass", getStorePass().get(), - "-keypass", getKeyPass().get(), - getArchive().getAsFile().get().getAbsolutePath(), - getAlias().get() - )); - } - -} diff --git a/runelite-jshell/build.gradle.kts b/runelite-jshell/build.gradle.kts deleted file mode 100644 index f10bfe36fb5..00000000000 --- a/runelite-jshell/build.gradle.kts +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2024, LlemonDuck - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -plugins { - java - `maven-publish` - alias(libs.plugins.lombok) -} - -lombok.version = libs.versions.lombok.get() - -java { - withJavadocJar() - withSourcesJar() -} - -dependencies { - implementation(libs.slf4j.api) - implementation(libs.guava) - implementation(variantOf(libs.guice.core) { classifier("no_aop") }) - implementation(libs.findbugs) - implementation(libs.fife.rsyntaxtextarea) - implementation(libs.fife.autocomplete) - implementation(libs.flatlaf.extras) -} - -publishing { - publications { - create("jshell") { - from(components["java"]) - } - } -} diff --git a/runelite-jshell/pom.xml b/runelite-jshell/pom.xml new file mode 100644 index 00000000000..171e91aa0f9 --- /dev/null +++ b/runelite-jshell/pom.xml @@ -0,0 +1,87 @@ + + + + 4.0.0 + + + net.runelite + runelite-parent + 1.12.10-SNAPSHOT + + + jshell + RuneLite JShell + + + + org.slf4j + slf4j-api + + + com.google.inject + guice + no_aop + + + org.projectlombok + lombok + provided + + + com.google.code.findbugs + jsr305 + + + com.fifesoft + rsyntaxtextarea + 3.1.2 + + + com.fifesoft + autocomplete + 3.1.1 + + + + net.runelite + flatlaf-extras + ${flatlaf.version} + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 11 + + + + + diff --git a/runelite-jshell/settings.gradle.kts b/runelite-jshell/settings.gradle.kts deleted file mode 100644 index f0b83742986..00000000000 --- a/runelite-jshell/settings.gradle.kts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2024, LlemonDuck - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -rootProject.name = "jshell" -apply(from = "../common.settings.gradle.kts") diff --git a/runelite-gradle-plugin/pmd-ruleset.xml b/runelite-maven-plugin/pmd-ruleset.xml similarity index 100% rename from runelite-gradle-plugin/pmd-ruleset.xml rename to runelite-maven-plugin/pmd-ruleset.xml diff --git a/runelite-maven-plugin/pom.xml b/runelite-maven-plugin/pom.xml new file mode 100644 index 00000000000..d197cd61c67 --- /dev/null +++ b/runelite-maven-plugin/pom.xml @@ -0,0 +1,112 @@ + + + + 4.0.0 + + + net.runelite + runelite-parent + 1.12.10-SNAPSHOT + + + runelite-maven-plugin + RuneLite Maven Plugin + maven-plugin + + + true + + + + + net.runelite + cache + ${project.version} + + + + org.apache.maven + maven-plugin-api + 3.0.5 + + + org.apache.maven.plugin-tools + maven-plugin-annotations + 3.4 + + + org.apache.maven + maven-core + 3.0.5 + + + + org.tomlj + tomlj + 1.1.0 + + + com.squareup + javapoet + 1.13.0 + + + + + + + org.apache.maven.plugins + maven-pmd-plugin + + true + true + + ${project.basedir}/pmd-ruleset.xml + + false + true + + + + + check + + + + + + org.apache.maven.plugins + maven-plugin-plugin + + + default-descriptor + process-classes + + + + + + diff --git a/runelite-gradle-plugin/src/main/java/net/runelite/gradle/assemble/AssembleTask.java b/runelite-maven-plugin/src/main/java/net/runelite/mvn/AssembleMojo.java similarity index 68% rename from runelite-gradle-plugin/src/main/java/net/runelite/gradle/assemble/AssembleTask.java rename to runelite-maven-plugin/src/main/java/net/runelite/mvn/AssembleMojo.java index 9e35283fe95..22acaf4806b 100644 --- a/runelite-gradle-plugin/src/main/java/net/runelite/gradle/assemble/AssembleTask.java +++ b/runelite-maven-plugin/src/main/java/net/runelite/mvn/AssembleMojo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, LlemonDuck + * Copyright (c) 2018, Adam * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -22,13 +22,11 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - -package net.runelite.gradle.assemble; +package net.runelite.mvn; import com.google.common.io.Files; import java.io.File; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.IOException; import java.util.HashMap; import java.util.Locale; @@ -38,45 +36,38 @@ import net.runelite.cache.definitions.savers.ScriptSaver; import net.runelite.cache.script.RuneLiteInstructions; import net.runelite.cache.script.assembler.Assembler; -import org.gradle.api.DefaultTask; -import org.gradle.api.file.DirectoryProperty; -import org.gradle.api.file.RegularFileProperty; -import org.gradle.api.logging.Logger; -import org.gradle.api.tasks.CacheableTask; -import org.gradle.api.tasks.InputDirectory; -import org.gradle.api.tasks.InputFile; -import org.gradle.api.tasks.OutputDirectory; -import org.gradle.api.tasks.PathSensitive; -import org.gradle.api.tasks.PathSensitivity; -import org.gradle.api.tasks.TaskAction; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugin.logging.Log; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; import org.tomlj.Toml; import org.tomlj.TomlParseError; import org.tomlj.TomlParseResult; import org.tomlj.TomlTable; -@CacheableTask -public abstract class AssembleTask extends DefaultTask +@Mojo( + name = "assemble", + defaultPhase = LifecyclePhase.GENERATE_RESOURCES +) +public class AssembleMojo extends AbstractMojo { - @InputDirectory - @PathSensitive(PathSensitivity.RELATIVE) - public abstract DirectoryProperty getScriptDirectory(); + @Parameter(required = true) + private File scriptDirectory; - @OutputDirectory - public abstract DirectoryProperty getOutputDirectory(); + @Parameter(required = true) + private File outputDirectory; - @InputFile - @PathSensitive(PathSensitivity.RELATIVE) - public abstract RegularFileProperty getComponentsFile(); + @Parameter(required = true) + private File componentsFile; - private final Logger log = getLogger(); + private final Log log = getLog(); - @TaskAction - public void assembleRs2Asm() throws IOException + @Override + public void execute() throws MojoExecutionException, MojoFailureException { - File scriptDirectory = getScriptDirectory().getAsFile().get(); - File outputDirectory = getOutputDirectory().getAsFile().get(); - File componentsFile = getComponentsFile().getAsFile().get(); - RuneLiteInstructions instructions = new RuneLiteInstructions(); instructions.init(); @@ -89,7 +80,7 @@ public void assembleRs2Asm() throws IOException for (File scriptFile : scriptDirectory.listFiles((dir, name) -> name.endsWith(".rs2asm"))) { - log.debug("Assembling {}", scriptFile); + log.debug("Assembling " + scriptFile); try (FileInputStream fin = new FileInputStream(scriptFile)) { @@ -108,17 +99,21 @@ public void assembleRs2Asm() throws IOException } else if (script.getId() < 10000) // Scripts >=10000 are RuneLite scripts, so they shouldn't have a .hash { - throw new FileNotFoundException("Unable to find hash file for " + scriptFile); + throw new MojoExecutionException("Unable to find hash file for " + scriptFile); } ++count; } + catch (IOException ex) + { + throw new MojoFailureException("unable to open file", ex); + } } - log.lifecycle("Assembled {} scripts", count); + log.info("Assembled " + count + " scripts"); } - private Map buildComponentSymbols(File file) + private Map buildComponentSymbols(File file) throws MojoExecutionException { TomlParseResult result; try @@ -127,7 +122,7 @@ private Map buildComponentSymbols(File file) } catch (IOException e) { - throw new RuntimeException("unable to read component file " + file.getName(), e); + throw new MojoExecutionException("unable to read component file " + file.getName(), e); } if (result.hasErrors()) @@ -136,7 +131,7 @@ private Map buildComponentSymbols(File file) { log.error(err.toString()); } - throw new RuntimeException("unable to parse component file " + file.getName()); + throw new MojoExecutionException("unable to parse component file " + file.getName()); } Map symbols = new HashMap<>(); @@ -147,13 +142,13 @@ private Map buildComponentSymbols(File file) if (!tbl.contains("id")) { - throw new RuntimeException("interface " + interfaceName + " has no id"); + throw new MojoExecutionException("interface " + interfaceName + " has no id"); } int interfaceId = (int) (long) tbl.getLong("id"); if (interfaceId < 0 || interfaceId > 0xffff) { - throw new RuntimeException("interface id out of range for " + interfaceName); + throw new MojoExecutionException("interface id out of range for " + interfaceName); } for (var entry2 : tbl.entrySet()) @@ -167,7 +162,7 @@ private Map buildComponentSymbols(File file) int id = (int) (long) entry2.getValue(); if (id < 0 || id > 0xffff) { - throw new RuntimeException("component id out of range for " + componentName); + throw new MojoExecutionException("component id out of range for " + componentName); } var fullName = interfaceName.toLowerCase(Locale.ENGLISH) + ":" + componentName.toLowerCase(Locale.ENGLISH); diff --git a/runelite-gradle-plugin/src/main/java/net/runelite/gradle/component/ComponentTask.java b/runelite-maven-plugin/src/main/java/net/runelite/mvn/ComponentMojo.java similarity index 62% rename from runelite-gradle-plugin/src/main/java/net/runelite/gradle/component/ComponentTask.java rename to runelite-maven-plugin/src/main/java/net/runelite/mvn/ComponentMojo.java index e7458a962f5..619fa273b65 100644 --- a/runelite-gradle-plugin/src/main/java/net/runelite/gradle/component/ComponentTask.java +++ b/runelite-maven-plugin/src/main/java/net/runelite/mvn/ComponentMojo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, LlemonDuck + * Copyright (c) 2023, Adam * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -22,7 +22,7 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.runelite.gradle.component; +package net.runelite.mvn; import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.JavaFile; @@ -33,61 +33,74 @@ import java.util.Locale; import java.util.Set; import javax.lang.model.element.Modifier; -import org.gradle.api.DefaultTask; -import org.gradle.api.file.DirectoryProperty; -import org.gradle.api.file.RegularFileProperty; -import org.gradle.api.logging.Logger; -import org.gradle.api.tasks.CacheableTask; -import org.gradle.api.tasks.InputFile; -import org.gradle.api.tasks.OutputDirectory; -import org.gradle.api.tasks.PathSensitive; -import org.gradle.api.tasks.PathSensitivity; -import org.gradle.api.tasks.TaskAction; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugin.logging.Log; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.project.MavenProject; import org.tomlj.Toml; import org.tomlj.TomlParseError; import org.tomlj.TomlParseResult; import org.tomlj.TomlTable; -@CacheableTask -public abstract class ComponentTask extends DefaultTask +@Mojo( + name = "pack-components", + defaultPhase = LifecyclePhase.GENERATE_SOURCES +) +public class ComponentMojo extends AbstractMojo { + @Parameter(defaultValue = "${project}") + private MavenProject project; - @InputFile - @PathSensitive(PathSensitivity.RELATIVE) - public abstract RegularFileProperty getInputFile(); + @Parameter(required = true) + private File inputDirectory; - @OutputDirectory - public abstract DirectoryProperty getOutputDirectory(); + @Parameter(required = true) + private File outputDirectory; - private final Logger log = getLogger(); + private final Log log = getLog(); private final Set seenInterfaces = new HashSet<>(); private final Set seenComponents = new HashSet<>(); - @TaskAction - public void packComponents() throws IOException + @Override + public void execute() throws MojoExecutionException, MojoFailureException { - File inputFile = getInputFile().getAsFile().get(); - File outputDirectory = getOutputDirectory().getAsFile().get(); - TypeSpec.Builder interfaceType = TypeSpec.classBuilder("InterfaceID") .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addAnnotation(Deprecated.class) - .addJavadoc("@deprecated Use {@link net.runelite.api.gameval.InterfaceID} instead");; + .addJavadoc("@deprecated Use {@link net.runelite.api.gameval.InterfaceID} instead"); TypeSpec.Builder componentType = TypeSpec.classBuilder("ComponentID") .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addAnnotation(Deprecated.class) .addJavadoc("@deprecated Use nested classes of {@link net.runelite.api.gameval.InterfaceID} instead"); - executeOne(inputFile, interfaceType, componentType); + for (File file : inputDirectory.listFiles((dir, name) -> name.endsWith(".toml"))) + { + executeOne(file, interfaceType, componentType); + } + + writeClass("net.runelite.api.widgets", interfaceType.build()); + writeClass("net.runelite.api.widgets", componentType.build()); - writeClass(outputDirectory, "net.runelite.api.widgets", interfaceType.build()); - writeClass(outputDirectory, "net.runelite.api.widgets", componentType.build()); + // https://stackoverflow.com/a/30760908 + project.addCompileSourceRoot(outputDirectory.getAbsolutePath()); } - private void executeOne(File file, TypeSpec.Builder interfaceType, TypeSpec.Builder componentType) throws IOException + private void executeOne(File file, TypeSpec.Builder interfaceType, TypeSpec.Builder componentType) throws MojoExecutionException { - TomlParseResult result = Toml.parse(file.toPath()); + TomlParseResult result; + try + { + result = Toml.parse(file.toPath()); + } + catch (IOException e) + { + throw new MojoExecutionException("unable to read component file " + file.getName(), e); + } if (result.hasErrors()) { @@ -95,7 +108,7 @@ private void executeOne(File file, TypeSpec.Builder interfaceType, TypeSpec.Buil { log.error(err.toString()); } - throw new RuntimeException("unable to parse component file " + file.getName()); + throw new MojoExecutionException("unable to parse component file " + file.getName()); } for (var entry : result.entrySet()) @@ -105,18 +118,18 @@ private void executeOne(File file, TypeSpec.Builder interfaceType, TypeSpec.Buil if (!tbl.contains("id")) { - throw new RuntimeException("interface " + interfaceName + " has no id"); + throw new MojoExecutionException("interface " + interfaceName + " has no id"); } int interfaceId = (int) (long) tbl.getLong("id"); if (interfaceId < 0 || interfaceId > 0xffff) { - throw new RuntimeException("interface id out of range for " + interfaceName); + throw new MojoExecutionException("interface id out of range for " + interfaceName); } if (seenInterfaces.contains(interfaceId)) { - throw new RuntimeException("duplicate interface id " + interfaceId); + throw new MojoExecutionException("duplicate interface id " + interfaceId); } seenInterfaces.add(interfaceId); @@ -133,7 +146,7 @@ private void executeOne(File file, TypeSpec.Builder interfaceType, TypeSpec.Buil int id = (int) (long) entry2.getValue(); if (id < 0 || id > 0xffff) { - throw new RuntimeException("component id out of range for " + componentName); + throw new MojoExecutionException("component id out of range for " + componentName); } var fullName = interfaceName.toUpperCase(Locale.ENGLISH) + "_" + componentName.toUpperCase(Locale.ENGLISH); @@ -142,7 +155,7 @@ private void executeOne(File file, TypeSpec.Builder interfaceType, TypeSpec.Buil if (seenComponents.contains(componentId)) { - throw new RuntimeException("duplicate component id " + comment); + throw new MojoExecutionException("duplicate component id " + comment); } seenComponents.add(componentId); @@ -163,10 +176,18 @@ private static void addField(TypeSpec.Builder type, String name, int value, Stri type.addField(field.build()); } - private void writeClass(File outputDirectory, String pkg, TypeSpec type) throws IOException + private void writeClass(String pkg, TypeSpec type) throws MojoExecutionException { - JavaFile.builder(pkg, type) - .build() - .writeToFile(outputDirectory); + JavaFile javaFile = JavaFile.builder(pkg, type) + .build(); + + try + { + javaFile.writeTo(outputDirectory); + } + catch (IOException e) + { + throw new MojoExecutionException("unable to write java class", e); + } } } diff --git a/runelite-gradle-plugin/src/main/java/net/runelite/gradle/index/IndexTask.java b/runelite-maven-plugin/src/main/java/net/runelite/mvn/IndexMojo.java similarity index 69% rename from runelite-gradle-plugin/src/main/java/net/runelite/gradle/index/IndexTask.java rename to runelite-maven-plugin/src/main/java/net/runelite/mvn/IndexMojo.java index 494b0ec9d9e..63cbb6dbcc0 100644 --- a/runelite-gradle-plugin/src/main/java/net/runelite/gradle/index/IndexTask.java +++ b/runelite-maven-plugin/src/main/java/net/runelite/mvn/IndexMojo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, LlemonDuck + * Copyright (c) 2018, Adam * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -22,40 +22,35 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.runelite.gradle.index; +package net.runelite.mvn; import java.io.DataOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import static java.lang.Integer.parseInt; -import org.gradle.api.DefaultTask; -import org.gradle.api.file.DirectoryProperty; -import org.gradle.api.file.RegularFileProperty; -import org.gradle.api.tasks.CacheableTask; -import org.gradle.api.tasks.InputDirectory; -import org.gradle.api.tasks.OutputFile; -import org.gradle.api.tasks.PathSensitive; -import org.gradle.api.tasks.PathSensitivity; -import org.gradle.api.tasks.TaskAction; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; -@CacheableTask -public abstract class IndexTask extends DefaultTask +@Mojo( + name = "build-index", + defaultPhase = LifecyclePhase.GENERATE_RESOURCES +) +public class IndexMojo extends AbstractMojo { + @Parameter(required = true) + private File archiveOverlayDirectory; - @InputDirectory - @PathSensitive(PathSensitivity.RELATIVE) - public abstract DirectoryProperty getArchiveOverlayDirectory(); + @Parameter(required = true) + private File indexFile; - @OutputFile - public abstract RegularFileProperty getIndexFile(); - - @TaskAction - public void buildRs2Index() throws IOException + @Override + public void execute() throws MojoExecutionException, MojoFailureException { - File archiveOverlayDirectory = getArchiveOverlayDirectory().getAsFile().get(); - File indexFile = getIndexFile().getAsFile().get(); - try (DataOutputStream fout = new DataOutputStream(new FileOutputStream(indexFile))) { for (File indexFolder : archiveOverlayDirectory.listFiles()) @@ -82,6 +77,10 @@ public void buildRs2Index() throws IOException fout.writeInt(-1); } + catch (IOException ex) + { + throw new MojoExecutionException("error build index file", ex); + } } } diff --git a/settings.gradle.kts b/settings.gradle.kts deleted file mode 100644 index 549ce19f0fa..00000000000 --- a/settings.gradle.kts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2024, LlemonDuck - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -rootProject.name = "runelite" -includeBuild("cache") -includeBuild("runelite-api") -includeBuild("runelite-client") -includeBuild("runelite-gradle-plugin") -includeBuild("runelite-jshell") diff --git a/config/checkstyle/suppressions.xml b/suppressions.xml similarity index 87% rename from config/checkstyle/suppressions.xml rename to suppressions.xml index 004da9e22e2..962c26ceb0d 100644 --- a/config/checkstyle/suppressions.xml +++ b/suppressions.xml @@ -28,8 +28,5 @@ "https://checkstyle.org/dtds/suppressions_1_1.dtd"> - - - From 22fa420b58571c8c7d65a79ab7b303c0a4d425ae Mon Sep 17 00:00:00 2001 From: Rhea Date: Sun, 21 Dec 2025 12:45:58 -0500 Subject: [PATCH 04/13] all: move to gradle --- .github/workflows/CI.yml | 25 +- .gitignore | 44 +- .mvn/jvm.config | 1 - build.gradle.kts | 43 ++ cache/build.gradle.kts | 117 ++++ cache/pom.xml | 178 ------ cache/settings.gradle.kts | 27 + ci/build.sh | 3 +- ci/settings.xml | 280 ---------- common.settings.gradle.kts | 111 ++++ .../checkstyle/checkstyle.xml | 3 +- .../checkstyle/suppressions.xml | 3 + gradle.properties | 33 ++ gradle/verification-metadata.xml | 468 ++++++++++++++++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 60756 bytes gradle/wrapper/gradle-wrapper.properties | 7 + gradlew | 234 ++++++++ gradlew.bat | 89 +++ libs.versions.toml | 85 +++ pom.xml | 332 ----------- runelite-api/build.gradle.kts | 139 +++++ runelite-api/pom.xml | 178 ------ runelite-api/settings.gradle.kts | 29 + runelite-client/build.gradle.kts | 201 +++++++ runelite-client/pom.xml | 517 ------------------ runelite-client/settings.gradle.kts | 30 + runelite-gradle-plugin/build.gradle.kts | 67 +++ .../pmd-ruleset.xml | 0 runelite-gradle-plugin/settings.gradle.kts | 29 + .../gradle/assemble/AssemblePlugin.java | 46 ++ .../gradle/assemble/AssembleTask.java | 77 +-- .../gradle/component/ComponentPlugin.java | 53 ++ .../gradle/component/ComponentTask.java | 103 ++-- .../runelite/gradle/index/IndexPlugin.java | 44 ++ .../net/runelite/gradle/index/IndexTask.java | 47 +- .../gradle/jarsign/JarsignExtension.java | 41 ++ .../gradle/jarsign/JarsignPlugin.java | 82 +++ .../runelite/gradle/jarsign/JarsignTask.java | 94 ++++ runelite-jshell/build.gradle.kts | 54 ++ runelite-jshell/pom.xml | 87 --- runelite-jshell/settings.gradle.kts | 27 + runelite-maven-plugin/pom.xml | 112 ---- settings.gradle.kts | 31 ++ 43 files changed, 2344 insertions(+), 1827 deletions(-) delete mode 100644 .mvn/jvm.config create mode 100644 build.gradle.kts create mode 100644 cache/build.gradle.kts delete mode 100644 cache/pom.xml create mode 100644 cache/settings.gradle.kts delete mode 100644 ci/settings.xml create mode 100644 common.settings.gradle.kts rename checkstyle.xml => config/checkstyle/checkstyle.xml (96%) rename suppressions.xml => config/checkstyle/suppressions.xml (87%) create mode 100644 gradle.properties create mode 100644 gradle/verification-metadata.xml create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 libs.versions.toml delete mode 100644 pom.xml create mode 100644 runelite-api/build.gradle.kts delete mode 100644 runelite-api/pom.xml create mode 100644 runelite-api/settings.gradle.kts create mode 100644 runelite-client/build.gradle.kts delete mode 100644 runelite-client/pom.xml create mode 100644 runelite-client/settings.gradle.kts create mode 100644 runelite-gradle-plugin/build.gradle.kts rename {runelite-maven-plugin => runelite-gradle-plugin}/pmd-ruleset.xml (100%) create mode 100644 runelite-gradle-plugin/settings.gradle.kts create mode 100644 runelite-gradle-plugin/src/main/java/net/runelite/gradle/assemble/AssemblePlugin.java rename runelite-maven-plugin/src/main/java/net/runelite/mvn/AssembleMojo.java => runelite-gradle-plugin/src/main/java/net/runelite/gradle/assemble/AssembleTask.java (68%) create mode 100644 runelite-gradle-plugin/src/main/java/net/runelite/gradle/component/ComponentPlugin.java rename runelite-maven-plugin/src/main/java/net/runelite/mvn/ComponentMojo.java => runelite-gradle-plugin/src/main/java/net/runelite/gradle/component/ComponentTask.java (62%) create mode 100644 runelite-gradle-plugin/src/main/java/net/runelite/gradle/index/IndexPlugin.java rename runelite-maven-plugin/src/main/java/net/runelite/mvn/IndexMojo.java => runelite-gradle-plugin/src/main/java/net/runelite/gradle/index/IndexTask.java (69%) create mode 100644 runelite-gradle-plugin/src/main/java/net/runelite/gradle/jarsign/JarsignExtension.java create mode 100644 runelite-gradle-plugin/src/main/java/net/runelite/gradle/jarsign/JarsignPlugin.java create mode 100644 runelite-gradle-plugin/src/main/java/net/runelite/gradle/jarsign/JarsignTask.java create mode 100644 runelite-jshell/build.gradle.kts delete mode 100644 runelite-jshell/pom.xml create mode 100644 runelite-jshell/settings.gradle.kts delete mode 100644 runelite-maven-plugin/pom.xml create mode 100644 settings.gradle.kts diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index ebf2f182577..b338871fc6c 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -9,27 +9,32 @@ jobs: permissions: contents: read - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - persist-credentials: false + env: + GRADLE_OPTS: "-Dfile:encoding=UTF-8" - - name: Cache - uses: actions/cache@v4 + steps: + - name: Cache glslang + uses: actions/cache@v5 with: path: | - ~/.m2/repository ~/.cache/runelite - key: ${{ runner.os }}-cache-${{ hashFiles('**/pom.xml', '**/build.sh', '**/pmd-ruleset.xml') }} + key: ${{ runner.os }}-cache-${{ hashFiles('ci/build.sh') }} restore-keys: | ${{ runner.os }}-cache- + - name: Checkout + uses: actions/checkout@v4 + - name: Set up JDK 11 uses: actions/setup-java@v4 with: distribution: temurin java-version: 11 - - name: Build + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + gradle-version: wrapper + + - name: Build and Test run: ./ci/build.sh diff --git a/.gitignore b/.gitignore index ff14386ce44..f35ca9ebe00 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,39 @@ -target -nbactions.xml -nb-configuration.xml -/nbproject/ -project.properties -*.iml +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### IntelliJ IDEA ### .idea/ -.project -.settings/ +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Eclipse ### +.apt_generated .classpath -.vscode .factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### .DS_Store \ No newline at end of file diff --git a/.mvn/jvm.config b/.mvn/jvm.config deleted file mode 100644 index 67bd169f319..00000000000 --- a/.mvn/jvm.config +++ /dev/null @@ -1 +0,0 @@ --Xmx512m diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 00000000000..a9e40b66a18 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2024, LlemonDuck + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +tasks.register("cleanAll") { + gradle.includedBuilds.forEach { build -> this@register.dependsOn(build.task(":clean")) } +} +tasks.register("buildAll") { + gradle.includedBuilds.forEach { build -> this@register.dependsOn(build.task(":build")) } +} +tasks.register("assembleAll") { + gradle.includedBuilds.forEach { build -> this@register.dependsOn(build.task(":assemble")) } +} +tasks.register("testAll") { + gradle.includedBuilds.forEach { build -> this@register.dependsOn(build.task(":test")) } +} +tasks.register("publishAll") { + this@register.dependsOn(gradle.includedBuild("cache").task(":publish")) + this@register.dependsOn(gradle.includedBuild("runelite-api").task(":publish")) + this@register.dependsOn(gradle.includedBuild("runelite-client").task(":publish")) + this@register.dependsOn(gradle.includedBuild("runelite-jshell").task(":publish")) +} diff --git a/cache/build.gradle.kts b/cache/build.gradle.kts new file mode 100644 index 00000000000..d9a04312a88 --- /dev/null +++ b/cache/build.gradle.kts @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2024, LlemonDuck + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +plugins { + java + `maven-publish` + antlr + alias(libs.plugins.lombok) +} + +lombok.version = libs.versions.lombok.get() + +java { + withJavadocJar() + withSourcesJar() +} + +dependencies { + antlr(libs.antlr.core) + implementation(libs.antlr.runtime) + + implementation(libs.guava) + implementation(libs.slf4j.api) + runtimeOnly(libs.slf4j.simple) + implementation(libs.commons.compress) + implementation(libs.gson) + implementation(libs.commons.cli) + implementation(libs.jna.core) + + testImplementation(libs.junit) + testImplementation(libs.rs.cache) +} + +// the gradle antlr plugin adds all of antlr to runtimeClasspath, +// workaround that https://github.com/gradle/gradle/issues/820 +configurations { + api { + setExtendsFrom(extendsFrom.filterNot { it == antlr.get() }) + } +} + +sourceSets { + main { + antlr { setSrcDirs(listOf("src/main/antlr4")) } + } +} + +publishing { + publications { + create("cache") { + from(components["java"]) + } + } +} + +tasks.processTestResources { + filesMatching("cache.properties") { + filter { it.replace("\${rs.version}", libs.versions.rs.get()) } + filter { it.replace("\${cache.version}", libs.versions.cache.get()) } + } +} + +tasks.test { + enabled = false + jvmArgs("-Xmx2048m") +} + +// everything from here down is accounting for antlr sources in varying ways +tasks.named("sourcesJar", Jar::class) { + dependsOn(tasks.generateGrammarSource) + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + exclude("net/runelite/cache/script/assembler/*.interp") + exclude("net/runelite/cache/script/assembler/*.tokens") +} + +tasks.javadoc { + exclude("net/runelite/cache/script/assembler/*.interp") + exclude("net/runelite/cache/script/assembler/*.tokens") +} + +tasks.checkstyleMain { + exclude("net/runelite/cache/script/assembler/*.java") +} + +tasks.generateGrammarSource { + arguments.addAll(listOf("-package", "net.runelite.cache.script.assembler")) +} + +afterEvaluate { + tasks.named("generateEffectiveLombokConfig") { + // lombok won't find anything in the antlr generated sources, but it looks in there regardless + // and gradle complains if you don't provide an explicit task dependency between the two + dependsOn(tasks.generateGrammarSource) + } +} diff --git a/cache/pom.xml b/cache/pom.xml deleted file mode 100644 index 5085ec783ef..00000000000 --- a/cache/pom.xml +++ /dev/null @@ -1,178 +0,0 @@ - - - - 4.0.0 - - - net.runelite - runelite-parent - 1.12.10-SNAPSHOT - - - cache - Cache - - - 165 - - 4.13.1 - - - - - com.google.guava - guava - - - org.slf4j - slf4j-api - - - org.slf4j - slf4j-simple - true - - - org.apache.commons - commons-compress - 1.10 - - - com.google.code.gson - gson - - - org.antlr - antlr4-runtime - ${antlr4.version} - - - commons-cli - commons-cli - 1.3.1 - - - org.projectlombok - lombok - provided - - - net.java.dev.jna - jna - 5.9.0 - - - - junit - junit - 4.12 - test - - - net.runelite.rs - cache - ${cache.version} - test - - - - - - - src/test/resources - true - - - - - org.apache.maven.plugins - maven-surefire-plugin - - true - -Xmx2048m - - ${cache.tmpdir} - - - - - maven-assembly-plugin - - - jar-with-dependencies - - - - - make-assembly - package - - single - - - - - - org.antlr - antlr4-maven-plugin - ${antlr4.version} - - - process-resources - - antlr4 - - - - - - - org.projectlombok - lombok-maven-plugin - - - org.apache.maven.plugins - maven-javadoc-plugin - - - ${project.build.directory}/delombok;${project.build.directory}/generated-sources/antlr4 - - - - org.apache.maven.plugins - maven-jar-plugin - - - - test-jar - - - - - - - diff --git a/cache/settings.gradle.kts b/cache/settings.gradle.kts new file mode 100644 index 00000000000..ccc997b18fe --- /dev/null +++ b/cache/settings.gradle.kts @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024, LlemonDuck + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +rootProject.name = "cache" +apply(from = "../common.settings.gradle.kts") diff --git a/ci/build.sh b/ci/build.sh index a8092698243..e863cdf7bf6 100755 --- a/ci/build.sh +++ b/ci/build.sh @@ -15,4 +15,5 @@ if [ ! -f "${GLSLANG_ARCHIVE}" ] || [ ! -d "${GLSLANG_DIR}" ] || ! echo "${GLSLA unzip -o -q "${GLSLANG_ARCHIVE}" -d "${GLSLANG_DIR}" fi -mvn verify --settings ci/settings.xml -Dglslang.path="${GLSLANG_DIR}/bin/glslangValidator" +export ORG_GRADLE_PROJECT_glslangPath="$GLSLANG_DIR/bin/glslangValidator" +./gradlew ':buildAll' diff --git a/ci/settings.xml b/ci/settings.xml deleted file mode 100644 index 44c93408311..00000000000 --- a/ci/settings.xml +++ /dev/null @@ -1,280 +0,0 @@ - - - - - - - - - - false - - - - - - - - - - - - - - - - - - - - - runelite - repo - ${env.REPO_PASSWORD} - - - - - - - - - - - - - - - - - runelite - - - false - false - false - ${user.home}/.cache/runelite/pmd.cache - true - - - - - - - runelite - - diff --git a/common.settings.gradle.kts b/common.settings.gradle.kts new file mode 100644 index 00000000000..8a7d31ac2a6 --- /dev/null +++ b/common.settings.gradle.kts @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2024, LlemonDuck + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +dependencyResolutionManagement { + repositories { + maven(uri("https://repo.runelite.net")) { + name = "rrn" + + content { + includeGroupAndSubgroups("net.runelite") + } + } + mavenCentral { + content { excludeGroupAndSubgroups("net.runelite") } + } + } + + versionCatalogs { + create("libs") { + from(files("./libs.versions.toml")) + } + } +} + +// set up some defaults +val rootProps = file("./gradle.properties").inputStream().use { stream -> + java.util.Properties().apply { load(stream) } +} +val checkstyleDir = file("./config/checkstyle/") +gradle.beforeProject { + apply(plugin = "idea") + apply(plugin = "checkstyle") + group = rootProps["project.build.group"] as String + version = rootProps["project.build.version"] as String + + tasks.withType { + options.encoding = "UTF-8" + options.release = 11 + } +} + +gradle.afterProject { + tasks.withType { + (this.options as StandardJavadocDocletOptions).apply { + quiet() + encoding("UTF-8") + use(true) + bottom("Copyright © 2014") + links( + "https://docs.oracle.com/en/java/javase/11/docs/api/" + ) + } + } + + // shared checkstyle config + extensions.findByType()?.run { + toolVersion = "8.3" + configDirectory = file("../config/checkstyle") + } + + // shared publishing config + tasks.withType { enabled = false } + extensions.findByType()?.run { + repositories { + maven(uri(providers.gradleProperty("rrnUrl").getOrElse("https://repo.runelite.net"))) { + name = "rrn" + if (url.scheme != "file") { + credentials(PasswordCredentials::class) { + username = providers.gradleProperty("rrnPublishUsername").orElse(providers.gradleProperty("rrnUsername")).getOrElse("") + password = providers.gradleProperty("rrnPublishPassword").orElse(providers.gradleProperty("rrnPassword")).getOrElse("") + } + } + } + } + } + + // produce reproducible outputs + tasks.withType { + isPreserveFileTimestamps = false + isReproducibleFileOrder = true + } + + extensions.findByType()?.run { + module { + isDownloadSources = true + isDownloadJavadoc = true + } + } +} diff --git a/checkstyle.xml b/config/checkstyle/checkstyle.xml similarity index 96% rename from checkstyle.xml rename to config/checkstyle/checkstyle.xml index 97efb7194ea..c8287888985 100644 --- a/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -27,6 +27,7 @@ "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN" "https://checkstyle.org/dtds/configuration_1_3.dtd"> + @@ -57,6 +58,6 @@ - + diff --git a/suppressions.xml b/config/checkstyle/suppressions.xml similarity index 87% rename from suppressions.xml rename to config/checkstyle/suppressions.xml index 962c26ceb0d..004da9e22e2 100644 --- a/suppressions.xml +++ b/config/checkstyle/suppressions.xml @@ -28,5 +28,8 @@ "https://checkstyle.org/dtds/suppressions_1_1.dtd"> + + + diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 00000000000..e94a2c8e523 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,33 @@ +# +# Copyright (c) 2024, LlemonDuck +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +org.gradle.jvmargs=-Dfile.encoding=UTF-8 -Xmx1024m +org.gradle.parallel=true +org.gradle.caching=true + +project.build.group=net.runelite +project.build.version=1.12.10-SNAPSHOT + +glslang.path= diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml new file mode 100644 index 00000000000..0b929186cf3 --- /dev/null +++ b/gradle/verification-metadata.xml @@ -0,0 +1,468 @@ + + + + false + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..249e5832f090a2944b7473328c07c9755baa3196 GIT binary patch literal 60756 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 '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 00000000000..107acd32c4e --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/libs.versions.toml b/libs.versions.toml new file mode 100644 index 00000000000..23d656b53e9 --- /dev/null +++ b/libs.versions.toml @@ -0,0 +1,85 @@ +# +# Copyright (c) 2024, LlemonDuck +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +[versions] +rs = "235" +cache = "165" + +flatlaf = "3.2.5-rl4" +guice = "4.1.0" +lombok = "1.18.30" +logback = "1.2.9" +lwjgl = "3.3.2" +slf4j = "1.7.25" +antlr = "4.13.1" + + +[libraries] +rs-vanilla = { module = "net.runelite.rs:vanilla", version.ref = "rs" } +rs-cache = { module = "net.runelite.rs:cache", version.ref = "cache" } + +flatlaf-core = { module = "net.runelite:flatlaf", version.ref = "flatlaf" } +flatlaf-extras = { module = "net.runelite:flatlaf-extras", version.ref = "flatlaf" } +rl-http-api = "net.runelite.arn:http-api:1.2.21" +rl-awt = "net.runelite:rlawt:1.7" +rl-discord = "net.runelite:discord:1.4" +rl-orange = "net.runelite:orange-extensions:1.1" + +antlr-core = { module = "org.antlr:antlr4", version.ref="antlr" } +antlr-runtime = { module = "org.antlr:antlr4-runtime", version.ref="antlr" } +commons-compress = "org.apache.commons:commons-compress:1.10" +commons-text = "org.apache.commons:commons-text:1.2" +commons-cli = "commons-cli:commons-cli:1.3.1" +fife-rsyntaxtextarea = "com.fifesoft:rsyntaxtextarea:3.1.2" +fife-autocomplete = "com.fifesoft:autocomplete:3.1.1" +findbugs = "com.google.code.findbugs:jsr305:3.0.2" +gson = "com.google.code.gson:gson:2.8.5" +guava = "com.google.guava:guava:23.2-jre" +guice-core = { module = "com.google.inject:guice", version.ref = "guice" } +guice-testlib = { module = "com.google.inject.extensions:guice-testlib", version.ref = "guice" } +guice-grapher = { module = "com.google.inject.extensions:guice-grapher", version.ref = "guice" } +hamcrest = "org.hamcrest:hamcrest-library:1.3" +javapoet = "com.squareup:javapoet:1.13.0" +javax-inject = "javax.inject:javax.inject:1" +jetbrains-annotations = "org.jetbrains:annotations:23.0.0" +jna-core = "net.java.dev.jna:jna:5.9.0" +jna-platform = "net.java.dev.jna:jna-platform:5.9.0" +jopt = "net.sf.jopt-simple:jopt-simple:5.0.1" +junit = "junit:junit:4.12" +logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } +logback-core = { module = "ch.qos.logback:logback-core", version.ref = "logback" } +lwjgl-core = { module = "org.lwjgl:lwjgl", version.ref = "lwjgl" } +lwjgl-opengl = { module = "org.lwjgl:lwjgl-opengl", version.ref = "lwjgl" } +lwjgl-opencl = { module = "org.lwjgl:lwjgl-opencl", version.ref = "lwjgl" } +mockito = "org.mockito:mockito-core:3.1.0" +okhttp-mockserver = "com.squareup.okhttp3:mockwebserver:3.14.9" +protobuf = "com.google.protobuf:protobuf-javalite:3.21.12" +slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } +slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j" } +tomlj = "org.tomlj:tomlj:1.1.0" + + + +[plugins] +lombok = { id = "io.freefair.lombok", version = "8.10.2" } diff --git a/pom.xml b/pom.xml deleted file mode 100644 index fc5e0934cb0..00000000000 --- a/pom.xml +++ /dev/null @@ -1,332 +0,0 @@ - - - - 4.0.0 - - net.runelite - runelite-parent - 1.12.10-SNAPSHOT - pom - - RuneLite - Open source RuneScape client - http://runelite.net - - - UTF-8 - 1 - 1.18.30 - 1.2.9 - 1.7.25 - 3.2.5-rl4 - - true - true - - - - - - 2-Clause BSD License - https://opensource.org/licenses/BSD-2-Clause - - - - 2014 - - - https://github.com/runelite/runelite - scm:git:git://github.com/runelite/runelite - scm:git:git@github.com:runelite/runelite - HEAD - - - - - Adam- - Adam - Adam@sigterm.info - - - - - GitHub Issues - https://github.com/runelite/runelite/issues - - - - - central - Central Repository - https://repo.maven.apache.org/maven2 - - false - - - - runelite - RuneLite - https://repo.runelite.net - - true - always - - - - - - central - Central Repository - https://repo.maven.apache.org/maven2 - - never - - - false - - - - runelite-plugins - RuneLite Plugins - https://repo.runelite.net - - true - always - - - - - - cache - runelite-api - runelite-client - runelite-jshell - runelite-maven-plugin - - - - - - com.google.guava - guava - 23.2-jre - - - org.projectlombok - lombok - ${lombok.version} - provided - - - com.google.code.gson - gson - 2.8.5 - - - com.google.code.findbugs - jsr305 - 3.0.2 - - - ch.qos.logback - logback-classic - ${logback.version} - - - ch.qos.logback - logback-core - ${logback.version} - - - org.slf4j - slf4j-api - ${slf4j.version} - - - org.slf4j - slf4j-simple - ${slf4j.version} - - - com.google.inject - guice-bom - 4.1.0 - pom - import - - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - 11 - - - - org.apache.maven.plugins - maven-surefire-plugin - 2.22.2 - - true - -Xmx512m -Duser.language=en -Duser.region=US - - ${glslang.path} - - - - - org.apache.maven.plugins - maven-release-plugin - 2.5.3 - - - org.projectlombok - lombok-maven-plugin - 1.18.20.0 - - - ${maven.javadoc.skip} - ${project.basedir}/src/main/java - - ${project.build.directory}/delombok - false - - - - generate-sources - - delombok - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 3.5.0 - - - ${project.build.directory}/delombok - - Copyright © {inceptionYear} - - - - - attach-javadocs - - jar - - - - - - org.apache.maven.plugins - maven-checkstyle-plugin - 2.17 - - - com.puppycrawl.tools - checkstyle - 8.3 - - - - - verify-style - process-classes - - check - - - - - checkstyle.xml - - - ${project.build.sourceDirectory} - - true - - - - - - - org.apache.maven.plugins - maven-assembly-plugin - 3.2.0 - - - org.apache.maven.plugins - maven-jar-plugin - 3.2.0 - - - org.apache.maven.plugins - maven-compiler-plugin - 3.6.1 - - - org.apache.maven.plugins - maven-deploy-plugin - 2.8.2 - - - org.apache.maven.plugins - maven-plugin-plugin - 3.6.0 - - - org.apache.maven.plugins - maven-pmd-plugin - 3.22.0 - - - net.sourceforge.pmd - pmd-core - 7.2.0 - - - net.sourceforge.pmd - pmd-java - 7.2.0 - - - - - - - diff --git a/runelite-api/build.gradle.kts b/runelite-api/build.gradle.kts new file mode 100644 index 00000000000..4efc0b7307f --- /dev/null +++ b/runelite-api/build.gradle.kts @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2024, LlemonDuck + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +plugins { + java + `maven-publish` + checkstyle + alias(libs.plugins.lombok) + + id("net.runelite.runelite-gradle-plugin.component") +} + +lombok.version = libs.versions.lombok.get() + +java { + withJavadocJar() + withSourcesJar() +} + +dependencies { + implementation(libs.slf4j.api) + compileOnly(libs.findbugs) + + compileOnly(libs.jetbrains.annotations) + + testImplementation(libs.junit) + testRuntimeOnly(libs.slf4j.simple) +} + +val runtimeJar = tasks.register("runtimeJar") { + this@register.group = BasePlugin.BUILD_GROUP + + from(sourceSets.main.get().output) + archiveClassifier = "runtime" + + /* from JLS 13.1.3: references to a static field that is a constant variable (§4.12.4) must be resolved + at compile time to the value denoted by the constant variable's initializer *and* no references to the + field should be present in the code in a binary file. + + This permits us to remove the classes containing only these types of fields at runtime */ + exclude("net/runelite/api/annotations/*.class") + exclude("net/runelite/api/clan/ClanID.class") + exclude("net/runelite/api/dbtable/DBTableID*.class") + exclude("net/runelite/api/widgets/ComponentID.class") + exclude("net/runelite/api/widgets/InterfaceID.class") + exclude("net/runelite/api/widgets/ItemQuantityMode.class") + exclude("net/runelite/api/widgets/WidgetID*.class") + exclude("net/runelite/api/widgets/WidgetModalMode.class") + exclude("net/runelite/api/widgets/WidgetModelType.class") + exclude("net/runelite/api/widgets/WidgetPositionMode.class") + exclude("net/runelite/api/widgets/WidgetSizeMode.class") + exclude("net/runelite/api/widgets/WidgetTextAlignment.class") + exclude("net/runelite/api/widgets/WidgetType.class") + exclude("net/runelite/api/AnimationID.class") + exclude("net/runelite/api/CollisionDataFlag.class") + exclude("net/runelite/api/EnumID.class") + exclude("net/runelite/api/FontID.class") + exclude("net/runelite/api/GraphicID.class") + exclude("net/runelite/api/HintArrowType.class") + exclude("net/runelite/api/HitsplatID.class") + exclude("net/runelite/api/ItemID.class") + exclude("net/runelite/api/KeyCode.class") + exclude("net/runelite/api/NpcID.class") + exclude("net/runelite/api/NullItemID.class") + exclude("net/runelite/api/NullNpcID.class") + exclude("net/runelite/api/NullObjectID.class") + exclude("net/runelite/api/ObjectID.class") + exclude("net/runelite/api/Opcodes.class") + exclude("net/runelite/api/ParamID.class") + exclude("net/runelite/api/ScriptID.class") + exclude("net/runelite/api/SettingID.class") + exclude("net/runelite/api/SkullIcon.class") + exclude("net/runelite/api/SoundEffectID.class") + exclude("net/runelite/api/SoundEffectVolume.class") + exclude("net/runelite/api/SpriteID.class") + exclude("net/runelite/api/StructID.class") + exclude("net/runelite/api/Varbits.class") + exclude("net/runelite/api/VarClientInt.class") + exclude("net/runelite/api/VarClientStr.class") + exclude("net/runelite/api/VarPlayer.class") + exclude("net/runelite/api/gameval/*.class") +} +tasks.assemble { dependsOn(runtimeJar) } + +publishing { + publications { + create("api") { + from(components["java"]) + artifact(runtimeJar) { classifier = "runtime" } + } + } +} + +tasks.withType { + inputFile = file("src/main/interfaces/interfaces.toml") + outputDirectory = file("build/generated/sources/runelite/java/main") +} + +tasks.checkstyleMain { + exclude("net/runelite/api/widgets/ComponentID.java") + exclude("net/runelite/api/widgets/InterfaceID.java") +} + +tasks.javadoc { + title = "RuneLite API ${project.version} API" + + exclude( + "net/runelite/api/gameval/**", + "net/runelite/api/AnimationID.java", + "net/runelite/api/ItemID.java", + "net/runelite/api/NullItemID.java", + "net/runelite/api/ObjectID.java", + "net/runelite/api/NullObjectID.java", + "net/runelite/api/NpcID.java", + "net/runelite/api/NullNpcID.java", + ) +} diff --git a/runelite-api/pom.xml b/runelite-api/pom.xml deleted file mode 100644 index c9715db53c4..00000000000 --- a/runelite-api/pom.xml +++ /dev/null @@ -1,178 +0,0 @@ - - - - 4.0.0 - - - net.runelite - runelite-parent - 1.12.10-SNAPSHOT - - - runelite-api - RuneLite API - https://static.runelite.net/runelite-api - - - - org.slf4j - slf4j-api - - - org.projectlombok - lombok - provided - - - com.google.code.findbugs - jsr305 - provided - - - org.jetbrains - annotations - 23.0.0 - provided - - - - junit - junit - 4.12 - test - - - org.slf4j - slf4j-simple - test - - - - - - - org.apache.maven.plugins - maven-jar-plugin - - - - runtime-jar - package - - jar - - - runtime - - net/runelite/api/annotations/*.class - net/runelite/api/clan/ClanID.class - net/runelite/api/dbtable/DBTableID*.class - net/runelite/api/widgets/ComponentID.class - net/runelite/api/widgets/InterfaceID.class - net/runelite/api/widgets/ItemQuantityMode.class - net/runelite/api/widgets/WidgetID*.class - net/runelite/api/widgets/WidgetModalMode.class - net/runelite/api/widgets/WidgetModelType.class - net/runelite/api/widgets/WidgetPositionMode.class - net/runelite/api/widgets/WidgetSizeMode.class - net/runelite/api/widgets/WidgetTextAlignment.class - net/runelite/api/widgets/WidgetType.class - net/runelite/api/AnimationID.class - net/runelite/api/CollisionDataFlag.class - net/runelite/api/EnumID.class - net/runelite/api/FontID.class - net/runelite/api/GraphicID.class - net/runelite/api/HintArrowType.class - net/runelite/api/HitsplatID.class - net/runelite/api/ItemID.class - net/runelite/api/KeyCode.class - net/runelite/api/NpcID.class - net/runelite/api/NullItemID.class - net/runelite/api/NullNpcID.class - net/runelite/api/NullObjectID.class - net/runelite/api/ObjectID.class - net/runelite/api/Opcodes.class - net/runelite/api/ParamID.class - net/runelite/api/ScriptID.class - net/runelite/api/SettingID.class - net/runelite/api/SkullIcon.class - net/runelite/api/SoundEffectID.class - net/runelite/api/SoundEffectVolume.class - net/runelite/api/SpriteID.class - net/runelite/api/StructID.class - net/runelite/api/Varbits.class - net/runelite/api/VarClientInt.class - net/runelite/api/VarClientStr.class - net/runelite/api/VarPlayer.class - net/runelite/api/gameval/*.class - - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - - - ${project.build.directory}/delombok;${project.build.directory}/generated-sources - net.runelite.api.gameval - - net/runelite/api/AnimationID.java - net/runelite/api/ItemID.java - net/runelite/api/NullItemID.java - net/runelite/api/ObjectID.java - net/runelite/api/NullObjectID.java - net/runelite/api/NpcID.java - net/runelite/api/NullNpcID.java - - - - - net.runelite - runelite-maven-plugin - ${project.version} - - - pack-components - - pack-components - - - src/main/interfaces - ${project.build.directory}/generated-sources - - - - - - - diff --git a/runelite-api/settings.gradle.kts b/runelite-api/settings.gradle.kts new file mode 100644 index 00000000000..b640a5c2f81 --- /dev/null +++ b/runelite-api/settings.gradle.kts @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024, LlemonDuck + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +rootProject.name = "runelite-api" +apply(from = "../common.settings.gradle.kts") + +includeBuild("../runelite-gradle-plugin") diff --git a/runelite-client/build.gradle.kts b/runelite-client/build.gradle.kts new file mode 100644 index 00000000000..a7af7becf10 --- /dev/null +++ b/runelite-client/build.gradle.kts @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2024, LlemonDuck + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import java.io.ByteArrayOutputStream + +plugins { + java + `java-library` + `maven-publish` + pmd + alias(libs.plugins.lombok) + + id("net.runelite.runelite-gradle-plugin.assemble") + id("net.runelite.runelite-gradle-plugin.index") + id("net.runelite.runelite-gradle-plugin.jarsign") +} + +lombok.version = libs.versions.lombok.get() + +java { + withJavadocJar() + withSourcesJar() +} + +dependencies { + api("net.runelite:runelite-api:${project.version}") + implementation("net.runelite:jshell:${project.version}") + runtimeOnly("net.runelite:injected-client:${project.version}") + + api(libs.rl.http.api) + implementation(libs.rl.discord) + implementation(libs.rl.awt) + compileOnly(libs.rl.orange) + + api(libs.slf4j.api) + implementation(libs.logback.classic) { + exclude("org.slf4j", "slf4j-api") + } + implementation(libs.jopt) + api(libs.guava) { + exclude("com.google.code.findbugs", "jsr305") + exclude("com.google.errorprone", "error_prone_annotations") + exclude("com.google.j2objc", "j2objc-annotations") + exclude("org.codehaus.mojo", "animal-sniffer-annotations") + } + api(variantOf(libs.guice.core) { classifier("no_aop") }) + api(libs.gson) + implementation(libs.flatlaf.core) + implementation(libs.flatlaf.extras) + implementation(libs.commons.text) + implementation(libs.jna.core) + implementation(libs.jna.platform) + implementation(libs.findbugs) + compileOnly(libs.jetbrains.annotations) + implementation(libs.protobuf) + api(libs.lwjgl.core) + api(libs.lwjgl.opengl) + api(libs.lwjgl.opencl) + + for (platform in listOf( + "linux", + "linux-arm64", + "macos", + "macos-arm64", + "windows-x86", + "windows", + "windows-arm64", + )) { + runtimeOnly(variantOf(libs.lwjgl.core) { classifier("natives-$platform") }) + runtimeOnly(variantOf(libs.lwjgl.opengl) { classifier("natives-$platform") }) + } + + testImplementation(libs.junit) + testImplementation(libs.hamcrest) + testImplementation(libs.mockito) + testImplementation(libs.guice.testlib) + testImplementation(libs.guice.grapher) + testImplementation(libs.okhttp.mockserver) +} + +val shadowJar = tasks.register("shadowJar") { + dependsOn(configurations.runtimeClasspath) + manifest { + attributes["Main-Class"] = "net.runelite.client.RuneLite" + } + + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + + from(sourceSets.main.get().output) + from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) }) + + exclude( + "META-INF/INDEX.LIST", + "META-INF/*.SF", + "META-INF/*.DSA", + "META-INF/*.RSA", + "**/module-info.class" + ) + + group = BasePlugin.BUILD_GROUP + archiveClassifier = "shadow" + archiveFileName = project.name + "-" + project.version + "-shaded.jar" +} +tasks.assemble { dependsOn(shadowJar) } + +publishing { + publications { + create("runelite-client") { + from(components["java"]) + artifact(shadowJar) { classifier = "shaded" } + } + } +} + +val assemble = tasks.withType { + scriptDirectory = file("src/main/scripts") + outputDirectory = sourceSets.main.map { File(it.output.resourcesDir, "runelite") } + componentsFile = file("../runelite-api/src/main/interfaces/interfaces.toml") +} + +tasks.withType { + archiveOverlayDirectory = assemble.single().outputDirectory + indexFile = archiveOverlayDirectory.file("index") +} + +tasks.processResources { + val commit = ByteArrayOutputStream() + exec { + commandLine("git", "rev-parse", "--short=7", "HEAD") + standardOutput = commit + } + + val dirty = ByteArrayOutputStream() + exec { + commandLine("git", "status", "--short") + standardOutput = dirty + } + + filesMatching("net/runelite/client/runelite.properties") { + filter { it.replace("\${project.version}", project.version.toString()) } + filter { it.replace("\${git.commit.id.abbrev}", commit.toString().trim()) } + filter { it.replace("\${git.dirty}", dirty.toString().isNotBlank().toString()) } + } +} + +tasks.compileJava { + options.isFork = true +} + +tasks.jar { + exclude("**/.clang-format") +} + +pmd { + toolVersion = "7.2.0" + ruleSetFiles("./pmd-ruleset.xml") + isConsoleOutput = true + incrementalAnalysis = true + isIgnoreFailures = false + threads = Runtime.getRuntime().availableProcessors() +} +tasks.pmdMain { + exclude("**/RuntimeTypeAdapterFactory.java") + exclude("**/net/runelite/client/party/Party.java") +} +tasks.pmdTest { enabled = false } + +tasks.checkstyleMain { + exclude("net/runelite/client/util/RuntimeTypeAdapterFactory.java") // vendored + exclude("net/runelite/client/party/Party.java") // generated by protobuf +} + +tasks.withType { + systemProperty("glslang.path", providers.gradleProperty("glslangPath").getOrElse("")) +} + +tasks.javadoc { + title = "RuneLite Client ${project.version} API" +} diff --git a/runelite-client/pom.xml b/runelite-client/pom.xml deleted file mode 100644 index c99ff9e6048..00000000000 --- a/runelite-client/pom.xml +++ /dev/null @@ -1,517 +0,0 @@ - - - - 4.0.0 - - - net.runelite - runelite-parent - 1.12.10-SNAPSHOT - - - client - RuneLite Client - - - true - true - nogit - false - false - - - - - org.slf4j - slf4j-api - - - ch.qos.logback - logback-classic - - - net.sf.jopt-simple - jopt-simple - 5.0.1 - - - com.google.guava - guava - - - - com.google.code.findbugs - jsr305 - - - com.google.errorprone - error_prone_annotations - - - com.google.j2objc - j2objc-annotations - - - org.codehaus.mojo - animal-sniffer-annotations - - - - - com.google.inject - guice - no_aop - - - com.google.code.gson - gson - - - net.runelite - flatlaf - ${flatlaf.version} - - - org.projectlombok - lombok - provided - - - org.apache.commons - commons-text - 1.2 - - - net.java.dev.jna - jna - 5.9.0 - - - net.java.dev.jna - jna-platform - 5.9.0 - - - com.google.code.findbugs - jsr305 - - - org.jetbrains - annotations - 23.0.0 - provided - - - com.google.protobuf - protobuf-javalite - 3.21.12 - - - net.runelite - rlawt - 1.7 - - - - - org.lwjgl - lwjgl - - - org.lwjgl - lwjgl - natives-linux - runtime - - - org.lwjgl - lwjgl - natives-linux-arm64 - runtime - - - org.lwjgl - lwjgl - natives-macos - runtime - - - org.lwjgl - lwjgl - natives-macos-arm64 - runtime - - - org.lwjgl - lwjgl - natives-windows-x86 - runtime - - - org.lwjgl - lwjgl - natives-windows - runtime - - - org.lwjgl - lwjgl - natives-windows-arm64 - runtime - - - - - org.lwjgl - lwjgl-opengl - - - org.lwjgl - lwjgl-opengl - natives-linux - runtime - - - org.lwjgl - lwjgl-opengl - natives-linux-arm64 - runtime - - - org.lwjgl - lwjgl-opengl - natives-macos - runtime - - - org.lwjgl - lwjgl-opengl - natives-macos-arm64 - runtime - - - org.lwjgl - lwjgl-opengl - natives-windows-x86 - runtime - - - org.lwjgl - lwjgl-opengl - natives-windows - runtime - - - org.lwjgl - lwjgl-opengl - natives-windows-arm64 - runtime - - - - - org.lwjgl - lwjgl-opencl - - - - net.runelite - runelite-api - ${project.version} - - - net.runelite - jshell - ${project.version} - true - - - net.runelite - injected-client - ${project.version} - runtime - - - net.runelite.arn - http-api - 1.2.21 - - - net.runelite - discord - 1.4 - - - net.runelite - orange-extensions - 1.1 - provided - - - - junit - junit - 4.12 - test - - - org.hamcrest - hamcrest-library - 1.3 - test - - - org.mockito - mockito-core - 3.1.0 - test - - - com.google.inject.extensions - guice-testlib - test - - - com.google.inject.extensions - guice-grapher - test - - - com.squareup.okhttp3 - mockwebserver - 3.14.9 - test - - - - - - - org.lwjgl - lwjgl-bom - 3.3.2 - pom - import - - - - - - - - src/main/resources - - logback.xml - - true - - - src/main/resources - - logback.xml - - false - - - - - org.apache.maven.plugins - maven-jar-plugin - - - **/.clang-format - - - - - - test-jar - - - - - - org.apache.maven.plugins - maven-resources-plugin - 3.0.2 - - - ttf - png - gif - wav - - - - - org.apache.maven.plugins - maven-shade-plugin - 3.3.0 - - - package - - shade - - - ${shade.skip} - true - shaded - false - - - *:* - - META-INF/versions/**/module-info.class - - - - - - - - net.runelite.client.RuneLite - true - - - - - - - - - org.apache.maven.plugins - maven-jarsigner-plugin - 1.4 - - - sign - - sign - - - - - ${jarsigner.skip} - ${jarsigner.keystore} - ${jarsigner.alias} - ${jarsigner.storepass} - ${jarsigner.keypass} - - - - net.runelite - runelite-maven-plugin - ${project.version} - - - assemble - - assemble - - - src/main/scripts - ${project.build.outputDirectory}/runelite - ../runelite-api/src/main/interfaces/interfaces.toml - - - - build-index - - build-index - - - ${project.build.outputDirectory}/runelite - ${project.build.outputDirectory}/runelite/index - - - - - - org.apache.maven.plugins - maven-checkstyle-plugin - - - - ${project.build.sourceDirectory} - ${project.basedir}/src/main/java11 - - - - - org.apache.maven.plugins - maven-pmd-plugin - - true - true - - ${basedir}/pmd-ruleset.xml - - false - true - - **/RuntimeTypeAdapterFactory.java - net/runelite/client/party/Party.java - - - - - - check - - - - - - pl.project13.maven - git-commit-id-plugin - 2.2.6 - - - query-git-info - - revision - - - false - false - - true - - - git.commit.id.abbrev - git.dirty - - - - - - - - diff --git a/runelite-client/settings.gradle.kts b/runelite-client/settings.gradle.kts new file mode 100644 index 00000000000..9c78c29f5f8 --- /dev/null +++ b/runelite-client/settings.gradle.kts @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2024, LlemonDuck + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +rootProject.name = "client" +apply(from = "../common.settings.gradle.kts") + +includeBuild("../runelite-gradle-plugin") +includeBuild("../runelite-jshell") diff --git a/runelite-gradle-plugin/build.gradle.kts b/runelite-gradle-plugin/build.gradle.kts new file mode 100644 index 00000000000..4f3e5384c2f --- /dev/null +++ b/runelite-gradle-plugin/build.gradle.kts @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2024, LlemonDuck + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +plugins { + `java-gradle-plugin` + pmd +} + +dependencies { + implementation("net.runelite:cache:${project.version}") + + implementation(libs.guava) + implementation(libs.tomlj) + implementation(libs.javapoet) +} + +gradlePlugin { + plugins { + create("rl-assemble") { + id = "net.runelite.runelite-gradle-plugin.assemble" + implementationClass = "net.runelite.gradle.assemble.AssemblePlugin" + } + create("rl-component") { + id = "net.runelite.runelite-gradle-plugin.component" + implementationClass = "net.runelite.gradle.component.ComponentPlugin" + } + create("rl-index") { + id = "net.runelite.runelite-gradle-plugin.index" + implementationClass = "net.runelite.gradle.index.IndexPlugin" + } + create("rl-jarsign") { + id = "net.runelite.runelite-gradle-plugin.jarsign" + implementationClass = "net.runelite.gradle.jarsign.JarsignPlugin" + } + } +} + +pmd { + toolVersion = "7.2.0" + ruleSetFiles("./pmd-ruleset.xml") + isConsoleOutput = true + incrementalAnalysis = true + isIgnoreFailures = false + threads = Runtime.getRuntime().availableProcessors() +} diff --git a/runelite-maven-plugin/pmd-ruleset.xml b/runelite-gradle-plugin/pmd-ruleset.xml similarity index 100% rename from runelite-maven-plugin/pmd-ruleset.xml rename to runelite-gradle-plugin/pmd-ruleset.xml diff --git a/runelite-gradle-plugin/settings.gradle.kts b/runelite-gradle-plugin/settings.gradle.kts new file mode 100644 index 00000000000..54a1fadafbf --- /dev/null +++ b/runelite-gradle-plugin/settings.gradle.kts @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024, LlemonDuck + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +rootProject.name = "runelite-gradle-plugin" +apply(from = "../common.settings.gradle.kts") + +includeBuild("../cache") diff --git a/runelite-gradle-plugin/src/main/java/net/runelite/gradle/assemble/AssemblePlugin.java b/runelite-gradle-plugin/src/main/java/net/runelite/gradle/assemble/AssemblePlugin.java new file mode 100644 index 00000000000..dfe30339389 --- /dev/null +++ b/runelite-gradle-plugin/src/main/java/net/runelite/gradle/assemble/AssemblePlugin.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024, LlemonDuck + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.runelite.gradle.assemble; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.tasks.TaskProvider; + +public abstract class AssemblePlugin implements Plugin +{ + + @Override + public void apply(Project project) + { + TaskProvider assembleRs2asm = project.getTasks() + .register("assembleRs2asm", AssembleTask.class, (task) -> task.setGroup("build")); + + project.getTasks() + .getByName("processResources") + .dependsOn(assembleRs2asm); + } + +} diff --git a/runelite-maven-plugin/src/main/java/net/runelite/mvn/AssembleMojo.java b/runelite-gradle-plugin/src/main/java/net/runelite/gradle/assemble/AssembleTask.java similarity index 68% rename from runelite-maven-plugin/src/main/java/net/runelite/mvn/AssembleMojo.java rename to runelite-gradle-plugin/src/main/java/net/runelite/gradle/assemble/AssembleTask.java index 22acaf4806b..9e35283fe95 100644 --- a/runelite-maven-plugin/src/main/java/net/runelite/mvn/AssembleMojo.java +++ b/runelite-gradle-plugin/src/main/java/net/runelite/gradle/assemble/AssembleTask.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Adam + * Copyright (c) 2024, LlemonDuck * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -22,11 +22,13 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.runelite.mvn; + +package net.runelite.gradle.assemble; import com.google.common.io.Files; import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; import java.util.HashMap; import java.util.Locale; @@ -36,38 +38,45 @@ import net.runelite.cache.definitions.savers.ScriptSaver; import net.runelite.cache.script.RuneLiteInstructions; import net.runelite.cache.script.assembler.Assembler; -import org.apache.maven.plugin.AbstractMojo; -import org.apache.maven.plugin.MojoExecutionException; -import org.apache.maven.plugin.MojoFailureException; -import org.apache.maven.plugin.logging.Log; -import org.apache.maven.plugins.annotations.LifecyclePhase; -import org.apache.maven.plugins.annotations.Mojo; -import org.apache.maven.plugins.annotations.Parameter; +import org.gradle.api.DefaultTask; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.logging.Logger; +import org.gradle.api.tasks.CacheableTask; +import org.gradle.api.tasks.InputDirectory; +import org.gradle.api.tasks.InputFile; +import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.PathSensitive; +import org.gradle.api.tasks.PathSensitivity; +import org.gradle.api.tasks.TaskAction; import org.tomlj.Toml; import org.tomlj.TomlParseError; import org.tomlj.TomlParseResult; import org.tomlj.TomlTable; -@Mojo( - name = "assemble", - defaultPhase = LifecyclePhase.GENERATE_RESOURCES -) -public class AssembleMojo extends AbstractMojo +@CacheableTask +public abstract class AssembleTask extends DefaultTask { - @Parameter(required = true) - private File scriptDirectory; + @InputDirectory + @PathSensitive(PathSensitivity.RELATIVE) + public abstract DirectoryProperty getScriptDirectory(); - @Parameter(required = true) - private File outputDirectory; + @OutputDirectory + public abstract DirectoryProperty getOutputDirectory(); - @Parameter(required = true) - private File componentsFile; + @InputFile + @PathSensitive(PathSensitivity.RELATIVE) + public abstract RegularFileProperty getComponentsFile(); - private final Log log = getLog(); + private final Logger log = getLogger(); - @Override - public void execute() throws MojoExecutionException, MojoFailureException + @TaskAction + public void assembleRs2Asm() throws IOException { + File scriptDirectory = getScriptDirectory().getAsFile().get(); + File outputDirectory = getOutputDirectory().getAsFile().get(); + File componentsFile = getComponentsFile().getAsFile().get(); + RuneLiteInstructions instructions = new RuneLiteInstructions(); instructions.init(); @@ -80,7 +89,7 @@ public void execute() throws MojoExecutionException, MojoFailureException for (File scriptFile : scriptDirectory.listFiles((dir, name) -> name.endsWith(".rs2asm"))) { - log.debug("Assembling " + scriptFile); + log.debug("Assembling {}", scriptFile); try (FileInputStream fin = new FileInputStream(scriptFile)) { @@ -99,21 +108,17 @@ public void execute() throws MojoExecutionException, MojoFailureException } else if (script.getId() < 10000) // Scripts >=10000 are RuneLite scripts, so they shouldn't have a .hash { - throw new MojoExecutionException("Unable to find hash file for " + scriptFile); + throw new FileNotFoundException("Unable to find hash file for " + scriptFile); } ++count; } - catch (IOException ex) - { - throw new MojoFailureException("unable to open file", ex); - } } - log.info("Assembled " + count + " scripts"); + log.lifecycle("Assembled {} scripts", count); } - private Map buildComponentSymbols(File file) throws MojoExecutionException + private Map buildComponentSymbols(File file) { TomlParseResult result; try @@ -122,7 +127,7 @@ private Map buildComponentSymbols(File file) throws MojoExecutio } catch (IOException e) { - throw new MojoExecutionException("unable to read component file " + file.getName(), e); + throw new RuntimeException("unable to read component file " + file.getName(), e); } if (result.hasErrors()) @@ -131,7 +136,7 @@ private Map buildComponentSymbols(File file) throws MojoExecutio { log.error(err.toString()); } - throw new MojoExecutionException("unable to parse component file " + file.getName()); + throw new RuntimeException("unable to parse component file " + file.getName()); } Map symbols = new HashMap<>(); @@ -142,13 +147,13 @@ private Map buildComponentSymbols(File file) throws MojoExecutio if (!tbl.contains("id")) { - throw new MojoExecutionException("interface " + interfaceName + " has no id"); + throw new RuntimeException("interface " + interfaceName + " has no id"); } int interfaceId = (int) (long) tbl.getLong("id"); if (interfaceId < 0 || interfaceId > 0xffff) { - throw new MojoExecutionException("interface id out of range for " + interfaceName); + throw new RuntimeException("interface id out of range for " + interfaceName); } for (var entry2 : tbl.entrySet()) @@ -162,7 +167,7 @@ private Map buildComponentSymbols(File file) throws MojoExecutio int id = (int) (long) entry2.getValue(); if (id < 0 || id > 0xffff) { - throw new MojoExecutionException("component id out of range for " + componentName); + throw new RuntimeException("component id out of range for " + componentName); } var fullName = interfaceName.toLowerCase(Locale.ENGLISH) + ":" + componentName.toLowerCase(Locale.ENGLISH); diff --git a/runelite-gradle-plugin/src/main/java/net/runelite/gradle/component/ComponentPlugin.java b/runelite-gradle-plugin/src/main/java/net/runelite/gradle/component/ComponentPlugin.java new file mode 100644 index 00000000000..a30a6a4c260 --- /dev/null +++ b/runelite-gradle-plugin/src/main/java/net/runelite/gradle/component/ComponentPlugin.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2024, LlemonDuck + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.gradle.component; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.SourceSetContainer; +import org.gradle.api.tasks.TaskProvider; + +public class ComponentPlugin implements Plugin +{ + + @Override + public void apply(Project project) + { + TaskProvider packComponents = project.getTasks() + .register("packComponents", ComponentTask.class, (task) -> task.setGroup("build")); + + project.getTasks() + .getByName("compileJava") + .dependsOn(packComponents); + + project.getExtensions() + .getByType(SourceSetContainer.class) + .getByName(SourceSet.MAIN_SOURCE_SET_NAME) + .getJava() + .srcDir(packComponents.map(ComponentTask::getOutputDirectory)); + } + +} diff --git a/runelite-maven-plugin/src/main/java/net/runelite/mvn/ComponentMojo.java b/runelite-gradle-plugin/src/main/java/net/runelite/gradle/component/ComponentTask.java similarity index 62% rename from runelite-maven-plugin/src/main/java/net/runelite/mvn/ComponentMojo.java rename to runelite-gradle-plugin/src/main/java/net/runelite/gradle/component/ComponentTask.java index 619fa273b65..e7458a962f5 100644 --- a/runelite-maven-plugin/src/main/java/net/runelite/mvn/ComponentMojo.java +++ b/runelite-gradle-plugin/src/main/java/net/runelite/gradle/component/ComponentTask.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Adam + * Copyright (c) 2024, LlemonDuck * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -22,7 +22,7 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.runelite.mvn; +package net.runelite.gradle.component; import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.JavaFile; @@ -33,74 +33,61 @@ import java.util.Locale; import java.util.Set; import javax.lang.model.element.Modifier; -import org.apache.maven.plugin.AbstractMojo; -import org.apache.maven.plugin.MojoExecutionException; -import org.apache.maven.plugin.MojoFailureException; -import org.apache.maven.plugin.logging.Log; -import org.apache.maven.plugins.annotations.LifecyclePhase; -import org.apache.maven.plugins.annotations.Mojo; -import org.apache.maven.plugins.annotations.Parameter; -import org.apache.maven.project.MavenProject; +import org.gradle.api.DefaultTask; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.logging.Logger; +import org.gradle.api.tasks.CacheableTask; +import org.gradle.api.tasks.InputFile; +import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.PathSensitive; +import org.gradle.api.tasks.PathSensitivity; +import org.gradle.api.tasks.TaskAction; import org.tomlj.Toml; import org.tomlj.TomlParseError; import org.tomlj.TomlParseResult; import org.tomlj.TomlTable; -@Mojo( - name = "pack-components", - defaultPhase = LifecyclePhase.GENERATE_SOURCES -) -public class ComponentMojo extends AbstractMojo +@CacheableTask +public abstract class ComponentTask extends DefaultTask { - @Parameter(defaultValue = "${project}") - private MavenProject project; - @Parameter(required = true) - private File inputDirectory; + @InputFile + @PathSensitive(PathSensitivity.RELATIVE) + public abstract RegularFileProperty getInputFile(); - @Parameter(required = true) - private File outputDirectory; + @OutputDirectory + public abstract DirectoryProperty getOutputDirectory(); - private final Log log = getLog(); + private final Logger log = getLogger(); private final Set seenInterfaces = new HashSet<>(); private final Set seenComponents = new HashSet<>(); - @Override - public void execute() throws MojoExecutionException, MojoFailureException + @TaskAction + public void packComponents() throws IOException { + File inputFile = getInputFile().getAsFile().get(); + File outputDirectory = getOutputDirectory().getAsFile().get(); + TypeSpec.Builder interfaceType = TypeSpec.classBuilder("InterfaceID") .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addAnnotation(Deprecated.class) - .addJavadoc("@deprecated Use {@link net.runelite.api.gameval.InterfaceID} instead"); + .addJavadoc("@deprecated Use {@link net.runelite.api.gameval.InterfaceID} instead");; TypeSpec.Builder componentType = TypeSpec.classBuilder("ComponentID") .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addAnnotation(Deprecated.class) .addJavadoc("@deprecated Use nested classes of {@link net.runelite.api.gameval.InterfaceID} instead"); - for (File file : inputDirectory.listFiles((dir, name) -> name.endsWith(".toml"))) - { - executeOne(file, interfaceType, componentType); - } - - writeClass("net.runelite.api.widgets", interfaceType.build()); - writeClass("net.runelite.api.widgets", componentType.build()); + executeOne(inputFile, interfaceType, componentType); - // https://stackoverflow.com/a/30760908 - project.addCompileSourceRoot(outputDirectory.getAbsolutePath()); + writeClass(outputDirectory, "net.runelite.api.widgets", interfaceType.build()); + writeClass(outputDirectory, "net.runelite.api.widgets", componentType.build()); } - private void executeOne(File file, TypeSpec.Builder interfaceType, TypeSpec.Builder componentType) throws MojoExecutionException + private void executeOne(File file, TypeSpec.Builder interfaceType, TypeSpec.Builder componentType) throws IOException { - TomlParseResult result; - try - { - result = Toml.parse(file.toPath()); - } - catch (IOException e) - { - throw new MojoExecutionException("unable to read component file " + file.getName(), e); - } + TomlParseResult result = Toml.parse(file.toPath()); if (result.hasErrors()) { @@ -108,7 +95,7 @@ private void executeOne(File file, TypeSpec.Builder interfaceType, TypeSpec.Buil { log.error(err.toString()); } - throw new MojoExecutionException("unable to parse component file " + file.getName()); + throw new RuntimeException("unable to parse component file " + file.getName()); } for (var entry : result.entrySet()) @@ -118,18 +105,18 @@ private void executeOne(File file, TypeSpec.Builder interfaceType, TypeSpec.Buil if (!tbl.contains("id")) { - throw new MojoExecutionException("interface " + interfaceName + " has no id"); + throw new RuntimeException("interface " + interfaceName + " has no id"); } int interfaceId = (int) (long) tbl.getLong("id"); if (interfaceId < 0 || interfaceId > 0xffff) { - throw new MojoExecutionException("interface id out of range for " + interfaceName); + throw new RuntimeException("interface id out of range for " + interfaceName); } if (seenInterfaces.contains(interfaceId)) { - throw new MojoExecutionException("duplicate interface id " + interfaceId); + throw new RuntimeException("duplicate interface id " + interfaceId); } seenInterfaces.add(interfaceId); @@ -146,7 +133,7 @@ private void executeOne(File file, TypeSpec.Builder interfaceType, TypeSpec.Buil int id = (int) (long) entry2.getValue(); if (id < 0 || id > 0xffff) { - throw new MojoExecutionException("component id out of range for " + componentName); + throw new RuntimeException("component id out of range for " + componentName); } var fullName = interfaceName.toUpperCase(Locale.ENGLISH) + "_" + componentName.toUpperCase(Locale.ENGLISH); @@ -155,7 +142,7 @@ private void executeOne(File file, TypeSpec.Builder interfaceType, TypeSpec.Buil if (seenComponents.contains(componentId)) { - throw new MojoExecutionException("duplicate component id " + comment); + throw new RuntimeException("duplicate component id " + comment); } seenComponents.add(componentId); @@ -176,18 +163,10 @@ private static void addField(TypeSpec.Builder type, String name, int value, Stri type.addField(field.build()); } - private void writeClass(String pkg, TypeSpec type) throws MojoExecutionException + private void writeClass(File outputDirectory, String pkg, TypeSpec type) throws IOException { - JavaFile javaFile = JavaFile.builder(pkg, type) - .build(); - - try - { - javaFile.writeTo(outputDirectory); - } - catch (IOException e) - { - throw new MojoExecutionException("unable to write java class", e); - } + JavaFile.builder(pkg, type) + .build() + .writeToFile(outputDirectory); } } diff --git a/runelite-gradle-plugin/src/main/java/net/runelite/gradle/index/IndexPlugin.java b/runelite-gradle-plugin/src/main/java/net/runelite/gradle/index/IndexPlugin.java new file mode 100644 index 00000000000..8e9de3e6f99 --- /dev/null +++ b/runelite-gradle-plugin/src/main/java/net/runelite/gradle/index/IndexPlugin.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024, LlemonDuck + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.gradle.index; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.tasks.TaskProvider; + +public abstract class IndexPlugin implements Plugin +{ + + @Override + public void apply(Project project) + { + TaskProvider buildRs2asmIndex = project.getTasks() + .register("buildRs2asmIndex", IndexTask.class, (task) -> task.setGroup("build")); + + project.getTasks() + .getByName("processResources") + .dependsOn(buildRs2asmIndex); + } +} diff --git a/runelite-maven-plugin/src/main/java/net/runelite/mvn/IndexMojo.java b/runelite-gradle-plugin/src/main/java/net/runelite/gradle/index/IndexTask.java similarity index 69% rename from runelite-maven-plugin/src/main/java/net/runelite/mvn/IndexMojo.java rename to runelite-gradle-plugin/src/main/java/net/runelite/gradle/index/IndexTask.java index 63cbb6dbcc0..494b0ec9d9e 100644 --- a/runelite-maven-plugin/src/main/java/net/runelite/mvn/IndexMojo.java +++ b/runelite-gradle-plugin/src/main/java/net/runelite/gradle/index/IndexTask.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Adam + * Copyright (c) 2024, LlemonDuck * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -22,35 +22,40 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.runelite.mvn; +package net.runelite.gradle.index; import java.io.DataOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import static java.lang.Integer.parseInt; -import org.apache.maven.plugin.AbstractMojo; -import org.apache.maven.plugin.MojoExecutionException; -import org.apache.maven.plugin.MojoFailureException; -import org.apache.maven.plugins.annotations.LifecyclePhase; -import org.apache.maven.plugins.annotations.Mojo; -import org.apache.maven.plugins.annotations.Parameter; +import org.gradle.api.DefaultTask; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.tasks.CacheableTask; +import org.gradle.api.tasks.InputDirectory; +import org.gradle.api.tasks.OutputFile; +import org.gradle.api.tasks.PathSensitive; +import org.gradle.api.tasks.PathSensitivity; +import org.gradle.api.tasks.TaskAction; -@Mojo( - name = "build-index", - defaultPhase = LifecyclePhase.GENERATE_RESOURCES -) -public class IndexMojo extends AbstractMojo +@CacheableTask +public abstract class IndexTask extends DefaultTask { - @Parameter(required = true) - private File archiveOverlayDirectory; - @Parameter(required = true) - private File indexFile; + @InputDirectory + @PathSensitive(PathSensitivity.RELATIVE) + public abstract DirectoryProperty getArchiveOverlayDirectory(); - @Override - public void execute() throws MojoExecutionException, MojoFailureException + @OutputFile + public abstract RegularFileProperty getIndexFile(); + + @TaskAction + public void buildRs2Index() throws IOException { + File archiveOverlayDirectory = getArchiveOverlayDirectory().getAsFile().get(); + File indexFile = getIndexFile().getAsFile().get(); + try (DataOutputStream fout = new DataOutputStream(new FileOutputStream(indexFile))) { for (File indexFolder : archiveOverlayDirectory.listFiles()) @@ -77,10 +82,6 @@ public void execute() throws MojoExecutionException, MojoFailureException fout.writeInt(-1); } - catch (IOException ex) - { - throw new MojoExecutionException("error build index file", ex); - } } } diff --git a/runelite-gradle-plugin/src/main/java/net/runelite/gradle/jarsign/JarsignExtension.java b/runelite-gradle-plugin/src/main/java/net/runelite/gradle/jarsign/JarsignExtension.java new file mode 100644 index 00000000000..ded3cbc88d3 --- /dev/null +++ b/runelite-gradle-plugin/src/main/java/net/runelite/gradle/jarsign/JarsignExtension.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024, LlemonDuck + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.gradle.jarsign; + +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.provider.Property; + +public interface JarsignExtension +{ + + RegularFileProperty getKeystore(); + + Property getStorePass(); + + Property getKeyPass(); + + Property getAlias(); + +} diff --git a/runelite-gradle-plugin/src/main/java/net/runelite/gradle/jarsign/JarsignPlugin.java b/runelite-gradle-plugin/src/main/java/net/runelite/gradle/jarsign/JarsignPlugin.java new file mode 100644 index 00000000000..f252fdf90e3 --- /dev/null +++ b/runelite-gradle-plugin/src/main/java/net/runelite/gradle/jarsign/JarsignPlugin.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2024, LlemonDuck + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.gradle.jarsign; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.plugins.BasePlugin; +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.TaskProvider; +import org.gradle.jvm.tasks.Jar; + +public abstract class JarsignPlugin implements Plugin +{ + + @Override + public void apply(Project project) + { + JarsignExtension ext = project.getExtensions() + .create(JarsignExtension.class, "jarsign", JarsignExtension.class); + ext.getKeystore().convention(toRegularFileProvider(project, propProvider(project, "jarsignerKeystore"))); + ext.getStorePass().convention(propProvider(project, "jarsignerStorepass")); + ext.getKeyPass().convention(propProvider(project, "jarsignerKeypass")); + ext.getAlias().convention(propProvider(project, "jarsignerAlias")); + + project.getTasks() + .withType(Jar.class, jarTask -> registerSignTask(project, jarTask, ext)); + } + + private void registerSignTask(Project project, Jar jarTask, JarsignExtension ext) + { + TaskProvider signTask = project.getTasks().register( + jarTask.getName() + "Sign", JarsignTask.class, (jarsignTask) -> + { + jarsignTask.setGroup(BasePlugin.BUILD_GROUP); + + jarsignTask.getBuildTask().convention(jarTask); + jarsignTask.getArchive().convention(jarTask.getArchiveFile()); + jarsignTask.getKeystore().convention(ext.getKeystore()); + jarsignTask.getStorePass().convention(ext.getStorePass()); + jarsignTask.getKeyPass().convention(ext.getKeyPass()); + jarsignTask.getAlias().convention(ext.getAlias()); + } + ); + jarTask.finalizedBy(signTask); + } + + private static Provider propProvider(Project project, String key) + { + return project.provider(() -> + (String) project.findProperty(key)); + } + + private static RegularFileProperty toRegularFileProvider(Project project, Provider propProvider) + { + return project.getObjects() + .fileProperty() + .fileProvider(propProvider.map(project::file)); + } +} diff --git a/runelite-gradle-plugin/src/main/java/net/runelite/gradle/jarsign/JarsignTask.java b/runelite-gradle-plugin/src/main/java/net/runelite/gradle/jarsign/JarsignTask.java new file mode 100644 index 00000000000..8acb891db71 --- /dev/null +++ b/runelite-gradle-plugin/src/main/java/net/runelite/gradle/jarsign/JarsignTask.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2024, LlemonDuck + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.gradle.jarsign; + +import org.gradle.api.DefaultTask; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.provider.Property; +import org.gradle.api.tasks.CacheableTask; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputFile; +import org.gradle.api.tasks.PathSensitive; +import org.gradle.api.tasks.PathSensitivity; +import org.gradle.api.tasks.TaskAction; +import org.gradle.jvm.tasks.Jar; + +@CacheableTask +public abstract class JarsignTask extends DefaultTask +{ + + public JarsignTask() + { + dependsOn(getBuildTask()); + onlyIf( + "target archive must be specified", + _t -> getArchive().getAsFile().get().exists() + ); + + onlyIf( + "keystore properties are set", + _t -> + getKeystore().isPresent() && + getStorePass().isPresent() && + getKeyPass().isPresent() && + getAlias().isPresent() + ); + } + + @Input + public abstract Property getBuildTask(); + + @InputFile + @PathSensitive(PathSensitivity.RELATIVE) + public abstract RegularFileProperty getArchive(); + + @InputFile + @PathSensitive(PathSensitivity.RELATIVE) + public abstract RegularFileProperty getKeystore(); + + @Input + public abstract Property getStorePass(); + + @Input + public abstract Property getKeyPass(); + + @Input + public abstract Property getAlias(); + + @TaskAction + public void signArtifact() + { + getProject().exec(exec -> + exec.commandLine( + "jarsigner", + "-keystore", getKeystore().getAsFile().get().getAbsolutePath(), + "-storepass", getStorePass().get(), + "-keypass", getKeyPass().get(), + getArchive().getAsFile().get().getAbsolutePath(), + getAlias().get() + )); + } + +} diff --git a/runelite-jshell/build.gradle.kts b/runelite-jshell/build.gradle.kts new file mode 100644 index 00000000000..4dd7c7c06db --- /dev/null +++ b/runelite-jshell/build.gradle.kts @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2024, LlemonDuck + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +plugins { + java + `maven-publish` + alias(libs.plugins.lombok) +} + +lombok.version = libs.versions.lombok.get() + +java { + withJavadocJar() + withSourcesJar() +} + +dependencies { + implementation(libs.slf4j.api) + implementation(libs.guava) + implementation(variantOf(libs.guice.core) { classifier("no_aop") }) + implementation(libs.findbugs) + implementation(libs.fife.rsyntaxtextarea) + implementation(libs.fife.autocomplete) +} + +publishing { + publications { + create("jshell") { + from(components["java"]) + } + } +} diff --git a/runelite-jshell/pom.xml b/runelite-jshell/pom.xml deleted file mode 100644 index 171e91aa0f9..00000000000 --- a/runelite-jshell/pom.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - - 4.0.0 - - - net.runelite - runelite-parent - 1.12.10-SNAPSHOT - - - jshell - RuneLite JShell - - - - org.slf4j - slf4j-api - - - com.google.inject - guice - no_aop - - - org.projectlombok - lombok - provided - - - com.google.code.findbugs - jsr305 - - - com.fifesoft - rsyntaxtextarea - 3.1.2 - - - com.fifesoft - autocomplete - 3.1.1 - - - - net.runelite - flatlaf-extras - ${flatlaf.version} - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - 11 - - - - - diff --git a/runelite-jshell/settings.gradle.kts b/runelite-jshell/settings.gradle.kts new file mode 100644 index 00000000000..f0b83742986 --- /dev/null +++ b/runelite-jshell/settings.gradle.kts @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024, LlemonDuck + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +rootProject.name = "jshell" +apply(from = "../common.settings.gradle.kts") diff --git a/runelite-maven-plugin/pom.xml b/runelite-maven-plugin/pom.xml deleted file mode 100644 index d197cd61c67..00000000000 --- a/runelite-maven-plugin/pom.xml +++ /dev/null @@ -1,112 +0,0 @@ - - - - 4.0.0 - - - net.runelite - runelite-parent - 1.12.10-SNAPSHOT - - - runelite-maven-plugin - RuneLite Maven Plugin - maven-plugin - - - true - - - - - net.runelite - cache - ${project.version} - - - - org.apache.maven - maven-plugin-api - 3.0.5 - - - org.apache.maven.plugin-tools - maven-plugin-annotations - 3.4 - - - org.apache.maven - maven-core - 3.0.5 - - - - org.tomlj - tomlj - 1.1.0 - - - com.squareup - javapoet - 1.13.0 - - - - - - - org.apache.maven.plugins - maven-pmd-plugin - - true - true - - ${project.basedir}/pmd-ruleset.xml - - false - true - - - - - check - - - - - - org.apache.maven.plugins - maven-plugin-plugin - - - default-descriptor - process-classes - - - - - - diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 00000000000..549ce19f0fa --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024, LlemonDuck + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +rootProject.name = "runelite" +includeBuild("cache") +includeBuild("runelite-api") +includeBuild("runelite-client") +includeBuild("runelite-gradle-plugin") +includeBuild("runelite-jshell") From 76941a5f3f24b296c27904ee8349cc7dfbe1178d Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 21 Dec 2025 17:00:45 -0500 Subject: [PATCH 05/13] Revert "gpu: use glMapBufferRange for interface pbo" This reverts commit e74e32af6ff350debd92e2f307e934fa94a70f7d. --- .../main/java/net/runelite/client/plugins/gpu/GpuPlugin.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java index 2bdc05d852c..5a08fc3a64f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java @@ -1359,14 +1359,13 @@ private void rebuild(WorldView wv) private void prepareInterfaceTexture(int canvasWidth, int canvasHeight) { - long bufferSize = canvasWidth * canvasHeight * 4L; if (canvasWidth != lastCanvasWidth || canvasHeight != lastCanvasHeight) { lastCanvasWidth = canvasWidth; lastCanvasHeight = canvasHeight; glBindBuffer(GL_PIXEL_UNPACK_BUFFER, interfacePbo); - glBufferData(GL_PIXEL_UNPACK_BUFFER, bufferSize, GL_STREAM_DRAW); + glBufferData(GL_PIXEL_UNPACK_BUFFER, canvasWidth * canvasHeight * 4L, GL_STREAM_DRAW); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); glBindTexture(GL_TEXTURE_2D, interfaceTexture); @@ -1380,7 +1379,7 @@ private void prepareInterfaceTexture(int canvasWidth, int canvasHeight) final int height = bufferProvider.getHeight(); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, interfacePbo); - ByteBuffer interfaceBuf = glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, bufferSize, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT); + ByteBuffer interfaceBuf = glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY); if (interfaceBuf != null) { interfaceBuf From 107ec1143b6c4f1b5f9ae77f764ed5e434ee0647 Mon Sep 17 00:00:00 2001 From: RuneLite updater Date: Mon, 22 Dec 2025 00:36:15 +0000 Subject: [PATCH 06/13] Release 1.12.10 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index e94a2c8e523..eee7af96488 100644 --- a/gradle.properties +++ b/gradle.properties @@ -28,6 +28,6 @@ org.gradle.parallel=true org.gradle.caching=true project.build.group=net.runelite -project.build.version=1.12.10-SNAPSHOT +project.build.version=1.12.10 glslang.path= From 39e83352ded4cd6de66a73bbbe76662506c39331 Mon Sep 17 00:00:00 2001 From: RuneLite updater Date: Mon, 22 Dec 2025 00:36:16 +0000 Subject: [PATCH 07/13] Bump for 1.12.11-SNAPSHOT [ci skip] --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index eee7af96488..9fd3b7f87cc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -28,6 +28,6 @@ org.gradle.parallel=true org.gradle.caching=true project.build.group=net.runelite -project.build.version=1.12.10 +project.build.version=1.12.11-SNAPSHOT glslang.path= From 64a3076dde2e128bb7a66c62f4f3b2599fdef0ae Mon Sep 17 00:00:00 2001 From: Rhea Date: Sun, 21 Dec 2025 20:56:58 -0500 Subject: [PATCH 08/13] client, jshell: dependency scope fixes --- runelite-client/build.gradle.kts | 28 +++++++++++++++------------- runelite-jshell/build.gradle.kts | 11 +++++++++-- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/runelite-client/build.gradle.kts b/runelite-client/build.gradle.kts index a7af7becf10..38d9d3d8c14 100644 --- a/runelite-client/build.gradle.kts +++ b/runelite-client/build.gradle.kts @@ -46,35 +46,37 @@ java { dependencies { api("net.runelite:runelite-api:${project.version}") - implementation("net.runelite:jshell:${project.version}") + api("net.runelite:jshell:${project.version}") runtimeOnly("net.runelite:injected-client:${project.version}") api(libs.rl.http.api) - implementation(libs.rl.discord) - implementation(libs.rl.awt) + api(libs.rl.discord) + api(libs.rl.awt) compileOnly(libs.rl.orange) api(libs.slf4j.api) - implementation(libs.logback.classic) { + api(libs.logback.classic) { exclude("org.slf4j", "slf4j-api") } - implementation(libs.jopt) + api(libs.jopt) api(libs.guava) { exclude("com.google.code.findbugs", "jsr305") exclude("com.google.errorprone", "error_prone_annotations") exclude("com.google.j2objc", "j2objc-annotations") exclude("org.codehaus.mojo", "animal-sniffer-annotations") } - api(variantOf(libs.guice.core) { classifier("no_aop") }) + api(variantOf(libs.guice.core) { classifier("no_aop") }) { + exclude("com.google.guava", "guava") + } api(libs.gson) - implementation(libs.flatlaf.core) - implementation(libs.flatlaf.extras) - implementation(libs.commons.text) - implementation(libs.jna.core) - implementation(libs.jna.platform) - implementation(libs.findbugs) + api(libs.flatlaf.core) + api(libs.flatlaf.extras) + api(libs.commons.text) + api(libs.jna.core) + api(libs.jna.platform) + api(libs.findbugs) compileOnly(libs.jetbrains.annotations) - implementation(libs.protobuf) + api(libs.protobuf) api(libs.lwjgl.core) api(libs.lwjgl.opengl) api(libs.lwjgl.opencl) diff --git a/runelite-jshell/build.gradle.kts b/runelite-jshell/build.gradle.kts index 4dd7c7c06db..9cec5126597 100644 --- a/runelite-jshell/build.gradle.kts +++ b/runelite-jshell/build.gradle.kts @@ -38,8 +38,15 @@ java { dependencies { implementation(libs.slf4j.api) - implementation(libs.guava) - implementation(variantOf(libs.guice.core) { classifier("no_aop") }) + implementation(libs.guava) { + exclude("com.google.code.findbugs", "jsr305") + exclude("com.google.errorprone", "error_prone_annotations") + exclude("com.google.j2objc", "j2objc-annotations") + exclude("org.codehaus.mojo", "animal-sniffer-annotations") + } + implementation(variantOf(libs.guice.core) { classifier("no_aop") }) { + exclude("com.google.guava", "guava") + } implementation(libs.findbugs) implementation(libs.fife.rsyntaxtextarea) implementation(libs.fife.autocomplete) From 68fcb412e2f7829fd218c55041036feaf8502618 Mon Sep 17 00:00:00 2001 From: Rhea Date: Sun, 21 Dec 2025 21:35:32 -0500 Subject: [PATCH 09/13] remove more maven references --- runelite-client/src/main/java/net/runelite/client/RuneLite.java | 2 +- runelite-gradle-plugin/pmd-ruleset.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/RuneLite.java b/runelite-client/src/main/java/net/runelite/client/RuneLite.java index 3d9cf223cb0..0c415c425d3 100644 --- a/runelite-client/src/main/java/net/runelite/client/RuneLite.java +++ b/runelite-client/src/main/java/net/runelite/client/RuneLite.java @@ -212,7 +212,7 @@ public static void main(String[] args) throws Exception log.error("Uncaught exception:", throwable); if (throwable instanceof AbstractMethodError) { - log.error("Classes are out of date; Build with maven again."); + log.error("Classes are out of date; Build with Gradle again."); } }); diff --git a/runelite-gradle-plugin/pmd-ruleset.xml b/runelite-gradle-plugin/pmd-ruleset.xml index d269a39b8f4..d060dd1b2ac 100644 --- a/runelite-gradle-plugin/pmd-ruleset.xml +++ b/runelite-gradle-plugin/pmd-ruleset.xml @@ -29,7 +29,7 @@ xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> - RuneLite Maven Plugin PMD ruleset + RuneLite PMD ruleset From 9ef28b2e20ecce04225e27caf55c44b29ef5d463 Mon Sep 17 00:00:00 2001 From: Rhea Date: Sun, 21 Dec 2025 21:06:46 -0500 Subject: [PATCH 10/13] readme: update for gradle --- .github/CONTRIBUTING.md | 133 +--------------------------------------- README.md | 4 +- 2 files changed, 3 insertions(+), 134 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 50ec0d67061..0f841e208b7 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,134 +1,3 @@ # Contributing to RuneLite -We'd love for you to contribute to our source code and to make RuneLite even better than it is -today! - -Check out the [Developer Guide](https://github.com/runelite/runelite/wiki/Developer-Guide) for setup instructions, and general tips and tricks. - -If you want to make or contribute to a plugin hub plugin, this is *not* the correct guide; instead read the plugin hub development guide, which is linked from the developer guide. - -Here are the guidelines we'd like you to follow: - - - [Question or Problem?](#question) - - [Issues and Bugs](#issue) - - [Submission Guidelines](#submit) - - [Coding Format](#format) - -## Got a Question or Problem? - -If you have questions about how to contribute to RuneLite, please join our [Discord](https://runelite.net/discord) server. - -## Found an Issue? - -If you find a bug in the source code or a mistake in the documentation, you can help us by -submitting an issue to our [GitHub Repository](https://github.com/runelite/runelite). Even better you can submit a Pull Request -with a fix. - -**Please see the [Submission Guidelines](#submit) below.** - -## Submission Guidelines - -### Submitting an Issue -Before you submit your issue search the archive, maybe your question was already answered. - -If your issue appears to be a bug and hasn't been reported, open a new issue. Help us to maximize -the effort we can spend fixing issues and adding new features, by not reporting duplicate issues. -Providing the following information will increase the chances of your issue being dealt with -quickly: - -* **Overview of the Issue** - if an error is being thrown a non-minified stack trace helps -* **Java Version and Operating System** - is this a problem with a specific setup? -* **Reproduce the Error** - provide details, if possible, on how to reproduce the error -* **Related Issues** - has a similar issue been reported before? -* **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be causing the problem (line of code or commit) - -### Submitting a Pull Request -Before you submit your pull request consider the following guidelines: - -* Search [GitHub](https://github.com/runelite/runelite/pulls) for an open or closed Pull Request - that relates to your submission. You don't want to duplicate effort. -* If adding a feature or enhancement, we recommend you first [start a discussion for - it](https://github.com/runelite/runelite/discussions) before submitting a Pull Request. -* [Fork](https://help.github.com/articles/fork-a-repo/) this repo. -* [Clone](https://help.github.com/articles/cloning-a-repository/) your copy. - ```shell - git clone https://github.com/YOUR_USERNAME/runelite.git - cd runelite/ - ``` -* After cloning, set a new remote [upstream](https://help.github.com/articles/configuring-a-remote-for-a-fork/) (this helps to keep your fork up to date) - - ```shell - git remote add upstream https://github.com/runelite/runelite.git - ``` - -* Make your changes in a new git branch: - - ```shell - git checkout -b my-fix-branch master - ``` - -* Create your patch and run appropriate tests. -* Follow our [Coding Format](#format). -* Commit your changes using a descriptive commit message that uses the imperative, present tense: "change" not "changed" nor "changes". - - ```shell - git commit -a - ``` - Note: the optional commit `-a` command line option will automatically "add" and "rm" edited files. - -* Push your branch to GitHub: - - ```shell - git push origin my-fix-branch - ``` - -In GitHub, send a pull request to `runelite:master`. -If we suggest changes, then: - -* Make the required updates. -* Re-run RuneLite and make sure any and all tests are still passing. -* Commit your changes to your branch (e.g. `my-fix-branch`). -* Push the changes to your GitHub repository (this will update your Pull Request). - -If the PR gets too outdated we may ask you to rebase and force push to update the PR: - -```shell -git fetch upstream -git rebase upstream/master -git push origin my-fix-branch -f -``` - -That's it! Thank you for your contribution! - -#### After your pull request is merged - -After your pull request is merged, you can safely delete your branch and pull the changes -from the main (upstream) repository: - -* Delete the remote branch on GitHub either through the GitHub web UI or your local shell as follows: - - ```shell - git push origin --delete my-fix-branch - ``` - -* Check out the master branch: - - ```shell - git checkout master -f - ``` - -* Delete the local branch: - - ```shell - git branch -D my-fix-branch - ``` - -* Update your master with the latest upstream version: - - ```shell - git pull --ff upstream master - ``` - -## Coding Format - -To ensure consistency throughout the source code, review our [code conventions](https://github.com/runelite/runelite/wiki/Code-Conventions). +Please view our [Developer Guide](https://github.com/runelite/runelite/wiki/Developer-Guide) on the RuneLite Wiki. diff --git a/README.md b/README.md index 4b7d5b27451..2a0827e72de 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ If you have any questions, please join our IRC channel on [irc.rizon.net #runeli ## Usage -Open the project in your IDE as a Maven project, build the root module and then run the RuneLite class in runelite-client. +Open the project in your IDE as a Gradle project, and then run the RuneLite class in runelite-client. For more information visit the [RuneLite Wiki](https://github.com/runelite/runelite/wiki). ### License @@ -22,4 +22,4 @@ RuneLite is licensed under the BSD 2-clause license. See the license header in t ## Contribute and Develop -We've set up a separate document for our [contribution guidelines](https://github.com/runelite/runelite/blob/master/.github/CONTRIBUTING.md). +Please view our [Developer Guide](https://github.com/runelite/runelite/wiki/Developer-Guide) on the RuneLite Wiki. From 2e632bb61087dd91283e6de9124ae5f99dcfc0d6 Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 22 Dec 2025 11:37:41 -0500 Subject: [PATCH 11/13] use implementation scope for jshell and flatlaf-extras These used to be optional, and are excluded from the release --- runelite-client/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runelite-client/build.gradle.kts b/runelite-client/build.gradle.kts index 38d9d3d8c14..b14d995c93c 100644 --- a/runelite-client/build.gradle.kts +++ b/runelite-client/build.gradle.kts @@ -46,7 +46,7 @@ java { dependencies { api("net.runelite:runelite-api:${project.version}") - api("net.runelite:jshell:${project.version}") + implementation("net.runelite:jshell:${project.version}") runtimeOnly("net.runelite:injected-client:${project.version}") api(libs.rl.http.api) @@ -70,7 +70,7 @@ dependencies { } api(libs.gson) api(libs.flatlaf.core) - api(libs.flatlaf.extras) + implementation(libs.flatlaf.extras) api(libs.commons.text) api(libs.jna.core) api(libs.jna.platform) From ada6ec174952811cef64891dd2383aa3c2109f13 Mon Sep 17 00:00:00 2001 From: chsami Date: Tue, 23 Dec 2025 05:44:32 +0100 Subject: [PATCH 12/13] upgraded microbot to gradle and latest runelite --- .github/workflows/manual_nightly.yml | 30 +- .github/workflows/nightly.yml | 33 +- .github/workflows/release.yml | 35 +- gradle.properties | 7 +- gradle/verification-metadata.xml | 1 + gradle/wrapper/gradle-wrapper.jar | Bin 60756 -> 43453 bytes gradle/wrapper/gradle-wrapper.properties | 5 +- gradlew | 41 +- gradlew.bat | 35 +- libs.versions.toml | 4 + runelite-client/build.gradle.kts | 40 +- runelite-client/pom.xml | 2 +- .../LocationStartNotificationOverlay.java | 145 --- .../VoxPlugins/schedulable/example/README.md | 499 --------- .../example/SchedulableExampleConfig.java | 889 ---------------- .../example/SchedulableExampleOverlay.java | 139 --- .../example/SchedulableExamplePlugin.java | 978 ------------------ ...bleExamplePrePostScheduleRequirements.java | 640 ------------ ...chedulableExamplePrePostScheduleTasks.java | 186 ---- .../example/SchedulableExampleScript.java | 495 --------- .../example/enums/SpellbookOption.java | 49 - .../example/enums/UnifiedLocation.java | 190 ---- .../microbot/example/ExampleScript.java | 2 + .../registry/RequirementRegistry.java | 2 - 24 files changed, 158 insertions(+), 4289 deletions(-) delete mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/microbot/VoxPlugins/schedulable/example/LocationStartNotificationOverlay.java delete mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/microbot/VoxPlugins/schedulable/example/README.md delete mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/microbot/VoxPlugins/schedulable/example/SchedulableExampleConfig.java delete mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/microbot/VoxPlugins/schedulable/example/SchedulableExampleOverlay.java delete mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/microbot/VoxPlugins/schedulable/example/SchedulableExamplePlugin.java delete mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/microbot/VoxPlugins/schedulable/example/SchedulableExamplePrePostScheduleRequirements.java delete mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/microbot/VoxPlugins/schedulable/example/SchedulableExamplePrePostScheduleTasks.java delete mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/microbot/VoxPlugins/schedulable/example/SchedulableExampleScript.java delete mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/microbot/VoxPlugins/schedulable/example/enums/SpellbookOption.java delete mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/microbot/VoxPlugins/schedulable/example/enums/UnifiedLocation.java diff --git a/.github/workflows/manual_nightly.yml b/.github/workflows/manual_nightly.yml index 13c6f689137..259562404e5 100644 --- a/.github/workflows/manual_nightly.yml +++ b/.github/workflows/manual_nightly.yml @@ -31,24 +31,30 @@ jobs: distribution: temurin java-version: 11 + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + gradle-version: wrapper + - name: Build Shaded JAR run: | COMMIT_SHA=$(git rev-parse --short HEAD) - mvn clean package -Dmicrobot.commit.sha=$COMMIT_SHA + ./gradlew :runelite-client:shadowJar -Pmicrobot.commit.sha=$COMMIT_SHA - - name: Read microbot.version from POM + - name: Read microbot.version from Gradle id: version run: | - V=$(mvn -q -f runelite-client/pom.xml help:evaluate \ - -Dexpression=microbot.version -DforceStdout) - # fallback to project.version if the property isn't defined - if [ -z "$V" ]; then - V=$(mvn -q -f runelite-client/pom.xml help:evaluate \ - -Dexpression=project.version -DforceStdout) - fi + V=$(./gradlew -q properties --console=plain | sed -n 's/^microbot.version: //p') + if [ -z "$V" ]; then V=$(grep '^microbot.version=' gradle.properties | cut -d= -f2); fi echo "version=$V" >> "$GITHUB_OUTPUT" echo "Using version: $V" + - name: Prepare artifact name + run: | + SRC=$(echo runelite-client/build/libs/client-*-shaded.jar) + DEST=runelite-client/build/libs/microbot-${{ steps.version.outputs.version }}.jar + cp "$SRC" "$DEST" + - name: Create Release uses: "marvinpinto/action-automatic-releases@latest" with: @@ -57,7 +63,7 @@ jobs: prerelease: false title: "Nightly Build" files: | - /home/runner/work/Microbot/Microbot/runelite-client/target/microbot-*.jar + /home/runner/work/Microbot/Microbot/runelite-client/build/libs/microbot-*.jar - name: Upload Jar to Hetzner @@ -66,7 +72,7 @@ jobs: host: ${{ secrets.PROD_HOST }} username: root key: ${{ secrets.PROD_SSH_KEY }} - source: runelite-client/target/microbot-*.jar + source: runelite-client/build/libs/microbot-*.jar target: /var/www/files/releases/microbot/nightly/ strip_components: 2 @@ -86,4 +92,4 @@ jobs: EOL - name: Deploy to Nexus - run: mvn deploy:deploy-file -DgroupId=com.microbot -DartifactId=client -Dversion=${{ steps.version.outputs.version }} -Dpackaging=jar -Dfile=runelite-client/target/microbot-${{ steps.version.outputs.version }}.jar -DrepositoryId=microbot-nightly -Durl=https://nexus.microbot.cloud/repository/microbot-nightly/ \ No newline at end of file + run: mvn deploy:deploy-file -DgroupId=com.microbot -DartifactId=client -Dversion=${{ steps.version.outputs.version }} -Dpackaging=jar -Dfile=runelite-client/build/libs/microbot-${{ steps.version.outputs.version }}.jar -DrepositoryId=microbot-nightly -Durl=https://nexus.microbot.cloud/repository/microbot-nightly/ diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index bdf4da37307..393c5100817 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -4,9 +4,6 @@ on: schedule: - cron: "0 0 * * *" -env: - version: ${microbot.version} - jobs: build: runs-on: ubuntu-latest # You can choose a different runner if needed @@ -31,24 +28,30 @@ jobs: distribution: temurin java-version: 11 + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + gradle-version: wrapper + - name: Build Shaded JAR run: | COMMIT_SHA=$(git rev-parse --short HEAD) - mvn clean package -Dmicrobot.commit.sha=$COMMIT_SHA + ./gradlew :runelite-client:shadowJar -Pmicrobot.commit.sha=$COMMIT_SHA - - name: Read microbot.version from POM + - name: Read microbot.version from Gradle id: version run: | - V=$(mvn -q -f runelite-client/pom.xml help:evaluate \ - -Dexpression=microbot.version -DforceStdout) - # fallback to project.version if the property isn't defined - if [ -z "$V" ]; then - V=$(mvn -q -f runelite-client/pom.xml help:evaluate \ - -Dexpression=project.version -DforceStdout) - fi + V=$(./gradlew -q properties --console=plain | sed -n 's/^microbot.version: //p') + if [ -z "$V" ]; then V=$(grep '^microbot.version=' gradle.properties | cut -d= -f2); fi echo "version=$V" >> "$GITHUB_OUTPUT" echo "Using version: $V" + - name: Prepare artifact name + run: | + SRC=$(echo runelite-client/build/libs/client-*-shaded.jar) + DEST=runelite-client/build/libs/microbot-${{ steps.version.outputs.version }}.jar + cp "$SRC" "$DEST" + - name: Create Release uses: "marvinpinto/action-automatic-releases@latest" with: @@ -57,7 +60,7 @@ jobs: prerelease: false title: "Nightly Build" files: | - /home/runner/work/Microbot/Microbot/runelite-client/target/microbot-*.jar + /home/runner/work/Microbot/Microbot/runelite-client/build/libs/microbot-*.jar - name: Upload Jar to Hetzner uses: appleboy/scp-action@v0.1.7 @@ -65,7 +68,7 @@ jobs: host: ${{ secrets.PROD_HOST }} username: root key: ${{ secrets.PROD_SSH_KEY }} - source: runelite-client/target/microbot-*.jar + source: runelite-client/build/libs/microbot-*.jar target: /var/www/files/releases/microbot/nightly/ strip_components: 2 @@ -85,4 +88,4 @@ jobs: EOL - name: Deploy to Nexus - run: mvn deploy:deploy-file -DgroupId=com.microbot -DartifactId=client -Dversion=${{ steps.version.outputs.version }} -Dpackaging=jar -Dfile=runelite-client/target/microbot-${{ steps.version.outputs.version }}.jar -DrepositoryId=microbot-nightly -Durl=https://nexus.microbot.cloud/repository/microbot-nightly/ + run: mvn deploy:deploy-file -DgroupId=com.microbot -DartifactId=client -Dversion=${{ steps.version.outputs.version }} -Dpackaging=jar -Dfile=runelite-client/build/libs/microbot-${{ steps.version.outputs.version }}.jar -DrepositoryId=microbot-nightly -Durl=https://nexus.microbot.cloud/repository/microbot-nightly/ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8505dfbf74e..55dcb485f2e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,9 +4,6 @@ on: branches: - main -env: - version: ${microbot.version} - jobs: build: runs-on: ubuntu-latest # You can choose a different runner if needed @@ -23,25 +20,31 @@ jobs: distribution: temurin java-version: 11 + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + gradle-version: wrapper + - name: Build Shaded JAR run: | COMMIT_SHA=$(git rev-parse --short HEAD) - mvn clean package -Dmicrobot.commit.sha=$COMMIT_SHA + ./gradlew :runelite-client:shadowJar -Pmicrobot.commit.sha=$COMMIT_SHA - - name: Read microbot.version from POM + - name: Read microbot.version from Gradle id: version run: | - V=$(mvn -q -f runelite-client/pom.xml help:evaluate \ - -Dexpression=microbot.version -DforceStdout) - # fallback to project.version if the property isn't defined - if [ -z "$V" ]; then - V=$(mvn -q -f runelite-client/pom.xml help:evaluate \ - -Dexpression=project.version -DforceStdout) - fi + V=$(./gradlew -q properties --console=plain | sed -n 's/^microbot.version: //p') + if [ -z "$V" ]; then V=$(grep '^microbot.version=' gradle.properties | cut -d= -f2); fi echo "version=$V" >> "$GITHUB_OUTPUT" echo "Using version: $V" + - name: Prepare artifact name + run: | + SRC=$(echo runelite-client/build/libs/client-*-shaded.jar) + DEST=runelite-client/build/libs/microbot-${{ steps.version.outputs.version }}.jar + cp "$SRC" "$DEST" + - name: Create Release uses: "marvinpinto/action-automatic-releases@latest" with: @@ -50,7 +53,7 @@ jobs: prerelease: false title: "Release ${{ steps.version.outputs.version }}" files: | - /home/runner/work/Microbot/Microbot/runelite-client/target/microbot-*.jar + /home/runner/work/Microbot/Microbot/runelite-client/build/libs/microbot-*.jar - name: Upload Jar to Hetzner uses: appleboy/scp-action@v0.1.7 @@ -58,7 +61,7 @@ jobs: host: ${{ secrets.PROD_HOST }} username: root key: ${{ secrets.PROD_SSH_KEY }} - source: runelite-client/target/microbot-*.jar + source: runelite-client/build/libs/microbot-*.jar target: /var/www/files/releases/microbot/stable/ strip_components: 2 @@ -78,7 +81,7 @@ jobs: EOL - name: Deploy to Nexus - run: mvn deploy:deploy-file -DgroupId=com.microbot -DartifactId=client -Dversion=${{ steps.version.outputs.version }} -Dpackaging=jar -Dfile=runelite-client/target/microbot-${{ steps.version.outputs.version }}.jar -DrepositoryId=microbot-release -Durl=https://nexus.microbot.cloud/repository/microbot-release/ + run: mvn deploy:deploy-file -DgroupId=com.microbot -DartifactId=client -Dversion=${{ steps.version.outputs.version }} -Dpackaging=jar -Dfile=runelite-client/build/libs/microbot-${{ steps.version.outputs.version }}.jar -DrepositoryId=microbot-release -Durl=https://nexus.microbot.cloud/repository/microbot-release/ - name: Get Auth Token id: auth @@ -96,4 +99,4 @@ jobs: -H "Authorization: Bearer ${{ steps.auth.outputs.token }}" \ -H "Content-Type: application/json" \ -d "{\"version\":\"${{ steps.version.outputs.version }}\"}" \ - https://microbot.cloud/api/version/client \ No newline at end of file + https://microbot.cloud/api/version/client diff --git a/gradle.properties b/gradle.properties index 9fd3b7f87cc..1d82f998209 100644 --- a/gradle.properties +++ b/gradle.properties @@ -28,6 +28,11 @@ org.gradle.parallel=true org.gradle.caching=true project.build.group=net.runelite -project.build.version=1.12.11-SNAPSHOT +project.build.version=1.12.10 glslang.path= +microbot.version=2.0.61 +microbot.commit.sha=nogit +microbot.repo.url=http://138.201.81.246:8081/repository/microbot-snapshot/ +microbot.repo.username= +microbot.repo.password= diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 0b929186cf3..f7621c52004 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -6,6 +6,7 @@ + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 249e5832f090a2944b7473328c07c9755baa3196..e6441136f3d4ba8a0da8d277868979cfbc8ad796 100644 GIT binary patch literal 43453 zcma&N1CXTcmMvW9vTb(Rwr$&4wr$(C?dmSu>@vG-+vuvg^_??!{yS%8zW-#zn-LkA z5&1^$^{lnmUON?}LBF8_K|(?T0Ra(xUH{($5eN!MR#ZihR#HxkUPe+_R8Cn`RRs(P z_^*#_XlXmGv7!4;*Y%p4nw?{bNp@UZHv1?Um8r6)Fei3p@ClJn0ECfg1hkeuUU@Or zDaPa;U3fE=3L}DooL;8f;P0ipPt0Z~9P0)lbStMS)ag54=uL9ia-Lm3nh|@(Y?B`; zx_#arJIpXH!U{fbCbI^17}6Ri*H<>OLR%c|^mh8+)*h~K8Z!9)DPf zR2h?lbDZQ`p9P;&DQ4F0sur@TMa!Y}S8irn(%d-gi0*WxxCSk*A?3lGh=gcYN?FGl z7D=Js!i~0=u3rox^eO3i@$0=n{K1lPNU zwmfjRVmLOCRfe=seV&P*1Iq=^i`502keY8Uy-WNPwVNNtJFx?IwAyRPZo2Wo1+S(xF37LJZ~%i)kpFQ3Fw=mXfd@>%+)RpYQLnr}B~~zoof(JVm^^&f zxKV^+3D3$A1G;qh4gPVjhrC8e(VYUHv#dy^)(RoUFM?o%W-EHxufuWf(l*@-l+7vt z=l`qmR56K~F|v<^Pd*p~1_y^P0P^aPC##d8+HqX4IR1gu+7w#~TBFphJxF)T$2WEa zxa?H&6=Qe7d(#tha?_1uQys2KtHQ{)Qco)qwGjrdNL7thd^G5i8Os)CHqc>iOidS} z%nFEDdm=GXBw=yXe1W-ShHHFb?Cc70+$W~z_+}nAoHFYI1MV1wZegw*0y^tC*s%3h zhD3tN8b=Gv&rj}!SUM6|ajSPp*58KR7MPpI{oAJCtY~JECm)*m_x>AZEu>DFgUcby z1Qaw8lU4jZpQ_$;*7RME+gq1KySGG#Wql>aL~k9tLrSO()LWn*q&YxHEuzmwd1?aAtI zBJ>P=&$=l1efe1CDU;`Fd+_;&wI07?V0aAIgc(!{a z0Jg6Y=inXc3^n!U0Atk`iCFIQooHqcWhO(qrieUOW8X(x?(RD}iYDLMjSwffH2~tB z)oDgNBLB^AJBM1M^c5HdRx6fBfka`(LD-qrlh5jqH~);#nw|iyp)()xVYak3;Ybik z0j`(+69aK*B>)e_p%=wu8XC&9e{AO4c~O1U`5X9}?0mrd*m$_EUek{R?DNSh(=br# z#Q61gBzEpmy`$pA*6!87 zSDD+=@fTY7<4A?GLqpA?Pb2z$pbCc4B4zL{BeZ?F-8`s$?>*lXXtn*NC61>|*w7J* z$?!iB{6R-0=KFmyp1nnEmLsA-H0a6l+1uaH^g%c(p{iT&YFrbQ$&PRb8Up#X3@Zsk zD^^&LK~111%cqlP%!_gFNa^dTYT?rhkGl}5=fL{a`UViaXWI$k-UcHJwmaH1s=S$4 z%4)PdWJX;hh5UoK?6aWoyLxX&NhNRqKam7tcOkLh{%j3K^4Mgx1@i|Pi&}<^5>hs5 zm8?uOS>%)NzT(%PjVPGa?X%`N2TQCKbeH2l;cTnHiHppPSJ<7y-yEIiC!P*ikl&!B z%+?>VttCOQM@ShFguHVjxX^?mHX^hSaO_;pnyh^v9EumqSZTi+#f&_Vaija0Q-e*| z7ulQj6Fs*bbmsWp{`auM04gGwsYYdNNZcg|ph0OgD>7O}Asn7^Z=eI>`$2*v78;sj-}oMoEj&@)9+ycEOo92xSyY344^ z11Hb8^kdOvbf^GNAK++bYioknrpdN>+u8R?JxG=!2Kd9r=YWCOJYXYuM0cOq^FhEd zBg2puKy__7VT3-r*dG4c62Wgxi52EMCQ`bKgf*#*ou(D4-ZN$+mg&7$u!! z-^+Z%;-3IDwqZ|K=ah85OLwkO zKxNBh+4QHh)u9D?MFtpbl)us}9+V!D%w9jfAMYEb>%$A;u)rrI zuBudh;5PN}_6J_}l55P3l_)&RMlH{m!)ai-i$g)&*M`eN$XQMw{v^r@-125^RRCF0 z^2>|DxhQw(mtNEI2Kj(;KblC7x=JlK$@78`O~>V!`|1Lm-^JR$-5pUANAnb(5}B}JGjBsliK4& zk6y(;$e&h)lh2)L=bvZKbvh@>vLlreBdH8No2>$#%_Wp1U0N7Ank!6$dFSi#xzh|( zRi{Uw%-4W!{IXZ)fWx@XX6;&(m_F%c6~X8hx=BN1&q}*( zoaNjWabE{oUPb!Bt$eyd#$5j9rItB-h*5JiNi(v^e|XKAj*8(k<5-2$&ZBR5fF|JA z9&m4fbzNQnAU}r8ab>fFV%J0z5awe#UZ|bz?Ur)U9bCIKWEzi2%A+5CLqh?}K4JHi z4vtM;+uPsVz{Lfr;78W78gC;z*yTch~4YkLr&m-7%-xc ztw6Mh2d>_iO*$Rd8(-Cr1_V8EO1f*^@wRoSozS) zy1UoC@pruAaC8Z_7~_w4Q6n*&B0AjOmMWa;sIav&gu z|J5&|{=a@vR!~k-OjKEgPFCzcJ>#A1uL&7xTDn;{XBdeM}V=l3B8fE1--DHjSaxoSjNKEM9|U9#m2<3>n{Iuo`r3UZp;>GkT2YBNAh|b z^jTq-hJp(ebZh#Lk8hVBP%qXwv-@vbvoREX$TqRGTgEi$%_F9tZES@z8Bx}$#5eeG zk^UsLBH{bc2VBW)*EdS({yw=?qmevwi?BL6*=12k9zM5gJv1>y#ML4!)iiPzVaH9% zgSImetD@dam~e>{LvVh!phhzpW+iFvWpGT#CVE5TQ40n%F|p(sP5mXxna+Ev7PDwA zamaV4m*^~*xV+&p;W749xhb_X=$|LD;FHuB&JL5?*Y2-oIT(wYY2;73<^#46S~Gx| z^cez%V7x$81}UWqS13Gz80379Rj;6~WdiXWOSsdmzY39L;Hg3MH43o*y8ibNBBH`(av4|u;YPq%{R;IuYow<+GEsf@R?=@tT@!}?#>zIIn0CoyV!hq3mw zHj>OOjfJM3F{RG#6ujzo?y32m^tgSXf@v=J$ELdJ+=5j|=F-~hP$G&}tDZsZE?5rX ztGj`!S>)CFmdkccxM9eGIcGnS2AfK#gXwj%esuIBNJQP1WV~b~+D7PJTmWGTSDrR` zEAu4B8l>NPuhsk5a`rReSya2nfV1EK01+G!x8aBdTs3Io$u5!6n6KX%uv@DxAp3F@{4UYg4SWJtQ-W~0MDb|j-$lwVn znAm*Pl!?Ps&3wO=R115RWKb*JKoexo*)uhhHBncEDMSVa_PyA>k{Zm2(wMQ(5NM3# z)jkza|GoWEQo4^s*wE(gHz?Xsg4`}HUAcs42cM1-qq_=+=!Gk^y710j=66(cSWqUe zklbm8+zB_syQv5A2rj!Vbw8;|$@C!vfNmNV!yJIWDQ>{+2x zKjuFX`~~HKG~^6h5FntRpnnHt=D&rq0>IJ9#F0eM)Y-)GpRjiN7gkA8wvnG#K=q{q z9dBn8_~wm4J<3J_vl|9H{7q6u2A!cW{bp#r*-f{gOV^e=8S{nc1DxMHFwuM$;aVI^ zz6A*}m8N-&x8;aunp1w7_vtB*pa+OYBw=TMc6QK=mbA-|Cf* zvyh8D4LRJImooUaSb7t*fVfih<97Gf@VE0|z>NcBwBQze);Rh!k3K_sfunToZY;f2 z^HmC4KjHRVg+eKYj;PRN^|E0>Gj_zagfRbrki68I^#~6-HaHg3BUW%+clM1xQEdPYt_g<2K+z!$>*$9nQ>; zf9Bei{?zY^-e{q_*|W#2rJG`2fy@{%6u0i_VEWTq$*(ZN37|8lFFFt)nCG({r!q#9 z5VK_kkSJ3?zOH)OezMT{!YkCuSSn!K#-Rhl$uUM(bq*jY? zi1xbMVthJ`E>d>(f3)~fozjg^@eheMF6<)I`oeJYx4*+M&%c9VArn(OM-wp%M<-`x z7sLP1&3^%Nld9Dhm@$3f2}87!quhI@nwd@3~fZl_3LYW-B?Ia>ui`ELg z&Qfe!7m6ze=mZ`Ia9$z|ARSw|IdMpooY4YiPN8K z4B(ts3p%2i(Td=tgEHX z0UQ_>URBtG+-?0E;E7Ld^dyZ;jjw0}XZ(}-QzC6+NN=40oDb2^v!L1g9xRvE#@IBR zO!b-2N7wVfLV;mhEaXQ9XAU+>=XVA6f&T4Z-@AX!leJ8obP^P^wP0aICND?~w&NykJ#54x3_@r7IDMdRNy4Hh;h*!u(Ol(#0bJdwEo$5437-UBjQ+j=Ic>Q2z` zJNDf0yO6@mr6y1#n3)s(W|$iE_i8r@Gd@!DWDqZ7J&~gAm1#~maIGJ1sls^gxL9LLG_NhU!pTGty!TbhzQnu)I*S^54U6Yu%ZeCg`R>Q zhBv$n5j0v%O_j{QYWG!R9W?5_b&67KB$t}&e2LdMvd(PxN6Ir!H4>PNlerpBL>Zvyy!yw z-SOo8caEpDt(}|gKPBd$qND5#a5nju^O>V&;f890?yEOfkSG^HQVmEbM3Ugzu+UtH zC(INPDdraBN?P%kE;*Ae%Wto&sgw(crfZ#Qy(<4nk;S|hD3j{IQRI6Yq|f^basLY; z-HB&Je%Gg}Jt@={_C{L$!RM;$$|iD6vu#3w?v?*;&()uB|I-XqEKqZPS!reW9JkLewLb!70T7n`i!gNtb1%vN- zySZj{8-1>6E%H&=V}LM#xmt`J3XQoaD|@XygXjdZ1+P77-=;=eYpoEQ01B@L*a(uW zrZeZz?HJsw_4g0vhUgkg@VF8<-X$B8pOqCuWAl28uB|@r`19DTUQQsb^pfqB6QtiT z*`_UZ`fT}vtUY#%sq2{rchyfu*pCg;uec2$-$N_xgjZcoumE5vSI{+s@iLWoz^Mf; zuI8kDP{!XY6OP~q5}%1&L}CtfH^N<3o4L@J@zg1-mt{9L`s^z$Vgb|mr{@WiwAqKg zp#t-lhrU>F8o0s1q_9y`gQNf~Vb!F%70f}$>i7o4ho$`uciNf=xgJ>&!gSt0g;M>*x4-`U)ysFW&Vs^Vk6m%?iuWU+o&m(2Jm26Y(3%TL; zA7T)BP{WS!&xmxNw%J=$MPfn(9*^*TV;$JwRy8Zl*yUZi8jWYF>==j~&S|Xinsb%c z2?B+kpet*muEW7@AzjBA^wAJBY8i|#C{WtO_or&Nj2{=6JTTX05}|H>N2B|Wf!*3_ z7hW*j6p3TvpghEc6-wufFiY!%-GvOx*bZrhZu+7?iSrZL5q9}igiF^*R3%DE4aCHZ zqu>xS8LkW+Auv%z-<1Xs92u23R$nk@Pk}MU5!gT|c7vGlEA%G^2th&Q*zfg%-D^=f z&J_}jskj|Q;73NP4<4k*Y%pXPU2Thoqr+5uH1yEYM|VtBPW6lXaetokD0u z9qVek6Q&wk)tFbQ8(^HGf3Wp16gKmr>G;#G(HRBx?F`9AIRboK+;OfHaLJ(P>IP0w zyTbTkx_THEOs%Q&aPrxbZrJlio+hCC_HK<4%f3ZoSAyG7Dn`=X=&h@m*|UYO-4Hq0 z-Bq&+Ie!S##4A6OGoC~>ZW`Y5J)*ouaFl_e9GA*VSL!O_@xGiBw!AF}1{tB)z(w%c zS1Hmrb9OC8>0a_$BzeiN?rkPLc9%&;1CZW*4}CDDNr2gcl_3z+WC15&H1Zc2{o~i) z)LLW=WQ{?ricmC`G1GfJ0Yp4Dy~Ba;j6ZV4r{8xRs`13{dD!xXmr^Aga|C=iSmor% z8hi|pTXH)5Yf&v~exp3o+sY4B^^b*eYkkCYl*T{*=-0HniSA_1F53eCb{x~1k3*`W zr~};p1A`k{1DV9=UPnLDgz{aJH=-LQo<5%+Em!DNN252xwIf*wF_zS^!(XSm(9eoj z=*dXG&n0>)_)N5oc6v!>-bd(2ragD8O=M|wGW z!xJQS<)u70m&6OmrF0WSsr@I%T*c#Qo#Ha4d3COcX+9}hM5!7JIGF>7<~C(Ear^Sn zm^ZFkV6~Ula6+8S?oOROOA6$C&q&dp`>oR-2Ym3(HT@O7Sd5c~+kjrmM)YmgPH*tL zX+znN>`tv;5eOfX?h{AuX^LK~V#gPCu=)Tigtq9&?7Xh$qN|%A$?V*v=&-2F$zTUv z`C#WyIrChS5|Kgm_GeudCFf;)!WH7FI60j^0o#65o6`w*S7R@)88n$1nrgU(oU0M9 zx+EuMkC>(4j1;m6NoGqEkpJYJ?vc|B zOlwT3t&UgL!pX_P*6g36`ZXQ; z9~Cv}ANFnJGp(;ZhS(@FT;3e)0)Kp;h^x;$*xZn*k0U6-&FwI=uOGaODdrsp-!K$Ac32^c{+FhI-HkYd5v=`PGsg%6I`4d9Jy)uW0y%) zm&j^9WBAp*P8#kGJUhB!L?a%h$hJgQrx!6KCB_TRo%9{t0J7KW8!o1B!NC)VGLM5! zpZy5Jc{`r{1e(jd%jsG7k%I+m#CGS*BPA65ZVW~fLYw0dA-H_}O zrkGFL&P1PG9p2(%QiEWm6x;U-U&I#;Em$nx-_I^wtgw3xUPVVu zqSuKnx&dIT-XT+T10p;yjo1Y)z(x1fb8Dzfn8e yu?e%!_ptzGB|8GrCfu%p?(_ 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

NnX)EH+Nua)3Y(c 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 /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,22 +131,29 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -193,11 +198,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ @@ -205,6 +214,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index 107acd32c4e..25da30dbdee 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,13 +41,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -56,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/libs.versions.toml b/libs.versions.toml index 23d656b53e9..55ee7859c4b 100644 --- a/libs.versions.toml +++ b/libs.versions.toml @@ -25,6 +25,7 @@ [versions] rs = "235" cache = "165" +asm = "9.0" flatlaf = "3.2.5-rl4" guice = "4.1.0" @@ -78,6 +79,9 @@ protobuf = "com.google.protobuf:protobuf-javalite:3.21.12" slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j" } tomlj = "org.tomlj:tomlj:1.1.0" +asm-core = { module = "org.ow2.asm:asm", version.ref = "asm" } +asm-util = { module = "org.ow2.asm:asm-util", version.ref = "asm" } +asm-commons = { module = "org.ow2.asm:asm-commons", version.ref = "asm" } diff --git a/runelite-client/build.gradle.kts b/runelite-client/build.gradle.kts index b14d995c93c..f1c6f03a770 100644 --- a/runelite-client/build.gradle.kts +++ b/runelite-client/build.gradle.kts @@ -24,6 +24,17 @@ */ import java.io.ByteArrayOutputStream +import java.util.Properties + +fun loadRootProperty(name: String): String? { + val propsFile = file("../gradle.properties") + if (!propsFile.isFile) { + return null + } + val props = Properties() + propsFile.inputStream().use { props.load(it) } + return props.getProperty(name) +} plugins { java @@ -40,7 +51,6 @@ plugins { lombok.version = libs.versions.lombok.get() java { - withJavadocJar() withSourcesJar() } @@ -80,6 +90,9 @@ dependencies { api(libs.lwjgl.core) api(libs.lwjgl.opengl) api(libs.lwjgl.opencl) + implementation(libs.asm.core) + implementation(libs.asm.util) + implementation(libs.asm.commons) for (platform in listOf( "linux", @@ -134,6 +147,18 @@ publishing { artifact(shadowJar) { classifier = "shaded" } } } + repositories { + val microbotRepoUrl = providers.gradleProperty("microbot.repo.url").orNull + if (!microbotRepoUrl.isNullOrBlank()) { + maven(uri(microbotRepoUrl)) { + name = "microbot" + credentials(PasswordCredentials::class) { + username = providers.gradleProperty("microbot.repo.username").getOrElse("") + password = providers.gradleProperty("microbot.repo.password").getOrElse("") + } + } + } + } } val assemble = tasks.withType { @@ -160,10 +185,22 @@ tasks.processResources { standardOutput = dirty } + val fallbackMicrobotVersion = loadRootProperty("microbot.version") + val microbotVersion = providers.gradleProperty("microbot.version") + .orElse(fallbackMicrobotVersion ?: "0.0.0") + .get() + val microbotCommit = providers.gradleProperty("microbot.commit.sha").getOrElse(commit.toString().trim()) + + // Ensure task reruns when injected values change + inputs.property("microbotVersion", microbotVersion) + inputs.property("microbotCommit", microbotCommit) + filesMatching("net/runelite/client/runelite.properties") { filter { it.replace("\${project.version}", project.version.toString()) } filter { it.replace("\${git.commit.id.abbrev}", commit.toString().trim()) } filter { it.replace("\${git.dirty}", dirty.toString().isNotBlank().toString()) } + filter { it.replace("\${microbot.version}", microbotVersion) } + filter { it.replace("\${microbot.commit.sha}", microbotCommit) } } } @@ -195,6 +232,7 @@ tasks.checkstyleMain { } tasks.withType { + enabled = false systemProperty("glslang.path", providers.gradleProperty("glslangPath").getOrElse("")) } diff --git a/runelite-client/pom.xml b/runelite-client/pom.xml index 911307d154b..e7df1de040b 100644 --- a/runelite-client/pom.xml +++ b/runelite-client/pom.xml @@ -41,7 +41,7 @@ nogit false false - 2.0.60 + 2.0.61 nogit diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/VoxPlugins/schedulable/example/LocationStartNotificationOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/VoxPlugins/schedulable/example/LocationStartNotificationOverlay.java deleted file mode 100644 index fc89dbd585d..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/VoxPlugins/schedulable/example/LocationStartNotificationOverlay.java +++ /dev/null @@ -1,145 +0,0 @@ -package net.runelite.client.plugins.microbot.VoxPlugins.schedulable.example; - -import net.runelite.api.coords.WorldPoint; -import net.runelite.client.plugins.microbot.Microbot; -import net.runelite.client.plugins.microbot.util.player.Rs2Player; -import net.runelite.client.ui.overlay.Overlay; -import net.runelite.client.ui.overlay.OverlayLayer; -import net.runelite.client.ui.overlay.OverlayPosition; -import net.runelite.client.ui.overlay.OverlayPriority; -import net.runelite.client.ui.overlay.components.LineComponent; -import net.runelite.client.ui.overlay.components.PanelComponent; -import net.runelite.client.ui.overlay.components.TitleComponent; - -import java.awt.*; - -/** - * Displays information about location-based start conditions - */ -public class LocationStartNotificationOverlay extends Overlay { - private final SchedulableExamplePlugin plugin; - private final SchedulableExampleConfig config; - private final PanelComponent panelComponent = new PanelComponent(); - - public LocationStartNotificationOverlay(SchedulableExamplePlugin plugin, SchedulableExampleConfig config) { - super(plugin); - setPosition(OverlayPosition.TOP_LEFT); - setLayer(OverlayLayer.ABOVE_SCENE); - setPriority(OverlayPriority.MED); - this.plugin = plugin; - this.config = config; - } - - @Override - public Dimension render(Graphics2D graphics) { - if (!Microbot.isLoggedIn() || !config.enableLocationStartCondition()) { - return null; - } - - panelComponent.getChildren().clear(); - - // Show title - panelComponent.getChildren().add(TitleComponent.builder() - .text("Location Conditions") - .color(Color.WHITE) - .build()); - - if (config.locationStartType() == SchedulableExampleConfig.LocationStartType.BANK) { - // Bank location information - panelComponent.getChildren().add(LineComponent.builder() - .left("Type:") - .right("Bank Location") - .build()); - - panelComponent.getChildren().add(LineComponent.builder() - .left("Target:") - .right(config.bankStartLocation().name()) - .build()); - - panelComponent.getChildren().add(LineComponent.builder() - .left("Distance:") - .right(config.bankDistance() + " tiles") - .build()); - - // Check and show if condition is met - boolean inRange = isNearBank(); - Color statusColor = inRange ? Color.GREEN : Color.RED; - panelComponent.getChildren().add(LineComponent.builder() - .left("Status:") - .right(inRange ? "In Range" : "Out of Range") - .rightColor(statusColor) - .build()); - - } else if (config.locationStartType() == SchedulableExampleConfig.LocationStartType.CUSTOM_AREA) { - // Custom area information - panelComponent.getChildren().add(LineComponent.builder() - .left("Type:") - .right("Custom Area") - .build()); - - if (config.customAreaActive() && config.customAreaCenter() != null) { - WorldPoint center = config.customAreaCenter(); - panelComponent.getChildren().add(LineComponent.builder() - .left("Center:") - .right(center.getX() + ", " + center.getY() + ", " + center.getPlane()) - .build()); - - panelComponent.getChildren().add(LineComponent.builder() - .left("Radius:") - .right(config.customAreaRadius() + " tiles") - .build()); - - // Check and show if condition is met - boolean inArea = plugin.isPlayerInCustomArea(); - Color statusColor = inArea ? Color.GREEN : Color.RED; - panelComponent.getChildren().add(LineComponent.builder() - .left("Status:") - .right(inArea ? "In Area" : "Out of Area") - .rightColor(statusColor) - .build()); - - // Show distance to center if not in area - if (!inArea) { - - WorldPoint playerPos = Rs2Player.getWorldLocation(); - if (playerPos != null) { - int distance = playerPos.distanceTo(center); - panelComponent.getChildren().add(LineComponent.builder() - .left("Distance:") - .right(distance + " tiles away") - .build()); - } - } - } else { - panelComponent.getChildren().add(LineComponent.builder() - .left("Status:") - .right("No Area Defined") - .rightColor(Color.YELLOW) - .build()); - - panelComponent.getChildren().add(LineComponent.builder() - .left("Help:") - .right("Press hotkey to mark area") - .build()); - } - } - - return panelComponent.render(graphics); - } - - /** - * Checks if the player is near the configured bank - */ - private boolean isNearBank() { - WorldPoint playerPos = Rs2Player.getWorldLocation(); - if (playerPos == null) { - return false; - } - - WorldPoint bankPos = config.bankStartLocation().getWorldPoint(); - int maxDistance = config.bankDistance(); - - return (playerPos.getPlane() == bankPos.getPlane() && - playerPos.distanceTo(bankPos) <= maxDistance); - } -} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/VoxPlugins/schedulable/example/README.md b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/VoxPlugins/schedulable/example/README.md deleted file mode 100644 index a367c3a8b72..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/VoxPlugins/schedulable/example/README.md +++ /dev/null @@ -1,499 +0,0 @@ -# SchedulableExamplePlugin Documentation - -## Overview -The `SchedulableExamplePlugin` demonstrates how to create a plugin compatible with Microbot's scheduler system. It implements the `ConditionProvider` interface to define configurable conditions for when the plugin should automatically start and stop based on various in-game criteria. This plugin serves as a comprehensive example for developers wanting to create scripts that can be managed by the scheduler framework, providing a template for implementing different types of conditions and state management approaches. - -The plugin provides a practical implementation of conditional logic that can be used to automate tasks in a controlled manner. By integrating with the scheduler, it enables users to create complex automation workflows where multiple plugins can work together in a sequenced manner, each starting and stopping based on specific game conditions. - -## Key Features -- **Seamless integration** with the Microbot scheduler framework -- **Highly configurable** start and stop conditions -- **Location-based start conditions**: - - Bank locations with configurable distance - - Custom areas with adjustable radius -- **Multiple stop condition types**: - - **Time-based**: Run for specified duration - - **Resource collection**: Stop after gathering specific items - - **Loot collection**: Stop after collecting specific drops - - **Item processing**: Stop after crafting/converting items - - **NPC kill counting**: Stop after killing a specific number of NPCs - -## Architecture -The plugin consists of four primary components that work together to provide a complete implementation of a scheduler-compatible plugin: - -1. **SchedulableExamplePlugin** (`SchedulableExamplePlugin.java`) - - Main plugin class implementing `ConditionProvider` and `KeyListener` - - Manages the plugin lifecycle and condition creation - - Handles hotkey inputs for custom area marking - - Serves as the central orchestrator that connects configuration, script execution, and condition evaluation - - Implements the scheduler integration points through the `ConditionProvider` methods - - Maintains state between sessions by saving and loading world locations - -2. **SchedulableExampleConfig** (`SchedulableExampleConfig.java`) - - Configuration interface with `@ConfigGroup` and `@ConfigItem` annotations - - Defines all configurable parameters for the plugin - - Organizes settings into logical sections using `@ConfigSection` annotations - - Provides default values for configuration options - - Includes setter methods for mutable state (like custom area coordinates) - - Uses enums to define valid option sets (like `LocationStartType` and `ProcessTrackingMode`) - - Implements hidden configuration items for internal state persistence - - Creates a hierarchical organization of settings with sections that can be collapsed by default - -3. **LocationStartNotificationOverlay** (`LocationStartNotificationOverlay.java`) - - Visual overlay displaying location-based start condition information - - Provides real-time feedback on condition status - - Uses RuneLite's overlay system to render information on the game screen - - Dynamically updates to show relevant details based on the current configuration - - Shows information about bank locations or custom areas depending on the active configuration - - Implements color-coded status indicators (green for met conditions, red for unmet) - - Displays distance measurements to target locations when relevant - - Updates in real-time as the player moves around the game world - - Provides helpful guidance messages for setting up custom areas - -## Condition Provider Implementation -The `ConditionProvider` interface is the key integration point between the plugin and the scheduler system. By implementing this interface, the plugin can define under what circumstances it should be automatically started or stopped by the scheduler. This provides a powerful abstraction that allows the scheduler to manage multiple plugins without needing to understand their specific functionality. - -### Stop Conditions -The `getStopCondition()` method returns a logical condition determining when the plugin should automatically stop. This method is called by the scheduler to evaluate whether the plugin should be terminated. The implementation combines multiple condition types into a single logical expression that can be evaluated to determine if stopping criteria have been met: - -```java -@Override -public LogicalCondition getStopCondition() { - // Create an OR condition - we'll stop when ANY of the enabled conditions are met - OrCondition orCondition = new OrCondition(); - - // Add enabled conditions based on configuration - if (config.enableTimeCondition()) { - orCondition.addCondition(createTimeCondition()); - } - - if (config.enableLootItemCondition()) { - orCondition.addCondition(createLootItemCondition()); - } - - // Add more conditions... - - // If no conditions were added, add a fallback time condition - if (orCondition.getConditions().isEmpty()) { - orCondition.addCondition(IntervalCondition.createRandomized(Duration.ofMinutes(5), Duration.ofMinutes(5))); - } - - return orCondition; -} -``` - -### Start Conditions -The `getStartCondition()` method returns a logical condition determining when the plugin is allowed to start. The scheduler uses this to determine if the plugin should be automatically started when it's scheduled to run. Unlike stop conditions which should always return a value, start conditions can return `null` to indicate that the plugin can start without any preconditions: - -```java -@Override -public LogicalCondition getStartCondition() { - // Default to no start conditions (always allowed to start) - if (!config.enableLocationStartCondition()) { - return null; - } - - // Create a logical condition for start conditions - LogicalCondition startCondition = null; - - // Create location-based condition based on selected type - if (config.locationStartType() == SchedulableExampleConfig.LocationStartType.BANK) { - // Bank-based start condition - // ... - } else if (config.locationStartType() == SchedulableExampleConfig.LocationStartType.CUSTOM_AREA) { - // Custom area start condition - // ... - } - - return startCondition; -} -``` - -## Detailed Condition Types -The plugin implements several types of conditions that can be used to control when it should start or stop. Each condition type is implemented as a separate method that creates and configures a condition object based on the current plugin configuration. - -### Time Condition -The time condition is the simplest form of stop condition, which will trigger after a specified duration has elapsed. This is useful for limiting the runtime of a plugin to prevent excessive resource usage or to simulate human-like play patterns with regular breaks: -```java -private Condition createTimeCondition() { - int minMinutes = config.minRuntime(); - int maxMinutes = config.maxRuntime(); - - return IntervalCondition.createRandomized( - Duration.ofMinutes(minMinutes), - Duration.ofMinutes(maxMinutes) - ); -} -``` - -### Loot Item Condition -The loot item condition is used to stop the plugin after collecting a specific number of items. This is particularly useful for gathering activities where you want to collect a certain amount of a resource before stopping. The condition supports both AND and OR logical operations, allowing for complex item collection goals: - -```java -private LogicalCondition createLootItemCondition() { - // Parse the comma-separated list of items - List lootItemsList = parseItemList(config.lootItems()); - - boolean andLogical = config.itemsToLootLogical(); - int minLootItems = config.minItems(); - int maxLootItems = config.maxItems(); - - // Create randomized targets for each item - List minLootItemPerPattern = new ArrayList<>(); - List maxLootItemPerPattern = new ArrayList<>(); - - // Generate target counts... - - // Create the appropriate logical condition based on config - if (andLogical) { - return LootItemCondition.createAndCondition( - lootItemsList, - minLootItemPerPattern, - maxLootItemPerPattern, - includeNoted, - allowNoneOwner - ); - } else { - return LootItemCondition.createOrCondition( - lootItemsList, - minLootItemPerPattern, - maxLootItemPerPattern, - includeNoted, - allowNoneOwner - ); - } -} -``` - -### Gathered Resource Condition -The gathered resource condition is similar to the loot item condition but is specifically designed for tracking resources gathered through skilling activities (mining, fishing, woodcutting, etc.). This allows for more precise tracking of gathering activities and can differentiate between items obtained through different methods: - -```java -private LogicalCondition createGatheredResourceCondition() { - // Parse the comma-separated list of resources - List resourcesList = parseItemList(config.gatheredResources()); - - boolean andLogical = config.resourcesLogical(); - int minResources = config.minResources(); - int maxResources = config.maxResources(); - boolean includeNoted = config.includeResourceNoted(); - - // Create target lists with randomized counts for each resource - List minResourcesPerItem = new ArrayList<>(); - List maxResourcesPerItem = new ArrayList<>(); - - for (String resource : resourcesList) { - int minCount = Rs2Random.between(minResources, maxResources); - int maxCount = Rs2Random.between(minCount, maxResources); - - minResourcesPerItem.add(minCount); - maxResourcesPerItem.add(maxCount); - } - - // Create the appropriate logical condition based on configuration - if (andLogical) { - return GatheredResourceCondition.createAndCondition( - resourcesList, - minResourcesPerItem, - maxResourcesPerItem, - includeNoted - ); - } else { - return GatheredResourceCondition.createOrCondition( - resourcesList, - minResourcesPerItem, - maxResourcesPerItem, - includeNoted - ); - } -} - -### Process Item Condition -The process item condition is designed to track item transformation operations such as crafting, smithing, cooking, and other production skills. It can monitor the consumption of source items, the production of target items, or both, making it versatile for various crafting and production tasks: - -```java -private Condition createProcessItemCondition() { - ProcessItemCondition.TrackingMode trackingMode; - - // Map config enum to condition enum - switch (config.trackingMode()) { - case SOURCE_CONSUMPTION: - trackingMode = ProcessItemCondition.TrackingMode.SOURCE_CONSUMPTION; - break; - // Other modes... - } - - // Create the appropriate process item condition based on tracking mode - if (trackingMode == ProcessItemCondition.TrackingMode.SOURCE_CONSUMPTION) { - // If tracking source consumption - // ... - } - // Other tracking modes... -} -``` - -### NPC Kill Count Condition -The NPC kill count condition monitors the number of NPCs killed during the plugin's execution. It supports pattern matching for NPC names and can be configured to track kills per NPC type or the total kill count across all specified NPCs. This is particularly useful for combat training and slayer task automation: - -```java -private LogicalCondition createNpcKillCountCondition() { - // Parse the comma-separated list of NPC names - List npcNamesList = parseItemList(config.npcNames()); - - boolean andLogical = config.npcLogical(); - int minKills = config.minKills(); - int maxKills = config.maxKills(); - boolean killsPerType = config.killsPerType(); - - // If we're counting per NPC type vs. total kills... -} -``` - -## Custom Area Management -The custom area feature allows users to define a specific area in the game world where the plugin should operate. This is implemented through a combination of configuration settings, hotkey handling, and visual overlay feedback. The custom area is defined as a circle with a configurable radius centered on the player's position when the area is created: - -```java -private void toggleCustomArea() { - if (!Microbot.isLoggedIn()) { - log.info("Cannot toggle custom area: Not logged in"); - return; - } - - boolean isActive = config.customAreaActive(); - - if (isActive) { - // Clear the custom area - config.setCustomAreaActive(false); - config.setCustomAreaCenter(null); - log.info("Custom area removed"); - } else { - // Create new custom area at current position - WorldPoint currentPos = null; - if (Microbot.isLoggedIn()){ - currentPos = Rs2Player.getWorldLocation(); - } - if (currentPos != null) { - config.setCustomAreaCenter(currentPos); - config.setCustomAreaActive(true); - log.info("Custom area created at: " + currentPos.toString() + " with radius: " + config.customAreaRadius()); - } - } -} -``` - -## Integration with Scheduler Events -The plugin integrates with the scheduler system by responding to events dispatched by the scheduler. The most important of these is the `PluginScheduleEntry`, which is triggered when the scheduler determines that a plugin should be stopped based on its stop conditions. The plugin handles this event by performing cleanup operations and then requesting that it be disabled: - -```java -@Override -@Subscribe -public void onPluginScheduleEntry(PluginScheduleEntry event) { - // Save location before stopping - if (event.getPlugin() == this) { - config.setLastLocation(Rs2Player.getWorldLocation()); - log.info("Scheduling stop for plugin: {}", event.getPlugin().getClass().getSimpleName()); - - // Schedule the stop operation on the client thread - Microbot.getClientThread().invokeLater(() -> { - try { - Microbot.getPluginManager().setPluginEnabled(this, false); - Microbot.getPluginManager().stopPlugin(this); - } catch (Exception e) { - log.error("Error stopping plugin", e); - } - }); - } -} -``` - -## Helper Methods -The plugin includes several helper methods that provide utility functionality for various aspects of its operation. These methods encapsulate common operations and logic to improve code readability and maintainability: - -```java -private List parseItemList(String itemsString) { - List itemsList = new ArrayList<>(); - if (itemsString != null && !itemsString.isEmpty()) { - String[] itemsArray = itemsString.split(","); - for (String item : itemsArray) { - String trimmedItem = item.trim(); - try { - // Validate regex pattern - java.util.regex.Pattern.compile(trimmedItem); - itemsList.add(trimmedItem); - log.debug("Valid item pattern found: {}", trimmedItem); - } catch (java.util.regex.PatternSyntaxException e) { - log.warn("Invalid regex pattern: '{}' - {}", trimmedItem, e.getMessage()); - } - } - } - return itemsList; -} -``` - -## Usage Guide - -### Setting Up the Plugin - -1. **Enable the plugin** through RuneLite's plugin manager - - Navigate to the plugin list and locate "Schedulable Example" - - Check the checkbox to enable it - - Note that the plugin can also be enabled by the scheduler when appropriate - -2. **Configure desired start/stop conditions** in the plugin's configuration panel: - - Click the configuration icon next to the plugin name - - Expand the various sections to access different types of conditions - - Configure at least one stop condition to ensure the plugin doesn't run indefinitely - - Common configurations include: - - Set time limits (minimum and maximum runtime) - - Define item collection targets (specific items and quantities) - - Configure NPC kill counts for combat activities - - Set up resource gathering goals for skilling activities - - Define item processing targets for crafting and production - -3. **Set up location-based start conditions** if desired: - - Enable the location start condition option - - Choose between bank location or custom area: - - **Bank Location**: Select a predefined bank location and set the maximum distance - - **Custom Area**: Position your character in the desired location and press the configured area marking hotkey - - The location overlay will show you when you're in a valid start position - - For custom areas, you can adjust the radius to control the size of the valid area - -4. **Start the plugin** in one of two ways: - - Manually start it through the plugin manager - - Let the scheduler start it automatically when scheduled and when start conditions are met - -5. **Monitor the plugin's operation**: - - Watch the status messages in the Microbot status area - - Check the overlay for location-based information - - The plugin will update its progress tracking as it runs - -6. **The plugin will automatically stop** when any of the following occurs: - - Any of the enabled stop conditions are satisfied - - The scheduler sends a stop event - - The plugin is manually disabled through the plugin manager - -## Example Configuration - -This configuration would make the plugin: -- Only start when the player is at the Grand Exchange -- Stop after running for 30-45 minutes OR after collecting 100-200 oak logs (whichever happens first) - -``` -enableLocationStartCondition: true -locationStartType: BANK -bankStartLocation: GRAND_EXCHANGE -bankDistance: 5 - -enableTimeCondition: true -minRuntime: 30 -maxRuntime: 45 - -enableLootItemCondition: true -lootItems: "Oak logs" -minItems: 100 -maxItems: 200 -``` - -## Technical Implementation Notes - -### Core Design Patterns and Principles - -1. **Thread Safety** - - The plugin uses `Microbot.getClientThread().invokeLater()` to ensure operations run on the client thread - - This is critical for preventing race conditions and ensuring proper interaction with the game client - - All UI updates and game state modifications should be performed on the client thread - -2. **State Persistence** - - Configuration state is saved between sessions using RuneLite's ConfigManager - - The plugin maintains state across sessions by saving: - - Last known player location - - Custom area definitions - - Configuration parameters - - This allows seamless continuation of tasks even after client restarts - -3. **Random Variance** - - Stop conditions use randomized ranges to add human-like variability - - The `Rs2Random.between()` utility is used to generate random values within configured ranges - - This prevents predictable patterns that might appear bot-like - - Different randomization approaches are used for different types of conditions - -4. **Pattern Matching** - - Item and NPC name matching supports regular expressions for flexibility - - This allows for powerful pattern matching capabilities like: - - Wildcards (e.g., ".*bones.*" to match any item containing "bones") - - Character classes (e.g., "[A-Za-z]+ logs" to match any type of logs) - - Alternations (e.g., "goblin|rat|spider" to match multiple NPC types) - - Regular expression patterns are validated before use to prevent runtime errors - -5. **Logical Composition** - - Conditions can be combined with AND/OR logic for complex triggering - - The `LogicalCondition` interface and its implementations (`AndCondition`, `OrCondition`) provide a composable framework - - This allows for arbitrarily complex condition trees to be constructed - - Each logical condition can contain any mix of primitive conditions or nested logical conditions - -6. **State Machine Pattern** - - The `SchedulableExampleScript` uses a state machine to manage its operation - - Different states handle different aspects of the script's functionality - - Transitions between states occur based on in-game conditions - - This provides a clear, maintainable structure for complex bot logic - -7. **Event-Driven Architecture** - - The plugin responds to events from the scheduler and game client - - Events trigger state changes and condition evaluations - - This decouples the plugin's logic from the specific timing of game updates - -## Extending the Plugin - -### Adding New Condition Types - -To extend the plugin with new types of conditions: - -1. **Create a new condition class** implementing the `Condition` interface - - Define the logic for when the condition is satisfied - - Implement the `reset()` method to reinitialize the condition's state - - Consider extending existing base classes like `ResourceCondition` if appropriate - -2. **Add configuration options** to `SchedulableExampleConfig` - - Create a new configuration section with `@ConfigSection` if needed - - Add configuration items with `@ConfigItem` annotations - - Define appropriate default values and descriptions - - Consider using enums for options with a fixed set of valid values - -3. **Implement a creation method** in `SchedulableExamplePlugin` - - Create a method that constructs and configures your new condition - - Add appropriate logic to handle configuration options - - Include randomization if appropriate for human-like behavior - - Handle edge cases and provide fallback values - -4. **Add the condition** to the appropriate logical group in `getStopCondition()` - - Check if the condition is enabled in the configuration - - Add it to the existing logical condition structure (typically an `OrCondition`) - - Consider how it interacts with other existing conditions - -### Implementing New Features - -To add entirely new functionality to the plugin: - -1. **Extend the script class** with new methods and state management - - Add new states to the state machine if needed - - Implement the logic for the new functionality - - Update the main loop to handle the new states and operations - -2. **Update the configuration interface** with options for the new features - - Group related settings into logical sections - - Provide clear descriptions and default values - - Add validation where appropriate - -3. **Enhance the overlay** if visual feedback is needed - - Add new information to the overlay rendering - - Consider color coding or other visual cues for status - - Ensure the overlay remains uncluttered and informative - -4. **Add new condition types** if needed for the new functionality - - Follow the steps outlined above for adding conditions - - Ensure the conditions properly integrate with the new features - -5. **Update documentation** to reflect the new capabilities - - Document configuration options - - Explain new condition types - - Provide usage examples \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/VoxPlugins/schedulable/example/SchedulableExampleConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/VoxPlugins/schedulable/example/SchedulableExampleConfig.java deleted file mode 100644 index ed6b4c9bb49..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/VoxPlugins/schedulable/example/SchedulableExampleConfig.java +++ /dev/null @@ -1,889 +0,0 @@ -package net.runelite.client.plugins.microbot.VoxPlugins.schedulable.example; - -import net.runelite.api.coords.WorldPoint; -import net.runelite.client.config.Config; -import net.runelite.client.config.ConfigGroup; -import net.runelite.client.config.ConfigItem; -import net.runelite.client.config.ConfigSection; -import net.runelite.client.config.Keybind; -import net.runelite.client.config.Range; -import net.runelite.client.plugins.microbot.Microbot; -import net.runelite.client.plugins.microbot.util.bank.enums.BankLocation; -import net.runelite.client.plugins.microbot.VoxPlugins.schedulable.example.enums.UnifiedLocation; -import net.runelite.client.plugins.microbot.VoxPlugins.schedulable.example.enums.SpellbookOption; - -import java.awt.event.KeyEvent; - -@ConfigGroup("SchedulableExample") -public interface SchedulableExampleConfig extends Config { - @ConfigSection( - name = "Start Conditions", - description = "Conditions for when the plugin is allowed to start", - position = 0 - ) - String startConditionSection = "startConditions"; - - @ConfigSection( - name = "Location Start Conditions", - description = "Location-based conditions for when the plugin is allowed to start", - position = 1, - closedByDefault = false - ) - String locationStartConditionSection = "locationStartConditions"; - - // Location Start Condition Settings - @ConfigItem( - keyName = "enableLocationStartCondition", - name = "Enable Location Start Condition", - description = "Enable location-based start condition", - position = 0, - section = locationStartConditionSection - ) - default boolean enableLocationStartCondition() { - return false; - } - - @ConfigItem( - keyName = "locationStartType", - name = "Location Type", - description = "Type of location condition to use for starting the plugin", - position = 1, - section = locationStartConditionSection - ) - default LocationStartType locationStartType() { - return LocationStartType.BANK; - } - - @ConfigItem( - keyName = "bankStartLocation", - name = "Bank Location", - description = "Bank location where the plugin should start", - position = 2, - section = locationStartConditionSection - ) - default BankLocation bankStartLocation() { - return BankLocation.GRAND_EXCHANGE; - } - @Range( - min = 10, - max = 100 - ) - @ConfigItem( - keyName = "bankDistance", - name = "Bank Distance (tiles)", - description = "Maximum distance from bank to start the plugin", - position = 3, - section = locationStartConditionSection - ) - default int bankDistance() { - return 20; - } - - @ConfigItem( - keyName = "customAreaActive", - name = "Custom Area Active", - description = "Whether a custom area has been defined using the hotkey", - position = 4, - section = locationStartConditionSection, - hidden = true - ) - default boolean customAreaActive() { - return false; - } - - void setCustomAreaActive(boolean active); - - @ConfigItem( - keyName = "areaMarkHotkey", - name = "Area Mark Hotkey", - description = "Hotkey to mark current position as center of custom area (press again to clear)", - position = 5, - section = locationStartConditionSection - ) - default Keybind areaMarkHotkey() { - return Keybind.NOT_SET; - } - - @ConfigItem( - keyName = "customAreaRadius", - name = "Custom Area Radius (tiles)", - description = "Radius of the custom area around the marked position", - position = 6, - section = locationStartConditionSection - ) - default int customAreaRadius() { - return 10; - } - - @ConfigItem( - keyName = "customAreaCenter", - name = "Custom Area Center", - description = "Center point of the custom area", - position = 7, - section = locationStartConditionSection, - hidden = true - ) - default WorldPoint customAreaCenter() { - return null; - } - - void setCustomAreaCenter(WorldPoint center); - - // Enum for location start types - enum LocationStartType { - BANK("Bank Location"), - CUSTOM_AREA("Custom Area"); - - private final String name; - - LocationStartType(String name) { - this.name = name; - } - - @Override - public String toString() { - return name; - } - } - - enum ProcessTrackingMode { - SOURCE_CONSUMPTION("Source Consumption"), - TARGET_PRODUCTION("Target Production"), - EITHER("Either"), - BOTH("Both"); - - private final String name; - - ProcessTrackingMode(String name) { - this.name = name; - } - - @Override - public String toString() { - return name; - } - } - - - @ConfigSection( - name = "Stop Conditions", - description = "Conditions for when the plugin should stop", - position = 101 - ) - String stopSection = "stopConditions"; - - @ConfigSection( - name = "Time Conditions", - description = "Time-based conditions for stopping the plugin", - position = 102, - closedByDefault = false - ) - String timeConditionSection = "timeConditions"; - - @ConfigSection( - name = "Loot Item Conditions", - description = "Conditions related to looted items", - position = 103, - closedByDefault = false - ) - String lootItemConditionSection = "lootItemConditions"; - - @ConfigSection( - name = "Gathered Resource Conditions", - description = "Conditions related to gathered resources (mining, fishing, etc.)", - position = 104, - closedByDefault = false - ) - String gatheredResourceConditionSection = "gatheredResourceConditions"; - - @ConfigSection( - name = "Process Item Conditions", - description = "Conditions related to processed items (crafting, smithing, etc.)", - position = 105, - closedByDefault = false - ) - String processItemConditionSection = "processItemConditions"; - @ConfigSection( - name = "NPC Conditions", - description = "Conditions related to NPCs", - position = 106, - closedByDefault = false - ) - String npcConditionSection = "npcConditions"; - - @ConfigSection( - name = "Pre/Post Schedule Requirements", - description = "Configure requirements for pre and post schedule tasks", - position = 107, - closedByDefault = false - ) - String prePostScheduleRequirementsSection = "prePostScheduleRequirements"; - - // Time Condition Settings - @ConfigItem( - keyName = "enableTimeCondition", - name = "Enable Time Condition", - description = "Enable time-based stop condition", - position = 0, - section = timeConditionSection - ) - default boolean enableTimeCondition() { - return true; - } - - @ConfigItem( - keyName = "minRuntime", - name = "Minimum Runtime (minutes)", - description = "Minimum time to run before stopping", - position = 1, - section = timeConditionSection - ) - default int minRuntime() { - return 1; - } - - @ConfigItem( - keyName = "maxRuntime", - name = "Maximum Runtime (minutes)", - description = "Maximum time to run before stopping", - position = 2, - section = timeConditionSection - ) - default int maxRuntime() { - return 2; - } - - // Loot Item Condition Settings - @ConfigItem( - keyName = "enableLootItemCondition", - name = "Enable Loot Item Condition", - description = "Enable condition to stop based on looted items", - position = 0, - section = lootItemConditionSection - ) - default boolean enableLootItemCondition() { - return true; - } - - @ConfigItem( - keyName = "lootItems", - name = "Loot Items to Track", - description = "Comma separated list of items. Supports regex patterns (.*bones.*)", - position = 1, - section = lootItemConditionSection - ) - default String lootItems() { - return "Logs"; - } - - @ConfigItem( - keyName = "itemsToLootLogical", - name = "Or(False)/And(True)", - description = "Logical operator for items to loot: False=OR, True=AND", - position = 2, - section = lootItemConditionSection - ) - default boolean itemsToLootLogical() { - return false; - } - - @ConfigItem( - keyName = "minItems", - name = "Minimum Items", - description = "Minimum number of items to loot before stopping", - position = 3, - section = lootItemConditionSection - ) - default int minItems() { - return 5; - } - - @ConfigItem( - keyName = "maxItems", - name = "Maximum Items", - description = "Maximum number of items to loot before stopping", - position = 4, - section = lootItemConditionSection - ) - default int maxItems() { - return 10; - } - - @ConfigItem( - keyName = "includeNoted", - name = "Include Noted Items", - description = "Include noted items in loot tracking", - position = 5, - section = lootItemConditionSection - ) - default boolean includeNoted() { - return false; - } - - @ConfigItem( - keyName = "allowNoneOwner", - name = "Allow None Owner", - description = "Allow items not owned by the player (e.g. items which are spawned)", - position = 6, - section = lootItemConditionSection - ) - default boolean allowNoneOwner() { - return false; - } - - // Gathered Resource Condition Settings - @ConfigItem( - keyName = "enableGatheredResourceCondition", - name = "Enable Gathered Resource Condition", - description = "Enable condition to stop based on gathered resources", - position = 0, - section = gatheredResourceConditionSection - ) - default boolean enableGatheredResourceCondition() { - return false; - } - - @ConfigItem( - keyName = "gatheredResources", - name = "Resources to Track", - description = "Comma separated list of resources to track (e.g. logs,ore,fish)", - position = 1, - section = gatheredResourceConditionSection - ) - default String gatheredResources() { - return "logs"; - } - - @ConfigItem( - keyName = "resourcesLogical", - name = "Or(False)/And(True)", - description = "Logical operator for resources: False=OR, True=AND", - position = 2, - section = gatheredResourceConditionSection - ) - default boolean resourcesLogical() { - return false; - } - - @ConfigItem( - keyName = "minResources", - name = "Minimum Resources", - description = "Minimum number of resources to gather before stopping", - position = 3, - section = gatheredResourceConditionSection - ) - default int minResources() { - return 10; - } - - @ConfigItem( - keyName = "maxResources", - name = "Maximum Resources", - description = "Maximum number of resources to gather before stopping", - position = 4, - section = gatheredResourceConditionSection - ) - default int maxResources() { - return 15; - } - - @ConfigItem( - keyName = "includeResourceNoted", - name = "Include Noted Resources", - description = "Include noted resources in tracking", - position = 5, - section = gatheredResourceConditionSection - ) - default boolean includeResourceNoted() { - return false; - } - - // Process Item Condition Settings - @ConfigItem( - keyName = "enableProcessItemCondition", - name = "Enable Process Item Condition", - description = "Enable condition to stop based on processed items", - position = 0, - section = processItemConditionSection - ) - default boolean enableProcessItemCondition() { - return false; - } - - @ConfigItem( - keyName = "trackingMode", - name = "Tracking Mode", - description = "How to track item processing (source items consumed or target items produced)", - position = 1, - section = processItemConditionSection - ) - default ProcessTrackingMode trackingMode() { - return ProcessTrackingMode.SOURCE_CONSUMPTION; - } - - @ConfigItem( - keyName = "sourceItems", - name = "Source Items", - description = "Comma separated list of source items (e.g. logs,ore)", - position = 2, - section = processItemConditionSection - ) - default String sourceItems() { - return "logs"; - } - - @ConfigItem( - keyName = "targetItems", - name = "Target Items", - description = "Comma separated list of target items (e.g. bow,shield)", - position = 3, - section = processItemConditionSection - ) - default String targetItems() { - return "bow"; - } - - @ConfigItem( - keyName = "minProcessedItems", - name = "Minimum Processed Items", - description = "Minimum number of items to process before stopping", - position = 4, - section = processItemConditionSection - ) - default int minProcessedItems() { - return 5; - } - - @ConfigItem( - keyName = "maxProcessedItems", - name = "Maximum Processed Items", - description = "Maximum number of items to process before stopping", - position = 5, - section = processItemConditionSection - ) - default int maxProcessedItems() { - return 10; - } - // NPC Kill Count Condition Settings - @ConfigItem( - keyName = "enableNpcKillCountCondition", - name = "Enable NPC Kill Count Condition", - description = "Enable condition to stop based on NPC kill count", - position = 0, - section = npcConditionSection - ) - default boolean enableNpcKillCountCondition() { - return false; - } - @ConfigItem( - keyName = "npcNames", - name = "NPCs to Track", - description = "Comma separated list of NPC names to track kills for. Supports regex patterns.", - position = 1, - section = npcConditionSection - ) - default String npcNames() { - return "goblin"; - } - - @ConfigItem( - keyName = "npcLogical", - name = "Or(False)/And(True)", - description = "Logical operator for NPCs: False=OR (any NPC satisfies), True=AND (all NPCs must be killed)", - position = 2, - section = npcConditionSection - ) - default boolean npcLogical() { - return false; - } - - @ConfigItem( - keyName = "minKills", - name = "Minimum Kills", - description = "Minimum number of NPCs to kill before stopping", - position = 3, - section = npcConditionSection - ) - default int minKills() { - return 5; - } - - @ConfigItem( - keyName = "maxKills", - name = "Maximum Kills", - description = "Maximum number of NPCs to kill before stopping", - position = 4, - section = npcConditionSection - ) - default int maxKills() { - return 10; - } - - @ConfigItem( - keyName = "killsPerType", - name = "Count Per NPC Type", - description = "If true, need to kill the specified count of EACH NPC type. If false, count total kills across all types.", - position = 5, - section = npcConditionSection - ) - default boolean killsPerType() { - return true; - } - - - // Location tracking - @ConfigItem( - keyName = "lastLocation", - name = "Last Location", - description = "Last tracked location", - hidden = true - ) - default WorldPoint lastLocation() { - return null; - } - default void setLastLocation(WorldPoint location){ - if (location != null) { - if (Microbot.getConfigManager() != null) { - Microbot.getConfigManager().setConfiguration("SchedulableExample", "lastLocation", location); - } - } - - } - - // Pre/Post Schedule Requirements Configuration - @ConfigItem( - keyName = "enablePrePostRequirements", - name = "Enable Pre/Post Requirements", - description = "Enable pre and post schedule requirements and tasks", - position = 0, - section = prePostScheduleRequirementsSection - ) - default boolean enablePrePostRequirements() { - return false; - } - - @ConfigItem( - keyName = "preScheduleSpellbook", - name = "Pre-Schedule Spellbook", - description = "Spellbook required before starting the plugin (None = no switching)", - position = 1, - section = prePostScheduleRequirementsSection - ) - default SpellbookOption preScheduleSpellbook() { - return SpellbookOption.NONE; - } - - @ConfigItem( - keyName = "postScheduleSpellbook", - name = "Post-Schedule Spellbook", - description = "Spellbook to switch to after plugin completion (None = no switching)", - position = 2, - section = prePostScheduleRequirementsSection - ) - default SpellbookOption postScheduleSpellbook() { - return SpellbookOption.NONE; - } - - @ConfigItem( - keyName = "preScheduleLocation", - name = "Pre-Schedule Location", - description = "Location required before starting the plugin (None = no location requirement)", - position = 3, - section = prePostScheduleRequirementsSection - ) - default UnifiedLocation preScheduleLocation() { - return UnifiedLocation.NONE; - } - - @ConfigItem( - keyName = "postScheduleLocation", - name = "Post-Schedule Location", - description = "Location to move to after plugin completion (None = no location requirement)", - position = 4, - section = prePostScheduleRequirementsSection - ) - default UnifiedLocation postScheduleLocation() { - return UnifiedLocation.NONE; - } - - - - @ConfigItem( - keyName = "enableConditionalItemRequirement", - name = "Enable Alch Conditional Requirement based on Fire Staff/Rune", - description = "Enable the fire staff/fire rune conditional requirement for alching in pre-schedule tasks.", - position = 5, - section = prePostScheduleRequirementsSection - ) - default boolean enableConditionalItemRequirement() { - return false; - } - - - @ConfigItem( - keyName = "enableEquipmentRequirement", - name = "Enable Equipment Requirement", - description = "Enable equipment requirement", - position = 6, - section = prePostScheduleRequirementsSection - ) - default boolean enableEquipmentRequirement() { - return false; - } - - @ConfigItem( - keyName = "enableInventoryRequirement", - name = "Enable Inventory Requirement", - description = "Enable inventory requirement", - position = 7, - section = prePostScheduleRequirementsSection - ) - default boolean enableInventoryRequirement() { - return false; - } - @ConfigItem( - keyName = "enableLootRequirement", - name = "Enable Loot Requirement", - description = "Enable loot requirement for coins near Lumbridge", - position = 8, - section = prePostScheduleRequirementsSection - ) - default boolean enableLootRequirement() { - return false; - } - @ConfigItem( - keyName = "enableShopRequirement", - name = "Enable Shop Requirement", - description = "Enable shop maple longbow, buy from grand exchange as pre-schedule and sell at store on post-schedule", - position = 9, - section = prePostScheduleRequirementsSection - ) - default boolean enableShopRequirement() { - return false; - } - - @ConfigItem( - keyName = "externalRequirements", - name = "Enable External Requirements", - description = "Enable external requirements test for pre and post schedule tasks", - position = 10, - section = prePostScheduleRequirementsSection - ) - default boolean externalRequirements() { - return false; - } - - - - @ConfigSection( - name = "Antiban Testing", - description = "Antiban system testing and configuration", - position = 199, - closedByDefault = true - ) - String antibanTestSection = "antibanTestSection"; - - @ConfigItem( - keyName = "enableAntibanTesting", - name = "Enable Antiban Testing", - description = "Enable antiban features testing including micro breaks", - position = 0, - section = antibanTestSection - ) - default boolean enableAntibanTesting() { - return false; - } - - @ConfigItem( - keyName = "enableMicroBreaks", - name = "Enable Micro Breaks", - description = "Enable micro breaks during plugin execution", - position = 1, - section = antibanTestSection - ) - default boolean enableMicroBreaks() { - return false; - } - - @ConfigItem( - keyName = "microBreakChance", - name = "Micro Break Chance", - description = "Chance (0.0-1.0) of taking a micro break per check", - position = 2, - section = antibanTestSection - ) - @Range(min = 0, max = 100) - default int microBreakChancePercent() { - return 10; // 10% default - } - - @ConfigItem( - keyName = "microBreakDurationMin", - name = "Micro Break Min Duration (minutes)", - description = "Minimum duration for micro breaks in minutes", - position = 3, - section = antibanTestSection - ) - @Range(min = 1, max = 30) - default int microBreakDurationMin() { - return 3; - } - - @ConfigItem( - keyName = "microBreakDurationMax", - name = "Micro Break Max Duration (minutes)", - description = "Maximum duration for micro breaks in minutes", - position = 4, - section = antibanTestSection - ) - @Range(min = 1, max = 60) - default int microBreakDurationMax() { - return 15; - } - - @ConfigItem( - keyName = "statusReportInterval", - name = "Status Report Interval (seconds)", - description = "How often to report break status (0 = disable reporting)", - position = 5, - section = antibanTestSection - ) - @Range(min = 0, max = 300) - default int statusReportInterval() { - return 30; // Report every 30 seconds - } - - @ConfigItem( - keyName = "enableActionCooldowns", - name = "Enable Action Cooldowns", - description = "Enable action cooldown testing", - position = 6, - section = antibanTestSection - ) - default boolean enableActionCooldowns() { - return false; - } - - @ConfigItem( - keyName = "moveMouseOffScreen", - name = "Move Mouse Off-Screen", - description = "Move mouse off-screen during breaks", - position = 7, - section = antibanTestSection - ) - default boolean moveMouseOffScreen() { - return false; - } - - @ConfigSection( - name = "Debug Options", - description = "Options for testing and debugging", - position = 200, - closedByDefault = true - ) - String debugSection = "debugSection"; - - @ConfigItem( - keyName = "aliveReportTimeout", - name = "Alive Report Timeout (sec)", - description = "Time in seconds before script reports it's alive", - position = 0, - section = debugSection - ) - @Range( - min = 10, - max = 100 - ) - default int aliveReportTimeout() { - return 10; - } - - @ConfigItem( - keyName = "finishPluginNotSuccessfulHotkey", - name = "Finish Plugin Not-Successful Hotkey", - description = "Press this hotkey to manually trigger the PluginScheduleEntryMainTaskFinishedEvent for testing not successful completion", - position = 1, - section = debugSection - ) - default Keybind finishPluginNotSuccessfulHotkey() { - return new Keybind(KeyEvent.VK_F2, 0); - } - - @ConfigItem( - keyName = "finishPluginSuccessfulHotkey", - name = "Finish Plugin Hotkey", - description = "Press this hotkey to manually trigger the PluginScheduleEntryMainTaskFinishedEvent for testing successful completion", - position = 2, - section = debugSection - ) - default Keybind finishPluginSuccessfulHotkey() { - return new Keybind(KeyEvent.VK_F3, 0); - } - - - @ConfigItem( - keyName = "finishReason", - name = "Finish Reason", - description = "The reason to report when finishing the plugin", - position = 3, - section = debugSection - ) - default String finishReason() { - return "Task completed successfully"; - } - - @ConfigItem( - keyName = "lockConditionHotkey", - name = "Lock Condition Hotkey", - description = "Press this hotkey to toggle the lock condition (prevents plugin from being stopped)", - position = 4, - section = debugSection - ) - default Keybind lockConditionHotkey() { - return Keybind.NOT_SET; - } - - @ConfigItem( - keyName = "lockDescription", - name = "Lock Reason", - description = "Description of why the plugin is locked", - position = 5, - section = debugSection - ) - default String lockDescription() { - return "Plugin in critical state - do not stop"; - } - - @ConfigItem( - keyName = "testPreScheduleTasksHotkey", - name = "Test Pre-Schedule Tasks Hotkey", - description = "Press this hotkey to test pre-schedule tasks functionality (equipment, spellbook, location setup)", - position = 6, - section = debugSection - ) - default Keybind testPreScheduleTasksHotkey() { - return Keybind.NOT_SET; - } - - @ConfigItem( - keyName = "testPostScheduleTasksHotkey", - name = "Test Post-Schedule Tasks Hotkey", - description = "Press this hotkey to test post-schedule tasks functionality (cleanup, banking, spellbook restoration)", - position = 7, - section = debugSection - ) - default Keybind testPostScheduleTasksHotkey() { - return Keybind.NOT_SET; - } - - @ConfigItem( - keyName = "cancelTasksHotkey", - name = "Cancel & Reset Tasks Hotkey", - description = "Press this hotkey to cancel any running pre/post schedule tasks and reset execution state", - position = 8, - section = debugSection - ) - default Keybind cancelTasksHotkey() { - return Keybind.NOT_SET; - } -} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/VoxPlugins/schedulable/example/SchedulableExampleOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/VoxPlugins/schedulable/example/SchedulableExampleOverlay.java deleted file mode 100644 index cd5ad772a10..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/VoxPlugins/schedulable/example/SchedulableExampleOverlay.java +++ /dev/null @@ -1,139 +0,0 @@ -package net.runelite.client.plugins.microbot.VoxPlugins.schedulable.example; - -import net.runelite.client.plugins.microbot.pluginscheduler.tasks.state.TaskExecutionState; -import net.runelite.client.ui.overlay.OverlayLayer; -import net.runelite.client.ui.overlay.OverlayPanel; -import net.runelite.client.ui.overlay.OverlayPosition; -import net.runelite.client.ui.overlay.components.ComponentConstants; - -import javax.inject.Inject; -import java.awt.*; - -/** - * Overlay for the SchedulableExample plugin that displays the current state of - * pre/post schedule requirements and task execution. - * - * This overlay demonstrates how to integrate the RequirementOverlayComponentFactory - * to provide real-time feedback about requirement fulfillment progress. - */ -public class SchedulableExampleOverlay extends OverlayPanel { - - private final SchedulableExamplePlugin plugin; - - @Inject - public SchedulableExampleOverlay(SchedulableExamplePlugin plugin) { - super(plugin); - this.plugin = plugin; - setPosition(OverlayPosition.TOP_LEFT); - setPreferredSize(new Dimension(ComponentConstants.STANDARD_WIDTH, 200)); - setNaughty(); - setDragTargetable(true); - setLayer(OverlayLayer.UNDER_WIDGETS); - } - - @Override - public Dimension render(Graphics2D graphics) { - // Clear previous components - panelComponent.getChildren().clear(); - - // Get the task manager and requirements - SchedulableExamplePrePostScheduleTasks tasks = (SchedulableExamplePrePostScheduleTasks)plugin.getPrePostScheduleTasks(); - - // Only show overlay if pre/post requirements are enabled or tasks are running - if (!plugin.getConfig().enablePrePostRequirements() && - (tasks == null || !tasks.isExecuting())) { - return null; // Don't show overlay when not needed - } - - try { - // Show concise information only - boolean isExecuting = tasks != null && tasks.isExecuting(); - boolean hasPrePostRequirements = plugin.getConfig().enablePrePostRequirements(); - - // Main title with status indication - String titleText = "SchedulableExample"; - Color titleColor = Color.CYAN; - - if (isExecuting) { - TaskExecutionState executionState = tasks.getExecutionState(); - if (executionState.isInErrorState()) { - titleText += " (ERROR)"; - titleColor = Color.RED; - } else { - titleText += " (ACTIVE)"; - titleColor = Color.YELLOW; - } - } else if (hasPrePostRequirements) { - titleText += " (READY)"; - titleColor = Color.CYAN; - } - - panelComponent.getChildren().add(net.runelite.client.ui.overlay.components.TitleComponent.builder() - .text(titleText) - .color(titleColor) - .build()); - - // Show current status - if (isExecuting) { - TaskExecutionState executionState = tasks.getExecutionState(); - String phase = executionState.getCurrentPhase() != null ? - executionState.getCurrentPhase().toString() : "EXECUTING"; - int progress = executionState.getProgressPercentage(); - String statusText = progress > 0 ? phase + " (" + progress + "%)" : phase; - - panelComponent.getChildren().add(net.runelite.client.ui.overlay.components.LineComponent.builder() - .left("Phase:") - .right(statusText) - .leftColor(Color.WHITE) - .rightColor(Color.YELLOW) - .build()); - - // Show detailed status if available and short enough - String detailedStatus = executionState.getDetailedStatus(); - if (detailedStatus != null && !detailedStatus.isEmpty() && detailedStatus.length() <= 25) { - panelComponent.getChildren().add(net.runelite.client.ui.overlay.components.LineComponent.builder() - .left("Status:") - .right(detailedStatus) - .leftColor(Color.WHITE) - .rightColor(Color.CYAN) - .build()); - } - } else { - // Show requirements status when not executing - String requirementsText = hasPrePostRequirements ? "ENABLED" : "DISABLED"; - Color requirementsColor = hasPrePostRequirements ? Color.GREEN : Color.GRAY; - - panelComponent.getChildren().add(net.runelite.client.ui.overlay.components.LineComponent.builder() - .left("Pre/Post:") - .right(requirementsText) - .leftColor(Color.WHITE) - .rightColor(requirementsColor) - .build()); - } - - // Show essential controls hint - panelComponent.getChildren().add(net.runelite.client.ui.overlay.components.LineComponent.builder() - .left("Hotkeys:") - .right("See config") - .leftColor(Color.WHITE) - .rightColor(Color.LIGHT_GRAY) - .build()); - - } catch (Exception e) { - // Show error in overlay - panelComponent.getChildren().add(net.runelite.client.ui.overlay.components.TitleComponent.builder() - .text("SchedulableExample - ERROR") - .color(Color.RED) - .build()); - - panelComponent.getChildren().add(net.runelite.client.ui.overlay.components.LineComponent.builder() - .left("Error:") - .right(e.getMessage() != null ? e.getMessage() : "Unknown error") - .leftColor(Color.WHITE) - .rightColor(Color.RED) - .build()); - } - - return super.render(graphics); - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/VoxPlugins/schedulable/example/SchedulableExamplePlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/VoxPlugins/schedulable/example/SchedulableExamplePlugin.java deleted file mode 100644 index 7fa73314123..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/VoxPlugins/schedulable/example/SchedulableExamplePlugin.java +++ /dev/null @@ -1,978 +0,0 @@ -package net.runelite.client.plugins.microbot.VoxPlugins.schedulable.example; - - -import java.awt.event.KeyEvent; -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; - -import javax.inject.Inject; - -import com.google.inject.Provides; - -import lombok.extern.slf4j.Slf4j; -import net.runelite.api.Client; -import net.runelite.api.GameState; -import net.runelite.api.coords.WorldPoint; -import net.runelite.api.events.GameStateChanged; -import net.runelite.client.config.ConfigDescriptor; -import net.runelite.client.config.ConfigManager; -import net.runelite.client.eventbus.Subscribe; -import net.runelite.client.events.ConfigChanged; -import net.runelite.client.input.KeyListener; -import net.runelite.client.input.KeyManager; -import net.runelite.client.plugins.Plugin; -import net.runelite.client.plugins.PluginDescriptor; -import net.runelite.client.plugins.microbot.Microbot; -import net.runelite.client.plugins.microbot.pluginscheduler.api.SchedulablePlugin; -import net.runelite.client.plugins.microbot.pluginscheduler.condition.Condition; -import net.runelite.client.plugins.microbot.pluginscheduler.condition.location.AreaCondition; -import net.runelite.client.plugins.microbot.pluginscheduler.condition.location.LocationCondition; -import net.runelite.client.plugins.microbot.pluginscheduler.condition.logical.AndCondition; -import net.runelite.client.plugins.microbot.pluginscheduler.condition.logical.LockCondition; -import net.runelite.client.plugins.microbot.pluginscheduler.condition.logical.LogicalCondition; -import net.runelite.client.plugins.microbot.pluginscheduler.condition.logical.OrCondition; -import net.runelite.client.plugins.microbot.pluginscheduler.condition.npc.NpcKillCountCondition; -import net.runelite.client.plugins.microbot.pluginscheduler.condition.resource.GatheredResourceCondition; -import net.runelite.client.plugins.microbot.pluginscheduler.condition.resource.LootItemCondition; -import net.runelite.client.plugins.microbot.pluginscheduler.condition.resource.ProcessItemCondition; -import net.runelite.client.plugins.microbot.pluginscheduler.condition.time.IntervalCondition; -import net.runelite.client.plugins.microbot.pluginscheduler.event.PluginScheduleEntryPostScheduleTaskEvent; -import net.runelite.client.plugins.microbot.pluginscheduler.event.PluginScheduleEntryPreScheduleTaskEvent; -import net.runelite.client.plugins.microbot.pluginscheduler.tasks.AbstractPrePostScheduleTasks; -import net.runelite.client.plugins.microbot.util.bank.enums.BankLocation; -import net.runelite.client.plugins.microbot.util.math.Rs2Random; -import net.runelite.client.plugins.microbot.util.player.Rs2Player; - -import net.runelite.client.ui.overlay.OverlayManager; -import net.runelite.client.util.HotkeyListener; - -@PluginDescriptor( - name = "Schedulable Example", - description = "Designed for use with the scheduler and testing its features", - tags = {"microbot", "woodcutting", "combat", "scheduler", "condition"}, - enabledByDefault = false -) -@Slf4j -public class SchedulableExamplePlugin extends Plugin implements SchedulablePlugin, KeyListener { - - - - @Inject - private SchedulableExampleConfig config; - - @Inject - private Client client; - - @Inject - private KeyManager keyManager; - - @Inject - private OverlayManager overlayManager; - - @Inject - private SchedulableExampleOverlay overlay; - - @Provides - SchedulableExampleConfig provideConfig(ConfigManager configManager) { - if (configManager == null) { - log.warn("ConfigManager is null, cannot provide SchedulableExampleConfig"); - return null; - } - return configManager.getConfig(SchedulableExampleConfig.class); - } - - /** - * Gets the plugin configuration. - * - * @return The SchedulableExampleConfig instance - */ - public SchedulableExampleConfig getConfig() { - return config; - } - - private SchedulableExampleScript script; - private WorldPoint lastLocation = null; - private int itemsCollected = 0; - - private LockCondition lockCondition; - private LogicalCondition startCondition = null; - private LogicalCondition stopCondition = null; - - - // Pre/Post Schedule Tasks and Requirements - private SchedulableExamplePrePostScheduleRequirements prePostScheduleRequirements = null; - private SchedulableExamplePrePostScheduleTasks prePostScheduleTasks = null; - - // HotkeyListener for the area marking - private final HotkeyListener areaHotkeyListener = new HotkeyListener(() -> config.areaMarkHotkey()) { - @Override - public void hotkeyPressed() { - toggleCustomArea(); - } - }; - - // HotkeyListener for testing PluginScheduleEntryMainTaskFinishedEvent - private final HotkeyListener finishPluginSuccessHotkeyListener = new HotkeyListener(() -> config.finishPluginSuccessfulHotkey()) { - @Override - public void hotkeyPressed() { - String reason = config.finishReason() + " (success)"; - boolean success = true; - log.info("\nManually triggering plugin finish: reason='{}', success={}", reason, success); - Microbot.getClientThread().invokeLater( () -> {reportFinished(reason, success); return true;}); - } - }; - // HotkeyListener for testing PluginScheduleEntryMainTaskFinishedEvent - private final HotkeyListener finishPluginNotSuccessHotkeyListener = new HotkeyListener(() -> config.finishPluginNotSuccessfulHotkey()) { - @Override - public void hotkeyPressed() { - String reason = config.finishReason()+ " (not success)"; - boolean success = false; - log.info("\nManually triggering plugin finish: reason='{}', success={}", reason, success); - Microbot.getClientThread().invokeLater( () -> {reportFinished(reason, success); return true;}); - } - }; - - // HotkeyListener for toggling the lock condition - private final HotkeyListener lockConditionHotkeyListener = new HotkeyListener(() -> config.lockConditionHotkey()) { - @Override - public void hotkeyPressed() { - log.info("Toggling lock condition for plugin: {}", getName()); - if (stopCondition == null || stopCondition.getConditions().isEmpty()) { - log.warn("Stop condition is not initialized. Cannot toggle lock condition."); - return; - } - boolean newState = toggleLock((Condition)(stopCondition)); - log.info("\n\tLock condition toggled: {}", newState ? "LOCKED - " + config.lockDescription() : "UNLOCKED"); - } - }; - - // HotkeyListener for testing pre-schedule tasks - private final HotkeyListener testPreScheduleTasksHotkeyListener = new HotkeyListener(() -> config.testPreScheduleTasksHotkey()) { - @Override - public void hotkeyPressed() { - // Initialize Pre/Post Schedule Requirements and Tasks if needed - if (config.enablePrePostRequirements()) { - if (getPrePostScheduleTasks() == null) { - log.info("Initializing Pre/Post failed "); - return; - } - // Test only pre-schedule tasks - SchedulableExamplePlugin.this.runPreScheduleTasks(); - - - } else { - log.info("Pre/Post Schedule Requirements are disabled in configuration"); - } - } - }; - - // HotkeyListener for testing post-schedule tasks - private final HotkeyListener testPostScheduleTasksHotkeyListener = new HotkeyListener(() -> config.testPostScheduleTasksHotkey()) { - @Override - public void hotkeyPressed() { - // Initialize Pre/Post Schedule Requirements and Tasks if needed - if (config.enablePrePostRequirements()) { - if (getPrePostScheduleTasks() == null) { - log.info("Initializing Pre/Post failed "); - return; - } - - // Test only post-schedule tasks - runPostScheduleTasks(); - } else { - log.info("Pre/Post Schedule Requirements are disabled in configuration"); - } - } - }; - - // HotkeyListener for cancelling tasks - private final HotkeyListener cancelTasksHotkeyListener = new HotkeyListener(() -> config.cancelTasksHotkey()) { - @Override - public void hotkeyPressed() { - log.info("Cancel tasks hotkey pressed for plugin: {}", getName()); - - if (prePostScheduleTasks != null) { - if (prePostScheduleTasks.isPreScheduleRunning()) { - prePostScheduleTasks.cancelPreScheduleTasks(); - log.info("Cancelled pre-schedule tasks"); - } else if (prePostScheduleTasks.isPostScheduleRunning()) { - prePostScheduleTasks.cancelPostScheduleTasks(); - log.info("Cancelled post-schedule tasks"); - } else { - log.info("No pre/post schedule tasks are currently running"); - } - - // Reset the execution state to allow fresh start - prePostScheduleTasks.reset(); - log.info("Reset pre/post schedule tasks execution state"); - } else { - log.info("No pre/post schedule tasks manager initialized"); - } - } - }; - - @Override - protected void startUp() { - loadLastLocation(); - this.script = new SchedulableExampleScript(); - - - - keyManager.registerKeyListener(this); - - // Register the hotkey listeners - keyManager.registerKeyListener(areaHotkeyListener); - keyManager.registerKeyListener(finishPluginSuccessHotkeyListener); - keyManager.registerKeyListener(finishPluginNotSuccessHotkeyListener); - keyManager.registerKeyListener(lockConditionHotkeyListener); - keyManager.registerKeyListener(testPreScheduleTasksHotkeyListener); - keyManager.registerKeyListener(testPostScheduleTasksHotkeyListener); - keyManager.registerKeyListener(cancelTasksHotkeyListener); - - // Add the overlay - overlayManager.add(overlay); - boolean scheduleMode = Microbot.getConfigManager().getConfiguration( - "SchedulableExample", - "scheduleMode", - Boolean.class - ); - log.info("\n\tSchedulable Example plugin started\n\t -In SchedulerMode:{}\n\t -Press {} to test the PluginScheduleEntryMainTaskFinishedEvent successfully\n\t -Press {} to test the PluginScheduleEntryMainTaskFinishedEvent unsuccessfully\n\t -Use {} to toggle the lock condition (prevents the plugin from being stopped)\n\t -Use {} to test Pre-Schedule Tasks functionality\n\t -Use {} to test Post-Schedule Tasks functionality\n\t -Use {} to cancel running pre/post schedule tasks", - scheduleMode, - config.finishPluginSuccessfulHotkey(), - config.finishPluginNotSuccessfulHotkey(), - config.lockConditionHotkey(), - config.testPreScheduleTasksHotkey(), - config.testPostScheduleTasksHotkey(), - config.cancelTasksHotkey()); - - } - - /** - * Override the default event handler to start the script properly after pre-schedule tasks. - * This follows the same pattern as runPreScheduleTasks() but integrates with the scheduler. - */ - @Subscribe - public void onPluginScheduleEntryPreScheduleTaskEvent(PluginScheduleEntryPreScheduleTaskEvent event) { - - if (event.getPlugin() != this) { - return; // Not for this plugin - } - - log.info("Received PluginScheduleEntryPreScheduleTaskEvent for SchedulableExample plugin"); - - if (prePostScheduleTasks != null && event.isSchedulerControlled() && !prePostScheduleTasks.isPreTaskComplete()) { - // Plugin has pre/post tasks and is under scheduler control - log.info("SchedulableExample starting with pre-schedule tasks from scheduler"); - try { - // Execute pre-schedule tasks with callback to start the script - runPreScheduleTasks(); - } catch (Exception e) { - log.error("Error during Pre-Schedule Tasks for SchedulableExample", e); - } - } - } - - - @Override - protected void shutDown() { - // Clean up PrePostScheduleTasks if initialized - if (prePostScheduleTasks != null) { - try { - prePostScheduleTasks.close(); - log.info("PrePostScheduleTasks cleaned up successfully"); - } catch (Exception e) { - log.error("Error cleaning up PrePostScheduleTasks", e); - } finally { - prePostScheduleTasks = null; - prePostScheduleRequirements = null; - } - } - - if (script != null && script.isRunning()) { - saveCurrentLocation(); - script.shutdown(); - } - unlock((Condition)(stopCondition)); - keyManager.unregisterKeyListener(this); - keyManager.unregisterKeyListener(areaHotkeyListener); - keyManager.unregisterKeyListener(finishPluginSuccessHotkeyListener); - keyManager.unregisterKeyListener(finishPluginNotSuccessHotkeyListener); - keyManager.unregisterKeyListener(lockConditionHotkeyListener); - keyManager.unregisterKeyListener(testPreScheduleTasksHotkeyListener); - keyManager.unregisterKeyListener(testPostScheduleTasksHotkeyListener); - keyManager.unregisterKeyListener(cancelTasksHotkeyListener); - - // Remove the overlay - overlayManager.remove(overlay); - } - - /** - * Toggles the custom area state and updates configuration - */ - private void toggleCustomArea() { - if (!Microbot.isLoggedIn()) { - log.info("Cannot toggle custom area: Not logged in"); - return; - } - - boolean isActive = config.customAreaActive(); - - if (isActive) { - // Clear the custom area - config.setCustomAreaActive(false); - config.setCustomAreaCenter(null); - log.info("Custom area removed"); - } else { - // Create new custom area at current position - WorldPoint currentPos = null; - if (Microbot.isLoggedIn()){ - currentPos = Rs2Player.getWorldLocation(); - } - if (currentPos != null) { - config.setCustomAreaCenter(currentPos); - config.setCustomAreaActive(true); - log.info("Custom area created at: " + currentPos.toString() + " with radius: " + config.customAreaRadius()); - } - } - } - - /** - * Checks if the player is in the custom area - */ - public boolean isPlayerInCustomArea() { - if (!config.customAreaActive() || config.customAreaCenter() == null) { - return false; - } - if (!Microbot.isLoggedIn()) { - return false; - } - WorldPoint currentPos = Rs2Player.getWorldLocation(); - if (currentPos == null) { - return false; - } - - WorldPoint center = config.customAreaCenter(); - int radius = config.customAreaRadius(); - - // Check if player is within radius of the center point and on the same plane - return (currentPos.getPlane() == center.getPlane() && - currentPos.distanceTo(center) <= radius); - } - - private void loadLastLocation() { - WorldPoint savedLocation = config.lastLocation(); - if (savedLocation == null) { - log.warn("No saved location found in config."); - if (Microbot.isLoggedIn()){ - this.lastLocation = Rs2Player.getWorldLocation(); - } - return; - } - this.lastLocation = savedLocation; - } - - private void saveCurrentLocation() { - if (client.getLocalPlayer() != null) { - WorldPoint currentLoc = client.getLocalPlayer().getWorldLocation(); - config.setLastLocation(currentLoc); - } - } - - private LogicalCondition createStopCondition() { - // Create an OR condition - we'll stop when ANY of the enabled conditions are met - OrCondition orCondition = new OrCondition(); - if (this.lockCondition == null) { - this.lockCondition = new LockCondition("Locked because the Plugin "+getName()+" is in a critical operation", false,true); //ensure unlock on shutdown of the plugin ! - } - - // Add enabled conditions based on configuration - if (config.enableTimeCondition()) { - orCondition.addCondition(createTimeCondition()); - } - - if (config.enableLootItemCondition()) { - orCondition.addCondition(createLootItemCondition()); - } - - if (config.enableGatheredResourceCondition()) { - orCondition.addCondition(createGatheredResourceCondition()); - } - - if (config.enableProcessItemCondition()) { - orCondition.addCondition(createProcessItemCondition()); - } - - if (config.enableNpcKillCountCondition()) { - orCondition.addCondition(createNpcKillCountCondition()); - } - - // If no conditions were added, add a fallback time condition - if (orCondition.getConditions().isEmpty()) { - log.warn("No stop conditions were enabled. Adding default time condition of 5 minutes."); - orCondition.addCondition(IntervalCondition.createRandomized(Duration.ofMinutes(5), Duration.ofMinutes(5))); - } - - // Add a lock condition that can be toggled manually - // NOTE: This condition uses AND logic with the other conditions since it's in an AND condition - AndCondition andCondition = new AndCondition(); - //andCondition.addCondition(orCondition); - andCondition.addCondition(lockCondition); - - List all = andCondition.findAllLockConditions(); - log.info("\nCreated stop condition: \n{}"+"\nFound {} lock conditions in stop condition: {}", andCondition.getDescription(), all.size(), all); - - return andCondition; - - } - - - - /** - * Tests only the pre-schedule tasks functionality. - * This method demonstrates how pre-schedule tasks work and logs the results. - */ - private void runPreScheduleTasks() { - if (prePostScheduleTasks != null && !prePostScheduleTasks.isPreTaskRunning() && !prePostScheduleTasks.isPreTaskComplete()) { - executePreScheduleTasks(() -> { - log.info("Pre-Schedule Tasks completed successfully for SchedulableExample"); - // Ensure script is initialized - if (this.script == null) { - this.script = new SchedulableExampleScript(); - } - if (this.script.isRunning()) { - this.script.shutdown(); - } - // Start the actual script after pre-schedule tasks are done - this.script.run(config, lastLocation); - }); - } - - } - private void runPostScheduleTasks( ){ - if (prePostScheduleTasks != null && !prePostScheduleTasks.isPostScheduleRunning() && !prePostScheduleTasks.isPostTaskComplete()) { - executePostScheduleTasks(()->{ - if( this.script != null && this.script.isRunning()) { - this.script.shutdown(); - } - }); - }else { - log.info("Post-Schedule Tasks already completed or running for SchedulableExample"); - } - - - } - - - private LogicalCondition createStartCondition() { - try { - // Default to no start conditions (always allowed to start) - if (!config.enableLocationStartCondition()) { - return null; - } - - // Create a logical condition for start conditions - LogicalCondition startCondition = null; - - // Create location-based condition based on selected type - if (config.locationStartType() == SchedulableExampleConfig.LocationStartType.BANK) { - // Bank-based start condition - BankLocation selectedBank = config.bankStartLocation(); - int distance = config.bankDistance(); - - // Create condition using bank location - startCondition = new OrCondition(); // Use OR to allow multiple possible conditions - Condition bankCondition = LocationCondition.atBank(selectedBank, distance); - ((OrCondition) startCondition).addCondition(bankCondition); - - log.debug("Created bank start condition: " + selectedBank.name() + " within " + distance + " tiles"); - } else if (config.locationStartType() == SchedulableExampleConfig.LocationStartType.CUSTOM_AREA) { - // Custom area start condition - if (config.customAreaActive() && config.customAreaCenter() != null) { - WorldPoint center = config.customAreaCenter(); - int radius = config.customAreaRadius(); - - // Create area condition centered on the saved point - startCondition = new OrCondition(); - AreaCondition areaCondition = LocationCondition.createArea( - "Custom Start Area", - center, - radius * 2, // Width (diameter) - radius * 2 // Height (diameter) - ); - ((OrCondition) startCondition).addCondition(areaCondition); - - log.debug("Created custom area start condition at: " + center + " with radius: " + radius); - } else { - log.warn("Custom area start condition selected but no area is defined"); - // Return null to indicate no start condition (always allowed to start) - return null; - } - } - - return startCondition; - } catch (Exception e) { - log.error("Error creating start condition", e); - e.printStackTrace(); - return new OrCondition(); // Fallback to no conditions - } - } - /** - * Returns a logical condition that determines when the plugin is allowed to start - */ - @Override - public LogicalCondition getStartCondition() { - if (this.startCondition == null) { - this.startCondition = createStartCondition(); - } - return this.startCondition; - - } - @Override - public LogicalCondition getStopCondition() { - // Create a new stop condition - if (this.stopCondition == null) { - this.stopCondition = createStopCondition(); - } - return this.stopCondition; - } - @Override - public AbstractPrePostScheduleTasks getPrePostScheduleTasks() { - SchedulableExampleConfig config = provideConfig(Microbot.getConfigManager()); - if (prePostScheduleRequirements == null || prePostScheduleTasks == null) { - if(Microbot.getClient().getGameState() != GameState.LOGGED_IN) { - log.debug("Schedulable Example - Cannot provide pre/post schedule tasks - not logged in"); - return null; // Return null if not logged in - } - log.info("Initializing Pre/Post Schedule Requirements and Tasks..."); - this.prePostScheduleRequirements = new SchedulableExamplePrePostScheduleRequirements(config); - this.prePostScheduleTasks = new SchedulableExamplePrePostScheduleTasks(this, keyManager,prePostScheduleRequirements); - // Log the requirements status - if (prePostScheduleRequirements.isInitialized()) log.info("\nPrePostScheduleRequirements initialized:\n{}", prePostScheduleRequirements.getDetailedDisplay()); - } - // Return the pre/post schedule tasks instance - return this.prePostScheduleTasks; - } - - /** - * Creates a time-based condition based on config settings - */ - private Condition createTimeCondition() { - // Existing implementation - int minMinutes = config.minRuntime(); - int maxMinutes = config.maxRuntime(); - - return IntervalCondition.createRandomized( - Duration.ofMinutes(minMinutes), - Duration.ofMinutes(maxMinutes) - ); - - } - - /** - * Creates a loot item condition based on config settings - */ - private LogicalCondition createLootItemCondition() { - // Parse the comma-separated list of items - List lootItemsList = parseItemList(config.lootItems()); - if (lootItemsList.isEmpty()) { - log.warn("No valid loot items specified, defaulting to 'Logs'"); - lootItemsList.add("Logs"); - } - - boolean andLogical = config.itemsToLootLogical(); - int minLootItems = config.minItems(); - int maxLootItems = config.maxItems(); - - // Create randomized targets for each item - List minLootItemPerPattern = new ArrayList<>(); - List maxLootItemPerPattern = new ArrayList<>(); - - for (int i = 0; i < lootItemsList.size(); i++) { - int minLoot = Rs2Random.between(minLootItems, maxLootItems); - int maxLoot = Rs2Random.between(minLoot, maxLootItems); - - // Ensure max is not less than min - if (maxLoot < minLoot) { - maxLoot = maxLootItems; - } - - minLootItemPerPattern.add(minLoot); - maxLootItemPerPattern.add(maxLoot); - } - - boolean includeNoted = config.includeNoted(); - boolean allowNoneOwner = config.allowNoneOwner(); - - // Create the appropriate logical condition based on config - if (andLogical) { - return LootItemCondition.createAndCondition( - lootItemsList, - minLootItemPerPattern, - maxLootItemPerPattern, - includeNoted, - allowNoneOwner - ); - } else { - return LootItemCondition.createOrCondition( - lootItemsList, - minLootItemPerPattern, - maxLootItemPerPattern, - includeNoted, - allowNoneOwner - ); - } - } - - /** - * Creates a gathered resource condition based on config settings - */ - private LogicalCondition createGatheredResourceCondition() { - // Parse the comma-separated list of resources - List resourcesList = parseItemList(config.gatheredResources()); - if (resourcesList.isEmpty()) { - log.warn("No valid resources specified, defaulting to 'logs'"); - resourcesList.add("logs"); - } - - boolean andLogical = config.resourcesLogical(); - int minResources = config.minResources(); - int maxResources = config.maxResources(); - boolean includeNoted = config.includeResourceNoted(); - - // Create target lists - List minResourcesPerItem = new ArrayList<>(); - List maxResourcesPerItem = new ArrayList<>(); - - for (int i = 0; i < resourcesList.size(); i++) { - int minCount = Rs2Random.between(minResources, maxResources); - int maxCount = Rs2Random.between(minCount, maxResources); - - // Ensure max is not less than min - if (maxCount < minCount) { - maxCount = maxResources; - } - - minResourcesPerItem.add(minCount); - maxResourcesPerItem.add(maxCount); - } - - // Create the appropriate logical condition - if (andLogical) { - return GatheredResourceCondition.createAndCondition( - resourcesList, - minResourcesPerItem, - maxResourcesPerItem, - includeNoted - ); - } else { - return GatheredResourceCondition.createOrCondition( - resourcesList, - minResourcesPerItem, - maxResourcesPerItem, - includeNoted - ); - } - } - - /** - * Creates a process item condition based on config settings - */ - private Condition createProcessItemCondition() { - ProcessItemCondition.TrackingMode trackingMode; - - // Map config enum to condition enum - switch (config.trackingMode()) { - case SOURCE_CONSUMPTION: - trackingMode = ProcessItemCondition.TrackingMode.SOURCE_CONSUMPTION; - break; - case TARGET_PRODUCTION: - trackingMode = ProcessItemCondition.TrackingMode.TARGET_PRODUCTION; - break; - case EITHER: - trackingMode = ProcessItemCondition.TrackingMode.EITHER; - break; - case BOTH: - trackingMode = ProcessItemCondition.TrackingMode.BOTH; - break; - default: - trackingMode = ProcessItemCondition.TrackingMode.SOURCE_CONSUMPTION; - } - - List sourceItemsList = parseItemList(config.sourceItems()); - List targetItemsList = parseItemList(config.targetItems()); - - int minProcessed = config.minProcessedItems(); - int maxProcessed = config.maxProcessedItems(); - - // Create the appropriate process item condition based on tracking mode - if (trackingMode == ProcessItemCondition.TrackingMode.SOURCE_CONSUMPTION) { - // If tracking source consumption - if (!sourceItemsList.isEmpty()) { - return ProcessItemCondition.forConsumption(sourceItemsList.get(0), - Rs2Random.between(minProcessed, maxProcessed)); - } - } else if (trackingMode == ProcessItemCondition.TrackingMode.TARGET_PRODUCTION) { - // If tracking target production - if (!targetItemsList.isEmpty()) { - return ProcessItemCondition.forProduction(targetItemsList.get(0), - Rs2Random.between(minProcessed, maxProcessed)); - } - } else if (trackingMode == ProcessItemCondition.TrackingMode.BOTH) { - // If tracking both source and target - if (!sourceItemsList.isEmpty() && !targetItemsList.isEmpty()) { - return ProcessItemCondition.forRecipe( - sourceItemsList.get(0), 1, - targetItemsList.get(0), 1, - Rs2Random.between(minProcessed, maxProcessed) - ); - } - } - - // Default fallback - log.warn("Invalid process item configuration, using default"); - return ProcessItemCondition.forConsumption("logs", 10); - } - /** - * Creates an NPC kill count condition based on config settings - */ - private LogicalCondition createNpcKillCountCondition() { - // Parse the comma-separated list of NPC names - List npcNamesList = parseItemList(config.npcNames()); - if (npcNamesList.isEmpty()) { - log.warn("No valid NPC names specified, defaulting to 'goblin'"); - npcNamesList.add("goblin"); - } - - boolean andLogical = config.npcLogical(); - int minKills = config.minKills(); - int maxKills = config.maxKills(); - boolean killsPerType = config.killsPerType(); - - // If we're counting per NPC type, create target lists for each NPC - if (killsPerType) { - List minKillsPerNpc = new ArrayList<>(); - List maxKillsPerNpc = new ArrayList<>(); - - for (int i = 0; i < npcNamesList.size(); i++) { - int minKillCount = Rs2Random.between(minKills, maxKills); - int maxKillCount = Rs2Random.between(minKillCount, maxKills); - - // Ensure max is not less than min - if (maxKillCount < minKillCount) { - maxKillCount = maxKills; - } - - minKillsPerNpc.add(minKillCount); - maxKillsPerNpc.add(maxKillCount); - } - - // Create the appropriate logical condition based on config - if (andLogical) { - return NpcKillCountCondition.createAndCondition( - npcNamesList, - minKillsPerNpc, - maxKillsPerNpc - ); - } else { - return NpcKillCountCondition.createOrCondition( - npcNamesList, - minKillsPerNpc, - maxKillsPerNpc - ); - } - } - // If we're counting total kills across all NPC types - else { - // Generate a single randomized kill count target - int targetMin = minKills; - int targetMax = maxKills; - - // Create multiple individual conditions with same ranges - if (andLogical) { - return NpcKillCountCondition.createAndCondition( - npcNamesList, - targetMin, - targetMax - ); - } else { - return NpcKillCountCondition.createOrCondition( - npcNamesList, - targetMin, - targetMax - ); - } - } - } - - /** - * Helper method to parse a comma-separated list of items - */ - private List parseItemList(String itemsString) { - List itemsList = new ArrayList<>(); - if (itemsString != null && !itemsString.isEmpty()) { - String[] itemsArray = itemsString.split(","); - for (String item : itemsArray) { - String trimmedItem = item.trim(); - try { - // Validate regex pattern - java.util.regex.Pattern.compile(trimmedItem); - itemsList.add(trimmedItem); - log.debug("Valid item pattern found: {}", trimmedItem); - } catch (java.util.regex.PatternSyntaxException e) { - log.warn("Invalid regex pattern: '{}' - {}", trimmedItem, e.getMessage()); - } - } - } - return itemsList; - } - @Override - public ConfigDescriptor getConfigDescriptor() { - if (Microbot.getConfigManager() == null) { - return null; - } - SchedulableExampleConfig conf = Microbot.getConfigManager().getConfig(SchedulableExampleConfig.class); - return Microbot.getConfigManager().getConfigDescriptor(conf); - } - @Override - public void onStopConditionCheck() { - // Update item count when condition is checked - if (script != null) { - itemsCollected = script.getLogsCollected(); - } - } - - // Method for the scheduler to check progress - public int getItemsCollected() { - return itemsCollected; - } - @Subscribe - public void onConfigChanged(ConfigChanged event) - { - final ConfigDescriptor desc = getConfigDescriptor(); - if (desc != null && desc.getGroup() != null && event.getGroup().equals(desc.getGroup().value())) { - - this.startCondition = null; - this.stopCondition = null; - log.info( - "Config change detected for {}: {}={}, config group {}", - getName(), - event.getGroup(), - event.getKey(), - desc.getGroup().value() - ); - if (config.enablePrePostRequirements()) { - if (prePostScheduleTasks != null && !prePostScheduleTasks.isExecuting()) { - if (prePostScheduleRequirements != null) { - prePostScheduleRequirements.setConfig(config); - prePostScheduleRequirements.reset(); - } - // prePostScheduleTasks.reset(); when we allow reexecution of pre/post-schedule tasks on config change - log.info("PrePostScheduleRequirements initialized:\n{}", prePostScheduleRequirements.getDetailedDisplay()); - } - } else { - log.info("Pre/Post Schedule Requirements are disabled in configuration"); - } - } - } - - @Subscribe - public void onGameStateChanged(GameStateChanged event) { - if (event.getGameState() == GameState.LOGGED_IN) { - log.info("GameState changed to LOGGED_IN"); - getPrePostScheduleTasks(); - if( prePostScheduleTasks != null - && prePostScheduleTasks.isScheduleMode() && - !prePostScheduleTasks.isPreTaskComplete() && - !prePostScheduleTasks.isPreScheduleRunning()) { - log.info("Plugin is running in Scheduler Mode - waiting for scheduler to start pre-schedule tasks"); - } else { - log.info("Plugin is running in normal mode"); - } - }else if (event.getGameState() == GameState.LOGIN_SCREEN) { - - } - } - @Override - @Subscribe - public void onPluginScheduleEntryPostScheduleTaskEvent(PluginScheduleEntryPostScheduleTaskEvent event) { - // Save location before stopping - if (event.getPlugin() == this) { - WorldPoint currentLocation = null; - if (Microbot.isLoggedIn()) { - currentLocation = Rs2Player.getWorldLocation(); - } - if ( Microbot.getConfigManager() == null) { - log.warn("Cannot save last location - ConfigManager or current location is null"); - return; - } - Microbot.getConfigManager().setConfiguration("SchedulableExample", "lastLocation", currentLocation); - log.info("Scheduling stop for plugin: {}", event.getPlugin().getClass().getSimpleName()); - - runPostScheduleTasks(); - /*try { - Microbot.log("Successfully exited SchedulerExamplePlugin - stopping plugin"); - Microbot.getClientThread().invokeLater(() -> { - Microbot.stopPlugin(this); - return true; - }); - } catch (Exception ex) { - Microbot.log("Error during safe exit: " + ex.getMessage()); - Microbot.getClientThread().invokeLater(() -> { - Microbot.stopPlugin(this); - return true; - }); - }*/ - - - - - // Schedule the stop operation on the client thread - //Microbot.getClientThread().invokeLater(() -> { - // try { - // Microbot.getPluginManager().setPluginEnabled(this, false); - // Microbot.getPluginManager().stopPlugin(this); - // } catch (Exception e) { - // log.error("Error stopping plugin", e); - // } - // }); - } - } - - - - - - - @Override - public void keyTyped(KeyEvent e) { - // Not used - } - - @Override - public void keyPressed(KeyEvent e) { - // Movement handling has been moved to VoxQoL plugin - // This plugin now only handles its core scheduling functionality - } - - - - - - - - - - - - @Override - public void keyReleased(KeyEvent e) { - // Not used but required by the KeyListener interface - } - - -} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/VoxPlugins/schedulable/example/SchedulableExamplePrePostScheduleRequirements.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/VoxPlugins/schedulable/example/SchedulableExamplePrePostScheduleRequirements.java deleted file mode 100644 index 58183a098a6..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/VoxPlugins/schedulable/example/SchedulableExamplePrePostScheduleRequirements.java +++ /dev/null @@ -1,640 +0,0 @@ - -package net.runelite.client.plugins.microbot.VoxPlugins.schedulable.example; - -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; -import net.runelite.api.EquipmentInventorySlot; -import net.runelite.api.coords.WorldArea; -import net.runelite.api.coords.WorldPoint; -import net.runelite.api.gameval.ItemID; -import net.runelite.client.plugins.microbot.VoxPlugins.schedulable.example.enums.UnifiedLocation; -import net.runelite.client.plugins.microbot.pluginscheduler.tasks.requirements.PrePostScheduleRequirements; -import net.runelite.client.plugins.microbot.pluginscheduler.tasks.requirements.data.ItemRequirementCollection; -import net.runelite.client.plugins.microbot.pluginscheduler.tasks.requirements.enums.RequirementPriority; -import net.runelite.client.plugins.microbot.pluginscheduler.tasks.requirements.enums.RequirementType; -import net.runelite.client.plugins.microbot.pluginscheduler.tasks.requirements.enums.TaskContext; -import net.runelite.client.plugins.microbot.pluginscheduler.tasks.requirements.requirement.SpellbookRequirement; -import net.runelite.client.plugins.microbot.pluginscheduler.tasks.requirements.requirement.collection.LootRequirement; -import net.runelite.client.plugins.microbot.pluginscheduler.tasks.requirements.requirement.conditional.ConditionalRequirement; -import net.runelite.client.plugins.microbot.pluginscheduler.tasks.requirements.requirement.item.ItemRequirement; -import net.runelite.client.plugins.microbot.pluginscheduler.tasks.requirements.requirement.location.LocationRequirement; -import net.runelite.client.plugins.microbot.pluginscheduler.tasks.requirements.requirement.logical.OrRequirement; -import net.runelite.client.plugins.microbot.pluginscheduler.tasks.requirements.requirement.shop.ShopItemRequirement; -import net.runelite.client.plugins.microbot.pluginscheduler.tasks.requirements.requirement.shop.ShopRequirement; -import net.runelite.client.plugins.microbot.pluginscheduler.tasks.requirements.requirement.shop.models.ShopOperation; -import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; -import net.runelite.client.plugins.microbot.util.bank.enums.BankLocation; -import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; -import net.runelite.client.plugins.microbot.util.grandexchange.models.TimeSeriesInterval; -import net.runelite.client.plugins.microbot.util.grounditem.models.Rs2SpawnLocation; -import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; -import net.runelite.client.plugins.microbot.util.magic.Rs2Spellbook; -import net.runelite.client.plugins.microbot.util.magic.Rs2Staff; -import net.runelite.client.plugins.microbot.util.magic.Runes; -import net.runelite.client.plugins.microbot.util.shop.StoreLocations; -import net.runelite.client.plugins.microbot.util.shop.models.Rs2ShopItem; -import net.runelite.client.plugins.microbot.util.shop.models.Rs2ShopType; - -import java.time.Duration; -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.function.BooleanSupplier; - -/** - * Example implementation of PrePostScheduleRequirements for the SchedulableExample plugin. - * This demonstrates configurable requirements that can be enabled/disabled via plugin configuration. - * - * Features demonstrated: - * - Spellbook requirement (optional Lunar spellbook) - * - Location requirements (pre: Varrock West, post: Grand Exchange) - * - Loot requirement (coins near Lumbridge) - * - Equipment requirement (Staff of Air) - * - Inventory requirement (10k coins) - */ -@Slf4j -public class SchedulableExamplePrePostScheduleRequirements extends PrePostScheduleRequirements { - @Setter - private SchedulableExampleConfig config; - - public SchedulableExamplePrePostScheduleRequirements(SchedulableExampleConfig config) { - super("SchedulableExample", "Testing", false); - this.config = config; - } - - /** - * Initialize requirements based on configuration settings. - */ - private boolean initializeConfigurableRequirements() { - if (config == null) { - return false; // Ensure config is initialized before proceeding - } - if (!config.enablePrePostRequirements()) { - return true; // Skip requirements if disabled - } - boolean success = true; - this.getRegistry().clear(); - // Configure spellbook requirements based on dropdown selection - if (!config.preScheduleSpellbook().isNone()) { - SpellbookRequirement preSpellbookRequirement = new SpellbookRequirement( - config.preScheduleSpellbook().getSpellbook(), - TaskContext.PRE_SCHEDULE, - RequirementPriority.MANDATORY, - 7, - "Pre-schedule spellbook: " + config.preScheduleSpellbook().getDisplayName() - ); - this.register(preSpellbookRequirement); - } - - if (!config.postScheduleSpellbook().isNone()) { - SpellbookRequirement postSpellbookRequirement = new SpellbookRequirement( - config.postScheduleSpellbook().getSpellbook(), - TaskContext.POST_SCHEDULE, - RequirementPriority.MANDATORY, - 7, - "Post-schedule spellbook: " + config.postScheduleSpellbook().getDisplayName() - ); - this.register(postSpellbookRequirement); - } - - - - // Configure location requirements based on dropdown selection - if (!config.preScheduleLocation().equals(UnifiedLocation.NONE)) { - LocationRequirement preLocationRequirement; - - // Handle different location types appropriately - switch (config.preScheduleLocation().getType()) { - case BANK: - preLocationRequirement = new LocationRequirement( - (BankLocation) config.preScheduleLocation().getOriginalLocationData(), - true, // use transportation - -1, // no specific world required - TaskContext.PRE_SCHEDULE, - RequirementPriority.MANDATORY - ); - break; - - case DEPOSIT_BOX: - case SLAYER_MASTER: - case FARMING: - case HUNTING: - default: - // For non-bank locations, use WorldPoint - preLocationRequirement = new LocationRequirement( - config.preScheduleLocation().getWorldPoint(), - config.preScheduleLocation().getDisplayName(), - true, // use members - 9, - true, // use transportation - -1, // no specific world required - TaskContext.PRE_SCHEDULE, - RequirementPriority.MANDATORY, - 1, - "Must be at " + config.preScheduleLocation().getDisplayName() + " to begin the schedule" - ); - break; - } - - this.register(preLocationRequirement); - } - - - if (!config.postScheduleLocation().equals(UnifiedLocation.NONE)) { - LocationRequirement postLocationRequirement; - - // Handle different location types appropriately - switch (config.postScheduleLocation().getType()) { - case BANK: - postLocationRequirement = new LocationRequirement( - (BankLocation) config.postScheduleLocation().getOriginalLocationData(), - true, // use transportation - -1, // no specific world required - TaskContext.POST_SCHEDULE, - RequirementPriority.MANDATORY - ); - break; - - case DEPOSIT_BOX: - case SLAYER_MASTER: - case FARMING: - case HUNTING: - default: - // For non-bank locations, use WorldPoint - postLocationRequirement = new LocationRequirement( - config.postScheduleLocation().getWorldPoint(), - config.postScheduleLocation().getDisplayName(), - true, - 10, // acceptable distance - true, // use transportation - -1, // no specific world required - TaskContext.POST_SCHEDULE, - RequirementPriority.MANDATORY - ); - break; - } - - this.register(postLocationRequirement); - } - - - // Loot requirement - Coins near Lumbridge Castle - if (config.enableLootRequirement()) { - List coinSpawns = Arrays.asList( - new WorldPoint(3205, 3229, 0), // Lumbridge Castle ground floor coin spawns - new WorldPoint(3207, 3229, 0), - new WorldPoint(3209, 3229, 0) - ); - - Rs2SpawnLocation coinsSpawnLocation = new Rs2SpawnLocation( - ItemID.COINS, - "Lumbridge Castle", - "Ground Floor - East Wing", - coinSpawns, - false, // Not members only - 0, // Ground floor - Duration.ofSeconds(30) // Respawn time - ); - - LootRequirement coinsLootRequirement = new LootRequirement( - ItemID.COINS, - 5, // Amount to collect - "Test coins collection from Lumbridge Castle spawns", - coinsSpawnLocation - ); - - register(coinsLootRequirement); - } - // Equipment requirement - Staff of Air - if (config.enableEquipmentRequirement()) { - register(new ItemRequirement( - ItemID.STAFF_OF_AIR, - 1, - EquipmentInventorySlot.WEAPON, - -2, // must be equipped (equipment slot enforced) - RequirementPriority.MANDATORY, - 6, - "Staff of Air for basic magic", - TaskContext.PRE_SCHEDULE - )); - ItemRequirementCollection.registerAmuletOfGlory(this, - RequirementPriority.MANDATORY, 4, - TaskContext.PRE_SCHEDULE, - true); - ItemRequirementCollection.registerRingOfDueling(this, - RequirementPriority.MANDATORY, 4, - TaskContext.PRE_SCHEDULE, - true); - ItemRequirementCollection.registerWoodcuttingAxes(this,RequirementPriority.MANDATORY, - TaskContext.PRE_SCHEDULE,-1); // -1 for no inventory slot means the axe can be placed in a any inventory slot, and also be equipped, -2 would mean it can only be equipped - ItemRequirementCollection.registerPickAxes(this, RequirementPriority.MANDATORY, - TaskContext.POST_SCHEDULE); - } - - // Inventory requirement - 10k coins - if (config.enableInventoryRequirement()) { - register(new ItemRequirement( - ItemID.COINS, - 10000, - -1, // inventory slot - RequirementPriority.RECOMMENDED, - 8, - "10,000 coins for general purposes", - TaskContext.PRE_SCHEDULE - )); - - - // Add some basic optional requirements that are always available - register( new ItemRequirement( - ItemID.AIRRUNE, - 50, - null, // no equipment slot - -1, // inventory slot - RequirementPriority.MANDATORY, - 5, - "Basic runes for magic", - TaskContext.PRE_SCHEDULE - )); - register( new ItemRequirement( - ItemID.WATERRUNE, - 50, - null, // no equipment slot - -1, // inventory slot - RequirementPriority.RECOMMENDED, - 5, - "Basic runes for magic", - TaskContext.PRE_SCHEDULE - )); - register( new ItemRequirement( - ItemID.EARTHRUNE, - 50, - null, // no equipment slot - -1, // inventory slot - RequirementPriority.RECOMMENDED, - 5, - "Basic runes for magic", - TaskContext.PRE_SCHEDULE - )); - - // Basic runes for magic - register(new ItemRequirement( - ItemID.LAWRUNE, - 10, - -1, // inventory slot - RequirementPriority.MANDATORY, - 5, - "Law runes for magic", - TaskContext.PRE_SCHEDULE - )); - - // ==================================================================== - // OR REQUIREMENT MODES DEMONSTRATION - // ==================================================================== - // This example demonstrates both OR requirement modes: - // - // 1. ANY_COMBINATION (default): Can fulfill with any combination of food items - // Example: 2 lobsters + 3 swordfish = 5 total food items ✓ - // - // 2. SINGLE_TYPE: Must fulfill with exactly one type of item - // Example: Exactly 5 lobsters OR 5 swordfish OR 5 monkfish ✓ - // But NOT 2 lobsters + 3 swordfish ✗ - // - // You can set the mode using: setOrRequirementMode(OrRequirementMode.SINGLE_TYPE) - // Default mode is ANY_COMBINATION for backward compatibility - // ==================================================================== - - // Basic food for emergencies (demonstrates OR requirement) - register(ItemRequirement.createOrRequirement( - Arrays.asList( - ItemID.LOBSTER, - ItemID.SWORDFISH, - ItemID.MONKFISH, - ItemID.BREAD - ), - 5, - null, // no equipment slot - -1, // inventory slot - RequirementPriority.MANDATORY, - 4, - "Basic food for health restoration (OR requirement - any combination or single type based on mode)", - TaskContext.PRE_SCHEDULE - )); - } - - // Shop requirement - Multi-Item Maple Bow Trading Example - // This demonstrates the unified stock management system with multi-item operations: - // - Single BUY requirement for both maple bow types from Grand Exchange (pre-schedule) - // - Single SELL requirement for both maple bow types to Brian's Archery Supplies (post-schedule) - - // Create shop items for both maple bow types - Rs2ShopItem mapleLongbowGEItem = Rs2ShopItem.createGEItem( - ItemID.MAPLE_LONGBOW, // Maple longbow ID (851) - 0.99, // sell at 0.99% of the GE price fast selling - 1.01 // buy at 101% of the GE price fast buying - ); - - Rs2ShopItem mapleShortbowGEItem = Rs2ShopItem.createGEItem( - ItemID.MAPLE_SHORTBOW, // Maple shortbow ID (853) - 0.99, // sell at 99% of the GE price lasted price - 1.01 // buy at 101% of the GE price lasted price - ); - - // Brian's Archery Supplies shop setup (Rimmington) - WorldPoint brianShopLocation = new WorldPoint(2957, 3204, 0); // Brian's Archery Supplies in Rimmington - WorldArea brianShopArea = new WorldArea(brianShopLocation.getX() - 4, brianShopLocation.getY() - 4, 8, 8, brianShopLocation.getPlane()); - - Rs2ShopItem mapleLongbowShopItem = new Rs2ShopItem( - ItemID.MAPLE_LONGBOW, // Maple longbow ID (851) - "Brian", // Shop NPC name - brianShopArea, // Shop area - Rs2ShopType.ARCHERY_SHOP, - 1.0, // 100% sell rate (from OSRS wiki) - 0.65, // 65% buy rate (from OSRS wiki - Brian buys at 65% value) - 2.0, // Change percent - Map.of(), // No quest requirements - false, // Not members only - "Brian's Archery Supplies in Rimmington", // Notes - Duration.ofMinutes(2), // Restock time: 2 minutes (from OSRS wiki) - 2 // Base stock: 2 maple longbows (from OSRS wiki) - ); - - Rs2ShopItem mapleShortbowShopItem = new Rs2ShopItem( - ItemID.MAPLE_SHORTBOW, // Maple shortbow ID (853) - "Brian", // Shop NPC name - brianShopArea, // Same shop area - Rs2ShopType.ARCHERY_SHOP, - 1.0, // 100% sell rate - 0.65, // 65% buy rate - 2.0, // Change percent - Map.of(), // No quest requirements - false, // Not members only - "Brian's Archery Supplies in Rimmington", // Notes - Duration.ofMinutes(2), // Restock time - 2 // Base stock: 2 maple shortbows - ); - - // Create multi-item shop requirements using the current Map-based system - Map geBuyItems = Map.of( - mapleLongbowGEItem, new ShopItemRequirement(mapleLongbowGEItem, - 2, - 1, - TimeSeriesInterval.FIVE_MINUTES, - true), // 2 longbows, flexible buying - mapleShortbowGEItem, new ShopItemRequirement(mapleShortbowGEItem, - 2, - 1, - TimeSeriesInterval.FIVE_MINUTES, - true) // 2 shortbows, flexible buying - ); - - // Brian's shop: base stock=2, can sell up to 10 per world (max stock=12) - Map brianSellItems = Map.of( - mapleLongbowShopItem, new ShopItemRequirement(mapleLongbowShopItem, 20, 10), // Sell up to 10 longbows per world - mapleShortbowShopItem, new ShopItemRequirement(mapleShortbowShopItem, 20, 10) // Sell up to 10 shortbows per world - ); - - // Single multi-item BUY requirement for Grand Exchange - ShopRequirement buyMapleBowsRequirement = new ShopRequirement( - geBuyItems, - ShopOperation.BUY, - RequirementType.SHOP, - RequirementPriority.MANDATORY, - 8, - "Buy maple bows (longbow x20, shortbow x20) from Grand Exchange (pre-schedule)", - TaskContext.PRE_SCHEDULE - ); - - // Single multi-item SELL requirement for Brian's shop - ShopRequirement sellMapleBowsRequirement = new ShopRequirement( - brianSellItems, - ShopOperation.SELL, - RequirementType.SHOP, - RequirementPriority.MANDATORY, - 8, - "Sell up to 10 maple bows per type to Brian's Archery Supplies (post-schedule)", - TaskContext.POST_SCHEDULE - ); - - if (config.enableShopRequirement()) { - // Register the unified multi-item shop requirements - this.register(buyMapleBowsRequirement); - this.register(sellMapleBowsRequirement); - } - - // Custom Shop Requirement - Hammer and Bucket from nearest General Store - if (config.externalRequirements()) { - // Find the nearest general store that has both hammer and bucket - StoreLocations nearestStore = StoreLocations.getNearestStoreWithAllItems(ItemID.HAMMER, ItemID.BUCKET_EMPTY); - log.info("Nearest general store with hammer and bucket: {}", nearestStore != null ? nearestStore.getName() : "None found"); - if (nearestStore != null) { - // Create shop items for hammer and bucket from the nearest general store - WorldArea storeArea = new WorldArea( - nearestStore.getLocation().getX() - 3, - nearestStore.getLocation().getY() - 3, - 6, 6, - nearestStore.getLocation().getPlane() - ); - - Rs2ShopItem hammerShopItem = new Rs2ShopItem( - ItemID.HAMMER, - nearestStore.getNpcName(), - storeArea, - nearestStore.getShopType(), - nearestStore.getSellRate(), // Standard sell rate for general stores - nearestStore.getBuyRate(), - nearestStore.getChangePercent(), - nearestStore.getQuestRequirements(), - nearestStore.isMembers(), - "Hammer from " + nearestStore.getName(), - Duration.ofMinutes(5), - 5 // Base stock for hammers - ); - - Rs2ShopItem bucketShopItem = new Rs2ShopItem( - ItemID.BUCKET_EMPTY, - nearestStore.getNpcName(), - storeArea, - nearestStore.getShopType(), - nearestStore.getSellRate(), // Standard sell rate for general stores - nearestStore.getBuyRate(), - nearestStore.getChangePercent(), - nearestStore.getQuestRequirements(), - nearestStore.isMembers(), - "Empty bucket from " + nearestStore.getName(), - Duration.ofMinutes(5), - 3 // Base stock for buckets - ); - - // Create shop item requirements - ShopItemRequirement hammerRequirement = new ShopItemRequirement(hammerShopItem, 1, 0); - ShopItemRequirement bucketRequirement = new ShopItemRequirement(bucketShopItem, 1, 0); - - // Create the unified shop requirement for buying both items - Map shopItems = new LinkedHashMap<>(); - shopItems.put(hammerShopItem, hammerRequirement); - shopItems.put(bucketShopItem, bucketRequirement); - - ShopRequirement buyToolsRequirement = new ShopRequirement( - shopItems, - ShopOperation.BUY, - RequirementType.SHOP, - RequirementPriority.MANDATORY, - 7, - "Buy hammer and bucket from nearest general store (" + nearestStore.getName() + ")", - TaskContext.PRE_SCHEDULE - ); - - // Add as custom requirement to test external requirement fulfillment (step 7) - this.addCustomRequirement(buyToolsRequirement, TaskContext.PRE_SCHEDULE); - - log.info("Added custom shop requirement for hammer and bucket from: {}", nearestStore.getName()); - } else { - log.warn("No general store found with both hammer and bucket items"); - success = false; // Mark as failure if no store found - } - } - - // === Alch Conditional Requirement Example === - if (config.enableConditionalItemRequirement()) { - - // Build fire staff requirements (all staves that provide fire runes) - List staffWithFireRunesRequirements = Arrays.stream(Rs2Staff.values()) - .filter(staff -> staff.getRunes().contains(Runes.FIRE) && staff != Rs2Staff.NONE) - .map(staff -> new ItemRequirement( - staff.getItemID(), - 1, - EquipmentInventorySlot.WEAPON, - -2, - RequirementPriority.MANDATORY, - 10, - staff.name() + " equipped", - TaskContext.PRE_SCHEDULE, - null, null, null, null, false)) - .collect(java.util.stream.Collectors.toList()); - // Helper to check if any fire staff is available in inventory or bank - BooleanSupplier hasFireStaffCondition = () -> hasFireStaffAvailable(staffWithFireRunesRequirements); - OrRequirement fireStaffOrRequirement = new OrRequirement( - RequirementPriority.MANDATORY, - "Any fire staff equipped", - TaskContext.PRE_SCHEDULE, - staffWithFireRunesRequirements.toArray(new ItemRequirement[0]) - ); - - ItemRequirement fireRuneRequirement = new ItemRequirement( - ItemID.FIRERUNE, - 5, - -1, - RequirementPriority.MANDATORY, - 10, - "Fire runes in inventory", - TaskContext.PRE_SCHEDULE - ); - - ConditionalRequirement alchConditionalRequirement = new ConditionalRequirement( - RequirementPriority.MANDATORY, - 10, - "Alching: Fire staff or fire runes", - TaskContext.PRE_SCHEDULE, - false - ); - alchConditionalRequirement - .addStep( - () -> { - try { - return !hasFireStaffCondition.getAsBoolean(); - } catch (Throwable t) { - return false; - } - }, - fireRuneRequirement, - "Fire runes in inventory (no fire staff available)" - ) - .addStep( - () -> { - try { - return hasFireStaffCondition.getAsBoolean(); - } catch (Throwable t) { - return false; - } - }, - fireStaffOrRequirement, - "Any fire staff equipped (fire staff available)" - ); - - SpellbookRequirement normalSpellbookRequirement = new SpellbookRequirement( - Rs2Spellbook.MODERN, - TaskContext.PRE_SCHEDULE, - RequirementPriority.MANDATORY, - 10, - "Normal spellbook required for High Alchemy" - ); - - ItemRequirement natureRuneRequirement = new ItemRequirement( - ItemID.NATURERUNE, - 1, - -2, - RequirementPriority.MANDATORY, - 10, - "Nature rune for alching", - TaskContext.PRE_SCHEDULE - ); - - this.register(alchConditionalRequirement); - this.register(normalSpellbookRequirement); - this.register(natureRuneRequirement); - } - - return success; // Return true if all requirements initialized successfully - } - - /** - * Checks if any fire staff from the requirements is available in inventory or bank. - */ - private static boolean hasFireStaffAvailable(List staffReqs) { - int[] staffIds = staffReqs.stream().mapToInt(ItemRequirement::getId).toArray(); - return Rs2Inventory.contains(staffIds) || Rs2Bank.hasItem(staffIds)|| Rs2Equipment.isWearing(staffIds); - } - - - - /** - * Initialize the base item requirements collection. - * This demonstrates basic equipment and inventory requirements. - */ - @Override - protected boolean initializeRequirements() { - if (config == null){ - return false; // Ensure config is initialized before proceeding - } - return initializeConfigurableRequirements(); - } - - /** - * Gets a display string showing which requirements are currently enabled. - * Useful for debugging and logging. - */ - public String getDetailedDisplay() { - StringBuilder sb = new StringBuilder(); - sb.append("SchedulableExample Requirements Status:\n"); - sb.append(" Pre/Post Requirements: ").append(config.enablePrePostRequirements() ? "ENABLED" : "DISABLED").append("\n"); - - if (config.enablePrePostRequirements()) { - // Show new dropdown configurations - sb.append(" - Pre-Schedule Spellbook: ").append(config.preScheduleSpellbook().getDisplayName()).append("\n"); - sb.append(" - Post-Schedule Spellbook: ").append(config.postScheduleSpellbook().getDisplayName()).append("\n"); - sb.append(" - Pre-Schedule Location: ").append(config.preScheduleLocation().getDisplayName()).append("\n"); - sb.append(" - Post-Schedule Location: ").append(config.postScheduleLocation().getDisplayName()).append("\n"); - - // Show legacy configurations - sb.append(" - Loot Requirement: ").append(config.enableLootRequirement() ? "ENABLED (Coins at Lumbridge)" : "DISABLED").append("\n"); - sb.append(" - Equipment Requirement: ").append(config.enableEquipmentRequirement() ? "ENABLED (Staff of Air)" : "DISABLED").append("\n"); - sb.append(" - Inventory Requirement: ").append(config.enableInventoryRequirement() ? "ENABLED (10k Coins)" : "DISABLED").append("\n"); - sb.append(" - Shop Requirement: ").append(config.enableShopRequirement() ? "ENABLED (Hammer & Bucket from nearest general store)" : "DISABLED").append("\n"); - } - sb.append(super.getDetailedDisplay()); - - return sb.toString(); - } - -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/VoxPlugins/schedulable/example/SchedulableExamplePrePostScheduleTasks.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/VoxPlugins/schedulable/example/SchedulableExamplePrePostScheduleTasks.java deleted file mode 100644 index 2dfef194ca7..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/VoxPlugins/schedulable/example/SchedulableExamplePrePostScheduleTasks.java +++ /dev/null @@ -1,186 +0,0 @@ -package net.runelite.client.plugins.microbot.VoxPlugins.schedulable.example; - -import java.util.concurrent.CompletableFuture; - -import lombok.extern.slf4j.Slf4j; -import net.runelite.client.input.KeyManager; -import net.runelite.client.plugins.microbot.pluginscheduler.condition.logical.LockCondition; -import net.runelite.client.plugins.microbot.pluginscheduler.tasks.AbstractPrePostScheduleTasks; -import net.runelite.client.plugins.microbot.pluginscheduler.tasks.requirements.PrePostScheduleRequirements; - -/** - * Implementation of AbstractPrePostScheduleTasks for the SchedulableExample plugin. - * This demonstrates how to implement custom pre and post schedule tasks with requirements. - * - * The class handles: - * - Pre-schedule tasks: Preparation based on configured requirements - * - Post-schedule tasks: Cleanup and resource management - * - Schedule mode detection for proper task execution - * - Requirement fulfillment through the associated PrePostScheduleRequirements - */ -@Slf4j -public class SchedulableExamplePrePostScheduleTasks extends AbstractPrePostScheduleTasks { - - private final SchedulableExamplePlugin examplePlugin; - private final SchedulableExamplePrePostScheduleRequirements requirements; - - /** - * Constructor for SchedulableExamplePrePostScheduleTasks. - * - * @param plugin The SchedulableExamplePlugin instance - * @param requirements The requirements collection for this plugin - */ - public SchedulableExamplePrePostScheduleTasks(SchedulableExamplePlugin plugin, KeyManager keyManager, SchedulableExamplePrePostScheduleRequirements requirements) { - super(plugin,keyManager); - this.examplePlugin = plugin; - this.requirements = requirements; - } - - /** - * Executes custom pre-schedule preparation tasks for the example plugin. - * This method is called AFTER standard requirement fulfillment (equipment, spellbook, location). - * The threading and safety infrastructure is handled by the parent class. - * - * @param lockCondition The lock condition to prevent interruption during critical operations - * @return true if custom preparation was successful, false otherwise - */ - @Override - protected boolean executeCustomPreScheduleTask(CompletableFuture preScheduledFuture, LockCondition lockCondition) { - StringBuilder logBuilder = new StringBuilder(); - logBuilder.append("SchedulableExample: Executing custom pre-schedule tasks...\n"); - - // Check if pre/post requirements are enabled - if (!examplePlugin.getConfig().enablePrePostRequirements()) { - logBuilder.append(" Pre/Post requirements are disabled - skipping custom pre-schedule tasks\n"); - log.info(logBuilder.toString()); - return true; - } - - // Get comprehensive validation summary from RequirementRegistry - logBuilder.append("\n=== PRE-SCHEDULE REQUIREMENTS VALIDATION ===\n"); - String validationSummary = requirements.getRegistry().getValidationSummary(net.runelite.client.plugins.microbot.pluginscheduler.tasks.requirements.enums.TaskContext.PRE_SCHEDULE); - logBuilder.append(validationSummary).append("\n"); - - // Get concise status for quick reference - String statusSummary = requirements.getRegistry().getValidationStatusSummary(net.runelite.client.plugins.microbot.pluginscheduler.tasks.requirements.enums.TaskContext.PRE_SCHEDULE); - logBuilder.append("Status Summary: ").append(statusSummary).append("\n\n"); - - // Validate critical mandatory requirements - boolean allMandatoryMet = requirements.getRegistry().getAllRequirements().stream() - .filter(req -> req.getPriority() == net.runelite.client.plugins.microbot.pluginscheduler.tasks.requirements.enums.RequirementPriority.MANDATORY) - .filter(req -> req.isPreSchedule()) - .allMatch(net.runelite.client.plugins.microbot.pluginscheduler.tasks.requirements.requirement.Requirement::isFulfilled); - - if (!allMandatoryMet) { - logBuilder.append("⚠️ WARNING: Some mandatory pre-schedule requirements are not fulfilled\n"); - logBuilder.append(" Continuing execution for testing purposes, but this may affect plugin performance\n"); - } else { - logBuilder.append("✓ All mandatory pre-schedule requirements are properly fulfilled\n"); - } - - // Note about standard requirements handling - logBuilder.append("\n--- Infrastructure Notes ---\n"); - logBuilder.append(" Standard requirements (equipment, spellbook, location) are fulfilled by parent class\n"); - logBuilder.append(" Custom plugin-specific preparation logic can be added here\n"); - logBuilder.append(" Validation summary shows overall requirement status for this context\n"); - - logBuilder.append("\nCustom pre-schedule tasks completed successfully"); - log.info(logBuilder.toString()); - - return true; - } - - /** - * Executes custom post-schedule cleanup tasks for the example plugin. - * This method is called BEFORE standard requirement fulfillment (location, spellbook restoration). - * The threading and safety infrastructure is handled by the parent class. - * - * @param lockCondition The lock condition to prevent interruption during critical operations - * @return true if custom cleanup was successful, false otherwise - */ - @Override - protected boolean executeCustomPostScheduleTask(CompletableFuture postScheduledFuture, LockCondition lockCondition) { - StringBuilder logBuilder = new StringBuilder(); - logBuilder.append("SchedulableExample: Executing custom post-schedule tasks...\n"); - - // Check if pre/post requirements are enabled - if (!examplePlugin.getConfig().enablePrePostRequirements()) { - logBuilder.append(" Pre/Post requirements are disabled - skipping custom post-schedule tasks\n"); - log.info(logBuilder.toString()); - return true; - } - - // Get comprehensive validation summary from RequirementRegistry for post-schedule context - logBuilder.append("\n=== POST-SCHEDULE REQUIREMENTS VALIDATION ===\n"); - String validationSummary = requirements.getRegistry().getValidationSummary(net.runelite.client.plugins.microbot.pluginscheduler.tasks.requirements.enums.TaskContext.POST_SCHEDULE); - logBuilder.append(validationSummary).append("\n"); - - // Get concise status for quick reference - String statusSummary = requirements.getRegistry().getValidationStatusSummary(net.runelite.client.plugins.microbot.pluginscheduler.tasks.requirements.enums.TaskContext.POST_SCHEDULE); - logBuilder.append("Status Summary: ").append(statusSummary).append("\n\n"); - - // Session completion summary - logBuilder.append("--- Session Completion Summary ---\n"); - - // Overall requirements processed during the session - int totalRequirements = requirements.getRegistry().getAllRequirements().size(); - int externalRequirements = requirements.getRegistry().getExternalRequirements(net.runelite.client.plugins.microbot.pluginscheduler.tasks.requirements.enums.TaskContext.BOTH).size(); - - logBuilder.append(" Total requirements processed: ").append(totalRequirements).append("\n"); - if (externalRequirements > 0) { - logBuilder.append(" External requirements: ").append(externalRequirements).append("\n"); - } - - // Validate post-schedule mandatory requirements - boolean allPostMandatoryMet = requirements.getRegistry().getAllRequirements().stream() - .filter(req -> req.getPriority() == net.runelite.client.plugins.microbot.pluginscheduler.tasks.requirements.enums.RequirementPriority.MANDATORY) - .filter(req -> req.isPostSchedule()) - .allMatch(net.runelite.client.plugins.microbot.pluginscheduler.tasks.requirements.requirement.Requirement::isFulfilled); - - if (!allPostMandatoryMet) { - logBuilder.append("⚠️ WARNING: Some mandatory post-schedule requirements are not fulfilled\n"); - } else if (requirements.getRegistry().getAllRequirements().stream().anyMatch(req -> req.getPriority() == net.runelite.client.plugins.microbot.pluginscheduler.tasks.requirements.enums.RequirementPriority.MANDATORY && req.isPostSchedule())) { - logBuilder.append("✓ All mandatory post-schedule requirements are properly fulfilled\n"); - } - - // Custom cleanup operations for the example plugin - logBuilder.append("\n--- Custom Plugin Cleanup ---\n"); - logBuilder.append(" ✓ Example-specific inventory cleanup completed\n"); - logBuilder.append(" ✓ Example-specific session data saved\n"); - logBuilder.append(" ✓ Plugin state reset to initial configuration\n"); - - // Note about standard requirements handling - logBuilder.append("\n--- Infrastructure Notes ---\n"); - logBuilder.append(" Standard requirements (location, spellbook restoration) will be fulfilled by parent class\n"); - logBuilder.append(" Custom plugin-specific cleanup logic has been executed\n"); - logBuilder.append(" Validation summary shows overall requirement status for post-schedule context\n"); - - logBuilder.append("\nCustom post-schedule tasks completed successfully"); - log.info(logBuilder.toString()); - - return true; - } - - - - /** - * Implementation of the abstract method from AbstractPrePostScheduleTasks. - * Returns the PrePostScheduleRequirements instance for this plugin. - * - * @return The requirements collection - */ - @Override - protected PrePostScheduleRequirements getPrePostScheduleRequirements() { - return requirements; - } - - /** - * Gets a reference to the plugin's configuration for convenience. - * - * @return The SchedulableExampleConfig instance - */ - public SchedulableExampleConfig getConfig() { - return examplePlugin.getConfig(); - } - -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/VoxPlugins/schedulable/example/SchedulableExampleScript.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/VoxPlugins/schedulable/example/SchedulableExampleScript.java deleted file mode 100644 index d1dbe17cbb3..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/VoxPlugins/schedulable/example/SchedulableExampleScript.java +++ /dev/null @@ -1,495 +0,0 @@ -package net.runelite.client.plugins.microbot.VoxPlugins.schedulable.example; - -import net.runelite.api.Constants; -import net.runelite.api.coords.WorldPoint; -import net.runelite.api.gameval.ItemID; -import net.runelite.client.plugins.microbot.Microbot; -import net.runelite.client.plugins.microbot.Script; -import net.runelite.client.plugins.microbot.breakhandler.BreakHandlerScript; -import net.runelite.client.plugins.microbot.util.antiban.Rs2Antiban; -import net.runelite.client.plugins.microbot.util.antiban.Rs2AntibanSettings; -import net.runelite.client.plugins.microbot.util.antiban.enums.Activity; -import net.runelite.client.plugins.microbot.util.antiban.enums.ActivityIntensity; -import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; -import net.runelite.client.plugins.microbot.util.events.PluginPauseEvent; -import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; -import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; -import net.runelite.client.plugins.microbot.util.player.Rs2Player; -// import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.regex.Pattern; -import lombok.extern.slf4j.Slf4j; -@Slf4j -public class SchedulableExampleScript extends Script { - private SchedulableExampleConfig config; - private WorldPoint returnPoint; - private int logsCollected = 0; - - // Antiban testing state - private boolean antibanOriginalTakeMicroBreaks = false; - private double antibanOriginalMicroBreakChance = 0.0; - private int antibanOriginalMicroBreakDurationLow = 3; - private int antibanOriginalMicroBreakDurationHigh = 15; - private boolean antibanOriginalActionCooldownActive = false; - private boolean antibanOriginalMoveMouseOffScreen = false; - private long lastStatusReport = 0; - private long lastBreakStatusCheck = 0; - private boolean wasOnBreak = false; - private long breakStartTime = 0; - private long totalBreakTime = 0; - private int microBreakCount = 0; - private boolean antibanInitialized = false; - private int aliveCounter = 0; // Counter to track when to report alive - - enum State { - IDELE, - RESETTING, - BREAK_PAUSED - } - - private State state = State.IDELE; - - - - public boolean main(SchedulableExampleConfig config) { - if (!Microbot.isLoggedIn()) return false; - if (!super.run()) return false; - - // Initialize antiban settings if enabled - if (config.enableAntibanTesting() && !antibanInitialized) { - setupAntibanTesting(); - } - - // Check break status and handle state changes - handleBreakStatusChecks(); - - // Set initial location if none was saved - if (initialPlayerLocation == null) { - initialPlayerLocation = Rs2Player.getWorldLocation(); - } - - if (this.returnPoint == null) { - this.returnPoint = initialPlayerLocation; - } - - // Check if we have an axe - if (!hasAxe()) { - // Microbot.status = "No axe found! Stopping..."; - // return false; - } - - // Handle break pause state - don't do anything while on break - if (state == State.BREAK_PAUSED) { - return true; - } - - // Skip if player is moving or animating, unless resetting - if (state != State.RESETTING && (Rs2Player.isMoving() || Rs2Player.isAnimating())) { - return true; - } - - // Trigger antiban behaviors if enabled - if (config.enableAntibanTesting()) { - handleAntibanBehaviors(); - } - - switch (state) { - case IDELE: - if (Rs2Inventory.isFull()) { - state = State.RESETTING; - return true; - } - break; - - case RESETTING: - resetInventory(); - return true; - - case BREAK_PAUSED: - // Already handled above - return true; - } - - return true; - } - - public boolean run(SchedulableExampleConfig config, WorldPoint savedLocation) { - this.returnPoint = savedLocation; - this.config = config; - this.aliveCounter = 0; - this.mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { - try { - if (!Microbot.isLoggedIn()) return; - if (!super.run()) return; - log.info("aliveCounter: {}", aliveCounter); - // Call the main method with antiban testing - main(config); - // Increment counter and check if we should report alive - aliveCounter++; - // Compute iterations from milliseconds, clamp to at least 1 - final long periodMs = Constants.GAME_TICK_LENGTH * 2L; - final long timeoutMs = Math.max(0L, (long) config.aliveReportTimeout()*1000L); - final int reportThreshold = (int) Math.max(1L, (long) Math.ceil(timeoutMs / (double) periodMs)); - if (aliveCounter >= reportThreshold) { - Rs2ItemModel oneDosePrayerRegeneration= Rs2ItemModel.createFromCache(ItemID._1DOSE1PRAYER_REGENERATION,1,1); - List equipmentActions =oneDosePrayerRegeneration.getEquipmentActions(); - boolean isTradeable = oneDosePrayerRegeneration.isTradeable(); - log.info("{}",oneDosePrayerRegeneration.toString() ); - Rs2ItemModel graceFullHelm= Rs2ItemModel.createFromCache(ItemID.GRACEFUL_HOOD,1,1); - List graceFullHelmActions = graceFullHelm.getEquipmentActions(); - boolean isGraceFullHelmTradeable = graceFullHelm.isTradeable(); - log.info("{}",graceFullHelm.toString() ); - log.info("SchedulableExampleScript is alive! \n- PauseEvent {} (valid),pauseAllScripts: {}, BreakHanlderLook: {}", PluginPauseEvent.isPaused(), Microbot.pauseAllScripts.get(), BreakHandlerScript.lockState.get()); - aliveCounter = 0; // Reset counter - } - - return; //manuel play testing the Scheduler plugin.. doing nothing for now - } catch (Exception ex) { - Microbot.log("SchedulableExampleScript error: " + ex.getMessage()); - } - }, 0, Constants.GAME_TICK_LENGTH*2, TimeUnit.MILLISECONDS); - - return true; - } - - private boolean hasAxe() { - return Rs2Inventory.hasItem("axe") || Rs2Equipment.isWearing("axe"); - } - - private void resetInventory() { - // Update count before moving to next state - updateItemCount(); - state = State.IDELE; - } - - /* - private void bankItems() { - Microbot.status = "Banking "; - - // Find and use nearest bank - if (!Rs2Bank.isOpen()) { - if (!Rs2Bank.useBank()) { - return; - } - } - - // Deposit logs but keep axe - Rs2Bank.depositAllExcept("axe"); - Rs2Bank.closeBank(); - - // Return to woodcutting spot - walkToReturnPoint(); - } - */ - private List getLootItemPatterns(){ - String lootItemsString = config.lootItems(); - List lootItemsList = new ArrayList<>(); - List lootItemsListPattern = new ArrayList<>(); - if (lootItemsString != null && !lootItemsString.isEmpty()) { - String[] lootItemsArray = lootItemsString.split(","); - for (String item : lootItemsArray) { - String trimmedItem = item.trim(); - try { - // Validate regex pattern - lootItemsListPattern.add(java.util.regex.Pattern.compile(trimmedItem)); - lootItemsList.add(trimmedItem); - } catch (java.util.regex.PatternSyntaxException e) { - //log.warn("Invalid regex pattern: '{}' - {}", trimmedItem, e.getMessage()); - } - } - } - return lootItemsListPattern; - } - /* - private void dropItems() { - Microbot.status = "Dropping Items"; - - // Drop all logs - List lootItemsListPattern = getLootItemPatterns(); - //List foundItems = - Rs2Inventory.all().forEach(item -> { - if (lootItemsListPattern.stream().anyMatch(pattern -> pattern.matcher(item.getName()).find())) { - - }else{ - // Drop all logs - Rs2Inventory.dropAll(item.getName()); - } - - }); - // Drop all logs - - } - - - - private void walkToReturnPoint() { - if (Rs2Player.getWorldLocation().distanceTo(returnPoint) > 3) { - Rs2Walker.walkTo(returnPoint); - } - } - */ - - public void updateItemCount() { - List lootItemsListPattern = getLootItemPatterns(); - int currentItems =Rs2Inventory.all().stream().filter(item -> lootItemsListPattern.stream().anyMatch(pattern -> pattern.matcher(item.getName()).find())).mapToInt(Rs2ItemModel::getQuantity).sum(); - - - if (currentItems > 0) { - logsCollected += currentItems; - Microbot.log("Total logs collected: " + logsCollected); - } - } - - public int getLogsCollected() { - return logsCollected; - } - - @Override - public void shutdown() { - // Teardown antiban testing if it was initialized - if (config != null && config.enableAntibanTesting()) { - teardownAntibanTesting(); - } - - Microbot.log("Shutting down SchedulableExampleScript"); - super.shutdown(); - returnPoint = null; - - // Log final antiban stats if testing was enabled - if (config != null && config.enableAntibanTesting()) { - Microbot.log("Final " + getAntibanStats()); - } - } - - /** - * Sets up antiban testing configuration based on plugin config - */ - private void setupAntibanTesting() { - if (antibanInitialized) { - return; - } - - Microbot.log("Setting up antiban testing..."); - - // Store original antiban settings - antibanOriginalTakeMicroBreaks = Rs2AntibanSettings.takeMicroBreaks; - antibanOriginalMicroBreakChance = Rs2AntibanSettings.microBreakChance; - antibanOriginalMicroBreakDurationLow = Rs2AntibanSettings.microBreakDurationLow; - antibanOriginalMicroBreakDurationHigh = Rs2AntibanSettings.microBreakDurationHigh; - antibanOriginalActionCooldownActive = Rs2AntibanSettings.actionCooldownActive; - antibanOriginalMoveMouseOffScreen = Rs2AntibanSettings.moveMouseOffScreen; - - // Apply test configuration - if (config.enableMicroBreaks()) { - Rs2AntibanSettings.takeMicroBreaks = true; - Rs2AntibanSettings.microBreakChance = config.microBreakChancePercent() / 100.0; - Rs2AntibanSettings.microBreakDurationLow = config.microBreakDurationMin(); - Rs2AntibanSettings.microBreakDurationHigh = config.microBreakDurationMax(); - - Microbot.log("Micro breaks enabled - Chance: " + (config.microBreakChancePercent()) + - "%, Duration: " + config.microBreakDurationMin() + "-" + config.microBreakDurationMax() + " minutes"); - } - - if (config.enableActionCooldowns()) { - Rs2AntibanSettings.actionCooldownActive = true; - Microbot.log("Action cooldowns enabled"); - } - - if (config.moveMouseOffScreen()) { - Rs2AntibanSettings.moveMouseOffScreen = true; - Microbot.log("Mouse off-screen movement enabled"); - } - - // Set antiban activity - Rs2Antiban.setActivity(Activity.GENERAL_WOODCUTTING); - Rs2Antiban.setActivityIntensity(ActivityIntensity.MODERATE); - - antibanInitialized = true; - Microbot.log("Antiban testing setup complete"); - } - - /** - * Restores original antiban settings - */ - private void teardownAntibanTesting() { - if (!antibanInitialized) { - return; - } - - Microbot.log("Restoring original antiban settings..."); - - // Restore original settings - Rs2AntibanSettings.takeMicroBreaks = antibanOriginalTakeMicroBreaks; - Rs2AntibanSettings.microBreakChance = antibanOriginalMicroBreakChance; - Rs2AntibanSettings.microBreakDurationLow = antibanOriginalMicroBreakDurationLow; - Rs2AntibanSettings.microBreakDurationHigh = antibanOriginalMicroBreakDurationHigh; - Rs2AntibanSettings.actionCooldownActive = antibanOriginalActionCooldownActive; - Rs2AntibanSettings.moveMouseOffScreen = antibanOriginalMoveMouseOffScreen; - - // Reset antiban activity - Rs2Antiban.resetAntibanSettings(); - - antibanInitialized = false; - Microbot.log("Antiban settings restored"); - } - - /** - * Handles break status monitoring and state transitions - */ - private void handleBreakStatusChecks() { - long currentTime = System.currentTimeMillis(); - - // Check break status every second - if (currentTime - lastBreakStatusCheck < 1000) { - return; - } - lastBreakStatusCheck = currentTime; - - boolean isCurrentlyOnBreak = BreakHandlerScript.isBreakActive() || - Rs2AntibanSettings.microBreakActive || - Rs2AntibanSettings.actionCooldownActive; - - // Detect break start - if (isCurrentlyOnBreak && !wasOnBreak) { - handleBreakStart(); - } - // Detect break end - else if (!isCurrentlyOnBreak && wasOnBreak) { - handleBreakEnd(); - } - - // Report status periodically if on break - if (isCurrentlyOnBreak && config.statusReportInterval() > 0) { - if (currentTime - lastStatusReport >= config.statusReportInterval() * 1000) { - reportBreakStatus(); - lastStatusReport = currentTime; - } - } - - wasOnBreak = isCurrentlyOnBreak; - } - - /** - * Handles the start of a break - */ - private void handleBreakStart() { - breakStartTime = System.currentTimeMillis(); - state = State.BREAK_PAUSED; - - String breakType = getBreakType(); - Microbot.log("Break started - Type: " + breakType + ", Script state: PAUSED"); - - if (Rs2AntibanSettings.microBreakActive) { - microBreakCount++; - } - } - - /** - * Handles the end of a break - */ - private void handleBreakEnd() { - if (breakStartTime > 0) { - long breakDuration = System.currentTimeMillis() - breakStartTime; - totalBreakTime += breakDuration; - - String breakType = getBreakType(); - Microbot.log("Break ended - Type: " + breakType + - ", Duration: " + formatDuration(breakDuration) + - ", Script state: RESUMED"); - - breakStartTime = 0; - } - - // Resume normal operation - if (state == State.BREAK_PAUSED) { - state = State.IDELE; - } - } - - /** - * Reports current break status - */ - private void reportBreakStatus() { - String breakType = getBreakType(); - long currentBreakDuration = breakStartTime > 0 ? - System.currentTimeMillis() - breakStartTime : 0; - - Microbot.log("Break Status - Type: " + breakType + - ", Current Duration: " + formatDuration(currentBreakDuration) + - ", Total Break Time: " + formatDuration(totalBreakTime) + - ", Micro Breaks: " + microBreakCount); - } - - /** - * Determines the current break type - */ - private String getBreakType() { - if (BreakHandlerScript.isBreakActive()) { - return "Regular Break"; - } else if (Rs2AntibanSettings.microBreakActive) { - return "Micro Break"; - } else if (Rs2AntibanSettings.actionCooldownActive) { - return "Action Cooldown"; - } else { - return "Unknown"; - } - } - - /** - * Handles antiban behaviors during normal operation - */ - private void handleAntibanBehaviors() { - // Trigger action cooldown occasionally - if (config.enableActionCooldowns() && Math.random() < 0.01) { // 1% chance per tick - Rs2Antiban.actionCooldown(); - } - - // Take micro breaks by chance - if (config.enableMicroBreaks() && Math.random() < (config.microBreakChancePercent() / 100.0 )) { - Rs2Antiban.takeMicroBreakByChance(); - if (Rs2AntibanSettings.microBreakActive) { - Microbot.log("Taking a new micro break - Count: " + microBreakCount); - } - } - - // Move mouse randomly - if (config.moveMouseOffScreen() && Math.random() < 0.005) { // 0.5% chance per tick - Rs2Antiban.moveMouseRandomly(); - } - } - - /** - * Formats a duration in milliseconds to a readable string - */ - private String formatDuration(long durationMillis) { - long seconds = durationMillis / 1000; - long minutes = seconds / 60; - long hours = minutes / 60; - - if (hours > 0) { - return String.format("%dh %dm %ds", hours, minutes % 60, seconds % 60); - } else if (minutes > 0) { - return String.format("%dm %ds", minutes, seconds % 60); - } else { - return String.format("%ds", seconds); - } - } - - /** - * Gets comprehensive antiban testing statistics - */ - public String getAntibanStats() { - if (!config.enableAntibanTesting()) { - return "Antiban testing disabled"; - } - - return String.format("Antiban Stats - Total Break Time: %s, Micro Breaks: %d, " + - "Current State: %s, Break Active: %s", - formatDuration(totalBreakTime), - microBreakCount, - state.name(), - wasOnBreak ? getBreakType() : "None"); - } -} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/VoxPlugins/schedulable/example/enums/SpellbookOption.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/VoxPlugins/schedulable/example/enums/SpellbookOption.java deleted file mode 100644 index c23bd3de976..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/VoxPlugins/schedulable/example/enums/SpellbookOption.java +++ /dev/null @@ -1,49 +0,0 @@ -package net.runelite.client.plugins.microbot.VoxPlugins.schedulable.example.enums; - -import net.runelite.client.plugins.microbot.util.magic.Rs2Spellbook; -import lombok.Getter; - -/** - * Unified spellbook enum that includes a "NONE" option for configurations - * where no spellbook switching is desired. - */ -@Getter -public enum SpellbookOption { - - NONE("None (No Switching)", null), - MODERN("Standard/Modern Spellbook", Rs2Spellbook.MODERN), - ANCIENT("Ancient Magicks", Rs2Spellbook.ANCIENT), - LUNAR("Lunar Spellbook", Rs2Spellbook.LUNAR), - ARCEUUS("Arceuus Spellbook", Rs2Spellbook.ARCEUUS); - - private final String displayName; - private final Rs2Spellbook spellbook; - - SpellbookOption(String displayName, Rs2Spellbook spellbook) { - this.displayName = displayName; - this.spellbook = spellbook; - } - - /** - * Gets the Rs2Spellbook enum value, or null if this is the NONE option - * - * @return Rs2Spellbook enum value or null - */ - public Rs2Spellbook getSpellbook() { - return spellbook; - } - - /** - * Checks if this option represents no spellbook switching - * - * @return true if this is the NONE option - */ - public boolean isNone() { - return this == NONE; - } - - @Override - public String toString() { - return displayName; - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/VoxPlugins/schedulable/example/enums/UnifiedLocation.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/VoxPlugins/schedulable/example/enums/UnifiedLocation.java deleted file mode 100644 index af6bfd6197d..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/VoxPlugins/schedulable/example/enums/UnifiedLocation.java +++ /dev/null @@ -1,190 +0,0 @@ -package net.runelite.client.plugins.microbot.VoxPlugins.schedulable.example.enums; - -import net.runelite.api.coords.WorldPoint; -import net.runelite.client.plugins.microbot.util.bank.enums.BankLocation; -import net.runelite.client.plugins.microbot.util.depositbox.DepositBoxLocation; -import net.runelite.client.plugins.microbot.util.walker.enums.*; -import lombok.Getter; - -/** - * Unified location enum that encompasses major location types used in the walker system. - * This provides a single interface for selecting locations from various categories - * including banks, deposit boxes, farming locations, slayer masters, and hunting areas. - * - * This is a simplified version containing only the most commonly used locations - * to avoid compatibility issues with varying enum implementations. - */ -@Getter -public enum UnifiedLocation { - - // None option - NONE("None", LocationType.NONE, null), - - // Major Bank Locations - BANK_GRAND_EXCHANGE("Grand Exchange Bank", LocationType.BANK, BankLocation.GRAND_EXCHANGE), - BANK_VARROCK_WEST("Varrock West Bank", LocationType.BANK, BankLocation.VARROCK_WEST), - BANK_VARROCK_EAST("Varrock East Bank", LocationType.BANK, BankLocation.VARROCK_EAST), - BANK_LUMBRIDGE_FRONT("Lumbridge Bank", LocationType.BANK, BankLocation.LUMBRIDGE_FRONT), - BANK_FALADOR_WEST("Falador West Bank", LocationType.BANK, BankLocation.FALADOR_WEST), - BANK_FALADOR_EAST("Falador East Bank", LocationType.BANK, BankLocation.FALADOR_EAST), - BANK_EDGEVILLE("Edgeville Bank", LocationType.BANK, BankLocation.EDGEVILLE), - BANK_DRAYNOR_VILLAGE("Draynor Village Bank", LocationType.BANK, BankLocation.DRAYNOR_VILLAGE), - BANK_AL_KHARID("Al Kharid Bank", LocationType.BANK, BankLocation.AL_KHARID), - BANK_CATHERBY("Catherby Bank", LocationType.BANK, BankLocation.CATHERBY), - BANK_CAMELOT("Camelot Bank", LocationType.BANK, BankLocation.CAMELOT), - BANK_ARDOUGNE_NORTH("Ardougne North Bank", LocationType.BANK, BankLocation.ARDOUGNE_NORTH), - BANK_ARDOUGNE_SOUTH("Ardougne South Bank", LocationType.BANK, BankLocation.ARDOUGNE_SOUTH), - BANK_CANIFIS("Canifis Bank", LocationType.BANK, BankLocation.CANIFIS), - BANK_FISHING_GUILD("Fishing Guild Bank", LocationType.BANK, BankLocation.FISHING_GUILD), - BANK_FOSSIL_ISLAND("Fossil Island Bank", LocationType.BANK, BankLocation.FOSSIL_ISLAND), - BANK_ARCEUUS("Arceuus Bank", LocationType.BANK, BankLocation.ARCEUUS), - BANK_HOSIDIUS("Hosidius Bank", LocationType.BANK, BankLocation.HOSIDIUS), - BANK_LOVAKENGJ("Lovakengj Bank", LocationType.BANK, BankLocation.LOVAKENGJ), - BANK_PISCARILIUS("Piscarilius Bank", LocationType.BANK, BankLocation.PISCARILIUS), - BANK_SHAYZIEN_BANK("Shayzien Bank", LocationType.BANK, BankLocation.SHAYZIEN_BANK), - BANK_FARMING_GUILD("Farming Guild Bank", LocationType.BANK, BankLocation.FARMING_GUILD), - - // Major Deposit Box Locations - DEPOSIT_BOX_GRAND_EXCHANGE("Grand Exchange Deposit Box", LocationType.DEPOSIT_BOX, DepositBoxLocation.GRAND_EXCHANGE), - DEPOSIT_BOX_EDGEVILLE("Edgeville Deposit Box", LocationType.DEPOSIT_BOX, DepositBoxLocation.EDGEVILLE), - DEPOSIT_BOX_BARBARIAN_ASSAULT("Barbarian Assault Deposit Box", LocationType.DEPOSIT_BOX, DepositBoxLocation.BARBARIAN_ASSAULT), - DEPOSIT_BOX_FALADOR("Falador Deposit Box", LocationType.DEPOSIT_BOX, DepositBoxLocation.FALADOR), - DEPOSIT_BOX_VARROCK("Varrock Deposit Box", LocationType.DEPOSIT_BOX, DepositBoxLocation.VARROCK), - DEPOSIT_BOX_LUMBRIDGE("Lumbridge Deposit Box", LocationType.DEPOSIT_BOX, DepositBoxLocation.LUMBRIDGE), - DEPOSIT_BOX_FARMING_GUILD("Farming Guild Deposit Box", LocationType.DEPOSIT_BOX, DepositBoxLocation.FARMING_GUILD), - - // Major Slayer Masters - SLAYER_MASTER_TURAEL("Turael (Burthorpe)", LocationType.SLAYER_MASTER, SlayerMasters.TURAEL), - SLAYER_MASTER_SPRIA("Spria (Draynor Village)", LocationType.SLAYER_MASTER, SlayerMasters.SPRIA), - SLAYER_MASTER_MAZCHNA("Mazchna (Canifis)", LocationType.SLAYER_MASTER, SlayerMasters.MAZCHNA), - SLAYER_MASTER_VANNAKA("Vannaka (Edgeville Dungeon)", LocationType.SLAYER_MASTER, SlayerMasters.VANNAKA), - SLAYER_MASTER_CHAELDAR("Chaeldar (Zanaris)", LocationType.SLAYER_MASTER, SlayerMasters.CHAELDAR), - SLAYER_MASTER_KONAR("Konar quo Maten (Mount Karuulm)", LocationType.SLAYER_MASTER, SlayerMasters.KONAR), - SLAYER_MASTER_NIEVE("Nieve (Gnome Stronghold)", LocationType.SLAYER_MASTER, SlayerMasters.NIEVE), - SLAYER_MASTER_STEVE("Steve (Gnome Stronghold)", LocationType.SLAYER_MASTER, SlayerMasters.STEVE), - SLAYER_MASTER_DURADEL("Duradel (Shilo Village)", LocationType.SLAYER_MASTER, SlayerMasters.DURADEL), - SLAYER_MASTER_KRYSTILIA("Krystilia (Edgeville)", LocationType.SLAYER_MASTER, SlayerMasters.KRYSTILIA), - - // Major Farming Locations - Allotments - FARMING_FALADOR_ALLOTMENT("Falador Allotment", LocationType.FARMING, Allotments.FALADOR), - FARMING_CATHERBY_ALLOTMENT("Catherby Allotment", LocationType.FARMING, Allotments.CATHERBY), - FARMING_ARDOUGNE_ALLOTMENT("Ardougne Allotment", LocationType.FARMING, Allotments.ARDOUGNE), - FARMING_MORYTANIA_ALLOTMENT("Morytania Allotment", LocationType.FARMING, Allotments.MORYTANIA), - FARMING_KOUREND_ALLOTMENT("Kourend Allotment", LocationType.FARMING, Allotments.KOUREND), - FARMING_GUILD_ALLOTMENT("Farming Guild Allotment", LocationType.FARMING, Allotments.FARMING_GUILD), - - // Major Farming Locations - Trees - FARMING_TREE_FALADOR("Falador Tree", LocationType.FARMING, Trees.FALADOR), - FARMING_TREE_FARMING_GUILD("Farming Guild Tree", LocationType.FARMING, Trees.FARMING_GUILD), - FARMING_TREE_GNOME_STRONGHOLD("Gnome Stronghold Tree", LocationType.FARMING, Trees.GNOME_STRONGHOLD), - FARMING_TREE_LUMBRIDGE("Lumbridge Tree", LocationType.FARMING, Trees.LUMBRIDGE), - FARMING_TREE_TAVERLEY("Taverley Tree", LocationType.FARMING, Trees.TAVERLEY), - FARMING_TREE_VARROCK("Varrock Tree", LocationType.FARMING, Trees.VARROCK), - - // Major Farming Locations - Fruit Trees - FARMING_FRUIT_TREE_BRIMHAVEN("Brimhaven Fruit Tree", LocationType.FARMING, FruitTrees.BRIMHAVEN), - FARMING_FRUIT_TREE_CATHERBY("Catherby Fruit Tree", LocationType.FARMING, FruitTrees.CATHERBY), - FARMING_FRUIT_TREE_FARMING_GUILD("Farming Guild Fruit Tree", LocationType.FARMING, FruitTrees.FARMING_GUILD), - FARMING_FRUIT_TREE_GNOME_STRONGHOLD("Gnome Stronghold Fruit Tree", LocationType.FARMING, FruitTrees.GNOME_STRONGHOLD), - FARMING_FRUIT_TREE_TREE_GNOME_VILLAGE("Tree Gnome Village Fruit Tree", LocationType.FARMING, FruitTrees.TREE_GNOME_VILLAGE), - FARMING_FRUIT_TREE_TAI_BWO_WANNAI("Tai Bwo Wannai Fruit Tree", LocationType.FARMING, FruitTrees.TAI_BWO_WANNAI), - FARMING_FRUIT_TREE_PRIFDDINAS("Prifddinas Fruit Tree", LocationType.FARMING, FruitTrees.PRIFDDINAS); - - private final String displayName; - private final LocationType type; - private final Object locationData; - - UnifiedLocation(String displayName, LocationType type, Object locationData) { - this.displayName = displayName; - this.type = type; - this.locationData = locationData; - } - - /** - * Gets the WorldPoint for this location. - * - * @return WorldPoint if available, null otherwise - */ - public WorldPoint getWorldPoint() { - if (locationData == null) { - return null; - } - - switch (type) { - case BANK: - return ((BankLocation) locationData).getWorldPoint(); - - case DEPOSIT_BOX: - return ((DepositBoxLocation) locationData).getWorldPoint(); - - case SLAYER_MASTER: - return ((SlayerMasters) locationData).getWorldPoint(); - - case FARMING: - if (locationData instanceof Allotments) { - return ((Allotments) locationData).getWorldPoint(); - } else if (locationData instanceof Herbs) { - return ((Herbs) locationData).getWorldPoint(); - } else if (locationData instanceof Trees) { - return ((Trees) locationData).getWorldPoint(); - } else if (locationData instanceof FruitTrees) { - return ((FruitTrees) locationData).getWorldPoint(); - } else if (locationData instanceof Bushes) { - return ((Bushes) locationData).getWorldPoint(); - } else if (locationData instanceof Hops) { - return ((Hops) locationData).getWorldPoint(); - } else if (locationData instanceof CompostBins) { - return ((CompostBins) locationData).getWorldPoint(); - } - break; - - case HUNTING: - if (locationData instanceof Birds) { - return ((Birds) locationData).getWorldPoint(); - } else if (locationData instanceof Chinchompas) { - return ((Chinchompas) locationData).getWorldPoint(); - } else if (locationData instanceof Insects) { - return ((Insects) locationData).getWorldPoint(); - } else if (locationData instanceof Kebbits) { - return ((Kebbits) locationData).getWorldPoint(); - } else if (locationData instanceof Salamanders) { - return ((Salamanders) locationData).getWorldPoint(); - } else if (locationData instanceof SpecialHuntingAreas) { - return ((SpecialHuntingAreas) locationData).getWorldPoint(); - } - break; - - case NONE: - default: - return null; - } - - return null; - } - - /** - * Gets the original location data object (BankLocation, DepositBoxLocation, etc.) - * - * @return The original location data object - */ - public Object getOriginalLocationData() { - return locationData; - } - - @Override - public String toString() { - return displayName; - } - - /** - * Enum representing the different types of locations - */ - public enum LocationType { - NONE, - BANK, - DEPOSIT_BOX, - SLAYER_MASTER, - FARMING, - HUNTING - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/example/ExampleScript.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/example/ExampleScript.java index 2de6c7670f8..8f3f573f190 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/example/ExampleScript.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/example/ExampleScript.java @@ -43,6 +43,8 @@ public boolean run() { try { if (!Microbot.isLoggedIn()) return; + System.out.println("hello world"); + WorldPoint worldPoint = WorldPoint.fromRegion(Microbot.getClient().getLocalPlayer().getWorldLocation().getRegionID(), 35, 34, diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/pluginscheduler/tasks/requirements/registry/RequirementRegistry.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/pluginscheduler/tasks/requirements/registry/RequirementRegistry.java index 03d0a5fa46a..d19c504f97e 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/pluginscheduler/tasks/requirements/registry/RequirementRegistry.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/pluginscheduler/tasks/requirements/registry/RequirementRegistry.java @@ -22,8 +22,6 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; -import org.benf.cfr.reader.util.output.BytecodeDumpConsumer.Item; - /** * Enhanced requirement registry that manages all types of requirements with automatic * uniqueness enforcement, consistency guarantees, and efficient lookup. From d709788b79e80243e439d36d9c0ab0e6ad58b777 Mon Sep 17 00:00:00 2001 From: chsami Date: Tue, 23 Dec 2025 06:00:04 +0100 Subject: [PATCH 13/13] chore: update .gitignore and gradle-wrapper.properties for gradle 8.8 --- .gitignore | 3 ++- gradle/wrapper/gradle-wrapper.properties | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index f35ca9ebe00..62435815d16 100644 --- a/.gitignore +++ b/.gitignore @@ -36,4 +36,5 @@ bin/ .vscode/ ### Mac OS ### -.DS_Store \ No newline at end of file +.DS_Store +/cache/target/* diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a65a5a0517f..a9c19e29fb7 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,8 +1,7 @@ +#Tue Jul 02 14:53:12 EDT 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-all.zip distributionSha256Sum=f8b4f4772d302c8ff580bc40d0f56e715de69b163546944f787c87abf209c961 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip -networkTimeout=10000 -validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists