diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..429b26984 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +* text=auto eol=lf +*.jar binary +*.zip binary +*.png binary +*.ogg binary +*.ttf binary diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..a0d7cddda --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,229 @@ +name: Base Metals 1.10 CI + +on: + push: + branches: + - master-1.10.2 + - 'feature/**' + pull_request: + branches: + - master-1.10.2 + +permissions: + contents: read + +concurrency: + group: basemetals-1.10-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + cold-forge-bootstrap: + name: Cold Forge bootstrap + runs-on: ubuntu-latest + timeout-minutes: 75 + + steps: + - name: Check out source + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + + - name: Install pinned Java 25 ForgeGradle Mavenizer runtime + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 + with: + distribution: temurin + java-version: '25.0.3+9.0.LTS' + + - name: Install pinned Java 8 compilation toolchain + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 + with: + distribution: temurin + java-version: '8.0.502+7' + + - name: Install pinned Java 17 Gradle runtime + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 + with: + distribution: temurin + java-version: '17.0.1+12' + + - name: Stage checksum-verified legacy dependencies + id: dependencies + shell: bash + run: | + set -euo pipefail + mirror="$RUNNER_TEMP/legacy-dependency-mirror" + bash gradle/stage-legacy-dependencies.sh "$mirror" + echo "repository=$mirror" >> "$GITHUB_OUTPUT" + + - name: Bootstrap Forge from an empty cache + shell: bash + env: + GRADLE_USER_HOME: ${{ runner.temp }}/basemetals-cold-gradle + run: | + set -euo pipefail + test ! -e .gradle + test ! -e "$GRADLE_USER_HOME" + mkdir -p "$GRADLE_USER_HOME" + chmod +x ./gradlew + gradle_args=( + classes verifyReleaseDependencies + -PlegacyDependencyVerificationRepository="${{ steps.dependencies.outputs.repository }}" + --no-daemon --no-build-cache --stacktrace --max-workers=2 + -Porg.gradle.java.installations.paths="$JAVA_HOME,$JAVA_HOME_8_X64,$JAVA_HOME_25_X64" + -Porg.gradle.java.installations.auto-detect=false + -Porg.gradle.java.installations.auto-download=false + ) + ./gradlew "${gradle_args[@]}" + + - name: Verify same-cache bootstrap offline + shell: bash + env: + GRADLE_USER_HOME: ${{ runner.temp }}/basemetals-cold-gradle + run: | + set -euo pipefail + gradle_args=( + classes verifyReleaseDependencies + -PlegacyDependencyVerificationRepository="${{ steps.dependencies.outputs.repository }}" + --rerun-tasks --offline --no-daemon --no-build-cache --stacktrace --max-workers=2 + -Porg.gradle.java.installations.paths="$JAVA_HOME,$JAVA_HOME_8_X64,$JAVA_HOME_25_X64" + -Porg.gradle.java.installations.auto-detect=false + -Porg.gradle.java.installations.auto-download=false + ) + ./gradlew "${gradle_args[@]}" + + build: + name: Build, test, and audit + runs-on: ubuntu-latest + timeout-minutes: 90 + + steps: + - name: Check out source + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + + - name: Install pinned Java 25 ForgeGradle Mavenizer runtime + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 + with: + distribution: temurin + java-version: '25.0.3+9.0.LTS' + + - name: Install pinned Java 8 compilation and runtime toolchain + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 + with: + distribution: temurin + java-version: '8.0.502+7' + + - name: Install pinned Java 17 Gradle runtime + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 + with: + distribution: temurin + java-version: '17.0.1+12' + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6 + + - name: Stage checksum-verified dependencies + id: dependencies + shell: bash + run: | + set -euo pipefail + mirror="$RUNNER_TEMP/legacy-dependency-mirror" + bash gradle/stage-legacy-dependencies.sh "$mirror" + echo "repository=$mirror" >> "$GITHUB_OUTPUT" + + - name: Prepare clean packaged Forge runtime + id: forge + shell: bash + run: | + set -euo pipefail + runtime="$RUNNER_TEMP/forge-1.10.2-2511-runtime" + bash gradle/prepare-packaged-forge-runtime.sh "$runtime" "$JAVA_HOME_8_X64/bin/java" + echo "runtime=$runtime" >> "$GITHUB_OUTPUT" + + - name: Install world-audit tools + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 + with: + python-version: '3.12' + + - name: Install world-audit libraries + run: python3 -m pip install nbtlib==2.0.4 numpy==2.2.6 + + - name: Build, test, audit, qualify runtimes, and generate Eclipse launches + shell: bash + run: | + set -euo pipefail + chmod +x ./gradlew + gradle_args=( + clean check build javadoc verifyReleaseDependencies verifyReleaseArtifacts + writeReleaseChecksums prepareEclipse packagedForgeRuntimeTest + -PpythonExecutable=python3 + -PreleaseBuild=true + -PlegacyDependencyVerificationRepository="${{ steps.dependencies.outputs.repository }}" + -PpackagedForgeRuntimeRoot="${{ steps.forge.outputs.runtime }}" + --no-daemon --stacktrace --max-workers=2 + -Porg.gradle.java.installations.paths="$JAVA_HOME,$JAVA_HOME_8_X64,$JAVA_HOME_25_X64" + -Porg.gradle.java.installations.auto-detect=false + -Porg.gradle.java.installations.auto-download=false + ) + ./gradlew "${gradle_args[@]}" + cp build/release/SHA256SUMS "$RUNNER_TEMP/first-SHA256SUMS" + + - name: Verify the prepared release bundle + shell: bash + run: | + set -euo pipefail + bundle="$RUNNER_TEMP/release-input" + mkdir -p "$bundle" + cp build/libs/BaseMetals-2.5.1.110021{,-sources,-javadoc}.jar "$bundle/" + cp build/release/SHA256SUMS CHANGELOG.txt "$bundle/" + ./gradlew verifyPreparedReleaseArtifacts verifyMavenCoordinates \ + -PpreparedReleaseDir="$bundle" \ + -PlegacyDependencyVerificationRepository="${{ steps.dependencies.outputs.repository }}" \ + -Porg.gradle.java.installations.paths="$JAVA_HOME,$JAVA_HOME_8_X64,$JAVA_HOME_25_X64" \ + -Porg.gradle.java.installations.auto-detect=false \ + -Porg.gradle.java.installations.auto-download=false \ + --offline --no-daemon --stacktrace --max-workers=2 + + - name: Verify reproducible release archives + shell: bash + run: | + set -euo pipefail + gradle_args=( + clean build javadoc verifyReleaseArtifacts writeReleaseChecksums + -PreleaseBuild=true + -PlegacyDependencyVerificationRepository="${{ steps.dependencies.outputs.repository }}" + --offline --no-daemon --no-build-cache --stacktrace --max-workers=2 + -Porg.gradle.java.installations.paths="$JAVA_HOME,$JAVA_HOME_8_X64,$JAVA_HOME_25_X64" + -Porg.gradle.java.installations.auto-detect=false + -Porg.gradle.java.installations.auto-download=false + ) + ./gradlew "${gradle_args[@]}" + cmp "$RUNNER_TEMP/first-SHA256SUMS" build/release/SHA256SUMS + + - name: Upload audited release candidate + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: BaseMetals-1.10.2-${{ github.sha }} + if-no-files-found: error + retention-days: 30 + path: | + build/libs/BaseMetals-2.5.1.110021.jar + build/libs/BaseMetals-2.5.1.110021-sources.jar + build/libs/BaseMetals-2.5.1.110021-javadoc.jar + build/release/SHA256SUMS + CHANGELOG.txt + + - name: Upload bounded diagnostics on failure + if: failure() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: BaseMetals-1.10.2-diagnostics-${{ github.sha }} + if-no-files-found: ignore + retention-days: 14 + path: | + build/test-results/** + build/reports/** + build/packaged-forge-checks/**/*-console.txt + build/packaged-forge-checks/**/logs/*.log + build/packaged-forge-checks/**/crash-reports/*.txt + build/packaged-forge-checks/**/*-worldgen.json + build/packaged-forge-checks/**/*-review.json + build/packaged-forge-checks/**/*-launch.json + build/problems/** diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 000000000..28377a0b8 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,80 @@ +name: CodeQL + +on: + push: + branches: + - master-1.10.2 + - 'feature/**' + pull_request: + branches: + - master-1.10.2 + schedule: + - cron: '47 7 * * 4' + +permissions: + actions: read + contents: read + security-events: write + +jobs: + analyze: + name: Analyze Java + runs-on: ubuntu-latest + timeout-minutes: 60 + + steps: + - name: Check out source + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + + - name: Install pinned Java 25 ForgeGradle Mavenizer runtime + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 + with: + distribution: temurin + java-version: '25.0.3+9.0.LTS' + + - name: Install pinned Java 8 compilation toolchain + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 + with: + distribution: temurin + java-version: '8.0.502+7' + + - name: Install pinned Java 17 Gradle runtime + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 + with: + distribution: temurin + java-version: '17.0.1+12' + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6 + + - name: Initialize CodeQL + uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4 + with: + languages: java-kotlin + + - name: Stage checksum-verified dependencies + id: dependencies + shell: bash + run: | + set -euo pipefail + mirror="$RUNNER_TEMP/legacy-dependency-mirror" + bash gradle/stage-legacy-dependencies.sh "$mirror" + echo "repository=$mirror" >> "$GITHUB_OUTPUT" + + - name: Compile production code + shell: bash + run: | + set -euo pipefail + chmod +x ./gradlew + gradle_args=( + clean classes + -PlegacyDependencyVerificationRepository="${{ steps.dependencies.outputs.repository }}" + --no-daemon --no-build-cache --stacktrace --max-workers=2 + -Porg.gradle.java.installations.paths="$JAVA_HOME,$JAVA_HOME_8_X64,$JAVA_HOME_25_X64" + -Porg.gradle.java.installations.auto-detect=false + -Porg.gradle.java.installations.auto-download=false + ) + ./gradlew "${gradle_args[@]}" + + - name: Analyze + uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4 diff --git a/.github/workflows/release-on-tag.yml b/.github/workflows/release-on-tag.yml new file mode 100644 index 000000000..06f795b11 --- /dev/null +++ b/.github/workflows/release-on-tag.yml @@ -0,0 +1,87 @@ +name: Start Base Metals release from tag + +on: + push: + tags: + - '*.*.*.*' + +permissions: + actions: read + contents: read + +concurrency: + group: basemetals-release-starter-${{ github.ref_name }} + cancel-in-progress: false + +jobs: + validate-release-tag: + name: Validate tag for manual release confirmation + if: github.repository == 'MinecraftModDevelopmentMods/BaseMetals' + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - name: Check out tagged source + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + fetch-depth: 0 + + - name: Validate release tag, target metadata, and prior CI + env: + GH_TOKEN: ${{ github.token }} + shell: bash + run: | + set -euo pipefail + + value() { sed -n "s/^$1=//p" gradle.properties; } + release_version="$(value mod_version)" + minecraft_version="$(value minecraft_version)" + loader_name="$(value loader_name)" + loader_code="$(value loader_code)" + + IFS=. read -r mc_major mc_minor mc_patch extra <<<"$minecraft_version" + mc_patch="${mc_patch:-0}" + if [[ -n "${extra:-}" || ! "$mc_major" =~ ^[0-9]+$ || ! "$mc_minor" =~ ^[0-9]+$ \ + || ! "$mc_patch" =~ ^[0-9]+$ || "$loader_name:$loader_code" != 'forge:1' ]]; then + echo "Invalid target metadata $minecraft_version/$loader_name/$loader_code" >&2 + exit 1 + fi + printf -v minor_padded '%02d' "$((10#$mc_minor))" + printf -v patch_padded '%02d' "$((10#$mc_patch))" + target_suffix="${mc_major}${minor_padded}${patch_padded}${loader_code}" + + if [[ ! "$release_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.${target_suffix}$ ]]; then + echo "mod_version $release_version does not match target $target_suffix" >&2 + exit 1 + fi + if [[ "$GITHUB_REF_NAME" != "$release_version" ]]; then + echo "Release tag must equal mod_version $release_version; found $GITHUB_REF_NAME" >&2 + exit 1 + fi + + successful_ci="$(gh api \ + "repos/$GITHUB_REPOSITORY/commits/$GITHUB_SHA/check-runs?per_page=100" \ + --jq '[.check_runs[] | select(.name == "Build, test, and audit" and .conclusion == "success")] | length')" + if [[ "$successful_ci" -lt 1 ]]; then + echo "The tagged commit has no successful Build, test, and audit check" >&2 + exit 1 + fi + + - name: Record the required manual publication step + env: + RELEASE_WORKFLOW_URL: https://github.com/${{ github.repository }}/actions/workflows/deploy-release.yml + shell: bash + run: | + { + echo "## Release candidate validated" + echo + echo "Tag \`$GITHUB_REF_NAME\` matches the selected target and has a successful Build, test, and audit check." + echo + echo "**Nothing has been published.**" + echo + echo "To continue, open [Release Base Metals]($RELEASE_WORKFLOW_URL), select **Run workflow**, and enter:" + echo + echo "- release_version: \`$GITHUB_REF_NAME\`" + echo "- curseforge_release_level: \`release\`, \`beta\`, or \`alpha\`" + echo "- confirm_live_publication: \`true\`" + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/validate-gradle-build.yml b/.github/workflows/validate-gradle-build.yml new file mode 100644 index 000000000..81f7cd4ee --- /dev/null +++ b/.github/workflows/validate-gradle-build.yml @@ -0,0 +1,23 @@ +name: Validate Gradle Wrapper + +on: + push: + branches: + - master-1.10.2 + - 'feature/**' + pull_request: + branches: + - master-1.10.2 + +permissions: + contents: read + +jobs: + validation: + name: Validation + runs-on: ubuntu-latest + steps: + - name: Check out source + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + - name: Validate wrapper integrity + uses: gradle/actions/wrapper-validation@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6 diff --git a/.gitignore b/.gitignore index db0a3f03f..72e54f8b7 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,42 @@ libs/* !/libs/README.txt .DS_Store +[Dd]esktop.ini +[Tt]humbs.db + +# Local working notes and generated validation reports +AGENTS.md +CLAUDE.md +GEMINI.md +agent-notes/ +.agents/ +.codex/ +.claude/ +.gemini/ +development.md +registry_audit.md +validation.md +validation-*.md +ore_textures.md +agent-report*.md +codex-report*.md + +# Generated test evidence and Python bytecode +__pycache__/ +*.py[cod] +logs/ +crash-reports/ +run-*/ +*.log +*.hprof +*.jfr + +# Standalone server installs and disposable validation worlds +/libraries/ +/forge-*.jar +/minecraft_server*.jar +/world/ +/saves/ +.verification*/ +.validation*/ +validation-reports/ diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 4553e94ce..000000000 --- a/.travis.yml +++ /dev/null @@ -1,13 +0,0 @@ -language: java -sudo: false -cache: - directories: - - $HOME/.gradle -notifications: - email: false -jdk: - - oraclejdk8 -before_install: - - chmod a+x gradlew -install: ./gradlew setupCIWorkspace -S -script: ./gradlew clean build -S diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 843678a08..9ca7d0b6e 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1 +1,40 @@ +2.5.1.110021 for 1.10.2 + +- Require OreSpawn 4.1.2.110021 or a compatible newer 4.x release, with Forge + 12.18.3.2511 or later. Development uses the checksum-verified public release. +- Replace the old OreSpawn plugin and internal fallback generator with a native + OreSpawn provider. Keep the eleven ore rules and their historical distributions. +- Let OreSpawn import existing OS1 and OS3 configuration files, preserving custom + and disabled rules. Existing world profiles remain authoritative. +- Keep existing OreSpawn 4 provider settings when importing older files for the + first time, using the fix in the required OreSpawn release. +- Respect OreSpawn's Manage Vanilla Ores switch after importing old rules. + With it off, Minecraft places its own ores. With it on, the imported settings + replace the matching vanilla/default pass without generating the ores twice. + Existing terrain and saved settings are left alone. +- Retire the using_orespawn and fallback_orespawn switches. Ore generation is + now configured in OreSpawn, including when another mod places the ores. +- Keep door items, coal powder and liquid Mercury when upgrading from the original + Cyano release. Old item names continue to work after restarting, including items + stored in chunks that have not been opened yet. +- Leave Base Metals iron nuggets unchanged; Minecraft 1.10 has no vanilla equivalent. +- Support all eighteen Base Metal Golems from the unchanged 6.02.1 add-on, + including golems saved by the original Cyano release. +- Move Base Metals' Java packages and Maven group to zone.moddev.mc.basemetals. + Existing MMD add-ons keep using the same classes through a name adapter; + saved IDs, configuration names and the bundled library API are unchanged. +- Use the same Major.Minor.Patch.Target version format as the newer branches. +- Update to ForgeGradle 7 and Gradle 9.6.1 while keeping Java 8 game compatibility. + Eclipse launches now quote paths with spaces, use processed resources and leave + optional integration APIs and test mods off the normal runtime classpath. +- Pin and checksum the old integration dependencies, and add repeatable main, + sources and Javadoc jars with release-artifact checks. +- Add build, CodeQL and wrapper checks for master-1.10.2, with automated + Cyano/MMD save upgrades, OreSpawn configuration imports and vanilla takeover tests. +- Connect this target to the shared release action, using one verified bundle + for Maven, CurseForge and GitHub. Keep publication behind explicit confirmation. +- Normalize changed text files to LF and fix the stray import whitespace. +- Leave Windows folder metadata out of downloads and use UTF-8 for sources + and Javadocs on every platform. + 2.5.0 for 1.10.2 diff --git a/README.md b/README.md index 3b377a551..8a2c9aecc 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ -# Base Metals 1.10.2 [![Build Status](https://ci.mcmoddev.com/job/Base%20Metals%201.10.2/badge/icon)](https://ci.mcmoddev.com/job/Base%20Metals%201.10.2/) [![CurseForge](http://cf.way2muchnoise.eu/base-metals.svg)](https://minecraft.curseforge.com/projects/base-metals) [![Versions](http://cf.way2muchnoise.eu/versions/base-metals.svg)](https://minecraft.curseforge.com/projects/base-metals) +# Base Metals 1.10.2 + +[![Build](https://github.com/MinecraftModDevelopmentMods/BaseMetals/actions/workflows/ci.yml/badge.svg?branch=master-1.10.2)](https://github.com/MinecraftModDevelopmentMods/BaseMetals/actions/workflows/ci.yml?query=branch%3Amaster-1.10.2) +[![CodeQL](https://github.com/MinecraftModDevelopmentMods/BaseMetals/actions/workflows/codeql-analysis.yml/badge.svg?branch=master-1.10.2)](https://github.com/MinecraftModDevelopmentMods/BaseMetals/actions/workflows/codeql-analysis.yml?query=branch%3Amaster-1.10.2) ## Base Metals Mod @@ -10,7 +13,7 @@ Fantasy metals have been added to the Nether and the End: Cold Iron, Mithril, Ad ## Metals and Alloys -###Natural Metals +### Natural Metals **Antimony**: @@ -34,7 +37,7 @@ Fantasy metals have been added to the Nether and the End: Cold Iron, Mithril, Ad -###Metal Alloys +### Metal Alloys *Metal alloys are made by crafting together the powdered forms of the required metals, and then smelting the resulting alloy blend in a furnace.* **Brass**: This alloy of copper and zinc (3:1 ratio) is soft, but has a beautiful golden color. @@ -50,7 +53,7 @@ Fantasy metals have been added to the Nether and the End: Cold Iron, Mithril, Ad **Steel**: This alloy of iron and carbon (8:1 ratio) is as hard as iron and much more durable. -###Fantasy Metals +### Fantasy Metals **Adamantine**: (found in the Nether): Adamantine is a rare magical metal that is as strong as diamond, maybe even stronger. Armor made from Adamantine grants resistance to damage and tools made from adamantine are extra effective against monsters that have more than 10 hearts of health. @@ -72,22 +75,75 @@ Also known as a sledgehammer, this tool is designed for pulverizing rocks. Using ## Requirements -This mod requires that you install Minecraft Forge version 1.10.2-12.18.3.2185 or later (earlier versions of Forge for Minecraft 1.10.2 may work, but no guarantees). +Install Forge `12.18.3.2511` or later and +[MMD OreSpawn `4.1.2.110021`](https://www.curseforge.com/minecraft/mc-mods/mmd-orespawn/files/9276430) +or a newer compatible 4.x release for Minecraft 1.10.2. Base Metals no longer +includes its own fallback ore generator. + +OreSpawn handles ore placement and imports old OreSpawn configuration files. +The eleven ore defaults are unchanged. See [ore generation and upgrades](docs/ORE_GENERATION.md) +for configuration locations and advice for existing worlds. + +## Building and running from source + +This branch uses ForgeGradle 7 and the Gradle wrapper, like the newer branches. +Minecraft and the mod still run on Java 8. Gradle uses Java 17, while the initial +Minecraft setup also needs Java 25 for Mavenizer. + +Import the inner `BaseMetals` directory as an existing Gradle project. Use the +wrapper, then run `prepareEclipse` and choose `runClient` or `runServer`. +Optional integration mods are compile-time APIs, not ordinary launch dependencies. +See [the build guide](docs/BUILDING.md) for toolchains, checks and legacy-world tests. + +Source for this version lives on `master-1.10.2`. Releases use the shared +**Release Base Metals** action after the branch's build and compatibility checks +pass. Normal builds and tags do not publish anything. ## Installing -After you have successfully installed Forge, simply place the file *basemetals-#.#.#.jar* in your *mods* folder. You can get the basemetals-#.#.jar file from the Releases tab of this repository page. +Install Forge and OreSpawn, then put `BaseMetals-2.5.1.110021.jar` in your +`mods` folder. Downloads are available on CurseForge and the repository's +Releases page once the version is published. -##NEI +## NEI The Crack Hammer recipes from this mod will appear in NEI if you have NEI installed. Hooray! +## Upgrading an existing 1.10 world + +Back up your world before updating any mods. This build recognizes the original +Cyano names for door items, coal powder and liquid Mercury, including items in +chunks you visit later. Base Metals iron nuggets keep their existing identity. + +Mods that use Cyano's old Java API may need their own updates. Restoring saved +items cannot, by itself, repair those Java references. Base Metal Golems +`6.02.1`, with Extra Golems `6.02`, is supported by a small compatibility layer: +its eighteen golems use the existing Base Metals blocks and ingots. Neither +add-on needs changing, and neither is required when playing without golems. + +This release uses version `2.5.1.110021`. The final six digits identify Minecraft +1.10.2 on Forge, following the same version format as the newer branches. +The mod ID, saved block and item names, resources and configuration names remain +`basemetals`; moving the Java packages does not rename anything in your world. + ## Mod API -Check the developer releases to download the files *basemetals-#.#.#-deobf.jar*, *basemetals-#.#.#-sources.jar*, *basemetals-#.#.#-javadoc.jar*. In your Eclipse project (and *build.gradle* file), add *basemetals-#.#.#-deobf.jar* as a library dependency. All items and blocks can be conveniently accessed via classes in the *com.mcmoddev.basemetals.init* package. New recipes for the Crack Hammer can be added via the *com.mcmoddev.lib.registry.CrusherRecipeRegistry* class. +New integrations should use `zone.moddev.mc.basemetals.init` for materials, +blocks and items. Maven builds use +`zone.moddev.mc.basemetals:BaseMetals:2.5.1.110021`. Crack Hammer recipes still +use `com.mcmoddev.lib.registry.CrusherRecipeRegistry`; the bundled library API +has not moved. + +An early-loading adapter redirects compiled references to the former +`com.mcmoddev.basemetals` classes to their new names. They share the same mod +instance, material objects and registries, rather than registering a second +copy. This covers class references and literal reflection names, not arbitrary +class names assembled by an add-on at runtime. The Cyano compatibility layer +only supplies the block and ingot fields used by Base Metal Golems; it is not a +replacement for the complete Cyano API. ## Q&A diff --git a/build.gradle b/build.gradle index 8f9b97010..a6aa42cb8 100644 --- a/build.gradle +++ b/build.gradle @@ -1,496 +1,175 @@ -buildscript { - repositories { - jcenter() - maven { - name = 'forge' - url = 'http://files.minecraftforge.net/maven' - } - maven { - name = 'gradle' - url 'https://plugins.gradle.org/m2/' - } - maven { - name = 'sonatype' - url = 'https://oss.sonatype.org/content/groups/public' - } - } - dependencies { - classpath "net.minecraftforge.gradle:${project.fg_version}" - classpath 'org.junit.platform:junit-platform-gradle-plugin:1.2.+' - } -} - -plugins { -//id "com.github.spotbugs" version "1.6.1" -//id "com.jaredsburrows.checkerframework" version "0.2.1" - id "com.matthewprenger.cursegradle" version "1.0.10" - id "org.sonarqube" version "2.6.2" -} - -apply plugin: 'net.minecraftforge.gradle.forge' -apply plugin: 'com.matthewprenger.cursegradle' -apply plugin: 'maven-publish' -apply plugin: 'org.sonarqube' -apply plugin: 'org.junit.platform.gradle.plugin' -apply plugin: 'jacoco' -//apply plugin: 'spotbugs' -//apply plugin: 'com.jaredsburrows.checkerframework' - -repositories { - mavenLocal() - mavenCentral() -} - -dependencies { - testCompile('org.junit.jupiter:junit-jupiter-api:5.2.+') - testCompile('org.junit.platform:junit-platform-launcher:1.2.+') - testRuntime('org.junit.jupiter:junit-jupiter-engine:5.2.+') - testCompile('org.mockito:mockito-core:2.+') -} - -project.afterEvaluate { - def junitPlatformTestTask = project.tasks.getByName('junitPlatformTest') - - // configure jacoco to analyze the junitPlatformTest task - jacoco { - // this tool version is compatible with - toolVersion = '0.8.1' - applyTo junitPlatformTestTask - } - - // create junit platform jacoco task - project.task(type: JacocoReport, "junitPlatformJacocoReport", - { - sourceDirectories = files("./src/main") - classDirectories = files("$buildDir/classes/main") - executionData junitPlatformTestTask - }) -} - -junitPlatform { - // platformVersion '1.2.0' - // logManager 'org.apache.logging.log4j.jul.LogManager' - reportsDir file("${project.buildDir}/test-results/junit-platform") - // enableStandardTestTask true - // selectors (optional) - // filters (optional) -} - -tasks.withType(JavaCompile) { - options.encoding = 'UTF-8' - options.compilerArgs = [ - '-Xlint:deprecation', // deprecation, rawtypes, cast, unchecked, all -// '-Xdiags:verbose', -// '-Werror' - ] -} - -javadoc { - failOnError = false -} - -def mod_file = getModFile() -def short_version = getVersion('VERSION', mod_file) -version = mc_version + '-' + short_version -def display_version = short_version - -if (System.getenv().BUILD_NUMBER) { - version += '.' + System.getenv().BUILD_NUMBER - display_version += '.' + System.getenv().BUILD_NUMBER -} - -sourceCompatibility = targetCompatibility = '1.8' - -eclipse.project { - buildCommand 'org.eclipse.buildship.core.gradleprojectbuilder' - natures 'org.eclipse.buildship.core.gradleprojectnature' -} - -class Secrets { - def data = null - - def getProperty(String key) { - return data ? data[key] : '' - } -} - -import groovy.json.JsonSlurper - -def secretFile -if (System.getenv().SECRET_FILE) { - secretFile = file System.getenv().SECRET_FILE -} else { - secretFile = file 'secret.json' -} - -project.ext.secret = new Secrets() -if (secretFile.exists()) { - secretFile.withReader { - project.ext.secret.data = new JsonSlurper().parse it - } -} - -minecraft { - version = project.forge_mc_version + '-' + project.forge_version - runDir = 'run' - mappings = project.mcp_mappings - - if (project.hasProperty('signSHA1')) { - replace '@FINGERPRINT@', project.findProperty('signSHA1') - } else { - replace '@FINGERPRINT@', '' - } - - if (project.hasProperty('coreplugin')) { - clientJvmArgs += '-Dfml.coreMods.load=' + project.coreplugin - serverJvmArgs += '-Dfml.coreMods.load=' + project.coreplugin - } - - if (project.hasProperty('mc_username')) { - clientRunArgs += ['--username', project.mc_username] - if (project.hasProperty('mc_password')) { - clientRunArgs += ['--password', project.mc_password] - } - } - if (project.hasProperty('mc_uuid')) { - clientRunArgs += ['--uuid', project.mc_uuid] - } - - // disable server gui - serverRunArgs += 'nogui' - - // skip the screen to confirm that you want to load a world with missing registry entries - serverJvmArgs += '-Dfml.doNotBackup=true' - clientJvmArgs += '-Dfml.doNotBackup=true' - - // skip having to confirm on server - serverJvmArgs += '-Dfml.queryResult=confirm' - - //skip jansi warnings in the log - serverJvmArgs += '-Dlog4j.skipJansi=true' - clientJvmArgs += '-Dlog4j.skipJansi=true' - - if (project.hasProperty('client_args')) { - clientJvmArgs += project.client_args - } - if (project.hasProperty('server_args')) { - serverJvmArgs += project.server_args - } -} - -repositories { - maven { // Mantle, TCon, JEI - name 'DVS1 Maven FS' - url 'http://dvs1.progwml6.com/files/maven/' - } - maven { // CCL, CCC, NEI - name 'Chickenbones Repo' - url 'http://chickenbones.net/maven/' - } - maven { // The One Probe, EnderIO, EnderCore - name 'tterrag' - url 'http://maven.tterrag.com/' - } - maven { // Tesla - name 'MMD' - url 'https://maven.mcmoddev.com/' - } - maven { // MCMultipart - name 'amadornes' - url 'http://maven.amadornes.com/' - } - maven { // CraftTweaker, ZenScript - name 'blamejared' - url 'http://maven.blamejared.com/' - } - maven { // IC2 - name 'industrialcraft' - url 'http://maven.ic2.player.to/' - } - maven { // HWYLA - name 'tehnut' - url 'http://tehnut.info/maven/' - } - maven { // CoFH - name 'Covers Maven' - url 'http://maven.covers1624.net/' - } - maven { - name = 'CurseForge' - url = 'https://minecraft.curseforge.com/api/maven/' - } - maven { - name 'opencomputers' - url 'http://maven.cil.li/' - } - maven { // Mekanism, TAIGA - name 'jitpack' - url 'https://jitpack.io/' - } - maven { // Buildcraft - name 'buildcraft' - url 'https://mod-buildcraft.com/maven/' - } - maven { // Storage Drawers - name 'thiakil' - url 'http://maven.thiakil.com/' - } - ivy { - url "http://ae-mod.info/builds" - layout "pattern", { - artifact "[module]-[revision](-[classifier])(.[ext])" - } - } -} - -dependencies { - deobfProvided ("com.github.aidancbrady:Mekanism:${mekanism_version}") { - exclude group: 'MCMultiPart' - exclude group: 'mezz.jei' - - } - - deobfProvided ("com.github.TeamFRM:TAIGA:${taiga_version}") { - exclude group: 'mezz.jei' - exclude module: 'Mantle' - exclude module: 'TConstruct' - } - - deobfProvided "slimeknights.mantle:Mantle:${mc_version}-${mantle_version}" - - deobfProvided "slimeknights:TConstruct:${mc_version}-${tconstruct_version}" - - deobfProvided "mezz.jei:jei_${mc_version}:${jei_version}" -//deobfProvided "mezz.jei:jei_${mc_version}:${jei_version}:api" - - deobfProvided "codechicken:CodeChickenLib:${mc_version}-${ccl_version}:universal" - - deobfProvided ("codechicken:CodeChickenCore:${mc_version}-${ccc_version}:universal") { - exclude group: 'codechicken' - } - - // Crashes in game - deobfProvided ("codechicken:NotEnoughItems:${mc_version}-${nei_version}:universal") { - exclude group: 'codechicken' - exclude group: 'mezz.jei' - } - - deobfProvided "com.mcmoddev:AdditionalLootTables:${mc_version}-${alt_version}" - runtime "com.mcmoddev:AdditionalLootTables:${mc_version}-${alt_version}" - deobfProvided "com.mcmoddev:OreSpawn:${mc_version}-${orespawn_version}" - runtime "com.mcmoddev:OreSpawn:${mc_version}-${orespawn_version}" - - deobfProvided "MCMultiPart:MCMultiPart:${mcmp_version}:universal" - - deobfProvided "mcjty.theoneprobe:TheOneProbe:${top_mc_version}-${top_version}" -//deobfProvided "mcjty.theoneprobe:TheOneProbe:${top_mc_version}-${top_version}:api" - - deobfProvided "portablejim:VeinMiner:${veinminer_version}:api" - - deobfProvided "net.industrial-craft:industrialcraft-2:${ic2_version}" -//deobfProvided "net.industrial-craft:industrialcraft-2:${ic2_version}:api" - - deobfProvided ("MineTweaker3:MineTweaker3-MC1102-Main:${mc_version}-${minetweaker_version}") { - exclude group: 'org.ow2.asm' - } - - deobfProvided "com.blamejared:MTLib:${mtlib_version}" - - deobfProvided "mcp.mobius.waila:Hwyla:${hwyla_version}" -//deobfProvided "mcp.mobius.waila:Hwyla:${hwyla_version}:api" - - deobfProvided ("cofh:CoFHCore:${mc_version}-${cofhcore_version}:universal") { - exclude group: 'mezz.jei' - } - - deobfProvided ("cofh:ThermalFoundation:${mc_version}-${thermal_foundation_version}:universal") { - exclude module: 'CoFHCore' - } - - deobfProvided ("cofh:ThermalExpansion:${mc_version}-${thermal_expansion_version}:universal") { - exclude group: 'codechicken' - exclude group: 'mezz.jei' - exclude module: 'CoFHCore' - exclude module: 'ThermalFoundation' - } - - deobfProvided "com.enderio.core:EnderCore:${mc_version}-${endercore_version}" -//deobfProvided "com.enderio.core:EnderCore:${mc_version}-${endercore_version}:api" - -//deobfProvided "com.enderio:EnderIO:${mc_version}-${enderio_version}" - deobfProvided "com.enderio:EnderIO:${mc_version}-${enderio_version}:api" - - deobfProvided "mcjty:compatlayer:1.10-${compatlayer_version}" -} - -processResources { - inputs.property 'version', project.version - inputs.property 'mcversion', project.minecraft.version - - from (sourceSets.main.resources.srcDirs) { - include 'mcmod.info' - expand 'version': short_version, 'mcversion': project.minecraft.version - } - - from (sourceSets.main.resources.srcDirs) { - exclude 'mcmod.info' - } - - rename '(.+_at.cfg)', 'META-INF/$1' -} - -jar { - manifest { - if (project.hasProperty('coreplugin')) { - attributes 'FMLCorePluginContainsFMLMod': 'true' - attributes 'FMLCorePlugin': project.coreplugin - } -// attributes 'FMLAT' : 'mmdlib_at.cfg' - } -} - -task deobfJar(type: Jar) { - classifier = 'deobf' - from sourceSets.main.output -} - -task signJar(type: SignJar, dependsOn: reobfJar) { - // Skips if the keyStore property is missing. - onlyIf { - project.hasProperty('keyStore') - } - - // findProperty allows us to reference the property without it existing. - // Using project.propName would cause the script to fail validation if - // the property did not exist. - keyStore = project.findProperty('keyStore') - alias = project.findProperty('keyStoreAlias') - storePass = project.findProperty('keyStorePass') - keyPass = project.findProperty('keyStoreKeyPass') - inputFile = jar.archivePath - outputFile = jar.archivePath -} - -build.dependsOn signJar - -task javadocJar(type: Jar, dependsOn: javadoc) { - classifier = 'javadoc' - from javadoc.getDestinationDir() -} - -artifacts { - archives deobfJar - archives sourceJar - archives javadocJar -} - -publishing { - publications { - mavenJava(MavenPublication) { - groupId project.group - artifactId project.archivesBaseName - version project.version - from components.java - artifact sourceJar { - classifier 'sources' - } - artifact deobfJar { - classifier 'deobf' - } - artifact javadocJar { - classifier 'javadoc' - } - } - } - repositories { - maven { - credentials { - username secret.username - password secret.password - } - url secret.url - } - } -} - -curseforge { - if (project.hasProperty('cf_project_id') && project.hasProperty('cf_release_type') && - ((project.cf_release_type == 'alpha') || (project.cf_release_type == 'beta') || (project.cf_release_type == 'release'))) { - apiKey = secret.curseforgeAPIKey - project { - id = project.cf_project_id - changelog = file('CHANGELOG.txt') - releaseType = project.cf_release_type - addGameVersion(project.mc_version) - mainArtifact(jar) { - displayName = "$project.description $display_version" - relations { - requiredLibrary 'mmd-orespawn' - optionalLibrary 'additional-loot-tables' - } - } - addArtifact(sourceJar) { - displayName = "$project.description $display_version Sources" - } - addArtifact(deobfJar) { - displayName = "$project.description $display_version Development" - } - addArtifact(javadocJar) { - displayName = "$project.description $display_version Javadoc" - } - } - } -} - -sonarqube { - properties { - property 'sonar.host.url', secret.sonarHost - property 'sonar.organization', secret.sonarOrganization - property 'sonar.login', secret.sonarToken - // property 'sonar.projectName', project.archivesBaseName - // property 'sonar.projectKey', "$project.group:$project.archivesBaseName" - property 'sonar.branch.name', 'master-1.10.2' - // property 'sonar.branch.target', '' - property 'sonar.junit.reportPaths', "${project.buildDir}/test-results/junit-platform/*.xml" - property 'sonar.jacoco.reportPaths', "${project.buildDir}/jacoco/junitPlatformTest.exec" - } -} - -String getModFile() { - String path = '' - FileTree tree = fileTree(dir: 'src/main/java') - tree.include '**/*.java' - tree.visit { element -> - if (element.file.isFile()) { - element.file.eachLine { String s -> - s = s.trim() - if (s.startsWith('@Mod(')) { - path = "src/main/java/$element.relativePath" - } - } - } - } - return path -} - -String getVersion(String type, String mod_file) { - String major = '0' - String revision = '0' - String patch = '0' - File file = file(mod_file) - def prefix = ~/^(?:public|protected|private) static final String $type = ".*$/ - file.eachLine { String s -> - s = s.trim() - if (s ==~ prefix || prefix.matcher(s).matches()) { - String[] bits = s.split("=") - String interesting = bits[1].trim() - interesting = interesting.substring(1, interesting.length() - 2) - String[] pts = interesting.trim().split("\\.") - - major = pts[0] ?: '0' - revision = pts[1] ?: '0' - patch = pts[2] ?: '0' - } - } - return "$major.$revision.$patch" -} +import org.apache.tools.ant.filters.FixCrLfFilter + +plugins { + id 'java' + id 'eclipse' + id 'idea' + id 'maven-publish' + id 'net.minecraftforge.renamer' version '1.1.5' + id 'net.minecraftforge.gradle' version '7.0.34' +} + +group = project.mod_group +version = project.mod_version +base.archivesName = 'BaseMetals' + +if (!(project.version ==~ /\d+\.\d+\.\d+\.110021/)) { + throw new GradleException('Use Major.Minor.Patch.110021 for Minecraft 1.10.2 on Forge.') +} + +// Gradle and Mavenizer use modern JDKs; the mod and game still use Java 8. +java { + toolchain.languageVersion = JavaLanguageVersion.of(8) + withSourcesJar() + withJavadocJar() +} +tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' + sourceCompatibility = targetCompatibility = '1.8' + options.compilerArgs.addAll(['-Xmaxerrs', '1000']) +} +tasks.named('compileTestJava', JavaCompile) { options.compilerArgs.add('-proc:none') } +tasks.withType(Test).configureEach { useJUnitPlatform() } +tasks.withType(Javadoc).configureEach { + failOnError = false + options.encoding = 'UTF-8' + options.addStringOption('Xdoclint:none', '-quiet') + options.addStringOption('docencoding', 'UTF-8') + options.addStringOption('charset', 'UTF-8') + options.addBooleanOption('notimestamp', true) +} +tasks.withType(AbstractArchiveTask).configureEach { + preserveFileTimestamps = false + reproducibleFileOrder = true + includeEmptyDirs = false + exclude '**/desktop.ini', '**/Thumbs.db', '**/.DS_Store' +} +// Mapped dependency bytes no longer match their original signatures. Keep the +// verified downloads intact and strip signatures only from renamed copies. +tasks.withType(net.minecraftforge.renamer.gradle.RenameJar).configureEach { + additionalArgs.addAll('--strip-sigs', 'ALL', '--disable-abstract-param') +} + +def textPatterns = ['cfg', 'css', 'html', 'info', 'java', 'js', 'json', 'lang', 'mcmeta', 'md', 'properties', 'txt', 'xml'] + .collect { "**/*.${it}".toString() } +def normalizeText = { details -> + details.filter(FixCrLfFilter, eol: FixCrLfFilter.CrLf.newInstance('lf'), + eof: FixCrLfFilter.AddAsisRemove.newInstance('asis')) +} + +def developmentOutput = layout.buildDirectory.dir('forge10-development/main') +def prepareDevelopmentMod = tasks.register('prepareDevelopmentMod', Sync) { + group = 'ide' + description = 'Merges production classes and processed resources for Forge 1.10 launches.' + dependsOn tasks.named('classes') + from tasks.named('compileJava', JavaCompile).flatMap { it.destinationDirectory } + from sourceSets.main.output.resourcesDir + into developmentOutput +} + +minecraft { + mappings channel: project.mappings_channel, version: project.mappings_version + if (gradle.startParameter.offline) mavenizerArguments.add('--offline') + runs { + configureEach { + workingDir.convention layout.projectDirectory.dir( + providers.gradleProperty('baseMetalsRunDirectory').getOrElse('run')) + environment 'MC_VERSION', project.minecraft_version + environment 'MOD_CLASSES', developmentOutput.get().asFile.absolutePath + systemProperty 'fml.coreMods.load', project.coreplugin + systemProperty 'log4j.skipJansi', 'true' + systemProperty 'forge.logging.console.level', 'info' + systemProperty 'log4j.configurationFile', file('gradle/ide/log4j2.xml').toURI().toString() + if (providers.gradleProperty('baseMetalsLaunchProbePhase').isPresent()) { + systemProperty 'launch.phase', providers.gradleProperty('baseMetalsLaunchProbePhase').get() + } + } + register('client') + register('server') { args 'nogui' } + } +} + +tasks.matching { it.name in ['runClient', 'runServer'] }.configureEach { + dependsOn prepareDevelopmentMod + dependsOn 'prepareJava8Launcher' + doFirst { + Set splitOutputs = sourceSets.main.output.files.collect { it.canonicalFile } as Set + classpath = files(developmentOutput, tasks.named('prepareJava8Launcher'), classpath.filter { + !splitOutputs.contains(it.canonicalFile) && it.name != 'slime-launcher-0.2.2.jar' + }) + environment 'MC_VERSION', project.minecraft_version + environment 'MOD_CLASSES', developmentOutput.get().asFile.absolutePath + } +} + +def legacyMirror = file(providers.gradleProperty('legacyDependencyVerificationRepository') + .orElse(providers.gradleProperty('legacyDependencyRepository')) + .getOrElse('.gradle/legacy-dependencies')) +def legacyRepository = { String remote -> legacyMirror.isDirectory() ? legacyMirror.toURI() : uri(remote) } + +repositories { + minecraft.mavenizer(it) + maven fg.forgeMaven + maven fg.minecraftLibsMaven + maven { + name = 'VerifiedLegacyMirror' + url = legacyMirror.toURI() + metadataSources { artifact() } + } + exclusiveContent { + forRepository { maven { url = legacyRepository('https://www.cursemaven.com'); metadataSources { artifact() } } } + filter { includeGroup 'curse.maven' } + } + exclusiveContent { + forRepository { maven { url = legacyRepository('https://chickenbones.net/maven/'); metadataSources { artifact() } } } + filter { includeGroup 'codechicken' } + } + exclusiveContent { + forRepository { maven { url = legacyRepository('https://dvs1.progwml6.com/files/maven/'); metadataSources { artifact() } } } + filter { includeGroup 'slimeknights'; includeGroup 'slimeknights.mantle'; includeGroup 'mezz.jei' } + } + exclusiveContent { + forRepository { maven { url = legacyRepository('https://maven.ic2.player.to/'); metadataSources { artifact() } } } + filter { includeGroup 'net.industrial-craft' } + } + exclusiveContent { + forRepository { maven { url = legacyRepository('https://maven.covers1624.net/'); metadataSources { artifact() } } } + filter { includeGroup 'cofh' } + } + mavenCentral() +} + +apply from: 'gradle/dependencies.gradle' +renamer.mappings.from minecraft.dependency.toSrgFile + +tasks.named('processResources', ProcessResources) { + filteringCharset = 'UTF-8' + exclude '**/desktop.ini', '**/Thumbs.db', '**/.DS_Store' + inputs.property 'version', project.version + inputs.property 'mcversion', project.minecraft_version + filesMatching('mcmod.info') { expand version: project.version, mcversion: project.minecraft_version } + rename '(.+_at.cfg)', 'META-INF/$1' + filesMatching(textPatterns, normalizeText) +} + +tasks.named('jar', Jar) { + archiveClassifier = 'deobf' + destinationDirectory = layout.buildDirectory.dir('libs-dev') + manifest.attributes( + 'Implementation-Title': 'BaseMetals', 'Implementation-Version': project.version, + 'Maven-Artifact': "${project.group}:BaseMetals:${project.version}", + 'Built-On-Java': '8', 'Built-On': "${project.minecraft_version}-${project.forge_version}", + 'FMLCorePluginContainsFMLMod': 'true', 'FMLCorePlugin': project.coreplugin) +} +['sourcesJar', 'javadocJar'].each { name -> + tasks.named(name, Jar) { + filteringCharset = 'UTF-8' + filesMatching(textPatterns, normalizeText) + } +} + +ext.baseMetalsBuild = [developmentOutput: developmentOutput].asImmutable() +apply from: 'gradle/release/artifacts.gradle' +apply from: 'gradle/release/publishing.gradle' +apply from: 'gradle/verification/legacy-runtime.gradle' +apply from: 'gradle/ide/eclipse.gradle' diff --git a/docs/BUILDING.md b/docs/BUILDING.md new file mode 100644 index 000000000..088576b42 --- /dev/null +++ b/docs/BUILDING.md @@ -0,0 +1,114 @@ +# Building Base Metals 1.10.2 + +Use the inner `BaseMetals` directory as the project root. The Gradle wrapper is +part of the project; there is no need to install Gradle separately. + +## Java versions + +- Java 17 runs Gradle 9.6.1 and ForgeGradle 7.0.34. +- Java 25 runs Forge's Minecraft Mavenizer during Minecraft setup. +- Java 8 compiles the mod and runs Minecraft 1.10.2. + +Ordinary development accepts compatible Java 8 installations. Release checks use +Temurin 8u502-b07 and Gradle on Temurin 17.0.1+12. Do not change Eclipse's own +runtime to Java 8: its Gradle and Minecraft runtimes are separate settings. + +The compile and runtime target is Forge 12.18.3.2511 with stable-29 mappings. +OreSpawn 4.1.2.110021 is pinned through Curse Maven and verified against its +published SHA-256. Saved registry IDs and recipes have not changed. + +## Eclipse + +Import the project as an existing Gradle project, select the wrapper and use +Java 17 for Gradle. Run the `prepareEclipse` task once, then refresh the project. +Choose the generated `runClient` or `runServer` launch, using JavaSE-8. +Gradle synchronization regenerates those launches when needed. + +Eclipse copies resources from Gradle's processed output, so a clean Eclipse build +cannot replace the mod version with a `${version}` placeholder. After changing +textures or other resources, refresh Gradle or run `prepareEclipse` again. + +The launch tasks merge production classes and processed resources, so Minecraft +sees one mod root. Ordinary launches include the published OreSpawn 4.1.2.110021 +release. Other integration APIs, JUnit and the migration probes +are excluded. Keep personal worlds outside the development `run` directory. + +Remapped copies of signed legacy dependencies can report a fingerprint warning +in development. Their original downloads are still checksum-verified; the +transformed copies cannot retain a valid signature for the original bytes. + +## Build checks + +```text +gradlew clean check build javadoc verifyReleaseArtifacts writeReleaseChecksums prepareEclipse +``` + +The installable jar is `build/libs/BaseMetals-2.5.1.110021.jar`. Sources, +Javadocs are written alongside it; checksums are in `build/release/SHA256SUMS`. The mapped development jar +is kept separately under `build/libs-dev`; it is not the jar to install in a +normal Minecraft instance. Building never uploads anything. + +The dependency list and SHA-256 values are in `gradle/dependencies.json`. +For an offline or mirrored setup, Python 3 can stage those exact files before +running Gradle: + +```text +python gradle/stage-legacy-dependencies.py .gradle/legacy-dependencies +``` + +Use `--cache ` to reuse verified downloads. A complete local +mirror is used instead of the old artifact hosts. Generated mirrors, IDE files, +worlds and reports are ignored by Git. + +CI and the release dispatcher call `bash gradle/stage-legacy-dependencies.sh ` +and pass that directory as `-PlegacyDependencyVerificationRepository=`. +The older `legacyDependencyRepository` property still works for local setups. + +## CI and releases + +The target branch is `master-1.10.2`, matching OreSpawn's naming. CI runs on that +branch and feature branches, with separate CodeQL and wrapper checks. It tests +a cold Forge setup, an offline rebuild, Java 8 bytecode, Eclipse isolation and +byte-identical release jars. Public Cyano and MMD releases are used to create +temporary upgrade worlds; personal saves are never needed by CI. + +To run the packaged suite locally, prepare a disposable Forge server with +`gradle/prepare-packaged-forge-runtime.sh`, then run `packagedForgeRuntimeTest` +with `-PpackagedForgeRuntimeRoot=`. Python 3 needs `nbtlib` +2.0.4 and NumPy 2.2.6. On Windows, set `-PpythonExecutable=` if +`python3` is not available. The test destination must not already exist; +keep a failed run before cleaning the build directory. + +The shared **Release Base Metals** workflow lives on the repository's default +branch. Version `2.5.1.110021` selects `master-1.10.2`; it requires successful +CI for the exact source commit and explicit publication confirmation. It builds +one candidate, then publishes those same files to MMD Maven, CurseForge and +GitHub, in that order. A tag alone only validates the candidate. + +The four release secrets are `CURSEFORGE_TOKEN`, `MAVEN_UPLOAD_URL`, +`MAVEN_UPLOAD_USERNAME` and `MAVEN_UPLOAD_PASSWORD`. OreSpawn is a required +CurseForge relation; MMDLib is bundled in this branch, not a separate requirement. +Maven publication rejects missing credentials and non-HTTPS upload destinations. +`-PpreparedReleaseDir=` checks the three jars, checksums and changelog +without rebuilding the release. Do not run a publish task just to test a build. + +## Save and add-on compatibility + +Build the separate test mod with `migrationProbeJar`. It is written to +`build/test-mods`, never the release directory. The checks and their phased +first-load, reload and later-chunk procedure are described in +[the legacy test guide](../tools/validation/legacy110/README.md). + +Always test a disposable world copy. Base Metal Golems checks use the unchanged +6.02.1 add-on with Extra Golems 6.02, including golems saved by Cyano Base Metals. +Removing the add-ons from a live world can remove their entities; the no-add-on +test therefore uses a separate copy without those saved entities. + +The OreSpawn upgrade runner is `tools/validation/legacy110/run_orespawn_checks.py`. +It first saves a control world with the previous Base Metals and OreSpawn 3 jars, +then tests copies with the new jar and OreSpawn 4.1. It checks stock, custom and +disabled rules, OS1 imports, saved item data, reloads and chunks opened later. +Use `--help` for its input paths; its output must be a disposable directory. +Build the packaged probes with `packagedLaunchProbeJar packagedOreSpawnProbeJar`. +The ordinary probe jars use development mappings and are not interchangeable +with the packaged versions. diff --git a/docs/ORE_GENERATION.md b/docs/ORE_GENERATION.md new file mode 100644 index 000000000..7ed3645d5 --- /dev/null +++ b/docs/ORE_GENERATION.md @@ -0,0 +1,96 @@ +# Ore generation in Minecraft 1.10.2 + +Base Metals now requires MMD OreSpawn 4.1.2.110021 or a newer compatible 4.x +release. OreSpawn is the only ore-generation engine; Base Metals no longer +ships its old OS3 plugin or fallback generator. This also raises the minimum +Forge version to 12.18.3.2511. + +## Existing worlds + +Back up the world and its configuration folder before updating. Install the +new Base Metals and OreSpawn jars together, removing the older jars first. +Keep the old configuration files in place: + +- OreSpawn 1: `config/orespawn/*.json` +- OreSpawn 3: `config/orespawn3/*.json`, together with `config/orespawn.cfg` + +OreSpawn imports those files without changing the originals. Custom ore +frequencies, sizes, heights, dimensions and disabled rules carry across. The +old exclusive maximum height becomes an inclusive maximum one block lower; +for example, `maxHeight: 96` becomes `max_y: 95`. + +Read `config/orespawn-upgrade-report.txt` after the first start. It lists what +was imported and anything that needs attention. Missing blocks and unsupported +custom settings cannot be guessed safely, so keep the report with your backup. + +An OS1 `+` dimension entry is imported for the dimensions installed at that +start. If you add another dimension mod later, review its ore rules yourself. + +The resulting provider override is `config/basemetals-orespawn.json`. Each +world stores its effective settings in +`/serverconfig/orespawn-worldgen.json`. Existing OS4 overrides and world +profiles take priority over the defaults supplied by Base Metals. Updating the +mod does not reset them or replace generated terrain. Retrogen stays off +unless the imported configuration explicitly enables it. + +OreSpawn's **Manage Vanilla Ores** switch controls imported vanilla-ore rules +as well as its newer vanilla defaults. When it is off, Minecraft generates its +own ores; imported vanilla rules stay saved but inactive. When it is on, the +imported frequency, height and vein settings are used without adding a second +vanilla/default pass. This switch does not disable Base Metals' own ore rules. +Changing it affects new generation, not ore already placed in existing chunks. + +Existing provider overrides are kept even when legacy files are imported for +the first time. OreSpawn records the retained file in its upgrade report. An +unfinished or invalid override is left for the normal loader to check, not +silently replaced by older settings. + +Copy the instance configuration folder as well as the world when moving an old +save to another installation. A world alone cannot recover custom ore settings +that were stored outside it. + +## Changing ore placement + +Use OreSpawn's ore editor, or edit its JSON files while Minecraft is stopped: + +- `config/orespawn-worldgen.json` holds defaults for new worlds. +- `config/basemetals-orespawn.json` overrides the Base Metals provider. +- `/serverconfig/orespawn-worldgen.json` holds an existing world's rules. + +The old `using_orespawn` and `fallback_orespawn` entries in `BaseMetals.cfg` +are no longer read. They can remain in an older file, but they do not disable +OreSpawn 4. If CoFH World or another mod places your ores, disable the Base +Metals rules in OreSpawn to avoid generating them twice. + +If you previously used the internal fallback generator without an OreSpawn +configuration, new chunks use the defaults below. The fallback's approximate +placement is not an OreSpawn config that can be imported. Existing chunks are +still kept as they are. + +## Default rules + +Every rule ID starts with `basemetals:legacy/` and ends in `_ore`. +Those IDs are kept stable for imported configurations and saved world profiles. + +| Material | Dimension | Height, inclusive | Attempts per chunk | +| --- | --- | --- | ---: | +| Cold Iron | Nether | 0–127 | 5 | +| Adamantine | Nether | 0–127 | 2 | +| Star Steel | End | 0–254 | 5 | +| Copper | Ordinary dimensions | 0–95 | 10 | +| Silver | Ordinary dimensions | 0–31 | 4 | +| Tin | Ordinary dimensions | 0–127 | 10 | +| Lead | Ordinary dimensions | 0–63 | 5 | +| Zinc | Ordinary dimensions | 0–95 | 5 | +| Mercury | Ordinary dimensions | 0–31 | 3 | +| Nickel | Ordinary dimensions | 32–95 | 1 | +| Platinum | Ordinary dimensions | 1–31 | 0.125 | + +Ordinary dimensions means every dimension except the vanilla Nether and End. +Each attempt has a block budget of 4–11, matching the old size 8 / variation 4 +settings. Actual yield also depends on the available host rock. + +Antimony and Bismuth remain registered but do not gain new generation rules. +Materials disabled in `BaseMetals.cfg` are omitted from the native defaults. +Base Metals supplies no terrain, biome or strata settings. Mineralogy remains +optional and can supply ore-replaceable rocks through OreSpawn. diff --git a/gradle.properties b/gradle.properties index abbee5f35..2246dc119 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,47 +1,30 @@ -group = com.mcmoddev -archivesBaseName = BaseMetals -name = BaseMetals -description = Base Metals +org.gradle.jvmargs=-Xmx3G +org.gradle.daemon=false +org.gradle.configuration-cache=false +org.gradle.caching=true +org.gradle.parallel=false +net.minecraftforge.gradle.merge-source-sets=false -mc_version=1.10.2 +minecraft_version=1.10.2 +forge_version=12.18.3.2511 +mappings_channel=stable +mappings_version=29-1.10.2 -fg_version = ForgeGradle:2.2-SNAPSHOT -forge_mc_version = 1.10.2 -forge_version = 12.18.3.2185 -mcp_mappings = stable_29 - -cf_project_id = 240967 -cf_release_type = release - -coreplugin = com.mcmoddev.lib.asm.ASMPlugin - -top_mc_version=1.1x -ccl_version=2.5.9.+ -ccc_version=2.4.1.+ -nei_version=2.1.3.+ -jei_version=3.14.7.+ -mantle_version=1.1.5.+ -tconstruct_version=2.6.5.+ -mcmp_version=1.4.0 -top_version=1.4.19-60 -alt_version=2.0.2.+ -orespawn_version=3.2.2.+ -tesla_version=1.2.1.+ -bge_version=2.5.0-beta+ -bmi_version=2.5.0-beta+ -bme_version=2.5.0-beta+ -fme_version=2.5.0-beta+ -mme_version=2.5.0-beta+ -cofhcore_version=4.1.12.+ -mekanism_version=63e358a878 -taiga_version=251e1d9d78 -ic2_version=2.6.254-ex110 -minetweaker_version=3.+ -mtlib_version=1.+ -hwyla_version=1.8.17-B31_1.10.2 -veinminer_version=1.9-0.38.2.624+127e28f -endercore_version=0.4.1.+ -enderio_version=3.1.+ -thermal_foundation_version=2.1.5.12 -thermal_expansion_version=5.1.10.+ -compatlayer_version=0.3.1 +mod_id=basemetals +mod_group=zone.moddev.mc.basemetals +mod_version=2.5.1.110021 +mod_artifact=BaseMetals +loader_name=forge +loader_code=1 +java_version=8 +java_toolchain_version=8.0.502+7 +java_setup_version=8.0.502+7 +gradle_java_version=17 +gradle_java_setup_version=17.0.1+12 +mavenizer_java_version=25.0.3+9 +curseforge_project_id=240967 +cf_requirements=mmd-orespawn +orespawn_curse_project_id=245586 +coreplugin=zone.moddev.mc.basemetals.compat.BaseMetalsLoadingPlugin +orespawn_version=4.1.2.110021 +orespawn_version_range=[4.1.2.110021,5.0.0) diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle new file mode 100644 index 000000000..13942054f --- /dev/null +++ b/gradle/dependencies.gradle @@ -0,0 +1,70 @@ +import groovy.json.JsonSlurper +import java.security.MessageDigest + +def specs = new JsonSlurper().parse(file('gradle/dependencies.json')) +def mapped = specs.collectEntries { [(it.id): renamer.dependency(it.coordinate)] } + +configurations { + legacyModArtifacts { canBeConsumed = false; transitive = false } + java8LauncherArtifact { canBeConsumed = false; transitive = false } +} +dependencies { + implementation minecraft.dependency("net.minecraftforge:forge:${minecraft_version}-${forge_version}") + java8LauncherArtifact 'net.minecraftforge:slime-launcher:0.2.2' + compileOnly 'org.lwjgl.lwjgl:lwjgl_util:2.9.4-nightly-20150209' + specs.each { spec -> + if (spec.runtime) implementation mapped[spec.id] + else compileOnly mapped[spec.id] + legacyModArtifacts spec.coordinate + } + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.2' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.2' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.10.2' +} + +// Forge 1.10's mod scanner cannot read Java 11 multi-release entries. They are +// unused on Java 8, so omit them from the development launcher copy. +tasks.register('prepareJava8Launcher', Jar) { + dependsOn configurations.java8LauncherArtifact + archiveFileName = 'slime-launcher-0.2.2-java8.jar' + destinationDirectory = layout.buildDirectory.dir('development-dependencies') + from { zipTree(configurations.java8LauncherArtifact.singleFile) } + exclude 'META-INF/versions/**', 'META-INF/MANIFEST.MF', 'META-INF/*.SF', 'META-INF/*.RSA', 'META-INF/*.DSA' +} + +// Verify the original files, not renamed copies whose bytes depend on mappings. +tasks.register('verifyReleaseDependencies') { + group = 'verification' + description = 'Checks every pinned integration API against its SHA-256.' + inputs.file 'gradle/dependencies.json' + doLast { + def artifacts = configurations.legacyModArtifacts.resolvedConfiguration.resolvedArtifacts + def orespawn = specs.find { it.id == 'orespawn' } + if (orespawn.coordinate != 'curse.maven:mmd-orespawn-245586:9276430' + || orespawn.sha256 != '4D4975421FEEBF17505056306D06B0344D7A4553B6275885D7C5C5065B0348A6') { + throw new GradleException('Use the verified OreSpawn 4.1.2.110021 release.') + } + specs.each { spec -> + def parts = spec.coordinate.split(':') + def artifact = artifacts.find { it.moduleVersion.id.group == parts[0] + && it.name == parts[1] && it.moduleVersion.id.version == parts[2] } + if (artifact == null) throw new GradleException("Missing ${spec.coordinate}") + String actual = MessageDigest.getInstance('SHA-256').digest(artifact.file.bytes).encodeHex().toString() + if (!actual.equalsIgnoreCase(spec.sha256)) { + throw new GradleException("Checksum mismatch for ${spec.coordinate}: ${actual}") + } + if (spec.id == 'orespawn') { + new java.util.jar.JarFile(artifact.file).withCloseable { jar -> + def metadata = new JsonSlurper().parse(jar.getInputStream(jar.getJarEntry('mcmod.info'))) + if (!metadata.any { it.modid == 'orespawn' && it.version == project.orespawn_version } + || jar.getJarEntry('zone/moddev/mc/orespawn/api/OreSpawnApi.class') == null + || jar.manifest.mainAttributes.getValue('OreSpawn-API-Version') != '1') { + throw new GradleException('Wrong OreSpawn identity or public API.') + } + } + } + } + } +} +tasks.named('compileJava') { dependsOn tasks.named('verifyReleaseDependencies') } +tasks.named('check') { dependsOn tasks.named('verifyReleaseDependencies') } diff --git a/gradle/dependencies.json b/gradle/dependencies.json new file mode 100644 index 000000000..39fca48f9 --- /dev/null +++ b/gradle/dependencies.json @@ -0,0 +1,16 @@ +[ + {"id":"orespawn","coordinate":"curse.maven:mmd-orespawn-245586:9276430","sha256":"4D4975421FEEBF17505056306D06B0344D7A4553B6275885D7C5C5065B0348A6","repository":"https://www.cursemaven.com","runtime":true}, + {"id":"alt","coordinate":"curse.maven:additional-loot-tables-243157:2604973","sha256":"DFDEE18663A0CBC3A811E442C7B20F34F1765B1D36C6284CB3B8B8EAA3FC5620","repository":"https://www.cursemaven.com"}, + {"id":"mantle","coordinate":"slimeknights.mantle:Mantle:1.10.2-1.1.5.207","sha256":"520499E1329EA57DB3D7ABA671B30A0CC7DA60C02C97828CFAF57778B0923832","repository":"https://dvs1.progwml6.com/files/maven"}, + {"id":"tconstruct","coordinate":"slimeknights:TConstruct:1.10.2-2.6.5.11","sha256":"E33061BEFB4DA60B2EF79016A022C434149DDF1E573A01EFD2F202F03904E52D","repository":"https://dvs1.progwml6.com/files/maven"}, + {"id":"jei","coordinate":"mezz.jei:jei_1.10.2:3.14.7.420","sha256":"B29DC3077772543E765432CC2C91AAA657F2AC70E664707B3F1D9ACB2A02706A","repository":"https://dvs1.progwml6.com/files/maven"}, + {"id":"nei","coordinate":"codechicken:NotEnoughItems:1.10.2-2.1.3.220:universal","sha256":"C49BE9F4AE9ABD1CF397884D32F7F1DAD233C8ED4E74EA72F8B6656E8A8A1594","repository":"https://chickenbones.net/maven"}, + {"id":"ccl","coordinate":"codechicken:CodeChickenLib:1.10.2-2.5.9.320:universal","sha256":"E265A9B9F3E2ED4302CADAFDBC8520E90F12CF06BC60F267466AF8E6B752C8FC","repository":"https://chickenbones.net/maven"}, + {"id":"ccc","coordinate":"codechicken:CodeChickenCore:1.10.2-2.4.1.103:universal","sha256":"E2E4FDABAB58F76A25BCC9AE944639CA6265DA7BF81E62379CBED23851C2D479","repository":"https://chickenbones.net/maven"}, + {"id":"ic2","coordinate":"net.industrial-craft:industrialcraft-2:2.6.254-ex110","sha256":"CD51FB645C26FA96C6B22704EF168D00192A135F70BD97B1DA40CD5F80FA62A2","repository":"https://maven.ic2.player.to"}, + {"id":"cofh","coordinate":"cofh:CoFHCore:1.10.2-4.1.12.17:universal","sha256":"AD8924E7B142A98C03CF0C37DBD320DDBA553BA92487556C8D3D8F28D41409B3","repository":"https://maven.covers1624.net"}, + {"id":"mekanism","coordinate":"curse.maven:mekanism-268560:2475800","sha256":"E46A248BDA91F683812A10D1764CC95CA25A2BBBD24D945D1D4007DE54E79876","repository":"https://www.cursemaven.com"}, + {"id":"taiga","coordinate":"curse.maven:taiga-247661:2368038","sha256":"B835A38B80ED60E0F59426F82EE5EA2E681A2AE8DD877DB5DBCF7949B9C4B735","repository":"https://www.cursemaven.com"}, + {"id":"hwyla","coordinate":"curse.maven:hwyla-253449:2433252","sha256":"2FC66DBB0CD7A1B9395FB5AAC198CF29C69BE44DE12A771FC4E77D4106D2DDDF","repository":"https://www.cursemaven.com"}, + {"id":"veinminer","coordinate":"curse.maven:veinminer-67133:2528282","sha256":"C2F651221CFCE7049A0011426F3E712026ECE31CE61C6A47DEF68BD7C78EB10A","repository":"https://www.cursemaven.com"} +] diff --git a/gradle/ide/eclipse.gradle b/gradle/ide/eclipse.gradle new file mode 100644 index 000000000..b12e788e9 --- /dev/null +++ b/gradle/ide/eclipse.gradle @@ -0,0 +1,206 @@ +def escapeXml = { String text -> + text.replace('&', '&').replace('"', '"').replace('<', '<').replace('>', '>') +} + +eclipse { + project { + buildCommand 'org.eclipse.buildship.core.gradleprojectbuilder' + natures 'org.eclipse.buildship.core.gradleprojectnature' + } + classpath { + downloadSources = true + downloadJavadoc = true + file.beforeMerged { classpath -> + Set managed = ['src/main/java', 'src/main/resources', 'src/test/java', + 'build/resources/main', 'tools/validation/legacy110', + 'tools/validation/legacy110/migration-probe', + 'tools/validation/legacy110/launch-probe', + 'tools/validation/legacy110/orespawn-probe'] as Set + classpath.entries.removeAll { it.kind == 'src' && managed.contains(it.path) } + } + file.whenMerged { classpath -> + // Eclipse must copy the expanded metadata, not the source placeholders. + classpath.entries.findAll { it.kind == 'src' && it.path == 'src/main/resources' }.each { + it.path = 'build/resources/main' + } + classpath.entries.findAll { it.kind == 'src' && !it.path.startsWith('src/main/') }.each { + if (it.path != 'build/resources/main') it.entryAttributes['test'] = 'true' + } + } + file.withXml { provider -> + Node root = provider.asNode() + root.classpathentry.findAll { it.@kind == 'lib' }.each { root.remove(it) } + if (!root.classpathentry.any { it.@path == 'org.eclipse.buildship.core.gradleclasspathcontainer' }) { + root.appendNode('classpathentry', [kind: 'con', path: 'org.eclipse.buildship.core.gradleclasspathcontainer']) + } + } + } + synchronizationTasks 'genEclipseRuns' +} +idea.module { downloadSources = true; downloadJavadoc = true } + +def configureBuildship = tasks.register('configureEclipseBuildship') { + group = 'ide' + doLast { + File target = file('.settings/org.eclipse.buildship.core.prefs') + Properties values = new Properties() + if (target.isFile()) target.withInputStream { values.load(it) } + [ + 'eclipse.preferences.version': '1', + 'connection.gradle.distribution': 'GRADLE_DISTRIBUTION(WRAPPER)', + 'connection.gradle.user.home': gradle.gradleUserHomeDir.canonicalPath, + 'gradle.user.home': gradle.gradleUserHomeDir.canonicalPath, + 'connection.java.home': System.getProperty('java.home'), + 'java.home': System.getProperty('java.home'), + 'connection.project.dir': '', 'override.workspace.settings': 'true' + ].each { key, value -> values.setProperty(key, value) } + target.parentFile.mkdirs() + target.withOutputStream { values.store(it, 'Generated by configureEclipseBuildship.') } + } +} + +tasks.register('isolateEclipseProductionRuns') { + group = 'ide' + description = 'Quotes launcher paths and keeps optional API jars and tests out of ordinary launches.' + dependsOn configureBuildship, tasks.named('processResources'), tasks.named('prepareJava8Launcher') + doLast { + // Copy, rather than Sync: Eclipse owns and compiles the classes in bin/main. + copy { from layout.buildDirectory.dir('resources/main'); into file('bin/main') } + file('run').mkdirs() + List runtime = configurations.runtimeClasspath.files.collect { it.canonicalFile } + ['runClient.launch', 'runServer.launch'].each { name -> + File launch = file(name) + if (!launch.isFile()) throw new GradleException("Missing generated launch: ${name}") + String text = launch.getText('UTF-8').replace('\r\n', '\n') + text = text.replace(escapeXml(configurations.java8LauncherArtifact.singleFile.canonicalPath), + escapeXml(tasks.named('prepareJava8Launcher', Jar).get().archiveFile.get().asFile.canonicalPath)) + def arguments = text =~ /()/ + if (arguments.count != 1) throw new GradleException("Malformed launcher arguments: ${name}") + String args = arguments[0][2] + ['cache', 'metadata', 'to-srg', 'to-obf'].each { flag -> + int start = args.indexOf("--${flag} ") + if (start < 0) throw new GradleException("Missing --${flag} in ${name}") + start += flag.length() + 3 + int end = args.indexOf(' --', start) + if (end < 0) throw new GradleException("Unterminated --${flag} in ${name}") + String value = args.substring(start, end) + if (!value.startsWith('"')) args = args.substring(0, start) + '"' + value + '"' + args.substring(end) + } + text = text.replace(arguments[0][0], arguments[0][1] + args + arguments[0][3]) + text = text.replaceAll(//, + '') + String modClasses = '' + if (text.contains('key="MOD_CLASSES"')) { + text = text.replaceAll(//, + java.util.regex.Matcher.quoteReplacement(modClasses)) + } else { + text = text.replace('', ' ' + modClasses + '\n ') + } + List lines = text.split('\n', -1) as List + def entries = (0.. + String memento = '' + ' ' + }.join('\n') + text = lines.join('\n') + if (!text.contains('ATTR_EXCLUDE_TEST_CODE')) { + text = text.replace('', + ' \n') + } + launch.setText(text, 'UTF-8') + } + } +} +tasks.named('genEclipseRuns') { finalizedBy tasks.named('isolateEclipseProductionRuns') } +tasks.named('eclipse') { dependsOn tasks.named('genEclipseRuns') } + +def verifyJavaSourceLayout = tasks.register('verifyJavaSourceLayout') { + group = 'verification' + description = 'Checks that Java package names match their source folders, as Eclipse requires.' + inputs.files(provider { sourceSets.collect { it.java } }) + doLast { + List mismatches = [] + sourceSets.each { sourceSet -> + sourceSet.java.visit { entry -> + if (entry.directory || !entry.name.endsWith('.java')) return + + String path = entry.relativePath.pathString + int separator = path.lastIndexOf('/') + String expected = separator < 0 ? '' : path.substring(0, separator).replace('/', '.') + def declaration = entry.file.getText('UTF-8') =~ /(?m)^\s*package\s+([\w.]+)\s*;/ + String declared = declaration.find() ? declaration.group(1) : '' + + if (declared != expected) { + mismatches.add("${sourceSet.name}: ${path} declares '${declared}', expected '${expected}'") + } + } + } + if (!mismatches.isEmpty()) { + throw new GradleException('Java packages do not match their source folders:\n' + mismatches.join('\n')) + } + } +} + +tasks.named('check') { dependsOn verifyJavaSourceLayout } + +tasks.register('verifyEclipseProductionClasspath') { + group = 'verification' + dependsOn tasks.named('genEclipseRuns'), verifyJavaSourceLayout + mustRunAfter tasks.named('eclipse') + doLast { + List runtime = configurations.runtimeClasspath.files.collect { it.canonicalFile } + Set optional = configurations.compileClasspath.files.collect { it.canonicalFile } as Set + optional.removeAll(runtime) + ['runClient.launch', 'runServer.launch'].each { name -> + String text = file(name).getText('UTF-8').replace('\\', '/') + if (!text.contains('MAIN_TYPE" value="net.minecraftforge.launcher.Main"') + || !text.contains('StandardVMType/JavaSE-8') + || !text.contains('MC_VERSION" value="1.10.2"') + || !text.contains('ATTR_EXCLUDE_TEST_CODE') + || text.contains('gradleclasspathcontainer') || text.contains('bin/test') + || text.contains('slime-launcher-0.2.2.jar') + || text.contains('junit-') || text.contains('legacy110-probe')) { + throw new GradleException("Unsafe production launch: ${name}") + } + if (runtime.any { !text.contains(it.path.replace('\\', '/')) } + || optional.any { text.contains(it.path.replace('\\', '/')) }) { + throw new GradleException("Runtime/compile-only separation failed in ${name}") + } + ['cache', 'metadata', 'to-srg', 'to-obf'].each { flag -> + if (!text.contains("--${flag} "")) throw new GradleException("Unquoted --${flag} in ${name}") + } + } + String info = file('bin/main/mcmod.info').getText('UTF-8') + if (info.contains('${version}') || !info.contains(project.version.toString())) { + throw new GradleException('Eclipse is using unprocessed resources.') + } + def classpath = new groovy.xml.XmlSlurper().parse(file('.classpath')) + List sourcePaths = classpath.classpathentry.findAll { it.@kind == 'src' }.collect { it.@path.text() } + if (sourcePaths.size() != sourcePaths.unique(false).size() + || sourcePaths.contains('tools/validation/legacy110')) { + throw new GradleException('Eclipse retains stale or duplicate source-folder entries.') + } + classpath.classpathentry.findAll { it.@kind == 'src' && !it.@path.text().startsWith('src/main/') + && it.@path.text() != 'build/resources/main' }.each { + if (!it.attributes.attribute.any { it.@name == 'test' && it.@value == 'true' }) { + throw new GradleException("Non-production Eclipse source is not marked as test code: ${it.@path}") + } + } + } +} +tasks.register('prepareEclipse') { + group = 'ide' + description = 'Generates Eclipse metadata and verifies Java 8 production-only launches.' + dependsOn tasks.named('eclipse'), tasks.named('verifyEclipseProductionClasspath'), 'writeEclipseCompileClasspath' +} + +tasks.register('writeEclipseCompileClasspath') { + group = 'verification' + description = 'Writes the resolved editor classpath for headless Eclipse compiler checks.' + doLast { + File target = layout.buildDirectory.file('ide/compile-classpath.txt').get().asFile + target.parentFile.mkdirs() + target.setText(configurations.compileClasspath.asPath, 'UTF-8') + } +} diff --git a/gradle/ide/log4j2.xml b/gradle/ide/log4j2.xml new file mode 100644 index 000000000..a770e9811 --- /dev/null +++ b/gradle/ide/log4j2.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/gradle/prepare-packaged-forge-runtime.sh b/gradle/prepare-packaged-forge-runtime.sh new file mode 100644 index 000000000..e5cefc939 --- /dev/null +++ b/gradle/prepare-packaged-forge-runtime.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ $# -ne 2 ]]; then + echo "usage: $0 " >&2 + exit 2 +fi +runtime="$1" +java8="$2" +version=1.10.2-12.18.3.2511 +expected=664393C5A924B8F06CFB953903ABCF9FE5E99ECBE6AF8C7EA217339C766E519C + +if [[ -e "$runtime" && -n "$(find "$runtime" -mindepth 1 -maxdepth 1 -print -quit)" ]]; then + echo "Packaged Forge destination must be empty: $runtime" >&2 + exit 1 +fi +mkdir -p "$runtime" +installer="$runtime/forge-$version-installer.jar" +curl --fail --location --silent --show-error --retry 3 --connect-timeout 20 --max-time 180 \ + "https://maven.minecraftforge.net/net/minecraftforge/forge/$version/forge-$version-installer.jar" \ + --output "$installer" +actual="$(sha256sum "$installer" | awk '{print toupper($1)}')" +if [[ "$actual" != "$expected" ]]; then + echo "Forge installer checksum mismatch: $actual" >&2 + exit 1 +fi +# The 1.10 installer writes to its working directory and ignores a destination argument. +(cd "$runtime" && "$java8" -jar "forge-$version-installer.jar" --installServer) +if [[ ! -f "$runtime/forge-$version-universal.jar" && -f "$runtime/forge-$version.jar" ]]; then + cp "$runtime/forge-$version.jar" "$runtime/forge-$version-universal.jar" +fi +test -f "$runtime/forge-$version-universal.jar" +test -f "$runtime/minecraft_server.1.10.2.jar" +test -d "$runtime/libraries" diff --git a/gradle/release/artifacts.gradle b/gradle/release/artifacts.gradle new file mode 100644 index 000000000..85c7ed54c --- /dev/null +++ b/gradle/release/artifacts.gradle @@ -0,0 +1,182 @@ +import groovy.json.JsonSlurper +import java.security.MessageDigest +import java.util.jar.Manifest +import java.util.zip.ZipFile + +def releaseJar = renamer.classes(tasks.named('jar', Jar)) { + map.from minecraft.dependency.toSrgFile + archiveClassifier = null + output = layout.buildDirectory.file("libs/BaseMetals-${project.version}.jar") +} +['apiElements', 'runtimeElements'].each { name -> + configurations.named(name) { artifacts.clear() } + artifacts.add(name, releaseJar) +} +tasks.named('assemble') { dependsOn releaseJar, tasks.named('sourcesJar'), tasks.named('javadocJar') } + +tasks.register('verifyJava8Toolchain') { + group = 'verification' + description = 'Checks Java 8 compatibility; exact compiler versions are required only for release builds.' + doLast { + def compiler = javaToolchains.compilerFor { languageVersion = JavaLanguageVersion.of(8) }.get().metadata + if (compiler.languageVersion.asInt() != 8) throw new GradleException('Minecraft 1.10 requires Java 8.') + if (providers.gradleProperty('releaseBuild').getOrElse('false').toBoolean() + && (compiler.javaRuntimeVersion != '1.8.0_502-b07' + || System.getProperty('java.runtime.version') != '17.0.1+12')) { + throw new GradleException('Release builds require Temurin 8u502-b07 and Gradle on 17.0.1+12.') + } + if (providers.gradleProperty('releaseBuild').getOrElse('false').toBoolean()) { + def mavenizer = javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(25) }.get().metadata + if (mavenizer.javaRuntimeVersion != '25.0.3+9-LTS') { + throw new GradleException('Release setup requires Mavenizer on Temurin 25.0.3+9.') + } + } + } +} + +tasks.register('verifyReleaseConfiguration') { + group = 'verification' + dependsOn tasks.named('verifyJava8Toolchain') + doLast { + if (project.group != 'zone.moddev.mc.basemetals' || project.version != '2.5.1.110021' + || project.minecraft_version != '1.10.2' || project.forge_version != '12.18.3.2511' + || project.mappings_channel != 'stable' || project.mappings_version != '29-1.10.2') { + throw new GradleException('Base Metals target, mappings or Maven identity changed.') + } + String source = file('src/main/java/zone/moddev/mc/basemetals/BaseMetals.java').getText('UTF-8') + if (!source.contains('VERSION = "' + project.version + '"') || !source.contains('MODID = "basemetals"')) { + throw new GradleException('The mod identity differs from the build metadata.') + } + if (project.orespawn_version != '4.1.2.110021' + || project.orespawn_version_range != '[4.1.2.110021,5.0.0)' + || !source.contains('required-after:orespawn@' + project.orespawn_version_range) + || !source.contains('required-after:Forge@[12.18.3.2511,)')) { + throw new GradleException('The OreSpawn or Forge runtime requirement changed.') + } + } +} + +def expectedFiles = ['', '-sources', '-javadoc'].collect { "BaseMetals-${project.version}${it}.jar".toString() }.asImmutable() +def preparedDirectory = providers.gradleProperty('preparedReleaseDir') +def auditJars = { File directory -> + ['','-sources','-javadoc'].each { suffix -> + File jar = new File(directory, "BaseMetals-${project.version}${suffix}.jar") + if (!jar.isFile() || jar.length() == 0) throw new GradleException("Missing ${jar.name}") + new ZipFile(jar).withCloseable { zip -> + zip.entries().each { entry -> + String path = entry.name.toLowerCase(Locale.ROOT) + if (path.startsWith('review/') || path.contains('probe') || path.contains('fixture') + || path.contains('agent-notes') || path.contains('agents.md') || path.contains('.codex') + || path.contains('.agents') || path.contains('claude') || path.contains('gemini') + || path.tokenize('/').any { it in ['desktop.ini', 'thumbs.db', '.ds_store'] } + || path.startsWith('tools/') || path.startsWith('reference/') + || path.startsWith('com/mcmoddev/lib/oregen/') + || path.startsWith('com/mcmoddev/orespawn/') + || path == 'assets/basemetals/orespawn/basemetals.json' + || path.startsWith('org/junit/') || path.startsWith('net/minecraft/')) { + throw new GradleException("Non-production entry in ${jar.name}: ${entry.name}") + } + def date = entry.timeLocal + if (!entry.directory && !(date.toString() in ['1980-01-01T00:00', '1980-02-01T00:00'])) { + throw new GradleException("Volatile archive timestamp: ${entry.name}") + } + if (!entry.directory && path ==~ /.*\.(java|html|css|js|json|lang|mcmeta|md|txt|xml|cfg|info|properties)/) { + def decoder = java.nio.charset.StandardCharsets.UTF_8.newDecoder() + .onMalformedInput(java.nio.charset.CodingErrorAction.REPORT) + .onUnmappableCharacter(java.nio.charset.CodingErrorAction.REPORT) + String content = decoder.decode(java.nio.ByteBuffer.wrap(zip.getInputStream(entry).bytes)).toString() + if (content.contains('\r')) throw new GradleException("Non-LF text in ${entry.name}") + } + if (!entry.directory && path ==~ /.*\.(class|java|html|json|info|xml|properties|txt|md|lang|cfg)/) { + String content = new String(zip.getInputStream(entry).bytes, + java.nio.charset.StandardCharsets.ISO_8859_1) + if (content.contains(project.projectDir.absolutePath) + || content.contains('/home/runner/work/')) { + throw new GradleException("Local build path in ${entry.name}") + } + } + if (entry.name.endsWith('.class')) { + byte[] data = zip.getInputStream(entry).bytes + int major = ((data[6] & 255) << 8) | (data[7] & 255) + if (major != 52) throw new GradleException("Not Java 8 bytecode: ${entry.name}") + String constants = new String(data, java.nio.charset.StandardCharsets.ISO_8859_1) + if (constants.contains(project.projectDir.absolutePath)) { + throw new GradleException("Local path in ${entry.name}") + } + } + } + if (suffix == '') { + def manifest = new Manifest(zip.getInputStream(zip.getEntry('META-INF/MANIFEST.MF'))).mainAttributes + if (manifest.getValue('Maven-Artifact') != "${project.group}:BaseMetals:${project.version}" + || manifest.getValue('FMLCorePlugin') != project.coreplugin + || manifest.keySet().any { it.toString().toLowerCase().contains('timestamp') }) { + throw new GradleException('Incorrect release manifest.') + } + def info = new JsonSlurper().parse(zip.getInputStream(zip.getEntry('mcmod.info')))[0] + if (info.modid != 'basemetals' || info.version != project.version || info.mcversion != '1.10.2') { + throw new GradleException('Incorrect processed mcmod.info.') + } + int classes = zip.entries().count { it.name.endsWith('.class') } + if (classes != 218 || zip.getEntry('cyano/basemetals/init/Blocks.class') == null + || zip.getEntry('META-INF/basemetals-legacy-api.list') == null) { + throw new GradleException('The compatibility-layer class inventory changed.') + } + } + } + } +} + +tasks.register('verifyReleaseArtifacts') { + group = 'verification' + description = 'Audits the shipped jars for Java 8 bytecode, metadata and test isolation.' + dependsOn tasks.named('assemble'), tasks.named('verifyReleaseConfiguration'), tasks.named('verifyReleaseDependencies') + doLast { auditJars(layout.buildDirectory.dir('libs').get().asFile) } +} + +tasks.register('writeReleaseChecksums') { + group = 'verification' + dependsOn tasks.named('verifyReleaseArtifacts') + doLast { + File directory = layout.buildDirectory.dir('libs').get().asFile + List lines = ['','-sources','-javadoc'].collect { suffix -> + File jar = new File(directory, "BaseMetals-${project.version}${suffix}.jar") + MessageDigest.getInstance('SHA-256').digest(jar.bytes).encodeHex().toString() + ' ' + jar.name + } + File sums = layout.buildDirectory.file('release/SHA256SUMS').get().asFile + sums.parentFile.mkdirs() + sums.setText(lines.join('\n') + '\n', 'UTF-8') + } +} +tasks.named('check') { dependsOn tasks.named('verifyReleaseConfiguration') } + +tasks.register('verifyPreparedReleaseArtifacts') { + group = 'verification' + description = 'Verifies a previously built bundle without rebuilding its jars.' + doLast { + if (!preparedDirectory.isPresent()) throw new GradleException('Set preparedReleaseDir to the immutable release bundle.') + File directory = file(preparedDirectory.get()) + Set expected = (expectedFiles + ['SHA256SUMS', 'CHANGELOG.txt']) as Set + if (!directory.isDirectory() || directory.listFiles().collect { it.name }.toSet() != expected) { + throw new GradleException('The prepared release must contain exactly three jars, SHA256SUMS and CHANGELOG.txt.') + } + List lines = new File(directory, 'SHA256SUMS').readLines('UTF-8') + if (lines.size() != 3) throw new GradleException('The prepared checksum list must contain exactly three files.') + Set checked = [] as Set + lines.each { line -> + def match = line =~ /^([a-fA-F0-9]{64}) (BaseMetals-[^\/\\]+\.jar)$/ + if (!match.matches() || !expectedFiles.contains(match[0][2]) || !checked.add(match[0][2])) { + throw new GradleException('Invalid or duplicate prepared checksum entry.') + } + File jar = new File(directory, match[0][2]) + String actual = MessageDigest.getInstance('SHA-256').digest(jar.bytes).encodeHex().toString() + if (!actual.equalsIgnoreCase(match[0][1])) throw new GradleException("Prepared checksum mismatch: ${jar.name}") + } + if (!new File(directory, 'CHANGELOG.txt').getText('UTF-8').startsWith(project.version.toString() + ' for 1.10.2')) { + throw new GradleException('The prepared changelog belongs to a different release.') + } + auditJars(directory) + } +} + +ext.baseMetalsRelease = [jar: releaseJar, expectedFiles: expectedFiles, + preparedDirectory: preparedDirectory].asImmutable() diff --git a/gradle/release/publishing.gradle b/gradle/release/publishing.gradle new file mode 100644 index 000000000..3b16bac92 --- /dev/null +++ b/gradle/release/publishing.gradle @@ -0,0 +1,96 @@ +import groovy.xml.XmlSlurper + +def releaseJar = project.ext.baseMetalsRelease.jar +def expectedReleaseFiles = project.ext.baseMetalsRelease.expectedFiles +def preparedReleaseDir = project.ext.baseMetalsRelease.preparedDirectory + +// A normal build does not publish anything; publishing needs credentials. +def mavenUploadUrl = providers.environmentVariable('MAVEN_UPLOAD_URL') + .map { it.trim() }.filter { !it.isEmpty() } + .orElse('https://invalid.invalid/missing-maven-upload-url') +def mavenUploadUsername = providers.environmentVariable('MAVEN_UPLOAD_USERNAME') +def mavenUploadPassword = providers.environmentVariable('MAVEN_UPLOAD_PASSWORD') + +publishing { + publications { + mavenJava(MavenPublication) { + groupId = project.group.toString() + artifactId = base.archivesName.get() + version = project.version.toString() + if (preparedReleaseDir.isPresent()) { + File prepared = file(preparedReleaseDir.get()) + artifact(new File(prepared, expectedReleaseFiles[0])) + artifact(new File(prepared, expectedReleaseFiles[1])) { classifier = 'sources' } + artifact(new File(prepared, expectedReleaseFiles[2])) { classifier = 'javadoc' } + } else { + artifact(releaseJar) + artifact(tasks.named('sourcesJar')) + artifact(tasks.named('javadocJar')) + } + pom { + name = 'Base Metals' + description = 'Metals, alloys, equipment and Crack Hammer processing for Minecraft 1.10.2.' + url = 'https://github.com/MinecraftModDevelopmentMods/BaseMetals' + licenses { + license { + name = 'GNU Lesser General Public License, Version 2.1' + url = 'https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt' + } + } + scm { + connection = 'scm:git:https://github.com/MinecraftModDevelopmentMods/BaseMetals.git' + developerConnection = 'scm:git:ssh://git@github.com/MinecraftModDevelopmentMods/BaseMetals.git' + url = 'https://github.com/MinecraftModDevelopmentMods/BaseMetals' + } + } + } + } + repositories { + maven { + name = 'release' + url = uri(mavenUploadUrl.get()) + credentials { + username = mavenUploadUsername.orNull ?: '' + password = mavenUploadPassword.orNull ?: '' + } + } + } +} + +tasks.register('validateMavenReleaseCredentials') { + group = 'publishing' + doLast { + if (!providers.environmentVariable('MAVEN_UPLOAD_URL').getOrElse('').trim() + || !mavenUploadUsername.getOrElse('').trim() || !mavenUploadPassword.getOrElse('').trim()) { + throw new GradleException('MAVEN_UPLOAD_URL, MAVEN_UPLOAD_USERNAME, and MAVEN_UPLOAD_PASSWORD are required') + } + URI target = uri(providers.environmentVariable('MAVEN_UPLOAD_URL').get()) + if (target.scheme != 'https' || !target.host || target.host == 'invalid.invalid' + || !mavenUploadUsername.get().trim() || !mavenUploadPassword.get().trim()) { + throw new GradleException('Maven release publication requires an HTTPS repository and non-empty credentials') + } + } +} + +tasks.withType(PublishToMavenRepository).configureEach { + dependsOn tasks.named('validateMavenReleaseCredentials') + dependsOn 'verifyMavenCoordinates' + if (preparedReleaseDir.isPresent()) { + dependsOn tasks.named('verifyPreparedReleaseArtifacts') + } else { + dependsOn tasks.named('verifyReleaseArtifacts') + } +} + +tasks.register('verifyMavenCoordinates') { + group = 'verification' + dependsOn tasks.named('generatePomFileForMavenJavaPublication') + doLast { + def pom = new XmlSlurper(false, false).parse(layout.buildDirectory.file('publications/mavenJava/pom-default.xml').get().asFile) + def actual = [pom.groupId.text(), pom.artifactId.text(), pom.version.text()] + def expected = ['zone.moddev.mc.basemetals', 'BaseMetals', project.version.toString()] + if (actual != expected) throw new GradleException("Wrong Maven coordinate: ${actual.join(':')}") + } +} + +tasks.named('check') { dependsOn tasks.named('verifyMavenCoordinates') } diff --git a/gradle/stage-legacy-dependencies.py b/gradle/stage-legacy-dependencies.py new file mode 100644 index 000000000..480307595 --- /dev/null +++ b/gradle/stage-legacy-dependencies.py @@ -0,0 +1,53 @@ +"""Build a checksum-verified Maven mirror from downloads or an existing Gradle cache.""" + +import argparse +import hashlib +import json +import time +import urllib.error +import urllib.request +from pathlib import Path + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("destination", type=Path) + parser.add_argument("--cache", type=Path, help="Existing Gradle user home; matching files are verified before reuse") + args = parser.parse_args() + specs = json.loads(Path(__file__).with_name("dependencies.json").read_text(encoding="utf-8")) + + for spec in specs: + group, artifact, version, *classifier = spec["coordinate"].split(":") + filename = f"{artifact}-{version}" + ("-" + classifier[0] if classifier else "") + ".jar" + relative = Path(group.replace(".", "/")) / artifact / version / filename + target = args.destination.resolve() / relative + target.parent.mkdir(parents=True, exist_ok=True) + + if not target.exists(): + cached = list((args.cache / "caches/modules-2/files-2.1" / group / artifact / version).glob("*/" + filename)) if args.cache else [] + if cached: + data = cached[0].read_bytes() + else: + url = spec["repository"].rstrip("/") + "/" + relative.as_posix() + request = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"}) + for attempt in range(3): + try: + with urllib.request.urlopen(request, timeout=30) as response: + data = response.read() + break + except (OSError, urllib.error.URLError): + if attempt == 2: + raise + print(f"Retrying {spec['coordinate']}", flush=True) + time.sleep(2) + if hashlib.sha256(data).hexdigest().upper() != spec["sha256"]: + raise ValueError(f"Checksum mismatch: {spec['coordinate']}") + target.write_bytes(data) + + if hashlib.sha256(target.read_bytes()).hexdigest().upper() != spec["sha256"]: + raise ValueError(f"Checksum mismatch in mirror: {target}") + print(spec["id"], spec["sha256"], flush=True) + + +if __name__ == "__main__": + main() diff --git a/gradle/stage-legacy-dependencies.sh b/gradle/stage-legacy-dependencies.sh new file mode 100644 index 000000000..530f89330 --- /dev/null +++ b/gradle/stage-legacy-dependencies.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ $# -ne 1 ]]; then + echo "usage: $0 " >&2 + exit 2 +fi + +"${PYTHON:-python3}" "$(dirname "$0")/stage-legacy-dependencies.py" "$1" diff --git a/gradle/verification/legacy-runtime.gradle b/gradle/verification/legacy-runtime.gradle new file mode 100644 index 000000000..112f08b75 --- /dev/null +++ b/gradle/verification/legacy-runtime.gradle @@ -0,0 +1,97 @@ +// These mods are test tools, never part of the game or the public artifact. +sourceSets { + migrationProbe { + java.srcDirs = ['tools/validation/legacy110/migration-probe'] + java.include 'review/Legacy110Probe.java', 'review/GolemCompatibilityProbe.java' + compileClasspath = sourceSets.main.compileClasspath + } + launchProbe { + java.srcDirs = ['tools/validation/legacy110/launch-probe'] + java.include 'review/launch/DevLaunchProbe.java' + compileClasspath = sourceSets.main.compileClasspath + } + oreSpawnProbe { + java.srcDirs = ['tools/validation/legacy110/orespawn-probe'] + compileClasspath = sourceSets.main.compileClasspath + } + captureProbe { + java.srcDirs = ['tools/validation/legacy110/capture-probe'] + compileClasspath = sourceSets.main.compileClasspath + } +} + +def captureJar = tasks.register('captureProbeJar', Jar) { + archiveFileName = 'legacy-capture.jar' + destinationDirectory = layout.buildDirectory.dir('test-mods') + from sourceSets.captureProbe.output +} +def packagedCapture = renamer.classes(captureJar) { + map.from minecraft.dependency.toSrgFile + output = layout.buildDirectory.file('test-mods/legacy-capture-packaged.jar') +} + +tasks.register('oreSpawnProbeJar', Jar) { + group = 'verification' + description = 'Builds the isolated OreSpawn configuration and world-upgrade checks.' + dependsOn tasks.named('oreSpawnProbeClasses') + archiveFileName = 'orespawn-probe.jar' + destinationDirectory = layout.buildDirectory.dir('test-mods') + from sourceSets.oreSpawnProbe.output +} + +tasks.register('launchProbeJar', Jar) { + group = 'verification' + description = 'Builds the isolated menu/world probe used with generated launch profiles.' + dependsOn tasks.named('launchProbeClasses') + archiveFileName = 'launch-probe.jar' + destinationDirectory = layout.buildDirectory.dir('test-mods') + from sourceSets.launchProbe.output +} + +tasks.register('compileLegacyConsumer', JavaCompile) { + onlyIf { providers.gradleProperty('legacyBaselineJar').isPresent() } + source 'tools/validation/legacy110/fixtures/LegacyMmdConsumer.java' + classpath = sourceSets.main.compileClasspath + files(providers.gradleProperty('legacyBaselineJar').getOrElse([])) + destinationDirectory = layout.buildDirectory.dir('legacy-consumer') +} +tasks.register('migrationProbeJar', Jar) { + group = 'verification' + description = 'Builds the isolated server probes used on disposable legacy worlds.' + dependsOn tasks.named('migrationProbeClasses'), tasks.named('compileLegacyConsumer') + archiveFileName = 'legacy110-probe.jar' + destinationDirectory = layout.buildDirectory.dir('test-mods') + from sourceSets.migrationProbe.output + if (providers.gradleProperty('legacyBaselineJar').isPresent()) { + from layout.buildDirectory.dir('legacy-consumer') + } +} + +['launch', 'oreSpawn'].each { name -> + def mapped = tasks.named(name + 'ProbeJar', Jar) + def packaged = renamer.classes(mapped) { + map.from minecraft.dependency.toSrgFile + output = layout.buildDirectory.file("test-mods/${name == 'launch' ? 'launch' : 'orespawn'}-probe-packaged.jar") + } + tasks.register('packaged' + name.capitalize() + 'ProbeJar') { + group = 'verification' + description = 'Maps the probe for a packaged Forge server; ordinary IDE launches use the mapped development copy.' + dependsOn packaged + } +} + +tasks.register('packagedForgeRuntimeTest', Exec) { + group = 'verification' + description = 'Tests OS1/OS3 imports, overrides, reloads and the vanilla takeover switch.' + dependsOn 'verifyReleaseArtifacts', 'packagedLaunchProbeJar', 'packagedOreSpawnProbeJar', + 'migrationProbeJar', packagedCapture + doFirst { + def runtime = providers.gradleProperty('packagedForgeRuntimeRoot') + if (!runtime.isPresent()) throw new GradleException('Set packagedForgeRuntimeRoot to a prepared Forge server.') + def java8 = javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(8) }.get() + commandLine providers.gradleProperty('pythonExecutable').getOrElse('python3'), + 'tools/validation/legacy110/run_ci_checks.py', + '--java', java8.executablePath.asFile.absolutePath, + '--template', file(runtime.get()).absolutePath, + '--output', layout.buildDirectory.dir('packaged-forge-checks').get().asFile.absolutePath + } +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 0d4a95168..b1b8ef56b 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a95009c3b..dbe66e1d6 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,10 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip +distributionSha256Sum=9c0f7faeeb306cb14e4279a3e084ca6b596894089a0638e68a07c945a32c9e14 +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip +networkTimeout=10000 +retries=0 +retryBackOffMs=500 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index cccdd3d51..249efbb03 100755 --- a/gradlew +++ b/gradlew @@ -1,78 +1,128 @@ -#!/usr/bin/env sh +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## -## -## Gradle start up script for UN*X -## +# +# gradlew start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh gradlew +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -81,92 +131,118 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=$((i+1)) + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=$(save "$@") - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" fi +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index f9553162f..a51ec4f58 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,84 +1,82 @@ -@if "%DEBUG%" == "" @echo off +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem -@rem Gradle startup script for Windows +@rem gradlew startup script for Windows @rem @rem ########################################################################## -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 -goto fail - -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* +"%COMSPEC%" /c exit 1 :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 -:mainEnd -if "%OS%"=="Windows_NT" endlocal +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel -:omega +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/settings.gradle b/settings.gradle index 93d1ad365..ff69504d3 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,5 @@ -rootProject.name = archivesBaseName +plugins { + id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0' +} + +rootProject.name = 'BaseMetals' diff --git a/src/main/java/com/mcmoddev/alt/BaseMetalsALT.java b/src/main/java/com/mcmoddev/alt/BaseMetalsALT.java index c855e2fe6..bd18f2b66 100644 --- a/src/main/java/com/mcmoddev/alt/BaseMetalsALT.java +++ b/src/main/java/com/mcmoddev/alt/BaseMetalsALT.java @@ -2,7 +2,7 @@ import com.mcmoddev.alt.api.ALTPlugin; import com.mcmoddev.alt.api.IALTPlugin; -import com.mcmoddev.basemetals.BaseMetals; +import zone.moddev.mc.basemetals.BaseMetals; @ALTPlugin(modid = BaseMetals.MODID) class BaseMetalsALT implements IALTPlugin { diff --git a/src/main/java/com/mcmoddev/basemetals/proxy/CommonProxy.java b/src/main/java/com/mcmoddev/basemetals/proxy/CommonProxy.java deleted file mode 100644 index 0be23620d..000000000 --- a/src/main/java/com/mcmoddev/basemetals/proxy/CommonProxy.java +++ /dev/null @@ -1,145 +0,0 @@ -package com.mcmoddev.basemetals.proxy; - -import java.util.HashSet; - -import com.mcmoddev.basemetals.BaseMetals; -import com.mcmoddev.basemetals.data.MaterialNames; -import com.mcmoddev.basemetals.init.Achievements; -import com.mcmoddev.basemetals.init.Blocks; -import com.mcmoddev.basemetals.init.Fluids; -import com.mcmoddev.basemetals.init.ItemGroups; -import com.mcmoddev.basemetals.init.Items; -import com.mcmoddev.basemetals.init.Materials; -import com.mcmoddev.basemetals.init.Recipes; -import com.mcmoddev.basemetals.init.VillagerTrades; -import com.mcmoddev.basemetals.util.Config; -import com.mcmoddev.basemetals.util.EventHandler; -import com.mcmoddev.lib.data.Names; -import com.mcmoddev.lib.data.SharedStrings; -import com.mcmoddev.lib.fuels.FuelRegistry; -import com.mcmoddev.lib.integration.IntegrationManager; -import com.mcmoddev.lib.material.MMDMaterial; -import com.mcmoddev.lib.oregen.FallbackGenerator; -import com.mcmoddev.lib.oregen.FallbackGeneratorData; -import com.mcmoddev.lib.util.ConfigBase; -import com.mcmoddev.lib.util.ConfigBase.Options; - -import net.minecraftforge.common.MinecraftForge; -import net.minecraftforge.fml.common.Loader; -import net.minecraftforge.fml.common.MissingModsException; -import net.minecraftforge.fml.common.event.FMLInitializationEvent; -import net.minecraftforge.fml.common.event.FMLMissingMappingsEvent; -import net.minecraftforge.fml.common.event.FMLMissingMappingsEvent.MissingMapping; -import net.minecraftforge.fml.common.event.FMLPostInitializationEvent; -import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; -import net.minecraftforge.fml.common.registry.GameRegistry; -import net.minecraftforge.fml.common.versioning.ArtifactVersion; -import net.minecraftforge.fml.common.versioning.DefaultArtifactVersion; - -/** - * Base Metals Common Proxy. - * - * @author Jasmine Iwanek - * - */ -public class CommonProxy { - - private boolean allsGood = false; - - public boolean allsGood() { - return this.allsGood; - } - - public void preInit(final FMLPreInitializationEvent event) { - - Config.init(); - - if ((Options.requireMMDOreSpawn()) && (!Loader.isModLoaded(SharedStrings.ORESPAWN_MODID))) { - if (Options.fallbackOrespawn()) { - GameRegistry.registerWorldGenerator(new FallbackGenerator(), 0); - } else { - final HashSet orespawnMod = new HashSet<>(); - orespawnMod.add(new DefaultArtifactVersion(SharedStrings.ORESPAWN_VERSION)); - throw new MissingModsException(orespawnMod, SharedStrings.ORESPAWN_MODID, - SharedStrings.ORESPAWN_MISSING_TEXT); - } - } - - com.mcmoddev.lib.init.Materials.init(); - Materials.init(); - FuelRegistry.init(); - Fluids.init(); - ItemGroups.init(); - com.mcmoddev.lib.init.Blocks.init(); - Blocks.init(); - com.mcmoddev.lib.init.Items.init(); - Items.init(); - - VillagerTrades.init(); - - IntegrationManager.INSTANCE.preInit(event); - IntegrationManager.INSTANCE.runCallbacks("preInit"); - this.allsGood = true; - } - - public void onRemap(final FMLMissingMappingsEvent event) { - for (final MissingMapping mapping : event.get()) { - if (mapping.resourceLocation.getResourceDomain().equals(BaseMetals.MODID)) { - if (mapping.type.equals(GameRegistry.Type.BLOCK)) { - if ((Materials.hasMaterial(MaterialNames.MERCURY)) && ("liquid_mercury" - .equals(mapping.resourceLocation.getResourcePath()))) { - mapping.remap(com.mcmoddev.lib.init.Materials - .getMaterialByName(MaterialNames.MERCURY).getFluidBlock()); - } - } else if (mapping.type.equals(GameRegistry.Type.ITEM)) { - if ((Materials.hasMaterial(MaterialNames.COAL)) && ("carbon_powder" - .equals(mapping.resourceLocation.getResourcePath()))) { - mapping.remap(com.mcmoddev.lib.init.Materials - .getMaterialByName(MaterialNames.COAL).getItem(Names.POWDER)); - } - } - } - } - } - - public void init(final FMLInitializationEvent event) { - this.allsGood = false; - com.mcmoddev.lib.init.Recipes.init(); - Recipes.init(); - - Achievements.init(); - FuelRegistry.register(); - IntegrationManager.INSTANCE.runCallbacks("init"); - MinecraftForge.EVENT_BUS.register(new EventHandler()); - this.allsGood = true; - - // by this point all materials should have been registered both with MMDLib and Minecraft - // move to a separate function - potentially in FallbackGeneratorData - after the test - for (final MMDMaterial material : com.mcmoddev.lib.init.Materials.getAllMaterials()) { - if (material.hasBlock(Names.ORE)) { - FallbackGeneratorData.getInstance().addMaterial(material.getName(), - Names.ORE.toString(), material.getDefaultDimension()); - - if (material.hasBlock(Names.NETHERORE)) { - FallbackGeneratorData.getInstance().addMaterial(material.getName(), - Names.NETHERORE.toString(), -1); - } - - if (material.hasBlock(Names.ENDORE)) { - FallbackGeneratorData.getInstance().addMaterial(material.getName(), - Names.ENDORE.toString(), 1); - } - } - } - - ItemGroups.setupIcons(MaterialNames.STARSTEEL); - } - - public void postInit(final FMLPostInitializationEvent event) { - this.allsGood = false; - IntegrationManager.INSTANCE.runCallbacks("postInit"); - ConfigBase.postInit(); - this.allsGood = true; - FallbackGeneratorData.getInstance().setup(); - } -} diff --git a/src/main/java/com/mcmoddev/lib/block/BlockMMDBlock.java b/src/main/java/com/mcmoddev/lib/block/BlockMMDBlock.java index 73aa3d831..e015f9e4c 100644 --- a/src/main/java/com/mcmoddev/lib/block/BlockMMDBlock.java +++ b/src/main/java/com/mcmoddev/lib/block/BlockMMDBlock.java @@ -1,6 +1,6 @@ package com.mcmoddev.lib.block; -import com.mcmoddev.basemetals.data.AchievementNames; +import zone.moddev.mc.basemetals.data.AchievementNames; import com.mcmoddev.lib.init.Achievements; import com.mcmoddev.lib.material.IMMDObject; import com.mcmoddev.lib.material.MMDMaterial; diff --git a/src/main/java/com/mcmoddev/lib/block/BlockMMDEndOre.java b/src/main/java/com/mcmoddev/lib/block/BlockMMDEndOre.java index cd7ff25b9..d9c474c24 100644 --- a/src/main/java/com/mcmoddev/lib/block/BlockMMDEndOre.java +++ b/src/main/java/com/mcmoddev/lib/block/BlockMMDEndOre.java @@ -1,6 +1,6 @@ package com.mcmoddev.lib.block; -import com.mcmoddev.basemetals.data.MaterialNames; +import zone.moddev.mc.basemetals.data.MaterialNames; import com.mcmoddev.lib.data.Names; import com.mcmoddev.lib.init.Materials; import com.mcmoddev.lib.material.IMMDObject; diff --git a/src/main/java/com/mcmoddev/lib/block/BlockMMDNetherOre.java b/src/main/java/com/mcmoddev/lib/block/BlockMMDNetherOre.java index 1a9c827f6..d62becc36 100644 --- a/src/main/java/com/mcmoddev/lib/block/BlockMMDNetherOre.java +++ b/src/main/java/com/mcmoddev/lib/block/BlockMMDNetherOre.java @@ -1,6 +1,6 @@ package com.mcmoddev.lib.block; -import com.mcmoddev.basemetals.data.MaterialNames; +import zone.moddev.mc.basemetals.data.MaterialNames; import com.mcmoddev.lib.data.Names; import com.mcmoddev.lib.init.Materials; import com.mcmoddev.lib.material.IMMDObject; diff --git a/src/main/java/com/mcmoddev/lib/block/BlockMMDOre.java b/src/main/java/com/mcmoddev/lib/block/BlockMMDOre.java index 8d764f48a..0d61b7047 100644 --- a/src/main/java/com/mcmoddev/lib/block/BlockMMDOre.java +++ b/src/main/java/com/mcmoddev/lib/block/BlockMMDOre.java @@ -2,7 +2,7 @@ import java.util.Random; -import com.mcmoddev.basemetals.data.MaterialNames; +import zone.moddev.mc.basemetals.data.MaterialNames; import com.mcmoddev.lib.data.Names; import com.mcmoddev.lib.init.Materials; import com.mcmoddev.lib.material.IMMDObject; diff --git a/src/main/java/com/mcmoddev/lib/entity/EntityCustomBolt.java b/src/main/java/com/mcmoddev/lib/entity/EntityCustomBolt.java index d771cc222..406793214 100644 --- a/src/main/java/com/mcmoddev/lib/entity/EntityCustomBolt.java +++ b/src/main/java/com/mcmoddev/lib/entity/EntityCustomBolt.java @@ -1,6 +1,6 @@ package com.mcmoddev.lib.entity; -import com.mcmoddev.basemetals.data.MaterialNames; +import zone.moddev.mc.basemetals.data.MaterialNames; import com.mcmoddev.lib.data.Names; import com.mcmoddev.lib.init.Materials; diff --git a/src/main/java/com/mcmoddev/lib/init/Blocks.java b/src/main/java/com/mcmoddev/lib/init/Blocks.java index ccfee525a..e57c9ce82 100755 --- a/src/main/java/com/mcmoddev/lib/init/Blocks.java +++ b/src/main/java/com/mcmoddev/lib/init/Blocks.java @@ -14,7 +14,7 @@ import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; -import com.mcmoddev.basemetals.BaseMetals; +import zone.moddev.mc.basemetals.BaseMetals; import com.mcmoddev.lib.block.BlockMMDAnvil; import com.mcmoddev.lib.block.BlockMMDBars; import com.mcmoddev.lib.block.BlockMMDBlock; diff --git a/src/main/java/com/mcmoddev/lib/init/Fluids.java b/src/main/java/com/mcmoddev/lib/init/Fluids.java index e7223ba47..50e6c57fd 100644 --- a/src/main/java/com/mcmoddev/lib/init/Fluids.java +++ b/src/main/java/com/mcmoddev/lib/init/Fluids.java @@ -8,7 +8,7 @@ import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; -import com.mcmoddev.basemetals.data.MaterialNames; +import zone.moddev.mc.basemetals.data.MaterialNames; import com.mcmoddev.lib.block.InteractiveFluidBlock; import com.mcmoddev.lib.data.SharedStrings; import com.mcmoddev.lib.fluids.CustomFluid; diff --git a/src/main/java/com/mcmoddev/lib/init/Items.java b/src/main/java/com/mcmoddev/lib/init/Items.java index 2c98c58c5..ef8ca1f18 100644 --- a/src/main/java/com/mcmoddev/lib/init/Items.java +++ b/src/main/java/com/mcmoddev/lib/init/Items.java @@ -18,7 +18,7 @@ import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; -import com.mcmoddev.basemetals.BaseMetals; +import zone.moddev.mc.basemetals.BaseMetals; import com.mcmoddev.lib.block.BlockMMDBars; import com.mcmoddev.lib.block.BlockMMDBlock; import com.mcmoddev.lib.block.BlockMMDButton; diff --git a/src/main/java/com/mcmoddev/lib/init/Materials.java b/src/main/java/com/mcmoddev/lib/init/Materials.java index bb40e2219..221cb4a66 100755 --- a/src/main/java/com/mcmoddev/lib/init/Materials.java +++ b/src/main/java/com/mcmoddev/lib/init/Materials.java @@ -10,7 +10,7 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; -import com.mcmoddev.basemetals.BaseMetals; +import zone.moddev.mc.basemetals.BaseMetals; import com.mcmoddev.lib.data.MaterialStats; import com.mcmoddev.lib.data.SharedStrings; import com.mcmoddev.lib.material.MMDMaterial; diff --git a/src/main/java/com/mcmoddev/lib/init/Recipes.java b/src/main/java/com/mcmoddev/lib/init/Recipes.java index 611ef989d..a4d665e43 100755 --- a/src/main/java/com/mcmoddev/lib/init/Recipes.java +++ b/src/main/java/com/mcmoddev/lib/init/Recipes.java @@ -4,8 +4,8 @@ import org.apache.commons.lang3.StringUtils; -import com.mcmoddev.basemetals.BaseMetals; -import com.mcmoddev.basemetals.data.MaterialNames; +import zone.moddev.mc.basemetals.BaseMetals; +import zone.moddev.mc.basemetals.data.MaterialNames; import com.mcmoddev.lib.data.Names; import com.mcmoddev.lib.data.SharedStrings; import com.mcmoddev.lib.material.IMMDObject; diff --git a/src/main/java/com/mcmoddev/lib/init/VillagerTrades.java b/src/main/java/com/mcmoddev/lib/init/VillagerTrades.java index d7fee540b..f0e45b86c 100644 --- a/src/main/java/com/mcmoddev/lib/init/VillagerTrades.java +++ b/src/main/java/com/mcmoddev/lib/init/VillagerTrades.java @@ -10,7 +10,7 @@ import javax.annotation.Nonnull; -import com.mcmoddev.basemetals.util.VillagerTradeHelper; +import zone.moddev.mc.basemetals.util.VillagerTradeHelper; import com.mcmoddev.lib.data.MaterialStats; import com.mcmoddev.lib.data.Names; import com.mcmoddev.lib.data.SharedStrings; diff --git a/src/main/java/com/mcmoddev/lib/integration/IntegrationManager.java b/src/main/java/com/mcmoddev/lib/integration/IntegrationManager.java index d7a126cd1..141a25988 100644 --- a/src/main/java/com/mcmoddev/lib/integration/IntegrationManager.java +++ b/src/main/java/com/mcmoddev/lib/integration/IntegrationManager.java @@ -9,7 +9,7 @@ import javax.annotation.Nonnull; import com.google.common.collect.Lists; -import com.mcmoddev.basemetals.BaseMetals; +import zone.moddev.mc.basemetals.BaseMetals; import net.minecraftforge.fml.common.Loader; import net.minecraftforge.fml.common.discovery.ASMDataTable.ASMData; diff --git a/src/main/java/com/mcmoddev/lib/integration/plugins/EnderIOBase.java b/src/main/java/com/mcmoddev/lib/integration/plugins/EnderIOBase.java index 538460ad3..c9ec6dc33 100644 --- a/src/main/java/com/mcmoddev/lib/integration/plugins/EnderIOBase.java +++ b/src/main/java/com/mcmoddev/lib/integration/plugins/EnderIOBase.java @@ -2,7 +2,7 @@ import javax.annotation.Nonnull; -import com.mcmoddev.basemetals.BaseMetals; +import zone.moddev.mc.basemetals.BaseMetals; import com.mcmoddev.lib.data.Names; import com.mcmoddev.lib.init.Materials; diff --git a/src/main/java/com/mcmoddev/lib/integration/plugins/TAIGABase.java b/src/main/java/com/mcmoddev/lib/integration/plugins/TAIGABase.java index a3ca76050..17080fc13 100755 --- a/src/main/java/com/mcmoddev/lib/integration/plugins/TAIGABase.java +++ b/src/main/java/com/mcmoddev/lib/integration/plugins/TAIGABase.java @@ -1,20 +1,20 @@ -package com.mcmoddev.lib.integration.plugins; - -import com.mcmoddev.basemetals.BaseMetals; -import com.mcmoddev.lib.integration.IIntegration; -import com.mcmoddev.lib.util.ConfigBase.Options; - -public class TAIGABase implements IIntegration { - - public static final String PLUGIN_MODID = "taiga"; - - @Override - public void init() { - if (!Options.isModEnabled(PLUGIN_MODID)) { - if (!Options.isModEnabled(TinkersConstructBase.PLUGIN_MODID)) { - BaseMetals.logger.error("TAIGA Plugin requires the TinkersConstruct Plugin"); - } - return; - } - } -} +package com.mcmoddev.lib.integration.plugins; + +import zone.moddev.mc.basemetals.BaseMetals; +import com.mcmoddev.lib.integration.IIntegration; +import com.mcmoddev.lib.util.ConfigBase.Options; + +public class TAIGABase implements IIntegration { + + public static final String PLUGIN_MODID = "taiga"; + + @Override + public void init() { + if (!Options.isModEnabled(PLUGIN_MODID)) { + if (!Options.isModEnabled(TinkersConstructBase.PLUGIN_MODID)) { + BaseMetals.logger.error("TAIGA Plugin requires the TinkersConstruct Plugin"); + } + return; + } + } +} diff --git a/src/main/java/com/mcmoddev/lib/integration/plugins/tinkers/ModifierRegistry.java b/src/main/java/com/mcmoddev/lib/integration/plugins/tinkers/ModifierRegistry.java index ae8dcd786..893ef8b05 100755 --- a/src/main/java/com/mcmoddev/lib/integration/plugins/tinkers/ModifierRegistry.java +++ b/src/main/java/com/mcmoddev/lib/integration/plugins/tinkers/ModifierRegistry.java @@ -1,86 +1,86 @@ -package com.mcmoddev.lib.integration.plugins.tinkers; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; - -import javax.annotation.Nonnull; - -import com.mcmoddev.basemetals.BaseMetals; -import com.mcmoddev.lib.integration.plugins.tinkers.modifiers.ModifierFakeDiamond; -import com.mcmoddev.lib.integration.plugins.tinkers.modifiers.ModifierLeadPlated; -import com.mcmoddev.lib.integration.plugins.tinkers.modifiers.ModifierToxic; - -import net.minecraft.item.Item; -import net.minecraft.item.ItemStack; -import slimeknights.mantle.util.RecipeMatch; -import slimeknights.tconstruct.library.TinkerRegistry; -import slimeknights.tconstruct.library.modifiers.Modifier; - -/** - * Contains static instances of modifiers for registering with TiC. - * - * @author Daniel Hazelton <dshadowwolf@gmail.com> - * - */ - -public class ModifierRegistry { - - private static final Map modifiers = new HashMap<>(); - - private ModifierRegistry() { - throw new IllegalAccessError("Not an instantiable class"); - } - - public static void setModifierRecipe(@Nonnull final String name, - @Nonnull final ItemStack... ingredients) { - final Modifier t = modifiers.get(name); - if (t == null) { - BaseMetals.logger.error("Trying to add a recipe to unknown modifier %s, ignoring.", - name); - return; - } - - t.addRecipeMatch(new RecipeMatch.ItemCombination(1, ingredients)); - } - - public static void setModifierItem(@Nonnull final String name, @Nonnull final ItemStack item) { - setModifierItem(name, item.getItem()); - } - - public static void setModifierItem(@Nonnull final String name, @Nonnull final Item item) { - final Modifier t = modifiers.get(name); - if (t == null) { - BaseMetals.logger.error("Trying to add an item to unknown modifier %s, ignoring.", - name); - return; - } - - t.addItem(item); - } - - public static void initModifiers() { - modifiers.put("toxic", new ModifierToxic()); - modifiers.put("plated", new ModifierLeadPlated()); - modifiers.put("fake-diamond", new ModifierFakeDiamond()); - } - - public static void registerModifiers() { - for (final Entry ent : modifiers.entrySet()) { - TinkerRegistry.registerModifier(ent.getValue()); - } - } - - public static Map getModifierDetails(@Nonnull final String name) { - final Map rv = new HashMap<>(); - if (modifiers.containsKey(name)) { - final Modifier t = modifiers.get(name); - rv.put("name", t.getLocalizedName()); - rv.put("desc", t.getLocalizedDesc()); - return rv; - } else { - return Collections.emptyMap(); - } - } -} +package com.mcmoddev.lib.integration.plugins.tinkers; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import javax.annotation.Nonnull; + +import zone.moddev.mc.basemetals.BaseMetals; +import com.mcmoddev.lib.integration.plugins.tinkers.modifiers.ModifierFakeDiamond; +import com.mcmoddev.lib.integration.plugins.tinkers.modifiers.ModifierLeadPlated; +import com.mcmoddev.lib.integration.plugins.tinkers.modifiers.ModifierToxic; + +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import slimeknights.mantle.util.RecipeMatch; +import slimeknights.tconstruct.library.TinkerRegistry; +import slimeknights.tconstruct.library.modifiers.Modifier; + +/** + * Contains static instances of modifiers for registering with TiC. + * + * @author Daniel Hazelton <dshadowwolf@gmail.com> + * + */ + +public class ModifierRegistry { + + private static final Map modifiers = new HashMap<>(); + + private ModifierRegistry() { + throw new IllegalAccessError("Not an instantiable class"); + } + + public static void setModifierRecipe(@Nonnull final String name, + @Nonnull final ItemStack... ingredients) { + final Modifier t = modifiers.get(name); + if (t == null) { + BaseMetals.logger.error("Trying to add a recipe to unknown modifier %s, ignoring.", + name); + return; + } + + t.addRecipeMatch(new RecipeMatch.ItemCombination(1, ingredients)); + } + + public static void setModifierItem(@Nonnull final String name, @Nonnull final ItemStack item) { + setModifierItem(name, item.getItem()); + } + + public static void setModifierItem(@Nonnull final String name, @Nonnull final Item item) { + final Modifier t = modifiers.get(name); + if (t == null) { + BaseMetals.logger.error("Trying to add an item to unknown modifier %s, ignoring.", + name); + return; + } + + t.addItem(item); + } + + public static void initModifiers() { + modifiers.put("toxic", new ModifierToxic()); + modifiers.put("plated", new ModifierLeadPlated()); + modifiers.put("fake-diamond", new ModifierFakeDiamond()); + } + + public static void registerModifiers() { + for (final Entry ent : modifiers.entrySet()) { + TinkerRegistry.registerModifier(ent.getValue()); + } + } + + public static Map getModifierDetails(@Nonnull final String name) { + final Map rv = new HashMap<>(); + if (modifiers.containsKey(name)) { + final Modifier t = modifiers.get(name); + rv.put("name", t.getLocalizedName()); + rv.put("desc", t.getLocalizedDesc()); + return rv; + } else { + return Collections.emptyMap(); + } + } +} diff --git a/src/main/java/com/mcmoddev/lib/integration/plugins/tinkers/TinkersConstructRegistry.java b/src/main/java/com/mcmoddev/lib/integration/plugins/tinkers/TinkersConstructRegistry.java index 77fc7fd4e..f7725bb9c 100755 --- a/src/main/java/com/mcmoddev/lib/integration/plugins/tinkers/TinkersConstructRegistry.java +++ b/src/main/java/com/mcmoddev/lib/integration/plugins/tinkers/TinkersConstructRegistry.java @@ -1,546 +1,546 @@ -package com.mcmoddev.lib.integration.plugins.tinkers; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -import javax.annotation.Nonnull; - -import com.mcmoddev.basemetals.data.MaterialNames; -import com.mcmoddev.lib.data.Names; -import com.mcmoddev.lib.material.MMDMaterial; -import com.mcmoddev.lib.material.MMDMaterial.MaterialType; -import com.mcmoddev.lib.util.Oredicts; - -import net.minecraft.block.Block; -import net.minecraft.item.Item; -import net.minecraft.item.ItemStack; -import net.minecraftforge.fluids.Fluid; -import net.minecraftforge.fluids.FluidRegistry; -import net.minecraftforge.fluids.FluidStack; -import net.minecraftforge.oredict.OreDictionary; -import slimeknights.tconstruct.library.MaterialIntegration; -import slimeknights.tconstruct.library.TinkerRegistry; -import slimeknights.tconstruct.library.materials.Material; -import slimeknights.tconstruct.library.traits.AbstractTrait; - -/** - * Created by Daniel Hazelton on 2/21/2017. - * - * @author Daniel Hazelton <dshadowwolf@gmail.com> - */ - -public class TinkersConstructRegistry { - - private final Map registry = new HashMap<>(); - private final List integrations = new ArrayList<>(); - - private static TinkersConstructRegistry instance; - - /** - * Hacky-ass shit constructor. Be warned that this creates a semi-circular data structure. - * - * @constructor - */ - private TinkersConstructRegistry() { - // blank, hidden default constructor - } - - /** - * Return the existing instance or create a new one - if one doesn't exist - and return it - * - * @return an instance of the registry - */ - public static TinkersConstructRegistry getInstance() { - if (instance == null) { - instance = new TinkersConstructRegistry(); - } - - return instance; - } - - /** - * Has a material with the given name been registered ? - * - * @param name - * The name to test for - * @return Boolean truth value of whether or not the item has been registered - */ - public boolean isRegistered(@Nonnull final String name) { - return this.registry.containsKey(name); - } - - /** - * Used internally to add an entry to the registry - * - * @param name - * name of material being registered - * @param mat - * Material to be registered - * @return the material - */ - private TCMaterial put(@Nonnull final String name, @Nonnull final TCMaterial mat) { - if (this.isRegistered(name)) { - return this.registry.get(name); - } - this.registry.put(name, mat); - return mat; - } - - /** - * Used internally to access the registry and grab an entry. Helps to minimize some potential - * issues. - * - * @param name - * name used for material during registration - * @return the request material - */ - private TCMaterial get(@Nonnull final String name) { - return getInstance().registry.get(name); - } - - /** - * Get a the named material, returning a new one of the correct name if an existing one is not - * available - * - * @param name - * Name of the material - * @return Reference to the material asked for or a new one of the correct name - */ - public TCMaterial getMaterial(@Nonnull final String name) { - if (getInstance().isRegistered(name)) { - return getInstance().get(name); - } - return getInstance().put(name, new TCMaterial(name)); - } - - /** - * Get a material of a given name based on a given MMDMaterial - * - * @param name - * name of the material - * @param material - * MMDMaterial it is based on - * @return Any TCCode that represents an error or TCCode.SUCCESS - */ - public TCMaterial getMaterial(@Nonnull final String name, @Nonnull final MMDMaterial material) { - if (getInstance().isRegistered(name)) { - return getInstance().getMaterial(name); - } - - return getInstance().put(name, new TCMaterial(name, material)); - } - - /** - * Private internal function for adding a MaterialIntegration to said list - * - * @param m - * The MaterialIntegration to add to the list - */ - private static void addIntegration(@Nonnull final MaterialIntegration m) { - getInstance().integrations.add(m); - } - - /** - * Private internal that handles actual material registration and creates the - * MaterialIntegration objects used to make sure that our materials are properly registered with - * TiC - * - * @param mat - * The material being registered. - * @return Hopefully TCCode.SUCCESS, but can be any number of the various error code returns - */ - private TCCode register(@Nonnull final TCMaterial mat) { - final String matName = mat.getName().toLowerCase(); - final MMDMaterial mmdMaterial = mat.getMMDMaterial(); - final Fluid fluid = mmdMaterial.getFluid(); - if (TinkerRegistry.getMaterial(matName) != Material.UNKNOWN) { - return TCCode.MATERIAL_ALREADY_REGISTERED; - } - - final Boolean hasTraits = !mat.getTraitLocations().isEmpty(); - - if (fluid == null) { - return TCCode.BAD_MATERIAL; - } - - // make sure the name used here is all lower case - final Material tcmat = new Material(matName, mmdMaterial.getTintColor()); - - if (hasTraits) { - for (final String s : mat.getTraitLocations()) { - for (final AbstractTrait t : mat.getTraits(s)) { - tcmat.addTrait(t, "general".equals(s) ? null : s); - } - } - } - - Item matRepItem; - final MaterialType matType = mmdMaterial.getType(); - switch (matType) { - case METAL: - matRepItem = mmdMaterial.getItem(Names.INGOT); - break; - case GEM: - matRepItem = mmdMaterial.getItem(Names.GEM); - break; - case CRYSTAL: - matRepItem = mmdMaterial.getItem(Names.CRYSTAL); - break; - case MINERAL: - matRepItem = mmdMaterial.getItem(Names.INGOT); - break; - case ROCK: - case WOOD: - default: - return TCCode.BAD_MATERIAL; - } - - this.registerFluid(mmdMaterial, mat.getAmountPer()); - - TinkerRegistry.addMaterialStats(tcmat, mat.getHeadStats()); - TinkerRegistry.addMaterialStats(tcmat, mat.getHandleStats()); - TinkerRegistry.addMaterialStats(tcmat, mat.getExtraStats()); - TinkerRegistry.addMaterialStats(tcmat, mat.getBowStats()); - TinkerRegistry.addMaterialStats(tcmat, mat.getBowStringStats()); - TinkerRegistry.addMaterialStats(tcmat, mat.getArrowShaftStats()); - TinkerRegistry.addMaterialStats(tcmat, mat.getFletchingStats()); - - tcmat.setFluid(fluid).setCraftable(mat.getCraftable()).setCastable(mat.getCastable()) - .addItem(matRepItem, 1, Material.VALUE_Ingot); - tcmat.setRepresentativeItem(matRepItem); - - final String base = mmdMaterial.getName(); - final String suffix = base.substring(0, 1).toUpperCase() + base.substring(1); - final MaterialIntegration m = new MaterialIntegration(tcmat, fluid, suffix); - - if (mat.getToolForge()) { - m.toolforge(); - } - - addIntegration(m); - m.integrate(); - return TCCode.SUCCESS; - } - - /** - * Register all materials in the registry - * - * @return Any TCCode that represents an error or TCCode.SUCCESS - */ - public TCCode registerAll() { - for (final Entry ent : this.registry.entrySet()) { - // log ent.getKey() - the material name - here ? - final TCCode rv = this.register(ent.getValue()); - // Either we've had a success or the material is already registered - // in those two cases, we carry on. Otherwise we return the error and - // halt - if ((rv != TCCode.SUCCESS) && (rv != TCCode.MATERIAL_ALREADY_REGISTERED)) { - return rv; - } - } - return TCCode.SUCCESS; - } - - /** - * Register an item as melting to a given amount of a specific output fluid As we have no real - * method of detecting errors here, we could have this return void. Using TCCode for - * orthogonality and possible future utility - * - * @param input - * The Item to be melted - * @param output - * The fluid it melts to - * @param outputQuantity - * How much of the fluid, in mB - * @return Any TCCode that represents an error or TCCode.SUCCESS - */ - public TCCode registerMelting(@Nonnull final Item input, @Nonnull final Fluid output, - @Nonnull final int outputQuantity) { - if (outputQuantity == 0) { - return TCCode.FAILURE_PARAMETER_ERROR; - } - TinkerRegistry.registerMelting(input, output, outputQuantity); - return TCCode.SUCCESS; - } - - /** - * Same as the other function, but using an ore-dict - * - * @param oredictName - * OreDictionary entry to use as the input item - * @param output - * Output fluid - * @param outputQuantity - * Output Amount - * @return Any TCCode that represents an error or TCCode.SUCCESS - */ - public TCCode registerMelting(@Nonnull final String oredictName, @Nonnull final Fluid output, - @Nonnull final int outputQuantity) { - if (outputQuantity == 0) { - return TCCode.FAILURE_PARAMETER_ERROR; - } - TinkerRegistry.registerMelting(oredictName, output, outputQuantity); - return TCCode.SUCCESS; - } - - /** - * Internal helper to get a FluidStack from a pair of parameters to the variadic 'registerAlloy' - * function - * - * @param fluid - * String, Fluid or MetalMaterial representing the fluid - * @param amt - * passed in as an Object, but actually an Integer representing the amount of the - * fluid, in mB, that is in the stack - * @return a FluidStack representing the Fluid+Amount pair of parameters passed in - */ - private static FluidStack getFluidStack(@Nonnull final Object fluid, - @Nonnull final Object amt) { - final Integer amount = (Integer) amt; - if (fluid instanceof String) { - return new FluidStack(FluidRegistry.getFluid((String) fluid), amount.intValue()); - } else if (fluid instanceof MMDMaterial) { - return new FluidStack(FluidRegistry.getFluid(((MMDMaterial) fluid).getName()), - amount.intValue()); - } else if (fluid instanceof Fluid) { - return new FluidStack(FluidRegistry.getFluid(((Fluid) fluid).getName()), - amount.intValue()); - } - return null; - } - - /** - * Register an alloy with the given recipe, name and output fluid in the specified amount - * - * @param name - * Name of the alloy - * @param output - * Output fluid - * @param outputAmount - * Amount of fluid produced - * @param recipe - * A chunk of String/Fluid/MetalMaterial/TCMaterial followed by an amount - must be - * at least two entries (4 items) - * @return Any TCCode that represents an error or TCCode.SUCCESS - */ - public TCCode registerAlloy(@Nonnull final String name, @Nonnull final Fluid output, - @Nonnull final int outputAmount, @Nonnull final Object... recipe) { - if (outputAmount == 0) { - return TCCode.FAILURE_PARAMETER_ERROR; - } - - final FluidStack outputStack = new FluidStack(output, outputAmount); - final FluidStack[] inputs = new FluidStack[recipe.length / 2]; - - for (int i = 0, j = 0; i < recipe.length; i += 2, j++) { - inputs[j] = getFluidStack(recipe[i], recipe[i + 1]); - } - - TinkerRegistry.registerAlloy(outputStack, inputs); - return TCCode.SUCCESS; - } - - /** - * Variant for backwards compat - * - * @param output - * Oredict name of output fluid - * @param outputQty - * Amount of output produced - * @param inputNames - * Array of oredict names of inputs - * @param inputQty - * Array of quantities matching those inputs - * @return TCCode.SUCCESS or TCCode.FAILURE_PARAMETER_ERROR - */ - public TCCode registerAlloy(@Nonnull final String output, @Nonnull final int outputQty, - @Nonnull final String[] inputNames, @Nonnull final int[] inputQty) { - if (inputNames.length != inputQty.length) { - return TCCode.FAILURE_PARAMETER_ERROR; - } - - final FluidStack outputs = getFluidStack(output, outputQty); - final FluidStack[] inputs = new FluidStack[inputNames.length]; - - for (int i = 0; i < inputNames.length; i++) { - inputs[i] = getFluidStack(inputNames[i], inputQty[i]); - } - TinkerRegistry.registerAlloy(outputs, inputs); - return TCCode.SUCCESS; - } - - /** - * Register the ability to cast a block of material from a specified quantity of a fluid - * - * @param block - * Block that is created - * @param source - * Fluid being poured - * @param sourceQty - * Amount of fluid being poured - * @return Any TCCode that represents an error or TCCode.SUCCESS - */ - public TCCode registerBasin(@Nonnull final Block block, @Nonnull final Fluid source, - @Nonnull final int sourceQty) { - if (sourceQty == 0) { - return TCCode.FAILURE_PARAMETER_ERROR; - } - TinkerRegistry.registerBasinCasting(new ItemStack(block), null, source, - sourceQty); - return TCCode.SUCCESS; - } - - /** - * Register a casting table piece. This is either an ingot, a nugget or a gem - * - * @param output - * Item that is output - * @param source - * source material/fluid - * @param sourceQty - * Amount - * @return Any TCCode that represents an error or TCCode.SUCCESS - */ - public TCCode registerCasting(@Nonnull final Item output, @Nonnull final Fluid source, - @Nonnull final int sourceQty) { - if (sourceQty == 0) { - return TCCode.FAILURE_PARAMETER_ERROR; - } - TinkerRegistry.registerTableCasting(new ItemStack(output), null, source, - sourceQty); - return TCCode.SUCCESS; - } - - /** - * Register all the fluids a MetalMaterial might melt into, at the amountPer amount - * - * @param base - * MetalMaterial that is the base for this fluid - * @param amountPer - * How much per ingot/single item of the material - * @return TCCode.SUCCESS - at this point there are no failure points in this routine - */ - public TCCode registerFluid(@Nonnull final MMDMaterial base, @Nonnull final int amountPer) { - final String materialName = base.getName(); - final Fluid output = FluidRegistry.getFluid(materialName); - final String oreDictName = base.getCapitalizedName(); - - // hacky fix for Coal being itemCoal and not ingotCoal - if (MaterialNames.COAL.equals(base.getName())) { - this.meltingHelper("itemCoal", output, amountPer); - } - - this.meltingHelper(Oredicts.ORE + oreDictName, output, amountPer * 2); - this.meltingHelper(Oredicts.BLOCK + oreDictName, output, amountPer * 9); - this.meltingHelper(Oredicts.INGOT + oreDictName, output, amountPer); - this.meltingHelper(Oredicts.NUGGET + oreDictName, output, amountPer / 9); - this.meltingHelper(Oredicts.DUST + oreDictName, output, amountPer); - this.meltingHelper(Oredicts.DUST_SMALL + oreDictName, output, amountPer / 9); - if (base.hasBlock(Names.NETHERORE)) { - this.meltingHelper(base.getBlock(Names.NETHERORE), output, amountPer * 4); - } - - if (base.hasBlock(Names.ENDORE)) { - this.meltingHelper(base.getBlock(Names.ENDORE), output, amountPer * 4); - } - - if (base.hasBlock(Names.SLAB)) { - this.meltingHelper(base.getBlock(Names.SLAB), output, - (amountPer * 4) + (amountPer / 2)); - } - - if (base.hasBlock(Names.WALL)) { - this.meltingHelper(base.getBlock(Names.WALL), output, amountPer * 9); - } - - if (base.hasItem(Names.BOOTS)) { - this.meltingHelper(base.getItem(Names.BOOTS), output, amountPer * 4); - } - - if (base.hasItem(Names.HELMET)) { - this.meltingHelper(base.getItem(Names.HELMET), output, amountPer * 5); - } - - if (base.hasItem(Names.CHESTPLATE)) { - this.meltingHelper(base.getItem(Names.CHESTPLATE), output, amountPer * 8); - } - - if (base.hasItem(Names.LEGGINGS)) { - this.meltingHelper(base.getItem(Names.LEGGINGS), output, amountPer * 7); - } - - if (base.hasItem(Names.SHEARS)) { - this.meltingHelper(base.getItem(Names.SHEARS), output, amountPer * 2); - } - - if (base.hasBlock(Names.PRESSURE_PLATE)) { - this.meltingHelper(base.getBlock(Names.PRESSURE_PLATE), output, amountPer * 2); - } - - if (base.hasBlock(Names.BARS)) { - this.meltingHelper(base.getBlock(Names.BARS), output, ((amountPer / 9) * 3) + 6); // Fun - // math - } - - if (base.hasItem(Names.ROD)) { - this.meltingHelper(base.getItem(Names.ROD), output, amountPer / 2); - } - - if (base.hasItem(Names.DOOR)) { - this.meltingHelper(base.getItem(Names.DOOR), output, amountPer * 2); - } - - if (base.hasBlock(Names.TRAPDOOR)) { - this.meltingHelper(base.getBlock(Names.TRAPDOOR), output, amountPer * 4); - } - - if (base.hasBlock(Names.BUTTON)) { - this.meltingHelper(base.getBlock(Names.BUTTON), output, (amountPer / 9) * 2); - } - - return TCCode.SUCCESS; - } - - /* - * The following functions are helpers to help make the registerMelting function a touch less - * complex - */ - private void meltingHelper(@Nonnull final Item item, @Nonnull final Fluid output, - @Nonnull final int amount) { - if (item != null) { - TinkerRegistry.registerMelting(item, output, amount); - // } else { - // BaseMetals.logger.error("Attempted to register null item for fluid " + - // output.getName()); - } - } - - private void meltingHelper(@Nonnull final String itemName, @Nonnull final Fluid output, - @Nonnull final int amount) { - if (!OreDictionary.getOres(itemName).isEmpty()) { - TinkerRegistry.registerMelting(itemName, output, amount); - // } else { - // BaseMetals.logger.error("Attempted to register oredict for missing item " + - // itemName); - } - } - - private void meltingHelper(@Nonnull final Block block, @Nonnull final Fluid output, - @Nonnull final int amount) { - if (block != null) { - TinkerRegistry.registerMelting(block, output, amount); - // } else { - // BaseMetals.logger.error("Attempted to register null block for fluid " + - // output.getName()); - } - } - - public void integrateRecipes() { - for (final MaterialIntegration m : this.integrations) { - m.integrateRecipes(); - } - } -} +package com.mcmoddev.lib.integration.plugins.tinkers; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import javax.annotation.Nonnull; + +import zone.moddev.mc.basemetals.data.MaterialNames; +import com.mcmoddev.lib.data.Names; +import com.mcmoddev.lib.material.MMDMaterial; +import com.mcmoddev.lib.material.MMDMaterial.MaterialType; +import com.mcmoddev.lib.util.Oredicts; + +import net.minecraft.block.Block; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraftforge.fluids.Fluid; +import net.minecraftforge.fluids.FluidRegistry; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.oredict.OreDictionary; +import slimeknights.tconstruct.library.MaterialIntegration; +import slimeknights.tconstruct.library.TinkerRegistry; +import slimeknights.tconstruct.library.materials.Material; +import slimeknights.tconstruct.library.traits.AbstractTrait; + +/** + * Created by Daniel Hazelton on 2/21/2017. + * + * @author Daniel Hazelton <dshadowwolf@gmail.com> + */ + +public class TinkersConstructRegistry { + + private final Map registry = new HashMap<>(); + private final List integrations = new ArrayList<>(); + + private static TinkersConstructRegistry instance; + + /** + * Hacky-ass shit constructor. Be warned that this creates a semi-circular data structure. + * + * @constructor + */ + private TinkersConstructRegistry() { + // blank, hidden default constructor + } + + /** + * Return the existing instance or create a new one - if one doesn't exist - and return it + * + * @return an instance of the registry + */ + public static TinkersConstructRegistry getInstance() { + if (instance == null) { + instance = new TinkersConstructRegistry(); + } + + return instance; + } + + /** + * Has a material with the given name been registered ? + * + * @param name + * The name to test for + * @return Boolean truth value of whether or not the item has been registered + */ + public boolean isRegistered(@Nonnull final String name) { + return this.registry.containsKey(name); + } + + /** + * Used internally to add an entry to the registry + * + * @param name + * name of material being registered + * @param mat + * Material to be registered + * @return the material + */ + private TCMaterial put(@Nonnull final String name, @Nonnull final TCMaterial mat) { + if (this.isRegistered(name)) { + return this.registry.get(name); + } + this.registry.put(name, mat); + return mat; + } + + /** + * Used internally to access the registry and grab an entry. Helps to minimize some potential + * issues. + * + * @param name + * name used for material during registration + * @return the request material + */ + private TCMaterial get(@Nonnull final String name) { + return getInstance().registry.get(name); + } + + /** + * Get a the named material, returning a new one of the correct name if an existing one is not + * available + * + * @param name + * Name of the material + * @return Reference to the material asked for or a new one of the correct name + */ + public TCMaterial getMaterial(@Nonnull final String name) { + if (getInstance().isRegistered(name)) { + return getInstance().get(name); + } + return getInstance().put(name, new TCMaterial(name)); + } + + /** + * Get a material of a given name based on a given MMDMaterial + * + * @param name + * name of the material + * @param material + * MMDMaterial it is based on + * @return Any TCCode that represents an error or TCCode.SUCCESS + */ + public TCMaterial getMaterial(@Nonnull final String name, @Nonnull final MMDMaterial material) { + if (getInstance().isRegistered(name)) { + return getInstance().getMaterial(name); + } + + return getInstance().put(name, new TCMaterial(name, material)); + } + + /** + * Private internal function for adding a MaterialIntegration to said list + * + * @param m + * The MaterialIntegration to add to the list + */ + private static void addIntegration(@Nonnull final MaterialIntegration m) { + getInstance().integrations.add(m); + } + + /** + * Private internal that handles actual material registration and creates the + * MaterialIntegration objects used to make sure that our materials are properly registered with + * TiC + * + * @param mat + * The material being registered. + * @return Hopefully TCCode.SUCCESS, but can be any number of the various error code returns + */ + private TCCode register(@Nonnull final TCMaterial mat) { + final String matName = mat.getName().toLowerCase(); + final MMDMaterial mmdMaterial = mat.getMMDMaterial(); + final Fluid fluid = mmdMaterial.getFluid(); + if (TinkerRegistry.getMaterial(matName) != Material.UNKNOWN) { + return TCCode.MATERIAL_ALREADY_REGISTERED; + } + + final Boolean hasTraits = !mat.getTraitLocations().isEmpty(); + + if (fluid == null) { + return TCCode.BAD_MATERIAL; + } + + // make sure the name used here is all lower case + final Material tcmat = new Material(matName, mmdMaterial.getTintColor()); + + if (hasTraits) { + for (final String s : mat.getTraitLocations()) { + for (final AbstractTrait t : mat.getTraits(s)) { + tcmat.addTrait(t, "general".equals(s) ? null : s); + } + } + } + + Item matRepItem; + final MaterialType matType = mmdMaterial.getType(); + switch (matType) { + case METAL: + matRepItem = mmdMaterial.getItem(Names.INGOT); + break; + case GEM: + matRepItem = mmdMaterial.getItem(Names.GEM); + break; + case CRYSTAL: + matRepItem = mmdMaterial.getItem(Names.CRYSTAL); + break; + case MINERAL: + matRepItem = mmdMaterial.getItem(Names.INGOT); + break; + case ROCK: + case WOOD: + default: + return TCCode.BAD_MATERIAL; + } + + this.registerFluid(mmdMaterial, mat.getAmountPer()); + + TinkerRegistry.addMaterialStats(tcmat, mat.getHeadStats()); + TinkerRegistry.addMaterialStats(tcmat, mat.getHandleStats()); + TinkerRegistry.addMaterialStats(tcmat, mat.getExtraStats()); + TinkerRegistry.addMaterialStats(tcmat, mat.getBowStats()); + TinkerRegistry.addMaterialStats(tcmat, mat.getBowStringStats()); + TinkerRegistry.addMaterialStats(tcmat, mat.getArrowShaftStats()); + TinkerRegistry.addMaterialStats(tcmat, mat.getFletchingStats()); + + tcmat.setFluid(fluid).setCraftable(mat.getCraftable()).setCastable(mat.getCastable()) + .addItem(matRepItem, 1, Material.VALUE_Ingot); + tcmat.setRepresentativeItem(matRepItem); + + final String base = mmdMaterial.getName(); + final String suffix = base.substring(0, 1).toUpperCase() + base.substring(1); + final MaterialIntegration m = new MaterialIntegration(tcmat, fluid, suffix); + + if (mat.getToolForge()) { + m.toolforge(); + } + + addIntegration(m); + m.integrate(); + return TCCode.SUCCESS; + } + + /** + * Register all materials in the registry + * + * @return Any TCCode that represents an error or TCCode.SUCCESS + */ + public TCCode registerAll() { + for (final Entry ent : this.registry.entrySet()) { + // log ent.getKey() - the material name - here ? + final TCCode rv = this.register(ent.getValue()); + // Either we've had a success or the material is already registered + // in those two cases, we carry on. Otherwise we return the error and + // halt + if ((rv != TCCode.SUCCESS) && (rv != TCCode.MATERIAL_ALREADY_REGISTERED)) { + return rv; + } + } + return TCCode.SUCCESS; + } + + /** + * Register an item as melting to a given amount of a specific output fluid As we have no real + * method of detecting errors here, we could have this return void. Using TCCode for + * orthogonality and possible future utility + * + * @param input + * The Item to be melted + * @param output + * The fluid it melts to + * @param outputQuantity + * How much of the fluid, in mB + * @return Any TCCode that represents an error or TCCode.SUCCESS + */ + public TCCode registerMelting(@Nonnull final Item input, @Nonnull final Fluid output, + @Nonnull final int outputQuantity) { + if (outputQuantity == 0) { + return TCCode.FAILURE_PARAMETER_ERROR; + } + TinkerRegistry.registerMelting(input, output, outputQuantity); + return TCCode.SUCCESS; + } + + /** + * Same as the other function, but using an ore-dict + * + * @param oredictName + * OreDictionary entry to use as the input item + * @param output + * Output fluid + * @param outputQuantity + * Output Amount + * @return Any TCCode that represents an error or TCCode.SUCCESS + */ + public TCCode registerMelting(@Nonnull final String oredictName, @Nonnull final Fluid output, + @Nonnull final int outputQuantity) { + if (outputQuantity == 0) { + return TCCode.FAILURE_PARAMETER_ERROR; + } + TinkerRegistry.registerMelting(oredictName, output, outputQuantity); + return TCCode.SUCCESS; + } + + /** + * Internal helper to get a FluidStack from a pair of parameters to the variadic 'registerAlloy' + * function + * + * @param fluid + * String, Fluid or MetalMaterial representing the fluid + * @param amt + * passed in as an Object, but actually an Integer representing the amount of the + * fluid, in mB, that is in the stack + * @return a FluidStack representing the Fluid+Amount pair of parameters passed in + */ + private static FluidStack getFluidStack(@Nonnull final Object fluid, + @Nonnull final Object amt) { + final Integer amount = (Integer) amt; + if (fluid instanceof String) { + return new FluidStack(FluidRegistry.getFluid((String) fluid), amount.intValue()); + } else if (fluid instanceof MMDMaterial) { + return new FluidStack(FluidRegistry.getFluid(((MMDMaterial) fluid).getName()), + amount.intValue()); + } else if (fluid instanceof Fluid) { + return new FluidStack(FluidRegistry.getFluid(((Fluid) fluid).getName()), + amount.intValue()); + } + return null; + } + + /** + * Register an alloy with the given recipe, name and output fluid in the specified amount + * + * @param name + * Name of the alloy + * @param output + * Output fluid + * @param outputAmount + * Amount of fluid produced + * @param recipe + * A chunk of String/Fluid/MetalMaterial/TCMaterial followed by an amount - must be + * at least two entries (4 items) + * @return Any TCCode that represents an error or TCCode.SUCCESS + */ + public TCCode registerAlloy(@Nonnull final String name, @Nonnull final Fluid output, + @Nonnull final int outputAmount, @Nonnull final Object... recipe) { + if (outputAmount == 0) { + return TCCode.FAILURE_PARAMETER_ERROR; + } + + final FluidStack outputStack = new FluidStack(output, outputAmount); + final FluidStack[] inputs = new FluidStack[recipe.length / 2]; + + for (int i = 0, j = 0; i < recipe.length; i += 2, j++) { + inputs[j] = getFluidStack(recipe[i], recipe[i + 1]); + } + + TinkerRegistry.registerAlloy(outputStack, inputs); + return TCCode.SUCCESS; + } + + /** + * Variant for backwards compat + * + * @param output + * Oredict name of output fluid + * @param outputQty + * Amount of output produced + * @param inputNames + * Array of oredict names of inputs + * @param inputQty + * Array of quantities matching those inputs + * @return TCCode.SUCCESS or TCCode.FAILURE_PARAMETER_ERROR + */ + public TCCode registerAlloy(@Nonnull final String output, @Nonnull final int outputQty, + @Nonnull final String[] inputNames, @Nonnull final int[] inputQty) { + if (inputNames.length != inputQty.length) { + return TCCode.FAILURE_PARAMETER_ERROR; + } + + final FluidStack outputs = getFluidStack(output, outputQty); + final FluidStack[] inputs = new FluidStack[inputNames.length]; + + for (int i = 0; i < inputNames.length; i++) { + inputs[i] = getFluidStack(inputNames[i], inputQty[i]); + } + TinkerRegistry.registerAlloy(outputs, inputs); + return TCCode.SUCCESS; + } + + /** + * Register the ability to cast a block of material from a specified quantity of a fluid + * + * @param block + * Block that is created + * @param source + * Fluid being poured + * @param sourceQty + * Amount of fluid being poured + * @return Any TCCode that represents an error or TCCode.SUCCESS + */ + public TCCode registerBasin(@Nonnull final Block block, @Nonnull final Fluid source, + @Nonnull final int sourceQty) { + if (sourceQty == 0) { + return TCCode.FAILURE_PARAMETER_ERROR; + } + TinkerRegistry.registerBasinCasting(new ItemStack(block), null, source, + sourceQty); + return TCCode.SUCCESS; + } + + /** + * Register a casting table piece. This is either an ingot, a nugget or a gem + * + * @param output + * Item that is output + * @param source + * source material/fluid + * @param sourceQty + * Amount + * @return Any TCCode that represents an error or TCCode.SUCCESS + */ + public TCCode registerCasting(@Nonnull final Item output, @Nonnull final Fluid source, + @Nonnull final int sourceQty) { + if (sourceQty == 0) { + return TCCode.FAILURE_PARAMETER_ERROR; + } + TinkerRegistry.registerTableCasting(new ItemStack(output), null, source, + sourceQty); + return TCCode.SUCCESS; + } + + /** + * Register all the fluids a MetalMaterial might melt into, at the amountPer amount + * + * @param base + * MetalMaterial that is the base for this fluid + * @param amountPer + * How much per ingot/single item of the material + * @return TCCode.SUCCESS - at this point there are no failure points in this routine + */ + public TCCode registerFluid(@Nonnull final MMDMaterial base, @Nonnull final int amountPer) { + final String materialName = base.getName(); + final Fluid output = FluidRegistry.getFluid(materialName); + final String oreDictName = base.getCapitalizedName(); + + // hacky fix for Coal being itemCoal and not ingotCoal + if (MaterialNames.COAL.equals(base.getName())) { + this.meltingHelper("itemCoal", output, amountPer); + } + + this.meltingHelper(Oredicts.ORE + oreDictName, output, amountPer * 2); + this.meltingHelper(Oredicts.BLOCK + oreDictName, output, amountPer * 9); + this.meltingHelper(Oredicts.INGOT + oreDictName, output, amountPer); + this.meltingHelper(Oredicts.NUGGET + oreDictName, output, amountPer / 9); + this.meltingHelper(Oredicts.DUST + oreDictName, output, amountPer); + this.meltingHelper(Oredicts.DUST_SMALL + oreDictName, output, amountPer / 9); + if (base.hasBlock(Names.NETHERORE)) { + this.meltingHelper(base.getBlock(Names.NETHERORE), output, amountPer * 4); + } + + if (base.hasBlock(Names.ENDORE)) { + this.meltingHelper(base.getBlock(Names.ENDORE), output, amountPer * 4); + } + + if (base.hasBlock(Names.SLAB)) { + this.meltingHelper(base.getBlock(Names.SLAB), output, + (amountPer * 4) + (amountPer / 2)); + } + + if (base.hasBlock(Names.WALL)) { + this.meltingHelper(base.getBlock(Names.WALL), output, amountPer * 9); + } + + if (base.hasItem(Names.BOOTS)) { + this.meltingHelper(base.getItem(Names.BOOTS), output, amountPer * 4); + } + + if (base.hasItem(Names.HELMET)) { + this.meltingHelper(base.getItem(Names.HELMET), output, amountPer * 5); + } + + if (base.hasItem(Names.CHESTPLATE)) { + this.meltingHelper(base.getItem(Names.CHESTPLATE), output, amountPer * 8); + } + + if (base.hasItem(Names.LEGGINGS)) { + this.meltingHelper(base.getItem(Names.LEGGINGS), output, amountPer * 7); + } + + if (base.hasItem(Names.SHEARS)) { + this.meltingHelper(base.getItem(Names.SHEARS), output, amountPer * 2); + } + + if (base.hasBlock(Names.PRESSURE_PLATE)) { + this.meltingHelper(base.getBlock(Names.PRESSURE_PLATE), output, amountPer * 2); + } + + if (base.hasBlock(Names.BARS)) { + this.meltingHelper(base.getBlock(Names.BARS), output, ((amountPer / 9) * 3) + 6); // Fun + // math + } + + if (base.hasItem(Names.ROD)) { + this.meltingHelper(base.getItem(Names.ROD), output, amountPer / 2); + } + + if (base.hasItem(Names.DOOR)) { + this.meltingHelper(base.getItem(Names.DOOR), output, amountPer * 2); + } + + if (base.hasBlock(Names.TRAPDOOR)) { + this.meltingHelper(base.getBlock(Names.TRAPDOOR), output, amountPer * 4); + } + + if (base.hasBlock(Names.BUTTON)) { + this.meltingHelper(base.getBlock(Names.BUTTON), output, (amountPer / 9) * 2); + } + + return TCCode.SUCCESS; + } + + /* + * The following functions are helpers to help make the registerMelting function a touch less + * complex + */ + private void meltingHelper(@Nonnull final Item item, @Nonnull final Fluid output, + @Nonnull final int amount) { + if (item != null) { + TinkerRegistry.registerMelting(item, output, amount); + // } else { + // BaseMetals.logger.error("Attempted to register null item for fluid " + + // output.getName()); + } + } + + private void meltingHelper(@Nonnull final String itemName, @Nonnull final Fluid output, + @Nonnull final int amount) { + if (!OreDictionary.getOres(itemName).isEmpty()) { + TinkerRegistry.registerMelting(itemName, output, amount); + // } else { + // BaseMetals.logger.error("Attempted to register oredict for missing item " + + // itemName); + } + } + + private void meltingHelper(@Nonnull final Block block, @Nonnull final Fluid output, + @Nonnull final int amount) { + if (block != null) { + TinkerRegistry.registerMelting(block, output, amount); + // } else { + // BaseMetals.logger.error("Attempted to register null block for fluid " + + // output.getName()); + } + } + + public void integrateRecipes() { + for (final MaterialIntegration m : this.integrations) { + m.integrateRecipes(); + } + } +} diff --git a/src/main/java/com/mcmoddev/lib/integration/plugins/tinkers/TraitRegistry.java b/src/main/java/com/mcmoddev/lib/integration/plugins/tinkers/TraitRegistry.java index b6c7eba48..f486aa1d7 100755 --- a/src/main/java/com/mcmoddev/lib/integration/plugins/tinkers/TraitRegistry.java +++ b/src/main/java/com/mcmoddev/lib/integration/plugins/tinkers/TraitRegistry.java @@ -1,73 +1,73 @@ -package com.mcmoddev.lib.integration.plugins.tinkers; - -import java.lang.reflect.Field; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; - -import javax.annotation.Nonnull; - -import com.mcmoddev.basemetals.BaseMetals; - -import slimeknights.tconstruct.library.traits.ITrait; - -public class TraitRegistry { - - private static final Map registeredTraits = new HashMap<>(); - - private TraitRegistry() { - throw new IllegalAccessError("Not an instantiable class"); - } - - public static void addTrait(@Nonnull final String name, @Nonnull final ITrait trait) { - registeredTraits.put(name, trait); - } - - public static boolean hasTrait(@Nonnull final String name) { - return registeredTraits.keySet().contains(name); - } - - public static ITrait getTrait(@Nonnull final String name) { - return registeredTraits.get(name); - } - - public static ITrait get(@Nonnull final String name) { - return getTrait(name); - } - - public static void initTiCTraits() { - final Field[] fields = slimeknights.tconstruct.tools.TinkerTraits.class.getDeclaredFields(); - registerFieldArray(fields); - } - - public static void initMetalsTraits() { - final Field[] fields = com.mcmoddev.lib.integration.plugins.tinkers.traits.MMDTraits.class - .getDeclaredFields(); - registerFieldArray(fields); - } - - public static void initTAIGATraits() { - final Field[] fields = com.sosnitzka.taiga.MaterialTraits.class.getDeclaredFields(); - registerFieldArray(fields); - } - - private static void registerFieldArray(@Nonnull final Field[] regList) { - for (final Field f : regList) { - final Class clazz = f.getType(); - try { - registeredTraits.put(f.getName(), (ITrait) f.get(clazz)); - } catch (final Exception ex) { - BaseMetals.logger - .error("tried to register trait " + f.getName() + ", caught exception", ex); - } - } - } - - public static void dumpRegistry() { - for (final Entry e : registeredTraits.entrySet()) { - final String t = String.format("BaseMetals-TCon> Trait: %s - class %s", e.getKey(), - e.getValue().getClass().getName()); - BaseMetals.logger.info(t); - } - } -} +package com.mcmoddev.lib.integration.plugins.tinkers; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import javax.annotation.Nonnull; + +import zone.moddev.mc.basemetals.BaseMetals; + +import slimeknights.tconstruct.library.traits.ITrait; + +public class TraitRegistry { + + private static final Map registeredTraits = new HashMap<>(); + + private TraitRegistry() { + throw new IllegalAccessError("Not an instantiable class"); + } + + public static void addTrait(@Nonnull final String name, @Nonnull final ITrait trait) { + registeredTraits.put(name, trait); + } + + public static boolean hasTrait(@Nonnull final String name) { + return registeredTraits.keySet().contains(name); + } + + public static ITrait getTrait(@Nonnull final String name) { + return registeredTraits.get(name); + } + + public static ITrait get(@Nonnull final String name) { + return getTrait(name); + } + + public static void initTiCTraits() { + final Field[] fields = slimeknights.tconstruct.tools.TinkerTraits.class.getDeclaredFields(); + registerFieldArray(fields); + } + + public static void initMetalsTraits() { + final Field[] fields = com.mcmoddev.lib.integration.plugins.tinkers.traits.MMDTraits.class + .getDeclaredFields(); + registerFieldArray(fields); + } + + public static void initTAIGATraits() { + final Field[] fields = com.sosnitzka.taiga.MaterialTraits.class.getDeclaredFields(); + registerFieldArray(fields); + } + + private static void registerFieldArray(@Nonnull final Field[] regList) { + for (final Field f : regList) { + final Class clazz = f.getType(); + try { + registeredTraits.put(f.getName(), (ITrait) f.get(clazz)); + } catch (final Exception ex) { + BaseMetals.logger + .error("tried to register trait " + f.getName() + ", caught exception", ex); + } + } + } + + public static void dumpRegistry() { + for (final Entry e : registeredTraits.entrySet()) { + final String t = String.format("BaseMetals-TCon> Trait: %s - class %s", e.getKey(), + e.getValue().getClass().getName()); + BaseMetals.logger.info(t); + } + } +} diff --git a/src/main/java/com/mcmoddev/lib/item/ItemCrossbow.java b/src/main/java/com/mcmoddev/lib/item/ItemCrossbow.java index cc925d9d6..6143c2d8b 100644 --- a/src/main/java/com/mcmoddev/lib/item/ItemCrossbow.java +++ b/src/main/java/com/mcmoddev/lib/item/ItemCrossbow.java @@ -2,7 +2,7 @@ import javax.annotation.Nullable; -import com.mcmoddev.basemetals.data.MaterialNames; +import zone.moddev.mc.basemetals.data.MaterialNames; import com.mcmoddev.lib.data.Names; import com.mcmoddev.lib.entity.EntityCustomBolt; import com.mcmoddev.lib.init.Materials; diff --git a/src/main/java/com/mcmoddev/lib/item/ItemMMDArmor.java b/src/main/java/com/mcmoddev/lib/item/ItemMMDArmor.java index ac1445000..93d3d180c 100644 --- a/src/main/java/com/mcmoddev/lib/item/ItemMMDArmor.java +++ b/src/main/java/com/mcmoddev/lib/item/ItemMMDArmor.java @@ -8,8 +8,8 @@ import javax.annotation.Nonnull; -import com.mcmoddev.basemetals.BaseMetals; -import com.mcmoddev.basemetals.items.MMDToolEffects; +import zone.moddev.mc.basemetals.BaseMetals; +import zone.moddev.mc.basemetals.items.MMDToolEffects; import com.mcmoddev.lib.data.Names; import com.mcmoddev.lib.init.Materials; import com.mcmoddev.lib.material.IMMDObject; diff --git a/src/main/java/com/mcmoddev/lib/item/ItemMMDAxe.java b/src/main/java/com/mcmoddev/lib/item/ItemMMDAxe.java index c0f881c75..064d70b73 100644 --- a/src/main/java/com/mcmoddev/lib/item/ItemMMDAxe.java +++ b/src/main/java/com/mcmoddev/lib/item/ItemMMDAxe.java @@ -2,7 +2,7 @@ import java.util.List; -import com.mcmoddev.basemetals.items.MMDToolEffects; +import zone.moddev.mc.basemetals.items.MMDToolEffects; import com.mcmoddev.lib.data.MaterialStats; import com.mcmoddev.lib.init.Materials; import com.mcmoddev.lib.material.IMMDObject; diff --git a/src/main/java/com/mcmoddev/lib/item/ItemMMDBow.java b/src/main/java/com/mcmoddev/lib/item/ItemMMDBow.java index 3cf989df5..e081605f1 100644 --- a/src/main/java/com/mcmoddev/lib/item/ItemMMDBow.java +++ b/src/main/java/com/mcmoddev/lib/item/ItemMMDBow.java @@ -2,7 +2,7 @@ import java.util.List; -import com.mcmoddev.basemetals.items.MMDToolEffects; +import zone.moddev.mc.basemetals.items.MMDToolEffects; import com.mcmoddev.lib.material.IMMDObject; import com.mcmoddev.lib.material.MMDMaterial; diff --git a/src/main/java/com/mcmoddev/lib/item/ItemMMDCrackHammer.java b/src/main/java/com/mcmoddev/lib/item/ItemMMDCrackHammer.java index 1db2d47a1..51f7d087f 100644 --- a/src/main/java/com/mcmoddev/lib/item/ItemMMDCrackHammer.java +++ b/src/main/java/com/mcmoddev/lib/item/ItemMMDCrackHammer.java @@ -4,8 +4,8 @@ import java.util.List; import java.util.Set; -import com.mcmoddev.basemetals.data.AchievementNames; -import com.mcmoddev.basemetals.items.MMDToolEffects; +import zone.moddev.mc.basemetals.data.AchievementNames; +import zone.moddev.mc.basemetals.items.MMDToolEffects; import com.mcmoddev.lib.data.Names; import com.mcmoddev.lib.init.Achievements; import com.mcmoddev.lib.init.Materials; diff --git a/src/main/java/com/mcmoddev/lib/item/ItemMMDCrossbow.java b/src/main/java/com/mcmoddev/lib/item/ItemMMDCrossbow.java index 96cdd7e0e..5e8c571b1 100644 --- a/src/main/java/com/mcmoddev/lib/item/ItemMMDCrossbow.java +++ b/src/main/java/com/mcmoddev/lib/item/ItemMMDCrossbow.java @@ -2,7 +2,7 @@ import java.util.List; -import com.mcmoddev.basemetals.items.MMDToolEffects; +import zone.moddev.mc.basemetals.items.MMDToolEffects; import com.mcmoddev.lib.material.IMMDObject; import com.mcmoddev.lib.material.MMDMaterial; diff --git a/src/main/java/com/mcmoddev/lib/item/ItemMMDFishingRod.java b/src/main/java/com/mcmoddev/lib/item/ItemMMDFishingRod.java index ffbcedbab..cea9bd1dd 100644 --- a/src/main/java/com/mcmoddev/lib/item/ItemMMDFishingRod.java +++ b/src/main/java/com/mcmoddev/lib/item/ItemMMDFishingRod.java @@ -2,7 +2,7 @@ import java.util.List; -import com.mcmoddev.basemetals.items.MMDToolEffects; +import zone.moddev.mc.basemetals.items.MMDToolEffects; import com.mcmoddev.lib.material.IMMDObject; import com.mcmoddev.lib.material.MMDMaterial; diff --git a/src/main/java/com/mcmoddev/lib/item/ItemMMDHoe.java b/src/main/java/com/mcmoddev/lib/item/ItemMMDHoe.java index 196ad8045..b2d7ff47b 100644 --- a/src/main/java/com/mcmoddev/lib/item/ItemMMDHoe.java +++ b/src/main/java/com/mcmoddev/lib/item/ItemMMDHoe.java @@ -3,7 +3,7 @@ import java.util.HashSet; import java.util.Set; -import com.mcmoddev.basemetals.items.MMDToolEffects; +import zone.moddev.mc.basemetals.items.MMDToolEffects; import com.mcmoddev.lib.data.Names; import com.mcmoddev.lib.init.Materials; import com.mcmoddev.lib.material.IMMDObject; diff --git a/src/main/java/com/mcmoddev/lib/item/ItemMMDHorseArmor.java b/src/main/java/com/mcmoddev/lib/item/ItemMMDHorseArmor.java index 205aeaa1a..bdbf57970 100644 --- a/src/main/java/com/mcmoddev/lib/item/ItemMMDHorseArmor.java +++ b/src/main/java/com/mcmoddev/lib/item/ItemMMDHorseArmor.java @@ -2,7 +2,7 @@ import java.util.Locale; -import com.mcmoddev.basemetals.BaseMetals; +import zone.moddev.mc.basemetals.BaseMetals; import com.mcmoddev.lib.common.item.IHorseArmor; import com.mcmoddev.lib.material.MMDMaterial; diff --git a/src/main/java/com/mcmoddev/lib/item/ItemMMDPickaxe.java b/src/main/java/com/mcmoddev/lib/item/ItemMMDPickaxe.java index 088eca2ae..7a72d9e0d 100644 --- a/src/main/java/com/mcmoddev/lib/item/ItemMMDPickaxe.java +++ b/src/main/java/com/mcmoddev/lib/item/ItemMMDPickaxe.java @@ -4,7 +4,7 @@ import java.util.List; import java.util.Set; -import com.mcmoddev.basemetals.items.MMDToolEffects; +import zone.moddev.mc.basemetals.items.MMDToolEffects; import com.mcmoddev.lib.data.Names; import com.mcmoddev.lib.init.Materials; import com.mcmoddev.lib.material.IMMDObject; diff --git a/src/main/java/com/mcmoddev/lib/item/ItemMMDShears.java b/src/main/java/com/mcmoddev/lib/item/ItemMMDShears.java index 14b367a22..b00210a43 100644 --- a/src/main/java/com/mcmoddev/lib/item/ItemMMDShears.java +++ b/src/main/java/com/mcmoddev/lib/item/ItemMMDShears.java @@ -2,7 +2,7 @@ import java.util.List; -import com.mcmoddev.basemetals.items.MMDToolEffects; +import zone.moddev.mc.basemetals.items.MMDToolEffects; import com.mcmoddev.lib.material.IMMDObject; import com.mcmoddev.lib.material.MMDMaterial; diff --git a/src/main/java/com/mcmoddev/lib/item/ItemMMDShovel.java b/src/main/java/com/mcmoddev/lib/item/ItemMMDShovel.java index 579bed80a..5df2fefd2 100644 --- a/src/main/java/com/mcmoddev/lib/item/ItemMMDShovel.java +++ b/src/main/java/com/mcmoddev/lib/item/ItemMMDShovel.java @@ -2,7 +2,7 @@ import java.util.List; -import com.mcmoddev.basemetals.items.MMDToolEffects; +import zone.moddev.mc.basemetals.items.MMDToolEffects; import com.mcmoddev.lib.init.Materials; import com.mcmoddev.lib.material.IMMDObject; import com.mcmoddev.lib.material.MMDMaterial; diff --git a/src/main/java/com/mcmoddev/lib/item/ItemMMDSickle.java b/src/main/java/com/mcmoddev/lib/item/ItemMMDSickle.java index ad796c091..f9a10a617 100644 --- a/src/main/java/com/mcmoddev/lib/item/ItemMMDSickle.java +++ b/src/main/java/com/mcmoddev/lib/item/ItemMMDSickle.java @@ -9,7 +9,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Multimap; -import com.mcmoddev.basemetals.BaseMetals; +import zone.moddev.mc.basemetals.BaseMetals; import com.mcmoddev.lib.init.Materials; import com.mcmoddev.lib.material.IMMDObject; import com.mcmoddev.lib.material.MMDMaterial; diff --git a/src/main/java/com/mcmoddev/lib/item/ItemMMDSword.java b/src/main/java/com/mcmoddev/lib/item/ItemMMDSword.java index d1bd68d3e..3898f4f35 100644 --- a/src/main/java/com/mcmoddev/lib/item/ItemMMDSword.java +++ b/src/main/java/com/mcmoddev/lib/item/ItemMMDSword.java @@ -2,7 +2,7 @@ import java.util.List; -import com.mcmoddev.basemetals.items.MMDToolEffects; +import zone.moddev.mc.basemetals.items.MMDToolEffects; import com.mcmoddev.lib.init.Materials; import com.mcmoddev.lib.material.IMMDObject; import com.mcmoddev.lib.material.MMDMaterial; diff --git a/src/main/java/com/mcmoddev/lib/jei/BaseMetalsJEIPlugin.java b/src/main/java/com/mcmoddev/lib/jei/BaseMetalsJEIPlugin.java index 97b78bfee..b05569bb4 100644 --- a/src/main/java/com/mcmoddev/lib/jei/BaseMetalsJEIPlugin.java +++ b/src/main/java/com/mcmoddev/lib/jei/BaseMetalsJEIPlugin.java @@ -2,7 +2,7 @@ import java.util.stream.Collectors; -import com.mcmoddev.basemetals.BaseMetals; +import zone.moddev.mc.basemetals.BaseMetals; import com.mcmoddev.lib.registry.CrusherRecipeRegistry; import com.mcmoddev.lib.registry.recipe.ICrusherRecipe; diff --git a/src/main/java/com/mcmoddev/lib/jei/ICrusherRecipeCategory.java b/src/main/java/com/mcmoddev/lib/jei/ICrusherRecipeCategory.java index 8cd24b19c..5b355c993 100644 --- a/src/main/java/com/mcmoddev/lib/jei/ICrusherRecipeCategory.java +++ b/src/main/java/com/mcmoddev/lib/jei/ICrusherRecipeCategory.java @@ -5,7 +5,7 @@ import javax.annotation.Nonnull; -import com.mcmoddev.basemetals.BaseMetals; +import zone.moddev.mc.basemetals.BaseMetals; import mezz.jei.api.IGuiHelper; import mezz.jei.api.gui.IDrawable; diff --git a/src/main/java/com/mcmoddev/lib/material/MMDMaterial.java b/src/main/java/com/mcmoddev/lib/material/MMDMaterial.java index ae67651a1..124e34517 100644 --- a/src/main/java/com/mcmoddev/lib/material/MMDMaterial.java +++ b/src/main/java/com/mcmoddev/lib/material/MMDMaterial.java @@ -12,7 +12,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.mcmoddev.basemetals.BaseMetals; +import zone.moddev.mc.basemetals.BaseMetals; import com.mcmoddev.lib.data.MaterialStats; import com.mcmoddev.lib.data.Names; diff --git a/src/main/java/com/mcmoddev/lib/nei/CrusherRecipeHandler.java b/src/main/java/com/mcmoddev/lib/nei/CrusherRecipeHandler.java index 8aa0de1fe..bc46eb498 100644 --- a/src/main/java/com/mcmoddev/lib/nei/CrusherRecipeHandler.java +++ b/src/main/java/com/mcmoddev/lib/nei/CrusherRecipeHandler.java @@ -8,7 +8,7 @@ import org.lwjgl.opengl.GL11; -import com.mcmoddev.basemetals.BaseMetals; +import zone.moddev.mc.basemetals.BaseMetals; import com.mcmoddev.lib.registry.CrusherRecipeRegistry; import com.mcmoddev.lib.registry.recipe.ICrusherRecipe; diff --git a/src/main/java/com/mcmoddev/lib/nei/NEIBaseMetalsConfig.java b/src/main/java/com/mcmoddev/lib/nei/NEIBaseMetalsConfig.java index 5479405f1..f6d20dd55 100644 --- a/src/main/java/com/mcmoddev/lib/nei/NEIBaseMetalsConfig.java +++ b/src/main/java/com/mcmoddev/lib/nei/NEIBaseMetalsConfig.java @@ -1,6 +1,6 @@ package com.mcmoddev.lib.nei; -import com.mcmoddev.basemetals.BaseMetals; +import zone.moddev.mc.basemetals.BaseMetals; import codechicken.nei.api.IConfigureNEI; diff --git a/src/main/java/com/mcmoddev/lib/oregen/FallbackGenerator.java b/src/main/java/com/mcmoddev/lib/oregen/FallbackGenerator.java deleted file mode 100644 index 5690f45b3..000000000 --- a/src/main/java/com/mcmoddev/lib/oregen/FallbackGenerator.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.mcmoddev.lib.oregen; - -import java.util.ArrayList; -import java.util.List; -import java.util.Random; - -import net.minecraft.util.math.BlockPos; -import net.minecraft.world.World; -import net.minecraft.world.chunk.IChunkGenerator; -import net.minecraft.world.chunk.IChunkProvider; -import net.minecraft.world.gen.feature.WorldGenMinable; -import net.minecraftforge.fml.common.IWorldGenerator; - -public class FallbackGenerator implements IWorldGenerator { - - @Override - public void generate(final Random random, final int chunkX, final int chunkZ, final World world, - final IChunkGenerator chunkGenerator, final IChunkProvider chunkProvider) { - - final int dimension = world.provider.getDimension(); - final List ores = new ArrayList<>( - FallbackGeneratorData.getInstance().getSpawnsForDimension(dimension)); - if ((dimension != -1) && (dimension != 1)) { - ores.addAll( - FallbackGeneratorData.getInstance().getSpawnsForDimension(Integer.MIN_VALUE)); - } - - final int minY = this.getMinYForDimension(dimension); - final int maxY = this.getMaxYForDimension(dimension); - - for (final WorldGenMinable ore : ores) { - for (int i = 0; i < 8; i++) { - final int pos_x = (chunkX * 16) + random.nextInt(16); - final int pos_z = (chunkZ * 16) + random.nextInt(16); - final int pos_y = minY + random.nextInt((maxY - minY)); - final BlockPos p = new BlockPos(pos_x, pos_y, pos_z); - ore.generate(world, random, p); - } - } - } - - private int getMaxYForDimension(final int dimension) { - return dimension == -1 ? 126 : dimension == 1 ? 256 : 80; - } - - private int getMinYForDimension(final int dimension) { - return (dimension == -1) || (dimension == 1) ? 0 : 16; - } -} diff --git a/src/main/java/com/mcmoddev/lib/oregen/FallbackGeneratorData.java b/src/main/java/com/mcmoddev/lib/oregen/FallbackGeneratorData.java deleted file mode 100644 index 34e319f4f..000000000 --- a/src/main/java/com/mcmoddev/lib/oregen/FallbackGeneratorData.java +++ /dev/null @@ -1,155 +0,0 @@ -package com.mcmoddev.lib.oregen; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.TreeMap; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import com.google.common.base.Predicate; -import com.google.common.collect.ImmutableList; -import com.mcmoddev.lib.init.Materials; -import com.mcmoddev.lib.material.MMDMaterial; - -import net.minecraft.block.Block; -import net.minecraft.block.BlockStone; -import net.minecraft.block.state.IBlockState; -import net.minecraft.init.Blocks; -import net.minecraft.world.gen.feature.WorldGenMinable; - -public class FallbackGeneratorData { - - private static final Map>> materials = new TreeMap<>(); - private static final FallbackGeneratorData INSTANCE = new FallbackGeneratorData(); - private static final Map> spawns = new HashMap<>(); - - private FallbackGeneratorData() { - // blank - } - - /** - * - * @param materialName - * @param blockName - * @param dimension - */ - public void addMaterial(@Nonnull final String materialName, @Nonnull final String blockName, - @Nullable final Integer dimension) { - final MMDMaterial mat = Materials.getMaterialByName(materialName); - final int targetDim = dimension != null ? dimension : Integer.MIN_VALUE; - final Map> blockMap = materials.getOrDefault(targetDim, - new HashMap>()); - final List blocks = blockMap.getOrDefault(mat, new ArrayList<>()); - - if (!blocks.contains(blockName)) { - blocks.add(blockName); - } - - blockMap.put(mat, blocks); - materials.put(targetDim, blockMap); - } - - static class StonePredicate implements Predicate { - - private StonePredicate() { - } - - @Override - public boolean apply(final IBlockState comp) { - if (comp != null && comp.getBlock() == Blocks.STONE) { - final BlockStone.EnumType block = comp.getValue(BlockStone.VARIANT); - return isNatural(block); - } - return false; - } - - private boolean isNatural(BlockStone.EnumType block) { - switch(block) { - case STONE: - case GRANITE: - case ANDESITE: - case DIORITE: - return true; - default: - return false; - } - } - } - - static class NetherPredicate implements Predicate { - - private NetherPredicate() { - } - - @Override - public boolean apply(final IBlockState comp) { - if (comp != null) { - final Block b = comp.getBlock(); - return b.equals(Blocks.NETHERRACK); - } - return false; - } - } - - static class EndPredicate implements Predicate { - - private EndPredicate() { - } - - @Override - public boolean apply(final IBlockState comp) { - if (comp != null) { - final Block b = comp.getBlock(); - return b.equals(Blocks.END_STONE); - } - return false; - } - } - - /** - * - */ - public void setup() { - for (final Entry>> matMap : materials.entrySet()) { - final Map> mats = matMap.getValue(); - final List spawnList = spawns.getOrDefault(matMap.getKey(), - new ArrayList<>()); - final Predicate pred = this.getPredicateForDimension(matMap.getKey()); - - for (final Entry> matList : mats.entrySet()) { - final MMDMaterial mat = matList.getKey(); - matList.getValue().stream() - .forEach(blockName -> spawnList - .add(new WorldGenMinable(mat.getBlock(blockName).getDefaultState(), - mat.getSpawnSize() > 0 ? mat.getSpawnSize() : 8, pred))); - } - spawns.put(matMap.getKey(), spawnList); - } - } - - private Predicate getPredicateForDimension(final Integer key) { - switch (key) { - case -1: - return new NetherPredicate(); - case 1: - return new EndPredicate(); - case 0: - case Integer.MIN_VALUE: - default: - return new StonePredicate(); - } - } - - public List getSpawnsForDimension(final int dimension) { - return ImmutableList.copyOf(spawns.getOrDefault(dimension, Collections.emptyList())); - } - - public static FallbackGeneratorData getInstance() { - return INSTANCE; - } -} diff --git a/src/main/java/com/mcmoddev/lib/recipe/ShieldUpgradeRecipe.java b/src/main/java/com/mcmoddev/lib/recipe/ShieldUpgradeRecipe.java index 3f6925cd8..49051a646 100755 --- a/src/main/java/com/mcmoddev/lib/recipe/ShieldUpgradeRecipe.java +++ b/src/main/java/com/mcmoddev/lib/recipe/ShieldUpgradeRecipe.java @@ -1,178 +1,178 @@ -package com.mcmoddev.lib.recipe; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -import com.mcmoddev.basemetals.BaseMetals; -import com.mcmoddev.lib.data.MaterialStats; -import com.mcmoddev.lib.data.Names; -import com.mcmoddev.lib.init.Materials; -import com.mcmoddev.lib.material.MMDMaterial; -import com.mcmoddev.lib.util.Oredicts; - -import net.minecraft.enchantment.Enchantment; -import net.minecraft.enchantment.EnchantmentHelper; -import net.minecraft.inventory.InventoryCrafting; -import net.minecraft.item.ItemStack; -import net.minecraft.item.crafting.IRecipe; -import net.minecraft.item.crafting.RecipeRepairItem; -import net.minecraft.world.World; -import net.minecraftforge.oredict.OreDictionary; - -public class ShieldUpgradeRecipe extends RecipeRepairItem implements IRecipe { - - private String materialName; - - public ShieldUpgradeRecipe(final MMDMaterial input) { - this.materialName = input.getName(); - } - - @Override - public boolean matches(final InventoryCrafting inv, final World worldIn) { - final List upgradeMats = new ArrayList<>(); - final Collection allmats = Materials.getAllMaterials(); - final MMDMaterial input = Materials.getMaterialByName(this.materialName); - final ItemStack base = new ItemStack(input.getItem(Names.SHIELD), 1, 0); - - for (final MMDMaterial mat : allmats) { - if ((mat.getStat(MaterialStats.HARDNESS) >= input.getStat(MaterialStats.HARDNESS)) - && mat.getName().equals(this.materialName)) { - final List mats = OreDictionary - .getOres(Oredicts.PLATE + mat.getCapitalizedName()); - upgradeMats.addAll(mats); - } - } - - final boolean[] matches = new boolean[] { false, false }; - - for (int i = 0; i < inv.getSizeInventory(); i++) { - final ItemStack curItem = inv.getStackInSlot(i); - - if (curItem != null) { - final ItemStack comp = new ItemStack(curItem.getItem(), 1, curItem.getItemDamage()); - if (OreDictionary.itemMatches(base, comp, false)) { - matches[0] = true; - } else if (OreDictionary.containsMatch(false, upgradeMats, comp)) { - matches[1] = true; - } - } - } - return matches[0] ? matches[1] : false; - } - - @Override - public ItemStack getCraftingResult(final InventoryCrafting inv) { - final Map> plates = new HashMap<>(); - - final Collection allmats = Materials.getAllMaterials(); - final int hardness = ((Float) Materials.getMaterialByName(this.materialName) - .getStat(MaterialStats.HARDNESS)).intValue(); - - for (final MMDMaterial mat : allmats) { - if ((mat.getStat(MaterialStats.HARDNESS) >= hardness) - && (!mat.getName().equals(this.materialName))) { - final List mats = OreDictionary - .getOres(Oredicts.PLATE + mat.getCapitalizedName()); - plates.put(mat.getName(), mats); - } - } - - ItemStack plateMatched = null; - Map enchants = Collections.emptyMap(); - final ItemStack matcher = new ItemStack( - Materials.getMaterialByName(this.materialName).getItem(Names.SHIELD), 1, 0); - - for (int i = 0; i < inv.getSizeInventory(); i++) { - final ItemStack curItem = inv.getStackInSlot(i); - - if (curItem != null) { - final ItemStack comp = new ItemStack(curItem.getItem(), 1, curItem.getMetadata()); - for (final Entry> ent : plates.entrySet()) { - if (OreDictionary.containsMatch(false, ent.getValue(), comp)) { - plateMatched = new ItemStack( - Materials.getMaterialByName(ent.getKey().toLowerCase()) - .getItem(Names.SHIELD), - 1, 0); - } - } - if (OreDictionary.itemMatches(matcher, comp, false)) { - enchants = EnchantmentHelper.getEnchantments(curItem); - } - } - } - - BaseMetals.logger.debug("Adding %d enchantments to output item", enchants.size()); - EnchantmentHelper.setEnchantments(enchants, plateMatched); - return plateMatched; - } - - private MMDMaterial getUpgradeMat(final InventoryCrafting inv) { - for (int i = 0; i < inv.getSizeInventory(); i++) { - final ItemStack curItem = inv.getStackInSlot(i); - - if (curItem != null) { - for (final MMDMaterial e : Materials.getAllMaterials()) { - if (OreDictionary.containsMatch(false, - OreDictionary.getOres(Oredicts.PLATE + e.getName()), curItem)) { - return e; - } - } - } - } - return Materials.getMaterialByName(this.materialName); - } - - private ItemStack findBaseItem(final InventoryCrafting inv) { - final ItemStack input = new ItemStack( - Materials.getMaterialByName(this.materialName).getItem(Names.SHIELD), 1, 0); - - for (int i = 0; i < inv.getSizeInventory(); i++) { - final ItemStack a = inv.getStackInSlot(i); - if (a != null) { - final ItemStack comp = new ItemStack(a.getItem(), 1, a.getMetadata()); - if (OreDictionary.itemMatches(input, comp, false)) { - return a; - } - } - } - return null; - } - - private int getEnchantCount(final InventoryCrafting inv) { - final ItemStack k = this.findBaseItem(inv); - if (k != null) { - return EnchantmentHelper.getEnchantments(this.findBaseItem(inv)).size(); - } else { - return 0; - } - } - - /** - * - * @param inv - * @return - */ - public int getCost(final InventoryCrafting inv) { - final MMDMaterial shieldMat = Materials.getMaterialByName(this.materialName); - final MMDMaterial upgradeMat = this.getUpgradeMat(inv); - - final float hardDiff = upgradeMat.getStat(MaterialStats.HARDNESS) - - shieldMat.getStat(MaterialStats.HARDNESS); - final int enchantCount = this.getEnchantCount(inv); - - final float diffVal = hardDiff * 5; - final float enchantVal = upgradeMat.getStat(MaterialStats.MAGICAFFINITY) * enchantCount; - Float finalVal = diffVal + enchantVal; - - if (finalVal < 5.0f) { - finalVal = 5.0f; - } - - return finalVal.intValue(); - } -} +package com.mcmoddev.lib.recipe; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import zone.moddev.mc.basemetals.BaseMetals; +import com.mcmoddev.lib.data.MaterialStats; +import com.mcmoddev.lib.data.Names; +import com.mcmoddev.lib.init.Materials; +import com.mcmoddev.lib.material.MMDMaterial; +import com.mcmoddev.lib.util.Oredicts; + +import net.minecraft.enchantment.Enchantment; +import net.minecraft.enchantment.EnchantmentHelper; +import net.minecraft.inventory.InventoryCrafting; +import net.minecraft.item.ItemStack; +import net.minecraft.item.crafting.IRecipe; +import net.minecraft.item.crafting.RecipeRepairItem; +import net.minecraft.world.World; +import net.minecraftforge.oredict.OreDictionary; + +public class ShieldUpgradeRecipe extends RecipeRepairItem implements IRecipe { + + private String materialName; + + public ShieldUpgradeRecipe(final MMDMaterial input) { + this.materialName = input.getName(); + } + + @Override + public boolean matches(final InventoryCrafting inv, final World worldIn) { + final List upgradeMats = new ArrayList<>(); + final Collection allmats = Materials.getAllMaterials(); + final MMDMaterial input = Materials.getMaterialByName(this.materialName); + final ItemStack base = new ItemStack(input.getItem(Names.SHIELD), 1, 0); + + for (final MMDMaterial mat : allmats) { + if ((mat.getStat(MaterialStats.HARDNESS) >= input.getStat(MaterialStats.HARDNESS)) + && mat.getName().equals(this.materialName)) { + final List mats = OreDictionary + .getOres(Oredicts.PLATE + mat.getCapitalizedName()); + upgradeMats.addAll(mats); + } + } + + final boolean[] matches = new boolean[] { false, false }; + + for (int i = 0; i < inv.getSizeInventory(); i++) { + final ItemStack curItem = inv.getStackInSlot(i); + + if (curItem != null) { + final ItemStack comp = new ItemStack(curItem.getItem(), 1, curItem.getItemDamage()); + if (OreDictionary.itemMatches(base, comp, false)) { + matches[0] = true; + } else if (OreDictionary.containsMatch(false, upgradeMats, comp)) { + matches[1] = true; + } + } + } + return matches[0] ? matches[1] : false; + } + + @Override + public ItemStack getCraftingResult(final InventoryCrafting inv) { + final Map> plates = new HashMap<>(); + + final Collection allmats = Materials.getAllMaterials(); + final int hardness = ((Float) Materials.getMaterialByName(this.materialName) + .getStat(MaterialStats.HARDNESS)).intValue(); + + for (final MMDMaterial mat : allmats) { + if ((mat.getStat(MaterialStats.HARDNESS) >= hardness) + && (!mat.getName().equals(this.materialName))) { + final List mats = OreDictionary + .getOres(Oredicts.PLATE + mat.getCapitalizedName()); + plates.put(mat.getName(), mats); + } + } + + ItemStack plateMatched = null; + Map enchants = Collections.emptyMap(); + final ItemStack matcher = new ItemStack( + Materials.getMaterialByName(this.materialName).getItem(Names.SHIELD), 1, 0); + + for (int i = 0; i < inv.getSizeInventory(); i++) { + final ItemStack curItem = inv.getStackInSlot(i); + + if (curItem != null) { + final ItemStack comp = new ItemStack(curItem.getItem(), 1, curItem.getMetadata()); + for (final Entry> ent : plates.entrySet()) { + if (OreDictionary.containsMatch(false, ent.getValue(), comp)) { + plateMatched = new ItemStack( + Materials.getMaterialByName(ent.getKey().toLowerCase()) + .getItem(Names.SHIELD), + 1, 0); + } + } + if (OreDictionary.itemMatches(matcher, comp, false)) { + enchants = EnchantmentHelper.getEnchantments(curItem); + } + } + } + + BaseMetals.logger.debug("Adding %d enchantments to output item", enchants.size()); + EnchantmentHelper.setEnchantments(enchants, plateMatched); + return plateMatched; + } + + private MMDMaterial getUpgradeMat(final InventoryCrafting inv) { + for (int i = 0; i < inv.getSizeInventory(); i++) { + final ItemStack curItem = inv.getStackInSlot(i); + + if (curItem != null) { + for (final MMDMaterial e : Materials.getAllMaterials()) { + if (OreDictionary.containsMatch(false, + OreDictionary.getOres(Oredicts.PLATE + e.getName()), curItem)) { + return e; + } + } + } + } + return Materials.getMaterialByName(this.materialName); + } + + private ItemStack findBaseItem(final InventoryCrafting inv) { + final ItemStack input = new ItemStack( + Materials.getMaterialByName(this.materialName).getItem(Names.SHIELD), 1, 0); + + for (int i = 0; i < inv.getSizeInventory(); i++) { + final ItemStack a = inv.getStackInSlot(i); + if (a != null) { + final ItemStack comp = new ItemStack(a.getItem(), 1, a.getMetadata()); + if (OreDictionary.itemMatches(input, comp, false)) { + return a; + } + } + } + return null; + } + + private int getEnchantCount(final InventoryCrafting inv) { + final ItemStack k = this.findBaseItem(inv); + if (k != null) { + return EnchantmentHelper.getEnchantments(this.findBaseItem(inv)).size(); + } else { + return 0; + } + } + + /** + * + * @param inv + * @return + */ + public int getCost(final InventoryCrafting inv) { + final MMDMaterial shieldMat = Materials.getMaterialByName(this.materialName); + final MMDMaterial upgradeMat = this.getUpgradeMat(inv); + + final float hardDiff = upgradeMat.getStat(MaterialStats.HARDNESS) + - shieldMat.getStat(MaterialStats.HARDNESS); + final int enchantCount = this.getEnchantCount(inv); + + final float diffVal = hardDiff * 5; + final float enchantVal = upgradeMat.getStat(MaterialStats.MAGICAFFINITY) * enchantCount; + Float finalVal = diffVal + enchantVal; + + if (finalVal < 5.0f) { + finalVal = 5.0f; + } + + return finalVal.intValue(); + } +} diff --git a/src/main/java/com/mcmoddev/lib/registry/CrusherRecipeRegistry.java b/src/main/java/com/mcmoddev/lib/registry/CrusherRecipeRegistry.java index 50aa5003b..c1ac58537 100644 --- a/src/main/java/com/mcmoddev/lib/registry/CrusherRecipeRegistry.java +++ b/src/main/java/com/mcmoddev/lib/registry/CrusherRecipeRegistry.java @@ -10,7 +10,7 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; -import com.mcmoddev.basemetals.BaseMetals; +import zone.moddev.mc.basemetals.BaseMetals; import com.mcmoddev.lib.registry.recipe.ArbitraryCrusherRecipe; import com.mcmoddev.lib.registry.recipe.ICrusherRecipe; import com.mcmoddev.lib.registry.recipe.OreDictionaryCrusherRecipe; diff --git a/src/main/java/com/mcmoddev/lib/util/ConfigBase.java b/src/main/java/com/mcmoddev/lib/util/ConfigBase.java index 473e818b4..20418a025 100644 --- a/src/main/java/com/mcmoddev/lib/util/ConfigBase.java +++ b/src/main/java/com/mcmoddev/lib/util/ConfigBase.java @@ -11,7 +11,7 @@ import java.util.Set; import java.util.stream.Collectors; -import com.mcmoddev.basemetals.BaseMetals; +import zone.moddev.mc.basemetals.BaseMetals; import com.mcmoddev.lib.data.SharedStrings; import com.mcmoddev.lib.registry.CrusherRecipeRegistry; diff --git a/src/main/java/com/mcmoddev/orespawn/BaseMetalsOreSpawn.java b/src/main/java/com/mcmoddev/orespawn/BaseMetalsOreSpawn.java deleted file mode 100644 index 349b36457..000000000 --- a/src/main/java/com/mcmoddev/orespawn/BaseMetalsOreSpawn.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.mcmoddev.orespawn; - -import com.mcmoddev.basemetals.BaseMetals; -import com.mcmoddev.lib.data.SharedStrings; -import com.mcmoddev.orespawn.api.os3.OS3API; -import com.mcmoddev.orespawn.api.plugin.IOreSpawnPlugin; -import com.mcmoddev.orespawn.api.plugin.OreSpawnPlugin; - -@OreSpawnPlugin(modid = BaseMetals.MODID, resourcePath = SharedStrings.ORESPAWN_MODID) -public class BaseMetalsOreSpawn implements IOreSpawnPlugin { - - @Override - public void register(final OS3API apiInterface) { - // nothing for us to do - all of our ores are in the - // jar and the code handles that - } -} diff --git a/src/main/java/cyano/basemetals/init/Blocks.java b/src/main/java/cyano/basemetals/init/Blocks.java new file mode 100644 index 000000000..710d2c635 --- /dev/null +++ b/src/main/java/cyano/basemetals/init/Blocks.java @@ -0,0 +1,51 @@ +package cyano.basemetals.init; + +import net.minecraft.block.Block; +import zone.moddev.mc.basemetals.compat.CyanoMaterials; + +/** Old field names retained for the unchanged Base Metal Golems add-on. */ +public final class Blocks { + + public static Block adamantine_block; + public static Block aquarium_block; + public static Block brass_block; + public static Block bronze_block; + public static Block coldiron_block; + public static Block copper_block; + public static Block cupronickel_block; + public static Block electrum_block; + public static Block invar_block; + public static Block lead_block; + public static Block mithril_block; + public static Block nickel_block; + public static Block platinum_block; + public static Block silver_block; + public static Block starsteel_block; + public static Block steel_block; + public static Block tin_block; + public static Block zinc_block; + + private Blocks() { + } + + public static void bind() { + adamantine_block = CyanoMaterials.block("adamantine"); + aquarium_block = CyanoMaterials.block("aquarium"); + brass_block = CyanoMaterials.block("brass"); + bronze_block = CyanoMaterials.block("bronze"); + coldiron_block = CyanoMaterials.block("coldiron"); + copper_block = CyanoMaterials.block("copper"); + cupronickel_block = CyanoMaterials.block("cupronickel"); + electrum_block = CyanoMaterials.block("electrum"); + invar_block = CyanoMaterials.block("invar"); + lead_block = CyanoMaterials.block("lead"); + mithril_block = CyanoMaterials.block("mithril"); + nickel_block = CyanoMaterials.block("nickel"); + platinum_block = CyanoMaterials.block("platinum"); + silver_block = CyanoMaterials.block("silver"); + starsteel_block = CyanoMaterials.block("starsteel"); + steel_block = CyanoMaterials.block("steel"); + tin_block = CyanoMaterials.block("tin"); + zinc_block = CyanoMaterials.block("zinc"); + } +} diff --git a/src/main/java/cyano/basemetals/init/Items.java b/src/main/java/cyano/basemetals/init/Items.java new file mode 100644 index 000000000..412354164 --- /dev/null +++ b/src/main/java/cyano/basemetals/init/Items.java @@ -0,0 +1,51 @@ +package cyano.basemetals.init; + +import net.minecraft.item.Item; +import zone.moddev.mc.basemetals.compat.CyanoMaterials; + +/** These fields refer to current ingots, not another set of registered items. */ +public final class Items { + + public static Item adamantine_ingot; + public static Item aquarium_ingot; + public static Item brass_ingot; + public static Item bronze_ingot; + public static Item coldiron_ingot; + public static Item copper_ingot; + public static Item cupronickel_ingot; + public static Item electrum_ingot; + public static Item invar_ingot; + public static Item lead_ingot; + public static Item mithril_ingot; + public static Item nickel_ingot; + public static Item platinum_ingot; + public static Item silver_ingot; + public static Item starsteel_ingot; + public static Item steel_ingot; + public static Item tin_ingot; + public static Item zinc_ingot; + + private Items() { + } + + public static void bind() { + adamantine_ingot = CyanoMaterials.ingot("adamantine"); + aquarium_ingot = CyanoMaterials.ingot("aquarium"); + brass_ingot = CyanoMaterials.ingot("brass"); + bronze_ingot = CyanoMaterials.ingot("bronze"); + coldiron_ingot = CyanoMaterials.ingot("coldiron"); + copper_ingot = CyanoMaterials.ingot("copper"); + cupronickel_ingot = CyanoMaterials.ingot("cupronickel"); + electrum_ingot = CyanoMaterials.ingot("electrum"); + invar_ingot = CyanoMaterials.ingot("invar"); + lead_ingot = CyanoMaterials.ingot("lead"); + mithril_ingot = CyanoMaterials.ingot("mithril"); + nickel_ingot = CyanoMaterials.ingot("nickel"); + platinum_ingot = CyanoMaterials.ingot("platinum"); + silver_ingot = CyanoMaterials.ingot("silver"); + starsteel_ingot = CyanoMaterials.ingot("starsteel"); + steel_ingot = CyanoMaterials.ingot("steel"); + tin_ingot = CyanoMaterials.ingot("tin"); + zinc_ingot = CyanoMaterials.ingot("zinc"); + } +} diff --git a/src/main/java/com/mcmoddev/basemetals/BaseMetals.java b/src/main/java/zone/moddev/mc/basemetals/BaseMetals.java similarity index 78% rename from src/main/java/com/mcmoddev/basemetals/BaseMetals.java rename to src/main/java/zone/moddev/mc/basemetals/BaseMetals.java index 256da8c85..c97891541 100644 --- a/src/main/java/com/mcmoddev/basemetals/BaseMetals.java +++ b/src/main/java/zone/moddev/mc/basemetals/BaseMetals.java @@ -1,9 +1,9 @@ -package com.mcmoddev.basemetals; +package zone.moddev.mc.basemetals; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import com.mcmoddev.basemetals.proxy.CommonProxy; +import zone.moddev.mc.basemetals.proxy.CommonProxy; import com.mcmoddev.lib.data.SharedStrings; import net.minecraftforge.fluids.FluidRegistry; @@ -13,14 +13,15 @@ import net.minecraftforge.fml.common.SidedProxy; import net.minecraftforge.fml.common.event.FMLFingerprintViolationEvent; import net.minecraftforge.fml.common.event.FMLInitializationEvent; +import net.minecraftforge.fml.common.event.FMLModIdMappingEvent; import net.minecraftforge.fml.common.event.FMLMissingMappingsEvent; import net.minecraftforge.fml.common.event.FMLPostInitializationEvent; import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; /** * This is the entry point for this Mod. If you are writing your own Mod that uses this Mod, the - * classes of interest to you are the init classes (classes in package com.mcmoddev.basemetals.init) - * and the CrusherRecipeRegistry class (in package com.mcmoddev.basemetals.registry). Note that you + * classes of interest to you are the init classes (classes in package zone.moddev.mc.basemetals.init) + * and the CrusherRecipeRegistry class (in package com.mcmoddev.lib.registry). Note that you * should add 'dependencies = "required-after:basemetals"' to your @Mod annotation (e.g.
* @Mod(modid = "moremetals", name="More Metals!", version = "1.2.3", dependencies = * "required-after:basemetals")
@@ -33,9 +34,9 @@ modid = BaseMetals.MODID, name = BaseMetals.NAME, version = BaseMetals.VERSION, - dependencies = "required-after:Forge@[12.18.3.2185,);after:tconstruct;after:ic2;after:taiga;before:buildingbricks", + dependencies = "required-after:Forge@[12.18.3.2511,);required-after:orespawn@[4.1.2.110021,5.0.0);after:tconstruct;after:ic2;after:taiga;before:buildingbricks", acceptedMinecraftVersions = "[1.10.2]", - certificateFingerprint = "@FINGERPRINT@", + certificateFingerprint = "", updateJSON = BaseMetals.UPDATEJSON) public class BaseMetals { @@ -49,17 +50,14 @@ public class BaseMetals { protected static final String NAME = "Base Metals"; /** - * Version number, in Major.Minor.Patch format. The minor number is increased whenever a change - * is made that has the potential to break compatibility with other mods that depend on this - * one. + * Major.Minor.Patch.Target, where 110021 means Minecraft 1.10.2 on Forge. */ - public static final String VERSION = "2.5.0-beta5"; + public static final String VERSION = "2.5.1.110021"; protected static final String UPDATEJSON = SharedStrings.UPDATE_JSON_URL + "BaseMetals/master/update.json"; - private static final String PROXY_BASE = SharedStrings.MMD_PROXY_GROUP + MODID - + SharedStrings.DOT_PROXY_DOT; + private static final String PROXY_BASE = "zone.moddev.mc.basemetals.proxy."; @SidedProxy( clientSide = PROXY_BASE + SharedStrings.CLIENTPROXY, @@ -101,4 +99,9 @@ public static void postInit(final FMLPostInitializationEvent event) { public static void onRemap(final FMLMissingMappingsEvent event) { proxy.onRemap(event); } + + @EventHandler + public static void onIdMapping(final FMLModIdMappingEvent event) { + proxy.onIdMapping(); + } } diff --git a/src/main/java/zone/moddev/mc/basemetals/compat/BaseMetalsLoadingPlugin.java b/src/main/java/zone/moddev/mc/basemetals/compat/BaseMetalsLoadingPlugin.java new file mode 100644 index 000000000..3ca76f20a --- /dev/null +++ b/src/main/java/zone/moddev/mc/basemetals/compat/BaseMetalsLoadingPlugin.java @@ -0,0 +1,20 @@ +package zone.moddev.mc.basemetals.compat; + +import com.mcmoddev.lib.asm.ASMPlugin; +import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin; + +/** Keeps the existing horse-armour hooks and installs the old API name adapter. */ +@IFMLLoadingPlugin.Name("BaseMetals") +@IFMLLoadingPlugin.MCVersion("1.10.2") +@IFMLLoadingPlugin.SortingIndex(1001) +@IFMLLoadingPlugin.TransformerExclusions("zone.moddev.mc.basemetals.compat.") +public final class BaseMetalsLoadingPlugin extends ASMPlugin { + + @Override + public String[] getASMTransformerClass() { + return new String[] { + "zone.moddev.mc.basemetals.compat.LegacyNamespaceTransformer", + "com.mcmoddev.lib.asm.ASMTransformer" + }; + } +} diff --git a/src/main/java/zone/moddev/mc/basemetals/compat/CyanoMaterials.java b/src/main/java/zone/moddev/mc/basemetals/compat/CyanoMaterials.java new file mode 100644 index 000000000..be10006d1 --- /dev/null +++ b/src/main/java/zone/moddev/mc/basemetals/compat/CyanoMaterials.java @@ -0,0 +1,23 @@ +package zone.moddev.mc.basemetals.compat; + +import net.minecraft.block.Block; +import net.minecraft.item.Item; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.fml.common.registry.ForgeRegistries; + +/** Registry lookups for the small Cyano API used by Base Metal Golems. */ +public final class CyanoMaterials { + + private CyanoMaterials() { + } + + public static Block block(final String material) { + final ResourceLocation name = new ResourceLocation("basemetals", material + "_block"); + return ForgeRegistries.BLOCKS.containsKey(name) ? ForgeRegistries.BLOCKS.getValue(name) : null; + } + + public static Item ingot(final String material) { + final ResourceLocation name = new ResourceLocation("basemetals", material + "_ingot"); + return ForgeRegistries.ITEMS.containsKey(name) ? ForgeRegistries.ITEMS.getValue(name) : null; + } +} diff --git a/src/main/java/zone/moddev/mc/basemetals/compat/LegacyNamespaceTransformer.java b/src/main/java/zone/moddev/mc/basemetals/compat/LegacyNamespaceTransformer.java new file mode 100644 index 000000000..988ac8bf0 --- /dev/null +++ b/src/main/java/zone/moddev/mc/basemetals/compat/LegacyNamespaceTransformer.java @@ -0,0 +1,89 @@ +package zone.moddev.mc.basemetals.compat; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import net.minecraft.launchwrapper.IClassTransformer; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.commons.Remapper; +import org.objectweb.asm.commons.RemappingClassAdapter; + +/** Relinks existing MMD add-ons to the same classes and registries as Base Metals. */ +public final class LegacyNamespaceTransformer implements IClassTransformer { + + private static final String OLD = "com/mcmoddev/basemetals/"; + private static final String CURRENT = "zone/moddev/mc/basemetals/"; + private static final Set EXPORTS = loadExports(); + + @Override + public byte[] transform(final String name, final String transformedName, final byte[] bytes) { + if (bytes == null || name == null || name.startsWith("zone.moddev.mc.basemetals.") + || !(contains(bytes, OLD) || contains(bytes, OLD.replace('/', '.')))) { + return bytes; + } + + final ClassWriter output = new ClassWriter(0); + final Remapper names = new Remapper() { + @Override + public String map(final String internalName) { + return currentName(internalName); + } + + @Override + public Object mapValue(final Object value) { + if (value instanceof String) { + final String text = (String) value; + final String internal = text.replace('.', '/'); + final String mapped = currentName(internal); + if (mapped.equals(internal)) return value; + return text.indexOf('/') < 0 ? mapped.replace('/', '.') : mapped; + } + return super.mapValue(value); + } + }; + + new ClassReader(bytes).accept(new RemappingClassAdapter(output, names), 0); + return output.toByteArray(); + } + + private static String currentName(final String name) { + if (!name.startsWith(OLD)) return name; + + final String suffix = name.substring(OLD.length()); + final int nested = suffix.indexOf('$'); + final String owner = nested < 0 ? suffix : suffix.substring(0, nested); + return EXPORTS.contains(owner) ? CURRENT + suffix : name; + } + + private static Set loadExports() { + final Set result = new HashSet<>(); + final InputStream stream = LegacyNamespaceTransformer.class.getClassLoader() + .getResourceAsStream("META-INF/basemetals-legacy-api.list"); + if (stream == null) throw new IllegalStateException("Missing Base Metals API name list"); + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + if (!line.isEmpty() && !line.startsWith("#")) result.add(line); + } + } catch (final IOException failure) { + throw new IllegalStateException("Cannot read Base Metals API names", failure); + } + return Collections.unmodifiableSet(result); + } + + private static boolean contains(final byte[] bytes, final String text) { + for (int offset = 0; offset <= bytes.length - text.length(); offset++) { + int index = 0; + while (index < text.length() && bytes[offset + index] == text.charAt(index)) index++; + if (index == text.length()) return true; + } + return false; + } +} diff --git a/src/main/java/com/mcmoddev/basemetals/data/AchievementNames.java b/src/main/java/zone/moddev/mc/basemetals/data/AchievementNames.java similarity index 96% rename from src/main/java/com/mcmoddev/basemetals/data/AchievementNames.java rename to src/main/java/zone/moddev/mc/basemetals/data/AchievementNames.java index cb01c92e5..00d5af725 100644 --- a/src/main/java/com/mcmoddev/basemetals/data/AchievementNames.java +++ b/src/main/java/zone/moddev/mc/basemetals/data/AchievementNames.java @@ -1,4 +1,4 @@ -package com.mcmoddev.basemetals.data; +package zone.moddev.mc.basemetals.data; import com.mcmoddev.lib.data.SharedStrings; diff --git a/src/main/java/com/mcmoddev/basemetals/data/MaterialNames.java b/src/main/java/zone/moddev/mc/basemetals/data/MaterialNames.java old mode 100755 new mode 100644 similarity index 95% rename from src/main/java/com/mcmoddev/basemetals/data/MaterialNames.java rename to src/main/java/zone/moddev/mc/basemetals/data/MaterialNames.java index 94c932878..256cf3418 --- a/src/main/java/com/mcmoddev/basemetals/data/MaterialNames.java +++ b/src/main/java/zone/moddev/mc/basemetals/data/MaterialNames.java @@ -1,47 +1,47 @@ -package com.mcmoddev.basemetals.data; - -import com.mcmoddev.lib.data.SharedStrings; - -public class MaterialNames { - - public static final String ADAMANTINE = "adamantine"; - public static final String ANTIMONY = "antimony"; - public static final String AQUARIUM = "aquarium"; - public static final String BISMUTH = "bismuth"; - public static final String BRASS = "brass"; - public static final String BRONZE = "bronze"; - public static final String COAL = "coal"; - public static final String COLDIRON = "coldiron"; - public static final String COPPER = "copper"; - public static final String CHARCOAL = "charcoal"; - public static final String CUPRONICKEL = "cupronickel"; - public static final String DIAMOND = "diamond"; - public static final String ELECTRUM = "electrum"; - public static final String EMERALD = "emerald"; - public static final String ENDER = "ender"; - public static final String GOLD = "gold"; - public static final String INVAR = "invar"; - public static final String IRON = "iron"; - public static final String LAPIS = "lapis"; - public static final String LEAD = "lead"; - public static final String MERCURY = "mercury"; - public static final String MITHRIL = "mithril"; - public static final String NICKEL = "nickel"; - public static final String OBSIDIAN = "obsidian"; - public static final String PEWTER = "pewter"; - public static final String PLATINUM = "platinum"; - public static final String PRISMARINE = "prismarine"; - public static final String QUARTZ = "quartz"; - public static final String REDSTONE = "redstone"; - public static final String SILVER = "silver"; - public static final String STARSTEEL = "starsteel"; - public static final String STEEL = "steel"; - public static final String STONE = "stone"; - public static final String TIN = "tin"; - public static final String WOOD = "wood"; - public static final String ZINC = "zinc"; - - private MaterialNames() { - throw new IllegalAccessError(SharedStrings.NOT_INSTANTIABLE); - } -} +package zone.moddev.mc.basemetals.data; + +import com.mcmoddev.lib.data.SharedStrings; + +public class MaterialNames { + + public static final String ADAMANTINE = "adamantine"; + public static final String ANTIMONY = "antimony"; + public static final String AQUARIUM = "aquarium"; + public static final String BISMUTH = "bismuth"; + public static final String BRASS = "brass"; + public static final String BRONZE = "bronze"; + public static final String COAL = "coal"; + public static final String COLDIRON = "coldiron"; + public static final String COPPER = "copper"; + public static final String CHARCOAL = "charcoal"; + public static final String CUPRONICKEL = "cupronickel"; + public static final String DIAMOND = "diamond"; + public static final String ELECTRUM = "electrum"; + public static final String EMERALD = "emerald"; + public static final String ENDER = "ender"; + public static final String GOLD = "gold"; + public static final String INVAR = "invar"; + public static final String IRON = "iron"; + public static final String LAPIS = "lapis"; + public static final String LEAD = "lead"; + public static final String MERCURY = "mercury"; + public static final String MITHRIL = "mithril"; + public static final String NICKEL = "nickel"; + public static final String OBSIDIAN = "obsidian"; + public static final String PEWTER = "pewter"; + public static final String PLATINUM = "platinum"; + public static final String PRISMARINE = "prismarine"; + public static final String QUARTZ = "quartz"; + public static final String REDSTONE = "redstone"; + public static final String SILVER = "silver"; + public static final String STARSTEEL = "starsteel"; + public static final String STEEL = "steel"; + public static final String STONE = "stone"; + public static final String TIN = "tin"; + public static final String WOOD = "wood"; + public static final String ZINC = "zinc"; + + private MaterialNames() { + throw new IllegalAccessError(SharedStrings.NOT_INSTANTIABLE); + } +} diff --git a/src/main/java/com/mcmoddev/basemetals/data/TraitNames.java b/src/main/java/zone/moddev/mc/basemetals/data/TraitNames.java similarity index 94% rename from src/main/java/com/mcmoddev/basemetals/data/TraitNames.java rename to src/main/java/zone/moddev/mc/basemetals/data/TraitNames.java index fb7d53fc2..cf546ab68 100644 --- a/src/main/java/com/mcmoddev/basemetals/data/TraitNames.java +++ b/src/main/java/zone/moddev/mc/basemetals/data/TraitNames.java @@ -1,4 +1,4 @@ -package com.mcmoddev.basemetals.data; +package zone.moddev.mc.basemetals.data; import com.mcmoddev.lib.data.SharedStrings; diff --git a/src/main/java/com/mcmoddev/basemetals/init/Achievements.java b/src/main/java/zone/moddev/mc/basemetals/init/Achievements.java similarity index 97% rename from src/main/java/com/mcmoddev/basemetals/init/Achievements.java rename to src/main/java/zone/moddev/mc/basemetals/init/Achievements.java index fe5893bd4..09a952942 100644 --- a/src/main/java/com/mcmoddev/basemetals/init/Achievements.java +++ b/src/main/java/zone/moddev/mc/basemetals/init/Achievements.java @@ -1,7 +1,7 @@ -package com.mcmoddev.basemetals.init; +package zone.moddev.mc.basemetals.init; -import com.mcmoddev.basemetals.data.AchievementNames; -import com.mcmoddev.basemetals.data.MaterialNames; +import zone.moddev.mc.basemetals.data.AchievementNames; +import zone.moddev.mc.basemetals.data.MaterialNames; import com.mcmoddev.lib.data.Names; import com.mcmoddev.lib.data.SharedStrings; import com.mcmoddev.lib.init.Materials; diff --git a/src/main/java/com/mcmoddev/basemetals/init/Blocks.java b/src/main/java/zone/moddev/mc/basemetals/init/Blocks.java similarity index 98% rename from src/main/java/com/mcmoddev/basemetals/init/Blocks.java rename to src/main/java/zone/moddev/mc/basemetals/init/Blocks.java index cbd8d8598..b4e74db33 100644 --- a/src/main/java/com/mcmoddev/basemetals/init/Blocks.java +++ b/src/main/java/zone/moddev/mc/basemetals/init/Blocks.java @@ -1,9 +1,9 @@ -package com.mcmoddev.basemetals.init; +package zone.moddev.mc.basemetals.init; import java.util.Arrays; import java.util.List; -import com.mcmoddev.basemetals.data.MaterialNames; +import zone.moddev.mc.basemetals.data.MaterialNames; import com.mcmoddev.lib.block.BlockHumanDetector; import com.mcmoddev.lib.data.Names; import com.mcmoddev.lib.data.SharedStrings; diff --git a/src/main/java/com/mcmoddev/basemetals/init/Fluids.java b/src/main/java/zone/moddev/mc/basemetals/init/Fluids.java similarity index 95% rename from src/main/java/com/mcmoddev/basemetals/init/Fluids.java rename to src/main/java/zone/moddev/mc/basemetals/init/Fluids.java index 844f41094..30d4df479 100644 --- a/src/main/java/com/mcmoddev/basemetals/init/Fluids.java +++ b/src/main/java/zone/moddev/mc/basemetals/init/Fluids.java @@ -1,9 +1,9 @@ -package com.mcmoddev.basemetals.init; +package zone.moddev.mc.basemetals.init; import java.util.Arrays; import java.util.List; -import com.mcmoddev.basemetals.data.MaterialNames; +import zone.moddev.mc.basemetals.data.MaterialNames; import com.mcmoddev.lib.data.SharedStrings; import com.mcmoddev.lib.init.Materials; import com.mcmoddev.lib.util.ConfigBase.Options; diff --git a/src/main/java/com/mcmoddev/basemetals/init/ItemGroups.java b/src/main/java/zone/moddev/mc/basemetals/init/ItemGroups.java similarity index 97% rename from src/main/java/com/mcmoddev/basemetals/init/ItemGroups.java rename to src/main/java/zone/moddev/mc/basemetals/init/ItemGroups.java index 1a416c775..f87cef150 100644 --- a/src/main/java/com/mcmoddev/basemetals/init/ItemGroups.java +++ b/src/main/java/zone/moddev/mc/basemetals/init/ItemGroups.java @@ -1,4 +1,4 @@ -package com.mcmoddev.basemetals.init; +package zone.moddev.mc.basemetals.init; import com.mcmoddev.lib.data.Names; import com.mcmoddev.lib.data.SharedStrings; diff --git a/src/main/java/com/mcmoddev/basemetals/init/Items.java b/src/main/java/zone/moddev/mc/basemetals/init/Items.java similarity index 99% rename from src/main/java/com/mcmoddev/basemetals/init/Items.java rename to src/main/java/zone/moddev/mc/basemetals/init/Items.java index b909d4ac6..88f90cff7 100644 --- a/src/main/java/com/mcmoddev/basemetals/init/Items.java +++ b/src/main/java/zone/moddev/mc/basemetals/init/Items.java @@ -1,11 +1,11 @@ -package com.mcmoddev.basemetals.init; +package zone.moddev.mc.basemetals.init; import java.util.Arrays; import java.util.List; import javax.annotation.Nonnull; -import com.mcmoddev.basemetals.data.MaterialNames; +import zone.moddev.mc.basemetals.data.MaterialNames; import com.mcmoddev.lib.data.Names; import com.mcmoddev.lib.data.SharedStrings; import com.mcmoddev.lib.fuels.FuelRegistry; diff --git a/src/main/java/com/mcmoddev/basemetals/init/Materials.java b/src/main/java/zone/moddev/mc/basemetals/init/Materials.java similarity index 98% rename from src/main/java/com/mcmoddev/basemetals/init/Materials.java rename to src/main/java/zone/moddev/mc/basemetals/init/Materials.java index 3d029af7b..76133a5f5 100644 --- a/src/main/java/com/mcmoddev/basemetals/init/Materials.java +++ b/src/main/java/zone/moddev/mc/basemetals/init/Materials.java @@ -1,11 +1,11 @@ -package com.mcmoddev.basemetals.init; +package zone.moddev.mc.basemetals.init; import java.util.Arrays; import java.util.List; import javax.annotation.Nonnull; -import com.mcmoddev.basemetals.data.MaterialNames; +import zone.moddev.mc.basemetals.data.MaterialNames; import com.mcmoddev.lib.data.SharedStrings; import com.mcmoddev.lib.material.MMDMaterial.MaterialType; import com.mcmoddev.lib.util.ConfigBase.Options; diff --git a/src/main/java/com/mcmoddev/basemetals/init/Recipes.java b/src/main/java/zone/moddev/mc/basemetals/init/Recipes.java similarity index 99% rename from src/main/java/com/mcmoddev/basemetals/init/Recipes.java rename to src/main/java/zone/moddev/mc/basemetals/init/Recipes.java index 3358c0e22..c1652b47a 100644 --- a/src/main/java/com/mcmoddev/basemetals/init/Recipes.java +++ b/src/main/java/zone/moddev/mc/basemetals/init/Recipes.java @@ -1,6 +1,6 @@ -package com.mcmoddev.basemetals.init; +package zone.moddev.mc.basemetals.init; -import com.mcmoddev.basemetals.data.MaterialNames; +import zone.moddev.mc.basemetals.data.MaterialNames; import com.mcmoddev.lib.data.Names; import com.mcmoddev.lib.data.SharedStrings; import com.mcmoddev.lib.init.Materials; diff --git a/src/main/java/com/mcmoddev/basemetals/init/VillagerTrades.java b/src/main/java/zone/moddev/mc/basemetals/init/VillagerTrades.java similarity index 92% rename from src/main/java/com/mcmoddev/basemetals/init/VillagerTrades.java rename to src/main/java/zone/moddev/mc/basemetals/init/VillagerTrades.java index 7790106f2..4960123cf 100644 --- a/src/main/java/com/mcmoddev/basemetals/init/VillagerTrades.java +++ b/src/main/java/zone/moddev/mc/basemetals/init/VillagerTrades.java @@ -1,7 +1,7 @@ -package com.mcmoddev.basemetals.init; +package zone.moddev.mc.basemetals.init; -import com.mcmoddev.basemetals.data.MaterialNames; -import com.mcmoddev.basemetals.util.VillagerTradeHelper; +import zone.moddev.mc.basemetals.data.MaterialNames; +import zone.moddev.mc.basemetals.util.VillagerTradeHelper; import com.mcmoddev.lib.data.Names; import com.mcmoddev.lib.data.SharedStrings; import com.mcmoddev.lib.init.Materials; diff --git a/src/main/java/com/mcmoddev/basemetals/integration/plugins/DenseOres.java b/src/main/java/zone/moddev/mc/basemetals/integration/plugins/DenseOres.java old mode 100755 new mode 100644 similarity index 88% rename from src/main/java/com/mcmoddev/basemetals/integration/plugins/DenseOres.java rename to src/main/java/zone/moddev/mc/basemetals/integration/plugins/DenseOres.java index 4c7b1731b..0ad5c44a8 --- a/src/main/java/com/mcmoddev/basemetals/integration/plugins/DenseOres.java +++ b/src/main/java/zone/moddev/mc/basemetals/integration/plugins/DenseOres.java @@ -1,55 +1,55 @@ -package com.mcmoddev.basemetals.integration.plugins; - -import java.util.Arrays; -import java.util.List; - -import com.mcmoddev.basemetals.BaseMetals; -import com.mcmoddev.basemetals.data.MaterialNames; -import com.mcmoddev.lib.init.Materials; -import com.mcmoddev.lib.integration.IIntegration; -import com.mcmoddev.lib.integration.MMDPlugin; -import com.mcmoddev.lib.util.ConfigBase.Options; -import com.mcmoddev.lib.util.Oredicts; - -@MMDPlugin(addonId = BaseMetals.MODID, pluginId = DenseOres.PLUGIN_MODID) -public final class DenseOres extends com.mcmoddev.lib.integration.plugins.DenseOresBase - implements IIntegration { - - @Override - public void init() { - if (!Options.isModEnabled(PLUGIN_MODID)) { - return; - } - - registerOres(); - } - - /** - * Register all ores that are currently known by the materials registry. - * - * @author Daniel Hazelton <dshadowwolf@gmail.com> - */ - private static void registerOres() { - final List materials = Arrays.asList(MaterialNames.ADAMANTINE, - MaterialNames.ANTIMONY, MaterialNames.BISMUTH, MaterialNames.COLDIRON, - MaterialNames.COPPER, MaterialNames.LEAD, MaterialNames.MERCURY, - MaterialNames.NICKEL, MaterialNames.PLATINUM, MaterialNames.SILVER, - MaterialNames.TIN, MaterialNames.ZINC); - - materials.stream().filter(Materials::hasMaterial) - .filter(materialName -> !Materials.getMaterialByName(materialName).isEmpty()) - .forEach(materialName -> { - String baseMaterial; - switch (materialName) { - case MaterialNames.ADAMANTINE: - case MaterialNames.COLDIRON: - baseMaterial = Oredicts.NETHERRACK; - break; - default: - baseMaterial = Oredicts.STONE; - } - registerOre(String.format("%s_%s", materialName, Oredicts.ORE), baseMaterial, - 0); - }); - } -} +package zone.moddev.mc.basemetals.integration.plugins; + +import java.util.Arrays; +import java.util.List; + +import zone.moddev.mc.basemetals.BaseMetals; +import zone.moddev.mc.basemetals.data.MaterialNames; +import com.mcmoddev.lib.init.Materials; +import com.mcmoddev.lib.integration.IIntegration; +import com.mcmoddev.lib.integration.MMDPlugin; +import com.mcmoddev.lib.util.ConfigBase.Options; +import com.mcmoddev.lib.util.Oredicts; + +@MMDPlugin(addonId = BaseMetals.MODID, pluginId = DenseOres.PLUGIN_MODID) +public final class DenseOres extends com.mcmoddev.lib.integration.plugins.DenseOresBase + implements IIntegration { + + @Override + public void init() { + if (!Options.isModEnabled(PLUGIN_MODID)) { + return; + } + + registerOres(); + } + + /** + * Register all ores that are currently known by the materials registry. + * + * @author Daniel Hazelton <dshadowwolf@gmail.com> + */ + private static void registerOres() { + final List materials = Arrays.asList(MaterialNames.ADAMANTINE, + MaterialNames.ANTIMONY, MaterialNames.BISMUTH, MaterialNames.COLDIRON, + MaterialNames.COPPER, MaterialNames.LEAD, MaterialNames.MERCURY, + MaterialNames.NICKEL, MaterialNames.PLATINUM, MaterialNames.SILVER, + MaterialNames.TIN, MaterialNames.ZINC); + + materials.stream().filter(Materials::hasMaterial) + .filter(materialName -> !Materials.getMaterialByName(materialName).isEmpty()) + .forEach(materialName -> { + String baseMaterial; + switch (materialName) { + case MaterialNames.ADAMANTINE: + case MaterialNames.COLDIRON: + baseMaterial = Oredicts.NETHERRACK; + break; + default: + baseMaterial = Oredicts.STONE; + } + registerOre(String.format("%s_%s", materialName, Oredicts.ORE), baseMaterial, + 0); + }); + } +} diff --git a/src/main/java/com/mcmoddev/basemetals/integration/plugins/EnderIO.java b/src/main/java/zone/moddev/mc/basemetals/integration/plugins/EnderIO.java similarity index 91% rename from src/main/java/com/mcmoddev/basemetals/integration/plugins/EnderIO.java rename to src/main/java/zone/moddev/mc/basemetals/integration/plugins/EnderIO.java index 3b6b2fc75..8da58bdd7 100644 --- a/src/main/java/com/mcmoddev/basemetals/integration/plugins/EnderIO.java +++ b/src/main/java/zone/moddev/mc/basemetals/integration/plugins/EnderIO.java @@ -1,10 +1,10 @@ -package com.mcmoddev.basemetals.integration.plugins; +package zone.moddev.mc.basemetals.integration.plugins; import java.util.Arrays; import java.util.List; -import com.mcmoddev.basemetals.BaseMetals; -import com.mcmoddev.basemetals.data.MaterialNames; +import zone.moddev.mc.basemetals.BaseMetals; +import zone.moddev.mc.basemetals.data.MaterialNames; import com.mcmoddev.lib.init.Materials; import com.mcmoddev.lib.integration.IIntegration; import com.mcmoddev.lib.integration.MMDPlugin; diff --git a/src/main/java/com/mcmoddev/basemetals/integration/plugins/IC2.java b/src/main/java/zone/moddev/mc/basemetals/integration/plugins/IC2.java similarity index 94% rename from src/main/java/com/mcmoddev/basemetals/integration/plugins/IC2.java rename to src/main/java/zone/moddev/mc/basemetals/integration/plugins/IC2.java index 3c53fd9d1..5027a5794 100644 --- a/src/main/java/com/mcmoddev/basemetals/integration/plugins/IC2.java +++ b/src/main/java/zone/moddev/mc/basemetals/integration/plugins/IC2.java @@ -1,10 +1,10 @@ -package com.mcmoddev.basemetals.integration.plugins; +package zone.moddev.mc.basemetals.integration.plugins; import java.util.Arrays; import java.util.List; -import com.mcmoddev.basemetals.BaseMetals; -import com.mcmoddev.basemetals.data.MaterialNames; +import zone.moddev.mc.basemetals.BaseMetals; +import zone.moddev.mc.basemetals.data.MaterialNames; import com.mcmoddev.lib.data.Names; import com.mcmoddev.lib.init.Materials; import com.mcmoddev.lib.integration.IIntegration; diff --git a/src/main/java/com/mcmoddev/basemetals/integration/plugins/Mekanism.java b/src/main/java/zone/moddev/mc/basemetals/integration/plugins/Mekanism.java similarity index 93% rename from src/main/java/com/mcmoddev/basemetals/integration/plugins/Mekanism.java rename to src/main/java/zone/moddev/mc/basemetals/integration/plugins/Mekanism.java index dd68314ef..559f138bd 100644 --- a/src/main/java/com/mcmoddev/basemetals/integration/plugins/Mekanism.java +++ b/src/main/java/zone/moddev/mc/basemetals/integration/plugins/Mekanism.java @@ -1,12 +1,12 @@ -package com.mcmoddev.basemetals.integration.plugins; +package zone.moddev.mc.basemetals.integration.plugins; import java.util.Arrays; import java.util.List; import javax.annotation.Nonnull; -import com.mcmoddev.basemetals.BaseMetals; -import com.mcmoddev.basemetals.data.MaterialNames; +import zone.moddev.mc.basemetals.BaseMetals; +import zone.moddev.mc.basemetals.data.MaterialNames; import com.mcmoddev.lib.data.Names; import com.mcmoddev.lib.init.Materials; import com.mcmoddev.lib.integration.IIntegration; diff --git a/src/main/java/com/mcmoddev/basemetals/integration/plugins/TAIGA.java b/src/main/java/zone/moddev/mc/basemetals/integration/plugins/TAIGA.java old mode 100755 new mode 100644 similarity index 93% rename from src/main/java/com/mcmoddev/basemetals/integration/plugins/TAIGA.java rename to src/main/java/zone/moddev/mc/basemetals/integration/plugins/TAIGA.java index 8155ea69f..bbf9665b7 --- a/src/main/java/com/mcmoddev/basemetals/integration/plugins/TAIGA.java +++ b/src/main/java/zone/moddev/mc/basemetals/integration/plugins/TAIGA.java @@ -1,85 +1,85 @@ -package com.mcmoddev.basemetals.integration.plugins; - -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.List; - -import com.mcmoddev.basemetals.BaseMetals; -import com.mcmoddev.lib.data.Names; -import com.mcmoddev.lib.integration.IIntegration; -import com.mcmoddev.lib.integration.MMDPlugin; -import com.mcmoddev.lib.integration.plugins.TinkersConstructBase; -import com.mcmoddev.lib.integration.plugins.taiga.TAIGAItems; -import com.mcmoddev.lib.integration.plugins.tinkers.TraitRegistry; -import com.mcmoddev.lib.material.MMDMaterial; -import com.mcmoddev.lib.material.MMDMaterial.MaterialType; -import com.mcmoddev.lib.util.ConfigBase.Options; -import com.sosnitzka.taiga.Blocks; -import com.sosnitzka.taiga.Items; - -import net.minecraft.block.Block; -import net.minecraft.item.Item; -import net.minecraft.item.ItemStack; - -@MMDPlugin(addonId = BaseMetals.MODID, pluginId = TAIGA.PLUGIN_MODID) -public final class TAIGA extends com.mcmoddev.lib.integration.plugins.TAIGABase implements IIntegration { - - @Override - public void init() { - if (!Options.isModEnabled(PLUGIN_MODID) - && (!Options.isModEnabled(TinkersConstructBase.PLUGIN_MODID))) { - return; - } - - TraitRegistry.initTAIGATraits(); - TAIGAMaterials.init(); - TAIGAItems.init(TAIGAMaterials.materials); - - // TraitRegistry.dumpRegistry(); - } - - private static class TAIGAMaterials extends com.mcmoddev.lib.init.Materials { - - private static final Field[] allBlocks = Blocks.class.getDeclaredFields(); - - private static final List materials = new ArrayList<>(); - - public static void init() { - try { - for (final Field f : allBlocks) { - final String t = f.getName(); - - if (t.endsWith("Block") && !("basaltBlock".equals(t))) { - final String name = t.substring(0, t.length() - 5); - - final Block k = (Block) f.get(f.getClass()); - final float harvestLevel = k.getHarvestLevel(null); - final float resist = k.getExplosionResistance(null); - - final MMDMaterial repThis = createOrelessMaterial(name, MaterialType.METAL, - harvestLevel * 3.0f, resist / 2.5f, 1.0f, 0x00000000); - - repThis.addNewBlock(Names.BLOCK, k); - repThis.addNewItem(Names.INGOT, - new ItemStack((Item) Items.class - .getField(name.toLowerCase() + "Ingot").get(Items.class), 1) - .getItem()); - repThis.addNewItem(Names.POWDER, - new ItemStack((Item) Items.class - .getField(name.toLowerCase() + "Dust").get(Items.class), 1) - .getItem()); - repThis.addNewItem(Names.NUGGET, - new ItemStack((Item) Items.class - .getField(name.toLowerCase() + "Nugget").get(Items.class), - 1).getItem()); - - materials.add(repThis); - } - } - } catch (final Exception ex) { - throw new Error(ex.getMessage()); - // do nought - } - } - } +package zone.moddev.mc.basemetals.integration.plugins; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +import zone.moddev.mc.basemetals.BaseMetals; +import com.mcmoddev.lib.data.Names; +import com.mcmoddev.lib.integration.IIntegration; +import com.mcmoddev.lib.integration.MMDPlugin; +import com.mcmoddev.lib.integration.plugins.TinkersConstructBase; +import com.mcmoddev.lib.integration.plugins.taiga.TAIGAItems; +import com.mcmoddev.lib.integration.plugins.tinkers.TraitRegistry; +import com.mcmoddev.lib.material.MMDMaterial; +import com.mcmoddev.lib.material.MMDMaterial.MaterialType; +import com.mcmoddev.lib.util.ConfigBase.Options; +import com.sosnitzka.taiga.Blocks; +import com.sosnitzka.taiga.Items; + +import net.minecraft.block.Block; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; + +@MMDPlugin(addonId = BaseMetals.MODID, pluginId = TAIGA.PLUGIN_MODID) +public final class TAIGA extends com.mcmoddev.lib.integration.plugins.TAIGABase implements IIntegration { + + @Override + public void init() { + if (!Options.isModEnabled(PLUGIN_MODID) + && (!Options.isModEnabled(TinkersConstructBase.PLUGIN_MODID))) { + return; + } + + TraitRegistry.initTAIGATraits(); + TAIGAMaterials.init(); + TAIGAItems.init(TAIGAMaterials.materials); + + // TraitRegistry.dumpRegistry(); + } + + private static class TAIGAMaterials extends com.mcmoddev.lib.init.Materials { + + private static final Field[] allBlocks = Blocks.class.getDeclaredFields(); + + private static final List materials = new ArrayList<>(); + + public static void init() { + try { + for (final Field f : allBlocks) { + final String t = f.getName(); + + if (t.endsWith("Block") && !("basaltBlock".equals(t))) { + final String name = t.substring(0, t.length() - 5); + + final Block k = (Block) f.get(f.getClass()); + final float harvestLevel = k.getHarvestLevel(null); + final float resist = k.getExplosionResistance(null); + + final MMDMaterial repThis = createOrelessMaterial(name, MaterialType.METAL, + harvestLevel * 3.0f, resist / 2.5f, 1.0f, 0x00000000); + + repThis.addNewBlock(Names.BLOCK, k); + repThis.addNewItem(Names.INGOT, + new ItemStack((Item) Items.class + .getField(name.toLowerCase() + "Ingot").get(Items.class), 1) + .getItem()); + repThis.addNewItem(Names.POWDER, + new ItemStack((Item) Items.class + .getField(name.toLowerCase() + "Dust").get(Items.class), 1) + .getItem()); + repThis.addNewItem(Names.NUGGET, + new ItemStack((Item) Items.class + .getField(name.toLowerCase() + "Nugget").get(Items.class), + 1).getItem()); + + materials.add(repThis); + } + } + } catch (final Exception ex) { + throw new Error(ex.getMessage()); + // do nought + } + } + } } \ No newline at end of file diff --git a/src/main/java/com/mcmoddev/basemetals/integration/plugins/Thaumcraft.java b/src/main/java/zone/moddev/mc/basemetals/integration/plugins/Thaumcraft.java similarity index 81% rename from src/main/java/com/mcmoddev/basemetals/integration/plugins/Thaumcraft.java rename to src/main/java/zone/moddev/mc/basemetals/integration/plugins/Thaumcraft.java index 6416f256c..ecb6e9cec 100644 --- a/src/main/java/com/mcmoddev/basemetals/integration/plugins/Thaumcraft.java +++ b/src/main/java/zone/moddev/mc/basemetals/integration/plugins/Thaumcraft.java @@ -1,6 +1,6 @@ -package com.mcmoddev.basemetals.integration.plugins; +package zone.moddev.mc.basemetals.integration.plugins; -import com.mcmoddev.basemetals.BaseMetals; +import zone.moddev.mc.basemetals.BaseMetals; import com.mcmoddev.lib.integration.IIntegration; import com.mcmoddev.lib.integration.MMDPlugin; import com.mcmoddev.lib.util.ConfigBase.Options; diff --git a/src/main/java/com/mcmoddev/basemetals/integration/plugins/ThermalExpansion.java b/src/main/java/zone/moddev/mc/basemetals/integration/plugins/ThermalExpansion.java old mode 100755 new mode 100644 similarity index 93% rename from src/main/java/com/mcmoddev/basemetals/integration/plugins/ThermalExpansion.java rename to src/main/java/zone/moddev/mc/basemetals/integration/plugins/ThermalExpansion.java index db09d1480..e8a9975d9 --- a/src/main/java/com/mcmoddev/basemetals/integration/plugins/ThermalExpansion.java +++ b/src/main/java/zone/moddev/mc/basemetals/integration/plugins/ThermalExpansion.java @@ -1,94 +1,94 @@ -package com.mcmoddev.basemetals.integration.plugins; - -import java.util.Arrays; -import java.util.List; - -import com.mcmoddev.basemetals.BaseMetals; -import com.mcmoddev.basemetals.data.MaterialNames; -import com.mcmoddev.lib.data.Names; -import com.mcmoddev.lib.init.Materials; -import com.mcmoddev.lib.integration.IIntegration; -import com.mcmoddev.lib.integration.MMDPlugin; -import com.mcmoddev.lib.integration.plugins.ThermalExpansionBase; -import com.mcmoddev.lib.material.MMDMaterial; -import com.mcmoddev.lib.util.ConfigBase.Options; - -import cofh.api.util.ThermalExpansionHelper; - -@MMDPlugin(addonId = BaseMetals.MODID, pluginId = ThermalExpansion.PLUGIN_MODID) -public final class ThermalExpansion extends ThermalExpansionBase implements IIntegration { - - @Override - public void init() { - if (!Options.isModEnabled(PLUGIN_MODID)) { - return; - } - - final List materials = Arrays.asList(MaterialNames.ADAMANTINE, - MaterialNames.ANTIMONY, MaterialNames.AQUARIUM, MaterialNames.BISMUTH, - MaterialNames.BRASS, MaterialNames.COLDIRON, MaterialNames.CUPRONICKEL, - MaterialNames.PEWTER, MaterialNames.STARSTEEL, MaterialNames.ZINC, - MaterialNames.MERCURY); - - materials.stream().filter(Materials::hasMaterial) - .filter(materialName -> !Materials.getMaterialByName(materialName).isEmpty()) - .forEach(materialName -> { - addFurnace(materialName); - addCrucible(materialName); - addPlatePress(materialName); - addPressStorage(materialName); - addPulverizer(materialName); - }); - - final MMDMaterial brass = Materials.getMaterialByName(MaterialNames.BRASS); - final MMDMaterial copper = Materials.getMaterialByName(MaterialNames.COPPER); - final MMDMaterial cupronickel = Materials.getMaterialByName(MaterialNames.CUPRONICKEL); - final MMDMaterial nickel = Materials.getMaterialByName(MaterialNames.NICKEL); - final MMDMaterial zinc = Materials.getMaterialByName(MaterialNames.ZINC); - final MMDMaterial tin = Materials.getMaterialByName(MaterialNames.TIN); - final MMDMaterial bronze = Materials.getMaterialByName(MaterialNames.BRONZE); - - if (hasMaterials(MaterialNames.COPPER, MaterialNames.ZINC, MaterialNames.BRASS) - && materialsHaveItems(Arrays.asList(MaterialNames.COPPER, MaterialNames.ZINC, - MaterialNames.BRASS), Names.INGOT.toString())) { - ThermalExpansionHelper.addSmelterRecipe(4000, copper.getItemStack(Names.INGOT, 2), - zinc.getItemStack(Names.INGOT, 1), brass.getItemStack(Names.INGOT, 3)); - } - - if (hasMaterials(MaterialNames.COPPER, MaterialNames.NICKEL, MaterialNames.CUPRONICKEL) - && materialsHaveItems(Arrays.asList(MaterialNames.COPPER, MaterialNames.NICKEL, - MaterialNames.CUPRONICKEL), Names.INGOT.toString())) { - ThermalExpansionHelper.addSmelterRecipe(4000, copper.getItemStack(Names.INGOT, 3), - nickel.getItemStack(Names.INGOT, 1), cupronickel.getItemStack(Names.INGOT, 4)); - } - - if (hasMaterials(MaterialNames.COPPER, MaterialNames.TIN, MaterialNames.BRONZE) - && materialsHaveItems(Arrays.asList(MaterialNames.COPPER, MaterialNames.TIN, - MaterialNames.BRONZE), Names.INGOT.toString())) { - ThermalExpansionHelper.addSmelterRecipe(4000, copper.getItemStack(Names.INGOT, 3), - tin.getItemStack(Names.INGOT, 1), bronze.getItemStack(Names.INGOT, 3)); - } - } - - private static boolean materialsHaveItems(final List materialNames, - final String... items) { - for (final String item : items) { - for (final String materialName : materialNames) { - if (!Materials.getMaterialByName(materialName) - .hasItem(item)) { - return false; - } - } - } - return true; - } - - private static boolean hasMaterials(final String... materials) { - for (final String materialName : materials) { - if (!Materials.hasMaterial(materialName)) { - return false; - } - } - return true; - } -} +package zone.moddev.mc.basemetals.integration.plugins; + +import java.util.Arrays; +import java.util.List; + +import zone.moddev.mc.basemetals.BaseMetals; +import zone.moddev.mc.basemetals.data.MaterialNames; +import com.mcmoddev.lib.data.Names; +import com.mcmoddev.lib.init.Materials; +import com.mcmoddev.lib.integration.IIntegration; +import com.mcmoddev.lib.integration.MMDPlugin; +import com.mcmoddev.lib.integration.plugins.ThermalExpansionBase; +import com.mcmoddev.lib.material.MMDMaterial; +import com.mcmoddev.lib.util.ConfigBase.Options; + +import cofh.api.util.ThermalExpansionHelper; + +@MMDPlugin(addonId = BaseMetals.MODID, pluginId = ThermalExpansion.PLUGIN_MODID) +public final class ThermalExpansion extends ThermalExpansionBase implements IIntegration { + + @Override + public void init() { + if (!Options.isModEnabled(PLUGIN_MODID)) { + return; + } + + final List materials = Arrays.asList(MaterialNames.ADAMANTINE, + MaterialNames.ANTIMONY, MaterialNames.AQUARIUM, MaterialNames.BISMUTH, + MaterialNames.BRASS, MaterialNames.COLDIRON, MaterialNames.CUPRONICKEL, + MaterialNames.PEWTER, MaterialNames.STARSTEEL, MaterialNames.ZINC, + MaterialNames.MERCURY); + + materials.stream().filter(Materials::hasMaterial) + .filter(materialName -> !Materials.getMaterialByName(materialName).isEmpty()) + .forEach(materialName -> { + addFurnace(materialName); + addCrucible(materialName); + addPlatePress(materialName); + addPressStorage(materialName); + addPulverizer(materialName); + }); + + final MMDMaterial brass = Materials.getMaterialByName(MaterialNames.BRASS); + final MMDMaterial copper = Materials.getMaterialByName(MaterialNames.COPPER); + final MMDMaterial cupronickel = Materials.getMaterialByName(MaterialNames.CUPRONICKEL); + final MMDMaterial nickel = Materials.getMaterialByName(MaterialNames.NICKEL); + final MMDMaterial zinc = Materials.getMaterialByName(MaterialNames.ZINC); + final MMDMaterial tin = Materials.getMaterialByName(MaterialNames.TIN); + final MMDMaterial bronze = Materials.getMaterialByName(MaterialNames.BRONZE); + + if (hasMaterials(MaterialNames.COPPER, MaterialNames.ZINC, MaterialNames.BRASS) + && materialsHaveItems(Arrays.asList(MaterialNames.COPPER, MaterialNames.ZINC, + MaterialNames.BRASS), Names.INGOT.toString())) { + ThermalExpansionHelper.addSmelterRecipe(4000, copper.getItemStack(Names.INGOT, 2), + zinc.getItemStack(Names.INGOT, 1), brass.getItemStack(Names.INGOT, 3)); + } + + if (hasMaterials(MaterialNames.COPPER, MaterialNames.NICKEL, MaterialNames.CUPRONICKEL) + && materialsHaveItems(Arrays.asList(MaterialNames.COPPER, MaterialNames.NICKEL, + MaterialNames.CUPRONICKEL), Names.INGOT.toString())) { + ThermalExpansionHelper.addSmelterRecipe(4000, copper.getItemStack(Names.INGOT, 3), + nickel.getItemStack(Names.INGOT, 1), cupronickel.getItemStack(Names.INGOT, 4)); + } + + if (hasMaterials(MaterialNames.COPPER, MaterialNames.TIN, MaterialNames.BRONZE) + && materialsHaveItems(Arrays.asList(MaterialNames.COPPER, MaterialNames.TIN, + MaterialNames.BRONZE), Names.INGOT.toString())) { + ThermalExpansionHelper.addSmelterRecipe(4000, copper.getItemStack(Names.INGOT, 3), + tin.getItemStack(Names.INGOT, 1), bronze.getItemStack(Names.INGOT, 3)); + } + } + + private static boolean materialsHaveItems(final List materialNames, + final String... items) { + for (final String item : items) { + for (final String materialName : materialNames) { + if (!Materials.getMaterialByName(materialName) + .hasItem(item)) { + return false; + } + } + } + return true; + } + + private static boolean hasMaterials(final String... materials) { + for (final String materialName : materials) { + if (!Materials.hasMaterial(materialName)) { + return false; + } + } + return true; + } +} diff --git a/src/main/java/com/mcmoddev/basemetals/integration/plugins/TinkersConstruct.java b/src/main/java/zone/moddev/mc/basemetals/integration/plugins/TinkersConstruct.java similarity index 96% rename from src/main/java/com/mcmoddev/basemetals/integration/plugins/TinkersConstruct.java rename to src/main/java/zone/moddev/mc/basemetals/integration/plugins/TinkersConstruct.java index 44f40bc08..37ba1b320 100644 --- a/src/main/java/com/mcmoddev/basemetals/integration/plugins/TinkersConstruct.java +++ b/src/main/java/zone/moddev/mc/basemetals/integration/plugins/TinkersConstruct.java @@ -1,8 +1,8 @@ -package com.mcmoddev.basemetals.integration.plugins; +package zone.moddev.mc.basemetals.integration.plugins; -import com.mcmoddev.basemetals.BaseMetals; -import com.mcmoddev.basemetals.data.MaterialNames; -import com.mcmoddev.basemetals.data.TraitNames; +import zone.moddev.mc.basemetals.BaseMetals; +import zone.moddev.mc.basemetals.data.MaterialNames; +import zone.moddev.mc.basemetals.data.TraitNames; import com.mcmoddev.lib.data.Names; import com.mcmoddev.lib.init.Materials; import com.mcmoddev.lib.integration.IIntegration; diff --git a/src/main/java/com/mcmoddev/basemetals/integration/plugins/VeinMiner.java b/src/main/java/zone/moddev/mc/basemetals/integration/plugins/VeinMiner.java similarity index 91% rename from src/main/java/com/mcmoddev/basemetals/integration/plugins/VeinMiner.java rename to src/main/java/zone/moddev/mc/basemetals/integration/plugins/VeinMiner.java index 63074dd2d..98bb8597e 100644 --- a/src/main/java/com/mcmoddev/basemetals/integration/plugins/VeinMiner.java +++ b/src/main/java/zone/moddev/mc/basemetals/integration/plugins/VeinMiner.java @@ -1,12 +1,12 @@ -package com.mcmoddev.basemetals.integration.plugins; +package zone.moddev.mc.basemetals.integration.plugins; import java.util.Arrays; import java.util.List; import javax.annotation.Nonnull; -import com.mcmoddev.basemetals.BaseMetals; -import com.mcmoddev.basemetals.data.MaterialNames; +import zone.moddev.mc.basemetals.BaseMetals; +import zone.moddev.mc.basemetals.data.MaterialNames; import com.mcmoddev.lib.init.Materials; import com.mcmoddev.lib.integration.IIntegration; import com.mcmoddev.lib.integration.MMDPlugin; diff --git a/src/main/java/com/mcmoddev/basemetals/items/MMDToolEffects.java b/src/main/java/zone/moddev/mc/basemetals/items/MMDToolEffects.java similarity index 99% rename from src/main/java/com/mcmoddev/basemetals/items/MMDToolEffects.java rename to src/main/java/zone/moddev/mc/basemetals/items/MMDToolEffects.java index 237d42b19..5d1bdeb25 100644 --- a/src/main/java/com/mcmoddev/basemetals/items/MMDToolEffects.java +++ b/src/main/java/zone/moddev/mc/basemetals/items/MMDToolEffects.java @@ -1,4 +1,4 @@ -package com.mcmoddev.basemetals.items; +package zone.moddev.mc.basemetals.items; import java.util.HashMap; import java.util.Iterator; @@ -7,8 +7,8 @@ import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; -import com.mcmoddev.basemetals.data.AchievementNames; -import com.mcmoddev.basemetals.data.MaterialNames; +import zone.moddev.mc.basemetals.data.AchievementNames; +import zone.moddev.mc.basemetals.data.MaterialNames; import com.mcmoddev.lib.data.Names; import com.mcmoddev.lib.data.SharedStrings; import com.mcmoddev.lib.init.Achievements; diff --git a/src/main/java/com/mcmoddev/basemetals/proxy/ClientProxy.java b/src/main/java/zone/moddev/mc/basemetals/proxy/ClientProxy.java similarity index 96% rename from src/main/java/com/mcmoddev/basemetals/proxy/ClientProxy.java rename to src/main/java/zone/moddev/mc/basemetals/proxy/ClientProxy.java index bd68d4e79..7a9de9d3f 100644 --- a/src/main/java/com/mcmoddev/basemetals/proxy/ClientProxy.java +++ b/src/main/java/zone/moddev/mc/basemetals/proxy/ClientProxy.java @@ -1,6 +1,6 @@ -package com.mcmoddev.basemetals.proxy; +package zone.moddev.mc.basemetals.proxy; -import com.mcmoddev.basemetals.BaseMetals; +import zone.moddev.mc.basemetals.BaseMetals; import com.mcmoddev.lib.client.registrations.RegistrationHelper; import com.mcmoddev.lib.client.renderer.RenderCustomArrow; import com.mcmoddev.lib.client.renderer.RenderCustomBolt; diff --git a/src/main/java/zone/moddev/mc/basemetals/proxy/CommonProxy.java b/src/main/java/zone/moddev/mc/basemetals/proxy/CommonProxy.java new file mode 100644 index 000000000..1ebff500c --- /dev/null +++ b/src/main/java/zone/moddev/mc/basemetals/proxy/CommonProxy.java @@ -0,0 +1,139 @@ +package zone.moddev.mc.basemetals.proxy; + +import java.util.HashMap; +import java.util.Map; + +import zone.moddev.mc.basemetals.BaseMetals; +import zone.moddev.mc.basemetals.data.MaterialNames; +import zone.moddev.mc.basemetals.init.Achievements; +import zone.moddev.mc.basemetals.init.Blocks; +import zone.moddev.mc.basemetals.init.Fluids; +import zone.moddev.mc.basemetals.init.ItemGroups; +import zone.moddev.mc.basemetals.init.Items; +import zone.moddev.mc.basemetals.init.Materials; +import zone.moddev.mc.basemetals.init.Recipes; +import zone.moddev.mc.basemetals.init.VillagerTrades; +import zone.moddev.mc.basemetals.util.Config; +import zone.moddev.mc.basemetals.util.EventHandler; +import zone.moddev.mc.basemetals.util.LegacyMappings; +import zone.moddev.mc.basemetals.worldgen.BaseMetalsWorldgenProvider; +import com.mcmoddev.lib.fuels.FuelRegistry; +import com.mcmoddev.lib.integration.IntegrationManager; +import com.mcmoddev.lib.util.ConfigBase; + +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.common.event.FMLInitializationEvent; +import net.minecraftforge.fml.common.event.FMLMissingMappingsEvent; +import net.minecraftforge.fml.common.event.FMLMissingMappingsEvent.MissingMapping; +import net.minecraftforge.fml.common.event.FMLPostInitializationEvent; +import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; +import net.minecraftforge.fml.common.registry.ForgeRegistries; +import net.minecraftforge.fml.common.registry.GameData; +import net.minecraftforge.fml.common.registry.GameRegistry; + +/** + * Base Metals Common Proxy. + * + * @author Jasmine Iwanek + * + */ +public class CommonProxy { + + private boolean allsGood = false; + + public boolean allsGood() { + return this.allsGood; + } + + public void preInit(final FMLPreInitializationEvent event) { + + Config.init(); + + com.mcmoddev.lib.init.Materials.init(); + Materials.init(); + FuelRegistry.init(); + Fluids.init(); + ItemGroups.init(); + com.mcmoddev.lib.init.Blocks.init(); + Blocks.init(); + com.mcmoddev.lib.init.Items.init(); + Items.init(); + + cyano.basemetals.init.Blocks.bind(); + cyano.basemetals.init.Items.bind(); + + VillagerTrades.init(); + + IntegrationManager.INSTANCE.preInit(event); + IntegrationManager.INSTANCE.runCallbacks("preInit"); + this.allsGood = true; + } + + public void onRemap(final FMLMissingMappingsEvent event) { + for (final MissingMapping mapping : event.get()) { + if (mapping.resourceLocation.getResourceDomain().equals(BaseMetals.MODID)) { + if (mapping.type.equals(GameRegistry.Type.BLOCK)) { + final ResourceLocation mercury = new ResourceLocation(BaseMetals.MODID, "mercury"); + + if ("liquid_mercury".equals(mapping.resourceLocation.getResourcePath()) + && ForgeRegistries.BLOCKS.containsKey(mercury)) { + mapping.remap(ForgeRegistries.BLOCKS.getValue(mercury)); + } + } else if (mapping.type.equals(GameRegistry.Type.ITEM)) { + final String replacement = LegacyMappings.itemReplacement( + mapping.resourceLocation.getResourcePath()); + + if (replacement != null) { + final ResourceLocation target = new ResourceLocation(BaseMetals.MODID, replacement); + + // Disabled materials must not be remapped to the registry's default item. + if (ForgeRegistries.ITEMS.containsKey(target)) { + // Cyano saved two item IDs for each door. + // Add the alias after Forge loads the world's registry. + mapping.ignore(); + } + } + } + } + } + } + + public void onIdMapping() { + final Map aliases = new HashMap<>(); + + for (final Map.Entry replacement : LegacyMappings.itemReplacements().entrySet()) { + final ResourceLocation target = new ResourceLocation(BaseMetals.MODID, replacement.getValue()); + + if (ForgeRegistries.ITEMS.containsKey(target)) { + aliases.put(new ResourceLocation(BaseMetals.MODID, replacement.getKey()), target); + } + } + + // Forge has now installed the world's registry. + // Save the aliases for inventories in unopened chunks too. + GameData.getItemRegistry().loadAliases(aliases); + } + + public void init(final FMLInitializationEvent event) { + this.allsGood = false; + BaseMetalsWorldgenProvider.enqueue(); + com.mcmoddev.lib.init.Recipes.init(); + Recipes.init(); + + Achievements.init(); + FuelRegistry.register(); + IntegrationManager.INSTANCE.runCallbacks("init"); + MinecraftForge.EVENT_BUS.register(new EventHandler()); + this.allsGood = true; + + ItemGroups.setupIcons(MaterialNames.STARSTEEL); + } + + public void postInit(final FMLPostInitializationEvent event) { + this.allsGood = false; + IntegrationManager.INSTANCE.runCallbacks("postInit"); + ConfigBase.postInit(); + this.allsGood = true; + } +} diff --git a/src/main/java/com/mcmoddev/basemetals/proxy/ServerProxy.java b/src/main/java/zone/moddev/mc/basemetals/proxy/ServerProxy.java similarity index 78% rename from src/main/java/com/mcmoddev/basemetals/proxy/ServerProxy.java rename to src/main/java/zone/moddev/mc/basemetals/proxy/ServerProxy.java index 79f2c1260..f236ff0d9 100644 --- a/src/main/java/com/mcmoddev/basemetals/proxy/ServerProxy.java +++ b/src/main/java/zone/moddev/mc/basemetals/proxy/ServerProxy.java @@ -1,4 +1,4 @@ -package com.mcmoddev.basemetals.proxy; +package zone.moddev.mc.basemetals.proxy; /** * Base Metals Server Proxy. diff --git a/src/main/java/com/mcmoddev/basemetals/util/Config.java b/src/main/java/zone/moddev/mc/basemetals/util/Config.java similarity index 97% rename from src/main/java/com/mcmoddev/basemetals/util/Config.java rename to src/main/java/zone/moddev/mc/basemetals/util/Config.java index 6a5a039d7..808b8b8b9 100644 --- a/src/main/java/com/mcmoddev/basemetals/util/Config.java +++ b/src/main/java/zone/moddev/mc/basemetals/util/Config.java @@ -1,9 +1,9 @@ -package com.mcmoddev.basemetals.util; +package zone.moddev.mc.basemetals.util; import java.io.File; -import com.mcmoddev.basemetals.BaseMetals; -import com.mcmoddev.basemetals.data.MaterialNames; +import zone.moddev.mc.basemetals.BaseMetals; +import zone.moddev.mc.basemetals.data.MaterialNames; import com.mcmoddev.lib.data.ConfigKeys; import com.mcmoddev.lib.integration.plugins.DenseOresBase; import com.mcmoddev.lib.integration.plugins.EnderIOBase; @@ -73,9 +73,6 @@ public static void init() { true, "If true, then Base Metals will scan the Ore Dictionary to automatically add a \n" + "Crack Hammer recipe for every material that has an ore, dust, and ingot.")); - Options.setRequireMMDOreSpawn(configuration.getBoolean("using_orespawn", GENERAL_CAT, true, - "If false, then Base Metals will not require MMD Ore Spawn mod. \n" - + "Set to false if using another mod to manually handle ore generation.")); Options.setEnableAchievements(configuration.getBoolean("achievements", GENERAL_CAT, true, "If false, then Base Metals Achievements will be disabled (This is currently required if you disable any metals")); Options.setCrackHammerFullStack( @@ -89,9 +86,6 @@ public static void init() { Options.setTwoDustDrop(configuration.getBoolean("hammer_produces_two_dust", GENERAL_CAT, true, "If true, then the crack hammer when crushing ingots/ores two dust will drop")); - // fallback orespawn can live as a 'thingEnabled' for a bit... - Options.setFallbackOreSpawn(configuration.getBoolean("fallback_orespawn", GENERAL_CAT, true, - "disable this and using_orespawn to make MMDLib based mods not spawn any ores")); // INTEGRATION Options.modEnabled(EnderIOBase.PLUGIN_MODID, diff --git a/src/main/java/com/mcmoddev/basemetals/util/EventHandler.java b/src/main/java/zone/moddev/mc/basemetals/util/EventHandler.java similarity index 75% rename from src/main/java/com/mcmoddev/basemetals/util/EventHandler.java rename to src/main/java/zone/moddev/mc/basemetals/util/EventHandler.java index 263cf2c24..96e1add3f 100644 --- a/src/main/java/com/mcmoddev/basemetals/util/EventHandler.java +++ b/src/main/java/zone/moddev/mc/basemetals/util/EventHandler.java @@ -1,9 +1,9 @@ -package com.mcmoddev.basemetals.util; +package zone.moddev.mc.basemetals.util; import java.util.List; -import com.mcmoddev.basemetals.data.AchievementNames; -import com.mcmoddev.basemetals.data.MaterialNames; +import zone.moddev.mc.basemetals.data.AchievementNames; +import zone.moddev.mc.basemetals.data.MaterialNames; import com.mcmoddev.lib.init.Achievements; import com.mcmoddev.lib.item.ItemMMDBlend; import com.mcmoddev.lib.item.ItemMMDIngot; @@ -13,11 +13,6 @@ import com.mcmoddev.lib.recipe.ShieldUpgradeRecipe; import com.mcmoddev.lib.util.ConfigBase.Options; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.FontRenderer; -import net.minecraft.client.gui.GuiMainMenu; -import net.minecraft.client.gui.GuiScreen; -import net.minecraft.client.gui.GuiWorldSelection; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.SoundEvents; import net.minecraft.inventory.Container; @@ -33,13 +28,10 @@ import net.minecraftforge.event.ForgeEventFactory; import net.minecraftforge.event.entity.living.LivingAttackEvent; import net.minecraftforge.fml.common.FMLCommonHandler; -import net.minecraftforge.fml.common.Loader; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.gameevent.PlayerEvent.ItemCraftedEvent; import net.minecraftforge.fml.common.gameevent.PlayerEvent.ItemSmeltedEvent; -import net.minecraftforge.fml.common.gameevent.TickEvent; import net.minecraftforge.fml.relauncher.Side; -import net.minecraftforge.fml.relauncher.SideOnly; public class EventHandler { @@ -182,52 +174,4 @@ public void handleAnvilEvent(final AnvilUpdateEvent event) { } } - /** - * - * @param event - */ - @SideOnly(Side.CLIENT) - @SubscribeEvent - public void onUpdate(final TickEvent.RenderTickEvent event) { - if ((Options.requireMMDOreSpawn()) && (Loader.isModLoaded("orespawn"))) { - return; - } - - if (!Options.fallbackOrespawn()) { - return; - } - - if (!Options.requireMMDOreSpawn()) { - return; - } - - final Minecraft minecraft = Minecraft.getMinecraft(); - final GuiScreen guiscreen = minecraft.currentScreen; - if (guiscreen == null) { - return; - } - final FontRenderer fontRender = minecraft.fontRendererObj; - final int y = (guiscreen.height / 100) * 2; - int x = (guiscreen.width / 2); - - if (guiscreen instanceof GuiMainMenu) { - guiscreen.drawCenteredString(fontRender, - "MMD OreSpawn not present, but requested in configuration, using fallback generator!", - x, y, 0xffffff00); - } else if (guiscreen instanceof GuiWorldSelection) { - x = 10; - final int widest = fontRender.getStringWidth( - "This is likely not what you want - try turning off the 'using_orespawn' option"); - final int shortest = fontRender.getStringWidth("Fallback Ore Spawn Generator Enabled!"); - int wrap = widest + 50; - - if ((guiscreen.width / 2) <= wrap) { - wrap = shortest + 50; - } - - fontRender.drawSplitString( - "Fallback Ore Spawn Generator Enabled!\nThis is likely not what you want - try turning off the 'using_orespawn' option\n(or install MMD OreSpawn)", - x, y, wrap, 0xFFFFFF00); - } - } } diff --git a/src/main/java/zone/moddev/mc/basemetals/util/LegacyMappings.java b/src/main/java/zone/moddev/mc/basemetals/util/LegacyMappings.java new file mode 100644 index 000000000..027452588 --- /dev/null +++ b/src/main/java/zone/moddev/mc/basemetals/util/LegacyMappings.java @@ -0,0 +1,36 @@ +package zone.moddev.mc.basemetals.util; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +/** Registry names used by the original 1.10 release. */ +public final class LegacyMappings { + + private static final Map ITEMS; + + static { + final Map items = new LinkedHashMap<>(); + items.put("carbon_powder", "coal_powder"); + items.put("liquid_mercury", "mercury"); + + for (final String material : new String[] { "adamantine", "aquarium", "brass", "bronze", + "coldiron", "copper", "cupronickel", "electrum", "invar", "lead", "mithril", + "nickel", "platinum", "silver", "starsteel", "steel", "tin" }) { + items.put(material + "_door_item", material + "_door"); + } + + ITEMS = Collections.unmodifiableMap(items); + } + + private LegacyMappings() { + } + + public static String itemReplacement(final String oldPath) { + return ITEMS.get(oldPath); + } + + public static Map itemReplacements() { + return ITEMS; + } +} diff --git a/src/main/java/com/mcmoddev/basemetals/util/VillagerTradeHelper.java b/src/main/java/zone/moddev/mc/basemetals/util/VillagerTradeHelper.java similarity index 98% rename from src/main/java/com/mcmoddev/basemetals/util/VillagerTradeHelper.java rename to src/main/java/zone/moddev/mc/basemetals/util/VillagerTradeHelper.java index 8c7c0880b..bdd57737d 100644 --- a/src/main/java/com/mcmoddev/basemetals/util/VillagerTradeHelper.java +++ b/src/main/java/zone/moddev/mc/basemetals/util/VillagerTradeHelper.java @@ -1,4 +1,4 @@ -package com.mcmoddev.basemetals.util; +package zone.moddev.mc.basemetals.util; import net.minecraft.entity.passive.EntityVillager; import net.minecraft.util.ResourceLocation; diff --git a/src/main/java/zone/moddev/mc/basemetals/worldgen/BaseMetalsWorldgenProvider.java b/src/main/java/zone/moddev/mc/basemetals/worldgen/BaseMetalsWorldgenProvider.java new file mode 100644 index 000000000..45e93863f --- /dev/null +++ b/src/main/java/zone/moddev/mc/basemetals/worldgen/BaseMetalsWorldgenProvider.java @@ -0,0 +1,98 @@ +package zone.moddev.mc.basemetals.worldgen; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.fml.common.registry.ForgeRegistries; +import zone.moddev.mc.basemetals.BaseMetals; +import zone.moddev.mc.orespawn.api.GeologyFamily; +import zone.moddev.mc.orespawn.api.OreDimensionSelector; +import zone.moddev.mc.orespawn.api.OrePattern; +import zone.moddev.mc.orespawn.api.OreSpawnApi; +import zone.moddev.mc.orespawn.api.WorldgenProvider; + +/** Declares the historical ore rules; OreSpawn owns placement and config import. */ +public final class BaseMetalsWorldgenProvider { + + private BaseMetalsWorldgenProvider() { + } + + public static void enqueue() { + build(ForgeRegistries.BLOCKS::containsKey).ifPresent(provider -> { + if (!OreSpawnApi.enqueue(provider)) { + throw new IllegalStateException("Base Metals' OreSpawn provider was registered twice"); + } + }); + } + + public static Optional build(final Predicate available) { + final List ores = new ArrayList<>(); + final ResourceLocation nether = id("minecraft", "the_nether"); + final ResourceLocation end = id("minecraft", "the_end"); + + addOre(ores, available, "coldiron", nether, 0, 127, 5.0D); + addOre(ores, available, "adamantine", nether, 0, 127, 2.0D); + addOre(ores, available, "starsteel", end, 0, 254, 5.0D); + addOre(ores, available, "copper", null, 0, 95, 10.0D); + addOre(ores, available, "silver", null, 0, 31, 4.0D); + addOre(ores, available, "tin", null, 0, 127, 10.0D); + addOre(ores, available, "lead", null, 0, 63, 5.0D); + addOre(ores, available, "zinc", null, 0, 95, 5.0D); + addOre(ores, available, "mercury", null, 0, 31, 3.0D); + addOre(ores, available, "nickel", null, 32, 95, 1.0D); + addOre(ores, available, "platinum", null, 1, 31, 0.125D); + + // A pack may disable every ore-bearing material in BaseMetals.cfg. + if (ores.isEmpty()) return Optional.empty(); + + final WorldgenProvider.Builder provider = WorldgenProvider.builder(BaseMetals.MODID, 1); + ores.forEach(provider::ore); + return Optional.of(provider.build()); + } + + private static void addOre(final List ores, + final Predicate available, final String material, + final ResourceLocation dimension, final int minY, final int maxY, final double attempts) { + final ResourceLocation block = id(BaseMetals.MODID, material + "_ore"); + if (!available.test(block)) return; + + final WorldgenProvider.OreDefinition.Builder ore = WorldgenProvider.OreDefinition + .builder(id(BaseMetals.MODID, "legacy/" + material + "_ore"), block) + .material(id("orespawn", material)) + .nativeGeneration(false) + .retrogen(false) + .output(block, 100.0D); + + if (dimension == null) { + ore.dimensionSelector(OreDimensionSelector.ALL_EXCEPT_NETHER_AND_END, + placement -> configure(placement, minY, maxY, attempts)); + } else { + ore.dimension(dimension, placement -> configure(placement, minY, maxY, attempts)); + } + + ores.add(ore.build()); + } + + private static void configure(final WorldgenProvider.OreDimensionDefinition.Builder placement, + final int minY, final int maxY, final double attempts) { + placement.yRange(minY, maxY) + .attempts(attempts) + .quantityRange(4, 11) + .pattern(OrePattern.DEFAULT) + .placementChannel(id("orespawn", "standard")) + .hostBlock(id("minecraft", "stone")) + .hostBlock(id("minecraft", "netherrack")) + .hostBlock(id("minecraft", "end_stone")); + + // This also permits the ore-replaceable rocks supplied by Mineralogy. + for (final GeologyFamily family : GeologyFamily.values()) { + placement.hostFamily(family); + } + } + + private static ResourceLocation id(final String namespace, final String path) { + return new ResourceLocation(namespace, path); + } +} diff --git a/src/main/resources/META-INF/basemetals-legacy-api.list b/src/main/resources/META-INF/basemetals-legacy-api.list new file mode 100644 index 000000000..6d7f05237 --- /dev/null +++ b/src/main/resources/META-INF/basemetals-legacy-api.list @@ -0,0 +1,29 @@ +BaseMetals +data/AchievementNames +data/MaterialNames +data/TraitNames +init/Achievements +init/Blocks +init/Fluids +init/ItemGroups +init/Items +init/Materials +init/Recipes +init/VillagerTrades +integration/plugins/DenseOres +integration/plugins/EnderIO +integration/plugins/IC2 +integration/plugins/Mekanism +integration/plugins/TAIGA +integration/plugins/Thaumcraft +integration/plugins/ThermalExpansion +integration/plugins/TinkersConstruct +integration/plugins/VeinMiner +items/MMDToolEffects +proxy/ClientProxy +proxy/CommonProxy +proxy/ServerProxy +util/Config +util/EventHandler +util/LegacyMappings +util/VillagerTradeHelper diff --git a/src/main/resources/assets/basemetals/models/block/desktop.ini b/src/main/resources/assets/basemetals/models/block/desktop.ini deleted file mode 100644 index 890a90500..000000000 --- a/src/main/resources/assets/basemetals/models/block/desktop.ini +++ /dev/null @@ -1,17 +0,0 @@ -[LocalizedFileNames] -zinc_inner_stairs.json=@zinc_inner_stairs.json,0 -tin_inner_stairs.json=@tin_inner_stairs.json,0 -starsteel_inner_stairs.json=@starsteel_inner_stairs.json,0 -steel_inner_stairs.json=@steel_inner_stairs.json,0 -silver_inner_stairs.json=@silver_inner_stairs.json,0 -platinum_inner_stairs.json=@platinum_inner_stairs.json,0 -nickel_inner_stairs.json=@nickel_inner_stairs.json,0 -mithril_inner_stairs.json=@mithril_inner_stairs.json,0 -lead_inner_stairs.json=@lead_inner_stairs.json,0 -invar_inner_stairs.json=@invar_inner_stairs.json,0 -electrum_inner_stairs.json=@electrum_inner_stairs.json,0 -cupronickel_inner_stairs.json=@cupronickel_inner_stairs.json,0 -copper_inner_stairs.json=@copper_inner_stairs.json,0 -coldiron_inner_stairs.json=@coldiron_inner_stairs.json,0 -bronze_inner_stairs.json=@bronze_inner_stairs.json,0 -brass_inner_stairs.json=@brass_inner_stairs.json,0 diff --git a/src/main/resources/mcmod.info b/src/main/resources/mcmod.info index aa9d28e38..4cd9edabe 100644 --- a/src/main/resources/mcmod.info +++ b/src/main/resources/mcmod.info @@ -11,6 +11,7 @@ "credits": "Cyanobacterium (aka DrCyano) For starting the project & geologists everywhere. SeqSEE for helping with shields.", "logoFile": "", "screenshots": [], - "dependencies": [] + "requiredMods": ["orespawn@[4.1.2.110021,5.0.0)"], + "dependencies": ["orespawn@[4.1.2.110021,5.0.0)"] } ] diff --git a/src/test/java/zone/moddev/mc/basemetals/compat/LegacyNamespaceTransformerTests.java b/src/test/java/zone/moddev/mc/basemetals/compat/LegacyNamespaceTransformerTests.java new file mode 100644 index 000000000..9ff4efb69 --- /dev/null +++ b/src/test/java/zone/moddev/mc/basemetals/compat/LegacyNamespaceTransformerTests.java @@ -0,0 +1,87 @@ +package zone.moddev.mc.basemetals.compat; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.FieldNode; +import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.MethodNode; + +class LegacyNamespaceTransformerTests { + + @Test + void linksOldTypesFieldsInheritanceAndReflectionToCurrentClasses() { + final byte[] output = new LegacyNamespaceTransformer().transform("example.Consumer", null, fixture()); + final ClassNode type = new ClassNode(); + new ClassReader(output).accept(type, 0); + assertEquals("example/Consumer", type.name); + assertEquals("zone/moddev/mc/basemetals/proxy/CommonProxy", type.superName); + assertEquals("Lzone/moddev/mc/basemetals/BaseMetals;", ((FieldNode) type.fields.get(0)).desc); + + final MethodNode method = (MethodNode) type.methods.get(0); + assertEquals("zone/moddev/mc/basemetals/BaseMetals", ((FieldInsnNode) method.instructions.get(0)).owner); + assertEquals("zone.moddev.mc.basemetals.init.Materials", ((LdcInsnNode) method.instructions.get(2)).cst); + assertEquals(Type.getObjectType("zone/moddev/mc/basemetals/util/Config$Options"), + ((LdcInsnNode) method.instructions.get(4)).cst); + assertEquals("other.mod/textures/ore.png", ((LdcInsnNode) method.instructions.get(6)).cst); + } + + @Test + void doesNotRelocateAddonOwnedClassesInsideTheHistoricalPackage() { + final ClassWriter source = new ClassWriter(0); + source.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "com/mcmoddev/basemetals/addon/Extra", null, + "com/mcmoddev/basemetals/proxy/CommonProxy", null); + source.visitEnd(); + + final ClassNode type = new ClassNode(); + new ClassReader(new LegacyNamespaceTransformer().transform("com.mcmoddev.basemetals.addon.Extra", null, + source.toByteArray())).accept(type, 0); + assertEquals("com/mcmoddev/basemetals/addon/Extra", type.name); + assertEquals("zone/moddev/mc/basemetals/proxy/CommonProxy", type.superName); + } + + @Test + void leavesCanonicalClassesAndTheCyanoBridgeAlone() { + final LegacyNamespaceTransformer transformer = new LegacyNamespaceTransformer(); + final byte[] bytes = fixture(); + assertSame(bytes, transformer.transform("zone.moddev.mc.basemetals.BaseMetals", null, bytes)); + assertNull(transformer.transform("missing", "missing", null)); + + final ClassWriter source = new ClassWriter(0); + source.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "example/Golem", null, "java/lang/Object", null); + source.visitField(Opcodes.ACC_PUBLIC, "old", "Lcyano/basemetals/init/Blocks;", null, null).visitEnd(); + source.visitEnd(); + final byte[] golem = source.toByteArray(); + assertSame(golem, transformer.transform("example.Golem", null, golem)); + } + + private static byte[] fixture() { + final ClassWriter type = new ClassWriter(0); + type.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "example/Consumer", null, + "com/mcmoddev/basemetals/proxy/CommonProxy", null); + type.visitField(Opcodes.ACC_PUBLIC, "mod", "Lcom/mcmoddev/basemetals/BaseMetals;", null, null).visitEnd(); + + final MethodVisitor method = type.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "link", "()V", null, null); + method.visitCode(); + method.visitFieldInsn(Opcodes.GETSTATIC, "com/mcmoddev/basemetals/BaseMetals", "instance", "Lcom/mcmoddev/basemetals/BaseMetals;"); + method.visitInsn(Opcodes.POP); + method.visitLdcInsn("com.mcmoddev.basemetals.init.Materials"); + method.visitInsn(Opcodes.POP); + method.visitLdcInsn(Type.getObjectType("com/mcmoddev/basemetals/util/Config$Options")); + method.visitInsn(Opcodes.POP); + method.visitLdcInsn("other.mod/textures/ore.png"); + method.visitInsn(Opcodes.POP); + method.visitInsn(Opcodes.RETURN); + method.visitMaxs(1, 0); + method.visitEnd(); + type.visitEnd(); + return type.toByteArray(); + } +} diff --git a/src/test/java/com/mcmoddev/basemetals/init/BlocksTests.java b/src/test/java/zone/moddev/mc/basemetals/init/BlocksTests.java similarity index 94% rename from src/test/java/com/mcmoddev/basemetals/init/BlocksTests.java rename to src/test/java/zone/moddev/mc/basemetals/init/BlocksTests.java index 732fd1bf0..1bea84e2f 100644 --- a/src/test/java/com/mcmoddev/basemetals/init/BlocksTests.java +++ b/src/test/java/zone/moddev/mc/basemetals/init/BlocksTests.java @@ -1,4 +1,4 @@ -package com.mcmoddev.basemetals.init; +package zone.moddev.mc.basemetals.init; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; diff --git a/src/test/java/zone/moddev/mc/basemetals/release/ReleaseSetupTests.java b/src/test/java/zone/moddev/mc/basemetals/release/ReleaseSetupTests.java new file mode 100644 index 000000000..c98f12c3b --- /dev/null +++ b/src/test/java/zone/moddev/mc/basemetals/release/ReleaseSetupTests.java @@ -0,0 +1,80 @@ +package zone.moddev.mc.basemetals.release; + +import com.google.gson.JsonParser; +import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Properties; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class ReleaseSetupTests { + private static String text(final String name) throws Exception { + return new String(Files.readAllBytes(Paths.get(name)), StandardCharsets.UTF_8); + } + + @Test + void releaseMetadataMatchesTheSharedDispatcher() throws Exception { + final Properties values = new Properties(); + try (Reader reader = Files.newBufferedReader(Paths.get("gradle.properties"), StandardCharsets.UTF_8)) { + values.load(reader); + } + + assertEquals("1.10.2", values.getProperty("minecraft_version")); + assertTrue(values.getProperty("mod_version").matches("\\d+\\.\\d+\\.\\d+\\.110021")); + assertEquals("forge", values.getProperty("loader_name")); + assertEquals("1", values.getProperty("loader_code")); + assertEquals("240967", values.getProperty("curseforge_project_id")); + assertEquals("mmd-orespawn", values.getProperty("cf_requirements")); + assertEquals("245586", values.getProperty("orespawn_curse_project_id")); + assertEquals("8.0.502+7", values.getProperty("java_setup_version")); + assertEquals("17.0.1+12", values.getProperty("gradle_java_setup_version")); + assertTrue(text("build.gradle").contains("legacyDependencyVerificationRepository")); + assertTrue(Files.isRegularFile(Paths.get("gradle/stage-legacy-dependencies.sh"))); + assertTrue(text("gradle/release/artifacts.gradle").contains("release/SHA256SUMS")); + assertTrue(text("gradle/release/publishing.gradle").contains("name = 'release'")); + assertTrue(text("gradle/release/publishing.gradle").contains("verifyPreparedReleaseArtifacts")); + } + + @Test + void workflowsUseCanonicalBranchesAndPinnedActions() throws Exception { + try (Stream files = Files.list(Paths.get(".github/workflows"))) { + for (Path path : (Iterable) files::iterator) { + final String workflow = text(path.toString()); + assertFalse(workflow.contains("secrets: inherit"), path.toString()); + assertFalse(workflow.contains("pull_request_target"), path.toString()); + for (String line : workflow.split("\n")) { + if (line.trim().startsWith("uses:")) { + assertTrue(line.trim().matches("uses: [\\w./-]+@[a-f0-9]{40}( #.*)?"), line); + } + } + if (!path.endsWith("release-on-tag.yml")) { + assertTrue(workflow.contains("master-1.10.2"), path.toString()); + assertFalse(workflow.contains("master-1.12"), path.toString()); + } + } + } + final String ci = text(".github/workflows/ci.yml"); + assertTrue(ci.contains("name: Build, test, and audit")); + assertTrue(ci.contains("packagedForgeRuntimeTest")); + assertTrue(ci.contains("cmp \"$RUNNER_TEMP/first-SHA256SUMS\" build/release/SHA256SUMS")); + assertTrue(text(".github/workflows/release-on-tag.yml").contains("Nothing has been published")); + } + + @Test + void everyResourceAndFixtureIsValidJson() throws Exception { + for (String root : new String[]{"src/main/resources", "tools/validation/legacy110/fixtures"}) { + try (Stream files = Files.walk(Paths.get(root))) { + for (Path path : (Iterable) files.filter(p -> p.toString().endsWith(".json"))::iterator) { + try (Reader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) { + assertNotNull(new JsonParser().parse(reader), path.toString()); + } + } + } + } + } +} diff --git a/src/test/java/zone/moddev/mc/basemetals/util/LegacyMappingsTests.java b/src/test/java/zone/moddev/mc/basemetals/util/LegacyMappingsTests.java new file mode 100644 index 000000000..5143a8511 --- /dev/null +++ b/src/test/java/zone/moddev/mc/basemetals/util/LegacyMappingsTests.java @@ -0,0 +1,40 @@ +package zone.moddev.mc.basemetals.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +class LegacyMappingsTests { + + @Test + void originalDoorItemsKeepTheirMaterial() { + for (final String material : new String[] { "adamantine", "aquarium", "brass", "bronze", + "coldiron", "copper", "cupronickel", "electrum", "invar", "lead", "mithril", + "nickel", "platinum", "silver", "starsteel", "steel", "tin" }) { + assertEquals(material + "_door", LegacyMappings.itemReplacement(material + "_door_item")); + } + } + + @Test + void originalProcessingItemsUseTheirCurrentNames() { + assertEquals("coal_powder", LegacyMappings.itemReplacement("carbon_powder")); + assertEquals("mercury", LegacyMappings.itemReplacement("liquid_mercury")); + } + + @Test + void unchangedAndUnknownItemsAreNotRemapped() { + assertNull(LegacyMappings.itemReplacement("iron_nugget")); + assertNull(LegacyMappings.itemReplacement("human_detector")); + assertNull(LegacyMappings.itemReplacement("copper_door")); + assertNull(LegacyMappings.itemReplacement("unknown_door_item")); + } + + @Test + void historicalNamesCannotBeChangedAtRuntime() { + assertEquals(19, LegacyMappings.itemReplacements().size()); + assertThrows(UnsupportedOperationException.class, + () -> LegacyMappings.itemReplacements().put("iron_nugget", "wrong_item")); + } +} diff --git a/src/test/java/zone/moddev/mc/basemetals/worldgen/BaseMetalsWorldgenProviderTests.java b/src/test/java/zone/moddev/mc/basemetals/worldgen/BaseMetalsWorldgenProviderTests.java new file mode 100644 index 000000000..a8879cbf3 --- /dev/null +++ b/src/test/java/zone/moddev/mc/basemetals/worldgen/BaseMetalsWorldgenProviderTests.java @@ -0,0 +1,67 @@ +package zone.moddev.mc.basemetals.worldgen; + +import static org.junit.jupiter.api.Assertions.*; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class BaseMetalsWorldgenProviderTests { + + @Test + void preservesTheElevenHistoricalRules() throws Exception { + final JsonObject provider = BaseMetalsWorldgenProvider.build(block -> true).get().toJson(); + final JsonObject ores = provider.getAsJsonObject("ores"); + final JsonObject legacy = new JsonParser().parse(new String(Files.readAllBytes( + Paths.get("tools/validation/legacy110/fixtures/orespawn3-basemetals.json")), + StandardCharsets.UTF_8)).getAsJsonObject().getAsJsonObject("spawns"); + + assertEquals(5, provider.get("schema_version").getAsInt()); + assertEquals(1, provider.get("provider_revision").getAsInt()); + assertEquals(11, ores.entrySet().size()); + for (final Map.Entry entry : legacy.entrySet()) { + final JsonObject old = entry.getValue().getAsJsonObject(); + final JsonObject ore = ores.getAsJsonObject("basemetals:legacy/" + entry.getKey()); + final JsonObject parameters = old.getAsJsonObject("parameters"); + assertEquals(old.getAsJsonArray("blocks").get(0).getAsJsonObject().get("name"), ore.get("block")); + assertFalse(ore.get("native_generation").getAsBoolean()); + assertFalse(ore.get("retrogen").getAsBoolean()); + + final String dimension = entry.getKey().equals("starsteel_ore") ? "minecraft:the_end" : "minecraft:the_nether"; + final JsonObject placement = old.getAsJsonArray("dimensions").size() == 0 + ? ore.getAsJsonObject("dimension_selectors").getAsJsonObject("orespawn:all_except_nether_end") + : ore.getAsJsonObject("dimensions").getAsJsonObject(dimension); + assertEquals(parameters.get("minHeight").getAsInt(), placement.get("min_y").getAsInt()); + assertEquals(parameters.get("maxHeight").getAsInt() - 1, placement.get("max_y").getAsInt()); + assertEquals(parameters.get("frequency").getAsDouble(), placement.get("frequency").getAsDouble()); + assertEquals(4, placement.get("min_quantity").getAsInt()); + assertEquals(11, placement.get("max_quantity").getAsInt()); + assertEquals("orespawn:standard", placement.get("placement_channel").getAsString()); + } + for (final String empty : new String[] { "rocks", "terrain_dimensions", "fluid_deposits", "biome_palettes" }) { + assertEquals(0, provider.getAsJsonObject(empty).entrySet().size()); + } + } + + @Test + void omitsMaterialsThatThePackDisabled() { + final JsonObject ores = BaseMetalsWorldgenProvider.build(block -> !block.getResourcePath().equals("copper_ore")) + .get().toJson().getAsJsonObject("ores"); + assertEquals(10, ores.entrySet().size()); + assertFalse(ores.has("basemetals:legacy/copper_ore")); + assertFalse(BaseMetalsWorldgenProvider.build(block -> false).isPresent()); + } + + @Test + void doesNotShipTheOldGenerators() { + for (final String name : new String[] { "com.mcmoddev.orespawn.BaseMetalsOreSpawn", + "com.mcmoddev.lib.oregen.FallbackGenerator", "com.mcmoddev.lib.oregen.FallbackGeneratorData" }) { + assertThrows(ClassNotFoundException.class, () -> Class.forName(name, false, getClass().getClassLoader())); + } + } +} diff --git a/tools/validation/legacy110/EclipseModelCheck.java b/tools/validation/legacy110/EclipseModelCheck.java new file mode 100644 index 000000000..033b3979e --- /dev/null +++ b/tools/validation/legacy110/EclipseModelCheck.java @@ -0,0 +1,95 @@ +package review.ide; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; +import java.util.stream.Collectors; +import org.gradle.tooling.GradleConnector; +import org.gradle.tooling.ProjectConnection; +import org.gradle.tooling.model.build.BuildEnvironment; +import org.gradle.tooling.model.eclipse.EclipseProject; +import org.gradle.tooling.model.eclipse.EclipseSourceDirectory; +import org.gradle.tooling.model.gradle.GradleBuild; + +/** Requests the same project models used by Eclipse Buildship's import wizard. */ +public final class EclipseModelCheck { + + public static void main(final String[] args) throws Exception { + final File project = new File(args[0]); + final File cache = new File(args[1]); + final File javaHome = new File(args[2]); + + try (ProjectConnection connection = GradleConnector.newConnector() + .forProjectDirectory(project).useGradleUserHomeDir(cache) + .useBuildDistribution().connect()) { + final BuildEnvironment environment = connection.model(BuildEnvironment.class) + .setJavaHome(javaHome).withArguments("--offline").get(); + final GradleBuild build = connection.model(GradleBuild.class) + .setJavaHome(javaHome).withArguments("--offline").get(); + final EclipseProject eclipse = connection.model(EclipseProject.class) + .setJavaHome(javaHome).withArguments("--offline").get(); + + if (!"9.6.1".equals(environment.getGradle().getGradleVersion()) + || !"BaseMetals".equals(build.getRootProject().getName()) + || !"BaseMetals".equals(eclipse.getName()) + || eclipse.getSourceDirectories().stream().noneMatch(source -> "build/resources/main".equals(source.getPath())) + || !"1.8".equals(eclipse.getJavaSourceSettings().getSourceLanguageLevel().toString())) { + throw new AssertionError("Wrong wrapper, project or Java source level in the Eclipse model"); + } + eclipse.getClasspath().forEach(dependency -> { + if (!dependency.getFile().isFile()) { + throw new AssertionError("Missing Eclipse library: " + dependency.getFile()); + } + }); + if (eclipse.getSourceDirectories().stream().map(source -> source.getPath()).distinct().count() + != eclipse.getSourceDirectories().size()) { + throw new AssertionError("Duplicate Eclipse source directories"); + } + + for (final EclipseSourceDirectory source : eclipse.getSourceDirectories()) { + verifySourcePackages(new File(project, source.getPath()).toPath()); + } + + if (args.length > 3) { + final List libraries = new ArrayList<>(); + eclipse.getClasspath().forEach(dependency -> libraries.add(dependency.getFile().getAbsolutePath())); + Files.write(new File(args[3]).toPath(), libraries, StandardCharsets.UTF_8); + } + + System.out.println("BASE_METALS_ECLIPSE_MODEL_PASS gradle=" + + environment.getGradle().getGradleVersion() + " source=1.8 libraries=" + + eclipse.getClasspath().size() + " gradleJava=" + + environment.getJava().getJavaHome()); + } + } + + private static void verifySourcePackages(final Path root) throws IOException { + if (!Files.isDirectory(root)) return; + + final Pattern packageDeclaration = Pattern.compile("(?m)^\\s*package\\s+([\\w.]+)\\s*;"); + final List sources; + try (Stream files = Files.walk(root)) { + sources = files.filter(path -> path.toString().endsWith(".java")) + .collect(Collectors.toList()); + } + + for (final Path file : sources) { + final Path parent = root.relativize(file).getParent(); + final String expected = parent == null ? "" : parent.toString().replace(File.separatorChar, '.'); + final String text = new String(Files.readAllBytes(file), StandardCharsets.UTF_8); + final Matcher declaration = packageDeclaration.matcher(text); + final String declared = declaration.find() ? declaration.group(1) : ""; + + if (!expected.equals(declared)) { + throw new AssertionError("Package does not match the Eclipse source folder: " + file); + } + } + } +} diff --git a/tools/validation/legacy110/README.md b/tools/validation/legacy110/README.md new file mode 100644 index 000000000..ba7461506 --- /dev/null +++ b/tools/validation/legacy110/README.md @@ -0,0 +1,110 @@ +# 1.10 migration checks + +These checks use disposable worlds. Never point a server launch at the only copy +of a player's world. + +The main Gradle project compiles the complete mod with Forge 2511 and stable-29 +mappings. Run the repository's wrapper from the project root, using Java 17 for +Gradle. The Java 8 compiler and runtime are selected separately: + +```text +gradlew clean check build migrationProbeJar +``` + +The main jar contains production code and resources only. The separate +`build/test-mods/legacy110-probe.jar` is a server-side test mod; do not distribute it with releases +or leave it installed for normal play. + +`audit_artifacts.py` compares the candidate with the pre-namespace production jar. +It checks the complete class and resource inventory, Java 8 bytecode, saved +resource names, version metadata and exclusion of test material and local paths. + +## Runtime checks + +For the captured Cyano and MMD fixtures, launch the packaged server with +`review.manifest` pointing to `legacy_registry_manifest_runtime.json` and +`review.phase` set to `first`, `reload`, `delayed`, then `stable` on separate boots. +The first two boots leave the remote fixture chunk unopened. The next two check +every saved item and block state, including names, enchantments and other item +data. Each boot also checks the Human Detector's item, player-only detection and +redstone activation/release. Read the JSON report and require `passed: true`; +Minecraft's exit code alone does not indicate success. + +For a larger world, supply `review.chunks` instead: a UTF-8 file of +`dimension,chunkX,chunkZ` lines. The probe loads and saves those chunks, then stops +the server. Its completion message confirms only that the visits finished, not +that all content survived. + +Use `audit_world.py` before and after these visits to compare the saved blocks and +inventories. It needs Python 3, NumPy and nbtlib. Pass the earlier report through +`--source` for comparisons. The audit fingerprints the input world, checks every +saved Base Metals and Advantage block state, and compares item counts, damage and +NBT. The `--delay` check inspects Sylvester's remote powder chest; its raw chunk +hash must stay unchanged until the delayed visit. After that visit it must contain +220 coal powder items under the current name, and retain them after another boot. + +Keep the original world's fingerprint, the mod-jar hashes, each boot's logs and +the audit reports with the local test evidence. Existing Cyano integrations need +separate compatibility checks; registry migration cannot repair an add-on's Java +API references. + +## Golems and old add-on APIs + +Use the original Base Metal Golems 6.02.1 and Extra Golems 6.02 jars unchanged. +With Cyano Base Metals installed, `golem.phase=seed` saves all eighteen golem +types near spawn and another eighteen in the remote chest chunk. Keep that +seeded world as a read-only reference, and test a copy with the candidate jar. + +Run separate boots with `golem.phase` set to `first`, `reload`, `delayed` and +`stable`. The first two must exclude the remote chunk from `review.chunks`; +the last two include it. Each boot checks the real add-on's build-event handler, +material blocks and ingot drops. It also checks the saved golems that should +have loaded by that point. `audit_golems.py` compares their saved identities, +health, equipment and persistent NBT. Use `--unopened` before the delayed visit +to require an identical raw chunk, not just equivalent entities. + +For the old MMD API check, build the probe with +`-PlegacyBaselineJar=`. This compiles a small +consumer against the old classes, not the new source. Set +`golem.checkMmdApi=true` at runtime to check its class literals, reflection, +inheritance, mod instance and material lookups after relinking. + +Finally use a fresh, unseeded world copy with `golem.phase=absent`, first without +Base Metal Golems and then without Extra Golems too. Check the JSON reports for +`passed: true`; this also exercises the Human Detector and confirms the bridge +has no required add-on dependency. Never remove the add-ons from the only copy +of a world containing their entities. + +## OreSpawn upgrades + +`run_orespawn_checks.py` creates and saves a world with the previous Base Metals +and OreSpawn 3 jars, then upgrades disposable copies. It covers stock, edited +and disabled OS3 rules, optional OS1 input, existing OS4 overrides, reloads and +chunks opened later. The runner also samples fresh Overworld, Nether and End +chunks and checks every Base Metals ore against its configured height range. + +Use the packaged versions of its probes: `packagedLaunchProbeJar` and +`packagedOreSpawnProbeJar`. The regular probe jars use development mappings for +IDE launches. All probes remain outside the public jar. + +`run_vanilla_takeover_checks.py` checks the Manage Vanilla Ores switch against +an imported custom diamond rule. It needs the same packaged jars, Java and +Forge template as the upgrade runner, plus NumPy and nbtlib. It tests both switch +settings, reloads and later exploration, confirms Base Metals ores still generate, +and compares 49 existing chunks after switching off, on and back off. The source +configuration and fixture world must remain unchanged. + +## Automated CI suite + +`packagedForgeRuntimeTest` builds all probes separately and runs `run_ci_checks.py`. +It downloads checksum-pinned public releases, creates its own disposable Cyano +and MMD worlds, and checks first load, reload, delayed chunks and a further reload. +The fixture includes registered items with names, damage and NBT, plus block states +in the Overworld, Nether and End. Neither private worlds nor local mod builds are +needed. OS1/OS3 imports, override precedence and vanilla takeover are checked in +the same suite. + +The eighteen-golem/Advantage-mod checks remain a separate local compatibility +profile. They are not part of the ordinary client launch or the hosted CI suite. +All generated worlds, downloaded jars, logs and reports stay under the ignored +build directory. Keep failed evidence before running `clean`. diff --git a/tools/validation/legacy110/audit_artifacts.py b/tools/validation/legacy110/audit_artifacts.py new file mode 100644 index 000000000..6d57c3e17 --- /dev/null +++ b/tools/validation/legacy110/audit_artifacts.py @@ -0,0 +1,96 @@ +"""Check the namespace move against the previous complete production jar.""" + +import argparse +import hashlib +import json +import struct +import zipfile +from pathlib import Path + + +OLD = "com/mcmoddev/basemetals/" +CURRENT = "zone/moddev/mc/basemetals/" +ADDITIONS = {CURRENT + "compat/" + name + ".class" for name in ( + "BaseMetalsLoadingPlugin", "CyanoMaterials", "LegacyNamespaceTransformer", + "LegacyNamespaceTransformer$1")} +ADDITIONS.update("cyano/basemetals/init/" + name + ".class" for name in ("Blocks", "Items")) +ADDITIONS.add(CURRENT + "worldgen/BaseMetalsWorldgenProvider.class") +REMOVALS = {"com/mcmoddev/orespawn/BaseMetalsOreSpawn.class"} +REMOVALS.update("com/mcmoddev/lib/oregen/" + name + ".class" for name in ( + "FallbackGenerator", "FallbackGeneratorData", "FallbackGeneratorData$1", "FallbackGeneratorData$StonePredicate", + "FallbackGeneratorData$NetherPredicate", "FallbackGeneratorData$EndPredicate")) + + +def check(baseline, candidate): + errors = [] + with zipfile.ZipFile(baseline) as before, zipfile.ZipFile(candidate) as after: + old_files = {name for name in before.namelist() if not name.endswith("/")} + new_files = {name for name in after.namelist() if not name.endswith("/")} + old_classes = {name for name in old_files if name.endswith(".class")} + new_classes = {name for name in new_files if name.endswith(".class")} + expected = {CURRENT + name[len(OLD):] if name.startswith(OLD) else name for name in old_classes} + if new_classes != (expected - REMOVALS) | ADDITIONS: + errors.append("Unexpected production class additions or removals") + if any(name.startswith(OLD) for name in new_classes): + errors.append("Old Base Metals implementation classes are still packaged") + + exports = {name[len(OLD):-6] for name in old_classes if name.startswith(OLD) and "$" not in name} + declared = set(after.read("META-INF/basemetals-legacy-api.list").decode("utf-8").splitlines()) + if exports != declared: + errors.append("The legacy API list does not cover exactly the former classes") + + metadata = {"META-INF/MANIFEST.MF", "mcmod.info", "META-INF/basemetals-legacy-api.list"} + old_resources = old_files - old_classes - metadata + new_resources = new_files - new_classes - metadata + if old_resources - {"assets/basemetals/orespawn/basemetals.json"} != new_resources: + errors.append("Unexpected resource additions or removals") + for name in old_resources & new_resources: + old = before.read(name) + new = after.read(name) + if name.endswith((".json", ".lang", ".cfg", ".txt", ".mcmeta", ".info")): + old = old.replace(b"\r\n", b"\n") + new = new.replace(b"\r\n", b"\n") + if old and not old.endswith(b"\n"): + old += b"\n" + if new and not new.endswith(b"\n"): + new += b"\n" + if old != new: + errors.append("Changed saved resource: " + name) + + for name in new_files: + data = after.read(name) + lowered = name.lower() + if any(part in lowered for part in ("agent-notes", "agents.md", "codex", "review/", "probe", "fixtures/", "test/")): + errors.append("Non-production entry: " + name) + if name.endswith(".class") and struct.unpack(">H", data[6:8])[0] != 52: + errors.append("Not Java 8 bytecode: " + name) + if b"D:\\BaseMetals" in data or b"C:\\Users\\johnb" in data: + errors.append("Local path in " + name) + + mod = json.loads(after.read("mcmod.info"))[0] + if mod["modid"] != "basemetals" or mod["version"] != "2.5.1.110021": + errors.append("Wrong mod identity or version") + manifest = after.read("META-INF/MANIFEST.MF").decode("utf-8").replace("\r\n ", "") + if "zone.moddev.mc.basemetals.compat.BaseMetalsLoadingPlugin" not in manifest: + errors.append("Missing early namespace adapter") + + return {"candidate": str(candidate), "sha256": hashlib.sha256(candidate.read_bytes()).hexdigest().upper(), + "productionClasses": len(new_classes), "legacyApiClasses": len(exports), + "errors": errors, "passed": not errors} + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("baseline", type=Path) + parser.add_argument("candidate", type=Path) + parser.add_argument("output", type=Path) + args = parser.parse_args() + report = check(args.baseline, args.candidate) + args.output.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8") + print(json.dumps(report, indent=2)) + if not report["passed"]: + raise SystemExit(1) + + +if __name__ == "__main__": + main() diff --git a/tools/validation/legacy110/audit_golems.py b/tools/validation/legacy110/audit_golems.py new file mode 100644 index 000000000..fa7e3d874 --- /dev/null +++ b/tools/validation/legacy110/audit_golems.py @@ -0,0 +1,75 @@ +"""Check saved golem identities and the chunk deliberately opened on a later boot.""" + +import argparse +import hashlib +import io +import json +from pathlib import Path + +import nbtlib + +from audit_world import plain, raw_chunk + + +GROUPS = {"near": (14, 13), "later": (154, -504)} +MATERIALS = "adamantine aquarium brass bronze coldiron copper cupronickel electrum invar lead mithril nickel platinum silver starsteel steel tin zinc".split() +# These fields change during ordinary entity ticks, even with the AI disabled. +TRANSIENT = {"Motion", "Rotation", "FallDistance", "OnGround", "Air", "Fire", + "HurtTime", "HurtByTimestamp", "PortalCooldown"} + + +def capture(world): + result = {} + + for group, (x, z) in GROUPS.items(): + path = world / "region" / f"r.{x // 32}.{z // 32}.mca" + data = raw_chunk(path, (x & 31) + (z & 31) * 32) + chunk = nbtlib.File.parse(io.BytesIO(data))["Level"] + entities = {} + + for entity in chunk.get("Entities", []): + name = str(entity.get("CustomName", "")) + if not name.startswith(f"BM compatibility {group} "): + continue + if name in entities: + raise ValueError(f"Duplicate saved golem: {name}") + entities[name] = {key: plain(value) for key, value in entity.items() + if key not in TRANSIENT} + + expected = {f"BM compatibility {group} {material}" for material in MATERIALS} + if set(entities) != expected: + raise ValueError(f"Missing or unexpected {group} golems: {set(entities) ^ expected}") + + result[group] = {"chunk_sha256": hashlib.sha256(data).hexdigest(), "entities": entities} + + return result + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("world", type=Path) + parser.add_argument("output", type=Path) + parser.add_argument("--source", type=Path) + parser.add_argument("--unopened", action="store_true") + args = parser.parse_args() + groups = capture(args.world) + errors = [] + + if args.source: + source = json.loads(args.source.read_text(encoding="utf-8"))["groups"] + for group in GROUPS: + if groups[group]["entities"] != source[group]["entities"]: + errors.append(f"Saved {group} golem identity or NBT changed") + if args.unopened and groups["later"]["chunk_sha256"] != source["later"]["chunk_sha256"]: + errors.append("The later-opened chunk changed before its scheduled visit") + + report = {"groups": groups, "errors": errors, "passed": not errors} + args.output.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8") + print(json.dumps({"entities": {g: len(v["entities"]) for g, v in groups.items()}, + "errors": errors, "passed": not errors})) + if errors: + raise SystemExit(1) + + +if __name__ == "__main__": + main() diff --git a/tools/validation/legacy110/audit_world.py b/tools/validation/legacy110/audit_world.py new file mode 100644 index 000000000..51d94f67d --- /dev/null +++ b/tools/validation/legacy110/audit_world.py @@ -0,0 +1,219 @@ +"""Compare saved 1.10 blocks and inventories without opening Minecraft.""" + +import argparse +import gzip +import hashlib +import io +import json +import struct +import zlib +from collections import Counter +from pathlib import Path + +import nbtlib +import numpy as np + + +MATERIALS = "adamantine aquarium brass bronze coldiron copper cupronickel electrum invar lead mithril nickel platinum silver starsteel steel tin".split() +ALIASES = {"basemetals:carbon_powder": "basemetals:coal_powder", + "basemetals:liquid_mercury": "basemetals:mercury"} +ALIASES.update({f"basemetals:{name}_door_item": f"basemetals:{name}_door" for name in MATERIALS}) +PREFIXES = ("basemetals:", "poweradvantage:", "steamadvantage:", "electricadvantage:") + + +def fingerprint(root): + digest = hashlib.sha256() + count = size = 0 + for path in sorted(root.rglob("*"), key=lambda p: p.relative_to(root).as_posix()): + if not path.is_file(): + continue + digest.update(path.relative_to(root).as_posix().encode("utf-8") + b"\n") + with path.open("rb") as handle: + while data := handle.read(1024 * 1024): + digest.update(data) + size += len(data) + count += 1 + return {"files": count, "bytes": size, "sha256": digest.hexdigest().upper()} + + +def nbt(path): + data = path.read_bytes() + if data[:2] == b"\x1f\x8b": + data = gzip.decompress(data) + return nbtlib.File.parse(io.BytesIO(data)) + + +def raw_chunk(path, index): + with path.open("rb") as handle: + handle.seek(index * 4) + location = int.from_bytes(handle.read(4), "big") + if not location: + return None + handle.seek((location >> 8) * 4096) + length = struct.unpack(">I", handle.read(4))[0] + compression = handle.read(1)[0] + data = handle.read(length - 1) + if compression == 1: + return gzip.decompress(data) + if compression == 2: + return zlib.decompress(data) + if compression == 3: + return data + raise ValueError(f"Unsupported chunk compression {compression}") + + +def plain(value): + if isinstance(value, dict): + return {str(k): plain(v) for k, v in value.items()} + if isinstance(value, (list, nbtlib.List)): + return [plain(v) for v in value] + if hasattr(value, "tolist"): + return value.tolist() + if isinstance(value, str): + return str(value) + if isinstance(value, int): + return int(value) + if isinstance(value, float): + return float(value) + return value + + +def stacks(value, owner, result): + if isinstance(value, dict): + name = str(value.get("id", "")) + if "Count" in value and name.startswith(PREFIXES): + payload = plain(value) + payload["id"] = ALIASES.get(name, name) + result.append({"owner": owner, "original_id": name, "payload": payload}) + else: + for key, child in value.items(): + if key != "Sections": + stacks(child, owner, result) + elif isinstance(value, (list, nbtlib.List)): + for child in value: + stacks(child, owner, result) + + +def nibbles(values): + packed = np.asarray(values, dtype=np.int16) & 255 + result = np.empty(4096, dtype=np.int16) + result[0::2], result[1::2] = packed & 15, packed >> 4 + return result + + +def scan(world): + level = nbt(world / "level.dat") + registry = {int(v["V"]): str(v["K"]) for v in level["FML"]["Registries"]["minecraft:blocks"]["ids"]} + wanted = {numeric: name for numeric, name in registry.items() if name.startswith(PREFIXES)} + lookup = np.zeros(4096, dtype=np.bool_) + for numeric in wanted: + lookup[numeric] = True + items, blocks, points = [], {}, set() + block_totals = Counter() + chunks = 0 + + for path in sorted(world.rglob("r.*.*.mca")): + if path.parent.name != "region": + continue + relative = path.relative_to(world).as_posix() + dimension = -1 if relative.startswith("DIM-1/") else 1 if relative.startswith("DIM1/") else 0 + _, rx, rz, _ = path.name.split(".") + with path.open("rb") as handle: + locations = handle.read(4096) + + for index in range(1024): + if not int.from_bytes(locations[index * 4:index * 4 + 4], "big"): + continue + data = raw_chunk(path, index) + root = nbtlib.File.parse(io.BytesIO(data)) + chunk = root.get("Level", root) + chunks += 1 + point = (dimension, int(rx) * 32 + (index & 31), int(rz) * 32 + (index >> 5)) + before = len(items) + for tile in chunk.get("TileEntities", []): + owner = [relative, index, "tile", *[int(tile.get(k, 0)) for k in ("x", "y", "z")]] + stacks(tile, owner, items) + for entity in chunk.get("Entities", []): + owner = [relative, index, "entity", int(entity.get("UUIDMost", 0)), int(entity.get("UUIDLeast", 0))] + stacks(entity, owner, items) + if len(items) != before: + points.add(point) + + states = [] + for section in chunk.get("Sections", []): + ids = np.asarray(section["Blocks"], dtype=np.int16) & 255 + if "Add" in section: + ids |= nibbles(section["Add"]) << 8 + offsets = np.flatnonzero(lookup[ids]) + if not len(offsets): + continue + metadata = nibbles(section["Data"]) + for offset in offsets: + name = ALIASES.get(wanted[int(ids[offset])], wanted[int(ids[offset])]) + states.append([int(section["Y"]), int(offset), name, int(metadata[offset])]) + block_totals[name] += 1 + if name.endswith("_door") or name == "basemetals:mercury": + points.add(point) + if states: + key = f"{relative}:{index}" + blocks[key] = hashlib.sha256(json.dumps(sorted(states)).encode()).hexdigest() + + for path in sorted((world / "playerdata").glob("*.dat")): + stacks(nbt(path), ["player", path.name], items) + stacks(level.get("Data", {}).get("Player", {}), ["level-player"], items) + return {"chunks": chunks, "blocks": blocks, "block_totals": dict(block_totals), + "items": items, "points": sorted(points)} + + +def item_key(record): + return json.dumps([record["owner"], record["payload"]], sort_keys=True) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("world", type=Path) + parser.add_argument("output", type=Path) + parser.add_argument("--source", type=Path, help="Previously captured source audit") + parser.add_argument("--fingerprint-only", action="store_true") + parser.add_argument("--delay", action="store_true", help="Only inspect Sylvester's unopened remote chest chunk") + args = parser.parse_args() + report = {"fingerprint": fingerprint(args.world)} + + if args.delay: + data = raw_chunk(args.world / "region/r.4.-16.mca", 282) + root = nbtlib.File.parse(io.BytesIO(data)) + chest = next(t for t in root["Level"]["TileEntities"] if tuple(int(t[k]) for k in ("x", "y", "z")) == (2470, 68, -8053)) + report["delayed_chunk"] = {"sha256": hashlib.sha256(data).hexdigest(), + "legacy_units": sum(int(i["Count"]) for i in chest["Items"] if str(i["id"]) == "basemetals:carbon_powder"), + "current_units": sum(int(i["Count"]) for i in chest["Items"] if str(i["id"]) == "basemetals:coal_powder")} + elif not args.fingerprint_only: + report["content"] = scan(args.world) + + if args.source and not args.delay: + source = json.loads(args.source.read_text(encoding="utf-8"))["content"] + target = report["content"] + errors = [] + for chunk, digest in source["blocks"].items(): + if target["blocks"].get(chunk) != digest: + errors.append({"type": "blocks", "chunk": chunk}) + available = Counter(item_key(v) for v in target["items"]) + for record in source["items"]: + key = item_key(record) + if available[key]: + available[key] -= 1 + else: + errors.append({"type": "item", "expected": record}) + report.update(errors=errors, passed=not errors) + + args.output.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8") + print(json.dumps({k: v for k, v in report.items() if k != "content"}, indent=2), flush=True) + if "content" in report: + content = report["content"] + print("Chunks:", content["chunks"], "block chunks:", len(content["blocks"]), + "item stacks:", len(content["items"]), "visit points:", len(content["points"]), flush=True) + if report.get("passed") is False: + raise SystemExit(1) + + +if __name__ == "__main__": + main() diff --git a/tools/validation/legacy110/capture-probe/com/mcmoddev/validation/LegacyCapture.java b/tools/validation/legacy110/capture-probe/com/mcmoddev/validation/LegacyCapture.java new file mode 100644 index 000000000..03f46715f --- /dev/null +++ b/tools/validation/legacy110/capture-probe/com/mcmoddev/validation/LegacyCapture.java @@ -0,0 +1,417 @@ +package com.mcmoddev.validation; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +import com.mojang.authlib.GameProfile; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockDoor; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.item.EntityArmorStand; +import net.minecraft.init.Blocks; +import net.minecraft.init.Enchantments; +import net.minecraft.inventory.EntityEquipmentSlot; +import net.minecraft.item.Item; +import net.minecraft.item.ItemArmor; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.tileentity.TileEntityChest; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.EnumFacing; +import net.minecraft.world.WorldServer; +import net.minecraftforge.fluids.Fluid; +import net.minecraftforge.fluids.FluidRegistry; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.common.util.FakePlayer; +import net.minecraftforge.common.util.FakePlayerFactory; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.Mod.EventHandler; +import net.minecraftforge.fml.common.FMLCommonHandler; +import net.minecraftforge.fml.common.event.FMLServerStartedEvent; +import net.minecraftforge.fml.common.registry.ForgeRegistries; + +@Mod(modid = LegacyCapture.MOD_ID, name = "Base Metals Legacy Capture", version = "1.0", + acceptableRemoteVersions = "*", serverSideOnly = true) +public final class LegacyCapture { + public static final String MOD_ID = "legacycapture"; + private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); + + @EventHandler + public void serverStarted(FMLServerStartedEvent event) throws IOException { + net.minecraft.server.MinecraftServer server = FMLCommonHandler.instance().getMinecraftServerInstance(); + Path worldRoot = server.worldServerForDimension(0).getSaveHandler().getWorldDirectory().toPath(); + if (Files.isRegularFile(worldRoot.resolve("legacy_registry_manifest_runtime.json"))) { + verifySavedFixture(server, worldRoot); + server.saveAllWorlds(false); + server.initiateShutdown(); + return; + } + + net.minecraftforge.common.DimensionManager.initDimension(-1); + net.minecraftforge.common.DimensionManager.initDimension(1); + List worlds = new ArrayList<>(); + worlds.add(server.worldServerForDimension(0)); + worlds.add(server.worldServerForDimension(-1)); + worlds.add(server.worldServerForDimension(1)); + WorldServer world = worlds.get(0); + JsonObject manifest = new JsonObject(); + manifest.addProperty("source", "live Forge 1.10.2 registries"); + manifest.addProperty("forge", "12.18.3.2511"); + manifest.addProperty("fixture_format", 2); + manifest.addProperty("source_minecraft", "1.10.2"); + net.minecraftforge.fml.common.ModContainer mod = net.minecraftforge.fml.common.Loader.instance() + .getIndexedModList().get("basemetals"); + manifest.addProperty("source_basemetals", mod.getVersion()); + + List blockIds = ids(ForgeRegistries.BLOCKS.getEntries().stream() + .map(Map.Entry::getKey).collect(Collectors.toList())); + List itemIds = ids(ForgeRegistries.ITEMS.getEntries().stream() + .map(Map.Entry::getKey).collect(Collectors.toList())); + manifest.add("blocks", captureBlocks(worlds, blockIds)); + manifest.add("items", captureItems(world, itemIds)); + manifest.add("fluids", captureFluids(world)); + manifest.add("players", capturePlayer(world, itemIds)); + + Path gameDir = Paths.get(".").toAbsolutePath().normalize(); + write(gameDir.resolve("legacy_registry_manifest_runtime.json"), manifest); + Path worldDir = world.getSaveHandler().getWorldDirectory().toPath(); + write(worldDir.resolve("legacy_registry_manifest_runtime.json"), manifest); + Path os3 = gameDir.resolve("config").resolve("orespawn3").resolve("orespawn.json"); + if (Files.isRegularFile(os3)) { + Files.copy(os3, worldDir.resolve("legacy_orespawn3_orespawn.json"), + java.nio.file.StandardCopyOption.REPLACE_EXISTING); + } + try (InputStream baseMetalsRules = LegacyCapture.class.getResourceAsStream( + "/assets/basemetals/orespawn/basemetals.json")) { + if (baseMetalsRules != null) { + Files.copy(baseMetalsRules, worldDir.resolve("legacy_orespawn3_basemetals.json"), + java.nio.file.StandardCopyOption.REPLACE_EXISTING); + } + } + Files.write(worldDir.resolve("BASEMETALS_1_10_FIXTURE_COMPLETE.txt"), + ("Base Metals 1.10 fixture format 2 captured with " + blockIds.size() + + " blocks in all three dimensions, " + itemIds.size() + + " exact item stacks, filled fluid buckets, armor stands, and player data.\n") + .getBytes(StandardCharsets.UTF_8)); + + server.saveAllWorlds(false); + org.apache.logging.log4j.LogManager.getLogger(MOD_ID).info( + "BASEMETALS_LEGACY_CAPTURE PASS version={} blocks={} items={}", + mod.getVersion(), blockIds.size(), itemIds.size()); + server.initiateShutdown(); + } + + private static void verifySavedFixture(net.minecraft.server.MinecraftServer server, Path root) + throws IOException { + JsonObject manifest; + try (java.io.Reader reader = Files.newBufferedReader( + root.resolve("legacy_registry_manifest_runtime.json"), StandardCharsets.UTF_8)) { + manifest = new com.google.gson.JsonParser().parse(reader).getAsJsonObject(); + } + + net.minecraftforge.common.DimensionManager.initDimension(-1); + net.minecraftforge.common.DimensionManager.initDimension(1); + int states = 0; + for (com.google.gson.JsonElement element : manifest.getAsJsonArray("blocks")) { + JsonObject block = element.getAsJsonObject(); + for (com.google.gson.JsonElement stateElement : block.getAsJsonArray("states")) { + JsonObject saved = stateElement.getAsJsonObject(); + WorldServer world = server.worldServerForDimension(saved.get("dimension").getAsInt()); + BlockPos pos = new BlockPos(saved.get("x").getAsInt(), saved.get("y").getAsInt(), + saved.get("z").getAsInt()); + IBlockState actual = world.getBlockState(pos); + String expectedId = block.get("id").getAsString(); + if (!actual.getBlock().getRegistryName().toString().equals(expectedId)) { + throw new IllegalStateException("Source fixture lost " + expectedId + " at " + pos); + } + + // Record the state that 1.10 actually saved, not properties computed from neighbours. + saved.addProperty("saved_metadata", actual.getBlock().getMetaFromState(actual)); + JsonObject properties = new JsonObject(); + for (Map.Entry, Comparable> property + : actual.getProperties().entrySet()) { + properties.addProperty(property.getKey().getName(), property.getValue().toString() + .toLowerCase(java.util.Locale.ROOT)); + } + saved.add("saved_properties", properties); + states++; + } + } + + WorldServer world = server.worldServerForDimension(0); + int stacks = 0; + for (com.google.gson.JsonElement element : manifest.getAsJsonArray("items")) { + JsonObject saved = element.getAsJsonObject(); + BlockPos pos = new BlockPos(saved.get("chest_x").getAsInt(), saved.get("chest_y").getAsInt(), + saved.get("chest_z").getAsInt()); + world.getBlockState(pos); + TileEntityChest chest = (TileEntityChest) world.getTileEntity(pos); + ItemStack stack = chest.getStackInSlot(saved.get("slot").getAsInt()); + if (stack == null || !stack.getItem().getRegistryName().toString().equals(saved.get("id").getAsString()) + || stack.stackSize != saved.get("count").getAsInt() + || stack.getItemDamage() != saved.get("damage").getAsInt()) { + throw new IllegalStateException("Source fixture inventory changed at " + pos); + } + if (saved.has("armor_x")) { + BlockPos armorPos = new BlockPos(saved.get("armor_x").getAsDouble(), + saved.get("armor_y").getAsDouble(), saved.get("armor_z").getAsDouble()); + world.getChunkProvider().provideChunk(armorPos.getX() >> 4, armorPos.getZ() >> 4); + List stands = world.getEntitiesWithinAABB(EntityArmorStand.class, + new net.minecraft.util.math.AxisAlignedBB(armorPos).expand(0.75D, 0.75D, 0.75D)); + boolean found = false; + for (EntityArmorStand stand : stands) { + for (EntityEquipmentSlot equipment : EntityEquipmentSlot.values()) { + if (!equipment.getName().equals(saved.get("armor_slot").getAsString())) continue; + ItemStack worn = stand.getItemStackFromSlot(equipment); + found |= worn != null && worn.getItem().getRegistryName().toString() + .equals(saved.get("id").getAsString()) + && worn.getItemDamage() == saved.get("damage").getAsInt(); + } + } + if (!found) throw new IllegalStateException("Source armor stand was not saved: " + saved.get("id")); + } + stacks++; + } + + write(root.resolve("legacy_registry_manifest_runtime.json"), manifest); + Files.write(root.resolve("BASEMETALS_1_10_FIXTURE_RELOADED.txt"), + ("states=" + states + " items=" + stacks + "\\n").getBytes(StandardCharsets.UTF_8)); + org.apache.logging.log4j.LogManager.getLogger(MOD_ID).info( + "BASEMETALS_LEGACY_CAPTURE_RELOAD PASS states={} items={}", states, stacks); + } + + private static JsonArray captureBlocks(List worlds, List ids) { + JsonArray blocks = new JsonArray(); + for (ResourceLocation id : ids) { + Block block = ForgeRegistries.BLOCKS.getValue(id); + JsonObject entry = new JsonObject(); + entry.addProperty("id", id.toString()); + JsonArray states = new JsonArray(); + for (WorldServer world : worlds) { + int ordinal = 0; + for (ResourceLocation preceding : ids) { + if (preceding.equals(id)) break; + ordinal += ForgeRegistries.BLOCKS.getValue(preceding).getBlockState().getValidStates().size(); + } + for (IBlockState state : block.getBlockState().getValidStates()) { + JsonObject stateEntry = new JsonObject(); + stateEntry.addProperty("description", state.toString()); + int metadata; + try { + metadata = block.getMetaFromState(state); + } catch (RuntimeException ignored) { + metadata = -1; + } + stateEntry.addProperty("metadata", metadata); + stateEntry.addProperty("dimension", world.provider.getDimension()); + BlockPos pos = statePosition(ordinal++); + prepareSupport(world, pos, state); + world.setBlockState(pos, state, 2); + stateEntry.addProperty("x", pos.getX()); + stateEntry.addProperty("y", pos.getY()); + stateEntry.addProperty("z", pos.getZ()); + states.add(stateEntry); + } + } + entry.add("states", states); + blocks.add(entry); + } + return blocks; + } + + private static JsonArray captureItems(WorldServer world, List ids) { + JsonArray items = new JsonArray(); + int chestIndex = -1; + TileEntityChest chest = null; + int slot = 27; + int armorStandIndex = 0; + for (ResourceLocation id : ids) { + Item item = ForgeRegistries.ITEMS.getValue(id); + JsonObject entry = new JsonObject(); + entry.addProperty("id", id.toString()); + ItemStack stack = new ItemStack(item); + if (stack.isItemStackDamageable() && stack.getMaxDamage() > 1) { + stack.setItemDamage(Math.max(1, stack.getMaxDamage() / 3)); + if (stack.isItemEnchantable()) stack.addEnchantment(Enchantments.UNBREAKING, 2); + } + NBTTagCompound fixtureTag = stack.getSubCompound("basemetals_fixture", true); + fixtureTag.setString("proof", id.toString()); + stack.setStackDisplayName("Fixture " + id.toString()); + entry.addProperty("custom_name", stack.getDisplayName()); + entry.addProperty("metadata", stack.getMetadata()); + entry.addProperty("damage", stack.getItemDamage()); + entry.addProperty("count", stack.stackSize); + entry.addProperty("max_damage", stack.getMaxDamage()); + entry.addProperty("enchanted", stack.isItemEnchanted()); + entry.addProperty("tag_snbt", stack.getTagCompound().toString()); + + if (slot >= 27) { + chestIndex++; + BlockPos chestPos = chestPosition(chestIndex, 120); + world.setBlockState(chestPos, Blocks.CHEST.getDefaultState(), 3); + chest = (TileEntityChest) world.getTileEntity(chestPos); + slot = 0; + } + chest.setInventorySlotContents(slot, stack.copy()); + entry.addProperty("chest", chestIndex); + entry.addProperty("chest_x", chest.getPos().getX()); + entry.addProperty("chest_y", chest.getPos().getY()); + entry.addProperty("chest_z", chest.getPos().getZ()); + entry.addProperty("slot", slot++); + + if (item instanceof ItemArmor) { + ItemArmor armor = (ItemArmor) item; + EntityArmorStand stand = new EntityArmorStand(world); + stand.setPosition(-32.5D + armorStandIndex % 32, 121.0D, armorStandIndex / 32 + 0.5D); + world.getChunkProvider().provideChunk(((int) Math.floor(stand.posX)) >> 4, + ((int) Math.floor(stand.posZ)) >> 4); + stand.forceSpawn = true; + world.setBlockState(new BlockPos(stand.posX, 120, stand.posZ), Blocks.BEDROCK.getDefaultState(), 2); + stand.setNoGravity(true); + stand.setItemStackToSlot(armor.armorType, stack.copy()); + if (!world.spawnEntity(stand)) { + throw new IllegalStateException("Source runtime rejected armor stand for " + id); + } + entry.addProperty("armor_stand", armorStandIndex++); + entry.addProperty("armor_x", stand.posX); + entry.addProperty("armor_y", stand.posY); + entry.addProperty("armor_z", stand.posZ); + entry.addProperty("armor_slot", armor.armorType.getName()); + } + items.add(entry); + } + return items; + } + + private static JsonArray captureFluids(WorldServer world) { + JsonArray fluids = new JsonArray(); + final int[] ordinal = {0}; + FluidRegistry.getRegisteredFluids().entrySet().stream() + .filter(entry -> isBaseName(entry.getKey()) || isBaseName(entry.getValue().getName()) + || isBaseBlock(entry.getValue())) + .sorted(Map.Entry.comparingByKey()) + .forEach(entry -> { + Fluid fluid = entry.getValue(); + JsonObject value = new JsonObject(); + value.addProperty("name", entry.getKey()); + value.addProperty("block", fluid.getBlock() == null || fluid.getBlock().getRegistryName() == null + ? "" : fluid.getBlock().getRegistryName().toString()); + value.addProperty("temperature", fluid.getTemperature()); + value.addProperty("density", fluid.getDensity()); + net.minecraftforge.fluids.UniversalBucket universal = + net.minecraftforge.common.ForgeModContainer.getInstance().universalBucket; + ItemStack bucket = universal == null ? null + : net.minecraftforge.fluids.UniversalBucket.getFilledBucket(universal, fluid); + if (bucket != null && bucket.getItem() != null) { + int bucketOrdinal = ordinal[0]++; + int chestIndex = bucketOrdinal / 27; + int slot = bucketOrdinal % 27; + BlockPos chestPos = chestPosition(chestIndex, 130); + world.setBlockState(chestPos, Blocks.CHEST.getDefaultState(), 3); + TileEntityChest chest = (TileEntityChest) world.getTileEntity(chestPos); + chest.setInventorySlotContents(slot, bucket.copy()); + value.addProperty("bucket_chest", chestIndex); + value.addProperty("bucket_chest_x", chestPos.getX()); + value.addProperty("bucket_chest_y", chestPos.getY()); + value.addProperty("bucket_chest_z", chestPos.getZ()); + value.addProperty("bucket_slot", slot); + value.addProperty("bucket_id", bucket.getItem().getRegistryName().toString()); + value.addProperty("bucket_tag_snbt", + bucket.hasTagCompound() ? bucket.getTagCompound().toString() : ""); + } + fluids.add(value); + }); + return fluids; + } + + private static JsonArray capturePlayer(WorldServer world, List itemIds) { + JsonArray players = new JsonArray(); + FakePlayer player = FakePlayerFactory.get(world, + new GameProfile(UUID.fromString("75d746b1-d457-4f47-a8f9-02c7669c2d74"), + "BaseMetalsFixture")); + JsonObject entry = new JsonObject(); + entry.addProperty("uuid", player.getUniqueID().toString()); + entry.addProperty("name", player.getName()); + JsonArray inventory = new JsonArray(); + for (int slot = 0; slot < Math.min(9, itemIds.size()); slot++) { + ResourceLocation id = itemIds.get(slot); + ItemStack stack = new ItemStack(ForgeRegistries.ITEMS.getValue(id)); + NBTTagCompound fixtureTag = stack.getSubCompound("basemetals_fixture", true); + fixtureTag.setString("player_proof", id.toString()); + player.inventory.setInventorySlotContents(slot, stack); + JsonObject value = new JsonObject(); + value.addProperty("slot", slot); + value.addProperty("id", id.toString()); + inventory.add(value); + } + entry.add("inventory", inventory); + world.getSaveHandler().getPlayerNBTManager().writePlayerData(player); + players.add(entry); + return players; + } + + private static List ids(Iterable keys) { + List result = new ArrayList<>(); + for (ResourceLocation id : keys) { + if (id != null && (id.getResourceDomain().equals("basemetals") || id.getResourceDomain().equals("mmdlib"))) { + result.add(id); + } + } + return result.stream().sorted(Comparator.comparing(ResourceLocation::toString)).collect(Collectors.toList()); + } + + private static boolean isBaseName(String name) { + return name != null && (name.startsWith("basemetals") || name.startsWith("mmdlib")); + } + + private static boolean isBaseBlock(Fluid fluid) { + if (fluid.getBlock() == null || fluid.getBlock().getRegistryName() == null) return false; + String namespace = fluid.getBlock().getRegistryName().getResourceDomain(); + return namespace.equals("basemetals") || namespace.equals("mmdlib"); + } + + private static BlockPos statePosition(int ordinal) { + return new BlockPos(1_024 + (ordinal % 32) * 4, 80, 1_024 + (ordinal / 32) * 4); + } + + private static BlockPos chestPosition(int ordinal, int y) { + return new BlockPos(-1_024 + (ordinal % 16) * 3, y, 1_024 + (ordinal / 16) * 3); + } + + private static void prepareSupport(WorldServer world, BlockPos pos, IBlockState state) { + for (EnumFacing direction : EnumFacing.values()) { + world.setBlockState(pos.offset(direction), Blocks.BEDROCK.getDefaultState(), 2); + } + if (state.getBlock() instanceof BlockDoor) { + BlockDoor.EnumDoorHalf half = state.getValue(BlockDoor.HALF); + BlockPos companionPos = half == BlockDoor.EnumDoorHalf.LOWER ? pos.up() : pos.down(); + IBlockState companion = state.withProperty(BlockDoor.HALF, + half == BlockDoor.EnumDoorHalf.LOWER + ? BlockDoor.EnumDoorHalf.UPPER : BlockDoor.EnumDoorHalf.LOWER); + world.setBlockState(companionPos, companion, 2); + BlockPos lower = half == BlockDoor.EnumDoorHalf.LOWER ? pos : companionPos; + world.setBlockState(lower.down(), Blocks.BEDROCK.getDefaultState(), 2); + } + } + + private static void write(Path path, JsonObject json) throws IOException { + Files.createDirectories(path.getParent()); + Files.write(path, (GSON.toJson(json) + "\n").getBytes(StandardCharsets.UTF_8)); + } +} diff --git a/tools/validation/legacy110/fixtures/LegacyMmdConsumer.java b/tools/validation/legacy110/fixtures/LegacyMmdConsumer.java new file mode 100644 index 000000000..d8caf5a15 --- /dev/null +++ b/tools/validation/legacy110/fixtures/LegacyMmdConsumer.java @@ -0,0 +1,21 @@ +package review; + +/** Compiled against the old jar, then loaded unchanged by the new candidate. */ +public final class LegacyMmdConsumer extends com.mcmoddev.basemetals.proxy.CommonProxy { + + public static Object material() { + return com.mcmoddev.basemetals.init.Materials.getMaterialByName("copper"); + } + + public static Object instance() { + return com.mcmoddev.basemetals.BaseMetals.instance; + } + + public static Class literal() { + return com.mcmoddev.basemetals.BaseMetals.class; + } + + public static Class reflected() throws ClassNotFoundException { + return Class.forName("com.mcmoddev.basemetals.BaseMetals"); + } +} diff --git a/tools/validation/legacy110/fixtures/orespawn1-basemetals.json b/tools/validation/legacy110/fixtures/orespawn1-basemetals.json new file mode 100644 index 000000000..07faa03ed --- /dev/null +++ b/tools/validation/legacy110/fixtures/orespawn1-basemetals.json @@ -0,0 +1,103 @@ +{ + "dimensions":[ + { + "dimension":"+", + "__comment":"dimension 0 for overworld, -1 for the nether, 1 for the end, other numbers for dimensions added by other mods, and + for any dimension not already described by this file.", + "ores":[ + { + "__comment":"These settings are roughly equivalent to the parameters of the CUSTOM world json: blockID = the unlocalized name of a block in the format 'ModID:blockName',blockMeta = the meta data value for the block (optional),size = number of blocks to spawn per ore deposit, variation = vary the size of deposits by +/- this amount, frequency = number of deposits per chunk (can be less than 1, e.g. 0.5 for 1 deposit every 2 chunks), minHeight = minimum spawn height, maxHeight = maximum spawn height, biomes = array of biomes to spawn this ore in (if empty or absent, the ore will spawn in all biomes). Note that the size values in the Minecraft CUSTOM world json do not represent the actual number of blocks spawned, where-as the size value here does represent actual number of blocks. ", + "blockID":"basemetals:copper_ore", + "size":8, + "variation":4, + "frequency":10, + "minHeight":0, + "maxHeight":96 + }, + { + "blockID":"basemetals:silver_ore", + "size":8, + "variation":4, + "frequency":4, + "minHeight":0, + "maxHeight":32 + }, + { + "blockID":"basemetals:tin_ore", + "size":8, + "variation":4, + "frequency":10, + "minHeight":0, + "maxHeight":128 + }, + { + "blockID":"basemetals:lead_ore", + "size":8, + "variation":4, + "frequency":5, + "minHeight":0, + "maxHeight":64 + }, + { + "blockID":"basemetals:zinc_ore", + "size":8, + "variation":4, + "frequency":5, + "minHeight":0, + "maxHeight":96 + }, + { + "blockID":"basemetals:mercury_ore", + "size":8, + "variation":4, + "frequency":3, + "minHeight":0, + "maxHeight":32 + }, + { + "blockID":"basemetals:nickel_ore", + "size":8, + "variation":4, + "frequency":5, + "minHeight":50, + "maxHeight":128 + } + ] + }, + { + "dimension":-1, + "__comment":"the nether", + "ores":[ + { + "blockID":"basemetals:coldiron_ore", + "size":8, + "variation":4, + "frequency":5, + "minHeight":0, + "maxHeight":128 + }, + { + "blockID":"basemetals:adamantine_ore", + "size":8, + "variation":4, + "frequency":2, + "minHeight":0, + "maxHeight":128 + } + ] + }, + { + "dimension":1, + "__comment":"the end", + "ores":[ + { + "blockID":"basemetals:starsteel_ore", + "size":8, + "variation":4, + "frequency":5, + "minHeight":0, + "maxHeight":255 + } + ] + } + ] +} diff --git a/src/main/resources/assets/basemetals/orespawn/basemetals.json b/tools/validation/legacy110/fixtures/orespawn3-basemetals.json similarity index 99% rename from src/main/resources/assets/basemetals/orespawn/basemetals.json rename to tools/validation/legacy110/fixtures/orespawn3-basemetals.json index 0899d53c8..4b481884f 100644 --- a/src/main/resources/assets/basemetals/orespawn/basemetals.json +++ b/tools/validation/legacy110/fixtures/orespawn3-basemetals.json @@ -261,4 +261,4 @@ ] } } -} \ No newline at end of file +} diff --git a/tools/validation/legacy110/launch-probe/review/launch/DevLaunchProbe.java b/tools/validation/legacy110/launch-probe/review/launch/DevLaunchProbe.java new file mode 100644 index 000000000..8b8d711a6 --- /dev/null +++ b/tools/validation/legacy110/launch-probe/review/launch/DevLaunchProbe.java @@ -0,0 +1,157 @@ +package review.launch; + +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.concurrent.Future; +import net.minecraft.block.Block; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiMainMenu; +import net.minecraft.init.Blocks; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.server.MinecraftServer; +import net.minecraft.tileentity.TileEntityChest; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.GameType; +import net.minecraft.world.WorldServer; +import net.minecraft.world.WorldSettings; +import net.minecraft.world.WorldType; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.common.FMLCommonHandler; +import net.minecraftforge.fml.common.Loader; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.event.FMLInitializationEvent; +import net.minecraftforge.fml.common.event.FMLServerStartedEvent; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; + +/** Opens a disposable world and checks a named stack through a second launch. */ +@Mod(modid = "basemetalslaunchprobe", name = "Base Metals launch checks", version = "1", + acceptableRemoteVersions = "*", dependencies = "required-after:basemetals;after:orespawn") +public final class DevLaunchProbe { + + @Mod.EventHandler + public void init(final FMLInitializationEvent event) { + if (System.getProperty("launch.phase") == null) return; + if (FMLCommonHandler.instance().getSide().isClient()) { + MinecraftForge.EVENT_BUS.register(new Client()); + } + } + + @Mod.EventHandler + public void started(final FMLServerStartedEvent event) throws Exception { + if (System.getProperty("launch.phase") == null) return; + final MinecraftServer server = FMLCommonHandler.instance().getMinecraftServerInstance(); + if (!server.isDedicatedServer()) return; + + try { + checkWorld(server); + report(true, "dedicated server and saved inventory"); + } catch (final Throwable failure) { + report(false, failure.toString()); + throw failure; + } finally { + server.initiateShutdown(); + } + } + + private static void checkWorld(final MinecraftServer server) { + final String expectedVersion = System.getProperty("launch.expectedVersion", "2.5.1.110021"); + require(expectedVersion.equals(Loader.instance().getIndexedModList().get("basemetals").getVersion()), "Wrong Base Metals version"); + require(Loader.isModLoaded("orespawn"), "The ordinary launch did not load OreSpawn"); + for (final String optional : new String[]{"tconstruct", "ic2", "cofhcore", "Mekanism", "taiga", "golems_metal"}) { + require(!Loader.isModLoaded(optional), "Optional mod leaked into ordinary launch: " + optional); + } + + final WorldServer world = server.worldServerForDimension(0); + final BlockPos position = new BlockPos(8, 80, 8); + final ResourceLocation copper = new ResourceLocation("basemetals", "copper_block"); + final Block block = net.minecraftforge.fml.common.registry.ForgeRegistries.BLOCKS.getValue(copper); + final net.minecraft.item.Item ingot = net.minecraftforge.fml.common.registry.ForgeRegistries.ITEMS + .getValue(new ResourceLocation("basemetals", "copper_ingot")); + + if ("fresh".equals(System.getProperty("launch.phase"))) { + world.setBlockState(position, block.getDefaultState(), 2); + world.setBlockState(position.up(), Blocks.CHEST.getDefaultState(), 2); + final ItemStack stack = new ItemStack(ingot, 3); + stack.setStackDisplayName("FG7 saved copper"); + final NBTTagCompound marker = new NBTTagCompound(); + marker.setString("Check", "kept across reload"); + stack.getTagCompound().setTag("BaseMetalsBuildCheck", marker); + ((TileEntityChest) world.getTileEntity(position.up())).setInventorySlotContents(0, stack); + } + + require(world.getBlockState(position).getBlock() == block, "Saved copper block changed"); + final TileEntityChest chest = (TileEntityChest) world.getTileEntity(position.up()); + final ItemStack stack = chest.getStackInSlot(0); + require(stack != null && stack.getItem() == ingot && stack.stackSize == 3 + && "FG7 saved copper".equals(stack.getDisplayName()) + && "kept across reload".equals(stack.getTagCompound() + .getCompoundTag("BaseMetalsBuildCheck").getString("Check")), "Saved stack data changed"); + world.getChunkFromBlockCoords(position).setChunkModified(); + if (Boolean.getBoolean("launch.remoteMarker")) { + final BlockPos remote = new BlockPos(1288, 80, -1272); + world.setBlockState(remote, block.getDefaultState(), 2); + world.setBlockState(remote.up(), Blocks.CHEST.getDefaultState(), 2); + final ItemStack saved = chest.getStackInSlot(0).copy(); + ((TileEntityChest) world.getTileEntity(remote.up())).setInventorySlotContents(0, saved); + world.getChunkFromBlockCoords(remote).setChunkModified(); + } + server.saveAllWorlds(false); + } + + private static void report(final boolean passed, final String detail) throws Exception { + final JsonObject report = new JsonObject(); + report.addProperty("phase", System.getProperty("launch.phase")); + report.addProperty("passed", passed); + report.addProperty("detail", detail); + Files.write(Paths.get(System.getProperty("launch.phase") + "-launch.json"), + (new GsonBuilder().setPrettyPrinting().create().toJson(report) + "\n").getBytes(StandardCharsets.UTF_8)); + System.out.println("BASE_METALS_LAUNCH_" + (passed ? "PASS" : "FAIL") + " " + detail); + } + + private static void require(final boolean condition, final String message) { + if (!condition) throw new AssertionError(message); + } + + public static final class Client { + private final long started = System.currentTimeMillis(); + private boolean opening; + private int menuTicks; + private Future check; + private boolean finished; + + @SubscribeEvent + public void tick(final TickEvent.ClientTickEvent event) throws Exception { + if (event.phase != TickEvent.Phase.END || finished) return; + final Minecraft client = Minecraft.getMinecraft(); + + try { + if (!opening && client.currentScreen instanceof GuiMainMenu && ++menuTicks >= 20) { + opening = true; + System.out.println("BASE_METALS_LAUNCH_MENU"); + client.launchIntegratedServer("fg7-launch-check", "FG7 Launch Check", + new WorldSettings(8675309L, GameType.CREATIVE, false, false, WorldType.FLAT)); + } + if (opening && client.world != null && client.player != null && check == null) { + check = client.getIntegratedServer().addScheduledTask(() -> checkWorld(client.getIntegratedServer())); + } + if (check != null && check.isDone()) { + check.get(); + report(true, "main menu, integrated world and saved inventory"); + finished = true; + client.shutdown(); + } + if (System.currentTimeMillis() - started > 180000) throw new AssertionError("Client startup timed out"); + } catch (final Throwable failure) { + report(false, failure.toString()); + finished = true; + client.shutdown(); + } + } + } +} diff --git a/tools/validation/legacy110/migration-probe/review/GolemCompatibilityProbe.java b/tools/validation/legacy110/migration-probe/review/GolemCompatibilityProbe.java new file mode 100644 index 000000000..b0d17e3d2 --- /dev/null +++ b/tools/validation/legacy110/migration-probe/review/GolemCompatibilityProbe.java @@ -0,0 +1,224 @@ +package review; + +import static review.Legacy110Probe.call; + +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import java.io.Writer; +import java.lang.reflect.InvocationTargetException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.BlockPos; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.common.FMLCommonHandler; +import net.minecraftforge.fml.common.Loader; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.event.FMLServerStartedEvent; +import net.minecraftforge.fml.common.registry.ForgeRegistries; +import net.minecraftforge.fml.common.eventhandler.Event; + +/** Checks the unmodified Golems add-on against saved entities and current materials. */ +@Mod(modid = "basemetalsgolemprobe", name = "Base Metals golem checks", version = "1", + serverSideOnly = true, acceptableRemoteVersions = "*", + dependencies = "required-after:basemetals;after:golems_metal;after:legacy110review") +public final class GolemCompatibilityProbe { + private static final String[] MATERIALS = { "adamantine", "aquarium", "brass", "bronze", + "coldiron", "copper", "cupronickel", "electrum", "invar", "lead", "mithril", + "nickel", "platinum", "silver", "starsteel", "steel", "tin", "zinc" }; + private static final String[] CLASSES = { "Adamantine", "Aquarium", "Brass", "Bronze", + "ColdIron", "Copper", "Cupronickel", "Electrum", "Invar", "Lead", "Mithril", + "Nickel", "Platinum", "Silver", "StarSteel", "Steel", "Tin", "Zinc" }; + + @Mod.EventHandler + public void started(final FMLServerStartedEvent event) throws Exception { + final String phase = System.getProperty("golem.phase"); + if (phase == null) return; + + final Object server = FMLCommonHandler.instance().getMinecraftServerInstance(); + final Object world = call(server, new String[]{"func_71218_a", "worldServerForDimension"}, new Class[]{int.class}, 0); + final JsonObject report = new JsonObject(); + final JsonArray errors = new JsonArray(); + report.addProperty("phase", phase); + report.addProperty("addonPresent", Loader.isModLoaded("golems_metal")); + + try { + if ("absent".equals(phase)) { + require(!Loader.isModLoaded("golems_metal"), "Golems add-on was not excluded"); + report.add("humanDetector", Legacy110Probe.verifyHumanDetector(world)); + verifyBridge(); + } else if ("seed".equals(phase)) { + seed(world, 14, 13, "near"); + seed(world, 154, -504, "later"); + report.addProperty("seeded", 36); + } else { + require(Loader.isModLoaded("golems_metal"), "Missing unmodified Golems add-on"); + verifyBridge(); + report.addProperty("constructedAndBuilt", verifyConstruction(world)); + report.addProperty("nearEntities", verifySaved(world, 14, 13, "near")); + if ("delayed".equals(phase) || "stable".equals(phase)) { + report.addProperty("laterEntities", verifySaved(world, 154, -504, "later")); + } + } + + if (System.getProperty("review.chunks") != null) { + Legacy110Probe.visitChunks(server); + } + if (Boolean.getBoolean("golem.checkMmdApi")) { + verifyMmdApi(); + report.addProperty("oldMmdApi", true); + } + } catch (final Throwable failure) { + final Throwable cause = failure instanceof InvocationTargetException ? failure.getCause() : failure; + errors.add(new JsonPrimitive(cause.toString())); + cause.printStackTrace(); + } + + report.add("errors", errors); + report.addProperty("passed", errors.size() == 0); + try (Writer writer = Files.newBufferedWriter(Paths.get(phase + "-golems.json"), StandardCharsets.UTF_8)) { + new GsonBuilder().setPrettyPrinting().create().toJson(report, writer); + } + + call(server, new String[]{"func_71267_a", "saveAllWorlds"}, new Class[]{boolean.class}, false); + System.out.println("BASE_METALS_GOLEMS_" + (errors.size() == 0 ? "PASS" : "FAIL") + " phase=" + phase); + call(server, new String[]{"func_71263_m", "initiateShutdown"}, new Class[0]); + } + + private static void verifyBridge() throws Exception { + final Class blocks = Class.forName("cyano.basemetals.init.Blocks"); + final Class items = Class.forName("cyano.basemetals.init.Items"); + + for (final String material : MATERIALS) { + require(blocks.getField(material + "_block").get(null) == ForgeRegistries.BLOCKS.getValue(id(material, "block")), + material + " has a duplicate or missing block"); + require(items.getField(material + "_ingot").get(null) == ForgeRegistries.ITEMS.getValue(id(material, "ingot")), + material + " has a duplicate or missing ingot"); + } + } + + private static void verifyMmdApi() throws Exception { + final Class consumer = Class.forName("review.LegacyMmdConsumer"); + final Class current = Class.forName("zone.moddev.mc.basemetals.BaseMetals"); + require(consumer.getMethod("literal").invoke(null) == current, "Old MMD class literal has another identity"); + require(consumer.getMethod("reflected").invoke(null) == current, "Old MMD reflection name has another identity"); + require(consumer.getMethod("instance").invoke(null) == current.getField("instance").get(null), "Duplicate mod instance"); + require(consumer.getSuperclass() == Class.forName("zone.moddev.mc.basemetals.proxy.CommonProxy"), "Old MMD inheritance was not relinked"); + final Class materials = Class.forName("zone.moddev.mc.basemetals.init.Materials"); + require(consumer.getMethod("material").invoke(null) == materials.getMethod("getMaterialByName", String.class).invoke(null, "copper"), "Duplicate material registry"); + } + + private static int verifyConstruction(final Object world) throws Exception { + final Class worldType = Class.forName("net.minecraft.world.World"); + final Class stateType = Class.forName("net.minecraft.block.state.IBlockState"); + final BlockPos head = new BlockPos(400, 233, 400); + final BlockPos[] body = {new BlockPos(400, 232, 400), new BlockPos(400, 231, 400), + new BlockPos(399, 232, 400), new BlockPos(401, 232, 400)}; + final Object[] previous = new Object[body.length]; + call(world, new String[]{"func_72964_e", "getChunkFromChunkCoords"}, new Class[]{int.class, int.class}, 25, 25); + + for (int i = 0; i < body.length; i++) { + previous[i] = call(world, new String[]{"func_180495_p", "getBlockState"}, new Class[]{BlockPos.class}, body[i]); + } + + try { + for (int i = 0; i < MATERIALS.length; i++) { + final String material = MATERIALS[i]; + final Object block = ForgeRegistries.BLOCKS.getValue(id(material, "block")); + final Object state = call(block, new String[]{"func_176223_P", "getDefaultState"}, new Class[0]); + for (final BlockPos pos : body) { + call(world, new String[]{"func_180501_a", "setBlockState"}, new Class[]{BlockPos.class, stateType, int.class}, pos, state, 2); + } + + final Object event = Class.forName("com.golems.events.GolemBuildEvent") + .getConstructor(worldType, BlockPos.class, boolean.class).newInstance(world, head, true); + MinecraftForge.EVENT_BUS.post((Event) event); + final Object built = event.getClass().getMethod("getGolem").invoke(event); + require(built != null && built.getClass() == golemClass(i), "Wrong constructed golem for " + material); + require(!(Boolean) event.getClass().getMethod("isGolemBanned").invoke(event), "Default golem is banned: " + material); + verifyMaterial(built, material); + } + } finally { + for (int i = 0; i < body.length; i++) { + call(world, new String[]{"func_180501_a", "setBlockState"}, new Class[]{BlockPos.class, stateType, int.class}, body[i], previous[i], 2); + } + } + return MATERIALS.length; + } + + private static void verifyMaterial(final Object golem, final String material) throws Exception { + final Object ingot = ForgeRegistries.ITEMS.getValue(id(material, "ingot")); + final Object returned = golem.getClass().getMethod("getCreativeReturn").invoke(golem); + final Object returnedItem = call(returned, new String[]{"func_77973_b", "getItem"}, new Class[0]); + require(returnedItem == ForgeRegistries.ITEMS.getValue(id(material, "block")), "Wrong golem block: " + material); + + final List drops = new ArrayList<>(); + golem.getClass().getMethod("addGolemDrops", List.class, boolean.class, int.class).invoke(golem, drops, false, 0); + require(drops.size() == 1, "Wrong golem drops: " + material); + require(drops.get(0).getClass().getField("item").get(drops.get(0)) == ingot, "Wrong dropped ingot: " + material); + } + + private static void seed(final Object world, final int chunkX, final int chunkZ, final String group) throws Exception { + final Object chunk = call(world, new String[]{"func_72964_e", "getChunkFromChunkCoords"}, new Class[]{int.class, int.class}, chunkX, chunkZ); + final Class entityType = Class.forName("net.minecraft.entity.Entity"); + + for (int i = 0; i < MATERIALS.length; i++) { + final Object golem = golemClass(i).getConstructor(Class.forName("net.minecraft.world.World")).newInstance(world); + final String marker = "BM compatibility " + group + " " + MATERIALS[i]; + call(golem, new String[]{"func_96094_a", "setCustomNameTag"}, new Class[]{String.class}, marker); + call(golem, new String[]{"func_94061_f", "setNoAI"}, new Class[]{boolean.class}, true); + call(golem, new String[]{"func_189654_d", "setNoGravity"}, new Class[]{boolean.class}, true); + call(golem, new String[]{"func_70107_b", "setPosition"}, new Class[]{double.class, double.class, double.class}, + chunkX * 16.0 + 1.5 + (i % 4) * 3, 230.0, chunkZ * 16.0 + 1.5 + (i / 4) * 3); + final float maximum = (Float) call(golem, new String[]{"func_110138_aP", "getMaxHealth"}, new Class[0]); + call(golem, new String[]{"func_70606_j", "setHealth"}, new Class[]{float.class}, maximum - 1.5F); + require((Boolean) call(world, new String[]{"func_72838_d", "spawnEntityInWorld"}, new Class[]{entityType}, golem), "Could not seed " + marker); + } + call(chunk, new String[]{"func_76630_e", "setChunkModified"}, new Class[0]); + } + + private static int verifySaved(final Object world, final int chunkX, final int chunkZ, final String group) throws Exception { + call(world, new String[]{"func_72964_e", "getChunkFromChunkCoords"}, new Class[]{int.class, int.class}, chunkX, chunkZ); + final Class boxType = Class.forName("net.minecraft.util.math.AxisAlignedBB"); + final Object box = boxType.getConstructor(double.class, double.class, double.class, double.class, double.class, double.class) + .newInstance(chunkX * 16.0, 220.0, chunkZ * 16.0, chunkX * 16.0 + 16, 245.0, chunkZ * 16.0 + 16); + final List entities = (List) call(world, new String[]{"func_72872_a", "getEntitiesWithinAABB"}, + new Class[]{Class.class, boxType}, Class.forName("net.minecraft.entity.Entity"), box); + + for (int i = 0; i < MATERIALS.length; i++) { + final String marker = "BM compatibility " + group + " " + MATERIALS[i]; + Object match = null; + for (final Object entity : entities) { + final String name = (String) call(entity, new String[]{"func_95999_t", "getCustomNameTag"}, new Class[0]); + if (marker.equals(name)) { + require(match == null, "Duplicate saved golem: " + marker); + match = entity; + } + } + + require(match != null && match.getClass() == golemClass(i), "Missing or changed saved golem: " + marker); + verifyMaterial(match, MATERIALS[i]); + final float maximum = (Float) call(match, new String[]{"func_110138_aP", "getMaxHealth"}, new Class[0]); + final float health = (Float) call(match, new String[]{"func_110143_aJ", "getHealth"}, new Class[0]); + require(health == maximum - 1.5F, "Health changed: " + marker); + } + return MATERIALS.length; + } + + private static Class golemClass(final int index) throws ClassNotFoundException { + return Class.forName("com.golems_metal.entity.Entity" + CLASSES[index] + "Golem"); + } + + private static ResourceLocation id(final String material, final String form) { + return new ResourceLocation("basemetals", material + "_" + form); + } + + private static void require(final boolean condition, final String message) { + if (!condition) throw new AssertionError(message); + } +} diff --git a/tools/validation/legacy110/migration-probe/review/Legacy110Probe.java b/tools/validation/legacy110/migration-probe/review/Legacy110Probe.java new file mode 100644 index 000000000..e3870630b --- /dev/null +++ b/tools/validation/legacy110/migration-probe/review/Legacy110Probe.java @@ -0,0 +1,252 @@ +package review; + +import com.google.gson.*; +import java.io.*; +import java.lang.reflect.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.*; +import java.util.*; +import net.minecraftforge.fml.common.*; +import net.minecraftforge.fml.common.event.FMLServerStartedEvent; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.fml.common.registry.ForgeRegistries; + +@Mod(modid="legacy110review", name="Legacy 1.10 review", version="1", serverSideOnly=true, + acceptableRemoteVersions="*", dependencies="required-after:basemetals;after:poweradvantage;after:steamadvantage;after:electricadvantage") +public final class Legacy110Probe { + @Mod.EventHandler + public void started(FMLServerStartedEvent event) throws Exception { + if (System.getProperty("golem.phase") != null) return; + Object server = FMLCommonHandler.instance().getMinecraftServerInstance(); + Object world = call(server, new String[]{"func_71218_a", "worldServerForDimension"}, new Class[]{int.class}, 0); + + if (System.getProperty("review.chunks") != null) { + visitChunks(server); + call(server, new String[]{"func_71267_a","saveAllWorlds"},new Class[]{boolean.class},false); + System.out.println("LEGACY110_WORLD_VISITS_COMPLETE phase=" + System.getProperty("review.phase")); + call(server,new String[]{"func_71263_m","initiateShutdown"},new Class[0]); + return; + } + + JsonObject manifest; + try (Reader reader = Files.newBufferedReader(Paths.get(System.getProperty("review.manifest")), StandardCharsets.UTF_8)) { + manifest = new JsonParser().parse(reader).getAsJsonObject(); + } + String phase = System.getProperty("review.phase"); + JsonObject result = new JsonObject(); + JsonArray checked = new JsonArray(); + result.addProperty("phase", phase); + result.add("items", checked); + int missing = 0, changedData = 0, changedIdentity = 0; + for (JsonElement element : manifest.getAsJsonArray("items")) { + JsonObject expected = element.getAsJsonObject(); + int x=expected.get("chest_x").getAsInt(), z=expected.get("chest_z").getAsInt(); + if ((phase.equals("first") || phase.equals("reload")) && (x >> 4) == -64 && (z >> 4) == 64) continue; + BlockPos pos = new BlockPos(x, expected.get("chest_y").getAsInt(), z); + Object chest = call(world, new String[]{"func_175625_s", "getTileEntity"}, new Class[]{BlockPos.class}, pos); + if (chest == null) throw new AssertionError("Missing fixture chest " + pos); + Object stack = call(chest, new String[]{"func_70301_a", "getStackInSlot"}, new Class[]{int.class}, expected.get("slot").getAsInt()); + JsonObject actual = new JsonObject(); + actual.addProperty("old", expected.get("id").getAsString()); + if (stack == null) { + actual.addProperty("missing", true); + missing++; + } else { + Object item = call(stack, new String[]{"func_77973_b", "getItem"}, new Class[0]); + String id = String.valueOf(item.getClass().getMethod("getRegistryName").invoke(item)); + actual.addProperty("id", id); + + String expectedId = expected.get("id").getAsString(); + if (expectedId.endsWith("_door_item")) { + expectedId = expectedId.substring(0, expectedId.length() - 5); + } else if (expectedId.equals("basemetals:liquid_mercury")) { + expectedId = "basemetals:mercury"; + } else if (expectedId.equals("basemetals:carbon_powder")) { + expectedId = "basemetals:coal_powder"; + } + + if (!id.equals(expectedId)) changedIdentity++; + int count = integerField(stack, "field_77994_a", "stackSize"); + int damage = (Integer) call(stack, new String[]{"func_77952_i", "getItemDamage"}, new Class[0]); + Object tag = call(stack, new String[]{"func_77978_p", "getTagCompound"}, new Class[0]); + actual.addProperty("count", count); + actual.addProperty("damage", damage); + actual.addProperty("tag_snbt", String.valueOf(tag)); + boolean preserved = count == expected.get("count").getAsInt() + && damage == expected.get("damage").getAsInt() + && String.valueOf(tag).equals(expected.get("tag_snbt").getAsString()); + actual.addProperty("dataPreserved", preserved); + if (!preserved) changedData++; + } + checked.add(actual); + } + JsonObject mods = new JsonObject(); + for (String id : new String[]{"basemetals","poweradvantage","steamadvantage","electricadvantage"}) { + ModContainer mod = Loader.instance().getIndexedModList().get(id); + mods.addProperty(id, mod == null ? "MISSING" : mod.getVersion()); + } + result.add("mods", mods); + result.add("humanDetector", verifyHumanDetector(world)); + if (phase.equals("delayed") || phase.equals("stable")) { + JsonArray blockChanges = new JsonArray(); + int states = 0; + for (JsonElement blockElement : manifest.getAsJsonArray("blocks")) { + JsonObject entry = blockElement.getAsJsonObject(); + String oldId = entry.get("id").getAsString(); + String expectedId = oldId.equals("basemetals:liquid_mercury") ? "basemetals:mercury" : oldId; + for (JsonElement stateElement : entry.getAsJsonArray("states")) { + JsonObject saved = stateElement.getAsJsonObject(); + Object dimension = call(server,new String[]{"func_71218_a","worldServerForDimension"}, + new Class[]{int.class},saved.get("dimension").getAsInt()); + BlockPos pos = new BlockPos(saved.get("x").getAsInt(),saved.get("y").getAsInt(),saved.get("z").getAsInt()); + Object state = call(dimension,new String[]{"func_180495_p","getBlockState"},new Class[]{BlockPos.class},pos); + Object block = call(state,new String[]{"func_177230_c","getBlock"},new Class[0]); + String id = String.valueOf(block.getClass().getMethod("getRegistryName").invoke(block)); + int meta = (Integer)call(block,new String[]{"func_176201_c","getMetaFromState"}, + new Class[]{Class.forName("net.minecraft.block.state.IBlockState")},state); + int expectedMeta = saved.get("saved_metadata").getAsInt(); + if (!id.equals(expectedId) || meta != expectedMeta) { + JsonObject change = new JsonObject(); + change.addProperty("old",oldId); change.addProperty("id",id); + change.addProperty("expectedMeta",expectedMeta); change.addProperty("meta",meta); + change.addProperty("position",pos.toString()); change.addProperty("dimension",saved.get("dimension").getAsInt()); + blockChanges.add(change); + } + states++; + } + } + result.addProperty("blockStatesChecked",states); + result.add("blockChanges",blockChanges); + } + result.addProperty("missing", missing); + result.addProperty("changedData", changedData); + result.addProperty("changedIdentity", changedIdentity); + boolean passed = missing == 0 && changedData == 0 && changedIdentity == 0 + && (!result.has("blockChanges") || result.getAsJsonArray("blockChanges").size() == 0); + result.addProperty("passed", passed); + try (Writer writer=Files.newBufferedWriter(Paths.get(phase+"-review.json"),StandardCharsets.UTF_8)) { + new GsonBuilder().setPrettyPrinting().create().toJson(result,writer); + } + call(server, new String[]{"func_71267_a","saveAllWorlds"},new Class[]{boolean.class},false); + System.out.println("LEGACY110_" + (passed ? "PASS" : "FAIL") + " phase="+phase+" checked="+checked.size()+" missing="+missing+" changedData="+changedData); + call(server,new String[]{"func_71263_m","initiateShutdown"},new Class[0]); + } + + static void visitChunks(Object server) throws Exception { + int visited = 0; + for (String line : Files.readAllLines(Paths.get(System.getProperty("review.chunks")), StandardCharsets.UTF_8)) { + if (line.trim().isEmpty()) continue; + String[] point = line.split(","); + Object dimension = call(server, new String[]{"func_71218_a", "worldServerForDimension"}, + new Class[]{int.class}, Integer.parseInt(point[0])); + Object chunk = call(dimension, new String[]{"func_72964_e", "getChunkFromChunkCoords"}, + new Class[]{int.class, int.class}, Integer.parseInt(point[1]), Integer.parseInt(point[2])); + call(chunk, new String[]{"func_76630_e", "setChunkModified"}, new Class[0]); + visited++; + } + if (visited == 0) throw new AssertionError("No migration chunks were visited"); + System.out.println("LEGACY110_CHUNKS visited=" + visited); + } + + static JsonObject verifyHumanDetector(Object world) throws Exception { + ResourceLocation name = new ResourceLocation("basemetals", "human_detector"); + Object detector = ForgeRegistries.BLOCKS.getValue(name); + Object item = ForgeRegistries.ITEMS.getValue(name); + if (!ForgeRegistries.BLOCKS.containsKey(name) || !ForgeRegistries.ITEMS.containsKey(name)) { + throw new AssertionError("The Human Detector block and item must both be registered"); + } + Object itemBlock = call(item, new String[]{"func_179223_d", "getBlock"}, new Class[0]); + if (itemBlock != detector) throw new AssertionError("Human Detector item places another block"); + + Class worldType = Class.forName("net.minecraft.world.World"); + Class entityType = Class.forName("net.minecraft.entity.Entity"); + Method strength = null; + for (Method method : detector.getClass().getDeclaredMethods()) { + if (method.getReturnType() == int.class + && Arrays.equals(method.getParameterTypes(), new Class[]{worldType, BlockPos.class})) { + strength = method; + strength.setAccessible(true); + } + } + if (strength == null) throw new AssertionError("Missing player detection method"); + + BlockPos pos = new BlockPos(400, 230, 400); + call(world, new String[]{"func_72964_e", "getChunkFromChunkCoords"}, new Class[]{int.class, int.class}, 25, 25); + Object zombie = Class.forName("net.minecraft.entity.monster.EntityZombie") + .getConstructor(worldType).newInstance(world); + Object profile = Class.forName("com.mojang.authlib.GameProfile") + .getConstructor(UUID.class, String.class).newInstance(UUID.fromString("ff5b37c5-5351-4617-aa26-8e67005dc684"), "DetectorTest"); + Object player = Class.forName("net.minecraftforge.common.util.FakePlayer") + .getConstructor(Class.forName("net.minecraft.world.WorldServer"), profile.getClass()).newInstance(world, profile); + JsonObject result = new JsonObject(); + Class stateType = Class.forName("net.minecraft.block.state.IBlockState"); + BlockPos below = new BlockPos(400, 229, 400); + Object oldPlate = call(world, new String[]{"func_180495_p", "getBlockState"}, new Class[]{BlockPos.class}, pos); + Object oldFloor = call(world, new String[]{"func_180495_p", "getBlockState"}, new Class[]{BlockPos.class}, below); + + try { + Object stone = ForgeRegistries.BLOCKS.getValue(new ResourceLocation("minecraft", "stone")); + Object floor = call(stone, new String[]{"func_176223_P", "getDefaultState"}, new Class[0]); + Object plate = call(detector, new String[]{"func_176223_P", "getDefaultState"}, new Class[0]); + call(world, new String[]{"func_180501_a", "setBlockState"}, new Class[]{BlockPos.class, stateType, int.class}, below, floor, 2); + call(world, new String[]{"func_180501_a", "setBlockState"}, new Class[]{BlockPos.class, stateType, int.class}, pos, plate, 2); + int empty = (Integer) strength.invoke(detector, world, pos); + call(zombie, new String[]{"func_70107_b", "setPosition"}, new Class[]{double.class, double.class, double.class}, 400.5, 230.0, 400.5); + call(world, new String[]{"func_72838_d", "spawnEntityInWorld"}, new Class[]{entityType}, zombie); + int mob = (Integer) strength.invoke(detector, world, pos); + call(detector, new String[]{"func_180634_a", "onEntityCollidedWithBlock"}, new Class[]{worldType, BlockPos.class, stateType, entityType}, world, pos, plate, zombie); + int mobPower = power(detector, world, pos); + call(player, new String[]{"func_70107_b", "setPosition"}, new Class[]{double.class, double.class, double.class}, 400.5, 230.0, 400.5); + call(world, new String[]{"func_72838_d", "spawnEntityInWorld"}, new Class[]{entityType}, player); + int human = (Integer) strength.invoke(detector, world, pos); + call(detector, new String[]{"func_180634_a", "onEntityCollidedWithBlock"}, new Class[]{worldType, BlockPos.class, stateType, entityType}, world, pos, plate, player); + int humanPower = power(detector, world, pos); + call(player, new String[]{"func_70107_b", "setPosition"}, new Class[]{double.class, double.class, double.class}, 403.5, 230.0, 400.5); + int released = (Integer) strength.invoke(detector, world, pos); + Object pressed = call(world, new String[]{"func_180495_p", "getBlockState"}, new Class[]{BlockPos.class}, pos); + call(detector, new String[]{"func_180650_b", "updateTick"}, new Class[]{worldType, BlockPos.class, stateType, Random.class}, world, pos, pressed, new Random(0)); + int releasedPower = power(detector, world, pos); + + result.addProperty("empty", empty); + result.addProperty("mob", mob); + result.addProperty("player", human); + result.addProperty("released", released); + result.addProperty("mobPower", mobPower); + result.addProperty("playerPower", humanPower); + result.addProperty("releasedPower", releasedPower); + if (empty != 0 || mob != 0 || human != 15 || released != 0 + || mobPower != 0 || humanPower != 15 || releasedPower != 0) { + throw new AssertionError("Incorrect Human Detector strengths: " + result); + } + } finally { + call(world, new String[]{"func_72900_e", "removeEntity"}, new Class[]{entityType}, player); + call(world, new String[]{"func_72900_e", "removeEntity"}, new Class[]{entityType}, zombie); + call(world, new String[]{"func_180501_a", "setBlockState"}, new Class[]{BlockPos.class, stateType, int.class}, pos, oldPlate, 2); + call(world, new String[]{"func_180501_a", "setBlockState"}, new Class[]{BlockPos.class, stateType, int.class}, below, oldFloor, 2); + } + return result; + } + + static int power(Object detector, Object world, BlockPos pos) throws Exception { + Object state = call(world, new String[]{"func_180495_p", "getBlockState"}, new Class[]{BlockPos.class}, pos); + Class facing = Class.forName("net.minecraft.util.EnumFacing"); + Object up = Enum.valueOf((Class) facing, "UP"); + return (Integer) call(detector, new String[]{"func_180656_a", "getWeakPower"}, + new Class[]{Class.forName("net.minecraft.block.state.IBlockState"), Class.forName("net.minecraft.world.IBlockAccess"), BlockPos.class, facing}, state, world, pos, up); + } + static Object call(Object object, String[] names, Class[] types, Object... args) throws Exception { + for(String name:names) { + try {Method method=object.getClass().getMethod(name,types); method.setAccessible(true); return method.invoke(object,args);} + catch(NoSuchMethodException ignored) {} + } + throw new NoSuchMethodException(object.getClass()+" "+Arrays.toString(names)); + } + static int integerField(Object object,String...names) throws Exception { + for(String name:names) { + try {return object.getClass().getField(name).getInt(object);} + catch(NoSuchFieldException ignored) {} + } + throw new NoSuchFieldException(Arrays.toString(names)); + } +} diff --git a/tools/validation/legacy110/orespawn-probe/review/worldgen/OreSpawn110Probe.java b/tools/validation/legacy110/orespawn-probe/review/worldgen/OreSpawn110Probe.java new file mode 100644 index 000000000..d3a79aa4d --- /dev/null +++ b/tools/validation/legacy110/orespawn-probe/review/worldgen/OreSpawn110Probe.java @@ -0,0 +1,194 @@ +package review.worldgen; + +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import java.lang.reflect.Field; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Map; +import java.util.LinkedHashMap; +import net.minecraft.block.Block; +import net.minecraft.block.state.IBlockState; +import net.minecraft.server.MinecraftServer; +import net.minecraft.item.ItemStack; +import net.minecraft.tileentity.TileEntityChest; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.WorldServer; +import net.minecraftforge.fml.common.FMLCommonHandler; +import net.minecraftforge.fml.common.Loader; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.event.FMLInitializationEvent; +import net.minecraftforge.fml.common.event.FMLServerStartedEvent; +import net.minecraftforge.fml.common.registry.GameRegistry; +import net.minecraftforge.common.DimensionManager; +import zone.moddev.mc.orespawn.api.OreSpawnApi; +import zone.moddev.mc.orespawn.api.ProviderStatus; + +/** Checks the effective OreSpawn profile, not just successful mod loading. */ +@Mod(modid = "basemetalsworldgenprobe", name = "Base Metals worldgen checks", version = "1", + serverSideOnly = true, acceptableRemoteVersions = "*", dependencies = "required-after:basemetals") +public final class OreSpawn110Probe { + @Mod.EventHandler + public void init(final FMLInitializationEvent event) { + if (System.getProperty("worldgen.vanillaManaged") != null) VanillaTakeoverChecks.register(); + } + + @Mod.EventHandler + public void started(final FMLServerStartedEvent event) throws Exception { + final MinecraftServer server = FMLCommonHandler.instance().getMinecraftServerInstance(); + final JsonObject report = new JsonObject(); + final String phase = System.getProperty("worldgen.phase", "first"); + report.addProperty("phase", phase); + + try { + require("4.1.2.110021".equals(Loader.instance().getIndexedModList().get("orespawn").getVersion()), + "Wrong OreSpawn release"); + for (final String retired : new String[] { + "com.mcmoddev.orespawn.BaseMetalsOreSpawn", + "com.mcmoddev.lib.oregen.FallbackGenerator", + "com.mcmoddev.lib.oregen.FallbackGeneratorData" }) { + try { + Class.forName(retired); + throw new AssertionError("Retired generation code is still packaged: " + retired); + } catch (ClassNotFoundException expected) { + // These classes must no longer be shipped by Base Metals. + } + } + + require(OreSpawnApi.getProviderStatus("basemetals") == ProviderStatus.ACTIVE, + "Base Metals provider is not active"); + require(OreSpawnApi.isOreTakeoverActive("basemetals"), "OreSpawn is not managing Base Metals ores"); + final JsonObject profile = OreSpawnApi.getActiveProfile(server).get().toJson(); + report.add("profile", profile); + + final JsonObject expected = new JsonParser().parse(new String(Files.readAllBytes( + Paths.get(System.getProperty("worldgen.expected"))), StandardCharsets.UTF_8)).getAsJsonObject(); + assertSubset(expected, profile, "profile"); + final JsonObject ores = profile.getAsJsonObject("ores"); + int owned = 0; + for (final Map.Entry ore : ores.entrySet()) { + if (ore.getKey().startsWith("basemetals:")) owned++; + else require(!ore.getValue().toString().contains("basemetals:"), + "Base Metals ore was registered twice: " + ore.getKey()); + } + require(owned == expected.getAsJsonObject("ores").entrySet().size(), "Unexpected Base Metals rule count"); + report.addProperty("baseMetalsRules", owned); + + final Field generators = GameRegistry.class.getDeclaredField("worldGenerators"); + generators.setAccessible(true); + int oreSpawnGenerators = 0; + for (final Object generator : (Iterable) generators.get(null)) { + final String name = generator.getClass().getName(); + require(!name.contains("FallbackGenerator"), "A fallback generator is registered"); + if (name.equals("zone.moddev.mc.orespawn.worldgen.OreSpawnWorldGenerator")) oreSpawnGenerators++; + } + require(oreSpawnGenerators == 1, "Expected exactly one OreSpawn generator"); + if (System.getProperty("worldgen.vanillaManaged") != null) { + VanillaTakeoverChecks.check(server.worldServerForDimension(0), profile, report); + } + if (Boolean.getBoolean("worldgen.sample")) sampleOres(server, ores, report); + if (Boolean.getBoolean("worldgen.savedMarker")) { + checkSavedMarker(server.worldServerForDimension(0), new BlockPos(8, 80, 8)); + if (Boolean.getBoolean("worldgen.openRemote")) { + checkSavedMarker(server.worldServerForDimension(0), new BlockPos(1288, 80, -1272)); + } + } + report.addProperty("passed", true); + System.out.println("BASE_METALS_OS41_PASS " + phase + " rules=" + owned); + server.saveAllWorlds(false); + } catch (final Throwable failure) { + report.addProperty("passed", false); + report.addProperty("failure", failure.toString()); + throw failure; + } finally { + Files.write(Paths.get(phase + "-worldgen.json"), + (new GsonBuilder().setPrettyPrinting().create().toJson(report) + "\n").getBytes(StandardCharsets.UTF_8)); + server.initiateShutdown(); + } + } + + private static void sampleOres(final MinecraftServer server, final JsonObject ores, final JsonObject report) { + final JsonObject samples = new JsonObject(); + for (final int dimension : new int[] { 0, -1, 1 }) { + if (DimensionManager.getWorld(dimension) == null) DimensionManager.initDimension(dimension); + final WorldServer world = server.worldServerForDimension(dimension); + final String name = dimension == 0 ? "minecraft:overworld" + : dimension == -1 ? "minecraft:the_nether" : "minecraft:the_end"; + final Map counts = new LinkedHashMap<>(); + for (int x = -3; x <= 3; x++) { + for (int z = -3; z <= 3; z++) { + world.getChunkFromChunkCoords(x, z); + } + } + + final BlockPos.MutableBlockPos position = new BlockPos.MutableBlockPos(); + for (int cx = -2; cx <= 2; cx++) { + for (int cz = -2; cz <= 2; cz++) { + final net.minecraft.world.chunk.Chunk chunk = world.getChunkFromChunkCoords(cx, cz); + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + for (int y = 0; y < 256; y++) { + position.setPos(cx * 16 + x, y, cz * 16 + z); + final IBlockState state = chunk.getBlockState(position); + final Block block = state.getBlock(); + if (!"basemetals".equals(block.getRegistryName().getResourceDomain()) + || !block.getRegistryName().getResourcePath().endsWith("_ore")) continue; + + final String path = block.getRegistryName().getResourcePath(); + final JsonObject ore = ores.getAsJsonObject("basemetals:legacy/" + path); + require(ore != null, "An undeclared ore generated: " + path); + final JsonObject dimensions = ore.getAsJsonObject("dimensions"); + final JsonObject selectors = ore.getAsJsonObject("dimension_selectors"); + final JsonObject placement = dimensions != null && dimensions.has(name) + ? dimensions.getAsJsonObject(name) + : dimension == 0 && selectors != null + ? selectors.getAsJsonObject("orespawn:all_except_nether_end") : null; + require(placement != null && y >= placement.get("min_y").getAsInt() + && y <= placement.get("max_y").getAsInt(), + "Ore generated outside its dimension or height: " + path); + counts.put(path, counts.getOrDefault(path, 0) + 1); + } + } + } + } + } + require(!counts.isEmpty(), "No Base Metals ores generated in " + name); + final JsonObject sample = new JsonObject(); + counts.forEach(sample::addProperty); + samples.add(name, sample); + } + report.add("oreSamples", samples); + } + + private static void checkSavedMarker(final WorldServer world, final BlockPos position) { + require("basemetals:copper_block".equals(world.getBlockState(position).getBlock().getRegistryName().toString()), + "An existing copper block changed"); + final TileEntityChest chest = (TileEntityChest) world.getTileEntity(position.up()); + final ItemStack stack = chest.getStackInSlot(0); + require(stack != null && stack.stackSize == 3 + && "basemetals:copper_ingot".equals(stack.getItem().getRegistryName().toString()) + && "FG7 saved copper".equals(stack.getDisplayName()) + && "kept across reload".equals(stack.getTagCompound() + .getCompoundTag("BaseMetalsBuildCheck").getString("Check")), "Saved item data changed"); + world.getChunkFromBlockCoords(position).setChunkModified(); + } + + private static void assertSubset(final JsonElement expected, final JsonElement actual, final String path) { + require(actual != null, "Missing " + path); + if (expected.isJsonObject()) { + require(actual.isJsonObject(), "Not an object: " + path); + for (final Map.Entry entry : expected.getAsJsonObject().entrySet()) { + assertSubset(entry.getValue(), actual.getAsJsonObject().get(entry.getKey()), path + "/" + entry.getKey()); + } + } else { + require(expected.equals(actual), "Changed " + path + ": expected " + expected + ", got " + actual); + } + } + + private static void require(final boolean condition, final String message) { + if (!condition) throw new AssertionError(message); + } +} diff --git a/tools/validation/legacy110/orespawn-probe/review/worldgen/VanillaTakeoverChecks.java b/tools/validation/legacy110/orespawn-probe/review/worldgen/VanillaTakeoverChecks.java new file mode 100644 index 000000000..8425ff5ed --- /dev/null +++ b/tools/validation/legacy110/orespawn-probe/review/worldgen/VanillaTakeoverChecks.java @@ -0,0 +1,107 @@ +package review.worldgen; + +import com.google.gson.JsonObject; +import net.minecraft.block.Block; +import net.minecraft.init.Blocks; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.WorldServer; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.terraingen.OreGenEvent; +import net.minecraftforge.fml.common.eventhandler.Event; +import net.minecraftforge.fml.common.eventhandler.EventPriority; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + +/** Checks imported vanilla rules alongside Base Metals in a packaged server. */ +public final class VanillaTakeoverChecks { + private static final VanillaTakeoverChecks INSTANCE = new VanillaTakeoverChecks(); + private static int diamondEvents; + private static int deniedDiamondEvents; + + private VanillaTakeoverChecks() { + } + + static void register() { + MinecraftForge.ORE_GEN_BUS.register(INSTANCE); + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void observe(final OreGenEvent.GenerateMinable event) { + if (event.getType() == OreGenEvent.GenerateMinable.EventType.DIAMOND) { + diamondEvents++; + if (event.getResult() == Event.Result.DENY) deniedDiamondEvents++; + } + } + + static void check(final WorldServer world, final JsonObject profile, final JsonObject report) { + final boolean managed = Boolean.parseBoolean(System.getProperty("worldgen.vanillaManaged")); + require(profile.get("manage_vanilla_ores").getAsBoolean() == managed, "Wrong vanilla-management setting"); + + final JsonObject imported = profile.getAsJsonObject("ores").getAsJsonObject("orespawn:legacy/diamond_ore"); + require(imported != null, "The saved imported diamond rule disappeared"); + final JsonObject placement = imported.getAsJsonObject("dimension_selectors") + .getAsJsonObject("orespawn:all_except_nether_end"); + require(placement.get("frequency").getAsDouble() == 0.375D, "Custom diamond frequency changed"); + require(placement.get("min_y").getAsInt() == 7 && placement.get("max_y").getAsInt() == 11, + "Custom diamond height changed"); + require(placement.get("quantity").getAsInt() == 4, "Custom diamond vein size changed"); + require(!imported.get("retrogen").getAsBoolean(), "The imported retrogen setting changed"); + + if (Boolean.getBoolean("worldgen.openVanillaOld")) { + for (int x = 64; x <= 70; x++) { + for (int z = 64; z <= 70; z++) world.getChunkFromChunkCoords(x, z); + } + report.addProperty("oldVanillaChunksOpened", 49); + } + + diamondEvents = 0; + deniedDiamondEvents = 0; + final int base = Integer.getInteger("worldgen.vanillaSampleBase", 64); + for (int x = base - 1; x <= base + 7; x++) { + for (int z = base - 1; z <= base + 7; z++) world.getChunkFromChunkCoords(x, z); + } + + int diamonds = 0; + int outsideCustomHeight = 0; + int baseMetalsOres = 0; + final BlockPos.MutableBlockPos position = new BlockPos.MutableBlockPos(); + for (int cx = base; cx < base + 7; cx++) { + for (int cz = base; cz < base + 7; cz++) { + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + for (int y = 0; y < 32; y++) { + position.setPos((cx << 4) + x, y, (cz << 4) + z); + final Block block = world.getBlockState(position).getBlock(); + if (block == Blocks.DIAMOND_ORE) { + diamonds++; + if (y < 7 || y > 11) outsideCustomHeight++; + } + if (block.getRegistryName() != null + && "basemetals".equals(block.getRegistryName().getResourceDomain()) + && block.getRegistryName().getResourcePath().endsWith("_ore")) baseMetalsOres++; + } + } + } + } + } + + report.addProperty("vanillaManaged", managed); + report.addProperty("diamondEvents", diamondEvents); + report.addProperty("deniedDiamondEvents", deniedDiamondEvents); + report.addProperty("diamonds", diamonds); + report.addProperty("diamondsOutsideCustomHeight", outsideCustomHeight); + report.addProperty("baseMetalsOres", baseMetalsOres); + + if (!Boolean.getBoolean("worldgen.vanillaReload")) { + require(diamondEvents > 0, "No new-chunk diamond generation events were observed"); + require(deniedDiamondEvents == (managed ? diamondEvents : 0), "The vanilla takeover switch was not respected"); + } + require(diamonds > 0, "No diamond ore was placed"); + require(managed ? outsideCustomHeight == 0 : outsideCustomHeight > 0, + "The wrong diamond placement settings were used"); + require(baseMetalsOres > 0, "Vanilla management disabled Base Metals ore generation"); + } + + private static void require(final boolean condition, final String message) { + if (!condition) throw new AssertionError(message); + } +} diff --git a/tools/validation/legacy110/run_ci_checks.py b/tools/validation/legacy110/run_ci_checks.py new file mode 100644 index 000000000..2dbd21b39 --- /dev/null +++ b/tools/validation/legacy110/run_ci_checks.py @@ -0,0 +1,115 @@ +"""Build disposable upgrade worlds from public releases, then test the packaged candidate.""" + +import argparse +import hashlib +import subprocess +import sys +import time +import urllib.error +import urllib.request +from pathlib import Path + +from run_orespawn_checks import digest, prepare, run + + +ROOT = Path(__file__).resolve().parents[3] +FIXTURES = Path(__file__).with_name("fixtures") +RELEASES = { + "cyano": ("base-metals-240967", "2323100", "F919CEF794C73F02758BBD967346DBA0B53F6383153C7E29BD79F211CDA8C319"), + "mmd": ("base-metals-240967", "2533174", "51F04770C316E62D9ED3EF713144F02FA2EC899645813627091365E12F61C3EF"), + "mmdlib": ("mmdlib-261744", "2400405", "286149F4E8132535135C27285A750916D35AB3ECDDB28AE4475FC604D1FDF178"), + "os3": ("mmd-orespawn-245586", "2533153", "D964148C20175E16CCEFAB02AE43A181A50DC08A1D636A77E59CFE8C8EA8D41A"), + "os1": ("mmd-orespawn-245586", "2323091", "91345E4B825AA06E05F470066F467AABA0C7A2804A8DBCA4D3CD1905ED53607C"), + "orespawn": ("mmd-orespawn-245586", "9276430", "4D4975421FEEBF17505056306D06B0344D7A4553B6275885D7C5C5065B0348A6"), +} + + +def download(directory, name): + artifact, version, expected = RELEASES[name] + target = directory / f"{artifact}-{version}.jar" + if not target.exists(): + url = f"https://www.cursemaven.com/curse/maven/{artifact}/{version}/{target.name}" + request = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"}) + for attempt in range(3): + try: + with urllib.request.urlopen(request, timeout=30) as response: + data = response.read() + break + except (OSError, urllib.error.URLError): + if attempt == 2: + raise + print(f"Retrying public fixture download: {name}", flush=True) + time.sleep(2) + if hashlib.sha256(data).hexdigest().upper() != expected: + raise AssertionError(f"Public fixture checksum mismatch: {name}") + target.write_bytes(data) + if digest(target).upper() != expected: + raise AssertionError(f"Fixture checksum mismatch: {target}") + print("Verified public fixture:", name, expected, flush=True) + return target + + +def capture(java, directory): + log = directory / "capture-console.txt" + with log.open("wb") as output: + result = subprocess.run([str(java), "-Xms256M", "-Xmx2G", "-jar", + "forge-1.10.2-12.18.3.2511-universal.jar", "nogui"], + cwd=directory, stdout=output, stderr=subprocess.STDOUT, timeout=360) + manifest = directory / "legacy_registry_manifest_runtime.json" + if result.returncode or not manifest.is_file() or "BASEMETALS_LEGACY_CAPTURE" not in log.read_text(errors="replace"): + raise AssertionError(f"Legacy fixture capture failed: {log}") + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + for name in ("java", "template", "output"): + parser.add_argument("--" + name, type=Path, required=True) + args = parser.parse_args() + args.output = args.output.resolve() + args.output.mkdir(parents=True, exist_ok=False) + downloads = args.output / "downloads" + downloads.mkdir() + jars = {name: download(downloads, name) for name in RELEASES} + mod = ROOT / "build/libs/BaseMetals-2.5.1.110021.jar" + probes = ROOT / "build/test-mods" + + def script(name, *arguments): + subprocess.run([sys.executable, str(Path(__file__).with_name(name)), + "--java", str(args.java), "--template", str(args.template), + "--mod", str(mod), "--orespawn", str(jars["orespawn"]), + "--probe", str(probes / "orespawn-probe-packaged.jar"), *map(str, arguments)], check=True) + + script("run_orespawn_checks.py", "--output", args.output / "worldgen", + "--os3", FIXTURES / "orespawn3-basemetals.json", "--os1", FIXTURES / "orespawn1-basemetals.json", + "--old-mod", jars["mmd"], "--old-orespawn", jars["os3"], + "--old-support-mod", jars["mmdlib"], "--old-version", "2.5.0-beta4", + "--launch-probe", probes / "launch-probe-packaged.jar") + script("run_vanilla_takeover_checks.py", "--output", args.output / "takeover") + + for family in ("cyano", "mmd"): + source = args.output / (family + "-source") + support = [jars["mmdlib"], jars["os3"]] if family == "mmd" else [jars["os1"]] + prepare(source, args.template, [jars[family], *support, probes / "legacy-capture-packaged.jar"]) + # Villages can otherwise populate over the remote block-state test grid. + properties = source / "server.properties" + properties.write_text(properties.read_text(encoding="utf-8") + "generate-structures=false\n", encoding="utf-8") + capture(args.java, source) + capture(args.java, source) + before = {p.relative_to(source / "world"): digest(p) for p in (source / "world").rglob("*") if p.is_file()} + target = args.output / (family + "-upgraded") + prepare(target, args.template, [mod, jars["orespawn"], probes / "legacy110-probe.jar"], source / "world") + manifest = source / "world/legacy_registry_manifest_runtime.json" + later = target / "world/region/r.-2.2.mca" + unopened = digest(later) + for phase in ("first", "reload", "delayed", "stable"): + run(args.java, target, phase, {"review.phase": phase, "review.manifest": manifest}, report_kind="review") + if phase in ("first", "reload") and digest(later) != unopened: + raise AssertionError("An unopened fixture region changed during upgrade") + after = {p.relative_to(source / "world"): digest(p) for p in (source / "world").rglob("*") if p.is_file()} + if before != after: + raise AssertionError("The source fixture was modified") + print("BASE_METALS_CI_RUNTIME_PASS", flush=True) + + +if __name__ == "__main__": + main() diff --git a/tools/validation/legacy110/run_eclipse_profile.py b/tools/validation/legacy110/run_eclipse_profile.py new file mode 100644 index 000000000..802c5113f --- /dev/null +++ b/tools/validation/legacy110/run_eclipse_profile.py @@ -0,0 +1,66 @@ +"""Run a generated Eclipse profile in an isolated directory, with a test-only probe.""" + +import argparse +import json +import os +import shlex +import shutil +import subprocess +import time +import xml.etree.ElementTree as ET +from pathlib import Path + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("profile", type=Path) + parser.add_argument("java", type=Path) + parser.add_argument("directory", type=Path) + parser.add_argument("phase", choices=["fresh", "reload"]) + parser.add_argument("--probe", type=Path, required=True) + parser.add_argument("--production-output", type=Path, + help="Use a disposable Eclipse build without touching the editor's bin directory") + args = parser.parse_args() + profile = args.profile.resolve() + root = ET.parse(profile).getroot() + strings = {node.get("key"): node.get("value") for node in root.findall("stringAttribute")} + environment = os.environ.copy() + for node in root.findall("mapAttribute/mapEntry"): + environment[node.get("key")] = node.get("value") + production_output = (args.production_output or profile.parent / "bin/main").resolve() + environment["MOD_CLASSES"] = str(production_output) + + archives = [] + for node in root.findall("listAttribute[@key='org.eclipse.jdt.launching.CLASSPATH']/listEntry"): + entry = ET.fromstring(node.get("value")) + path = entry.get("externalArchive") + if path: + archives.append(path) + elif entry.get("projectName"): + archives.append(str(production_output)) + + directory = args.directory.resolve() + (directory / "mods").mkdir(parents=True, exist_ok=True) + shutil.copy2(args.probe, directory / "mods" / args.probe.name) + (directory / "eula.txt").write_text("eula=true\n", encoding="utf-8") + if not (directory / "server.properties").exists(): + (directory / "server.properties").write_text("server-ip=127.0.0.1\nserver-port=0\nonline-mode=false\nlevel-type=FLAT\nview-distance=3\n", encoding="utf-8") + + command = [str(args.java), "-Xmx3G", "-Dlaunch.phase=" + args.phase] + command += shlex.split(strings.get("org.eclipse.jdt.launching.VM_ARGUMENTS", "")) + command += ["-cp", os.pathsep.join(dict.fromkeys(archives)), strings["org.eclipse.jdt.launching.MAIN_TYPE"]] + command += shlex.split(strings["org.eclipse.jdt.launching.PROGRAM_ARGUMENTS"]) + started = time.time_ns() + with (directory / (args.phase + "-console.log")).open("w", encoding="utf-8") as log: + process = subprocess.run(command, cwd=directory, env=environment, stdout=log, + stderr=subprocess.STDOUT, timeout=300) + + report = directory / (args.phase + "-launch.json") + if (process.returncode or not report.exists() or report.stat().st_mtime_ns < started + or not json.loads(report.read_text())["passed"]): + raise SystemExit(f"Launch failed; inspect {directory / (args.phase + '-console.log')}") + print(report.read_text(), flush=True) + + +if __name__ == "__main__": + main() diff --git a/tools/validation/legacy110/run_orespawn_checks.py b/tools/validation/legacy110/run_orespawn_checks.py new file mode 100644 index 000000000..e7c0564a2 --- /dev/null +++ b/tools/validation/legacy110/run_orespawn_checks.py @@ -0,0 +1,258 @@ +"""Exercise OreSpawn upgrades on disposable packaged Forge servers.""" + +import argparse +import copy +import hashlib +import json +import shutil +import subprocess +import time +from pathlib import Path + + +DIMENSIONS = {-1: "minecraft:the_nether", 0: "minecraft:overworld", 1: "minecraft:the_end"} +JAR_NAME = "forge-1.10.2-12.18.3.2511-universal.jar" + + +def write_json(path, value): + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(value, indent=2) + "\n", encoding="utf-8") + + +def digest(path): + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def prepare(directory, template, mods, world=None, flat=True): + directory.mkdir(parents=True, exist_ok=False) + for name in (JAR_NAME, "minecraft_server.1.10.2.jar"): + shutil.copy2(template / name, directory / name) + shutil.copytree(template / "libraries", directory / "libraries") + (directory / "mods").mkdir() + for mod in mods: + shutil.copy2(mod, directory / "mods" / mod.name) + (directory / "eula.txt").write_text("eula=true\n", encoding="utf-8") + (directory / "server.properties").write_text( + "level-name=world\nserver-port=0\nonline-mode=false\nlevel-seed=8675309\n" + "view-distance=2\nmax-tick-time=-1\nspawn-protection=0\n" + + ("level-type=FLAT\n" if flat else "level-type=DEFAULT\n"), encoding="utf-8") + if world: + if directory.resolve().is_relative_to(world.resolve()): + raise ValueError("The disposable directory must not be inside its source world") + shutil.copytree(world, directory / "world") + + +def run(java, directory, phase, properties, report_kind="worldgen", expected_failure=False): + arguments = [str(java), "-Xms256M", "-Xmx2G"] + arguments += [f"-D{key}={value}" for key, value in properties.items()] + arguments += ["-jar", JAR_NAME, "nogui"] + started = time.time_ns() + log = directory / f"{phase}-console.txt" + with log.open("wb") as output: + result = subprocess.run(arguments, cwd=directory, stdout=output, + stderr=subprocess.STDOUT, timeout=240) + report_path = directory / f"{phase}-{report_kind}.json" + if not report_path.is_file() or report_path.stat().st_mtime_ns < started: + raise AssertionError(f"No fresh runtime report; read {log}") + report = json.loads(report_path.read_text(encoding="utf-8")) + if expected_failure: + if report.get("passed") or "Retired generation code" not in report.get("failure", ""): + raise AssertionError(f"Did not reproduce the old generator: {report_path}") + elif result.returncode or not report.get("passed"): + raise AssertionError(f"Runtime check failed: {report_path}; read {log}") + print(directory.name, phase, "expected failure" if expected_failure else "passed", flush=True) + return report + + +def reject_dependency(java, directory, template, mods): + prepare(directory, template, mods) + log = directory / "rejection-console.txt" + with log.open("wb") as output: + subprocess.run([str(java), "-Xms256M", "-Xmx2G", "-jar", JAR_NAME, "nogui"], + cwd=directory, stdout=output, stderr=subprocess.STDOUT, timeout=120) + + text = log.read_text(encoding="utf-8", errors="replace") + if ("MissingModsException" not in text or "orespawn" not in text + or "4.1.2.110021" not in text or (directory / "world/level.dat").exists()): + raise AssertionError(f"The unsupported dependency was not clearly rejected; read {log}") + print(directory.name, "rejected before world load", flush=True) + + +def expected_os3(source): + ores = {} + for name, spawn in source["spawns"].items(): + parameters = spawn["parameters"] + size = parameters["size"] + variation = parameters.get("variation", 0) + placement = {"enabled": spawn.get("enabled", True), "min_y": parameters["minHeight"], + "max_y": parameters["maxHeight"] - 1, "frequency": parameters["frequency"]} + if variation: + placement.update(min_quantity=max(1, size - variation), max_quantity=min(64, size + variation - 1)) + else: + placement["quantity"] = size + dimensions = spawn.get("dimensions", []) + if dimensions: + domain = {"dimensions": {DIMENSIONS[value]: placement for value in dimensions}} + else: + domain = {"dimension_selectors": {"orespawn:all_except_nether_end": placement}} + ores[f"basemetals:legacy/{name}"] = { + "block": spawn["blocks"][0]["name"], "enabled": spawn.get("enabled", True), + "retrogen": spawn.get("retrogen", False), **domain} + return {"ores": ores} + + +def expected_os1(source): + ores = {} + explicit = {int(value["dimension"]) for value in source["dimensions"] if value["dimension"] != "+"} + ordinal = 0 + for dimension in source["dimensions"]: + domains = sorted(set(DIMENSIONS) - explicit) if dimension["dimension"] == "+" else [int(dimension["dimension"])] + for ore in dimension["ores"]: + name = ore["blockID"].replace(":", "_") + "_" + str(ordinal) + ordinal += 1 + placement = {"enabled": True, "min_y": ore.get("minHeight", 0), + "max_y": ore.get("maxHeight", 256) - 1, "frequency": ore.get("frequency", 0.5)} + size, variation = ore.get("size", 8), ore.get("variation", 0) + if variation: + placement.update(min_quantity=max(1, size - variation), max_quantity=min(64, size + variation - 1)) + else: + placement["quantity"] = size + ores[f"basemetals:legacy/{name}"] = {"block": ore["blockID"], "enabled": True, + "dimensions": {DIMENSIONS[value]: placement for value in domains}} + return {"ores": ores} + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + for name in ("java", "template", "mod", "orespawn", "probe", "output", "os3"): + parser.add_argument("--" + name, type=Path, required=True) + parser.add_argument("--os1", type=Path) + parser.add_argument("--old-mod", type=Path) + parser.add_argument("--old-orespawn", type=Path) + parser.add_argument("--old-support-mod", type=Path, action="append", default=[]) + parser.add_argument("--old-version", default="2.5.1.110021") + parser.add_argument("--launch-probe", type=Path) + parser.add_argument("--red", action="store_true") + args = parser.parse_args() + args.output.mkdir(parents=True, exist_ok=True) + mods = [args.mod, args.orespawn, args.probe] + stock = json.loads(args.os3.read_text(encoding="utf-8")) + + if args.red: + directory = args.output / "red" + prepare(directory, args.template, mods) + write_json(directory / "expected.json", expected_os3(stock)) + run(args.java, directory, "first", {"worldgen.expected": directory / "expected.json"}, expected_failure=True) + return + + if not all((args.old_mod, args.old_orespawn, args.launch_probe)): + parser.error("Full upgrade checks need --old-mod, --old-orespawn and --launch-probe") + + reject_dependency(args.java, args.output / "missing-orespawn", args.template, [args.mod]) + reject_dependency(args.java, args.output / "old-orespawn-rejected", args.template, + [args.mod, args.old_orespawn]) + + mods.append(args.launch_probe) + control = args.output / "old-os3-control" + prepare(control, args.template, [args.old_mod, args.old_orespawn, args.launch_probe, *args.old_support_mod]) + write_json(control / "config/orespawn3/basemetals.json", stock) + run(args.java, control, "fresh", {"launch.phase": "fresh", "launch.remoteMarker": "true", + "launch.expectedVersion": args.old_version}, report_kind="launch") + + custom = copy.deepcopy(stock) + custom["spawns"]["tin_ore"]["parameters"].update(size=5, variation=0, frequency=2.75, minHeight=10, maxHeight=50) + custom["spawns"]["nickel_ore"]["dimensions"] = [0] + custom["spawns"]["silver_ore"]["enabled"] = False + custom["spawns"]["lead_ore"]["retrogen"] = True + disabled = copy.deepcopy(stock) + for spawn in disabled["spawns"].values(): + spawn["enabled"] = False + + scenarios = [("os3-stock", stock, "orespawn3", expected_os3(stock)), + ("os3-custom", custom, "orespawn3", expected_os3(custom)), + ("os3-disabled", disabled, "orespawn3", expected_os3(disabled))] + if args.os1: + source = json.loads(args.os1.read_text(encoding="utf-8")) + scenarios.append(("os1", source, "orespawn", expected_os1(source))) + + for name, source, legacy_directory, expected in scenarios: + directory = args.output / name + prepare(directory, args.template, mods, control / "world") + legacy = directory / "config" / legacy_directory / "basemetals.json" + legacy.parent.mkdir(parents=True, exist_ok=True) + if name == "os1": + shutil.copy2(args.os1, legacy) + elif name == "os3-stock": + shutil.copy2(control / "config/orespawn3/basemetals.json", legacy) + else: + write_json(legacy, source) + source_hash = digest(legacy) + write_json(directory / "expected.json", expected) + properties = {"worldgen.expected": directory / "expected.json", "worldgen.savedMarker": "true"} + remote = directory / "world/region/r.2.-3.mca" + remote_hash = digest(remote) + run(args.java, directory, "first", properties) + stable = [directory / "config/basemetals-orespawn.json", + directory / "config/orespawn-upgrade-report.txt", + directory / "world/serverconfig/orespawn-worldgen.json"] + hashes = {path: digest(path) for path in stable} + properties["worldgen.phase"] = "reload" + run(args.java, directory, "reload", properties) + if digest(remote) != remote_hash: + raise AssertionError(f"An unopened region changed during upgrade: {directory}") + if digest(legacy) != source_hash or any(digest(path) != value for path, value in hashes.items()): + raise AssertionError(f"Migration rewrote a source or was not stable on reload: {directory}") + properties["worldgen.openRemote"] = "true" + for phase in ("later", "stable"): + properties["worldgen.phase"] = phase + run(args.java, directory, phase, properties) + if digest(legacy) != source_hash or any(digest(path) != value for path, value in hashes.items()): + raise AssertionError(f"Migration changed after opening later chunks: {directory}") + write_json(directory / "migration-hashes.json", {str(path.relative_to(directory)): value for path, value in hashes.items()}) + + fresh = args.output / "native-fresh" + prepare(fresh, args.template, mods, flat=False) + write_json(fresh / "expected.json", expected_os3(stock)) + run(args.java, fresh, "first", {"worldgen.expected": fresh / "expected.json", "worldgen.sample": "true"}) + run(args.java, fresh, "reload", {"worldgen.phase": "reload", "worldgen.expected": fresh / "expected.json"}) + + existing = args.output / "existing-os4" + prepare(existing, args.template, mods, fresh / "world") + shutil.copytree(fresh / "config", existing / "config", dirs_exist_ok=True) + profile_path = existing / "world/serverconfig/orespawn-worldgen.json" + profile = json.loads(profile_path.read_text(encoding="utf-8")) + tin = "basemetals:legacy/tin_ore" + selector = "orespawn:all_except_nether_end" + profile["ores"][tin]["dimension_selectors"][selector]["frequency"] = 6.25 + write_json(profile_path, profile) + override = {"schema_version": 5, "provider_modid": "basemetals", "provider_revision": 1, + "ores": {key: copy.deepcopy(value) for key, value in profile["ores"].items() + if key.startswith("basemetals:")}} + override["ores"][tin]["dimension_selectors"][selector]["frequency"] = 9.0 + override_path = existing / "config/basemetals-orespawn.json" + write_json(override_path, override) + write_json(existing / "config/orespawn3/basemetals.json", custom) + expected = expected_os3(stock) + expected["ores"][tin]["dimension_selectors"][selector]["frequency"] = 6.25 + write_json(existing / "expected.json", expected) + hashes = {path: digest(path) for path in (profile_path, override_path)} + for phase in ("first", "reload"): + run(args.java, existing, phase, {"worldgen.phase": phase, "worldgen.expected": existing / "expected.json"}) + if any(digest(path) != value for path, value in hashes.items()): + raise AssertionError("An existing OS4 override or world profile was overwritten") + + future = args.output / "existing-os4-new-world" + prepare(future, args.template, mods) + write_json(future / "config/basemetals-orespawn.json", override) + write_json(future / "config/orespawn3/basemetals.json", custom) + write_json(future / "expected.json", {"ores": override["ores"]}) + override_hash = digest(future / "config/basemetals-orespawn.json") + for phase in ("first", "reload"): + run(args.java, future, phase, {"worldgen.phase": phase, "worldgen.expected": future / "expected.json"}) + if digest(future / "config/basemetals-orespawn.json") != override_hash: + raise AssertionError("The existing override changed when creating a new world") + print("BASE_METALS_OS41_MIGRATION_SUITE_PASS", flush=True) + + +if __name__ == "__main__": + main() diff --git a/tools/validation/legacy110/run_vanilla_takeover_checks.py b/tools/validation/legacy110/run_vanilla_takeover_checks.py new file mode 100644 index 000000000..d10212378 --- /dev/null +++ b/tools/validation/legacy110/run_vanilla_takeover_checks.py @@ -0,0 +1,101 @@ +"""Check the vanilla takeover switch with Base Metals and an imported OS3 rule.""" + +import argparse +import copy +import io +import json +import shutil +from pathlib import Path + +import nbtlib + +from audit_world import fingerprint, plain, raw_chunk +from run_orespawn_checks import digest, expected_os3, prepare, run, write_json + + +def terrain(world): + result = {} + for x in range(64, 71): + for z in range(64, 71): + path = world / "region" / f"r.{x >> 5}.{z >> 5}.mca" + raw = raw_chunk(path, (x & 31) + (z & 31) * 32) + if raw is None: + raise AssertionError(f"Missing saved chunk: {x},{z}") + level = nbtlib.File.parse(io.BytesIO(raw))["Level"] + if not level["TerrainPopulated"]: + raise AssertionError(f"The terrain fixture is not fully generated: {x},{z}") + result[f"{x},{z}"] = { + "sections": [{key: plain(section[key]) for key in ("Y", "Blocks", "Data", "Add") if key in section} + for section in level["Sections"]], + "biomes": plain(level["Biomes"])} + return result + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + for name in ("java", "template", "mod", "orespawn", "probe", "output"): + parser.add_argument("--" + name, type=Path, required=True) + args = parser.parse_args() + stock = json.loads((Path(__file__).parent / "fixtures/orespawn3-basemetals.json").read_text(encoding="utf-8")) + mods = [args.mod, args.orespawn, args.probe] + source = {"version": "2.0", "spawns": {"diamond_ore": { + "enabled": True, "retrogen": False, "feature": "default", "replaces": "default", + "dimensions": [], "biomes": {"excludes": []}, + "parameters": {"frequency": 0.375, "size": 4, "variation": 0, "minHeight": 7, "maxHeight": 12}, + "blocks": [{"name": "minecraft:diamond_ore", "chance": 100}]}}} + + initial = args.output / "management-off" + prepare(initial, args.template, mods, flat=False) + write_json(initial / "config/orespawn3/orespawn.json", source) + (initial / "config/orespawn.cfg").write_text( + 'general {\n B:"Replace Vanilla Oregen"=false\n B:Retrogen=false\n B:"Force Retrogen"=false\n}\n', + encoding="utf-8") + write_json(initial / "expected.json", expected_os3(stock)) + properties = {"worldgen.expected": initial / "expected.json", "worldgen.vanillaManaged": "false"} + first = run(args.java, initial, "first", properties) + original_rule = copy.deepcopy(first["profile"]["ores"]["orespawn:legacy/diamond_ore"]) + original_terrain = terrain(initial / "world") + properties.update({"worldgen.phase": "reload", "worldgen.vanillaReload": "true"}) + run(args.java, initial, "reload", properties) + if terrain(initial / "world") != original_terrain: + raise AssertionError("Reloading changed the original terrain") + + source_fingerprint = fingerprint(initial / "world") + switched = args.output / "management-toggled" + prepare(switched, args.template, mods, initial / "world", flat=False) + shutil.copytree(initial / "config", switched / "config", dirs_exist_ok=True) + write_json(switched / "expected.json", expected_os3(stock)) + legacy = switched / "config/orespawn3/orespawn.json" + legacy_hash = digest(legacy) + profile_path = switched / "world/serverconfig/orespawn-worldgen.json" + results = [] + + for phase, managed, base, reload_only in ( + ("on", True, 80, False), ("on-reload", True, 80, True), + ("on-later", True, 96, False), ("off-again", False, 128, False), + ("off-reload", False, 128, True)): + profile = json.loads(profile_path.read_text(encoding="utf-8")) + profile["manage_vanilla_ores"] = managed + write_json(profile_path, profile) + report = run(args.java, switched, phase, { + "worldgen.phase": phase, "worldgen.expected": switched / "expected.json", + "worldgen.vanillaManaged": str(managed).lower(), "worldgen.vanillaSampleBase": base, + "worldgen.vanillaReload": str(reload_only).lower(), "worldgen.openVanillaOld": "true"}) + if report["profile"]["ores"]["orespawn:legacy/diamond_ore"] != original_rule: + raise AssertionError("Changing vanilla management rewrote the imported rule") + if terrain(switched / "world") != original_terrain: + raise AssertionError("Opening old chunks or switching management changed existing terrain") + if digest(legacy) != legacy_hash: + raise AssertionError("The original OS3 configuration changed") + results.append({key: report[key] for key in ("phase", "vanillaManaged", "diamonds", + "diamondsOutsideCustomHeight", "diamondEvents", "deniedDiamondEvents", "baseMetalsOres")}) + + if fingerprint(initial / "world") != source_fingerprint: + raise AssertionError("The source fixture changed") + write_json(args.output / "takeover-results.json", { + "passed": True, "protectedChunks": 49, "sourceUnchanged": True, "phases": results}) + print("BASE_METALS_VANILLA_TAKEOVER_PASS", flush=True) + + +if __name__ == "__main__": + main()