diff --git a/.github/renovate.json5 b/.github/renovate.json5 new file mode 100644 index 0000000..ded324a --- /dev/null +++ b/.github/renovate.json5 @@ -0,0 +1,22 @@ +{ + "extends": [ + ":enableRenovate", + ":semanticCommits", + ":enablePreCommit", + "config:best-practices" + ], + "platform": "github", + "onboarding": false, + "requireConfig": "optional", + "timezone": "UTC", + "dependencyDashboard": true, + "platformCommit": true, + "prCreation": "not-pending", + "suppressNotifications": ["prIgnoreNotification"], + "rebaseWhen": "conflicted", + "packageRules": [ + {"matchUpdateTypes": ["major"], "addLabels": ["dependency/major"]}, + {"matchUpdateTypes": ["minor"], "addLabels": ["dependency/minor"]}, + {"matchUpdateTypes": ["patch"], "addLabels": ["dependency/patch"]} + ] +} \ No newline at end of file diff --git a/.github/workflows/flex-build.yml b/.github/workflows/flex-build.yml new file mode 100644 index 0000000..fa6e331 --- /dev/null +++ b/.github/workflows/flex-build.yml @@ -0,0 +1,82 @@ +--- +name: Flex Build + +"on": + workflow_call: + inputs: + mc: + description: Minecraft version + type: string + lex: + description: LexForge version + type: string + neo: + description: NeoForge version + type: string + java: + description: Java version + required: true + type: string + upload: + description: Upload the build artifacts + default: true + type: boolean + publish: + description: Run Gradle publish + default: false + type: boolean + +jobs: + build: # TODO: add build attestation and generate then combine gradle dependency graphs for SBOM + name: Build ${{ inputs.mc }} + runs-on: blacksmith-2vcpu-ubuntu-2204 + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Cache build + id: cache + uses: useblacksmith/cache@v5 + with: + path: build + key: build-${{ inputs.mc }}-${{ hashFiles('./*') }} + + - if: steps.cache.outputs.cache-hit != 'true' + name: Setup Java + uses: useblacksmith/setup-java@v5 + with: + distribution: temurin + java-version: ${{ inputs.java }} + + - if: steps.cache.outputs.cache-hit != 'true' + name: Setup Gradle + uses: useblacksmith/setup-gradle/setup-gradle@v5 + with: + workflow-job-context: '{}' # FIXME: avoid this cache duplication workaround + + - if: | + steps.cache.outputs.cache-hit != 'true' + && !inputs.release + name: Gradle build + run: > + ./gradlew build --stacktrace + -Pminecraft_version=${{ inputs.mc }} + -Plexforge_version=${{ inputs.lex }} + -Pneoforge_version=${{ inputs.neo }} + + - if: inputs.release + name: Gradle publish + env: + IS_MAVEN_PUB: true + DEPLOY_TO_GITHUB_PACKAGES_URL: https://maven.pkg.github.com/${{ github.repository }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: ./gradlew publish + + - if: inputs.upload + name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: jars-${{ inputs.mc }} + path: build/libs/*.jar diff --git a/.github/workflows/lifecycle.yml b/.github/workflows/lifecycle.yml new file mode 100644 index 0000000..08d83ea --- /dev/null +++ b/.github/workflows/lifecycle.yml @@ -0,0 +1,356 @@ +--- +name: Lifecycle + +"on": + push: + branches: + - main + pull_request: + types: + - closed + - opened + - synchronize + - reopened + workflow_dispatch: + inputs: + versions: + description: Comma-delimited mc versions to build and run (i.e. 1.20.4,1.21) + default: "all" + ref: + description: Git ref to checkout before build (i.e. my-feature-branch ) + default: "main" + +concurrency: # FIXME: prevent release commit cancellation + group: > + ${{ github.workflow }}- + ${{ github.event_name }}- + ${{ github.event.inputs.ref || github.ref }} + cancel-in-progress: true + +jobs: + testbuild: + name: TestBuild + runs-on: blacksmith-2vcpu-ubuntu-2204 + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Setup Java 8 + uses: useblacksmith/setup-java@v5 + with: + distribution: temurin + java-version: 8 + + - name: Setup Java 16 + uses: useblacksmith/setup-java@v5 + with: + distribution: temurin + java-version: 16 + + - name: Setup Java 17 + uses: useblacksmith/setup-java@v5 + with: + distribution: temurin + java-version: 17 + + - name: Setup Java 21 + uses: useblacksmith/setup-java@v5 + with: + distribution: temurin + java-version: 21 + + - name: Setup Gradle + uses: useblacksmith/setup-gradle/setup-gradle@v6 + with: + cache-read-only: false + build-scan-publish: true + build-scan-terms-of-service-url: https://gradle.com/terms-of-service + build-scan-terms-of-service-agree: true + + - name: Gradle build + run: > + ./gradlew build --stacktrace + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: jars + path: | + **/build/libs/*.jar + + release-please: + name: Release Please + if: github.event_name == 'fake' # FIXME: remove, testing purposes + runs-on: blacksmith-2vcpu-ubuntu-2204 + outputs: + release_created: ${{ steps.release-please.outputs.release_created }} + tag_name: ${{ steps.release-please.outputs.tag_name }} + permissions: + contents: write + pull-requests: write + steps: + - if: github.event_name == 'push' # TODO: explicit on push to main + id: release-please + name: Run Release Please + uses: googleapis/release-please-action@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + matrices: + if: github.event.action != 'closed' + name: Construct matrices + needs: release-please + runs-on: blacksmith-2vcpu-ubuntu-2204 + outputs: + build-matrix: ${{ steps.matrices.outputs.build-matrix }} + run-matrix: ${{ steps.matrices.outputs.run-matrix }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1 + ref: ${{ inputs.ref || github.ref }} + + - if: github.event_name != 'workflow_dispatch' + id: paths + name: Get paths + run: | + { delimiter="$(openssl rand -hex 8)" + echo "filter<<${delimiter}" + for dir in $(find . -maxdepth 1 -type d -not -path "./.git*" -print | cut -d/ -f2-); do + echo "'$dir' : ['$dir/**']" + done; echo "${delimiter}"; } >> "${GITHUB_OUTPUT}" + echo "${GITHUB_OUTPUT}" + cat "${GITHUB_OUTPUT}" || true + + - if: github.event_name != 'workflow_dispatch' + id: filter + name: Filter + uses: dorny/paths-filter@v3.0.2 + with: + filters: | + ${{ steps.paths.outputs.filter }} + + # TODO: if only README, LICENSE, .gitignore changed we do not need to run MC + - id: matrices # TODO: build origin matrices dynamically, consider collapsing this into a .py + name: Construct matrices + run: | + import os + import json + + build_matrix = {"include": []} + run_matrix = {"version": []} + + build_data = [ + # we need to build everything with java >= 11 for manifold, it gets compiled to java 8 + # for now no api builds {"mc": "api", "java": "8"}, + {"mc": "1.21.3", "lex": "53.0.7", "neo": "11-beta", "java": "21"}, + {"mc": "1.21.1", "lex": "52.0.2", "neo": "4", "java": "21"}, + {"mc": "1.21", "lex": "51.0.24", "neo": "96-beta", "java": "21"}, + {"mc": "1.20.6", "lex": "50.1.10", "neo": "119", "java": "21"}, + {"mc": "1.20.4", "lex": "49.0.38", "neo": "219", "java": "21"}, + {"mc": "1.20.3", "lex": "49.0.2", "neo": "8-beta", "java": "21"}, + {"mc": "1.20.2", "lex": "48.1.0", "neo": "88", "java": "21"}, + {"mc": "1.20.1", "lex": "47.2.23", "java": "21"}, + {"mc": "1.19.4", "lex": "45.2.9", "java": "21"}, + {"mc": "1.19.3", "lex": "44.1.23", "java": "21"}, + {"mc": "1.19.2", "lex": "43.3.9", "java": "21"}, + {"mc": "1.19.1", "lex": "42.0.9", "java": "21"}, + {"mc": "1.19", "lex": "41.1.0", "java": "21"}, + {"mc": "1.18.2", "lex": "40.2.18", "java": "21"}, + {"mc": "1.17.1", "lex": "37.1.1", "java": "16"}, + {"mc": "1.16.5", "lex": "36.2.42", "java": "21"}, + {"mc": "1.12.2", "lex": "14.23.5.2860", "java": "21"}, + {"mc": "1.8.9", "lex": "11.15.1.2318-1.8.9", "java": "21"}, + {"mc": "1.7.10", "lex": "10.13.4.1614-1.7.10", "java": "21"} + ] + + run_data = [ + {"mc": "1.21.3", "type": "lexforge", "modloader": "forge", "regex": ".*forge.*", "java": "21"}, + {"mc": "1.21.3", "type": "neoforge", "modloader": "neoforge", "regex": ".*neoforge.*", "java": "21"}, + {"mc": "1.21.3", "type": "fabric", "modloader": "fabric", "regex": ".*fabric.*", "java": "21"}, + {"mc": "1.21.1", "type": "lexforge", "modloader": "forge", "regex": ".*forge.*", "java": "21"}, + {"mc": "1.21.1", "type": "neoforge", "modloader": "neoforge", "regex": ".*neoforge.*", "java": "21"}, + {"mc": "1.21.1", "type": "fabric", "modloader": "fabric", "regex": ".*fabric.*", "java": "21"}, + {"mc": "1.21", "type": "lexforge", "modloader": "forge", "regex": ".*forge.*", "java": "21"}, + {"mc": "1.21", "type": "neoforge", "modloader": "neoforge", "regex": ".*neoforge.*", "java": "21"}, + {"mc": "1.21", "type": "fabric", "modloader": "fabric", "regex": ".*fabric.*", "java": "21"}, + {"mc": "1.20.6", "type": "lexforge", "modloader": "forge", "regex": ".*forge.*", "java": "21"}, + {"mc": "1.20.6", "type": "neoforge", "modloader": "neoforge", "regex": ".*neoforge.*", "java": "21"}, + {"mc": "1.20.6", "type": "fabric", "modloader": "fabric", "regex": ".*fabric.*", "java": "21"}, + {"mc": "1.20.4", "type": "lexforge", "modloader": "forge", "regex": ".*forge.*", "java": "17"}, + {"mc": "1.20.4", "type": "neoforge", "modloader": "neoforge", "regex": ".*neoforge.*", "java": "17"}, + {"mc": "1.20.4", "type": "fabric", "modloader": "fabric", "regex": ".*fabric.*", "java": "17"}, + {"mc": "1.20.3", "type": "lexforge", "modloader": "forge", "regex": ".*forge.*", "java": "17"}, + {"mc": "1.20.3", "type": "neoforge", "modloader": "neoforge", "regex": ".*neoforge.*", "java": "17"}, + {"mc": "1.20.3", "type": "fabric", "modloader": "fabric", "regex": ".*fabric.*", "java": "17"}, + {"mc": "1.20.2", "type": "lexforge", "modloader": "forge", "regex": ".*forge.*", "java": "17"}, + {"mc": "1.20.2", "type": "neoforge", "modloader": "neoforge", "regex": ".*neoforge.*", "java": "17"}, + {"mc": "1.20.2", "type": "fabric", "modloader": "fabric", "regex": ".*fabric.*", "java": "17"}, + {"mc": "1.20.1", "type": "lexforge", "modloader": "forge", "regex": ".*forge.*", "java": "17"}, + {"mc": "1.20.1", "type": "fabric", "modloader": "fabric", "regex": ".*fabric.*", "java": "17"}, + {"mc": "1.19.4", "type": "fabric", "modloader": "fabric", "regex": ".*fabric.*", "java": "17"}, + {"mc": "1.19.4", "type": "lexforge", "modloader": "forge", "regex": ".*forge.*", "java": "17"}, + {"mc": "1.19.3", "type": "fabric", "modloader": "fabric", "regex": ".*fabric.*", "java": "17"}, + {"mc": "1.19.3", "type": "lexforge", "modloader": "forge", "regex": ".*forge.*", "java": "17"}, + {"mc": "1.19.2", "type": "fabric", "modloader": "fabric", "regex": ".*fabric.*", "java": "17"}, + {"mc": "1.19.2", "type": "lexforge", "modloader": "forge", "regex": ".*forge.*", "java": "17"}, + {"mc": "1.19.1", "type": "fabric", "modloader": "fabric", "regex": ".*fabric.*", "java": "17"}, + {"mc": "1.19.1", "type": "lexforge", "modloader": "forge", "regex": ".*forge.*", "java": "17"}, + {"mc": "1.19", "type": "fabric", "modloader": "fabric", "regex": ".*fabric.*", "java": "17"}, + {"mc": "1.19", "type": "lexforge", "modloader": "forge", "regex": ".*forge.*", "java": "17"}, + {"mc": "1.18.2", "type": "fabric", "modloader": "fabric", "regex": ".*fabric.*", "java": "17"}, + {"mc": "1.18.2", "type": "lexforge", "modloader": "forge", "regex": ".*forge.*", "java": "17"}, + {"mc": "1.17.1", "type": "fabric", "modloader": "fabric", "regex": ".*fabric.*", "java": "16"}, + {"mc": "1.17.1", "type": "lexforge", "modloader": "forge", "regex": ".*forge.*", "java": "16"}, + {"mc": "1.16.5", "type": "fabric", "modloader": "fabric", "regex": ".*fabric.*", "java": "8"}, + {"mc": "1.16.5", "type": "lexforge", "modloader": "forge", "regex": ".*forge.*", "java": "8"}, + {"mc": "1.12.2", "type": "lexforge", "modloader": "forge", "regex": ".*forge.*", "java": "8"}, + {"mc": "1.8.9", "type": "lexforge", "modloader": "forge", "regex": ".*orge.*", "java": "8"}, + {"mc": "1.7.10", "type": "lexforge", "modloader": "forge", "regex": ".*orge.*", "java": "8"} + ] + + versions_to_run = [] + + match os.getenv('GITHUB_EVENT_NAME'): + case 'pull_request': + if "${{ steps.filter.outcome }}" == "success": + # TODO: if only README or LICENSE changed, no need to run all versions! + # dirs_to_filter = json.loads('${{ steps.filter.outputs.changes }}') + versions_to_run = ['all'] + + case 'workflow_dispatch': + input_versions = '${{ github.event.inputs.versions }}' + print(input_versions) + if input_versions == 'all': + # If "all" is specified in the input, run all possible builds and runs + versions_to_run = ['all'] + else: + # Otherwise, filter based on the versions listed in the input + versions_to_run = [item.strip() for item in input_versions.split(',')] + + case 'push': + if '${{ needs.release-please.outputs.release_created }}' == 'true': + # If a release is created, use the full matrices + versions_to_run = ['all'] + + if 'all' in versions_to_run: + build_matrix['include'], run_matrix['version'] = build_data, run_data + else: + build_matrix['include'].extend([item for item in build_data if item["mc"] in versions_to_run]) + run_matrix['version'].extend([item for item in run_data if item["mc"] in versions_to_run]) + + with open(os.environ['GITHUB_OUTPUT'], 'a') as fh: + print(json.dumps(build_matrix, indent=2)) + fh.write(f'build-matrix={json.dumps(build_matrix)}\n') + print(json.dumps(run_matrix, indent=2)) + fh.write(f'run-matrix={json.dumps(run_matrix)}\n') + shell: python {0} + + build: + if: fromJSON(needs.matrices.outputs.build-matrix).include[0] != null + name: Build + needs: matrices + strategy: + fail-fast: false + matrix: + ${{ insert }}: ${{ fromJSON(needs.matrices.outputs.build-matrix) }} + uses: ./.github/workflows/flex-build.yml + with: + mc: ${{ matrix.mc }} + lex: ${{ matrix.lex }} + neo: ${{ matrix.neo }} + java: ${{ matrix.java }} + + run: + if: github.event_name != 'push' + name: Run tests + needs: + - matrices + - build + runs-on: blacksmith-2vcpu-ubuntu-2204 + strategy: + fail-fast: false + matrix: + ${{ insert }}: ${{ fromJSON(needs.matrices.outputs.run-matrix) }} + xvfb: [true, false] + steps: + - name: Checkout # TODO: simplify away this step, currently needed for `uses: ./` + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Download artifact + uses: actions/download-artifact@v4 + with: + name: jars-${{ matrix.version.mc }} + + - name: Bootstrap mods + run: | + mkdir -p run/mods + cp mc-runtime-test-*-${{ matrix.version.type }}-release.jar run/mods + + - name: Setup Java temurin-${{ matrix.version.java }} + uses: useblacksmith/setup-java@v5 + with: + java-version: ${{ matrix.version.java }} + distribution: temurin + + - name: Run game + timeout-minutes: 3 + uses: headlesshq/mc-runtime-test@3.0.0 + with: + mc: ${{ matrix.version.mc }} + mc-runtime-test: none + modloader: ${{ matrix.version.modloader }} + regex: ${{ matrix.version.regex }} + java: ${{ matrix.version.java }} + xvfb: ${{ matrix.xvfb }} + headlessmc-command: ${{ !matrix.xvfb && '-lwjgl' || '' }} --retries 3 --jvm -Djava.awt.headless=true + + release: + if: needs.release-please.outputs.release_created == 'true' + needs: + - release-please + - build + name: Release + permissions: + contents: write + runs-on: blacksmith-2vcpu-ubuntu-2204 + steps: + - name: Download artifacts + uses: actions/download-artifact@v4 + + - name: Upload release artifacts + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: jars-*/*-release.jar + tag: ${{ needs.release-please.outputs.tag_name }} + overwrite: true + file_glob: true + + - name: Upload api artifacts + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: jars-*/*-api-*.jar # TODO: make API jar names consistent with release jars, then simplify away this step + tag: ${{ needs.release-please.outputs.tag_name }} + overwrite: true + file_glob: true + + clean: # TODO: run this conditionally per #22 + name: Clean up + needs: + - run + - release + runs-on: blacksmith-2vcpu-ubuntu-2204 + steps: + - name: Delete artifacts + uses: geekyeggo/delete-artifact@v5.1.0 + with: + name: | + jars-* + failOnError: false diff --git a/README.md b/README.md index 453bd22..0d46843 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Mc-Runtime-Test-Mods can be configured via a few System properties. These are listed and documented [here](api/src/main/java/io/github/headlesshq/mcrtapi/McRuntimeTest.java). You can set these properties in the mc-runtime-test action with the headlessmc-command input, like this: ```yaml -uses: 3arthqu4ke/mc-runtime-test@3.0.0 +uses: headlesshq/mc-runtime-test@3.0.0 with: mc: 1.21.4 modloader: fabric diff --git a/api/build.gradle b/api/build.gradle index f4c85f7..543fbfa 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -1,7 +1,7 @@ plugins { id 'java' id 'maven-publish' - id 'systems.manifold.manifold-gradle-plugin' version '0.0.2-alpha' + id 'systems.manifold.manifold-gradle-plugin' } group 'io.github.headlesshq' @@ -15,7 +15,7 @@ repositories { } dependencies { - annotationProcessor("systems.manifold:manifold-preprocessor:${rootProject.manifold_version}") + annotationProcessor("systems.manifold:manifold-preprocessor:2024.1.37") } compileJava { @@ -27,11 +27,12 @@ compileJava { afterEvaluate { publishing { publications { - "${name.toLowerCase()}"(MavenPublication) { - ((MavenPublication) it).groupId "${group}" - ((MavenPublication) it).artifactId "${archivesBaseName.toLowerCase()}" - ((MavenPublication) it).version "${version}" - from components.java + create(name.toLowerCase(), MavenPublication) { + groupId = group + artifactId = archivesBaseName.toLowerCase() + version = project.version // or just version + + from(components.java) } } diff --git a/api/build.properties b/api/build.properties new file mode 100644 index 0000000..c7d9f5c --- /dev/null +++ b/api/build.properties @@ -0,0 +1,21 @@ +MC_1_7_10=0 +MC_1_8_9=1 +MC_1_12_2=2 +MC_1_16_5=3 +MC_1_17_1=4 +MC_1_18_2=5 +MC_1_19=6 +MC_1_19_1=7 +MC_1_19_2=8 +MC_1_19_3=9 +MC_1_19_4=10 +MC_1_20_1=11 +MC_1_20_2=12 +MC_1_20_3=13 +MC_1_20_4=14 +MC_1_20_6=15 +MC_1_21=16 +MC_1_21_1=17 +MC_1_21_3=18 +MC_1_21_4=19 +MC_VER=19 \ No newline at end of file diff --git a/build.gradle b/build.gradle deleted file mode 100644 index d69f8e9..0000000 --- a/build.gradle +++ /dev/null @@ -1,316 +0,0 @@ -import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar - -plugins { - id 'com.github.johnrengelman.shadow' version '8.1.1' - id 'java' - id 'maven-publish' - id 'xyz.wagyourtail.unimined' version '1.3.11' - id 'systems.manifold.manifold-gradle-plugin' version '0.0.2-alpha' - // TODO: do we want forgix? - // id "io.github.pacifistmc.forgix" version "1.2.9" -} - -def setupPreprocessors(List mc_versions, int mc_index) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < mc_versions.size(); i++) { - String verStr = mc_versions[i].replace(".", "_"); - sb.append("MC_" + verStr + "=" + i.toString() + "\n"); - if (mc_index == i) { - sb.append("MC_VER=" + i.toString() + "\n"); - } - } - - new File(projectDir, "build.properties").text = sb.toString() -} - -project.gradle.ext.getProperties().each { prop -> - rootProject.ext.set(prop.key, prop.value) -} - -setupPreprocessors(rootProject.mc_versions, rootProject.mc_index) - -manifold { - manifoldVersion = rootProject.manifold_version -} - -group 'io.github.headlesshq' -version "$minecraft_version-${project(':api').project_version}" - -base { - archivesName = 'mc-runtime-test' -} - -sourceCompatibility = JavaVersion.VERSION_1_8 -targetCompatibility = JavaVersion.VERSION_1_8 - -sourceSets { - fabric { - compileClasspath += sourceSets.main.output - runtimeClasspath += sourceSets.main.output - } - lexforge { - compileClasspath += sourceSets.main.output - runtimeClasspath += sourceSets.main.output - } - if (project.loaders.contains('neoforge')) { - neoforge - } -} - -repositories { - maven { - url = "https://maven.fabricmc.net/" - } - maven { - name = "sponge" - url = "https://repo.spongepowered.org/maven" - } - maven { - name = 'impactdevelopment-repo' - url = 'https://impactdevelopment.github.io/maven/' - } - mavenCentral() - maven { - name = "wagyourtail releases" - url = "https://maven.wagyourtail.xyz/releases" - } -} - -unimined.minecraft { - version project.minecraft_version - - mappings { - for (mapping in ((String) project.mappings).split(",")) { - String[] split = mapping.split(':') - String name = split[0] - if (name == 'searge') { - searge() - } else if (name == 'mcp') { - mcp("stable", split[1]) - } else if (name == 'mojmap') { - mojmap() - // intermediary() - // yarn(1) - devFallbackNamespace "mojmap" - } - } - } - - defaultRemapJar = false -} - -unimined.minecraft(sourceSets.fabric) { - combineWith(sourceSets.main) - - if (project.loaders.contains('legacyfabric')) { - legacyFabric { - loader project.fabric_version - } - } else { - fabric { - loader project.fabric_version - } - } - - defaultRemapJar = true -} - -if (project.loaders.contains('neoforge')) { - unimined.minecraft(sourceSets.neoforge) { - combineWith(sourceSets.main) - - neoForge { - loader project.neoforge_version - mixinConfig 'mc_runtime_test.mixins.json' - } - - minecraftRemapper.config { - ignoreConflicts(true) - } - - defaultRemapJar = true - } -} - -unimined.minecraft(sourceSets.lexforge) { - combineWith(sourceSets.main) - - minecraftForge { - loader project.lexforge_version - mixinConfig 'mc_runtime_test.mixins.json' - } - - defaultRemapJar = true -} - -configurations { - mainImplementation - implementation { - extendsFrom lexforgeImplementation - extendsFrom fabricImplementation - } - - jarLibs - implementation.extendsFrom jarLibs -} - -sourceSets { - main { - compileClasspath += configurations.mainImplementation - runtimeClasspath += configurations.mainImplementation - } -} - -dependencies { - annotationProcessor("systems.manifold:manifold-preprocessor:${rootProject.manifold_version}") - lexforgeAnnotationProcessor("systems.manifold:manifold-preprocessor:${rootProject.manifold_version}") - fabricAnnotationProcessor("systems.manifold:manifold-preprocessor:${rootProject.manifold_version}") - if (project.loaders.contains('neoforge')) { - neoforgeAnnotationProcessor("systems.manifold:manifold-preprocessor:${rootProject.manifold_version}") - } - - if (project.loaders.contains('legacyfabric')) { - implementation 'net.minecraft:launchwrapper:1.12' - jarLibs 'com.github.ImpactDevelopment:SimpleTweaker:1.2' - jarLibs('org.spongepowered:mixin:0.8.5') { - exclude module: 'launchwrapper' - exclude module: 'guava' - exclude module: 'gson' - exclude module: 'commons-io' - } - } else { - implementation('org.spongepowered:mixin:0.8.5') { - exclude module: 'launchwrapper' - exclude module: 'guava' - exclude module: 'gson' - exclude module: 'commons-io' - } - } - - // yes, I actually want this at runtime to use assertions! - jarLibs 'org.junit.jupiter:junit-jupiter-api:5.10.1' - jarLibs project(':api') -} - -def mc_platforms = ['Fabric', 'Lexforge'] -if (project.loaders.contains('neoforge')) { - mc_platforms = ['Fabric', 'Neoforge', 'Lexforge'] -} - -for (String platform_capitalized : mc_platforms) { - def platform = platform_capitalized.toLowerCase() - def remapJarTask = tasks.named("remap${platform_capitalized}Jar", AbstractArchiveTask).get() - def shadowTask = tasks.register("${platform}ShadowJar", ShadowJar) { - dependsOn(remapJarTask) - it.group = 'build' - it.archiveClassifier = "${platform}-release" - from remapJarTask.outputs - it.configurations += [ project.configurations.jarLibs ] - // Mixin is signed - exclude 'META-INF/*.RSA' - exclude 'META-INF/*.SF' - exclude "**/module-info.class" - } - - tasks.named('build') { finalizedBy(shadowTask) } -} - - -jar { - enabled = false -} - -tasks.withType(org.gradle.jvm.tasks.Jar).configureEach { - from("LICENSE") { - duplicatesStrategy = DuplicatesStrategy.INCLUDE - rename { "${it}_${project.archivesBaseName}" } - } - - manifest { - if (project.loaders.contains('legacyfabric')) { - attributes( - 'Implementation-Title': 'MC-Runtime-Test', - 'TweakClass': 'io.github.headlesshq.mcrt.tweaker.McRuntimeTestTweaker', - 'MixinConfigs': "mc_runtime_test.mixins.json", - 'Implementation-Version': project.version, - ) - } else { // no TweakClass - attributes( - 'Implementation-Title': 'MC-Runtime-Test', - 'MixinConfigs': "mc_runtime_test.mixins.json", - 'Implementation-Version': project.version, - ) - } - } -} - -def expansions = [ - version : project.version, - mc_version : minecraft_version, -] - -processFabricResources { - filesMatching("fabric.mod.json") { - expand expansions - } -} - -processLexforgeResources { - filesMatching("META-INF/mods.toml") { - expand expansions - } - - filesMatching("mcmod.info") { - expand expansions - } -} - -if (project.loaders.contains('neoforge')) { - processNeoforgeResources { - // TODO: this is legacy neoforge, newer neoforge needs the other toml, check if its ok to leave this in - filesMatching("META-INF/mods.toml") { - expand expansions - } - - filesMatching("META-INF/neoforge.mods.toml") { - expand expansions - } - } -} - -afterEvaluate { - publishing { - publications { - "${name.toLowerCase()}"(MavenPublication) { - ((MavenPublication) it).groupId "${group}" - ((MavenPublication) it).artifactId "${archivesBaseName.toLowerCase()}" - ((MavenPublication) it).version "${version}" - from components.java - for (String platform: mc_platforms) { - String platform_lower = platform.toLowerCase() - artifact tasks.named("${platform_lower}Jar").get() - artifact tasks.named("remap${platform}Jar").get() - artifact tasks.named("${platform_lower}ShadowJar").get() - } - } - } - - repositories { - if (System.getenv('DEPLOY_TO_GITHUB_PACKAGES_URL') == null) { - maven { - name = 'BuildDirMaven' - url = rootProject.projectDir.toPath().parent.resolve('build').resolve('maven') - } - } else { - maven { - name = 'GithubPagesMaven' - url = System.getenv('DEPLOY_TO_GITHUB_PACKAGES_URL') - credentials { - username = System.getenv('GITHUB_USER') - password = System.getenv('GITHUB_TOKEN') - } - } - } - } - } -} diff --git a/build.properties b/build.properties index ae37856..36f72a3 100644 --- a/build.properties +++ b/build.properties @@ -1,21 +1,20 @@ -MC_1_12_2=0 -MC_1_16_5=1 -MC_1_17_1=2 -MC_1_18_2=3 -MC_1_19=4 -MC_1_19_1=5 -MC_1_19_2=6 -MC_1_19_3=7 -MC_1_19_4=8 -MC_1_20_1=9 -MC_1_20_2=10 -MC_1_20_3=11 -MC_1_20_4=12 -MC_1_20_6=13 -MC_1_21=14 -MC_1_21_1=15 -MC_1_21_3=16 -MC_1_21_4=17 -MC_1_7_10=18 -MC_VER=18 -MC_1_8_9=19 +MC_1_7_10=0 +MC_1_8_9=1 +MC_1_12_2=2 +MC_1_16_5=3 +MC_1_17_1=4 +MC_1_18_2=5 +MC_1_19=6 +MC_1_19_1=7 +MC_1_19_2=8 +MC_1_19_3=9 +MC_1_19_4=10 +MC_1_20_1=11 +MC_1_20_2=12 +MC_1_20_3=13 +MC_1_20_4=14 +MC_1_20_6=15 +MC_1_21=16 +MC_1_21_1=17 +MC_1_21_3=18 +MC_1_21_4=19 diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle new file mode 100644 index 0000000..3ec46ed --- /dev/null +++ b/buildSrc/build.gradle @@ -0,0 +1,59 @@ +plugins { + id 'groovy' + id 'java-gradle-plugin' + id 'maven-publish' +} + +group = "io.github.headlesshq.mcrt" +version = "1.0.0" + +repositories { + maven { + url = uri("https://maven.pkg.github.com/marcusk-studio/unimined") + credentials { + username = System.getenv("GITHUB_USER") + password = System.getenv("GITHUB_TOKEN") + } + } + maven { + url = "https://maven.fabricmc.net/" + } + maven { + name = "sponge" + url = "https://repo.spongepowered.org/maven" + } + maven { + name = 'impactdevelopment-repo' + url = 'https://impactdevelopment.github.io/maven/' + } + mavenCentral() + maven { + name = "wagyourtail releases" + url = "https://maven.wagyourtail.xyz/releases" + } + maven { + url = "https://repo.spongepowered.org/maven" + } + maven { + url = "https://impactdevelopment.github.io/maven/" + } +} + +dependencies { + implementation gradleApi() + implementation localGroovy() + + implementation("com.gradleup.shadow:shadow-gradle-plugin:9.0.0-beta4") + implementation("xyz.wagyourtail.unimined:unimined:1.3.11-SNAPSHOT") + implementation("systems.manifold:manifold-gradle-plugin:0.0.2-alpha") + +} + +gradlePlugin { + plugins { + create("mcrtJavaConventions") { + id = "io.github.headlesshq.mcrt.java-conventions" + implementationClass = "io.github.headlesshq.mcrt.VersionConventionPlugin" + } + } +} diff --git a/buildSrc/build.properties b/buildSrc/build.properties new file mode 100644 index 0000000..b8654ce --- /dev/null +++ b/buildSrc/build.properties @@ -0,0 +1,21 @@ +MC_1_7_10=0 +MC_1_8_9=1 +MC_1_12_2=2 +MC_1_16_5=3 +MC_1_17_1=4 +MC_1_18_2=5 +MC_1_19=6 +MC_1_19_1=7 +MC_1_19_2=8 +MC_1_19_3=9 +MC_1_19_4=10 +MC_1_20_1=11 +MC_1_20_2=12 +MC_1_20_3=13 +MC_1_20_4=14 +MC_1_20_6=15 +MC_1_21=16 +MC_1_21_1=17 +MC_1_21_3=18 +MC_1_21_4=19 +MC_VER=19 diff --git a/buildSrc/settings.gradle b/buildSrc/settings.gradle new file mode 100644 index 0000000..cfd2e9c --- /dev/null +++ b/buildSrc/settings.gradle @@ -0,0 +1,35 @@ + +pluginManagement { + repositories { + mavenCentral() + maven { + url = "https://repo.spongepowered.org/maven" + } + maven { + url = "https://impactdevelopment.github.io/maven/" + } + maven { + url = "https://maven.neoforged.net/releases" + } + maven { + url = "https://maven.minecraftforge.net/" + } + maven { + url = "https://maven.fabricmc.net/" + } + maven { + url = "https://maven.wagyourtail.xyz/releases" + } + maven { + url 'https://3arthqu4ke.github.io/maven' + } + maven { + url = "https://maven.wagyourtail.xyz/snapshots" + } + gradlePluginPortal() { + content { + excludeGroup("org.apache.logging.log4j") + } + } + } +} diff --git a/buildSrc/src/main/groovy/META-INF/gradle-plugins/io.github.headlesshq.mcrt.java-conventions.properties b/buildSrc/src/main/groovy/META-INF/gradle-plugins/io.github.headlesshq.mcrt.java-conventions.properties new file mode 100644 index 0000000..6c6231a --- /dev/null +++ b/buildSrc/src/main/groovy/META-INF/gradle-plugins/io.github.headlesshq.mcrt.java-conventions.properties @@ -0,0 +1 @@ +implementation-class=io.github.headlesshq.mcrt.VersionConventionPlugin diff --git a/buildSrc/src/main/groovy/io/github/headlesshq/mcrt/VersionConventionPlugin.groovy b/buildSrc/src/main/groovy/io/github/headlesshq/mcrt/VersionConventionPlugin.groovy new file mode 100644 index 0000000..00845d5 --- /dev/null +++ b/buildSrc/src/main/groovy/io/github/headlesshq/mcrt/VersionConventionPlugin.groovy @@ -0,0 +1,331 @@ +package io.github.headlesshq.mcrt + +import org.gradle.api.* +import org.gradle.api.file.DuplicatesStrategy +import org.gradle.api.publish.maven.MavenPublication +import org.gradle.api.tasks.bundling.AbstractArchiveTask +import org.gradle.api.tasks.compile.JavaCompile +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar + +class VersionConventionPlugin implements Plugin { + @Override + void apply(Project project) { + // Apply necessary plugins + project.pluginManager.apply('java') + project.pluginManager.apply('maven-publish') + project.pluginManager.apply('xyz.wagyourtail.unimined') + project.pluginManager.apply('com.gradleup.shadow') + project.pluginManager.apply('systems.manifold.manifold-gradle-plugin') + + def verId = project.name + + // Skip configuration for certain subprojects + if (verId == 'shared' || verId == 'api') return + + project.logger.lifecycle("Configuring subproject: ${project.path}, version=${verId}") + + // Load version-specific properties + def propsFile = project.file("gradle.properties") + if (!propsFile.exists()) { + throw new GradleException("Properties file not found: ${propsFile}") + } + + def props = new Properties() + propsFile.withInputStream { props.load(it) } + + def currentMinecraftVersion = props.getProperty('minecraft_version') + def currentLexforgeVersion = props.getProperty('lexforge_version') + def currentNeoForgeVersion = props.getProperty('neoforge_version') + def currentLoaders = props.getProperty('loaders')?.split(',')*.trim() + def currentMappings = props.getProperty('mappings') + + // Load global properties + def globalPropsFile = project.rootProject.file("build.properties") + if (!globalPropsFile.exists()) { + throw new GradleException("Global build properties file not found: ${globalPropsFile}") + } + + def globalProps = new Properties() + globalPropsFile.withInputStream { globalProps.load(it) } + + def mappingKey = "MC_${currentMinecraftVersion.replace('.', '_')}" + def mcVerValue = globalProps.getProperty(mappingKey) + + // Log loaded properties + project.logger.lifecycle("'minecraft_version' = ${currentMinecraftVersion}") + project.logger.lifecycle("'lexforge_version' = ${currentLexforgeVersion}") + project.logger.lifecycle("'neoforge_version' = ${currentNeoForgeVersion}") + project.logger.lifecycle("'loaders' = ${currentLoaders}") + project.logger.lifecycle("'mappings' = ${currentMappings}") + project.logger.lifecycle("MC_VER = ${mcVerValue}") + + // Project version + def projectVersion = project.rootProject.hasProperty('project_version') ? project.rootProject.property('project_version') : 'unknown' + + // Build version + def buildVersion = "${currentMinecraftVersion}-${project_version}" + + // Configure source sets + configureSourceSets(project, currentLoaders) + + // Configure Unimined + configureUnimined(project, currentMinecraftVersion, currentMappings, currentLoaders, currentLexforgeVersion, currentNeoForgeVersion) + + // Configure configurations + configureConfigurations(project) + + // Configure dependencies + configureDependencies(project, currentLoaders) + + // Configure tasks + configureTasks(project, mcVerValue, buildVersion, currentMinecraftVersion, currentLoaders) + + // Configure publishing + configurePublishing(project, buildVersion, currentLoaders) + } + + private void configureSourceSets(Project project, def currentLoaders) { + project.sourceSets { + main { + // java { + // srcDirs += "${project.rootDir}/shared/src/main/java" + // } + // resources { + // srcDirs += "${project.rootDir}/shared/src/main/resources" + // } + compileClasspath += project.configurations.mainImplementation + runtimeClasspath += project.configurations.mainImplementation + } + + fabric { + compileClasspath += main.output + runtimeClasspath += main.output + } + + lexforge { + compileClasspath += main.output + runtimeClasspath += main.output + } + + if (currentLoaders.contains('neoforge')) { + neoforge { + compileClasspath += main.output + runtimeClasspath += main.output + } + } + } + } + + private void configureUnimined(Project project, def minecraftVersion, def mappings, def loaders, def lexforgeVersion, def neoforgeVersion) { + project.unimined.minecraft { + version minecraftVersion + mappings { + mappings?.split(',')?.each { mapping -> + def split = mapping.split(':') + switch (split[0]) { + case 'searge': searge(); break + case 'mcp': mcp("stable", split[1]); break + case 'mojmap': mojmap(); devFallbackNamespace "mojmap"; break + } + } + } + defaultRemapJar = false + } + + if (loaders.contains('legacyfabric')) { + project.unimined.minecraft(project.sourceSets.fabric) { + combineWith(project.sourceSets.main) + legacyFabric { loader project.findProperty('fabric_version') } + defaultRemapJar = true + } + } else { + project.unimined.minecraft(project.sourceSets.fabric) { + combineWith(project.sourceSets.main) + fabric { loader project.findProperty('fabric_version') } + defaultRemapJar = true + } + } + + if (loaders.contains('neoforge')) { + project.unimined.minecraft(project.sourceSets.neoforge) { + combineWith(project.sourceSets.main) + neoForge { + loader neoforgeVersion + mixinConfig 'mc_runtime_test.mixins.json' + } + minecraftRemapper.config { ignoreConflicts(true) } + defaultRemapJar = true + } + } + + project.unimined.minecraft(project.sourceSets.lexforge) { + combineWith(project.sourceSets.main) + minecraftForge { + loader lexforgeVersion + mixinConfig 'mc_runtime_test.mixins.json' + } + defaultRemapJar = true + } + } + + private void configureConfigurations(Project project) { + project.configurations { + mainImplementation + implementation { + extendsFrom lexforgeImplementation + extendsFrom fabricImplementation + } + jarLibs + implementation.extendsFrom jarLibs + } + } + + private void configureDependencies(Project project, def loaders) { + project.dependencies { + annotationProcessor("systems.manifold:manifold-preprocessor:2024.1.37") + lexforgeAnnotationProcessor("systems.manifold:manifold-preprocessor:2024.1.37") + fabricAnnotationProcessor("systems.manifold:manifold-preprocessor:2024.1.37") + if (loaders.contains('neoforge')) { + neoforgeAnnotationProcessor("systems.manifold:manifold-preprocessor:2024.1.37") + } + + if (loaders.contains('legacyfabric')) { + implementation 'net.minecraft:launchwrapper:1.12' + jarLibs 'com.github.ImpactDevelopment:SimpleTweaker:1.2' + jarLibs('org.spongepowered:mixin:0.7.11-SNAPSHOT') { + exclude module: 'launchwrapper' + exclude module: 'guava' + exclude module: 'gson' + exclude module: 'commons-io' + } + } else { + implementation('org.spongepowered:mixin:0.8.5') { + exclude module: 'launchwrapper' + exclude module: 'guava' + exclude module: 'gson' + exclude module: 'commons-io' + } + } + + jarLibs 'org.junit.jupiter:junit-jupiter-api:5.10.1' + jarLibs project.project(':api') + } + } + + private void configureTasks(Project project, def mcVerValue, def buildVersion, def currentMinecraftVersion, def currentLoaders) { + def mcPlatforms = currentLoaders.contains('neoforge') ? ['Fabric', 'Neoforge', 'Lexforge'] : ['Fabric', 'Lexforge'] + + mcPlatforms.each { platform -> + def platformLower = platform.toLowerCase() + def remapJarTask = project.tasks.register("remap${platform}Jar", AbstractArchiveTask) { + group = 'build' + } + + def shadowTask = project.tasks.register("${platformLower}ShadowJar", ShadowJar) { + dependsOn(remapJarTask) + group = 'build' + archiveClassifier = "${platformLower}-release" + from(remapJarTask.map { it.outputs }) + configurations += [project.configurations.jarLibs] + exclude 'META-INF/*.RSA' + exclude 'META-INF/*.SF' + exclude "**/module-info.class" + } + + project.tasks.named('build').configure { + finalizedBy(shadowTask) + } + } + + project.tasks.named('jar').configure { + enabled = false + } + + project.tasks.withType(JavaCompile).configureEach { + options.compilerArgs += ["-AMC_VER=${mcVerValue}"] + } + + project.tasks.withType(org.gradle.jvm.tasks.Jar).configureEach { + from("LICENSE") { + duplicatesStrategy = DuplicatesStrategy.INCLUDE + rename { "${it}_${project.archivesBaseName}" } + } + manifest { + attributes( + 'Implementation-Title': 'MC-Runtime-Test', + 'MixinConfigs': "mc_runtime_test.mixins.json", + 'Implementation-Version': buildVersion + ) + if (currentLoaders.contains('legacyfabric')) { + attributes( + 'TweakClass': 'io.github.headlesshq.mcrt.tweaker.McRuntimeTestTweaker' + ) + } + } + } + + def expansions = [ + version : buildVersion, + mc_version : currentMinecraftVersion, + ] + + project.tasks.named('processFabricResources').configure { + filesMatching("fabric.mod.json") { + expand(expansions) + } + } + + project.tasks.named('processLexforgeResources').configure { + filesMatching("META-INF/mods.toml") { expand(expansions) } + filesMatching("mcmod.info") { expand(expansions) } + } + + if (currentLoaders.contains('neoforge')) { + project.tasks.named('processNeoforgeResources').configure { + filesMatching("META-INF/mods.toml") { expand(expansions) } + filesMatching("META-INF/neoforge.mods.toml") { expand(expansions) } + } + } + } + + private void configurePublishing(Project project, def buildVersion, def loaders) { + def mcPlatforms = loaders.contains('neoforge') ? ['Fabric', 'Neoforge', 'Lexforge'] : ['Fabric', 'Lexforge'] + + project.publishing { + publications { + create(project.name.toLowerCase(), MavenPublication) { + groupId = project.group + artifactId = project.archivesBaseName.toLowerCase() + version = project.version + + from(project.components.java) + + mcPlatforms.each { platform -> + def platformLower = platform.toLowerCase() + artifact project.tasks.named("${platformLower}Jar").get() + artifact project.tasks.named("remap${platform}Jar").get() + artifact project.tasks.named("${platformLower}ShadowJar").get() + } + } + } + + repositories { + if (System.getenv('DEPLOY_TO_GITHUB_PACKAGES_URL') != null) { + maven { + name = 'GithubPagesMaven' + url = System.getenv('DEPLOY_TO_GITHUB_PACKAGES_URL') + credentials { + username = System.getenv('GITHUB_ACTOR') + password = System.getenv('GITHUB_TOKEN') + } + } + } else { + maven { + name = 'BuildDirMaven' + url = project.layout.buildDirectory.dir('maven').get() + } + } + } + } + } +} diff --git a/gradle.properties b/gradle.properties index 9d29308..98267c7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,8 @@ org.gradle.jvmargs = -Xmx2G -#org.gradle.parallel=true +org.gradle.parallel=true org.gradle.caching=true +# org.gradle.configureondemand=false +org.gradle.unsafe.isolated-projects=true # which version is used by the pre-processor minecraft_version = 1.21.4 @@ -10,3 +12,4 @@ hmc.lwjgl = false manifold_version=2024.1.37 fabric_version = 0.14.20 +project_version = 3.0.0 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 7e14f33..574c616 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Fri Dec 06 20:26:09 CET 2024 +gi#Fri Dec 06 20:26:09 CET 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000..6dc148f --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "bootstrap-sha": "42445f09578667ad2a3b620ca669ba1c13c1c1ac", + "packages": { + ".": { + "include-component-in-tag": false, + "include-v-in-tag": false, + "package-name": "mc-runtime-test", + "release-type": "simple", + "extra-files": [ + "action.yml", + "api/gradle.properties" + ] + } + } +} diff --git a/settings.gradle b/settings.gradle index 531d729..4ef9484 100644 --- a/settings.gradle +++ b/settings.gradle @@ -15,41 +15,67 @@ pluginManagement { url = "https://maven.wagyourtail.xyz/releases" } maven { - url 'https://3arthqu4ke.github.io/maven' + url = "https://3arthqu4ke.github.io/maven" } maven { url = "https://maven.wagyourtail.xyz/snapshots" } + maven { + url = "https://repo.spongepowered.org/maven" + } + maven { + url = "https://impactdevelopment.github.io/maven/" + } gradlePluginPortal() { content { excludeGroup("org.apache.logging.log4j") } } } + plugins { + id "systems.manifold.manifold-gradle-plugin" version "0.0.2-alpha" + } } -def loadVersions() { - def versions = fileTree("versions").files.name - for (int i = 0; i < versions.size(); i++) { - versions[i] = versions[i].replaceAll("\\.properties", "") + +def versionDirs = new File(rootDir, "versions").listFiles() + .findAll { it.isDirectory() } // Filter only directories + .findAll { new File(it, "build.gradle").exists() } // Filter directories containing build.gradle + .collect { it.name } // Collect directory names + +versionDirs.each { include ":versions:${it}" } +println("${versionDirs}") + +def setupPreprocessors(List mcVersions) { + StringBuilder sb = new StringBuilder(); + mcVersions.eachWithIndex { ver, i -> + String verStr = ver.replace(".", "_"); + sb.append("MC_${verStr}=${i}\n") } + new File(rootDir, "build.properties").text = sb.toString() +} + +def loadVersions(def directoryNames) { + // Sort by semantic version + def sortedVersions = directoryNames.sort { a, b -> + def partsA = a.tokenize('.')*.toInteger() + def partsB = b.tokenize('.')*.toInteger() - versions.sort() + int comparison = partsA[0] <=> partsB[0] + if (comparison != 0) return comparison - var mcVersion = minecraft_version - var mcIndex = versions.indexOf(minecraft_version) - def props = new Properties() - props.load(new FileInputStream("$rootDir/versions/" + "$mcVersion" + ".properties")) + comparison = partsA[1] <=> partsB[1] + if (comparison != 0) return comparison - props.each { prop -> - gradle.ext.set(prop.key, prop.value) + return partsA[2] <=> partsB[2] } - gradle.ext.mc_versions = versions - gradle.ext.mc_index = mcIndex + //setupPreprocessors(sortedVersions) + gradle.ext.mc_versions = sortedVersions } -loadVersions() +loadVersions(versionDirs) include 'api' +include ':shared' rootProject.name = 'mc-runtime-test' diff --git a/shared/build.gradle,dis b/shared/build.gradle,dis new file mode 100644 index 0000000..89919f6 --- /dev/null +++ b/shared/build.gradle,dis @@ -0,0 +1,49 @@ +plugins { + //id 'com.gradleup.shadow' version '9.0.0-beta4' + id 'java' + id 'maven-publish' + id 'systems.manifold.manifold-gradle-plugin' + id 'io.github.headlesshq.mcrt.java-conventions' + // TODO: do we want forgix? + // id "io.github.pacifistmc.forgix" version "1.2.9" +} + +// TODO: is this necessary? +project.gradle.ext.getProperties().each { prop -> + rootProject.ext.set(prop.key, prop.value) +} + +dependencies { + annotationProcessor("systems.manifold:manifold-preprocessor:2024.1.37") +} + +manifold { + manifoldVersion = '2024.1.37' //rootProject.manifold_version +} + +group = 'io.github.headlesshq' +base { + archivesName = 'mc-runtime-test' +} + +sourceCompatibility = JavaVersion.VERSION_1_8 +targetCompatibility = JavaVersion.VERSION_1_8 + +repositories { + maven { + url = "https://maven.fabricmc.net/" + } + maven { + name = "sponge" + url = "https://repo.spongepowered.org/maven" + } + maven { + name = 'impactdevelopment-repo' + url = 'https://impactdevelopment.github.io/maven/' + } + mavenCentral() + maven { + name = "wagyourtail releases" + url = "https://maven.wagyourtail.xyz/releases" + } +} diff --git a/shared/build.properties b/shared/build.properties new file mode 100644 index 0000000..c7d9f5c --- /dev/null +++ b/shared/build.properties @@ -0,0 +1,21 @@ +MC_1_7_10=0 +MC_1_8_9=1 +MC_1_12_2=2 +MC_1_16_5=3 +MC_1_17_1=4 +MC_1_18_2=5 +MC_1_19=6 +MC_1_19_1=7 +MC_1_19_2=8 +MC_1_19_3=9 +MC_1_19_4=10 +MC_1_20_1=11 +MC_1_20_2=12 +MC_1_20_3=13 +MC_1_20_4=14 +MC_1_20_6=15 +MC_1_21=16 +MC_1_21_1=17 +MC_1_21_3=18 +MC_1_21_4=19 +MC_VER=19 \ No newline at end of file diff --git a/shared/gradle.properties b/shared/gradle.properties new file mode 100644 index 0000000..9d29308 --- /dev/null +++ b/shared/gradle.properties @@ -0,0 +1,12 @@ +org.gradle.jvmargs = -Xmx2G +#org.gradle.parallel=true +org.gradle.caching=true + +# which version is used by the pre-processor +minecraft_version = 1.21.4 +# Whether to use the headlessmc lwjgl agent or not +hmc.lwjgl = false + +manifold_version=2024.1.37 + +fabric_version = 0.14.20 diff --git a/shared/settings.gradle b/shared/settings.gradle new file mode 100644 index 0000000..8f65dfa --- /dev/null +++ b/shared/settings.gradle @@ -0,0 +1,29 @@ + +pluginManagement { + repositories { + mavenCentral() + maven { + url = "https://maven.neoforged.net/releases" + } + maven { + url = "https://maven.minecraftforge.net/" + } + maven { + url = "https://maven.fabricmc.net/" + } + maven { + url = "https://maven.wagyourtail.xyz/releases" + } + maven { + url = "https://3arthqu4ke.github.io/maven" + } + maven { + url = "https://maven.wagyourtail.xyz/snapshots" + } + gradlePluginPortal() { + content { + excludeGroup("org.apache.logging.log4j") + } + } + } +} diff --git a/src/fabric/resources/fabric.mod.json b/shared/src/fabric/resources/fabric.mod.json similarity index 100% rename from src/fabric/resources/fabric.mod.json rename to shared/src/fabric/resources/fabric.mod.json diff --git a/src/lexforge/java/io/github/headlesshq/mcrt/forge/ForgeMod.java b/shared/src/lexforge/java/io/github/headlesshq/mcrt/forge/ForgeMod.java similarity index 100% rename from src/lexforge/java/io/github/headlesshq/mcrt/forge/ForgeMod.java rename to shared/src/lexforge/java/io/github/headlesshq/mcrt/forge/ForgeMod.java diff --git a/src/lexforge/resources/META-INF/mods.toml b/shared/src/lexforge/resources/META-INF/mods.toml similarity index 100% rename from src/lexforge/resources/META-INF/mods.toml rename to shared/src/lexforge/resources/META-INF/mods.toml diff --git a/src/lexforge/resources/mcmod.info b/shared/src/lexforge/resources/mcmod.info similarity index 100% rename from src/lexforge/resources/mcmod.info rename to shared/src/lexforge/resources/mcmod.info diff --git a/src/main/java/io/github/headlesshq/mcrt/McGameTestRunner.java b/shared/src/main/java/io/github/headlesshq/mcrt/McGameTestRunner.java similarity index 99% rename from src/main/java/io/github/headlesshq/mcrt/McGameTestRunner.java rename to shared/src/main/java/io/github/headlesshq/mcrt/McGameTestRunner.java index 18b55ac..f7915c1 100644 --- a/src/main/java/io/github/headlesshq/mcrt/McGameTestRunner.java +++ b/shared/src/main/java/io/github/headlesshq/mcrt/McGameTestRunner.java @@ -71,7 +71,7 @@ public static MultipleTestTracker runGameTests(UUID playerUUID, MinecraftServer Rotation rotation = StructureUtils.getRotationForRotationSteps(0); #if MC_VER == MC_1_16_5 Collection tests = GameTestRunner.runTests(testFunctions, pos, rotation, level, GameTestTicker.singleton, 8); - #elif MC_VER < 1_20_6 + #elif MC_VER < MC_1_20_6 Collection tests = GameTestRunner.runTests(testFunctions, pos, rotation, level, GameTestTicker.SINGLETON, 8); #endif MultipleTestTracker multipleTestTracker = new MultipleTestTracker(tests); diff --git a/src/main/java/io/github/headlesshq/mcrt/WorldCreator.java b/shared/src/main/java/io/github/headlesshq/mcrt/WorldCreator.java similarity index 100% rename from src/main/java/io/github/headlesshq/mcrt/WorldCreator.java rename to shared/src/main/java/io/github/headlesshq/mcrt/WorldCreator.java diff --git a/src/main/java/io/github/headlesshq/mcrt/mixin/ICreateWorldScreen.java b/shared/src/main/java/io/github/headlesshq/mcrt/mixin/ICreateWorldScreen.java similarity index 100% rename from src/main/java/io/github/headlesshq/mcrt/mixin/ICreateWorldScreen.java rename to shared/src/main/java/io/github/headlesshq/mcrt/mixin/ICreateWorldScreen.java diff --git a/src/main/java/io/github/headlesshq/mcrt/mixin/MixinLegacyMinecraft.java b/shared/src/main/java/io/github/headlesshq/mcrt/mixin/MixinLegacyMinecraft.java similarity index 100% rename from src/main/java/io/github/headlesshq/mcrt/mixin/MixinLegacyMinecraft.java rename to shared/src/main/java/io/github/headlesshq/mcrt/mixin/MixinLegacyMinecraft.java diff --git a/src/main/java/io/github/headlesshq/mcrt/mixin/MixinMinecraft.java b/shared/src/main/java/io/github/headlesshq/mcrt/mixin/MixinMinecraft.java similarity index 100% rename from src/main/java/io/github/headlesshq/mcrt/mixin/MixinMinecraft.java rename to shared/src/main/java/io/github/headlesshq/mcrt/mixin/MixinMinecraft.java diff --git a/src/main/java/io/github/headlesshq/mcrt/tweaker/McRuntimeTestTweaker.java b/shared/src/main/java/io/github/headlesshq/mcrt/tweaker/McRuntimeTestTweaker.java similarity index 100% rename from src/main/java/io/github/headlesshq/mcrt/tweaker/McRuntimeTestTweaker.java rename to shared/src/main/java/io/github/headlesshq/mcrt/tweaker/McRuntimeTestTweaker.java diff --git a/src/main/resources/mc_runtime_test.mixins.json b/shared/src/main/resources/mc_runtime_test.mixins.json similarity index 100% rename from src/main/resources/mc_runtime_test.mixins.json rename to shared/src/main/resources/mc_runtime_test.mixins.json diff --git a/src/main/resources/pack.mcmeta b/shared/src/main/resources/pack.mcmeta similarity index 100% rename from src/main/resources/pack.mcmeta rename to shared/src/main/resources/pack.mcmeta diff --git a/src/neoforge/java/io/github/headlesshq/mcrt/forge/NeoForgeMod.java b/shared/src/neoforge/java/io/github/headlesshq/mcrt/forge/NeoForgeMod.java similarity index 100% rename from src/neoforge/java/io/github/headlesshq/mcrt/forge/NeoForgeMod.java rename to shared/src/neoforge/java/io/github/headlesshq/mcrt/forge/NeoForgeMod.java diff --git a/src/neoforge/resources/META-INF/mods.toml b/shared/src/neoforge/resources/META-INF/mods.toml similarity index 100% rename from src/neoforge/resources/META-INF/mods.toml rename to shared/src/neoforge/resources/META-INF/mods.toml diff --git a/src/neoforge/resources/META-INF/neoforge.mods.toml b/shared/src/neoforge/resources/META-INF/neoforge.mods.toml similarity index 100% rename from src/neoforge/resources/META-INF/neoforge.mods.toml rename to shared/src/neoforge/resources/META-INF/neoforge.mods.toml diff --git a/versions/1.12.2.properties b/versions/1.12.2.properties deleted file mode 100644 index d425b9c..0000000 --- a/versions/1.12.2.properties +++ /dev/null @@ -1,4 +0,0 @@ -loaders = legacyfabric,lexforge -mappings = searge,mcp:39-1.12 -minecraft_version = 1.12.2 -lexforge_version = 14.23.5.2860 diff --git a/versions/1.16.5.properties b/versions/1.16.5.properties deleted file mode 100644 index b7098e7..0000000 --- a/versions/1.16.5.properties +++ /dev/null @@ -1,4 +0,0 @@ -loaders = fabric,lexforge -mappings = mojmap -minecraft_version = 1.16.5 -lexforge_version = 36.2.42 \ No newline at end of file diff --git a/versions/1.17.1.properties b/versions/1.17.1.properties deleted file mode 100644 index c20bceb..0000000 --- a/versions/1.17.1.properties +++ /dev/null @@ -1,4 +0,0 @@ -loaders = fabric,lexforge -mappings = mojmap -minecraft_version = 1.17.1 -lexforge_version = 37.1.1 \ No newline at end of file diff --git a/versions/1.18.2.properties b/versions/1.18.2.properties deleted file mode 100644 index cb96eb5..0000000 --- a/versions/1.18.2.properties +++ /dev/null @@ -1,4 +0,0 @@ -loaders = fabric,lexforge -mappings = mojmap -minecraft_version = 1.18.2 -lexforge_version = 40.2.18 diff --git a/versions/1.19.1.properties b/versions/1.19.1.properties deleted file mode 100644 index 7e8be16..0000000 --- a/versions/1.19.1.properties +++ /dev/null @@ -1,4 +0,0 @@ -loaders = fabric,lexforge -mappings = mojmap -minecraft_version = 1.19.1 -lexforge_version = 42.0.9 diff --git a/versions/1.19.2.properties b/versions/1.19.2.properties deleted file mode 100644 index 7527157..0000000 --- a/versions/1.19.2.properties +++ /dev/null @@ -1,4 +0,0 @@ -loaders = fabric,lexforge -mappings = mojmap -minecraft_version = 1.19.2 -lexforge_version = 43.3.9 diff --git a/versions/1.19.3.properties b/versions/1.19.3.properties deleted file mode 100644 index 32315d0..0000000 --- a/versions/1.19.3.properties +++ /dev/null @@ -1,4 +0,0 @@ -loaders = fabric,lexforge -mappings = mojmap -minecraft_version = 1.19.3 -lexforge_version = 44.1.23 diff --git a/versions/1.19.4.properties b/versions/1.19.4.properties deleted file mode 100644 index e69dcd5..0000000 --- a/versions/1.19.4.properties +++ /dev/null @@ -1,4 +0,0 @@ -loaders = fabric,lexforge -mappings = mojmap -minecraft_version = 1.19.4 -lexforge_version = 45.2.9 diff --git a/versions/1.19.properties b/versions/1.19.properties deleted file mode 100644 index cbc330c..0000000 --- a/versions/1.19.properties +++ /dev/null @@ -1,4 +0,0 @@ -loaders = fabric,lexforge -mappings = mojmap -minecraft_version = 1.19 -lexforge_version = 41.1.0 diff --git a/versions/1.20.1.properties b/versions/1.20.1.properties deleted file mode 100644 index 782063c..0000000 --- a/versions/1.20.1.properties +++ /dev/null @@ -1,4 +0,0 @@ -loaders = fabric,lexforge -mappings = mojmap -minecraft_version = 1.20.1 -lexforge_version = 47.2.23 diff --git a/versions/1.20.2.properties b/versions/1.20.2.properties deleted file mode 100644 index 57a0c94..0000000 --- a/versions/1.20.2.properties +++ /dev/null @@ -1,5 +0,0 @@ -loaders = fabric,lexforge,neoforge -mappings = mojmap -minecraft_version = 1.20.2 -neoforge_version = 88 -lexforge_version = 48.1.0 diff --git a/versions/1.20.3.properties b/versions/1.20.3.properties deleted file mode 100644 index 31ffac2..0000000 --- a/versions/1.20.3.properties +++ /dev/null @@ -1,5 +0,0 @@ -loaders = fabric,lexforge,neoforge -mappings = mojmap -minecraft_version = 1.20.3 -neoforge_version = 8-beta -lexforge_version = 49.0.2 diff --git a/versions/1.20.4.properties b/versions/1.20.4.properties deleted file mode 100644 index 1be3e91..0000000 --- a/versions/1.20.4.properties +++ /dev/null @@ -1,5 +0,0 @@ -loaders = fabric,lexforge,neoforge -mappings = mojmap -minecraft_version = 1.20.4 -neoforge_version = 1-beta -lexforge_version = 49.0.30 diff --git a/versions/1.20.6.properties b/versions/1.20.6.properties deleted file mode 100644 index 9590e52..0000000 --- a/versions/1.20.6.properties +++ /dev/null @@ -1,5 +0,0 @@ -loaders = fabric,lexforge,neoforge -mappings = mojmap -minecraft_version = 1.20.6 -neoforge_version = 119 -lexforge_version = 50.1.10 diff --git a/versions/1.21.1.properties b/versions/1.21.1.properties deleted file mode 100644 index 8903c68..0000000 --- a/versions/1.21.1.properties +++ /dev/null @@ -1,5 +0,0 @@ -loaders = fabric,lexforge,neoforge -mappings = mojmap -minecraft_version = 1.21.1 -neoforge_version = 4 -lexforge_version = 52.0.2 diff --git a/versions/1.21.3/build.gradle b/versions/1.21.3/build.gradle new file mode 100644 index 0000000..b87497e --- /dev/null +++ b/versions/1.21.3/build.gradle @@ -0,0 +1,39 @@ +plugins { + id 'io.github.headlesshq.mcrt.java-conventions' +} + +repositories { + maven { + url = "https://maven.fabricmc.net/" + } + maven { + name = "sponge" + url = "https://repo.spongepowered.org/maven" + } + maven { + name = 'impactdevelopment-repo' + url = 'https://impactdevelopment.github.io/maven/' + } + mavenCentral() + maven { + name = "wagyourtail releases" + url = "https://maven.wagyourtail.xyz/releases" + } +} + +dependencies { + //implementation project(':shared') + implementation project(':api') +} + +manifold { + manifoldVersion = '2024.1.37' //rootProject.manifold_version +} + +group = 'io.github.headlesshq' +base { + archivesName = 'mc-runtime-test' +} + +sourceCompatibility = JavaVersion.VERSION_1_8 +targetCompatibility = JavaVersion.VERSION_1_8 diff --git a/versions/1.21.3/build.properties b/versions/1.21.3/build.properties new file mode 100644 index 0000000..ddccb0b --- /dev/null +++ b/versions/1.21.3/build.properties @@ -0,0 +1,21 @@ +MC_1_7_10=0 +MC_1_8_9=1 +MC_1_12_2=2 +MC_1_16_5=3 +MC_1_17_1=4 +MC_1_18_2=5 +MC_1_19=6 +MC_1_19_1=7 +MC_1_19_2=8 +MC_1_19_3=9 +MC_1_19_4=10 +MC_1_20_1=11 +MC_1_20_2=12 +MC_1_20_3=13 +MC_1_20_4=14 +MC_1_20_6=15 +MC_1_21=16 +MC_1_21_1=17 +MC_1_21_3=18 +MC_1_21_4=19 +MC_VER=18 diff --git a/versions/1.21.3/fabric/resources/fabric.mod.json b/versions/1.21.3/fabric/resources/fabric.mod.json new file mode 100644 index 0000000..3667604 --- /dev/null +++ b/versions/1.21.3/fabric/resources/fabric.mod.json @@ -0,0 +1,28 @@ +{ + "schemaVersion": 1, + "id": "mc_runtime_test", + "version": "${version}", + + "name": "MC-Runtime-Test", + "description": "Run tests on the Minecraft client at runtime", + "authors": [ + "3arthqu4ke" + ], + "contact": { + "homepage": "https://github.com/3arthqu4ke/mc-runtime-test", + "sources": "https://github.com/3arthqu4ke/mc-runtime-test" + }, + + "license": "MIT", + + "environment": "*", + "mixins": [ + "mc_runtime_test.mixins.json" + ], + + "depends": { + "fabricloader": ">=0.14.19", + "minecraft": "~${mc_version}", + "java": ">=8" + } +} diff --git a/versions/1.21.3.properties b/versions/1.21.3/gradle.properties similarity index 100% rename from versions/1.21.3.properties rename to versions/1.21.3/gradle.properties diff --git a/versions/1.21.3/lexforge/java/io/github/headlesshq/mcrt/forge/ForgeMod.java b/versions/1.21.3/lexforge/java/io/github/headlesshq/mcrt/forge/ForgeMod.java new file mode 100644 index 0000000..3064268 --- /dev/null +++ b/versions/1.21.3/lexforge/java/io/github/headlesshq/mcrt/forge/ForgeMod.java @@ -0,0 +1,16 @@ +package io.github.headlesshq.mcrt.forge; + +#if MC_VER == MC_1_7_10 +import cpw.mods.fml.common.Mod; +#else +import net.minecraftforge.fml.common.Mod; +#endif + +#if MC_VER == MC_1_7_10 || MC_VER == MC_1_8_9 || MC_VER == MC_1_12_2 +@Mod(modid = "mc_runtime_test") +#else +@Mod("mc_runtime_test") +#endif +public class ForgeMod { + +} diff --git a/versions/1.21.3/lexforge/resources/META-INF/mods.toml b/versions/1.21.3/lexforge/resources/META-INF/mods.toml new file mode 100644 index 0000000..4772e27 --- /dev/null +++ b/versions/1.21.3/lexforge/resources/META-INF/mods.toml @@ -0,0 +1,20 @@ +modLoader = "javafml" +loaderVersion = "[1,)" +issueTrackerURL = "https://github.com/3arthqu4ke/mc-runtime-test" +license = "MIT" + +[[mods]] +modId = "mc_runtime_test" +version = "${version}" +displayName = "MC-Runtime-Test" +authors = "3arthqu4ke" +description = ''' + Run tests on the Minecraft client at runtime +''' + +[[dependencies.mc_runtime_test]] +modId = "minecraft" +mandatory = true +versionRange = "[${mc_version}]" +ordering = "NONE" +side = "BOTH" \ No newline at end of file diff --git a/versions/1.21.3/lexforge/resources/mcmod.info b/versions/1.21.3/lexforge/resources/mcmod.info new file mode 100644 index 0000000..e912a0c --- /dev/null +++ b/versions/1.21.3/lexforge/resources/mcmod.info @@ -0,0 +1,16 @@ +[ + { + "modid": "mcruntimetest", + "name": "MC-Runtime-Test", + "description": "Allows developers to test the client inside their CLI", + "version": "${version}", + "mcversion": "${mc_version}", + "url": "https://github.com/3arthqu4ke/mc-runtime-test", + "updateUrl": "", + "authorList": ["3arthqu4ke"], + "credits": "", + "logoFile": "/assets/modid/icon.png", + "screenshots": [], + "dependencies": [] + } +] \ No newline at end of file diff --git a/versions/1.21.3/main/java/io/github/headlesshq/mcrt/McGameTestRunner.java b/versions/1.21.3/main/java/io/github/headlesshq/mcrt/McGameTestRunner.java new file mode 100644 index 0000000..f7915c1 --- /dev/null +++ b/versions/1.21.3/main/java/io/github/headlesshq/mcrt/McGameTestRunner.java @@ -0,0 +1,109 @@ +package io.github.headlesshq.mcrt; + +#if MC_VER != MC_1_7_10 && MC_VER != MC_1_8_9 && MC_VER != MC_1_12_2 +import io.github.headlesshq.mcrtapi.McRuntimeTest; +import net.minecraft.core.BlockPos; +import net.minecraft.gametest.framework.*; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.block.Rotation; +import net.minecraft.world.level.levelgen.Heightmap; +#if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1 +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +#else +import org.slf4j.Logger; +import com.mojang.logging.LogUtils; +#endif + +import java.util.Collection; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * Similar to running the "/test runall" command. + */ +@SuppressWarnings({"CommentedOutCode", "StringConcatenationArgumentToLogCall", "ExtractMethodRecommender"}) +public class McGameTestRunner { + #if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1 + private static final Logger LOGGER = LogManager.getLogger(); + #else + private static final Logger LOGGER = LogUtils.getLogger(); + #endif + + /** + * Basically what happens in {@link TestCommand} when "runall" is used. + * We just exit with an error code if a test fails. + * + * @param playerUUID the uuid of the player. + * @param server the server to run the tests on. + */ + public static MultipleTestTracker runGameTests(UUID playerUUID, MinecraftServer server) throws ExecutionException, InterruptedException, TimeoutException { + return server.submit(() -> { + Player player = Objects.requireNonNull(server.getPlayerList().getPlayer(playerUUID)); + #if MC_VER >= MC_1_20_1 + ServerLevel level = (ServerLevel) player.level(); + #else + ServerLevel level = (ServerLevel) player.level; + #endif + GameTestRunner.clearMarkers(level); + Collection testFunctions = GameTestRegistry.getAllTestFunctions(); + LOGGER.info("TestFunctions: " + testFunctions); + if (testFunctions.size() < McRuntimeTest.MIN_GAME_TESTS_TO_FIND) { + LOGGER.error("Failed to find the minimum amount of gametests, expected " + McRuntimeTest.MIN_GAME_TESTS_TO_FIND + ", but found " + testFunctions.size()); + throw new IllegalStateException("Failed to find the minimum amount of gametests, expected " + McRuntimeTest.MIN_GAME_TESTS_TO_FIND + ", but found " + testFunctions.size()); + } + + GameTestRegistry.forgetFailedTests(); + + #if MC_VER >= MC_1_20_6 + Collection batches = GameTestBatchFactory.fromTestFunction(testFunctions, level); + GameTestRunner gameTestRunner = GameTestRunner.Builder.fromBatches(batches, level).build(); + gameTestRunner.start(); + + MultipleTestTracker multipleTestTracker = new MultipleTestTracker(gameTestRunner.getTestInfos()); + #else + BlockPos pos = createTestPositionAround(player, level); + Rotation rotation = StructureUtils.getRotationForRotationSteps(0); + #if MC_VER == MC_1_16_5 + Collection tests = GameTestRunner.runTests(testFunctions, pos, rotation, level, GameTestTicker.singleton, 8); + #elif MC_VER < MC_1_20_6 + Collection tests = GameTestRunner.runTests(testFunctions, pos, rotation, level, GameTestTicker.SINGLETON, 8); + #endif + MultipleTestTracker multipleTestTracker = new MultipleTestTracker(tests); + #endif + multipleTestTracker.addFailureListener(gameTestInfo -> { + LOGGER.error("Test failed: " + gameTestInfo); + if (gameTestInfo.getError() != null) { + LOGGER.error(String.valueOf(gameTestInfo), gameTestInfo.getError()); + } + + if (!gameTestInfo.isOptional() || McRuntimeTest.GAME_TESTS_FAIL_ON_OPTIONAL) { + System.exit(-1); + } + }); + + return multipleTestTracker; + }).get(60, TimeUnit.SECONDS); + } + + private static BlockPos createTestPositionAround(Player player, ServerLevel level) { + #if MC_VER > MC_1_18_2 + BlockPos blockPos = player.getOnPos(); + #else + BlockPos blockPos = new BlockPos(player.position()); + #endif + int y = level.getHeightmapPos(Heightmap.Types.WORLD_SURFACE, blockPos).getY(); + return new BlockPos(blockPos.getX(), y + 1, blockPos.getZ() + 3); + } + +} +#else +public class McGameTestRunner { + +} +#endif diff --git a/versions/1.21.3/main/java/io/github/headlesshq/mcrt/WorldCreator.java b/versions/1.21.3/main/java/io/github/headlesshq/mcrt/WorldCreator.java new file mode 100644 index 0000000..793ad08 --- /dev/null +++ b/versions/1.21.3/main/java/io/github/headlesshq/mcrt/WorldCreator.java @@ -0,0 +1,26 @@ +package io.github.headlesshq.mcrt; + +#if MC_VER == MC_1_7_10 || MC_VER == MC_1_8_9 || MC_VER == MC_1_12_2 +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.GuiCreateWorld; +import net.minecraft.client.gui.GuiScreen; + +/** + * Because unimined 1.7.10 produces weird classes like {@code net.minecraft.world.WorldSettings$GameType}, which do not work properly. + */ +public class WorldCreator extends GuiCreateWorld { + public WorldCreator(GuiScreen guiScreen) { + super(guiScreen); + } + + public void createNewWorld() { + actionPerformed(new GuiButton(0, 0, 0, "")); // <- id 0, loads world + } + +} +#else +@SuppressWarnings("unused") +public class WorldCreator { + +} +#endif diff --git a/versions/1.21.3/main/java/io/github/headlesshq/mcrt/mixin/ICreateWorldScreen.java b/versions/1.21.3/main/java/io/github/headlesshq/mcrt/mixin/ICreateWorldScreen.java new file mode 100644 index 0000000..74b471e --- /dev/null +++ b/versions/1.21.3/main/java/io/github/headlesshq/mcrt/mixin/ICreateWorldScreen.java @@ -0,0 +1,24 @@ +package io.github.headlesshq.mcrt.mixin; + +#if MC_VER != MC_1_7_10 && MC_VER != MC_1_8_9 && MC_VER != MC_1_12_2 +import net.minecraft.client.gui.screens.worldselection.CreateWorldScreen; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Mixin(CreateWorldScreen.class) +public interface ICreateWorldScreen { + @Invoker("onCreate") + void invokeOnCreate(); + +} +#else +import net.minecraft.client.Minecraft; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +@SuppressWarnings("unused") +@Mixin(Minecraft.class) // dummy because this is still in the mixin config +public interface ICreateWorldScreen { + +} +#endif diff --git a/versions/1.21.3/main/java/io/github/headlesshq/mcrt/mixin/MixinLegacyMinecraft.java b/versions/1.21.3/main/java/io/github/headlesshq/mcrt/mixin/MixinLegacyMinecraft.java new file mode 100644 index 0000000..8a4df02 --- /dev/null +++ b/versions/1.21.3/main/java/io/github/headlesshq/mcrt/mixin/MixinLegacyMinecraft.java @@ -0,0 +1,193 @@ +package io.github.headlesshq.mcrt.mixin; + +#if MC_VER == MC_1_7_10 || MC_VER == MC_1_8_9 || MC_VER == MC_1_12_2 +import io.github.headlesshq.mcrt.WorldCreator; +import io.github.headlesshq.mcrtapi.McRuntimeTest; +import net.minecraft.client.Minecraft; +#if MC_VER == MC_1_7_10 +import net.minecraft.client.entity.EntityClientPlayerMP; +#elif MC_VER == MC_1_8_9 || MC_VER == MC_1_12_2 +import net.minecraft.client.entity.EntityPlayerSP; +#endif +import net.minecraft.client.gui.GuiErrorScreen; +import net.minecraft.client.gui.GuiGameOver; +import net.minecraft.client.gui.GuiMainMenu; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.multiplayer.WorldClient; +import net.minecraft.server.integrated.IntegratedServer; +#if MC_VER == MC_1_12_2 +import net.minecraft.world.GameType; +#endif +import net.minecraft.world.WorldSettings; +import net.minecraft.world.WorldType; +import org.apache.logging.log4j.Logger; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.Random; + +// 1.7.10, 1.8.9 1.12.2 +@SuppressWarnings({"StringConcatenationArgumentToLogCall", "CommentedOutCode"}) +@Mixin(Minecraft.class) +public abstract class MixinLegacyMinecraft { + // TODO: this makes things ugly, maybe have a third MixinMinecraft just for 1.12.2? + #if MC_VER == MC_1_7_10 || MC_VER == MC_1_8_9 + @Shadow @Final private static Logger logger; + @Shadow public WorldClient theWorld; + @Shadow private IntegratedServer theIntegratedServer; + #else + @Shadow @Final private static Logger LOGGER; + @Shadow private IntegratedServer integratedServer; + @Shadow public WorldClient world; + #endif + + @Shadow public GuiScreen currentScreen; + #if MC_VER == MC_1_7_10 + @Shadow public EntityClientPlayerMP thePlayer; + #elif MC_VER == MC_1_8_9 + @Shadow public EntityPlayerSP thePlayer; + #elif MC_VER == MC_1_12_2 + @Shadow public EntityPlayerSP player; + #endif + + @Shadow + volatile boolean running; + + @Shadow public abstract void displayGuiScreen(GuiScreen par1); + + #if MC_VER == MC_1_8_9 || MC_VER == MC_1_12_2 + @Shadow public abstract void launchIntegratedServer(String par1, String par2, WorldSettings par3); + #endif + + @Unique + private boolean mcRuntimeTest$startedLoadingSPWorld = false; + + @Unique + #if MC_VER == MC_1_7_10 + private EntityClientPlayerMP mcRuntimeTest$GetPlayer() { + return thePlayer; + #elif MC_VER == MC_1_8_9 + private EntityPlayerSP mcRuntimeTest$GetPlayer() { + return thePlayer; + #elif MC_VER == MC_1_12_2 + private EntityPlayerSP mcRuntimeTest$GetPlayer() { + return player; + #endif + } + + @Unique + private Logger mcRuntimeTest$GetLogger() { + #if MC_VER == MC_1_7_10 || MC_VER == MC_1_8_9 + return logger; + #else + return LOGGER; + #endif + } + + @Unique + private WorldClient mcRuntimeTest$GetWorld() { + #if MC_VER == MC_1_7_10 || MC_VER == MC_1_8_9 + return theWorld; + #else + return world; + #endif + } + + @Unique + private IntegratedServer mcRuntimeTest$GetServer() { + #if MC_VER == MC_1_7_10 || MC_VER == MC_1_8_9 + return theIntegratedServer; + #else + return integratedServer; + #endif + } + + @Inject(method = "displayGuiScreen", at = @At("HEAD")) + private void displayGuiScreenHook(GuiScreen guiScreenIn, CallbackInfo ci) { + if (!McRuntimeTest.screenHook()) { + return; + } + + if (guiScreenIn instanceof GuiErrorScreen) { + running = false; + throw new RuntimeException("Error Screen " + guiScreenIn); + } else if (guiScreenIn instanceof GuiGameOver && mcRuntimeTest$GetPlayer() != null) { + mcRuntimeTest$GetPlayer().respawnPlayer(); + } + } + + @Inject(method = "runTick", at = @At("HEAD")) + private void tickHook(CallbackInfo ci) { + if (!McRuntimeTest.tickHook()) { + return; + } + + if (currentScreen instanceof GuiMainMenu) { + if (!mcRuntimeTest$startedLoadingSPWorld) { + mc_runtime_test$loadSinglePlayerWorld(); + mcRuntimeTest$startedLoadingSPWorld = true; + } + } else { + mcRuntimeTest$GetLogger().info("Waiting for overlay to disappear..."); + } + + if (mcRuntimeTest$GetPlayer() != null && mcRuntimeTest$GetWorld() != null) { + if (currentScreen == null) { + #if MC_VER == MC_1_7_10 + if (!theWorld.getChunkFromBlockCoords((int) thePlayer.posX, (int) thePlayer.posZ).isEmpty()) { + #elif MC_VER == MC_1_8_9 + if (!theWorld.getChunkFromChunkCoords(((int) thePlayer.posX) >> 4, ((int) thePlayer.posZ) >> 4).isEmpty()) { + #elif MC_VER == MC_1_12_2 + if (!world.getChunk(((int) player.posX) >> 4, ((int) player.posZ) >> 4).isEmpty()) { + #endif + if (mcRuntimeTest$GetPlayer().ticksExisted < 100) { + mcRuntimeTest$GetLogger().info("Waiting " + (100 - mcRuntimeTest$GetPlayer().ticksExisted) + " ticks before testing..."); + } else { + mcRuntimeTest$GetLogger().info("Test successful!"); + running = false; + } + } else { + mcRuntimeTest$GetLogger().info("Players chunk not yet loaded, " + mcRuntimeTest$GetPlayer() + ": cores: " + Runtime.getRuntime().availableProcessors() + + ", server running: " + (mcRuntimeTest$GetServer() == null ? "null" : mcRuntimeTest$GetServer().isServerRunning())); + } + } else { + mcRuntimeTest$GetLogger().info("Screen not yet null: " + currentScreen); + } + } else { + mcRuntimeTest$GetLogger().info("Waiting for player to load, screen: " + currentScreen + ", server: " + mcRuntimeTest$GetServer()); + } + } + + @Unique + private void mc_runtime_test$loadSinglePlayerWorld() { + #if MC_VER == MC_1_7_10 + WorldCreator creator = new WorldCreator(new GuiMainMenu()); + displayGuiScreen(creator); + creator.createNewWorld(); + #elif MC_VER == MC_1_8_9 || MC_VER == MC_1_12_2 + displayGuiScreen(null); + long seed = (new Random()).nextLong(); + #if MC_VER == MC_1_8_9 + WorldSettings worldSettings = new WorldSettings(seed, WorldSettings.GameType.SURVIVAL, true, false, WorldType.DEFAULT); + #elif MC_VER == MC_1_12_2 + WorldSettings worldSettings = new WorldSettings(seed, GameType.SURVIVAL, true, false, WorldType.DEFAULT); + #endif + launchIntegratedServer("new_world", "New World", worldSettings); + #endif + } + +} +#else +import net.minecraft.client.Minecraft; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(Minecraft.class) +public abstract class MixinLegacyMinecraft { + +} +#endif diff --git a/versions/1.21.3/main/java/io/github/headlesshq/mcrt/mixin/MixinMinecraft.java b/versions/1.21.3/main/java/io/github/headlesshq/mcrt/mixin/MixinMinecraft.java new file mode 100644 index 0000000..af29822 --- /dev/null +++ b/versions/1.21.3/main/java/io/github/headlesshq/mcrt/mixin/MixinMinecraft.java @@ -0,0 +1,144 @@ +package io.github.headlesshq.mcrt.mixin; + +#if MC_VER != MC_1_7_10 && MC_VER != MC_1_8_9 && MC_VER != MC_1_12_2 +// everything >= 1.16.5 +import io.github.headlesshq.mcrt.McGameTestRunner; +import io.github.headlesshq.mcrtapi.McRuntimeTest; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.*; +import net.minecraft.client.gui.screens.worldselection.CreateWorldScreen; +import net.minecraft.client.gui.screens.worldselection.SelectWorldScreen; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.client.server.IntegratedServer; +import net.minecraft.core.SectionPos; +import net.minecraft.gametest.framework.MultipleTestTracker; +#if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1 +import org.apache.logging.log4j.Logger; +#else +import org.slf4j.Logger; +#endif +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.Objects; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SuppressWarnings({"CommentedOutCode", "StringConcatenationArgumentToLogCall"}) +@Mixin(Minecraft.class) +public abstract class MixinMinecraft { + @Shadow @Final private static Logger LOGGER; + @Shadow @Nullable public LocalPlayer player; + @Shadow @Nullable public ClientLevel level; + @Shadow @Nullable public Screen screen; + @Shadow @Nullable private IntegratedServer singleplayerServer; + @Shadow private volatile boolean running; + + @Unique + private boolean mcRuntimeTest$startedLoadingSPWorld = false; + @Unique + private boolean mcRuntimeTest$worldCreationStarted = false; + @Unique + private MultipleTestTracker mcRuntimeTest$testTracker = null; + + @Shadow + public abstract @Nullable Overlay getOverlay(); + + @Shadow public abstract void setScreen(@Nullable Screen screen); + + @Inject(method = "setScreen", at = @At("HEAD")) + private void setScreenHook(Screen screen, CallbackInfo ci) { + if (!McRuntimeTest.screenHook()) { + return; + } + + if (screen instanceof ErrorScreen) { + running = false; + throw new RuntimeException("Error Screen " + screen); + } else if (screen instanceof DeathScreen && player != null) { + player.respawn(); + } + } + + @Inject(method = "tick", at = @At("HEAD")) + private void tickHook(CallbackInfo ci) throws ExecutionException, InterruptedException, TimeoutException { + if (!McRuntimeTest.tickHook()) { + return; + } + + if (getOverlay() == null) { + if (!mcRuntimeTest$startedLoadingSPWorld && getOverlay() == null) { + #if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1 + setScreen(CreateWorldScreen.create(new SelectWorldScreen(new TitleScreen()))); + #elif MC_VER == MC_1_18_2 + setScreen(CreateWorldScreen.createFresh(new SelectWorldScreen(new TitleScreen()))); + #else + CreateWorldScreen.openFresh(Minecraft.class.cast(this), null); + #endif + mcRuntimeTest$startedLoadingSPWorld = true; + } else if (!mcRuntimeTest$worldCreationStarted && screen instanceof ICreateWorldScreen) { + ((ICreateWorldScreen) screen).invokeOnCreate(); + mcRuntimeTest$worldCreationStarted = true; + } + } else { + LOGGER.info("Waiting for overlay to disappear..."); + } + + if (player != null && level != null) { + if (screen == null) { + if (!level.getChunk(SectionPos.blockToSectionCoord((int) player.getX()), SectionPos.blockToSectionCoord((int) player.getZ())).isEmpty()) { + if (player.tickCount < 100) { + LOGGER.info("Waiting " + (100 - player.tickCount) + " ticks before testing..."); + } else if (mcRuntimeTest$testTracker == null) { + if (McRuntimeTest.RUN_GAME_TESTS) { + LOGGER.info("Running game tests..."); + mcRuntimeTest$testTracker = McGameTestRunner.runGameTests(player.getUUID(), Objects.requireNonNull(singleplayerServer)); + } else { + LOGGER.info("Successfully finished."); + running = false; + } + } else if (mcRuntimeTest$testTracker.isDone()) { + if (mcRuntimeTest$testTracker.getFailedRequiredCount() > 0 + || mcRuntimeTest$testTracker.getFailedOptionalCount() > 0 && McRuntimeTest.GAME_TESTS_FAIL_ON_OPTIONAL) { + System.exit(-1); + } + + running = false; + } else { + LOGGER.info("Waiting for GameTest: " + mcRuntimeTest$testTracker.getProgressBar()); + } + } else { + LOGGER.info("Players chunk not yet loaded, " + player + ": cores: " + Runtime.getRuntime().availableProcessors() + + ", server running: " + (singleplayerServer == null ? "null" : singleplayerServer.isRunning())); + } + } else { + LOGGER.info("Screen not yet null: " + screen); + if (McRuntimeTest.CLOSE_ANY_SCREEN || McRuntimeTest.CLOSE_CREATE_WORLD_SCREEN && screen instanceof CreateWorldScreen) { + LOGGER.info("Closing screen"); + setScreen(null); + } + } + } else { + LOGGER.info("Waiting for player to load..."); + } + } + +} +#else +import net.minecraft.client.Minecraft; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(Minecraft.class) +public class MixinMinecraft { + +} +#endif diff --git a/versions/1.21.3/main/java/io/github/headlesshq/mcrt/tweaker/McRuntimeTestTweaker.java b/versions/1.21.3/main/java/io/github/headlesshq/mcrt/tweaker/McRuntimeTestTweaker.java new file mode 100644 index 0000000..f62ad39 --- /dev/null +++ b/versions/1.21.3/main/java/io/github/headlesshq/mcrt/tweaker/McRuntimeTestTweaker.java @@ -0,0 +1,42 @@ +package io.github.headlesshq.mcrt.tweaker; + +#if MC_VER == MC_1_7_10 || MC_VER == MC_1_8_9 || MC_VER == MC_1_12_2 +import io.github.impactdevelopment.simpletweaker.SimpleTweaker; +import net.minecraft.launchwrapper.LaunchClassLoader; +import org.spongepowered.asm.launch.MixinBootstrap; +import org.spongepowered.asm.mixin.MixinEnvironment; +import org.spongepowered.asm.mixin.Mixins; +import org.spongepowered.tools.obfuscation.mcp.ObfuscationServiceMCP; + +import java.io.IOException; + +@SuppressWarnings("unused") +public class McRuntimeTestTweaker extends SimpleTweaker { + @Override + public void injectIntoClassLoader(LaunchClassLoader classLoader) { + super.injectIntoClassLoader(classLoader); + MixinBootstrap.init(); + + String obfCtx = ObfuscationServiceMCP.NOTCH; + try { + if (classLoader.getClassBytes( + "net.minecraftforge.common.ForgeHooks") != null) { + obfCtx = ObfuscationServiceMCP.SEARGE; + } + } catch (IOException ignored) { } + + MixinEnvironment.getDefaultEnvironment() + .setSide(MixinEnvironment.Side.CLIENT); + MixinEnvironment.getDefaultEnvironment() + .setObfuscationContext(obfCtx); + + Mixins.addConfiguration("mc_runtime_test.mixins.json"); + } + +} +#else +@SuppressWarnings("unused") +public class McRuntimeTestTweaker { + +} +#endif diff --git a/versions/1.21.3/main/resources/mc_runtime_test.mixins.json b/versions/1.21.3/main/resources/mc_runtime_test.mixins.json new file mode 100644 index 0000000..a454a0c --- /dev/null +++ b/versions/1.21.3/main/resources/mc_runtime_test.mixins.json @@ -0,0 +1,16 @@ +{ + "required": true, + "minVersion": "0.7.11", + "package": "io.github.headlesshq.mcrt.mixin", + "compatibilityLevel": "JAVA_8", + "client": [ + "MixinLegacyMinecraft", + "MixinMinecraft" + ], + "injectors": { + "defaultRequire": 1 + }, + "mixins": [ + "ICreateWorldScreen" + ] +} \ No newline at end of file diff --git a/versions/1.21.3/main/resources/pack.mcmeta b/versions/1.21.3/main/resources/pack.mcmeta new file mode 100644 index 0000000..936492d --- /dev/null +++ b/versions/1.21.3/main/resources/pack.mcmeta @@ -0,0 +1,6 @@ +{ + "pack": { + "description": "Mc Runtime Test resources", + "pack_format": 3 + } +} diff --git a/versions/1.21.3/neoforge/java/io/github/headlesshq/mcrt/forge/NeoForgeMod.java b/versions/1.21.3/neoforge/java/io/github/headlesshq/mcrt/forge/NeoForgeMod.java new file mode 100644 index 0000000..69cc7bf --- /dev/null +++ b/versions/1.21.3/neoforge/java/io/github/headlesshq/mcrt/forge/NeoForgeMod.java @@ -0,0 +1,8 @@ +package io.github.headlesshq.mcrt.forge; + +import net.neoforged.fml.common.Mod; + +@Mod("mc_runtime_test") +public class NeoForgeMod { + +} diff --git a/versions/1.21.3/neoforge/resources/META-INF/mods.toml b/versions/1.21.3/neoforge/resources/META-INF/mods.toml new file mode 100644 index 0000000..2f35f75 --- /dev/null +++ b/versions/1.21.3/neoforge/resources/META-INF/mods.toml @@ -0,0 +1,20 @@ +modLoader = "javafml" +loaderVersion = "[1,)" +issueTrackerURL = "https://github.com/3arthqu4ke/mc-runtime-test" +license = "MIT" + +[[mods]] +modId = "mc_runtime_test" +version = "${version}" +displayName = "MC-Runtime-Test" +authors = "3arthqu4ke" +description = ''' + Run tests on the Minecraft client at runtime +''' + +[[dependencies.mc_runtime_test]] +modId = "minecraft" +mandatory = true +versionRange = "[${mc_version}]" +ordering = "NONE" +side = "BOTH" diff --git a/versions/1.21.3/neoforge/resources/META-INF/neoforge.mods.toml b/versions/1.21.3/neoforge/resources/META-INF/neoforge.mods.toml new file mode 100644 index 0000000..2f35f75 --- /dev/null +++ b/versions/1.21.3/neoforge/resources/META-INF/neoforge.mods.toml @@ -0,0 +1,20 @@ +modLoader = "javafml" +loaderVersion = "[1,)" +issueTrackerURL = "https://github.com/3arthqu4ke/mc-runtime-test" +license = "MIT" + +[[mods]] +modId = "mc_runtime_test" +version = "${version}" +displayName = "MC-Runtime-Test" +authors = "3arthqu4ke" +description = ''' + Run tests on the Minecraft client at runtime +''' + +[[dependencies.mc_runtime_test]] +modId = "minecraft" +mandatory = true +versionRange = "[${mc_version}]" +ordering = "NONE" +side = "BOTH" diff --git a/versions/1.21.4/build.gradle b/versions/1.21.4/build.gradle new file mode 100644 index 0000000..b87497e --- /dev/null +++ b/versions/1.21.4/build.gradle @@ -0,0 +1,39 @@ +plugins { + id 'io.github.headlesshq.mcrt.java-conventions' +} + +repositories { + maven { + url = "https://maven.fabricmc.net/" + } + maven { + name = "sponge" + url = "https://repo.spongepowered.org/maven" + } + maven { + name = 'impactdevelopment-repo' + url = 'https://impactdevelopment.github.io/maven/' + } + mavenCentral() + maven { + name = "wagyourtail releases" + url = "https://maven.wagyourtail.xyz/releases" + } +} + +dependencies { + //implementation project(':shared') + implementation project(':api') +} + +manifold { + manifoldVersion = '2024.1.37' //rootProject.manifold_version +} + +group = 'io.github.headlesshq' +base { + archivesName = 'mc-runtime-test' +} + +sourceCompatibility = JavaVersion.VERSION_1_8 +targetCompatibility = JavaVersion.VERSION_1_8 diff --git a/versions/1.21.4/build.properties b/versions/1.21.4/build.properties new file mode 100644 index 0000000..c7d9f5c --- /dev/null +++ b/versions/1.21.4/build.properties @@ -0,0 +1,21 @@ +MC_1_7_10=0 +MC_1_8_9=1 +MC_1_12_2=2 +MC_1_16_5=3 +MC_1_17_1=4 +MC_1_18_2=5 +MC_1_19=6 +MC_1_19_1=7 +MC_1_19_2=8 +MC_1_19_3=9 +MC_1_19_4=10 +MC_1_20_1=11 +MC_1_20_2=12 +MC_1_20_3=13 +MC_1_20_4=14 +MC_1_20_6=15 +MC_1_21=16 +MC_1_21_1=17 +MC_1_21_3=18 +MC_1_21_4=19 +MC_VER=19 \ No newline at end of file diff --git a/versions/1.21.4/fabric/resources/fabric.mod.json b/versions/1.21.4/fabric/resources/fabric.mod.json new file mode 100644 index 0000000..3667604 --- /dev/null +++ b/versions/1.21.4/fabric/resources/fabric.mod.json @@ -0,0 +1,28 @@ +{ + "schemaVersion": 1, + "id": "mc_runtime_test", + "version": "${version}", + + "name": "MC-Runtime-Test", + "description": "Run tests on the Minecraft client at runtime", + "authors": [ + "3arthqu4ke" + ], + "contact": { + "homepage": "https://github.com/3arthqu4ke/mc-runtime-test", + "sources": "https://github.com/3arthqu4ke/mc-runtime-test" + }, + + "license": "MIT", + + "environment": "*", + "mixins": [ + "mc_runtime_test.mixins.json" + ], + + "depends": { + "fabricloader": ">=0.14.19", + "minecraft": "~${mc_version}", + "java": ">=8" + } +} diff --git a/versions/1.21.4.properties b/versions/1.21.4/gradle.properties similarity index 100% rename from versions/1.21.4.properties rename to versions/1.21.4/gradle.properties diff --git a/versions/1.21.4/lexforge/java/io/github/headlesshq/mcrt/forge/ForgeMod.java b/versions/1.21.4/lexforge/java/io/github/headlesshq/mcrt/forge/ForgeMod.java new file mode 100644 index 0000000..3064268 --- /dev/null +++ b/versions/1.21.4/lexforge/java/io/github/headlesshq/mcrt/forge/ForgeMod.java @@ -0,0 +1,16 @@ +package io.github.headlesshq.mcrt.forge; + +#if MC_VER == MC_1_7_10 +import cpw.mods.fml.common.Mod; +#else +import net.minecraftforge.fml.common.Mod; +#endif + +#if MC_VER == MC_1_7_10 || MC_VER == MC_1_8_9 || MC_VER == MC_1_12_2 +@Mod(modid = "mc_runtime_test") +#else +@Mod("mc_runtime_test") +#endif +public class ForgeMod { + +} diff --git a/versions/1.21.4/lexforge/resources/META-INF/mods.toml b/versions/1.21.4/lexforge/resources/META-INF/mods.toml new file mode 100644 index 0000000..4772e27 --- /dev/null +++ b/versions/1.21.4/lexforge/resources/META-INF/mods.toml @@ -0,0 +1,20 @@ +modLoader = "javafml" +loaderVersion = "[1,)" +issueTrackerURL = "https://github.com/3arthqu4ke/mc-runtime-test" +license = "MIT" + +[[mods]] +modId = "mc_runtime_test" +version = "${version}" +displayName = "MC-Runtime-Test" +authors = "3arthqu4ke" +description = ''' + Run tests on the Minecraft client at runtime +''' + +[[dependencies.mc_runtime_test]] +modId = "minecraft" +mandatory = true +versionRange = "[${mc_version}]" +ordering = "NONE" +side = "BOTH" \ No newline at end of file diff --git a/versions/1.21.4/lexforge/resources/mcmod.info b/versions/1.21.4/lexforge/resources/mcmod.info new file mode 100644 index 0000000..e912a0c --- /dev/null +++ b/versions/1.21.4/lexforge/resources/mcmod.info @@ -0,0 +1,16 @@ +[ + { + "modid": "mcruntimetest", + "name": "MC-Runtime-Test", + "description": "Allows developers to test the client inside their CLI", + "version": "${version}", + "mcversion": "${mc_version}", + "url": "https://github.com/3arthqu4ke/mc-runtime-test", + "updateUrl": "", + "authorList": ["3arthqu4ke"], + "credits": "", + "logoFile": "/assets/modid/icon.png", + "screenshots": [], + "dependencies": [] + } +] \ No newline at end of file diff --git a/versions/1.21.4/main/java/io/github/headlesshq/mcrt/McGameTestRunner.java b/versions/1.21.4/main/java/io/github/headlesshq/mcrt/McGameTestRunner.java new file mode 100644 index 0000000..f7915c1 --- /dev/null +++ b/versions/1.21.4/main/java/io/github/headlesshq/mcrt/McGameTestRunner.java @@ -0,0 +1,109 @@ +package io.github.headlesshq.mcrt; + +#if MC_VER != MC_1_7_10 && MC_VER != MC_1_8_9 && MC_VER != MC_1_12_2 +import io.github.headlesshq.mcrtapi.McRuntimeTest; +import net.minecraft.core.BlockPos; +import net.minecraft.gametest.framework.*; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.block.Rotation; +import net.minecraft.world.level.levelgen.Heightmap; +#if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1 +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +#else +import org.slf4j.Logger; +import com.mojang.logging.LogUtils; +#endif + +import java.util.Collection; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * Similar to running the "/test runall" command. + */ +@SuppressWarnings({"CommentedOutCode", "StringConcatenationArgumentToLogCall", "ExtractMethodRecommender"}) +public class McGameTestRunner { + #if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1 + private static final Logger LOGGER = LogManager.getLogger(); + #else + private static final Logger LOGGER = LogUtils.getLogger(); + #endif + + /** + * Basically what happens in {@link TestCommand} when "runall" is used. + * We just exit with an error code if a test fails. + * + * @param playerUUID the uuid of the player. + * @param server the server to run the tests on. + */ + public static MultipleTestTracker runGameTests(UUID playerUUID, MinecraftServer server) throws ExecutionException, InterruptedException, TimeoutException { + return server.submit(() -> { + Player player = Objects.requireNonNull(server.getPlayerList().getPlayer(playerUUID)); + #if MC_VER >= MC_1_20_1 + ServerLevel level = (ServerLevel) player.level(); + #else + ServerLevel level = (ServerLevel) player.level; + #endif + GameTestRunner.clearMarkers(level); + Collection testFunctions = GameTestRegistry.getAllTestFunctions(); + LOGGER.info("TestFunctions: " + testFunctions); + if (testFunctions.size() < McRuntimeTest.MIN_GAME_TESTS_TO_FIND) { + LOGGER.error("Failed to find the minimum amount of gametests, expected " + McRuntimeTest.MIN_GAME_TESTS_TO_FIND + ", but found " + testFunctions.size()); + throw new IllegalStateException("Failed to find the minimum amount of gametests, expected " + McRuntimeTest.MIN_GAME_TESTS_TO_FIND + ", but found " + testFunctions.size()); + } + + GameTestRegistry.forgetFailedTests(); + + #if MC_VER >= MC_1_20_6 + Collection batches = GameTestBatchFactory.fromTestFunction(testFunctions, level); + GameTestRunner gameTestRunner = GameTestRunner.Builder.fromBatches(batches, level).build(); + gameTestRunner.start(); + + MultipleTestTracker multipleTestTracker = new MultipleTestTracker(gameTestRunner.getTestInfos()); + #else + BlockPos pos = createTestPositionAround(player, level); + Rotation rotation = StructureUtils.getRotationForRotationSteps(0); + #if MC_VER == MC_1_16_5 + Collection tests = GameTestRunner.runTests(testFunctions, pos, rotation, level, GameTestTicker.singleton, 8); + #elif MC_VER < MC_1_20_6 + Collection tests = GameTestRunner.runTests(testFunctions, pos, rotation, level, GameTestTicker.SINGLETON, 8); + #endif + MultipleTestTracker multipleTestTracker = new MultipleTestTracker(tests); + #endif + multipleTestTracker.addFailureListener(gameTestInfo -> { + LOGGER.error("Test failed: " + gameTestInfo); + if (gameTestInfo.getError() != null) { + LOGGER.error(String.valueOf(gameTestInfo), gameTestInfo.getError()); + } + + if (!gameTestInfo.isOptional() || McRuntimeTest.GAME_TESTS_FAIL_ON_OPTIONAL) { + System.exit(-1); + } + }); + + return multipleTestTracker; + }).get(60, TimeUnit.SECONDS); + } + + private static BlockPos createTestPositionAround(Player player, ServerLevel level) { + #if MC_VER > MC_1_18_2 + BlockPos blockPos = player.getOnPos(); + #else + BlockPos blockPos = new BlockPos(player.position()); + #endif + int y = level.getHeightmapPos(Heightmap.Types.WORLD_SURFACE, blockPos).getY(); + return new BlockPos(blockPos.getX(), y + 1, blockPos.getZ() + 3); + } + +} +#else +public class McGameTestRunner { + +} +#endif diff --git a/versions/1.21.4/main/java/io/github/headlesshq/mcrt/WorldCreator.java b/versions/1.21.4/main/java/io/github/headlesshq/mcrt/WorldCreator.java new file mode 100644 index 0000000..793ad08 --- /dev/null +++ b/versions/1.21.4/main/java/io/github/headlesshq/mcrt/WorldCreator.java @@ -0,0 +1,26 @@ +package io.github.headlesshq.mcrt; + +#if MC_VER == MC_1_7_10 || MC_VER == MC_1_8_9 || MC_VER == MC_1_12_2 +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.GuiCreateWorld; +import net.minecraft.client.gui.GuiScreen; + +/** + * Because unimined 1.7.10 produces weird classes like {@code net.minecraft.world.WorldSettings$GameType}, which do not work properly. + */ +public class WorldCreator extends GuiCreateWorld { + public WorldCreator(GuiScreen guiScreen) { + super(guiScreen); + } + + public void createNewWorld() { + actionPerformed(new GuiButton(0, 0, 0, "")); // <- id 0, loads world + } + +} +#else +@SuppressWarnings("unused") +public class WorldCreator { + +} +#endif diff --git a/versions/1.21.4/main/java/io/github/headlesshq/mcrt/mixin/ICreateWorldScreen.java b/versions/1.21.4/main/java/io/github/headlesshq/mcrt/mixin/ICreateWorldScreen.java new file mode 100644 index 0000000..74b471e --- /dev/null +++ b/versions/1.21.4/main/java/io/github/headlesshq/mcrt/mixin/ICreateWorldScreen.java @@ -0,0 +1,24 @@ +package io.github.headlesshq.mcrt.mixin; + +#if MC_VER != MC_1_7_10 && MC_VER != MC_1_8_9 && MC_VER != MC_1_12_2 +import net.minecraft.client.gui.screens.worldselection.CreateWorldScreen; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Mixin(CreateWorldScreen.class) +public interface ICreateWorldScreen { + @Invoker("onCreate") + void invokeOnCreate(); + +} +#else +import net.minecraft.client.Minecraft; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +@SuppressWarnings("unused") +@Mixin(Minecraft.class) // dummy because this is still in the mixin config +public interface ICreateWorldScreen { + +} +#endif diff --git a/versions/1.21.4/main/java/io/github/headlesshq/mcrt/mixin/MixinLegacyMinecraft.java b/versions/1.21.4/main/java/io/github/headlesshq/mcrt/mixin/MixinLegacyMinecraft.java new file mode 100644 index 0000000..8a4df02 --- /dev/null +++ b/versions/1.21.4/main/java/io/github/headlesshq/mcrt/mixin/MixinLegacyMinecraft.java @@ -0,0 +1,193 @@ +package io.github.headlesshq.mcrt.mixin; + +#if MC_VER == MC_1_7_10 || MC_VER == MC_1_8_9 || MC_VER == MC_1_12_2 +import io.github.headlesshq.mcrt.WorldCreator; +import io.github.headlesshq.mcrtapi.McRuntimeTest; +import net.minecraft.client.Minecraft; +#if MC_VER == MC_1_7_10 +import net.minecraft.client.entity.EntityClientPlayerMP; +#elif MC_VER == MC_1_8_9 || MC_VER == MC_1_12_2 +import net.minecraft.client.entity.EntityPlayerSP; +#endif +import net.minecraft.client.gui.GuiErrorScreen; +import net.minecraft.client.gui.GuiGameOver; +import net.minecraft.client.gui.GuiMainMenu; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.multiplayer.WorldClient; +import net.minecraft.server.integrated.IntegratedServer; +#if MC_VER == MC_1_12_2 +import net.minecraft.world.GameType; +#endif +import net.minecraft.world.WorldSettings; +import net.minecraft.world.WorldType; +import org.apache.logging.log4j.Logger; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.Random; + +// 1.7.10, 1.8.9 1.12.2 +@SuppressWarnings({"StringConcatenationArgumentToLogCall", "CommentedOutCode"}) +@Mixin(Minecraft.class) +public abstract class MixinLegacyMinecraft { + // TODO: this makes things ugly, maybe have a third MixinMinecraft just for 1.12.2? + #if MC_VER == MC_1_7_10 || MC_VER == MC_1_8_9 + @Shadow @Final private static Logger logger; + @Shadow public WorldClient theWorld; + @Shadow private IntegratedServer theIntegratedServer; + #else + @Shadow @Final private static Logger LOGGER; + @Shadow private IntegratedServer integratedServer; + @Shadow public WorldClient world; + #endif + + @Shadow public GuiScreen currentScreen; + #if MC_VER == MC_1_7_10 + @Shadow public EntityClientPlayerMP thePlayer; + #elif MC_VER == MC_1_8_9 + @Shadow public EntityPlayerSP thePlayer; + #elif MC_VER == MC_1_12_2 + @Shadow public EntityPlayerSP player; + #endif + + @Shadow + volatile boolean running; + + @Shadow public abstract void displayGuiScreen(GuiScreen par1); + + #if MC_VER == MC_1_8_9 || MC_VER == MC_1_12_2 + @Shadow public abstract void launchIntegratedServer(String par1, String par2, WorldSettings par3); + #endif + + @Unique + private boolean mcRuntimeTest$startedLoadingSPWorld = false; + + @Unique + #if MC_VER == MC_1_7_10 + private EntityClientPlayerMP mcRuntimeTest$GetPlayer() { + return thePlayer; + #elif MC_VER == MC_1_8_9 + private EntityPlayerSP mcRuntimeTest$GetPlayer() { + return thePlayer; + #elif MC_VER == MC_1_12_2 + private EntityPlayerSP mcRuntimeTest$GetPlayer() { + return player; + #endif + } + + @Unique + private Logger mcRuntimeTest$GetLogger() { + #if MC_VER == MC_1_7_10 || MC_VER == MC_1_8_9 + return logger; + #else + return LOGGER; + #endif + } + + @Unique + private WorldClient mcRuntimeTest$GetWorld() { + #if MC_VER == MC_1_7_10 || MC_VER == MC_1_8_9 + return theWorld; + #else + return world; + #endif + } + + @Unique + private IntegratedServer mcRuntimeTest$GetServer() { + #if MC_VER == MC_1_7_10 || MC_VER == MC_1_8_9 + return theIntegratedServer; + #else + return integratedServer; + #endif + } + + @Inject(method = "displayGuiScreen", at = @At("HEAD")) + private void displayGuiScreenHook(GuiScreen guiScreenIn, CallbackInfo ci) { + if (!McRuntimeTest.screenHook()) { + return; + } + + if (guiScreenIn instanceof GuiErrorScreen) { + running = false; + throw new RuntimeException("Error Screen " + guiScreenIn); + } else if (guiScreenIn instanceof GuiGameOver && mcRuntimeTest$GetPlayer() != null) { + mcRuntimeTest$GetPlayer().respawnPlayer(); + } + } + + @Inject(method = "runTick", at = @At("HEAD")) + private void tickHook(CallbackInfo ci) { + if (!McRuntimeTest.tickHook()) { + return; + } + + if (currentScreen instanceof GuiMainMenu) { + if (!mcRuntimeTest$startedLoadingSPWorld) { + mc_runtime_test$loadSinglePlayerWorld(); + mcRuntimeTest$startedLoadingSPWorld = true; + } + } else { + mcRuntimeTest$GetLogger().info("Waiting for overlay to disappear..."); + } + + if (mcRuntimeTest$GetPlayer() != null && mcRuntimeTest$GetWorld() != null) { + if (currentScreen == null) { + #if MC_VER == MC_1_7_10 + if (!theWorld.getChunkFromBlockCoords((int) thePlayer.posX, (int) thePlayer.posZ).isEmpty()) { + #elif MC_VER == MC_1_8_9 + if (!theWorld.getChunkFromChunkCoords(((int) thePlayer.posX) >> 4, ((int) thePlayer.posZ) >> 4).isEmpty()) { + #elif MC_VER == MC_1_12_2 + if (!world.getChunk(((int) player.posX) >> 4, ((int) player.posZ) >> 4).isEmpty()) { + #endif + if (mcRuntimeTest$GetPlayer().ticksExisted < 100) { + mcRuntimeTest$GetLogger().info("Waiting " + (100 - mcRuntimeTest$GetPlayer().ticksExisted) + " ticks before testing..."); + } else { + mcRuntimeTest$GetLogger().info("Test successful!"); + running = false; + } + } else { + mcRuntimeTest$GetLogger().info("Players chunk not yet loaded, " + mcRuntimeTest$GetPlayer() + ": cores: " + Runtime.getRuntime().availableProcessors() + + ", server running: " + (mcRuntimeTest$GetServer() == null ? "null" : mcRuntimeTest$GetServer().isServerRunning())); + } + } else { + mcRuntimeTest$GetLogger().info("Screen not yet null: " + currentScreen); + } + } else { + mcRuntimeTest$GetLogger().info("Waiting for player to load, screen: " + currentScreen + ", server: " + mcRuntimeTest$GetServer()); + } + } + + @Unique + private void mc_runtime_test$loadSinglePlayerWorld() { + #if MC_VER == MC_1_7_10 + WorldCreator creator = new WorldCreator(new GuiMainMenu()); + displayGuiScreen(creator); + creator.createNewWorld(); + #elif MC_VER == MC_1_8_9 || MC_VER == MC_1_12_2 + displayGuiScreen(null); + long seed = (new Random()).nextLong(); + #if MC_VER == MC_1_8_9 + WorldSettings worldSettings = new WorldSettings(seed, WorldSettings.GameType.SURVIVAL, true, false, WorldType.DEFAULT); + #elif MC_VER == MC_1_12_2 + WorldSettings worldSettings = new WorldSettings(seed, GameType.SURVIVAL, true, false, WorldType.DEFAULT); + #endif + launchIntegratedServer("new_world", "New World", worldSettings); + #endif + } + +} +#else +import net.minecraft.client.Minecraft; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(Minecraft.class) +public abstract class MixinLegacyMinecraft { + +} +#endif diff --git a/versions/1.21.4/main/java/io/github/headlesshq/mcrt/mixin/MixinMinecraft.java b/versions/1.21.4/main/java/io/github/headlesshq/mcrt/mixin/MixinMinecraft.java new file mode 100644 index 0000000..af29822 --- /dev/null +++ b/versions/1.21.4/main/java/io/github/headlesshq/mcrt/mixin/MixinMinecraft.java @@ -0,0 +1,144 @@ +package io.github.headlesshq.mcrt.mixin; + +#if MC_VER != MC_1_7_10 && MC_VER != MC_1_8_9 && MC_VER != MC_1_12_2 +// everything >= 1.16.5 +import io.github.headlesshq.mcrt.McGameTestRunner; +import io.github.headlesshq.mcrtapi.McRuntimeTest; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.*; +import net.minecraft.client.gui.screens.worldselection.CreateWorldScreen; +import net.minecraft.client.gui.screens.worldselection.SelectWorldScreen; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.client.server.IntegratedServer; +import net.minecraft.core.SectionPos; +import net.minecraft.gametest.framework.MultipleTestTracker; +#if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1 +import org.apache.logging.log4j.Logger; +#else +import org.slf4j.Logger; +#endif +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.Objects; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SuppressWarnings({"CommentedOutCode", "StringConcatenationArgumentToLogCall"}) +@Mixin(Minecraft.class) +public abstract class MixinMinecraft { + @Shadow @Final private static Logger LOGGER; + @Shadow @Nullable public LocalPlayer player; + @Shadow @Nullable public ClientLevel level; + @Shadow @Nullable public Screen screen; + @Shadow @Nullable private IntegratedServer singleplayerServer; + @Shadow private volatile boolean running; + + @Unique + private boolean mcRuntimeTest$startedLoadingSPWorld = false; + @Unique + private boolean mcRuntimeTest$worldCreationStarted = false; + @Unique + private MultipleTestTracker mcRuntimeTest$testTracker = null; + + @Shadow + public abstract @Nullable Overlay getOverlay(); + + @Shadow public abstract void setScreen(@Nullable Screen screen); + + @Inject(method = "setScreen", at = @At("HEAD")) + private void setScreenHook(Screen screen, CallbackInfo ci) { + if (!McRuntimeTest.screenHook()) { + return; + } + + if (screen instanceof ErrorScreen) { + running = false; + throw new RuntimeException("Error Screen " + screen); + } else if (screen instanceof DeathScreen && player != null) { + player.respawn(); + } + } + + @Inject(method = "tick", at = @At("HEAD")) + private void tickHook(CallbackInfo ci) throws ExecutionException, InterruptedException, TimeoutException { + if (!McRuntimeTest.tickHook()) { + return; + } + + if (getOverlay() == null) { + if (!mcRuntimeTest$startedLoadingSPWorld && getOverlay() == null) { + #if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1 + setScreen(CreateWorldScreen.create(new SelectWorldScreen(new TitleScreen()))); + #elif MC_VER == MC_1_18_2 + setScreen(CreateWorldScreen.createFresh(new SelectWorldScreen(new TitleScreen()))); + #else + CreateWorldScreen.openFresh(Minecraft.class.cast(this), null); + #endif + mcRuntimeTest$startedLoadingSPWorld = true; + } else if (!mcRuntimeTest$worldCreationStarted && screen instanceof ICreateWorldScreen) { + ((ICreateWorldScreen) screen).invokeOnCreate(); + mcRuntimeTest$worldCreationStarted = true; + } + } else { + LOGGER.info("Waiting for overlay to disappear..."); + } + + if (player != null && level != null) { + if (screen == null) { + if (!level.getChunk(SectionPos.blockToSectionCoord((int) player.getX()), SectionPos.blockToSectionCoord((int) player.getZ())).isEmpty()) { + if (player.tickCount < 100) { + LOGGER.info("Waiting " + (100 - player.tickCount) + " ticks before testing..."); + } else if (mcRuntimeTest$testTracker == null) { + if (McRuntimeTest.RUN_GAME_TESTS) { + LOGGER.info("Running game tests..."); + mcRuntimeTest$testTracker = McGameTestRunner.runGameTests(player.getUUID(), Objects.requireNonNull(singleplayerServer)); + } else { + LOGGER.info("Successfully finished."); + running = false; + } + } else if (mcRuntimeTest$testTracker.isDone()) { + if (mcRuntimeTest$testTracker.getFailedRequiredCount() > 0 + || mcRuntimeTest$testTracker.getFailedOptionalCount() > 0 && McRuntimeTest.GAME_TESTS_FAIL_ON_OPTIONAL) { + System.exit(-1); + } + + running = false; + } else { + LOGGER.info("Waiting for GameTest: " + mcRuntimeTest$testTracker.getProgressBar()); + } + } else { + LOGGER.info("Players chunk not yet loaded, " + player + ": cores: " + Runtime.getRuntime().availableProcessors() + + ", server running: " + (singleplayerServer == null ? "null" : singleplayerServer.isRunning())); + } + } else { + LOGGER.info("Screen not yet null: " + screen); + if (McRuntimeTest.CLOSE_ANY_SCREEN || McRuntimeTest.CLOSE_CREATE_WORLD_SCREEN && screen instanceof CreateWorldScreen) { + LOGGER.info("Closing screen"); + setScreen(null); + } + } + } else { + LOGGER.info("Waiting for player to load..."); + } + } + +} +#else +import net.minecraft.client.Minecraft; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(Minecraft.class) +public class MixinMinecraft { + +} +#endif diff --git a/versions/1.21.4/main/java/io/github/headlesshq/mcrt/tweaker/McRuntimeTestTweaker.java b/versions/1.21.4/main/java/io/github/headlesshq/mcrt/tweaker/McRuntimeTestTweaker.java new file mode 100644 index 0000000..f62ad39 --- /dev/null +++ b/versions/1.21.4/main/java/io/github/headlesshq/mcrt/tweaker/McRuntimeTestTweaker.java @@ -0,0 +1,42 @@ +package io.github.headlesshq.mcrt.tweaker; + +#if MC_VER == MC_1_7_10 || MC_VER == MC_1_8_9 || MC_VER == MC_1_12_2 +import io.github.impactdevelopment.simpletweaker.SimpleTweaker; +import net.minecraft.launchwrapper.LaunchClassLoader; +import org.spongepowered.asm.launch.MixinBootstrap; +import org.spongepowered.asm.mixin.MixinEnvironment; +import org.spongepowered.asm.mixin.Mixins; +import org.spongepowered.tools.obfuscation.mcp.ObfuscationServiceMCP; + +import java.io.IOException; + +@SuppressWarnings("unused") +public class McRuntimeTestTweaker extends SimpleTweaker { + @Override + public void injectIntoClassLoader(LaunchClassLoader classLoader) { + super.injectIntoClassLoader(classLoader); + MixinBootstrap.init(); + + String obfCtx = ObfuscationServiceMCP.NOTCH; + try { + if (classLoader.getClassBytes( + "net.minecraftforge.common.ForgeHooks") != null) { + obfCtx = ObfuscationServiceMCP.SEARGE; + } + } catch (IOException ignored) { } + + MixinEnvironment.getDefaultEnvironment() + .setSide(MixinEnvironment.Side.CLIENT); + MixinEnvironment.getDefaultEnvironment() + .setObfuscationContext(obfCtx); + + Mixins.addConfiguration("mc_runtime_test.mixins.json"); + } + +} +#else +@SuppressWarnings("unused") +public class McRuntimeTestTweaker { + +} +#endif diff --git a/versions/1.21.4/main/resources/mc_runtime_test.mixins.json b/versions/1.21.4/main/resources/mc_runtime_test.mixins.json new file mode 100644 index 0000000..a454a0c --- /dev/null +++ b/versions/1.21.4/main/resources/mc_runtime_test.mixins.json @@ -0,0 +1,16 @@ +{ + "required": true, + "minVersion": "0.7.11", + "package": "io.github.headlesshq.mcrt.mixin", + "compatibilityLevel": "JAVA_8", + "client": [ + "MixinLegacyMinecraft", + "MixinMinecraft" + ], + "injectors": { + "defaultRequire": 1 + }, + "mixins": [ + "ICreateWorldScreen" + ] +} \ No newline at end of file diff --git a/versions/1.21.4/main/resources/pack.mcmeta b/versions/1.21.4/main/resources/pack.mcmeta new file mode 100644 index 0000000..936492d --- /dev/null +++ b/versions/1.21.4/main/resources/pack.mcmeta @@ -0,0 +1,6 @@ +{ + "pack": { + "description": "Mc Runtime Test resources", + "pack_format": 3 + } +} diff --git a/versions/1.21.4/neoforge/java/io/github/headlesshq/mcrt/forge/NeoForgeMod.java b/versions/1.21.4/neoforge/java/io/github/headlesshq/mcrt/forge/NeoForgeMod.java new file mode 100644 index 0000000..69cc7bf --- /dev/null +++ b/versions/1.21.4/neoforge/java/io/github/headlesshq/mcrt/forge/NeoForgeMod.java @@ -0,0 +1,8 @@ +package io.github.headlesshq.mcrt.forge; + +import net.neoforged.fml.common.Mod; + +@Mod("mc_runtime_test") +public class NeoForgeMod { + +} diff --git a/versions/1.21.4/neoforge/resources/META-INF/mods.toml b/versions/1.21.4/neoforge/resources/META-INF/mods.toml new file mode 100644 index 0000000..2f35f75 --- /dev/null +++ b/versions/1.21.4/neoforge/resources/META-INF/mods.toml @@ -0,0 +1,20 @@ +modLoader = "javafml" +loaderVersion = "[1,)" +issueTrackerURL = "https://github.com/3arthqu4ke/mc-runtime-test" +license = "MIT" + +[[mods]] +modId = "mc_runtime_test" +version = "${version}" +displayName = "MC-Runtime-Test" +authors = "3arthqu4ke" +description = ''' + Run tests on the Minecraft client at runtime +''' + +[[dependencies.mc_runtime_test]] +modId = "minecraft" +mandatory = true +versionRange = "[${mc_version}]" +ordering = "NONE" +side = "BOTH" diff --git a/versions/1.21.4/neoforge/resources/META-INF/neoforge.mods.toml b/versions/1.21.4/neoforge/resources/META-INF/neoforge.mods.toml new file mode 100644 index 0000000..2f35f75 --- /dev/null +++ b/versions/1.21.4/neoforge/resources/META-INF/neoforge.mods.toml @@ -0,0 +1,20 @@ +modLoader = "javafml" +loaderVersion = "[1,)" +issueTrackerURL = "https://github.com/3arthqu4ke/mc-runtime-test" +license = "MIT" + +[[mods]] +modId = "mc_runtime_test" +version = "${version}" +displayName = "MC-Runtime-Test" +authors = "3arthqu4ke" +description = ''' + Run tests on the Minecraft client at runtime +''' + +[[dependencies.mc_runtime_test]] +modId = "minecraft" +mandatory = true +versionRange = "[${mc_version}]" +ordering = "NONE" +side = "BOTH" diff --git a/versions/1.21.properties b/versions/1.21.properties deleted file mode 100644 index 9c186be..0000000 --- a/versions/1.21.properties +++ /dev/null @@ -1,5 +0,0 @@ -loaders = fabric,lexforge,neoforge -mappings = mojmap -minecraft_version = 1.21 -neoforge_version = 96-beta -lexforge_version = 51.0.24 diff --git a/versions/1.7.10.properties b/versions/1.7.10.properties deleted file mode 100644 index ac4f972..0000000 --- a/versions/1.7.10.properties +++ /dev/null @@ -1,4 +0,0 @@ -loaders = legacyfabric,lexforge -mappings = searge,mcp:12-1.7.10 -minecraft_version = 1.7.10 -lexforge_version = 10.13.4.1614-1.7.10 diff --git a/versions/1.8.9.properties b/versions/1.8.9.properties deleted file mode 100644 index 922ed15..0000000 --- a/versions/1.8.9.properties +++ /dev/null @@ -1,4 +0,0 @@ -loaders = legacyfabric,lexforge -mappings = searge,mcp:12-1.7.10 -minecraft_version = 1.8.9 -lexforge_version = 11.15.1.2318-1.8.9