diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..ebd6108 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +* text=auto eol=lf +*.png binary +*.jar binary +*.zip binary diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..4fb57de --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,252 @@ +name: Base Minerals 1.11 CI + +on: + push: + branches: + - master-1.11.2 + - 'feature/**' + pull_request: + branches: + - master-1.11.2 + +permissions: + contents: read + +concurrency: + group: baseminerals-1.11-${{ 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 }}/baseminerals-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 + -Dorg.gradle.java.installations.paths="$JAVA_HOME,$JAVA_HOME_8_X64,$JAVA_HOME_25_X64" + -Dorg.gradle.java.installations.auto-detect=false + -Dorg.gradle.java.installations.auto-download=false + ) + ./gradlew "${gradle_args[@]}" + + - name: Verify same-cache bootstrap offline + shell: bash + env: + GRADLE_USER_HOME: ${{ runner.temp }}/baseminerals-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 + -Dorg.gradle.java.installations.paths="$JAVA_HOME,$JAVA_HOME_8_X64,$JAVA_HOME_25_X64" + -Dorg.gradle.java.installations.auto-detect=false + -Dorg.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.11.2-2588-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 pinned schema and workflow validators + run: python -m pip install -r tools/validation/requirements.txt + + - 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 verifyProjectResources packagedForgeRuntimeTest packagedSourcePolicyTest + -PpythonExecutable=python3 + -PpackagedForgeRuntimeOutput="$RUNNER_TEMP/baseminerals-packaged-checks" + -PpackagedSourcePolicyOutput="$RUNNER_TEMP/baseminerals-policy-checks" + -PreleaseBuild=true + -PlegacyDependencyVerificationRepository="${{ steps.dependencies.outputs.repository }}" + -PpackagedForgeRuntimeRoot="${{ steps.forge.outputs.runtime }}" + --no-daemon --stacktrace --max-workers=2 + -Dorg.gradle.java.installations.paths="$JAVA_HOME,$JAVA_HOME_8_X64,$JAVA_HOME_25_X64" + -Dorg.gradle.java.installations.auto-detect=false + -Dorg.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/BaseMinerals-2.5.1.111021{,-sources,-javadoc}.jar "$bundle/" + cp build/release/SHA256SUMS CHANGELOG.txt "$bundle/" + ./gradlew verifyPreparedReleaseArtifacts verifyMavenCoordinates \ + -PpreparedReleaseDir="$bundle" \ + -PlegacyDependencyVerificationRepository="${{ steps.dependencies.outputs.repository }}" \ + -Dorg.gradle.java.installations.paths="$JAVA_HOME,$JAVA_HOME_8_X64,$JAVA_HOME_25_X64" \ + -Dorg.gradle.java.installations.auto-detect=false \ + -Dorg.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 + -Dorg.gradle.java.installations.paths="$JAVA_HOME,$JAVA_HOME_8_X64,$JAVA_HOME_25_X64" + -Dorg.gradle.java.installations.auto-detect=false + -Dorg.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: BaseMinerals-1.11.2-${{ github.sha }} + if-no-files-found: error + retention-days: 30 + path: | + build/libs/BaseMinerals-2.5.1.111021.jar + build/libs/BaseMinerals-2.5.1.111021-sources.jar + build/libs/BaseMinerals-2.5.1.111021-javadoc.jar + build/release/SHA256SUMS + CHANGELOG.txt + + - name: Upload bounded diagnostics on failure + if: failure() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: BaseMinerals-1.11.2-diagnostics-${{ github.sha }} + if-no-files-found: ignore + retention-days: 14 + path: | + build/test-results/** + build/reports/** + ${{ runner.temp }}/baseminerals-packaged-checks/**/*-console.log + ${{ runner.temp }}/baseminerals-packaged-checks/**/logs/*.log + ${{ runner.temp }}/baseminerals-packaged-checks/**/crash-reports/*.txt + ${{ runner.temp }}/baseminerals-packaged-checks/**/*-minerals.json + ${{ runner.temp }}/baseminerals-packaged-checks/**/*-review.json + ${{ runner.temp }}/baseminerals-packaged-checks/**/*-launch.json + ${{ runner.temp }}/baseminerals-policy-checks/**/*-console.log + ${{ runner.temp }}/baseminerals-policy-checks/**/*-minerals.json + ${{ runner.temp }}/baseminerals-policy-checks/**/crash-reports/*.txt + build/problems/** + + - name: Upload generation qualification evidence + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: BaseMinerals-1.11.2-worldgen-${{ github.sha }} + if-no-files-found: ignore + retention-days: 30 + path: | + ${{ runner.temp }}/baseminerals-packaged-checks/results.json + ${{ runner.temp }}/baseminerals-packaged-checks/timings.json + ${{ runner.temp }}/baseminerals-packaged-checks/source-fixtures.json + ${{ runner.temp }}/baseminerals-packaged-checks/**/input-jars.json + ${{ runner.temp }}/baseminerals-packaged-checks/**/*-minerals.json + ${{ runner.temp }}/baseminerals-packaged-checks/**/*-console.log + ${{ runner.temp }}/baseminerals-policy-checks/results.json + ${{ runner.temp }}/baseminerals-policy-checks/**/*-minerals.json + ${{ runner.temp }}/baseminerals-policy-checks/**/*-console.log diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..fec3ab4 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,80 @@ +name: CodeQL + +on: + push: + branches: + - master-1.11.2 + - 'feature/**' + pull_request: + branches: + - master-1.11.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 + -Dorg.gradle.java.installations.paths="$JAVA_HOME,$JAVA_HOME_8_X64,$JAVA_HOME_25_X64" + -Dorg.gradle.java.installations.auto-detect=false + -Dorg.gradle.java.installations.auto-download=false + ) + ./gradlew "${gradle_args[@]}" + + - name: Analyze + uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4 diff --git a/.github/workflows/deploy-release.yml b/.github/workflows/deploy-release.yml new file mode 100644 index 0000000..66922bd --- /dev/null +++ b/.github/workflows/deploy-release.yml @@ -0,0 +1,147 @@ +name: Release Base Minerals to CurseForge + +run-name: Base Minerals ${{ inputs.release_version }} CurseForge (${{ inputs.confirm_live_publication && 'publish' || 'check only' }}) + +on: + workflow_dispatch: + inputs: + release_version: + description: Base Minerals version ending in .110021 or .111021 + required: true + type: string + release_sha: + description: Full merged commit SHA on the matching 1.10.2 or 1.11.2 branch + required: true + type: string + ci_run_id: + description: Successful upstream push CI run for the matching branch + required: true + type: string + player_sha256: + description: SHA-256 of the tested player jar + required: true + type: string + confirm_live_publication: + description: Upload the verified player jar to CurseForge + default: false + type: boolean + +permissions: + actions: read + contents: read + +concurrency: + group: baseminerals-curseforge-${{ inputs.release_version }} + cancel-in-progress: false + +jobs: + release: + name: Verify and release Base Minerals + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Verify the merged source and CI run + id: target + env: + GH_TOKEN: ${{ github.token }} + RELEASE_VERSION: ${{ inputs.release_version }} + RELEASE_SHA: ${{ inputs.release_sha }} + CI_RUN_ID: ${{ inputs.ci_run_id }} + PLAYER_SHA256: ${{ inputs.player_sha256 }} + shell: bash + run: | + set -euo pipefail + repo=MinecraftModDevelopmentMods/BaseMinerals + [[ "$GITHUB_REPOSITORY" == "$repo" && "$GITHUB_REF" == refs/heads/master-1.12 ]] + [[ "$RELEASE_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.(110021|111021)$ ]] + case "${RELEASE_VERSION##*.}" in + 110021) minecraft_version=1.10.2 ;; + 111021) minecraft_version=1.11.2 ;; + esac + branch="master-$minecraft_version" + [[ "$RELEASE_SHA" =~ ^[a-f0-9]{40}$ && "$PLAYER_SHA256" =~ ^[a-fA-F0-9]{64}$ && "$CI_RUN_ID" =~ ^[0-9]+$ ]] + + branch_sha="$(gh api "repos/$repo/branches/$branch" --jq .commit.sha)" + if [[ "$RELEASE_SHA" != "$branch_sha" ]]; then + echo "Expected the current upstream $branch SHA, found $branch_sha" >&2 + exit 1 + fi + gh api "repos/$repo/actions/runs/$CI_RUN_ID" | + jq -e --arg sha "$RELEASE_SHA" --arg repo "$repo" --arg branch "$branch" ' + .head_sha == $sha and .head_repository.full_name == $repo + and .head_branch == $branch and .event == "push" + and .status == "completed" and .conclusion == "success" + and .path == ".github/workflows/ci.yml"' >/dev/null + + properties="$(gh api "repos/$repo/contents/gradle.properties?ref=$RELEASE_SHA" --jq .content | base64 --decode)" + grep -Fx "mod_version=$RELEASE_VERSION" <<< "$properties" + grep -Fx "minecraft_version=$minecraft_version" <<< "$properties" + grep -Fx 'loader_name=forge' <<< "$properties" + grep -Fx 'curseforge_project_id=270972' <<< "$properties" + gh api "repos/$repo/contents/CHANGELOG.txt?ref=$RELEASE_SHA" --jq .content | + base64 --decode > "$RUNNER_TEMP/merged-changelog.txt" + echo "minecraft_version=$minecraft_version" >> "$GITHUB_OUTPUT" + + - name: Download the audited CI bundle + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 + with: + name: BaseMinerals-${{ steps.target.outputs.minecraft_version }}-${{ inputs.release_sha }} + path: ${{ runner.temp }}/release-input + github-token: ${{ github.token }} + run-id: ${{ inputs.ci_run_id }} + + - name: Verify the bundle and the tested player jar + id: bundle + env: + RELEASE_VERSION: ${{ inputs.release_version }} + PLAYER_SHA256: ${{ inputs.player_sha256 }} + CI_RUN_ID: ${{ inputs.ci_run_id }} + RELEASE_SHA: ${{ inputs.release_sha }} + shell: bash + run: | + set -euo pipefail + cd "$RUNNER_TEMP/release-input" + jar="BaseMinerals-$RELEASE_VERSION.jar" + cmp "$RUNNER_TEMP/merged-changelog.txt" CHANGELOG.txt + ( + cd build/libs + sha256sum --strict --check ../release/SHA256SUMS + printf '%s %s\n' "${PLAYER_SHA256,,}" "$jar" | sha256sum --strict --check + ) + echo "jar=$RUNNER_TEMP/release-input/build/libs/$jar" >> "$GITHUB_OUTPUT" + echo "Verified $jar from CI run $CI_RUN_ID at $RELEASE_SHA." >> "$GITHUB_STEP_SUMMARY" + + - name: Check CurseForge credential availability + env: + CURSEFORGE_TOKEN: ${{ secrets.CURSEFORGE_TOKEN }} + CONFIRM_LIVE: ${{ inputs.confirm_live_publication }} + shell: bash + run: | + if [[ -z "$CURSEFORGE_TOKEN" ]]; then + echo '::warning::CURSEFORGE_TOKEN is unavailable to Base Minerals.' + [[ "$CONFIRM_LIVE" == false ]] + else + echo 'The CurseForge upload credential is available.' + fi + + - name: Publish the tested jar to CurseForge + if: inputs.confirm_live_publication + uses: Kira-NT/mc-publish@52307b03863581dec6b652b83e597aec02ebb075 # v3.3.1 + with: + curseforge-token: ${{ secrets.CURSEFORGE_TOKEN }} + curseforge-id: 270972 + curseforge-dependencies: | + 240967(required) + 245586(required) + name: Base Minerals ${{ inputs.release_version }} for Minecraft ${{ steps.target.outputs.minecraft_version }} (Forge) + version: ${{ inputs.release_version }} + version-type: release + changelog-file: ${{ runner.temp }}/release-input/CHANGELOG.txt + loaders: forge + game-versions: ${{ steps.target.outputs.minecraft_version }} + game-version-filter: none + environment: client | server + java: Java 8 + fail-mode: fail + files: ${{ steps.bundle.outputs.jar }} diff --git a/.github/workflows/release-on-tag.yml b/.github/workflows/release-on-tag.yml new file mode 100644 index 0000000..6178058 --- /dev/null +++ b/.github/workflows/release-on-tag.yml @@ -0,0 +1,85 @@ +name: Start Base Minerals release from tag + +on: + push: + tags: + - '*.*.*.*' + +permissions: + actions: read + contents: read + +concurrency: + group: baseminerals-release-starter-${{ github.ref_name }} + cancel-in-progress: false + +jobs: + validate-release-tag: + name: Validate tag for manual release confirmation + if: github.repository == 'MinecraftModDevelopmentMods/BaseMinerals' + 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 "For a CurseForge release, open [Release Base Minerals]($RELEASE_WORKFLOW_URL) on master-1.12." + echo "Use the release version, merged SHA, successful upstream CI run ID and tested jar SHA-256." + echo "Leave confirmation off for the first run to verify the release inputs." + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/validate-gradle-build.yml b/.github/workflows/validate-gradle-build.yml new file mode 100644 index 0000000..0b533e4 --- /dev/null +++ b/.github/workflows/validate-gradle-build.yml @@ -0,0 +1,23 @@ +name: Validate Gradle Wrapper + +on: + push: + branches: + - master-1.11.2 + - 'feature/**' + pull_request: + branches: + - master-1.11.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 05a6827..3a14b22 100644 --- a/.gitignore +++ b/.gitignore @@ -20,9 +20,19 @@ build # other eclipse run -classes /README.txt /forge-*-changelog.txt -secret.json \ No newline at end of file +secret.json +# Local context and generated verification +/AGENTS.md +/agent-notes/ +/.codex/ +/.verification*/ +/release-candidates/ +__pycache__/ +*.pyc +.DS_Store +Thumbs.db +desktop.ini diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 4553e94..0000000 --- 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 cc7498e..45a49d1 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1 +1,9 @@ -Initial Release +2.5.1.111021 for 1.11.2 + +This brings the 1.10 update across to 1.11.2. It runs on Forge 13.20.1.2588 and needs Base Metals 2.5.1.111021 and OreSpawn 4.1.6.111021. + +The seven ores have new depths and deposit shapes, and now use KiriCattus's overlays on vanilla stone. Saved ore settings and existing chunks are kept. OreSpawn now preserves the imported lithium rule when you move an old OS1 world and can remove an untouched duplicate left by an earlier version. + +Material settings are available from Forge Mods and OreSpawn Mods and take effect after a restart. This update also fixes disabled material recipes and achievements, charcoal ownership, and molten material textures. Tinkers' Construct still supports melting and casting lithium and silicon. + +The old Electric Advantage 2.2.0 and Power Advantage 2.3.0 releases fail to load on this setup. Mineralogy 3.3.0.6 still places its own sulfur alongside Base Minerals sulfur. diff --git a/README.md b/README.md index 1fa2502..8294a37 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,30 @@ -# Base Minerals [![Build Status](https://travis-ci.org/jriwanek/BaseMinerals.svg?branch=master)](https://travis-ci.org/jriwanek/BaseMinerals) -Addon to Base Metals supporting Minerals +# Base Minerals -Content -------- -Bars +Base Minerals adds lithium, niter, phosphorus, potash, salt, saltpeter, silicon +and sulfur to Base Metals. It provides powders, storage blocks and, for some +materials, ingots, blends and molten forms. Charcoal uses the material supplied +by Base Metals. Make silicon from sand and coal or charcoal dust. -Doors +The seven ores use KiriCattus's overlays on Minecraft's stone texture. -Ores +This branch targets Minecraft 1.11.2 and Forge 13.20.1.2588 or later. It requires +Base Metals 2.5.1.111021 and OreSpawn 4.1.6.111021, or compatible versions in the +declared ranges. Mineralogy is optional. Tinkers' Construct adds melting and +casting for lithium and silicon. -Plates +Material switches remain in `config/BaseMinerals.cfg` and require a full restart. +Open the same screen from Forge Mods or OreSpawn's Mods directory. Configure ore +placement and shared ore outputs in OreSpawn. New worlds use different depths +and deposit shapes for each mineral. Vanilla stone and its variants are valid +hosts, and Base Minerals does not replace terrain. -Trapdoors +Existing registry names and configuration keys are kept. Saved OreSpawn profiles +and OS1 and OS3 imports keep their settings. OreSpawn 4.1.6 preserves the +imported lithium rule when an OS1 world moves without its original config. +Defaults for new worlds can be changed in `config/baseminerals-orespawn.json`. -Armor +See [ore generation](docs/ORE_GENERATION.md), [building](docs/BUILDING.md), +[verification](tools/validation/legacy111/README.md) and +[release preparation](docs/RELEASING.md). -Tools: Crackhammers, Axes, Hoes, Pickaxes, Shovels, Fishing Rods - -Ores, Blocks, Ingots & Nuggets - -Powders & Blends (Ingot and Nugget size) - -Rods - -Gears - -Weapons: Swords, Bows, Arrows, Crossbows & Bolts - -Materials ---------- -[In Progress] \ No newline at end of file +See the [known issues](docs/KNOWN_ISSUES.md) for historical mod compatibility. diff --git a/build.gradle b/build.gradle index 36af8b3..4723e99 100644 --- a/build.gradle +++ b/build.gradle @@ -1,374 +1,181 @@ -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:ForgeGradle:2.2-SNAPSHOT' - classpath 'gradle.plugin.com.matthewprenger:CurseGradle:1.0.11' - classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.6.2' - classpath 'org.junit.platform:junit-platform-gradle-plugin:1.2.0' - classpath 'com.jaredsburrows:gradle-checker-framework-plugin:0.2.1' - } -} +import org.apache.tools.ant.filters.FixCrLfFilter -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: 'com.jaredsburrows.checkerframework' - -repositories { - mavenLocal() - mavenCentral() +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' } -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 - } +group = project.mod_group +version = project.mod_version +base.archivesName = 'BaseMinerals' - // create junit platform jacoco task - project.task(type: JacocoReport, "junitPlatformJacocoReport", - { - sourceDirectories = files("./src/main") - classDirectories = files("$buildDir/classes/main") - executionData junitPlatformTestTask - }) +if (!(project.version ==~ /\d+\.\d+\.\d+\.111021/)) { + throw new GradleException('Use Major.Minor.Patch.111021 for Minecraft 1.11.2 on Forge.') } -junitPlatform { - platformVersion '1.0.3' // optional, defaults to plugin version - //logManager 'org.apache.logging.log4j.jul.LogManager' - reportsDir file('build/test-results/junit-platform') // this is the default - // enableStandardTestTask true - // selectors (optional) - // filters (optional) +// 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) { +tasks.withType(JavaCompile).configureEach { options.encoding = 'UTF-8' - options.compilerArgs = [ - '-Xlint:deprecation', // deprecation, rawtypes, cast, unchecked, all -// '-Xdiags:verbose', -// '-Werror' - ] + sourceCompatibility = targetCompatibility = '1.8' + options.compilerArgs.addAll(['-Xmaxerrs', '1000']) } - -javadoc { +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) } - -description = 'Base Minerals' -def mod_file = getModFile() -def mc_version = '1.11.2' - -ext { - short_version = getVersion('VERSION', mod_file) +tasks.withType(AbstractArchiveTask).configureEach { + preserveFileTimestamps = false + reproducibleFileOrder = true + includeEmptyDirs = false + exclude '**/desktop.ini', '**/Thumbs.db', '**/.DS_Store' } - -version = mc_version + '-' + short_version - -if (System.getenv().BUILD_NUMBER) { - version += '.' + System.getenv().BUILD_NUMBER +// 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') } -group = 'com.mcmoddev' -archivesBaseName = 'BaseMinerals' -sourceCompatibility = targetCompatibility = '1.8' - -class Secrets { - def data = null - - def getProperty(String key) { - return data ? data[key] : '' - } +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')) } -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 - } +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.11 launches.' + dependsOn tasks.named('classes') + from tasks.named('compileJava', JavaCompile).flatMap { it.destinationDirectory } + from sourceSets.main.output.resourcesDir + into developmentOutput } minecraft { - version = '1.11.2-13.20.1.2386' - runDir = 'run' - mappings = 'stable_32' - - replace '@FINGERPRINT@', project.findProperty('signSHA1') -} - -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 { - deobfCompile "slimeknights.mantle:Mantle:${mc_version}-${mantle_version}" - deobfCompile "slimeknights:TConstruct:${mc_version}-${tconstruct_version}" - - deobfCompile "com.mcmoddev:AdditionalLootTables:${mc_version}-${alt_version}" - deobfCompile "com.mcmoddev:OreSpawn:${mc_version}-${orespawn_version}" - deobfCompile "com.mcmoddev:BaseMetals:${mc_version}-${bme_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' - } -} - -jar { - manifest { - } -} - -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 + 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('baseMineralsRunDirectory').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('baseMineralsLaunchProbePhase').isPresent()) { + systemProperty 'launch.phase', providers.gradleProperty('baseMineralsLaunchProbePhase').get() } - url secret.url } + register('client') + register('server') { args 'nogui' } } } -curseforge { - apiKey = secret.curseforgeAPIKey - project { - id = '270972' - changelog = file('CHANGELOG.txt') - releaseType = 'release' - addGameVersion(project.mc_version) - def projectName = 'Base Minerals' - def displayVersion = getVersion('VERSION', mod_file) - if (System.getenv().BUILD_NUMBER) { - displayVersion += '.' + System.getenv().BUILD_NUMBER - } - mainArtifact(jar) { - displayName = "$project.description $displayVersion" - relations { - requiredLibrary 'mmd-orespawn' - requiredLibrary 'base-metals' - optionalLibrary 'additional-loot-tables' - } - } - addArtifact(sourceJar) { - displayName = "$project.description $displayVersion Sources" - } - addArtifact(deobfJar) { - displayName = "$project.description $displayVersion Development" - } - addArtifact(javadocJar) { - displayName = "$project.description $displayVersion Javadoc" - } +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 } } -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" - } -} +def legacyMirror = file(providers.gradleProperty('legacyDependencyVerificationRepository') + .orElse(providers.gradleProperty('legacyDependencyRepository')) + .getOrElse('.gradle/legacy-dependencies')) +def legacyRepository = { String remote -> legacyMirror.isDirectory() ? legacyMirror.toURI() : uri(remote) } -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" - } - } - } +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' } + } + exclusiveContent { + forRepository { maven { url = legacyRepository('https://maven.moddev.zone/releases'); metadataSources { artifact() } } } + filter { includeModule 'zone.moddev.mc.basemetals', 'BaseMetals'; includeModule 'zone.moddev.mc.orespawn', 'OreSpawn' } } - return path + mavenCentral() } -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("\\.") +apply from: 'gradle/dependencies.gradle' +renamer.mappings.from minecraft.dependency.toSrgFile - major = pts[0] ?: '0' - revision = pts[1] ?: '0' - patch = pts[2] ?: '0' - } - } - return "$major.$revision.$patch" -} +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 } + filesMatching('assets/baseminerals/lang/*.lang') { details -> details.name = details.name.toLowerCase(Locale.ROOT) } + 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': 'BaseMinerals', 'Implementation-Version': project.version, + 'Maven-Artifact': "${project.group}:BaseMinerals:${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.baseMineralsBuild = [developmentOutput: developmentOutput].asImmutable() +apply from: 'gradle/ore-models.gradle' +apply from: 'gradle/release/artifacts.gradle' +apply from: 'gradle/release/publishing.gradle' +apply from: 'gradle/verification/runtime.gradle' +apply from: 'gradle/ide/eclipse.gradle' diff --git a/docs/BUILDING.md b/docs/BUILDING.md new file mode 100644 index 0000000..67584e2 --- /dev/null +++ b/docs/BUILDING.md @@ -0,0 +1,75 @@ +# Building Base Minerals 1.11.2 + +Use Gradle 9.6.1, ForgeGradle 7.0.34, Renamer 1.1.5 and Foojay 1.0.0. Gradle runs +on Java 17 and Mavenizer uses Java 25. Compilation, bytecode and Minecraft use Java 8. +Forge is 13.20.1.2588 with stable-32 mappings. Release pins are Temurin 8u502-b07, +17.0.1+12 and 25.0.3+9-LTS. Install them or pass their homes with +`org.gradle.java.installations.paths`. Do not launch Gradle on Java 8. +On the command line use `-Dorg.gradle.java.installations.paths=` and +`-Dorg.gradle.java.installations.auto-detect=false` to keep offline runs on the +same physical JDK installations. Gradle 9 deprecates the older `-P` form. + +The staging script downloads and verifies the pinned public Base Metals, +OreSpawn and Tinkers dependencies using `gradle/dependencies.json`. + +Select Java 17 with `JAVA_HOME` and `PATH`, then stage the dependencies and build. + +```powershell +$env:GRADLE_USER_HOME='' +$mirror = '' +python gradle/stage-legacy-dependencies.py $mirror +python -m pip install -r tools/validation/requirements.txt +.\gradlew.bat clean check build javadoc verifyReleaseArtifacts verifyProjectResources prepareEclipse "-PlegacyDependencyVerificationRepository=$mirror" +``` + +Use `-PpythonExecutable=` if Python is not named `python3`. +`gradle/dependencies.json` pins production downloads and SHA-256. Staging creates +a verified Maven mirror. `-PlegacyDependencyVerificationRepository=` selects +its location. The build remaps development copies and strips invalid signatures +from those copies. No floating mod versions, JCenter or +`mavenLocal()` are used. A normal build never publishes. + +`build/libs/BaseMinerals-2.5.1.111021.jar` is the installed jar. Sources/Javadocs +are separate. Test probes are isolated from all release archives. + +## Eclipse + +Run `prepareEclipse` and import the inner Git/Gradle project using Buildship. Use +Java 17 for Gradle, a Java 8 project JRE and one physical cache for Eclipse and the +terminal. Production profiles contain only Base Minerals, Base Metals and OreSpawn. +Optional APIs are compilation dependencies. Test profiles are separate. Regenerate +profiles when dependency/toolchain locations change. FG7 uses `genEclipseRuns` and +`prepareEclipse`. + +Gradle launches merge production classes and processed resources into one mod +directory. Eclipse uses `bin/main`, with resources from `build/resources/main` so +metadata is not duplicated. `verifyEclipseProductionClasspath` checks isolation. +ForgeGradle's `source-sets-not-merged-for-eclipse` diagnostic refers to its own +automatic merge. This project merges production output explicitly and audits +the generated profiles, Buildship model and Java 8 Eclipse compiler. + +## Ore models + +`generateOreModels` writes the ore block models to `build/generated/ore-models`. +The normal build includes them in the game and sources jars. Blockstates and +inventory models use the existing model names. + +The seven ores use KiriCattus's transparent PNGs in +`src/main/resources/assets/baseminerals/textures/blocks/ore_overlays/`. The build +layers each PNG over Minecraft stone for the block and inventory models. +An ore with only a full texture in `textures/blocks/` uses that texture on a +single cube. To switch it to an overlay, put a PNG with the same name in +`ore_overlays/` and rebuild. + +## Verification + +`test` checks native declarations, configuration latching and namespace compatibility. +`verifyProjectResources` validates resources and the pinned engine schema. Packaged server checks require a clean Forge template: + +```text +gradlew packagedForgeRuntimeTest -PpackagedForgeRuntimeRoot= -PpackagedForgeRuntimeOutput= +gradlew packagedSourcePolicyTest -PpackagedForgeRuntimeRoot= -PpackagedSourcePolicyOutput= +``` + +The output directory must not already exist. See the validation README for exact +client commands, migration scenarios and limitations. diff --git a/docs/KNOWN_ISSUES.md b/docs/KNOWN_ISSUES.md new file mode 100644 index 0000000..94429a2 --- /dev/null +++ b/docs/KNOWN_ISSUES.md @@ -0,0 +1,20 @@ +# Known issues for Minecraft 1.11.2 + +## OS1 profiles with an edited lithium rule + +[OreSpawn 4.1.6.111021](https://www.curseforge.com/minecraft/mc-mods/mmd-orespawn/files/9524249) +keeps the imported lithium rule when a world moves without its original +configuration. It backs up affected profiles before removing an untouched +duplicate. If the added rule or its source policy was edited, OreSpawn +preserves the edits and flags the rule for review. + +## Historical Electric Advantage dependencies + +The historical Electric Advantage 2.2.0 and Power Advantage 2.3.0 jars target +Minecraft 1.10.2. In the 1.11.2 test, Power Advantage fails before world load +while linking `FluidDrainTileEntity`. That also blocks the combined case. + +[Mineralogy 3.3.0.6](https://www.curseforge.com/minecraft/mc-mods/minecraft-mineralogy/files/2561871) +loads and passes the chunk census. OreSpawn reports its sulfur as an external +source. The synthetic source-policy fixture verifies managed output choices, +including source removal and restoration. diff --git a/docs/ORE_GENERATION.md b/docs/ORE_GENERATION.md new file mode 100644 index 0000000..9095a8c --- /dev/null +++ b/docs/ORE_GENERATION.md @@ -0,0 +1,64 @@ +# Ore generation + +Base Minerals supplies one native schema-5 declaration through OreSpawn's public +API. OreSpawn owns baking and execution. This mod adds no fallback generator, +rocks, terrain, biomes, strata or fluid deposits, and needs no Mineralogy. + +## Fresh-world defaults + +Ranges are inclusive. Frequency is expected attempts per chunk. Quantity is a +requested block budget, so veins can yield less when hosts are unavailable. + +| Mineral | Y | Attempts/chunk | Quantity | Shape | +| --- | --- | ---: | --- | --- | +| Lithium | 8..47 | 4 | 12..18 | Vein | +| Niter | 16..63 | 5 | 8..16 | Clusters | +| Salt | 24..79 | 2.5 | 16..32 | Flat clusters | +| Sulfur | 4..31 | 3 | 12..18 | Clusters | +| Phosphorus | 16..63 | 3 | 12..18 | Clusters | +| Potash | 8..47 | 2.5 | 12..24 | Flat clusters | +| Saltpeter | 24..79 | 3.75 | 8..16 | Clusters | + +Lithium and phosphorus use a triangular height curve. Sulfur favors the bottom +of its range, and the others use uniform heights. Each mineral retains its previous +expected requested budget while changing depth and shape. These are gameplay +approximations rather than geological simulations. + +Lithium uses vein length 8 and node size 3. Cluster horizontal spread, vertical +spread and node size are respectively niter `(4,2,3)`, salt `(8,0,8)`, sulfur +`(4,2,4)`, phosphorus `(6,1,6)`, potash `(6,0,6)` and saltpeter `(4,1,3)`. +The balance draws on [lithium pegmatites](https://www.usgs.gov/data/lithium-deposits-united-states), +[evaporite potash](https://www.usgs.gov/publications/potash-a-global-overview-evaporate-related-potash-resources-including-spatial) +and [sedimentary phosphate](https://pubs.usgs.gov/bul/b1693/html/bull74kl.htm). + +Vanilla stone, including metadata variants, is eligible. Additional hosts are +intrusive igneous rocks for lithium, sedimentary rocks for nitrates, salt, potash +and phosphorus, and sedimentary or volcanic rocks for sulfur. There are no hard biome +restrictions. Rules cover ordinary dimensions except vanilla Nether/End. +Silicon and charcoal do not generate. + +## Saved settings and shared ores + +Rule IDs remain `baseminerals:legacy/_ore`. Canonical materials use +`orespawn:`, with the `orespawn:standard` placement channel. Niter and +saltpeter remain separate. Sulfur/Sulphur aliases refer to the same material. + +OreSpawn's reviewed source policies can share one placement budget across +equivalent outputs. Sulfur priority is Mineralogy, Base Minerals, then Electric +Advantage. Lithium priority is Base Minerals then Electric Advantage. Only actual +loaded/managed sources qualify for automatic consolidation. An external generator +does not become controllable merely through an Ore Dictionary alias. +An existing profile choice remains authoritative even after sources become +manageable. Review the resulting source groups in OreSpawn before changing them. + +Native global overrides use `config/baseminerals-orespawn.json`. Existing world +profiles and native overrides take precedence over legacy imports. Supported +OS1/OS3 custom and disabled rules retain their settings rather than adopting this +balance. The explicit Overworld rule overrides the ordinary-dimension fallback, +so each chunk receives a single placement budget. Existing +ore entries are kept as whole entries, including older selector-only rules. Missing entries follow OreSpawn's normal provider merge behavior. +Retrogen stays disabled. Keep legacy files until +the importer report and resulting settings have been reviewed. +OreSpawn 4.1.6 preserves the imported lithium rule when only the saved world +is moved. It backs up profiles before removing an untouched duplicate and +flags edited duplicates for review. See [known issues](KNOWN_ISSUES.md). diff --git a/docs/RELEASING.md b/docs/RELEASING.md new file mode 100644 index 0000000..4350e67 --- /dev/null +++ b/docs/RELEASING.md @@ -0,0 +1,23 @@ +# Release preparation + +Normal builds never publish. Main, sources, Javadocs and SHA-256 form one audited +candidate. Gradle enforces Java 8 bytecode, deterministic archives, processed +metadata, dependency hashes and exclusion of tests, local context and shared +library copies. Prepared bundles contain exactly those three jars, `SHA256SUMS` +and `CHANGELOG.txt`. `verifyPreparedReleaseArtifacts` audits the prepared files. + +CI has a cold bootstrap/offline rebuild, build/tests, resource/schema checks, +packaged server qualification, Eclipse generation and clean reproducibility check. +CodeQL/wrapper checks are separate. Actions use full commit hashes. + +The shared dispatcher on MMD's default branch, `master-1.12`, needs the +1.11.2 routing from this checkout before it can release this target. +Use the `Major.Minor.Patch.111021` version, full merged `master-1.11.2` SHA, +successful upstream push CI run ID and SHA-256 of the tested player jar. +Run with confirmation off to verify the source, changelog and CI bundle. + +Set `confirm_live_publication` to upload the verified player jar to CurseForge +project **270972** with Base Metals **240967** and MMD OreSpawn **245586** marked +as required. The workflow needs access to `CURSEFORGE_TOKEN`. Check the public +file and its download checksum after upload. If an upload times out, check its +status before trying again. diff --git a/gradle.properties b/gradle.properties index 4731ead..3173476 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,25 +1,33 @@ -mc_version=1.11.2 -top_mc_version=1.1x -ccl_version=2.7.0.+ -ccc_version=2.4.1.+ -nei_version=2.3.0.+ -jei_version=4.4.1.+ -mantle_version=1.2.0.+ -tconstruct_version=2.7.0.+ -mcmp_version=2.0.1 -mcmp_experimental_version=2.0.1_31 -top_version=1.4.7-57 -alt_version=2.0.+ -orespawn_version=3.2.+ -tesla_version=1.3.0.+ -bme_version=2.5.0-beta+ -mme_version=2.5.0-beta+ -fme_version=2.5.0-beta+ -bmi_version=2.5.0-beta+ -pa_version=2.3.0 -cofhcore_version=4.2.3.+ -mekanism_version=471563e083 -taiga_version=b874a41abb -ic2_version=2.7.71-ex111 -minetweaker_version=3.+ -hwyla_version=+ +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 + +minecraft_version=1.11.2 +forge_version=13.20.1.2588 +mappings_channel=stable +mappings_version=32-1.11 + +mod_id=baseminerals +mod_group=zone.moddev.mc.baseminerals +mod_version=2.5.1.111021 +mod_artifact=BaseMinerals +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=270972 +cf_requirements=mmd-orespawn,base-metals +orespawn_curse_project_id=245586 +coreplugin=zone.moddev.mc.baseminerals.compat.BaseMineralsLoadingPlugin +orespawn_version=4.1.6.111021 +orespawn_version_range=[4.1.6.111021,5.0.0) + +basemetals_version=2.5.1.111021 +basemetals_version_range=[2.5.1.111021,2.6.0) diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle new file mode 100644 index 0000000..ddb96c3 --- /dev/null +++ b/gradle/dependencies.gradle @@ -0,0 +1,75 @@ +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.11'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:9524249' + || orespawn.sha256 != '4246D1EB83FFFC325F04E3D3FE331D1404714F4A8D773ADE278A5D0EBEE5EDBC') { + throw new GradleException('Use the verified OreSpawn 4.1.6.111021 release.') + } + def basemetals = specs.find { it.id == 'basemetals' } + if (basemetals.coordinate != 'curse.maven:base-metals-240967:9526386' + || basemetals.sha256 != 'B69E62A786D32CB7D7129E237E3759355721F6D0ED90271C59B5E4444AF0F95C') { + throw new GradleException('Use the verified Base Metals 2.5.1.111021 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 0000000..2d06250 --- /dev/null +++ b/gradle/dependencies.json @@ -0,0 +1,28 @@ +[ + { + "id": "basemetals", + "coordinate": "curse.maven:base-metals-240967:9526386", + "sha256": "B69E62A786D32CB7D7129E237E3759355721F6D0ED90271C59B5E4444AF0F95C", + "repository": "https://www.cursemaven.com", + "runtime": true + }, + { + "id": "orespawn", + "coordinate": "curse.maven:mmd-orespawn-245586:9524249", + "sha256": "4246D1EB83FFFC325F04E3D3FE331D1404714F4A8D773ADE278A5D0EBEE5EDBC", + "repository": "https://www.cursemaven.com", + "runtime": true + }, + { + "id": "mantle", + "coordinate": "curse.maven:mantle-74924:2413993", + "sha256": "6C1C79BD8491106D65240D4DF139E2BEA7914BA2622FAE0662ADB3047D5EDF9D", + "repository": "https://www.cursemaven.com" + }, + { + "id": "tconstruct", + "coordinate": "curse.maven:tinkers-construct-74072:2451489", + "sha256": "2D7F4A08EDDA2881A432CD80FF26575CCEA593FCC8A597A435652C1A53B14CB8", + "repository": "https://www.cursemaven.com" + } +] diff --git a/gradle/ide/eclipse.gradle b/gradle/ide/eclipse.gradle new file mode 100644 index 0000000..b63025a --- /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/legacy111', + 'tools/validation/legacy111/migration-probe', + 'tools/validation/legacy111/launch-probe', + 'tools/validation/legacy111/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.11.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('legacy111-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/legacy111')) { + 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 0000000..a770e98 --- /dev/null +++ b/gradle/ide/log4j2.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/gradle/ore-models.gradle b/gradle/ore-models.gradle new file mode 100644 index 0000000..5e08fbf --- /dev/null +++ b/gradle/ore-models.gradle @@ -0,0 +1,77 @@ +import groovy.json.JsonOutput + +// Forge 1.11 predates Minecraft's data generator. Generate models during the build. +def assets = layout.projectDirectory.dir('src/main/resources/assets/baseminerals') +def textures = assets.dir('textures/blocks') +def solidTextures = fileTree(textures) { include '*_ore.png' } +def overlayTextures = fileTree(textures.dir('ore_overlays')) { include '*_ore.png' } +def oreStates = fileTree(assets.dir('blockstates')) { + include '*_ore.json' + // Historical placeholders with no registered ore blocks. + exclude 'charcoal_ore.json', 'silicon_ore.json' +} +def generated = layout.buildDirectory.dir('generated/ore-models') + +def generateOreModels = tasks.register('generateOreModels') { + group = 'build' + description = 'Generates ore models from the available overlay or solid textures.' + inputs.files(oreStates, solidTextures, overlayTextures).withPathSensitivity(PathSensitivity.RELATIVE) + outputs.dir(generated) + + doLast { + def names = oreStates.files.collect { it.name - '.json' }.sort() + def overlays = overlayTextures.files.collect { it.name - '.png' }.toSet() + def solid = solidTextures.files.collect { it.name - '.png' }.toSet() + def extra = overlays - names + def missing = names.findAll { !overlays.contains(it) && !solid.contains(it) } + if (extra || missing) { + throw new GradleException("Invalid ore textures. Missing textures: ${missing}. Unmatched overlays: ${extra}.") + } + + def directory = generated.get().dir('assets/baseminerals/models/block').asFile + directory.mkdirs() + def writeModel = { String name, Map model -> + new File(directory, "${name}.json").setText(JsonOutput.prettyPrint(JsonOutput.toJson(model)) + '\n', 'UTF-8') + } + def cube = { Number from, Number to, String texture -> + def faces = ['down', 'up', 'north', 'south', 'west', 'east'].collectEntries { face -> + [(face): [texture: texture, cullface: face, uv: [0, 0, 16, 16]]] + } + [from: [from, from, from], to: [to, to, to], faces: faces] + } + writeModel('ore_overlay', [ + parent: 'minecraft:block/block', + elements: [cube(0, 16, '#base'), cube(-0.05, 16.05, '#overlay')] + ]) + names.each { name -> + if (overlays.contains(name)) { + writeModel(name, [ + parent: 'baseminerals:block/ore_overlay', + textures: [particle: 'minecraft:blocks/stone', base: 'minecraft:blocks/stone', + overlay: "baseminerals:blocks/ore_overlays/${name}"] + ]) + } else { + writeModel(name, [ + parent: 'block/cube_all', + textures: [all: "baseminerals:blocks/${name}"] + ]) + } + } + directory.listFiles().findAll { + it.isFile() && it.name.endsWith('_ore.json') && !names.contains(it.name - '.json') + }.each { + if (!it.delete()) throw new GradleException("Could not remove stale generated ore model ${it.name}") + } + } +} + +tasks.named('processResources', ProcessResources) { + dependsOn generateOreModels + from generated + duplicatesStrategy = DuplicatesStrategy.FAIL +} + +tasks.named('sourcesJar', Jar) { + dependsOn generateOreModels + from generated +} diff --git a/gradle/prepare-packaged-forge-runtime.sh b/gradle/prepare-packaged-forge-runtime.sh new file mode 100644 index 0000000..d0fa2e4 --- /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.11.2-13.20.1.2588 +expected=00AEA3DDF1E78BBB4D5D05E8C22FB3E849DE09B47D828E57DAD373A16BB8A26F + +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.11 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.11.2.jar" +test -d "$runtime/libraries" diff --git a/gradle/release/artifacts.gradle b/gradle/release/artifacts.gradle new file mode 100644 index 0000000..aa30c52 --- /dev/null +++ b/gradle/release/artifacts.gradle @@ -0,0 +1,186 @@ +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/BaseMinerals-${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.11 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.baseminerals' || project.version != '2.5.1.111021' + || project.minecraft_version != '1.11.2' || project.forge_version != '13.20.1.2588' + || project.mappings_channel != 'stable' || project.mappings_version != '32-1.11') { + throw new GradleException('Base Minerals target, mappings or Maven identity changed.') + } + String source = file('src/main/java/zone/moddev/mc/baseminerals/BaseMinerals.java').getText('UTF-8') + if (!source.contains('VERSION = "' + project.version + '"') || !source.contains('MODID = "baseminerals"')) { + throw new GradleException('The mod identity differs from the build metadata.') + } + if (project.orespawn_version != '4.1.6.111021' + || project.orespawn_version_range != '[4.1.6.111021,5.0.0)' + || !source.contains('required-after:orespawn@' + project.orespawn_version_range) + || !source.contains('required-after:forge@[13.20.1.2588,)')) { + throw new GradleException('The OreSpawn or Forge runtime requirement changed.') + } + if (project.basemetals_version != '2.5.1.111021' + || project.basemetals_version_range != '[2.5.1.111021,2.6.0)' + || !source.contains('required-after:basemetals@' + project.basemetals_version_range)) { + throw new GradleException('The published Base Metals runtime requirement changed.') + } + } +} + +def expectedFiles = ['', '-sources', '-javadoc'].collect { "BaseMinerals-${project.version}${it}.jar".toString() }.asImmutable() +def preparedDirectory = providers.gradleProperty('preparedReleaseDir') +def auditJars = { File directory -> + ['','-sources','-javadoc'].each { suffix -> + File jar = new File(directory, "BaseMinerals-${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/') || path.startsWith('zone/moddev/mc/basemetals/') + || path.startsWith('com/mcmoddev/orespawn/') + || path == 'assets/baseminerals/orespawn/baseminerals.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}:BaseMinerals:${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 != 'baseminerals' || info.version != project.version || info.mcversion != '1.11.2') { + throw new GradleException('Incorrect processed mcmod.info.') + } + if (zip.getEntry('zone/moddev/mc/baseminerals/BaseMinerals.class') == null + || zip.getEntry('META-INF/baseminerals-legacy-api.list') == null) { + throw new GradleException('Missing mod entry point or legacy API map.') + } + } + } + } +} + +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, "BaseMinerals-${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}) (BaseMinerals-[^\/\\]+\.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.11.2')) { + throw new GradleException('The prepared changelog belongs to a different release.') + } + auditJars(directory) + } +} + +ext.baseMineralsRelease = [jar: releaseJar, expectedFiles: expectedFiles, + preparedDirectory: preparedDirectory].asImmutable() diff --git a/gradle/release/publishing.gradle b/gradle/release/publishing.gradle new file mode 100644 index 0000000..3fbf60a --- /dev/null +++ b/gradle/release/publishing.gradle @@ -0,0 +1,96 @@ +import groovy.xml.XmlSlurper + +def releaseJar = project.ext.baseMineralsRelease.jar +def expectedReleaseFiles = project.ext.baseMineralsRelease.expectedFiles +def preparedReleaseDir = project.ext.baseMineralsRelease.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 Minerals' + description = 'Minerals, ores and materials for Base Metals on Minecraft 1.11.2.' + url = 'https://github.com/MinecraftModDevelopmentMods/BaseMinerals' + 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/BaseMinerals.git' + developerConnection = 'scm:git:ssh://git@github.com/MinecraftModDevelopmentMods/BaseMinerals.git' + url = 'https://github.com/MinecraftModDevelopmentMods/BaseMinerals' + } + } + } + } + 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.baseminerals', 'BaseMinerals', 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 0000000..4803075 --- /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 0000000..530f893 --- /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/runtime.gradle b/gradle/verification/runtime.gradle new file mode 100644 index 0000000..befec1a --- /dev/null +++ b/gradle/verification/runtime.gradle @@ -0,0 +1,88 @@ +// Isolated probes are never included in ordinary launches or release jars. +sourceSets { + policyFixture { + java.srcDirs = ['tools/validation/legacy111/policy-fixture'] + compileClasspath = sourceSets.main.compileClasspath + sourceSets.main.output + } + runtimeProbe { + java.srcDirs = ['tools/validation/legacy111/runtime-probe'] + compileClasspath = sourceSets.main.compileClasspath + sourceSets.main.output + } +} +def legacyConsumer = tasks.register('generateLegacyConsumer', JavaExec) { + dependsOn tasks.named('runtimeProbeClasses') + javaLauncher = javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(8) } + classpath = sourceSets.runtimeProbe.output + sourceSets.main.compileClasspath + mainClass = 'review.minerals.LegacyFixtureGenerator' + args layout.buildDirectory.dir('test-fixtures/legacy-consumer').get().asFile.absolutePath + outputs.dir layout.buildDirectory.dir('test-fixtures/legacy-consumer') +} +def probe = tasks.register('runtimeProbeJar', Jar) { + dependsOn legacyConsumer + group = 'verification' + archiveFileName = 'baseminerals-probe.jar' + destinationDirectory = layout.buildDirectory.dir('test-mods') + from sourceSets.runtimeProbe.output + from layout.buildDirectory.dir('test-fixtures/legacy-consumer') +} +def packagedProbe = renamer.classes(probe) { + map.from minecraft.dependency.toSrgFile + output = layout.buildDirectory.file('test-mods/baseminerals-probe-packaged.jar') +} +tasks.register('packagedRuntimeProbeJar') { dependsOn packagedProbe } + +def policyFixtureJar = tasks.register('policyFixtureJar', Jar) { + archiveFileName = 'synthetic-source-policy-fixture.jar' + destinationDirectory = layout.buildDirectory.dir('test-mods') + from sourceSets.policyFixture.output +} +def packagedPolicyFixture = renamer.classes(policyFixtureJar) { + map.from minecraft.dependency.toSrgFile + output = layout.buildDirectory.file('test-mods/synthetic-source-policy-fixture-packaged.jar') +} +tasks.register('packagedPolicyFixtureJar') { dependsOn packagedPolicyFixture } + +// The schema audit also needs this deterministic JUnit export after a cache hit. +tasks.named('test', Test) { + outputs.file(layout.buildDirectory.file('reports/native-provider.json')) +} + +tasks.register('verifyProjectResources', Exec) { + group = 'verification' + dependsOn tasks.named('test'), tasks.named('processResources') + doFirst { + commandLine providers.gradleProperty('pythonExecutable').getOrElse('python3'), + 'tools/validation/legacy111/audit_resources.py', '--mirror', + file(providers.gradleProperty('legacyDependencyVerificationRepository').getOrElse('.gradle/legacy-dependencies')).absolutePath + } +} +tasks.register('packagedForgeRuntimeTest', Exec) { + group = 'verification' + dependsOn 'verifyReleaseArtifacts', 'packagedRuntimeProbeJar', 'test' + doFirst { + if (!providers.gradleProperty('packagedForgeRuntimeRoot').isPresent()) + throw new GradleException('Set packagedForgeRuntimeRoot to a disposable Forge server template.') + final def java8 = javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(8) }.get() + commandLine providers.gradleProperty('pythonExecutable').getOrElse('python3'), + 'tools/validation/legacy111/run_packaged_suite.py', '--java', java8.executablePath.asFile.absolutePath, + '--template', file(providers.gradleProperty('packagedForgeRuntimeRoot').get()).absolutePath, + '--output', file(providers.gradleProperty('packagedForgeRuntimeOutput').getOrElse('build/packaged-forge-checks')).absolutePath, + '--mirror', file(providers.gradleProperty('legacyDependencyVerificationRepository').getOrElse('.gradle/legacy-dependencies')).absolutePath + } +} + +tasks.register('packagedSourcePolicyTest', Exec) { + group = 'verification' + description = 'Tests managed-source policies with an isolated synthetic sibling, not a public integration claim.' + dependsOn 'verifyReleaseArtifacts', 'packagedRuntimeProbeJar', 'packagedPolicyFixtureJar' + doFirst { + if (!providers.gradleProperty('packagedForgeRuntimeRoot').isPresent()) + throw new GradleException('Set packagedForgeRuntimeRoot to a disposable Forge server template.') + final def java8 = javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(8) }.get() + commandLine providers.gradleProperty('pythonExecutable').getOrElse('python3'), + 'tools/validation/legacy111/run_policy_fixture.py', '--java', java8.executablePath.asFile.absolutePath, + '--template', file(providers.gradleProperty('packagedForgeRuntimeRoot').get()).absolutePath, + '--output', file(providers.gradleProperty('packagedSourcePolicyOutput').getOrElse('build/packaged-source-policies')).absolutePath, + '--mirror', file(providers.gradleProperty('legacyDependencyVerificationRepository').getOrElse('.gradle/legacy-dependencies')).absolutePath + } +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 0d4a951..b1b8ef5 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 a95009c..dbe66e1 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 cccdd3d..c0dee63 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 f955316..a51ec4f 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 new file mode 100644 index 0000000..bd5a40c --- /dev/null +++ b/settings.gradle @@ -0,0 +1,5 @@ +plugins { + id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0' +} + +rootProject.name = 'BaseMinerals' diff --git a/src/main/java/com/mcmoddev/alt/BaseMineralsALT.java b/src/main/java/com/mcmoddev/alt/BaseMineralsALT.java deleted file mode 100644 index d33340f..0000000 --- a/src/main/java/com/mcmoddev/alt/BaseMineralsALT.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.mcmoddev.alt; - -import com.mcmoddev.alt.api.IALTPlugin; - -//@ALTPlugin(modid = BaseMinerals.MODID) -public class BaseMineralsALT implements IALTPlugin { - // Intentionally left blank -} diff --git a/src/main/java/com/mcmoddev/baseminerals/init/Blocks.java b/src/main/java/com/mcmoddev/baseminerals/init/Blocks.java deleted file mode 100644 index c37b726..0000000 --- a/src/main/java/com/mcmoddev/baseminerals/init/Blocks.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.mcmoddev.baseminerals.init; - -import com.mcmoddev.baseminerals.init.ItemGroups; - -import java.util.Arrays; -import java.util.List; - -import com.mcmoddev.baseminerals.data.MaterialNames; -import com.mcmoddev.lib.data.Names; -import com.mcmoddev.lib.data.SharedStrings; -import com.mcmoddev.lib.init.Materials; -import com.mcmoddev.lib.material.MMDMaterial; - -/** - * This class initializes all blocks in Base Minerals. - * - * @author Jasmine Iwanek - * - */ -public class Blocks extends com.mcmoddev.lib.init.Blocks { - - protected Blocks() { - throw new IllegalAccessError(SharedStrings.NOT_INSTANTIABLE); - } - - /** - * - */ - public static void init() { - final List materials = Arrays.asList(MaterialNames.CHARCOAL, MaterialNames.LITHIUM, MaterialNames.NITER, - MaterialNames.PHOSPHORUS, MaterialNames.POTASH, MaterialNames.SALT, MaterialNames.SALTPETER, - MaterialNames.SILICON, MaterialNames.SULFUR); - - materials.stream().filter(Materials::hasMaterial).forEach(materialName -> { - final MMDMaterial material = Materials.getMaterialByName(materialName); - - create(Names.BLOCK, material); - create(Names.ORE, material); - }); - } -} diff --git a/src/main/java/com/mcmoddev/baseminerals/integration/plugins/TinkersConstruct.java b/src/main/java/com/mcmoddev/baseminerals/integration/plugins/TinkersConstruct.java deleted file mode 100644 index dd5b3b6..0000000 --- a/src/main/java/com/mcmoddev/baseminerals/integration/plugins/TinkersConstruct.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.mcmoddev.baseminerals.integration.plugins; - -import com.mcmoddev.baseminerals.BaseMinerals; -import com.mcmoddev.baseminerals.data.MaterialNames; -import com.mcmoddev.baseminerals.init.Materials; -import com.mcmoddev.lib.integration.MMDPlugin; -import com.mcmoddev.lib.integration.IIntegration; -import com.mcmoddev.lib.material.MMDMaterial; -import com.mcmoddev.lib.util.ConfigBase.Options; - -/** - * - * @author Jasmine Iwanek - * - */ -@MMDPlugin(addonId = BaseMinerals.MODID, pluginId = TinkersConstruct.PLUGIN_MODID) -public class TinkersConstruct extends com.mcmoddev.lib.integration.plugins.TinkersConstructBase implements IIntegration { - - @Override - public void init() { - if (!Options.isModEnabled(PLUGIN_MODID)) { - return; - } - - if (Materials.hasMaterial(MaterialNames.LITHIUM)) { - final MMDMaterial lithium = Materials.getMaterialByName(MaterialNames.LITHIUM); - registerFluid(lithium, 144); - registerCasting(lithium, 144); - } - - if (Materials.hasMaterial(MaterialNames.SILICON)) { - final MMDMaterial silicon = Materials.getMaterialByName(MaterialNames.SILICON); - registerFluid(silicon, 144); - registerCasting(silicon, 144); - } - } -} diff --git a/src/main/java/com/mcmoddev/baseminerals/proxy/ClientProxy.java b/src/main/java/com/mcmoddev/baseminerals/proxy/ClientProxy.java deleted file mode 100644 index 4e21c45..0000000 --- a/src/main/java/com/mcmoddev/baseminerals/proxy/ClientProxy.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.mcmoddev.baseminerals.proxy; - -import com.mcmoddev.baseminerals.BaseMinerals; -import com.mcmoddev.baseminerals.init.Blocks; -import com.mcmoddev.baseminerals.init.Fluids; -import com.mcmoddev.baseminerals.init.Items; -import com.mcmoddev.lib.client.registrations.RegistrationHelper; -import com.mcmoddev.lib.data.Names; - -import net.minecraft.client.renderer.block.model.ModelResourceLocation; -import net.minecraft.item.Item; -import net.minecraft.util.ResourceLocation; -import net.minecraftforge.client.model.ModelLoader; -import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; - -/** - * Base Minerals Client Proxy - * - * @author Jasmine Iwanek - * - */ -public class ClientProxy extends CommonProxy { - - @Override - public void preInit(FMLPreInitializationEvent event) { - super.preInit(event); - - for (final String name : Items.getItemRegistry().keySet()) { - if (!name.endsWith(Names.ANVIL.toString())) - RegistrationHelper.registerItemRender(name); - else { - final String[] names = new String[] { "intact", "slightly_damaged", "very_damaged" }; - final Item item = Items.getItemByName(name); - for (int i = 0; i < 3; i++) { - final ResourceLocation rl = new ResourceLocation(BaseMinerals.MODID, String.format("%s_%s", name, names[i])); - ModelLoader.setCustomModelResourceLocation(item, i, new ModelResourceLocation(rl, "inventory")); - } - } - } - - for (final String name : Blocks.getBlockRegistry().keySet()) { - RegistrationHelper.registerBlockRender(name); - } - - for (final String name : Fluids.getFluidBlockRegistry().keySet()) { - RegistrationHelper.registerFluidRender(name); - } - } -} diff --git a/src/main/java/com/mcmoddev/baseminerals/util/Config.java b/src/main/java/com/mcmoddev/baseminerals/util/Config.java deleted file mode 100644 index d3b6222..0000000 --- a/src/main/java/com/mcmoddev/baseminerals/util/Config.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.mcmoddev.baseminerals.util; - -import java.io.File; - -import com.mcmoddev.baseminerals.BaseMinerals; -import com.mcmoddev.baseminerals.data.MaterialNames; -import com.mcmoddev.lib.registry.CrusherRecipeRegistry; -import com.mcmoddev.lib.util.ConfigBase; - -import net.minecraftforge.common.MinecraftForge; -import net.minecraftforge.common.config.Configuration; -import net.minecraftforge.fml.client.event.ConfigChangedEvent; -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; - -/** - * @author Jasmine Iwanek - * - */ -public class Config extends ConfigBase { - - private static Configuration configuration; - private static final String CONFIG_FILE = "config/BaseMinerals.cfg"; - private static final String MATERIALS_CAT = "Minerals"; - - @SubscribeEvent - public void onConfigChange(ConfigChangedEvent.OnConfigChangedEvent e) { - if (e.getModID().equals(BaseMinerals.MODID)) { - init(); - } - } - - public static void init() { - if (configuration == null) { - configuration = new Configuration(new File(CONFIG_FILE)); - MinecraftForge.EVENT_BUS.register(new Config()); - } - - // MATERIALS - Options.materialEnabled(MaterialNames.CHARCOAL, configuration.getBoolean("EnableCharcoal", MATERIALS_CAT, true, - "Enable Charcoal Items and Materials")); - Options.materialEnabled(MaterialNames.LITHIUM, configuration.getBoolean("EnableLithium", MATERIALS_CAT, true, - "Enable Lithium Items and Materials")); - Options.materialEnabled(MaterialNames.NITER, configuration.getBoolean("EnableNiter", MATERIALS_CAT, true, - "Enable Niter Items and Materials")); - Options.materialEnabled(MaterialNames.PHOSPHORUS, configuration.getBoolean("EnablePhosphorus", MATERIALS_CAT, true, - "Enable Phosphorus Items and Materials")); - Options.materialEnabled(MaterialNames.POTASH, configuration.getBoolean("EnablePotash", MATERIALS_CAT, true, - "Enable Potash Items and Materials")); - Options.materialEnabled(MaterialNames.SALT, configuration.getBoolean("EnableSalt", MATERIALS_CAT, true, - "Enable Salt Items and Materials")); - Options.materialEnabled(MaterialNames.SALTPETER, configuration.getBoolean("EnableSaltpeter", MATERIALS_CAT, true, - "Enable Saltpeter Items and Materials")); - Options.materialEnabled(MaterialNames.SILICON, configuration.getBoolean("EnableSilicon", MATERIALS_CAT, true, - "Enable Silicon Items and Materials")); - Options.materialEnabled(MaterialNames.SULFUR, configuration.getBoolean("EnableSulfur", MATERIALS_CAT, true, - "Enable Sulfur Items and Materials")); - - if (configuration.hasChanged()) { - configuration.save(); - } - } - - public static void postInit() { - CrusherRecipeRegistry.getInstance().clearCache(); - } -} diff --git a/src/main/java/com/mcmoddev/orespawn/BaseMineralsOreSpawn.java b/src/main/java/com/mcmoddev/orespawn/BaseMineralsOreSpawn.java deleted file mode 100644 index 7b372ec..0000000 --- a/src/main/java/com/mcmoddev/orespawn/BaseMineralsOreSpawn.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.mcmoddev.orespawn; - -import com.mcmoddev.baseminerals.BaseMinerals; -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 = BaseMinerals.MODID, resourcePath = SharedStrings.ORESPAWN_MODID) -public class BaseMineralsOreSpawn implements IOreSpawnPlugin { - - @Override - public void register(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/com/mcmoddev/baseminerals/BaseMinerals.java b/src/main/java/zone/moddev/mc/baseminerals/BaseMinerals.java similarity index 67% rename from src/main/java/com/mcmoddev/baseminerals/BaseMinerals.java rename to src/main/java/zone/moddev/mc/baseminerals/BaseMinerals.java index 3f6cba4..df52ef7 100644 --- a/src/main/java/com/mcmoddev/baseminerals/BaseMinerals.java +++ b/src/main/java/zone/moddev/mc/baseminerals/BaseMinerals.java @@ -1,17 +1,17 @@ -package com.mcmoddev.baseminerals; +package zone.moddev.mc.baseminerals; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import com.mcmoddev.baseminerals.proxy.CommonProxy; +import zone.moddev.mc.baseminerals.proxy.CommonProxy; import com.mcmoddev.lib.data.SharedStrings; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod.EventHandler; import net.minecraftforge.fml.common.Mod.Instance; 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.FMLFingerprintViolationEvent; import net.minecraftforge.fml.common.event.FMLMissingMappingsEvent; import net.minecraftforge.fml.common.event.FMLPostInitializationEvent; import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; @@ -26,9 +26,9 @@ modid = BaseMinerals.MODID, name = BaseMinerals.NAME, version = BaseMinerals.VERSION, - dependencies = "required-after:forge@[13.20.1.2386,);required-after:basemetals;before:appliedenergistics2;before:buildingbricks", - acceptedMinecraftVersions = "[1.11.2,)", - certificateFingerprint = "@FINGERPRINT@", + dependencies = "required-after:forge@[13.20.1.2588,);required-after:basemetals@[2.5.1.111021,2.6.0);required-after:orespawn@[4.1.6.111021,5.0.0);after:tconstruct;after:enderio;before:buildingbricks", + acceptedMinecraftVersions = "[1.11.2]", + guiFactory = "zone.moddev.mc.baseminerals.client.BaseMineralsGuiFactory", updateJSON = BaseMinerals.UPDATEJSON) public class BaseMinerals { @@ -46,11 +46,11 @@ public class BaseMinerals { * increased whenever a change is made that has the potential to break * compatibility with other mods that depend on this one. */ - protected static final String VERSION = "2.5.0-beta4"; + public static final String VERSION = "2.5.1.111021"; - protected static final String UPDATEJSON = SharedStrings.UPDATE_JSON_URL + "BaseMinerals/master/update.json"; + protected static final String UPDATEJSON = "https://raw.githubusercontent.com/MinecraftModDevelopmentMods/BaseMinerals/master-1.11.2/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.baseminerals.proxy."; @SidedProxy(clientSide = PROXY_BASE + SharedStrings.CLIENTPROXY, serverSide = PROXY_BASE + SharedStrings.SERVERPROXY) @@ -58,10 +58,11 @@ public class BaseMinerals { public static final Logger logger = LogManager.getFormatterLogger(BaseMinerals.MODID); - @EventHandler - public void onFingerprintViolation(FMLFingerprintViolationEvent event) { - logger.warn(SharedStrings.INVALID_FINGERPRINT); - } + + /** Kept for old binary callers; unsigned jars do not subscribe to this event. */ + public void onFingerprintViolation(FMLFingerprintViolationEvent event) { + logger.warn(SharedStrings.INVALID_FINGERPRINT); + } @EventHandler public static void preInit(FMLPreInitializationEvent event) { @@ -80,7 +81,6 @@ public static void postInit(FMLPostInitializationEvent event) { @EventHandler public static void onRemap(FMLMissingMappingsEvent event) { - // we are not using this right now... - // proxy.onRemap(event); + proxy.onRemap(event); } } diff --git a/src/main/java/zone/moddev/mc/baseminerals/block/OverlayOreBlock.java b/src/main/java/zone/moddev/mc/baseminerals/block/OverlayOreBlock.java new file mode 100644 index 0000000..11827ed --- /dev/null +++ b/src/main/java/zone/moddev/mc/baseminerals/block/OverlayOreBlock.java @@ -0,0 +1,20 @@ +package zone.moddev.mc.baseminerals.block; + +import com.mcmoddev.lib.block.BlockMMDOre; +import com.mcmoddev.lib.material.MMDMaterial; +import net.minecraft.util.BlockRenderLayer; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +/** Uses the shared ore behaviour with alpha testing for overlay models. */ +public final class OverlayOreBlock extends BlockMMDOre { + public OverlayOreBlock(MMDMaterial material) { + super(material); + } + + @Override + @SideOnly(Side.CLIENT) + public BlockRenderLayer getBlockLayer() { + return BlockRenderLayer.CUTOUT_MIPPED; + } +} diff --git a/src/main/java/zone/moddev/mc/baseminerals/client/BaseMineralsConfigScreen.java b/src/main/java/zone/moddev/mc/baseminerals/client/BaseMineralsConfigScreen.java new file mode 100644 index 0000000..a136913 --- /dev/null +++ b/src/main/java/zone/moddev/mc/baseminerals/client/BaseMineralsConfigScreen.java @@ -0,0 +1,28 @@ +package zone.moddev.mc.baseminerals.client; + +import java.util.ArrayList; +import java.util.List; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.resources.I18n; +import net.minecraftforge.common.config.ConfigElement; +import net.minecraftforge.fml.client.config.GuiConfig; +import net.minecraftforge.fml.client.config.IConfigElement; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import zone.moddev.mc.baseminerals.BaseMinerals; +import zone.moddev.mc.baseminerals.util.Config; + +/** One parent-aware screen for Forge's mod list and OreSpawn's child-mod list. */ +@SideOnly(Side.CLIENT) +public final class BaseMineralsConfigScreen extends GuiConfig { + public BaseMineralsConfigScreen(final GuiScreen parent) { + super(parent, elements(), + BaseMinerals.MODID, false, true, I18n.format("config.baseminerals.title"), + I18n.format("config.baseminerals.description")); + } + private static List elements() { + final List elements = new ArrayList<>( + new ConfigElement(Config.getConfiguration().getCategory("Minerals")).getChildElements()); + return elements; + } +} diff --git a/src/main/java/zone/moddev/mc/baseminerals/client/BaseMineralsGuiFactory.java b/src/main/java/zone/moddev/mc/baseminerals/client/BaseMineralsGuiFactory.java new file mode 100644 index 0000000..e684c40 --- /dev/null +++ b/src/main/java/zone/moddev/mc/baseminerals/client/BaseMineralsGuiFactory.java @@ -0,0 +1,20 @@ +package zone.moddev.mc.baseminerals.client; + +import java.util.Collections; +import java.util.Set; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiScreen; +import net.minecraftforge.fml.client.IModGuiFactory; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +/** Forge 1.11's configuration factory; never loaded by a dedicated server. */ +@SideOnly(Side.CLIENT) +public final class BaseMineralsGuiFactory implements IModGuiFactory { + @Override public void initialize(final Minecraft minecraft) { } + @Override public Class mainConfigGuiClass() { return BaseMineralsConfigScreen.class; } + @Override public boolean hasConfigGui() { return true; } + @Override public GuiScreen createConfigGui(final GuiScreen parent) { return new BaseMineralsConfigScreen(parent); } + @Override public Set runtimeGuiCategories() { return Collections.emptySet(); } + @Override public RuntimeOptionGuiHandler getHandlerFor(final RuntimeOptionCategoryElement element) { return null; } +} diff --git a/src/main/java/zone/moddev/mc/baseminerals/compat/BaseMineralsLoadingPlugin.java b/src/main/java/zone/moddev/mc/baseminerals/compat/BaseMineralsLoadingPlugin.java new file mode 100644 index 0000000..aed41ce --- /dev/null +++ b/src/main/java/zone/moddev/mc/baseminerals/compat/BaseMineralsLoadingPlugin.java @@ -0,0 +1,19 @@ +package zone.moddev.mc.baseminerals.compat; + +import java.util.Map; +import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin; + +/** Relinks old add-on names without installing a second Base Metals transformer. */ +@IFMLLoadingPlugin.Name("BaseMinerals") +@IFMLLoadingPlugin.MCVersion("1.11.2") +@IFMLLoadingPlugin.SortingIndex(1002) +@IFMLLoadingPlugin.TransformerExclusions("zone.moddev.mc.baseminerals.compat.") +public final class BaseMineralsLoadingPlugin implements IFMLLoadingPlugin { + @Override public String[] getASMTransformerClass() { + return new String[] { "zone.moddev.mc.baseminerals.compat.LegacyNamespaceTransformer" }; + } + @Override public String getModContainerClass() { return null; } + @Override public String getSetupClass() { return null; } + @Override public void injectData(final Map data) { } + @Override public String getAccessTransformerClass() { return null; } +} diff --git a/src/main/java/zone/moddev/mc/baseminerals/compat/LegacyNamespaceTransformer.java b/src/main/java/zone/moddev/mc/baseminerals/compat/LegacyNamespaceTransformer.java new file mode 100644 index 0000000..848c833 --- /dev/null +++ b/src/main/java/zone/moddev/mc/baseminerals/compat/LegacyNamespaceTransformer.java @@ -0,0 +1,89 @@ +package zone.moddev.mc.baseminerals.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 Minerals. */ +public final class LegacyNamespaceTransformer implements IClassTransformer { + + private static final String OLD = "com/mcmoddev/baseminerals/"; + private static final String CURRENT = "zone/moddev/mc/baseminerals/"; + 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.baseminerals.") + || !(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/baseminerals-legacy-api.list"); + if (stream == null) throw new IllegalStateException("Missing Base Minerals 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 Minerals 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/baseminerals/data/AchievementNames.java b/src/main/java/zone/moddev/mc/baseminerals/data/AchievementNames.java similarity index 91% rename from src/main/java/com/mcmoddev/baseminerals/data/AchievementNames.java rename to src/main/java/zone/moddev/mc/baseminerals/data/AchievementNames.java index 84227ce..b022aab 100644 --- a/src/main/java/com/mcmoddev/baseminerals/data/AchievementNames.java +++ b/src/main/java/zone/moddev/mc/baseminerals/data/AchievementNames.java @@ -1,4 +1,4 @@ -package com.mcmoddev.baseminerals.data; +package zone.moddev.mc.baseminerals.data; import com.mcmoddev.lib.data.SharedStrings; diff --git a/src/main/java/com/mcmoddev/baseminerals/data/MaterialNames.java b/src/main/java/zone/moddev/mc/baseminerals/data/MaterialNames.java old mode 100755 new mode 100644 similarity index 91% rename from src/main/java/com/mcmoddev/baseminerals/data/MaterialNames.java rename to src/main/java/zone/moddev/mc/baseminerals/data/MaterialNames.java index e931689..4f3d6c4 --- a/src/main/java/com/mcmoddev/baseminerals/data/MaterialNames.java +++ b/src/main/java/zone/moddev/mc/baseminerals/data/MaterialNames.java @@ -1,20 +1,20 @@ -package com.mcmoddev.baseminerals.data; - -import com.mcmoddev.lib.data.SharedStrings; - -public final class MaterialNames { - - public static final String SULFUR = "sulfur"; - public static final String SILICON = "silicon"; - public static final String SALTPETER = "saltpeter"; - public static final String SALT = "salt"; - public static final String POTASH = "potash"; - public static final String NITER = "niter"; - public static final String PHOSPHORUS = "phosphorus"; - public static final String LITHIUM = "lithium"; - public static final String CHARCOAL = "charcoal"; // Vanilla - - private MaterialNames() { - throw new IllegalAccessError(SharedStrings.NOT_INSTANTIABLE); - } -} +package zone.moddev.mc.baseminerals.data; + +import com.mcmoddev.lib.data.SharedStrings; + +public final class MaterialNames { + + public static final String SULFUR = "sulfur"; + public static final String SILICON = "silicon"; + public static final String SALTPETER = "saltpeter"; + public static final String SALT = "salt"; + public static final String POTASH = "potash"; + public static final String NITER = "niter"; + public static final String PHOSPHORUS = "phosphorus"; + public static final String LITHIUM = "lithium"; + public static final String CHARCOAL = "charcoal"; // Vanilla + + private MaterialNames() { + throw new IllegalAccessError(SharedStrings.NOT_INSTANTIABLE); + } +} diff --git a/src/main/java/com/mcmoddev/baseminerals/init/Achievements.java b/src/main/java/zone/moddev/mc/baseminerals/init/Achievements.java similarity index 75% rename from src/main/java/com/mcmoddev/baseminerals/init/Achievements.java rename to src/main/java/zone/moddev/mc/baseminerals/init/Achievements.java index 1ccad69..ae4df0c 100644 --- a/src/main/java/com/mcmoddev/baseminerals/init/Achievements.java +++ b/src/main/java/zone/moddev/mc/baseminerals/init/Achievements.java @@ -1,8 +1,8 @@ -package com.mcmoddev.baseminerals.init; +package zone.moddev.mc.baseminerals.init; import com.mcmoddev.lib.util.ConfigBase.Options; -import com.mcmoddev.baseminerals.data.AchievementNames; -import com.mcmoddev.baseminerals.data.MaterialNames; +import zone.moddev.mc.baseminerals.data.AchievementNames; +import zone.moddev.mc.baseminerals.data.MaterialNames; import com.mcmoddev.lib.data.Names; import com.mcmoddev.lib.data.SharedStrings; @@ -24,7 +24,7 @@ public static void init() { AchievementPage page = new AchievementPage(Loader.instance().activeModContainer().getModId()); AchievementPage.registerAchievementPage(page); - if (Materials.hasMaterial(MaterialNames.SILICON)) { + if (Materials.hasMaterial(MaterialNames.SILICON) && Materials.getMaterialByName(MaterialNames.SILICON).hasItem(Names.INGOT)) { makeAchievement(AchievementNames.SILICON_MAKER, getAchievementByName("metallurgy"), 0, 0, Materials.getMaterialByName(MaterialNames.SILICON).getItem(Names.INGOT), page); } // TODO: Add lithium diff --git a/src/main/java/zone/moddev/mc/baseminerals/init/Blocks.java b/src/main/java/zone/moddev/mc/baseminerals/init/Blocks.java new file mode 100644 index 0000000..f2dddf8 --- /dev/null +++ b/src/main/java/zone/moddev/mc/baseminerals/init/Blocks.java @@ -0,0 +1,62 @@ +package zone.moddev.mc.baseminerals.init; + +import java.util.Arrays; +import java.util.List; + +import zone.moddev.mc.baseminerals.block.OverlayOreBlock; +import zone.moddev.mc.baseminerals.data.MaterialNames; +import com.mcmoddev.lib.data.Names; +import com.mcmoddev.lib.data.SharedStrings; +import com.mcmoddev.lib.init.Materials; +import com.mcmoddev.lib.material.MMDMaterial; +import com.mcmoddev.lib.util.Oredicts; +import net.minecraft.block.Block; + +/** + * This class initializes all blocks in Base Minerals. + * + * @author Jasmine Iwanek + * + */ +public class Blocks extends com.mcmoddev.lib.init.Blocks { + + protected Blocks() { + throw new IllegalAccessError(SharedStrings.NOT_INSTANTIABLE); + } + + /** + * + */ + public static void init() { +/* + if (zone.moddev.mc.baseminerals.util.Config.isEnabled(MaterialNames.CHARCOAL) && Materials.hasMaterial(MaterialNames.CHARCOAL)) { + create(Names.BLOCK, Materials.getMaterialByName(MaterialNames.CHARCOAL)); + } +*/ + final List materials = Arrays.asList(MaterialNames.CHARCOAL, MaterialNames.LITHIUM, MaterialNames.NITER, + MaterialNames.PHOSPHORUS, MaterialNames.POTASH, MaterialNames.SALT, MaterialNames.SALTPETER, + MaterialNames.SILICON, MaterialNames.SULFUR); + + materials.stream().filter(zone.moddev.mc.baseminerals.util.Config::isEnabled).filter(Materials::hasMaterial).forEach(materialName -> { + final MMDMaterial material = Materials.getMaterialByName(materialName); + + create(Names.BLOCK, material); + createOre(material); + }); +/* + if (zone.moddev.mc.baseminerals.util.Config.isEnabled(MaterialNames.SILICON) && Materials.hasMaterial(MaterialNames.SILICON)) { + create(Names.BLOCK, Materials.getMaterialByName(MaterialNames.SILICON)); + } +*/ + } + + private static void createOre(MMDMaterial material) { + if (!material.hasOre() || material.hasBlock(Names.ORE) || !isNameEnabled(Names.ORE)) { + return; + } + final Block ore = addBlock(new OverlayOreBlock(material), Names.ORE, material, + ItemGroups.getTab(SharedStrings.TAB_BLOCKS)); + material.addNewBlock(Names.ORE.toString(), ore); + Oredicts.registerOre("ore" + material.getCapitalizedName(), ore); + } +} diff --git a/src/main/java/com/mcmoddev/baseminerals/init/Fluids.java b/src/main/java/zone/moddev/mc/baseminerals/init/Fluids.java similarity index 88% rename from src/main/java/com/mcmoddev/baseminerals/init/Fluids.java rename to src/main/java/zone/moddev/mc/baseminerals/init/Fluids.java index f76e11e..2368a1d 100644 --- a/src/main/java/com/mcmoddev/baseminerals/init/Fluids.java +++ b/src/main/java/zone/moddev/mc/baseminerals/init/Fluids.java @@ -1,9 +1,9 @@ -package com.mcmoddev.baseminerals.init; +package zone.moddev.mc.baseminerals.init; import java.util.Arrays; import java.util.List; -import com.mcmoddev.baseminerals.data.MaterialNames; +import zone.moddev.mc.baseminerals.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/baseminerals/init/ItemGroups.java b/src/main/java/zone/moddev/mc/baseminerals/init/ItemGroups.java similarity index 92% rename from src/main/java/com/mcmoddev/baseminerals/init/ItemGroups.java rename to src/main/java/zone/moddev/mc/baseminerals/init/ItemGroups.java index 41e2f7d..90b6b40 100644 --- a/src/main/java/com/mcmoddev/baseminerals/init/ItemGroups.java +++ b/src/main/java/zone/moddev/mc/baseminerals/init/ItemGroups.java @@ -1,6 +1,6 @@ -package com.mcmoddev.baseminerals.init; +package zone.moddev.mc.baseminerals.init; -import com.mcmoddev.baseminerals.data.MaterialNames; +import zone.moddev.mc.baseminerals.data.MaterialNames; import com.mcmoddev.lib.data.Names; import com.mcmoddev.lib.data.SharedStrings; import com.mcmoddev.lib.init.Materials; @@ -8,7 +8,6 @@ import com.mcmoddev.lib.material.MMDMaterial; import net.minecraft.item.Item; -import net.minecraft.item.ItemStack; /** * This class initializes all item groups in Base Minerals. @@ -32,7 +31,7 @@ private ItemGroups() { */ public static void init() { } - + public static void setupIcons(String materialName) { if (Materials.hasMaterial(materialName)) { final MMDMaterial material = Materials.getMaterialByName(materialName); diff --git a/src/main/java/com/mcmoddev/baseminerals/init/Items.java b/src/main/java/zone/moddev/mc/baseminerals/init/Items.java similarity index 75% rename from src/main/java/com/mcmoddev/baseminerals/init/Items.java rename to src/main/java/zone/moddev/mc/baseminerals/init/Items.java index 702135b..999e27b 100644 --- a/src/main/java/com/mcmoddev/baseminerals/init/Items.java +++ b/src/main/java/zone/moddev/mc/baseminerals/init/Items.java @@ -1,9 +1,9 @@ -package com.mcmoddev.baseminerals.init; +package zone.moddev.mc.baseminerals.init; import java.util.Arrays; import java.util.List; -import com.mcmoddev.baseminerals.data.MaterialNames; +import zone.moddev.mc.baseminerals.data.MaterialNames; import com.mcmoddev.lib.data.Names; import com.mcmoddev.lib.data.SharedStrings; import com.mcmoddev.lib.fuels.FuelRegistry; @@ -28,7 +28,7 @@ protected Items() { */ public static void init() { - if (Materials.hasMaterial(MaterialNames.CHARCOAL)) { + if (zone.moddev.mc.baseminerals.util.Config.isEnabled(MaterialNames.CHARCOAL) && Materials.hasMaterial(MaterialNames.CHARCOAL)) { final MMDMaterial charcoal = Materials.getMaterialByName(MaterialNames.CHARCOAL); create(Names.NUGGET, charcoal); // Special create(Names.POWDER, charcoal); @@ -40,7 +40,7 @@ public static void init() { FuelRegistry.addFuel(Oredicts.BLOCK_CHARCOAL, 16000); } - if (Materials.hasMaterial(MaterialNames.LITHIUM)) { + if (zone.moddev.mc.baseminerals.util.Config.isEnabled(MaterialNames.LITHIUM) && Materials.hasMaterial(MaterialNames.LITHIUM)) { final MMDMaterial lithium = Materials.getMaterialByName(MaterialNames.LITHIUM); create(Names.INGOT, lithium); create(Names.NUGGET, lithium); @@ -52,14 +52,14 @@ public static void init() { MaterialNames.POTASH, MaterialNames.SALT, MaterialNames.SALTPETER, MaterialNames.SULFUR); - materials.stream().filter(Materials::hasMaterial).forEach(materialName -> { + materials.stream().filter(zone.moddev.mc.baseminerals.util.Config::isEnabled).filter(Materials::hasMaterial).forEach(materialName -> { final MMDMaterial material = Materials.getMaterialByName(materialName); create(Names.POWDER, material); create(Names.SMALLPOWDER, material); }); - if (Materials.hasMaterial(MaterialNames.SILICON)) { + if (zone.moddev.mc.baseminerals.util.Config.isEnabled(MaterialNames.SILICON) && Materials.hasMaterial(MaterialNames.SILICON)) { final MMDMaterial silicon = Materials.getMaterialByName(MaterialNames.SILICON); create(Names.BLEND, silicon); create(Names.INGOT, silicon); diff --git a/src/main/java/com/mcmoddev/baseminerals/init/Materials.java b/src/main/java/zone/moddev/mc/baseminerals/init/Materials.java similarity index 87% rename from src/main/java/com/mcmoddev/baseminerals/init/Materials.java rename to src/main/java/zone/moddev/mc/baseminerals/init/Materials.java index fbbe39b..8657dcb 100644 --- a/src/main/java/com/mcmoddev/baseminerals/init/Materials.java +++ b/src/main/java/zone/moddev/mc/baseminerals/init/Materials.java @@ -1,11 +1,11 @@ -package com.mcmoddev.baseminerals.init; +package zone.moddev.mc.baseminerals.init; import java.util.Arrays; import java.util.List; import javax.annotation.Nonnull; -import com.mcmoddev.baseminerals.data.MaterialNames; +import zone.moddev.mc.baseminerals.data.MaterialNames; import com.mcmoddev.lib.data.SharedStrings; import com.mcmoddev.lib.material.MMDMaterial.MaterialType; import com.mcmoddev.lib.util.ConfigBase.Options; @@ -31,14 +31,14 @@ public static void init() { MaterialNames.PHOSPHORUS, MaterialNames.POTASH, MaterialNames.SALT, MaterialNames.SALTPETER, MaterialNames.SULFUR); - if (Options.isMaterialEnabled(MaterialNames.CHARCOAL)) { + if (zone.moddev.mc.baseminerals.util.Config.isEnabled(MaterialNames.CHARCOAL) && !hasMaterial(MaterialNames.CHARCOAL)) { createOrelessMaterial(MaterialNames.CHARCOAL, MaterialType.MINERAL, 5, 2, 0, 0xFF000000); } - materials.stream().filter(Options::isMaterialEnabled).forEach(name -> createMaterial(name, getMaterialType(name), + materials.stream().filter(zone.moddev.mc.baseminerals.util.Config::isEnabled).forEach(name -> createMaterial(name, getMaterialType(name), getHardness(name), getStrength(name), getMagic(name), getColor(name))); - if (Options.isMaterialEnabled(MaterialNames.SILICON)) { + if (zone.moddev.mc.baseminerals.util.Config.isEnabled(MaterialNames.SILICON)) { createAlloyMaterial(MaterialNames.SILICON, MaterialType.METAL, 8, 8, 4.5, 0xFFC5C8C1); } } diff --git a/src/main/java/com/mcmoddev/baseminerals/init/Recipes.java b/src/main/java/zone/moddev/mc/baseminerals/init/Recipes.java similarity index 94% rename from src/main/java/com/mcmoddev/baseminerals/init/Recipes.java rename to src/main/java/zone/moddev/mc/baseminerals/init/Recipes.java index 8123cb6..5bf777f 100644 --- a/src/main/java/com/mcmoddev/baseminerals/init/Recipes.java +++ b/src/main/java/zone/moddev/mc/baseminerals/init/Recipes.java @@ -1,7 +1,7 @@ -package com.mcmoddev.baseminerals.init; +package zone.moddev.mc.baseminerals.init; -import com.mcmoddev.baseminerals.data.MaterialNames; -import com.mcmoddev.baseminerals.init.Materials; +import zone.moddev.mc.baseminerals.data.MaterialNames; +import zone.moddev.mc.baseminerals.init.Materials; import com.mcmoddev.lib.data.Names; import com.mcmoddev.lib.data.SharedStrings; import com.mcmoddev.lib.material.MMDMaterial; diff --git a/src/main/java/com/mcmoddev/baseminerals/init/VillagerTrades.java b/src/main/java/zone/moddev/mc/baseminerals/init/VillagerTrades.java similarity index 88% rename from src/main/java/com/mcmoddev/baseminerals/init/VillagerTrades.java rename to src/main/java/zone/moddev/mc/baseminerals/init/VillagerTrades.java index 0723e2f..c5eb213 100644 --- a/src/main/java/com/mcmoddev/baseminerals/init/VillagerTrades.java +++ b/src/main/java/zone/moddev/mc/baseminerals/init/VillagerTrades.java @@ -1,4 +1,4 @@ -package com.mcmoddev.baseminerals.init; +package zone.moddev.mc.baseminerals.init; import com.mcmoddev.lib.data.SharedStrings; diff --git a/src/main/java/com/mcmoddev/baseminerals/integration/plugins/DenseOres.java b/src/main/java/zone/moddev/mc/baseminerals/integration/plugins/DenseOres.java similarity index 89% rename from src/main/java/com/mcmoddev/baseminerals/integration/plugins/DenseOres.java rename to src/main/java/zone/moddev/mc/baseminerals/integration/plugins/DenseOres.java index 029c69c..cce52f8 100644 --- a/src/main/java/com/mcmoddev/baseminerals/integration/plugins/DenseOres.java +++ b/src/main/java/zone/moddev/mc/baseminerals/integration/plugins/DenseOres.java @@ -1,7 +1,7 @@ -package com.mcmoddev.baseminerals.integration.plugins; +package zone.moddev.mc.baseminerals.integration.plugins; -import com.mcmoddev.baseminerals.BaseMinerals; -import com.mcmoddev.baseminerals.data.MaterialNames; +import zone.moddev.mc.baseminerals.BaseMinerals; +import zone.moddev.mc.baseminerals.data.MaterialNames; import com.mcmoddev.lib.util.ConfigBase.Options; import com.mcmoddev.lib.init.Materials; import com.mcmoddev.lib.integration.IIntegration; @@ -27,7 +27,7 @@ public void init() { /** * Register all ores that are currently known by the materials registry - * + * * @author Daniel Hazelton <dshadowwolf@gmail.com> */ private static void registerOres() { diff --git a/src/main/java/com/mcmoddev/baseminerals/integration/plugins/EnderIO.java b/src/main/java/zone/moddev/mc/baseminerals/integration/plugins/EnderIO.java similarity index 85% rename from src/main/java/com/mcmoddev/baseminerals/integration/plugins/EnderIO.java rename to src/main/java/zone/moddev/mc/baseminerals/integration/plugins/EnderIO.java index e3322f7..867c05a 100644 --- a/src/main/java/com/mcmoddev/baseminerals/integration/plugins/EnderIO.java +++ b/src/main/java/zone/moddev/mc/baseminerals/integration/plugins/EnderIO.java @@ -1,7 +1,7 @@ -package com.mcmoddev.baseminerals.integration.plugins; +package zone.moddev.mc.baseminerals.integration.plugins; -import com.mcmoddev.baseminerals.BaseMinerals; -import com.mcmoddev.baseminerals.data.MaterialNames; +import zone.moddev.mc.baseminerals.BaseMinerals; +import zone.moddev.mc.baseminerals.data.MaterialNames; import com.mcmoddev.lib.integration.MMDPlugin; import com.mcmoddev.lib.util.ConfigBase.Options; import com.mcmoddev.lib.integration.IIntegration; diff --git a/src/main/java/com/mcmoddev/baseminerals/integration/plugins/IC2.java b/src/main/java/zone/moddev/mc/baseminerals/integration/plugins/IC2.java similarity index 79% rename from src/main/java/com/mcmoddev/baseminerals/integration/plugins/IC2.java rename to src/main/java/zone/moddev/mc/baseminerals/integration/plugins/IC2.java index 04a2544..ce03990 100644 --- a/src/main/java/com/mcmoddev/baseminerals/integration/plugins/IC2.java +++ b/src/main/java/zone/moddev/mc/baseminerals/integration/plugins/IC2.java @@ -1,6 +1,6 @@ -package com.mcmoddev.baseminerals.integration.plugins; +package zone.moddev.mc.baseminerals.integration.plugins; -import com.mcmoddev.baseminerals.BaseMinerals; +import zone.moddev.mc.baseminerals.BaseMinerals; import com.mcmoddev.lib.integration.MMDPlugin; import com.mcmoddev.lib.util.ConfigBase.Options; import com.mcmoddev.lib.integration.IIntegration; diff --git a/src/main/java/com/mcmoddev/baseminerals/integration/plugins/Mekanism.java b/src/main/java/zone/moddev/mc/baseminerals/integration/plugins/Mekanism.java similarity index 80% rename from src/main/java/com/mcmoddev/baseminerals/integration/plugins/Mekanism.java rename to src/main/java/zone/moddev/mc/baseminerals/integration/plugins/Mekanism.java index 6385e23..296115a 100644 --- a/src/main/java/com/mcmoddev/baseminerals/integration/plugins/Mekanism.java +++ b/src/main/java/zone/moddev/mc/baseminerals/integration/plugins/Mekanism.java @@ -1,6 +1,6 @@ -package com.mcmoddev.baseminerals.integration.plugins; +package zone.moddev.mc.baseminerals.integration.plugins; -import com.mcmoddev.baseminerals.BaseMinerals; +import zone.moddev.mc.baseminerals.BaseMinerals; import com.mcmoddev.lib.integration.MMDPlugin; import com.mcmoddev.lib.util.ConfigBase.Options; import com.mcmoddev.lib.integration.IIntegration; diff --git a/src/main/java/com/mcmoddev/baseminerals/integration/plugins/Thaumcraft.java b/src/main/java/zone/moddev/mc/baseminerals/integration/plugins/Thaumcraft.java similarity index 80% rename from src/main/java/com/mcmoddev/baseminerals/integration/plugins/Thaumcraft.java rename to src/main/java/zone/moddev/mc/baseminerals/integration/plugins/Thaumcraft.java index 725e07d..4aa6cc0 100644 --- a/src/main/java/com/mcmoddev/baseminerals/integration/plugins/Thaumcraft.java +++ b/src/main/java/zone/moddev/mc/baseminerals/integration/plugins/Thaumcraft.java @@ -1,6 +1,6 @@ -package com.mcmoddev.baseminerals.integration.plugins; +package zone.moddev.mc.baseminerals.integration.plugins; -import com.mcmoddev.baseminerals.BaseMinerals; +import zone.moddev.mc.baseminerals.BaseMinerals; import com.mcmoddev.lib.integration.MMDPlugin; import com.mcmoddev.lib.util.ConfigBase.Options; import com.mcmoddev.lib.integration.IIntegration; diff --git a/src/main/java/zone/moddev/mc/baseminerals/integration/plugins/TinkersConstruct.java b/src/main/java/zone/moddev/mc/baseminerals/integration/plugins/TinkersConstruct.java new file mode 100644 index 0000000..94ae6d3 --- /dev/null +++ b/src/main/java/zone/moddev/mc/baseminerals/integration/plugins/TinkersConstruct.java @@ -0,0 +1,55 @@ +package zone.moddev.mc.baseminerals.integration.plugins; + +import zone.moddev.mc.baseminerals.BaseMinerals; +import zone.moddev.mc.baseminerals.data.MaterialNames; +import zone.moddev.mc.baseminerals.init.Materials; +import com.mcmoddev.lib.data.Names; +import com.mcmoddev.lib.integration.MMDPlugin; +import com.mcmoddev.lib.integration.IIntegration; +import com.mcmoddev.lib.material.MMDMaterial; +import com.mcmoddev.lib.util.ConfigBase.Options; +import net.minecraft.item.ItemStack; +import net.minecraftforge.fluids.Fluid; +import slimeknights.tconstruct.library.TinkerRegistry; + +/** + * + * @author Jasmine Iwanek + * + */ +@MMDPlugin(addonId = BaseMinerals.MODID, pluginId = TinkersConstruct.PLUGIN_MODID) +public class TinkersConstruct extends com.mcmoddev.lib.integration.plugins.TinkersConstructBase implements IIntegration { + + @Override + public void init() { + if (!Options.isModEnabled(PLUGIN_MODID)) { + return; + } + + registerMoltenForms(MaterialNames.LITHIUM); + registerMoltenForms(MaterialNames.SILICON); + } + + private static void registerMoltenForms(String name) { + if (!Materials.hasMaterial(name)) { + return; + } + final MMDMaterial material = Materials.getMaterialByName(name); + final Fluid fluid = material.getFluid(); + if (fluid == null) { + return; + } + final int ingotAmount = 144; + registerFluid(material, ingotAmount); + // Tinkers on 1.11 represents an empty table or basin with ItemStack.EMPTY. + if (material.hasBlock(Names.BLOCK)) { + TinkerRegistry.registerBasinCasting(material.getBlockItemStack(Names.BLOCK), ItemStack.EMPTY, fluid, ingotAmount * 9); + } + if (material.hasItem(Names.INGOT)) { + TinkerRegistry.registerTableCasting(material.getItemStack(Names.INGOT), ItemStack.EMPTY, fluid, ingotAmount); + } + if (material.hasItem(Names.NUGGET)) { + TinkerRegistry.registerTableCasting(material.getItemStack(Names.NUGGET), ItemStack.EMPTY, fluid, ingotAmount / 9); + } + } +} diff --git a/src/main/java/com/mcmoddev/baseminerals/integration/plugins/VeinMiner.java b/src/main/java/zone/moddev/mc/baseminerals/integration/plugins/VeinMiner.java similarity index 82% rename from src/main/java/com/mcmoddev/baseminerals/integration/plugins/VeinMiner.java rename to src/main/java/zone/moddev/mc/baseminerals/integration/plugins/VeinMiner.java index bf4bcfa..39f3d4a 100644 --- a/src/main/java/com/mcmoddev/baseminerals/integration/plugins/VeinMiner.java +++ b/src/main/java/zone/moddev/mc/baseminerals/integration/plugins/VeinMiner.java @@ -1,6 +1,6 @@ -package com.mcmoddev.baseminerals.integration.plugins; +package zone.moddev.mc.baseminerals.integration.plugins; -import com.mcmoddev.baseminerals.BaseMinerals; +import zone.moddev.mc.baseminerals.BaseMinerals; import com.mcmoddev.lib.integration.MMDPlugin; import com.mcmoddev.lib.util.ConfigBase.Options; import com.mcmoddev.lib.integration.IIntegration; diff --git a/src/main/java/zone/moddev/mc/baseminerals/proxy/ClientProxy.java b/src/main/java/zone/moddev/mc/baseminerals/proxy/ClientProxy.java new file mode 100644 index 0000000..ef76d70 --- /dev/null +++ b/src/main/java/zone/moddev/mc/baseminerals/proxy/ClientProxy.java @@ -0,0 +1,85 @@ +package zone.moddev.mc.baseminerals.proxy; + +import zone.moddev.mc.baseminerals.BaseMinerals; +import zone.moddev.mc.baseminerals.init.Blocks; +import zone.moddev.mc.baseminerals.init.Fluids; +import zone.moddev.mc.baseminerals.init.Items; + +import net.minecraft.block.*; +import net.minecraft.block.state.IBlockState; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.ItemModelMesher; +import net.minecraft.client.renderer.block.model.ModelBakery; +import net.minecraft.client.renderer.block.model.ModelResourceLocation; +import net.minecraft.client.renderer.block.statemap.StateMapperBase; +import net.minecraft.item.Item; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.client.model.ModelLoader; +import net.minecraftforge.fml.common.event.FMLInitializationEvent; +import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; + +/** + * Base Minerals Client Proxy + * + * @author Jasmine Iwanek + * + */ +public class ClientProxy extends CommonProxy { + + @Override + public void preInit(FMLPreInitializationEvent event) { + super.preInit(event); + for (final String name : Fluids.getFluidBlockRegistry().keySet()) { + final Block block = Fluids.getFluidBlockByName(name); + final Item item = Item.getItemFromBlock(block); + if (!item.getRegistryName().getResourceDomain().equals(BaseMinerals.MODID)) + continue; + final ModelResourceLocation fluidModelLocation = new ModelResourceLocation(item.getRegistryName().getResourceDomain() + ":" + name, "fluid"); + ModelBakery.registerItemVariants(item); + ModelLoader.setCustomMeshDefinition(item, stack -> fluidModelLocation); + ModelLoader.setCustomStateMapper(block, new StateMapperBase() { + @Override + protected ModelResourceLocation getModelResourceLocation(IBlockState state) { + return fluidModelLocation; + } + }); + } + } + + @Override + public void init(FMLInitializationEvent event) { + super.init(event); + zone.moddev.mc.orespawn.api.client.WorldSettingsExtensionRegistry.registerConfigScreen( + BaseMinerals.MODID, zone.moddev.mc.baseminerals.client.BaseMineralsConfigScreen::new); + + for (final String name : Items.getItemRegistry().keySet()) { + registerRenderOuter(Items.getItemByName(name)); + } + + for (final String name : Blocks.getBlockRegistry().keySet()) { + registerRenderOuter(Blocks.getBlockByName(name)); + } + } + + public void registerRenderOuter(Item item) { + if (item != null) { + registerRender(item, Items.getNameOfItem(item)); + } + } + + public void registerRenderOuter(Block block) { + if ((block instanceof BlockDoor) || (block instanceof BlockSlab)) + return; // do not add door blocks or slabs + + if (block != null) { + registerRender(Item.getItemFromBlock(block), Blocks.getNameOfBlock(block)); + } + } + + public void registerRender(Item item, String name) { + final ItemModelMesher itemModelMesher = Minecraft.getMinecraft().getRenderItem().getItemModelMesher(); + if (!item.getRegistryName().getResourceDomain().equals(BaseMinerals.MODID)) + return; + itemModelMesher.register(item, 0, new ModelResourceLocation(new ResourceLocation(item.getRegistryName().getResourceDomain(), name), "inventory")); + } +} diff --git a/src/main/java/com/mcmoddev/baseminerals/proxy/CommonProxy.java b/src/main/java/zone/moddev/mc/baseminerals/proxy/CommonProxy.java similarity index 67% rename from src/main/java/com/mcmoddev/baseminerals/proxy/CommonProxy.java rename to src/main/java/zone/moddev/mc/baseminerals/proxy/CommonProxy.java index 02bacac..5500d1d 100644 --- a/src/main/java/com/mcmoddev/baseminerals/proxy/CommonProxy.java +++ b/src/main/java/zone/moddev/mc/baseminerals/proxy/CommonProxy.java @@ -1,15 +1,15 @@ -package com.mcmoddev.baseminerals.proxy; +package zone.moddev.mc.baseminerals.proxy; import java.util.HashSet; -import com.mcmoddev.baseminerals.BaseMinerals; -import com.mcmoddev.baseminerals.data.MaterialNames; -import com.mcmoddev.baseminerals.init.*; -import com.mcmoddev.baseminerals.util.Config; -import com.mcmoddev.baseminerals.util.EventHandler; +import zone.moddev.mc.baseminerals.BaseMinerals; +import zone.moddev.mc.baseminerals.data.MaterialNames; +import zone.moddev.mc.baseminerals.init.*; +import zone.moddev.mc.baseminerals.util.Config; +import zone.moddev.mc.baseminerals.util.EventHandler; import com.mcmoddev.lib.data.SharedStrings; import com.mcmoddev.lib.integration.IntegrationManager; -import com.mcmoddev.lib.oregen.FallbackGenerator; + import com.mcmoddev.lib.util.ConfigBase.Options; import net.minecraftforge.common.MinecraftForge; @@ -36,16 +36,6 @@ public void preInit(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); - } - } Materials.init(); Fluids.init(); @@ -57,7 +47,6 @@ public void preInit(FMLPreInitializationEvent event) { IntegrationManager.INSTANCE.preInit(event); } - /* Right now this isn't needed, but might be at some point public void onRemap(FMLMissingMappingsEvent event) { for (final MissingMapping mapping : event.get()) { if (mapping.resourceLocation.getResourceDomain().equals(BaseMinerals.MODID)) { @@ -69,9 +58,9 @@ public void onRemap(FMLMissingMappingsEvent event) { } } } - */ public void init(FMLInitializationEvent event) { + zone.moddev.mc.baseminerals.worldgen.BaseMineralsWorldgenProvider.enqueue(); Recipes.init(); Achievements.init(); diff --git a/src/main/java/com/mcmoddev/baseminerals/proxy/ServerProxy.java b/src/main/java/zone/moddev/mc/baseminerals/proxy/ServerProxy.java similarity index 77% rename from src/main/java/com/mcmoddev/baseminerals/proxy/ServerProxy.java rename to src/main/java/zone/moddev/mc/baseminerals/proxy/ServerProxy.java index 27e8e98..ace93fa 100644 --- a/src/main/java/com/mcmoddev/baseminerals/proxy/ServerProxy.java +++ b/src/main/java/zone/moddev/mc/baseminerals/proxy/ServerProxy.java @@ -1,4 +1,4 @@ -package com.mcmoddev.baseminerals.proxy; +package zone.moddev.mc.baseminerals.proxy; /** * Base Minerals Server Proxy diff --git a/src/main/java/zone/moddev/mc/baseminerals/util/Config.java b/src/main/java/zone/moddev/mc/baseminerals/util/Config.java new file mode 100644 index 0000000..692137e --- /dev/null +++ b/src/main/java/zone/moddev/mc/baseminerals/util/Config.java @@ -0,0 +1,43 @@ +package zone.moddev.mc.baseminerals.util; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.common.config.Configuration; +import net.minecraftforge.common.config.Property; +import net.minecraftforge.fml.client.event.ConfigChangedEvent; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import com.mcmoddev.lib.registry.CrusherRecipeRegistry; +import com.mcmoddev.lib.util.ConfigBase.Options; + +/** Persist requested settings, retaining the active registration choices until restart. */ +public class Config extends com.mcmoddev.lib.util.ConfigBase { + private static Configuration configuration; + private static final Map active=new HashMap<>(); + public static void init() { + if(configuration!=null) return; + configuration=new Configuration(new File("config/BaseMinerals.cfg")); + load(configuration); + if(configuration.hasChanged()) configuration.save(); + MinecraftForge.EVENT_BUS.register(new Config()); + } + static void load(Configuration value) { + for(String name:new String[]{"charcoal","lithium","niter","phosphorus","potash","salt","saltpeter","silicon","sulfur"}) { + String title=Character.toUpperCase(name.charAt(0))+name.substring(1); + Property property=value.get("Minerals","Enable"+title,true,"Enable "+title+" Items and Materials. Requires a game restart."); + property.setRequiresMcRestart(true); + property.setLanguageKey("config.baseminerals.enable_"+name); + boolean enabled=property.getBoolean(); active.put(name,enabled); + // Charcoal belongs to Base Metals. This add-on must not disable it globally. + if(!"charcoal".equals(name)) Options.materialEnabled(name,enabled); + if("lithium".equals(name)||"silicon".equals(name)) Options.fluidEnabled(name,enabled); + } + } + public static boolean isEnabled(String name) {return Boolean.TRUE.equals(active.get(name));} + public static Configuration getConfiguration() {if(configuration==null)init();return configuration;} + @SubscribeEvent public void onConfigChange(ConfigChangedEvent.OnConfigChangedEvent event) { + if("baseminerals".equals(event.getModID())) configuration.save(); + } + public static void postInit() {CrusherRecipeRegistry.getInstance().clearCache();} +} diff --git a/src/main/java/com/mcmoddev/baseminerals/util/EventHandler.java b/src/main/java/zone/moddev/mc/baseminerals/util/EventHandler.java similarity index 85% rename from src/main/java/com/mcmoddev/baseminerals/util/EventHandler.java rename to src/main/java/zone/moddev/mc/baseminerals/util/EventHandler.java index e58d721..02740a6 100644 --- a/src/main/java/com/mcmoddev/baseminerals/util/EventHandler.java +++ b/src/main/java/zone/moddev/mc/baseminerals/util/EventHandler.java @@ -1,7 +1,7 @@ -package com.mcmoddev.baseminerals.util; +package zone.moddev.mc.baseminerals.util; -import com.mcmoddev.baseminerals.data.AchievementNames; -import com.mcmoddev.baseminerals.data.MaterialNames; +import zone.moddev.mc.baseminerals.data.AchievementNames; +import zone.moddev.mc.baseminerals.data.MaterialNames; import com.mcmoddev.lib.init.Achievements; import com.mcmoddev.lib.item.ItemMMDIngot; import com.mcmoddev.lib.material.IMMDObject; diff --git a/src/main/java/zone/moddev/mc/baseminerals/worldgen/BaseMineralsWorldgenProvider.java b/src/main/java/zone/moddev/mc/baseminerals/worldgen/BaseMineralsWorldgenProvider.java new file mode 100644 index 0000000..af72066 --- /dev/null +++ b/src/main/java/zone/moddev/mc/baseminerals/worldgen/BaseMineralsWorldgenProvider.java @@ -0,0 +1,71 @@ +package zone.moddev.mc.baseminerals.worldgen; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.function.Consumer; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.fml.common.registry.ForgeRegistries; +import zone.moddev.mc.orespawn.api.*; + +/** Declarations only: OreSpawn resolves, bakes and executes all placement. */ +public final class BaseMineralsWorldgenProvider { + private BaseMineralsWorldgenProvider() { } + private static final Ore[] ORES={ + new Ore("lithium",8,47,4,12,18,OrePattern.VEIN,OreHeightDistribution.TRIANGLE,8,4,3,8,GeologyFamily.IGNEOUS_INTRUSIVE), + new Ore("niter",16,63,5,8,16,OrePattern.CLUSTERS,OreHeightDistribution.UNIFORM,4,2,3,8,GeologyFamily.SEDIMENTARY), + new Ore("salt",24,79,2.5,16,32,OrePattern.CLUSTERS,OreHeightDistribution.UNIFORM,8,0,8,8,GeologyFamily.SEDIMENTARY), + new Ore("sulfur",4,31,3,12,18,OrePattern.CLUSTERS,OreHeightDistribution.BOTTOM_TRIANGLE,4,2,4,8,GeologyFamily.SEDIMENTARY,GeologyFamily.IGNEOUS_VOLCANIC), + new Ore("phosphorus",16,63,3,12,18,OrePattern.CLUSTERS,OreHeightDistribution.TRIANGLE,6,1,6,8,GeologyFamily.SEDIMENTARY), + new Ore("potash",8,47,2.5,12,24,OrePattern.CLUSTERS,OreHeightDistribution.UNIFORM,6,0,6,8,GeologyFamily.SEDIMENTARY), + new Ore("saltpeter",24,79,3.75,8,16,OrePattern.CLUSTERS,OreHeightDistribution.UNIFORM,4,1,3,8,GeologyFamily.SEDIMENTARY) + }; + public static void enqueue() { + build(ForgeRegistries.BLOCKS::containsKey).ifPresent(provider->{ + if(!OreSpawnApi.enqueue(provider)) throw new IllegalStateException("Base Minerals provider registered twice"); + }); + } + public static Optional build(Predicate available) { + List ores=new ArrayList<>(); + for(Ore rule:ORES) { + ResourceLocation block=id("baseminerals",rule.name+"_ore"); + if(!available.test(block)) continue; + WorldgenProvider.OreDefinition.Builder ore=WorldgenProvider.OreDefinition.builder(id("baseminerals","legacy/"+rule.name+"_ore"),block) + .material(id("orespawn",rule.name)).nativeGeneration(false).retrogen(false).output(block,100D); + Consumer placement=p->{ + p.yRange(rule.minY,rule.maxY).attempts(rule.frequency).quantityRange(rule.minQuantity,rule.maxQuantity) + .pattern(rule.pattern).heightDistribution(rule.height).placementChannel(id("orespawn","standard")) + .spread(rule.horizontal,rule.vertical).nodeSize(rule.node).hostBlock(id("minecraft","stone")); + // API major 1 exposes vein length through the standard pattern codec. + if(rule.pattern==OrePattern.VEIN) { + com.google.gson.JsonObject settings=new com.google.gson.JsonObject(); + settings.addProperty("length",rule.length); + settings.addProperty("node_size",rule.node); + settings.addProperty("spread",rule.horizontal); + settings.addProperty("vertical_spread",rule.vertical); + p.pattern(id("orespawn","vein"),settings); + } + for(GeologyFamily family:rule.families) p.hostFamily(family); + }; + // Exact-domain siblings participate in the same Overworld source group. + // The explicit rule overrides this fallback, so placement still runs once. + ore.dimension(id("minecraft","overworld"),placement); + ore.dimensionSelector(OreDimensionSelector.ALL_EXCEPT_NETHER_AND_END,placement); + ores.add(ore.build()); + } + if(ores.isEmpty())return Optional.empty(); + WorldgenProvider.Builder provider=WorldgenProvider.builder("baseminerals",1); + ores.forEach(provider::ore); + return Optional.of(provider.build()); + } + private static ResourceLocation id(String mod,String name){return new ResourceLocation(mod,name);} + private static final class Ore { + final String name; final int minY,maxY,minQuantity,maxQuantity,horizontal,vertical,node,length; + final double frequency; final OrePattern pattern; final OreHeightDistribution height; final GeologyFamily[] families; + Ore(String name,int minY,int maxY,double frequency,int minQuantity,int maxQuantity,OrePattern pattern,OreHeightDistribution height,int horizontal,int vertical,int node,int length,GeologyFamily...families) { + this.name=name;this.minY=minY;this.maxY=maxY;this.frequency=frequency;this.minQuantity=minQuantity;this.maxQuantity=maxQuantity; + this.pattern=pattern;this.height=height;this.horizontal=horizontal;this.vertical=vertical;this.node=node;this.length=length;this.families=families; + } + } +} diff --git a/src/main/resources/META-INF/baseminerals-legacy-api.list b/src/main/resources/META-INF/baseminerals-legacy-api.list new file mode 100644 index 0000000..39f0ac2 --- /dev/null +++ b/src/main/resources/META-INF/baseminerals-legacy-api.list @@ -0,0 +1,23 @@ +BaseMinerals +data/AchievementNames +data/MaterialNames +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/Thaumcraft +integration/plugins/TinkersConstruct +integration/plugins/VeinMiner +proxy/ClientProxy +proxy/CommonProxy +proxy/ServerProxy +util/Config +util/EventHandler diff --git a/src/main/resources/assets/baseminerals/lang/de_DE.lang b/src/main/resources/assets/baseminerals/lang/de_DE.lang index 27b23f3..7145fe9 100644 --- a/src/main/resources/assets/baseminerals/lang/de_DE.lang +++ b/src/main/resources/assets/baseminerals/lang/de_DE.lang @@ -122,4 +122,4 @@ fluid.baseminerals.lithium=Geschmolzenes Lithium fluid.baseminerals.silicon=Geschmolzenes Silizium tile.baseminerals.lithium.name=Geschmolzenes Lithium -tile.baseminerals.lithium.name=Geschmolzenes Silizium \ No newline at end of file +tile.baseminerals.silicon.name=Geschmolzenes Silizium \ No newline at end of file diff --git a/src/main/resources/assets/baseminerals/lang/en_US.lang b/src/main/resources/assets/baseminerals/lang/en_US.lang index 9aff105..15bcecc 100644 --- a/src/main/resources/assets/baseminerals/lang/en_US.lang +++ b/src/main/resources/assets/baseminerals/lang/en_US.lang @@ -108,7 +108,6 @@ tile.baseminerals.saltpeter_ore.name=Saltpeter Ore tile.baseminerals.silicon_block.name=Silicon Block tile.baseminerals.silicon_ore.name=Silicon Ore -tile.baseminerals.silicon.name=Molten Silicon tile.baseminerals.sulfur_block.name=Sulfur Block tile.baseminerals.sulfur_ore.name=Sulfur Ore @@ -124,4 +123,16 @@ fluid.baseminerals.lithium=Molten Lithium fluid.baseminerals.silicon=Molten Silicon tile.baseminerals.lithium.name=Molten Lithium -tile.baseminerals.lithium.name=Molten Silicon +tile.baseminerals.silicon.name=Molten Silicon + +config.baseminerals.title=Base Minerals +config.baseminerals.description=Material switches take effect after restarting Minecraft. +config.baseminerals.enable_charcoal=Enable Charcoal +config.baseminerals.enable_lithium=Enable Lithium +config.baseminerals.enable_niter=Enable Niter +config.baseminerals.enable_phosphorus=Enable Phosphorus +config.baseminerals.enable_potash=Enable Potash +config.baseminerals.enable_salt=Enable Salt +config.baseminerals.enable_saltpeter=Enable Saltpeter +config.baseminerals.enable_silicon=Enable Silicon +config.baseminerals.enable_sulfur=Enable Sulfur diff --git a/src/main/resources/assets/baseminerals/models/block/lithium_ore.json b/src/main/resources/assets/baseminerals/models/block/lithium_ore.json deleted file mode 100644 index acdbf7f..0000000 --- a/src/main/resources/assets/baseminerals/models/block/lithium_ore.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "parent": "block/cube_all", - "textures": { - "all": "baseminerals:blocks/lithium_ore" - } -} diff --git a/src/main/resources/assets/baseminerals/models/block/niter_ore.json b/src/main/resources/assets/baseminerals/models/block/niter_ore.json deleted file mode 100644 index 5699b39..0000000 --- a/src/main/resources/assets/baseminerals/models/block/niter_ore.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "parent": "block/cube_all", - "textures": { - "all": "baseminerals:blocks/niter_ore" - } -} diff --git a/src/main/resources/assets/baseminerals/models/block/phosphorus_ore.json b/src/main/resources/assets/baseminerals/models/block/phosphorus_ore.json deleted file mode 100644 index 485fa4f..0000000 --- a/src/main/resources/assets/baseminerals/models/block/phosphorus_ore.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "parent": "block/cube_all", - "textures": { - "all": "baseminerals:blocks/phosphorus_ore" - } -} diff --git a/src/main/resources/assets/baseminerals/models/block/potash_ore.json b/src/main/resources/assets/baseminerals/models/block/potash_ore.json deleted file mode 100644 index 6e00183..0000000 --- a/src/main/resources/assets/baseminerals/models/block/potash_ore.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "parent": "block/cube_all", - "textures": { - "all": "baseminerals:blocks/potash_ore" - } -} diff --git a/src/main/resources/assets/baseminerals/models/block/salt_ore.json b/src/main/resources/assets/baseminerals/models/block/salt_ore.json deleted file mode 100644 index 046de71..0000000 --- a/src/main/resources/assets/baseminerals/models/block/salt_ore.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "parent": "block/cube_all", - "textures": { - "all": "baseminerals:blocks/salt_ore" - } -} diff --git a/src/main/resources/assets/baseminerals/models/block/saltpeter_ore.json b/src/main/resources/assets/baseminerals/models/block/saltpeter_ore.json deleted file mode 100644 index 2669156..0000000 --- a/src/main/resources/assets/baseminerals/models/block/saltpeter_ore.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "parent": "block/cube_all", - "textures": { - "all": "baseminerals:blocks/saltpeter_ore" - } -} diff --git a/src/main/resources/assets/baseminerals/models/block/sulfur_ore.json b/src/main/resources/assets/baseminerals/models/block/sulfur_ore.json deleted file mode 100644 index f342e04..0000000 --- a/src/main/resources/assets/baseminerals/models/block/sulfur_ore.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "parent": "block/cube_all", - "textures": { - "all": "baseminerals:blocks/sulfur_ore" - } -} diff --git a/src/main/resources/assets/baseminerals/textures/blocks/lithium_ore.png b/src/main/resources/assets/baseminerals/textures/blocks/lithium_ore.png deleted file mode 100644 index 6283e67..0000000 Binary files a/src/main/resources/assets/baseminerals/textures/blocks/lithium_ore.png and /dev/null differ diff --git a/src/main/resources/assets/baseminerals/textures/blocks/molten_metal_flow.png b/src/main/resources/assets/baseminerals/textures/blocks/molten_metal_flow.png new file mode 100644 index 0000000..0826d00 Binary files /dev/null and b/src/main/resources/assets/baseminerals/textures/blocks/molten_metal_flow.png differ diff --git a/src/main/resources/assets/baseminerals/textures/blocks/molten_metal_flow.png.mcmeta b/src/main/resources/assets/baseminerals/textures/blocks/molten_metal_flow.png.mcmeta new file mode 100644 index 0000000..034019f --- /dev/null +++ b/src/main/resources/assets/baseminerals/textures/blocks/molten_metal_flow.png.mcmeta @@ -0,0 +1,5 @@ +{ + "animation": { + "frametime": 3 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/baseminerals/textures/blocks/molten_metal_still.png b/src/main/resources/assets/baseminerals/textures/blocks/molten_metal_still.png new file mode 100644 index 0000000..441ff72 Binary files /dev/null and b/src/main/resources/assets/baseminerals/textures/blocks/molten_metal_still.png differ diff --git a/src/main/resources/assets/baseminerals/textures/blocks/molten_metal_still.png.mcmeta b/src/main/resources/assets/baseminerals/textures/blocks/molten_metal_still.png.mcmeta new file mode 100644 index 0000000..a7f252f --- /dev/null +++ b/src/main/resources/assets/baseminerals/textures/blocks/molten_metal_still.png.mcmeta @@ -0,0 +1,45 @@ +{ + "animation": { + "frametime": 2, + "frames": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 18, + 17, + 16, + 15, + 14, + 13, + 12, + 11, + 10, + 9, + 8, + 7, + 6, + 5, + 4, + 3, + 2, + 1 + ] + } +} diff --git a/src/main/resources/assets/baseminerals/textures/blocks/niter_ore.png b/src/main/resources/assets/baseminerals/textures/blocks/niter_ore.png deleted file mode 100644 index 3c1ddfa..0000000 Binary files a/src/main/resources/assets/baseminerals/textures/blocks/niter_ore.png and /dev/null differ diff --git a/src/main/resources/assets/baseminerals/textures/blocks/ore_overlays/lithium_ore.png b/src/main/resources/assets/baseminerals/textures/blocks/ore_overlays/lithium_ore.png new file mode 100644 index 0000000..09ca742 Binary files /dev/null and b/src/main/resources/assets/baseminerals/textures/blocks/ore_overlays/lithium_ore.png differ diff --git a/src/main/resources/assets/baseminerals/textures/blocks/ore_overlays/niter_ore.png b/src/main/resources/assets/baseminerals/textures/blocks/ore_overlays/niter_ore.png new file mode 100644 index 0000000..192119d Binary files /dev/null and b/src/main/resources/assets/baseminerals/textures/blocks/ore_overlays/niter_ore.png differ diff --git a/src/main/resources/assets/baseminerals/textures/blocks/ore_overlays/phosphorus_ore.png b/src/main/resources/assets/baseminerals/textures/blocks/ore_overlays/phosphorus_ore.png new file mode 100644 index 0000000..4cae97c Binary files /dev/null and b/src/main/resources/assets/baseminerals/textures/blocks/ore_overlays/phosphorus_ore.png differ diff --git a/src/main/resources/assets/baseminerals/textures/blocks/ore_overlays/potash_ore.png b/src/main/resources/assets/baseminerals/textures/blocks/ore_overlays/potash_ore.png new file mode 100644 index 0000000..46b144e Binary files /dev/null and b/src/main/resources/assets/baseminerals/textures/blocks/ore_overlays/potash_ore.png differ diff --git a/src/main/resources/assets/baseminerals/textures/blocks/ore_overlays/salt_ore.png b/src/main/resources/assets/baseminerals/textures/blocks/ore_overlays/salt_ore.png new file mode 100644 index 0000000..cb27893 Binary files /dev/null and b/src/main/resources/assets/baseminerals/textures/blocks/ore_overlays/salt_ore.png differ diff --git a/src/main/resources/assets/baseminerals/textures/blocks/ore_overlays/saltpeter_ore.png b/src/main/resources/assets/baseminerals/textures/blocks/ore_overlays/saltpeter_ore.png new file mode 100644 index 0000000..166b391 Binary files /dev/null and b/src/main/resources/assets/baseminerals/textures/blocks/ore_overlays/saltpeter_ore.png differ diff --git a/src/main/resources/assets/baseminerals/textures/blocks/ore_overlays/sulfur_ore.png b/src/main/resources/assets/baseminerals/textures/blocks/ore_overlays/sulfur_ore.png new file mode 100644 index 0000000..b59699b Binary files /dev/null and b/src/main/resources/assets/baseminerals/textures/blocks/ore_overlays/sulfur_ore.png differ diff --git a/src/main/resources/assets/baseminerals/textures/blocks/phosphorus_ore.png b/src/main/resources/assets/baseminerals/textures/blocks/phosphorus_ore.png deleted file mode 100644 index 1893512..0000000 Binary files a/src/main/resources/assets/baseminerals/textures/blocks/phosphorus_ore.png and /dev/null differ diff --git a/src/main/resources/assets/baseminerals/textures/blocks/potash_ore.png b/src/main/resources/assets/baseminerals/textures/blocks/potash_ore.png deleted file mode 100644 index 384ee46..0000000 Binary files a/src/main/resources/assets/baseminerals/textures/blocks/potash_ore.png and /dev/null differ diff --git a/src/main/resources/assets/baseminerals/textures/blocks/salt_ore.png b/src/main/resources/assets/baseminerals/textures/blocks/salt_ore.png deleted file mode 100644 index 2c6ba6f..0000000 Binary files a/src/main/resources/assets/baseminerals/textures/blocks/salt_ore.png and /dev/null differ diff --git a/src/main/resources/assets/baseminerals/textures/blocks/saltpeter_ore.png b/src/main/resources/assets/baseminerals/textures/blocks/saltpeter_ore.png deleted file mode 100644 index 26c7ebd..0000000 Binary files a/src/main/resources/assets/baseminerals/textures/blocks/saltpeter_ore.png and /dev/null differ diff --git a/src/main/resources/assets/baseminerals/textures/blocks/sulfur_ore.png b/src/main/resources/assets/baseminerals/textures/blocks/sulfur_ore.png deleted file mode 100644 index 5e3f365..0000000 Binary files a/src/main/resources/assets/baseminerals/textures/blocks/sulfur_ore.png and /dev/null differ diff --git a/src/main/resources/pack.mcmeta b/src/main/resources/pack.mcmeta index 1cbd107..ed6efd1 100644 --- a/src/main/resources/pack.mcmeta +++ b/src/main/resources/pack.mcmeta @@ -1,6 +1,6 @@ { "pack":{ - "pack_format":2, + "pack_format":3, "description":"Base Minerals" } } \ No newline at end of file diff --git a/src/test/java/zone/moddev/mc/baseminerals/NativeProviderTests.java b/src/test/java/zone/moddev/mc/baseminerals/NativeProviderTests.java new file mode 100644 index 0000000..9370609 --- /dev/null +++ b/src/test/java/zone/moddev/mc/baseminerals/NativeProviderTests.java @@ -0,0 +1,63 @@ +package zone.moddev.mc.baseminerals; + +import static org.junit.jupiter.api.Assertions.*; +import com.google.gson.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.*; +import java.util.Optional; +import java.util.function.Predicate; +import net.minecraft.util.ResourceLocation; +import org.junit.jupiter.api.Test; +import zone.moddev.mc.orespawn.api.WorldgenProvider; + +class NativeProviderTests { + @SuppressWarnings("unchecked") + private Optional build(Predicate available) throws Exception { + return (Optional) Class.forName("zone.moddev.mc.baseminerals.worldgen.BaseMineralsWorldgenProvider") + .getMethod("build", Predicate.class).invoke(null, available); + } + @Test void implementsApprovedBalanceAndExportsSchema() throws Exception { + JsonObject provider = build(id -> true).get().toJson(); + Files.createDirectories(Paths.get("build/reports")); + Files.write(Paths.get("build/reports/native-provider.json"), (provider.toString()+"\n").getBytes(StandardCharsets.UTF_8)); + assertEquals(5,provider.get("schema_version").getAsInt()); + JsonObject ores=provider.getAsJsonObject("ores"); + assertEquals(7,ores.entrySet().size()); + String[] names={"lithium","niter","salt","sulfur","phosphorus","potash","saltpeter"}; + double[][] rules={{8,47,4,12,18},{16,63,5,8,16},{24,79,2.5,16,32},{4,31,3,12,18},{16,63,3,12,18},{8,47,2.5,12,24},{24,79,3.75,8,16}}; + int[][] shapes={{8,4,3},{4,2,3},{8,0,8},{4,2,4},{6,1,6},{6,0,6},{4,1,3}}; + String[] heights={"triangle","uniform","uniform","bottom_triangle","triangle","uniform","uniform"}; + for(int i=0;i false).isPresent()); + JsonObject ores=build(id -> !id.getResourcePath().equals("lithium_ore")).get().toJson().getAsJsonObject("ores"); + assertEquals(6,ores.entrySet().size()); + assertFalse(ores.has("baseminerals:legacy/lithium_ore")); + } +} diff --git a/src/test/java/zone/moddev/mc/baseminerals/compat/LegacyNamespaceTransformerTests.java b/src/test/java/zone/moddev/mc/baseminerals/compat/LegacyNamespaceTransformerTests.java new file mode 100644 index 0000000..857ff81 --- /dev/null +++ b/src/test/java/zone/moddev/mc/baseminerals/compat/LegacyNamespaceTransformerTests.java @@ -0,0 +1,87 @@ +package zone.moddev.mc.baseminerals.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/baseminerals/proxy/CommonProxy", type.superName); + assertEquals("Lzone/moddev/mc/baseminerals/BaseMinerals;", ((FieldNode) type.fields.get(0)).desc); + + final MethodNode method = (MethodNode) type.methods.get(0); + assertEquals("zone/moddev/mc/baseminerals/BaseMinerals", ((FieldInsnNode) method.instructions.get(0)).owner); + assertEquals("zone.moddev.mc.baseminerals.init.Materials", ((LdcInsnNode) method.instructions.get(2)).cst); + assertEquals(Type.getObjectType("zone/moddev/mc/baseminerals/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/baseminerals/addon/Extra", null, + "com/mcmoddev/baseminerals/proxy/CommonProxy", null); + source.visitEnd(); + + final ClassNode type = new ClassNode(); + new ClassReader(new LegacyNamespaceTransformer().transform("com.mcmoddev.baseminerals.addon.Extra", null, + source.toByteArray())).accept(type, 0); + assertEquals("com/mcmoddev/baseminerals/addon/Extra", type.name); + assertEquals("zone/moddev/mc/baseminerals/proxy/CommonProxy", type.superName); + } + + @Test + void leavesCanonicalClassesAndTheCyanoBridgeAlone() { + final LegacyNamespaceTransformer transformer = new LegacyNamespaceTransformer(); + final byte[] bytes = fixture(); + assertSame(bytes, transformer.transform("zone.moddev.mc.baseminerals.BaseMinerals", 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/baseminerals/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/baseminerals/proxy/CommonProxy", null); + type.visitField(Opcodes.ACC_PUBLIC, "mod", "Lcom/mcmoddev/baseminerals/BaseMinerals;", 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/baseminerals/BaseMinerals", "instance", "Lcom/mcmoddev/baseminerals/BaseMinerals;"); + method.visitInsn(Opcodes.POP); + method.visitLdcInsn("com.mcmoddev.baseminerals.init.Materials"); + method.visitInsn(Opcodes.POP); + method.visitLdcInsn(Type.getObjectType("com/mcmoddev/baseminerals/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/zone/moddev/mc/baseminerals/util/MaterialConfigurationTests.java b/src/test/java/zone/moddev/mc/baseminerals/util/MaterialConfigurationTests.java new file mode 100644 index 0000000..478e715 --- /dev/null +++ b/src/test/java/zone/moddev/mc/baseminerals/util/MaterialConfigurationTests.java @@ -0,0 +1,47 @@ +package zone.moddev.mc.baseminerals.util; + +import static org.junit.jupiter.api.Assertions.*; +import java.nio.file.Path; +import net.minecraftforge.common.config.Configuration; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.io.TempDir; + +class MaterialConfigurationTests { + @BeforeEach void freshSharedStartupState() throws Exception { + for(String key:new String[]{"materialEnabled","fluidEnabled"}) { + java.lang.reflect.Field field=com.mcmoddev.lib.util.ConfigBase.Options.class.getDeclaredField(key); + field.setAccessible(true);((java.util.Map)field.get(null)).clear(); + } + } + @Test void initializesMaterialAndFluidSwitchesTogether() { + Configuration config=new Configuration(); + Config.load(config); + for(String name:new String[]{"lithium","silicon"}) { + assertTrue(Config.isEnabled(name)); + assertTrue(com.mcmoddev.lib.util.ConfigBase.Options.isFluidEnabled(name)); + } + assertTrue(config.getCategory("Minerals").get("EnableLithium").requiresMcRestart()); + } + @Test void disabledMaterialAlsoDisablesItsFluidAtStartup() { + Configuration config=new Configuration(); + config.get("Minerals","EnableLithium",true).set(false); + Config.load(config); + assertFalse(Config.isEnabled("lithium")); + assertFalse(com.mcmoddev.lib.util.ConfigBase.Options.isFluidEnabled("lithium")); + } + @Test void charcoalSwitchDoesNotDisableBaseMetalsSharedMaterial() { + com.mcmoddev.lib.util.ConfigBase.Options.materialEnabled("charcoal",true); + Configuration config=new Configuration(); + config.get("Minerals","EnableCharcoal",true).set(false); + Config.load(config); + assertFalse(Config.isEnabled("charcoal")); + assertTrue(com.mcmoddev.lib.util.ConfigBase.Options.isMaterialEnabled("charcoal")); + } + @Test void editingRequestedSwitchDoesNotChangeActiveRegistration() { + Configuration config=new Configuration(); + Config.load(config); + config.get("Minerals","EnableSalt",true).set(false); + assertTrue(Config.isEnabled("salt")); + } +} diff --git a/tools/validation/legacy111/EclipseModelCheck.java b/tools/validation/legacy111/EclipseModelCheck.java new file mode 100644 index 0000000..ebc1885 --- /dev/null +++ b/tools/validation/legacy111/EclipseModelCheck.java @@ -0,0 +1,99 @@ +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]); + final String[] modelArguments = args.length > 4 + ? new String[]{"--offline", "-Dorg.gradle.java.installations.paths=" + args[4], + "-Dorg.gradle.java.installations.auto-detect=false", "-Dorg.gradle.java.installations.auto-download=false"} + : new String[]{"--offline"}; + + try (ProjectConnection connection = GradleConnector.newConnector() + .forProjectDirectory(project).useGradleUserHomeDir(cache) + .useBuildDistribution().connect()) { + final BuildEnvironment environment = connection.model(BuildEnvironment.class) + .setJavaHome(javaHome).withArguments(modelArguments).get(); + final GradleBuild build = connection.model(GradleBuild.class) + .setJavaHome(javaHome).withArguments(modelArguments).get(); + final EclipseProject eclipse = connection.model(EclipseProject.class) + .setJavaHome(javaHome).withArguments(modelArguments).get(); + + if (!"9.6.1".equals(environment.getGradle().getGradleVersion()) + || !"BaseMinerals".equals(build.getRootProject().getName()) + || !"BaseMinerals".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_MINERALS_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/legacy111/README.md b/tools/validation/legacy111/README.md new file mode 100644 index 0000000..9a01fe8 --- /dev/null +++ b/tools/validation/legacy111/README.md @@ -0,0 +1,66 @@ +# Base Minerals 1.11 qualification + +Probes are separate mods under `build/test-mods`. Exclude them from distribution +and ordinary Eclipse profiles. All game directories/worlds are disposable. +Suites refuse to reuse output directories and fingerprint copied fixtures. + +`test` checks the provider settings, missing blocks, startup material +and fluid switches, restart latching, charcoal ownership and namespace relinking. +`verifyProjectResources` validates JSON and the exported provider against schemas +inside the pinned OreSpawn jar, configuration labels and workflow pins. + +```text +python tools/validation/legacy111/run_packaged_suite.py --java --template --mirror --output +``` + +The template contains only Forge 1.11.2-13.20.1.2588, vanilla server and libraries. +The suite covers all Y levels across 256 interior chunks per dimension for four +fixed seeds, with a populated border. It checks Nether and End exclusion, repeat +and reload digests, later chunks, disabled materials and generation, native +overrides, OS1 and OS3 imports, saved profiles and stacks, and Tinkers molten +forms. It also checks negative hosts, records attempts, quantities and selected +heights, and runs three paired timing comparisons. +The later-chunk case also recenses the original area. Every individual disabled +material is tested through actual generation, rather than registration alone. +Seed zero needs explicit fresh metadata because server.properties treats `0` +as random. `--quick` is only an early smoke. + +The packaged suite requires one imported lithium rule when an OS1 world moves +without its global import file. Run `reproduce_os1_reload.py` with `--java`, +`--template`, `--mirror` and a new `--output` directory for the shorter check. +See [known issues](../../../docs/KNOWN_ISSUES.md) for edited profiles. + +`run_overlap_suite.py` stages public sibling jars pinned in +`overlap_dependencies.json` and captures their real source policies. Its Electric +Advantage and Power Advantage jars are the historical 1.10.2 public releases. +Mineralogy is the 1.11.2 public 3.3.0.6 release, whose sulfur is an external +source in OreSpawn. +The runner preserves failures per integration and continues to collect the +other cases, then exits unsuccessfully if any public integration failed. + +`packagedSourcePolicyTest` runs eight cases with a clearly synthetic output-only +sibling. It checks Balanced, Single, Custom weights, Keep Original, physical +placement-budget equality and saved choices across removal/restoration. It does +not claim that the historical public Electric Advantage jar loads. + +After the full suite, `run_continuity_checks.py` accepts `--suite` and +`--selector-fixture` in addition to `--java`, `--template`, `--output`. It checks +copied OS1/OS3 worlds, saved selector-only profiles, all six vanilla stone +metadata hosts, bedrock/air/water/obsidian exclusions and decoded pattern settings. +`run_mineralogy_policy.py` tests reviewed sulfur output choices on copied worlds +when the fixture supplies an OreSpawn-managed Mineralogy source. + +`run_packaged_client.py` exercises both configuration entries, save/cancel/restart, +baked models and integrated-world fresh/reload checks against the installed +obfuscated client. It verifies assets in a disposable directory, saves screenshots +and rejects texture errors. Supply `--java`, `--cache`, `--installer`, `--template`, +`--mirror`, `--output` and `--phase first|reload`. + +Inspect complete logs as well as JSON reports. Forge's unsigned-distribution +warning, the pinned Base Metals fingerprint warning, offline Realms checks and normal +sound shutdown messages are distinct from loading/generation errors. Manual +visual acceptance, hosted CI and publication remain separate gates. +Tinkers' historical Base Metals registrations emit alternative-prefix warnings +for its own molten alloys. Missing-source tests deliberately exercise Forge's +missing-mod and mapping warnings. A Forge-only control reproduces the vanilla +Nether population warnings seen while the census probe loads chunks. diff --git a/tools/validation/legacy111/audit_resources.py b/tools/validation/legacy111/audit_resources.py new file mode 100644 index 0000000..53989e8 --- /dev/null +++ b/tools/validation/legacy111/audit_resources.py @@ -0,0 +1,61 @@ +"""Parse resources and validate exported native declarations against the pinned engine.""" +import argparse,json,re,zipfile +from pathlib import Path +from referencing import Registry,Resource +from jsonschema import Draft202012Validator +import yaml +from run_runtime_checks import ROOT,dependency + +def main(): + parser=argparse.ArgumentParser();parser.add_argument('--mirror',type=Path);args=parser.parse_args() + count=0 + resources=ROOT/'build/resources/main' + for path in resources.rglob('*'): + if path.is_file() and (path.suffix in ('.json','.mcmeta') or path.name=='mcmod.info'): + json.loads(path.read_text(encoding='utf-8-sig'));count+=1 + specs=json.loads((ROOT/'gradle/dependencies.json').read_text()) + with zipfile.ZipFile(dependency(next(s for s in specs if s['id']=='orespawn'),args.mirror)) as jar: + schemas=[json.loads(jar.read(n)) for n in jar.namelist() if n.startswith('META-INF/orespawn/docs/schemas/') and n.endswith('.json')] + registry=Registry().with_resources((s['$id'],Resource.from_contents(s)) for s in schemas) + schema=next(s for s in schemas if s['$id'].endswith('/orespawn-provider.schema.json')) + provider=json.loads((ROOT/'build/reports/native-provider.json').read_text()) + Draft202012Validator(schema,registry=registry).validate(provider) + assert len(provider['ores'])==7 + assert all(not provider.get(section) for section in ['rocks','terrain_dimensions','geomes','fluid_deposits','biome_palettes']) + assets=resources/'assets/baseminerals' + source_textures=ROOT/'src/main/resources/assets/baseminerals/textures/blocks' + for ore in provider['ores'].values(): + name=ore['block'].split(':',1)[1] + state=json.loads((assets/'blockstates'/f'{name}.json').read_text()) + item=json.loads((assets/'models/item'/f'{name}.json').read_text()) + model=json.loads((assets/'models/block'/f'{name}.json').read_text()) + assert state['variants']['normal']['model']==f'baseminerals:{name}',name + assert item['parent']==f'baseminerals:block/{name}',name + textures=assets/'textures/blocks' + if (textures/'ore_overlays'/f'{name}.png').is_file(): + assert model['parent']=='baseminerals:block/ore_overlay',name + assert (assets/'models/block/ore_overlay.json').is_file(),name + assert model['textures']=={ + 'particle':'minecraft:blocks/stone','base':'minecraft:blocks/stone', + 'overlay':f'baseminerals:blocks/ore_overlays/{name}' + },name + if not (source_textures/f'{name}.png').is_file(): + assert not (textures/f'{name}.png').exists(),f'{name}: stale solid texture' + else: + assert (textures/f'{name}.png').is_file(),f'{name}: missing solid texture' + assert model['parent']=='block/cube_all',name + assert model['textures']=={'all':f'baseminerals:blocks/{name}'},name + assert {'en_us.lang','de_de.lang'} <= {p.name for p in (assets/'lang').glob('*.lang')} + language=dict(line.split('=',1) for line in (assets/'lang/en_us.lang').read_text(encoding='utf-8').splitlines() if '=' in line and not line.startswith('#')) + for name in ['charcoal','lithium','niter','phosphorus','potash','salt','saltpeter','silicon','sulfur']: + assert 'config.baseminerals.enable_'+name in language,name + for path in (ROOT/'.github/workflows').glob('*.yml'): + workflow=yaml.load(path.read_text(),Loader=yaml.BaseLoader);assert workflow.get('on') and workflow.get('jobs'),path + for job in workflow['jobs'].values(): + for step in job.get('steps',[]): + if 'uses' in step:assert re.fullmatch(r'[^@]+@[0-9a-f]{40}',step['uses']),step['uses'] + release=(ROOT/'.github/workflows/deploy-release.yml').read_text() + assert all(value in release for value in ['confirm_live_publication','270972','240967(required)','245586(required)']) + print(f'BASE_MINERALS_RESOURCES_PASS json={count} provider_schema=5 workflows=5') + +if __name__=='__main__':main() diff --git a/tools/validation/legacy111/audit_runtime_logs.py b/tools/validation/legacy111/audit_runtime_logs.py new file mode 100644 index 0000000..a097ed2 --- /dev/null +++ b/tools/validation/legacy111/audit_runtime_logs.py @@ -0,0 +1,43 @@ +"""Audit full successful-run logs, with narrow, documented legacy exceptions.""" +import argparse,collections,json,re +from pathlib import Path +from run_runtime_checks import write_json,digest + +def category(line,path): + if 'FML appears to be missing any signature data.' in line:return 'unsigned Forge distribution' + if 'Invalid fingerprint detected!' in line and any('['+m+']' in line for m in ('basemetals','mineralogy')):return 'pinned dependency fingerprint' + if any(t in line for t in ('SERVER IS RUNNING IN OFFLINE/INSECURE MODE','server will make no attempt to authenticate','While this makes the game possible','To change this, set "online-mode"')):return 'isolated offline server' + if '[Client thread/WARN]: Author: Paul Lamb' in line:return 'legacy sound shutdown' + if 'client-' in str(path) and "Can't keep up!" in line:return 'automated client startup tick delay' + if re.search(r'\[FML\]: Missing English translation for (?:minecraft|mcp): minecraft\.jar ',line):return 'legacy Forge server resource lookup' + if '[FML]: Missing English translation for FML: assets/fml/lang/en_us.lang' in line:return 'legacy Forge server resource lookup' + if re.search(r'\[FML\]: Missing English translation for (basemineralsprobe|basemineralsclientprobe): assets/\1/lang/en_us\.lang$',line):return 'test fixture without language resources' + if ('[FML]: Missing English translation for electricadvantage: assets/electricadvantage/lang/en_us.lang' in line + and (path.parent/'mods/synthetic-source-policy-fixture-packaged.jar').is_file()):return 'test fixture without language resources' + if ('[FML]: Missing English translation for mantle: assets/mantle/lang/en_us.lang' in line + and (path.parent/'mods/mantle-74924-2413993.jar').is_file()):return 'pinned Mantle jar without language resources' + # The same census with a Forge-only control reproduces vanilla Nether population warnings. + if re.search(r'\[FML\]: Base Minerals checks loaded a new chunk \(-?\d+, -?\d+ Dimension: -1\) during chunk population',line):return 'probe-driven vanilla Nether population' + if 'tinkers' in str(path) and ('[FML]: ***' in line or '[FML]: * at ' in line or ('Dangerous alternative prefix `tconstruct`' in line and 'expected `basemetals`' in line)):return 'Base Metals Tinkers registration' + if 'missing-source' in str(path) and any(t in line for t in ('Ignoring invalid OreSpawn-managed ore \'electricadvantage:', 'saved with mod electricadvantage', 'unidentified mappings', 'Unidentified block: electricadvantage:', 'Unidentified item: electricadvantage:', 'mappings that may cause world breakage')):return 'intentional missing-source fixture' + return None + +def main(): + parser=argparse.ArgumentParser(description=__doc__) + parser.add_argument('--directories',type=Path,nargs='+',required=True);parser.add_argument('--output',type=Path,required=True) + args=parser.parse_args();counts=collections.Counter();unknown=[];files={} + for directory in args.directories: + for path in directory.rglob('*-console.log'): + text=path.read_text(errors='replace');files[str(path)]={'sha256':digest(path),'bytes':path.stat().st_size} + for line in text.splitlines(): + if re.search(r'\[(?:[^\]]*/)?(?:WARN|ERROR|FATAL)\]',line): + kind=category(line,path) + if kind:counts[kind]+=1 + else:unknown.append({'path':str(path),'line':line}) + for marker in ('BASE_MINERALS_RUNTIME_FAIL','Exception in server tick loop','Exception caught during firing event','java.lang.NullPointerException','[TEXTURE ERRORS]'): + if marker in text:unknown.append({'path':str(path),'fatal_marker':marker}) + result={'passed':not unknown,'logs':files,'documented_messages':dict(counts),'unclassified':unknown};write_json(args.output,result) + assert not unknown,('Unclassified runtime diagnostics',unknown[:10]) + print('BASE_MINERALS_FULL_LOG_AUDIT_PASS',len(files),'logs',flush=True) + +if __name__=='__main__':main() diff --git a/tools/validation/legacy111/dependency_requirements.py b/tools/validation/legacy111/dependency_requirements.py new file mode 100644 index 0000000..391d77e --- /dev/null +++ b/tools/validation/legacy111/dependency_requirements.py @@ -0,0 +1,55 @@ +"""Reject missing/old public dependencies before any disposable world is loaded.""" +import hashlib +import json +import subprocess +import urllib.request +import argparse +from pathlib import Path +from run_runtime_checks import ROOT, FORGE, dependency, prepare, write_json + +OLD = { + 'base':('base-metals-240967','2533178','abdf704023100ad6bb70351ce72c31a07943ad5bce8308155564194a25089ccc'), + 'os':('mmd-orespawn-245586','2533159','05cc48e59c7b20ed9b7f29ae014f5e2e5b2f16f289ef7d6f274f017b5e2a6f77'), + 'os4':('mmd-orespawn-245586','8840593','51ff282d41e3c566db2541ccad53968030ea12e985a5cee7307162bbf560c99a') +} + +def verify(java,template,output,base,orespawn,minerals): + fixtures=output/'dependency-fixtures';fixtures.mkdir() + old={} + for key,(artifact,version,expected) in OLD.items(): + target=fixtures/f'{artifact}-{version}.jar' + request=urllib.request.Request(f'https://www.cursemaven.com/curse/maven/{artifact}/{version}/{target.name}',headers={'User-Agent':'Mozilla/5.0'}) + with urllib.request.urlopen(request,timeout=45) as response:target.write_bytes(response.read()) + assert hashlib.sha256(target.read_bytes()).hexdigest()==expected,'Public fixture checksum mismatch' + old[key]=target + scenarios={ + 'missing-base':([orespawn,minerals],'basemetals','2.5.1.111021'), + 'old-base':([old['base'],orespawn,minerals],'basemetals','2.5.1.111021'), + 'missing-orespawn':([base,minerals],'orespawn','4.1.6.111021'), + 'old-orespawn':([base,old['os'],minerals],'orespawn','4.1.6.111021'), + 'old-orespawn4':([base,old['os4'],minerals],'orespawn','4.1.6.111021') + } + result={} + for name,(jars,modid,version) in scenarios.items(): + target=output/name;prepare(target,template,jars) + log=target/'rejection-console.log' + with log.open('wb') as stream: + subprocess.run([str(java),'-Xmx1G','-jar',FORGE,'nogui'],cwd=target,stdout=stream,stderr=subprocess.STDOUT,timeout=90) + text=log.read_text(errors='replace') + rejected = ('MissingModsException' in text or 'Missing mandatory dependencies' in text + or 'MultipleModsErrored' in text) + assert rejected and f'{modid}@[{version},' in text,log + assert not (target/'world/level.dat').exists(),'Incompatible dependencies reached world loading' + result[name]={'rejected_before_world':True,'required_mod':modid,'minimum':version} + print(name,'PASS',flush=True) + write_json(output/'dependency-requirements.json',result) + +def main(): + parser=argparse.ArgumentParser(description=__doc__) + for name in ('java','template','output'):parser.add_argument('--'+name,type=Path,required=True) + parser.add_argument('--mirror',type=Path);args=parser.parse_args();args.output.mkdir(parents=True,exist_ok=False) + specs=json.loads((ROOT/'gradle/dependencies.json').read_text()) + selected={s['id']:dependency(s,args.mirror) for s in specs if s['id'] in ('basemetals','orespawn')} + verify(args.java,args.template,args.output,selected['basemetals'],selected['orespawn'],ROOT/'build/libs/BaseMinerals-2.5.1.111021.jar') + +if __name__=='__main__':main() diff --git a/src/main/resources/assets/baseminerals/orespawn/baseminerals.json b/tools/validation/legacy111/fixtures/orespawn3-baseminerals.json old mode 100755 new mode 100644 similarity index 100% rename from src/main/resources/assets/baseminerals/orespawn/baseminerals.json rename to tools/validation/legacy111/fixtures/orespawn3-baseminerals.json diff --git a/tools/validation/legacy111/overlap_dependencies.json b/tools/validation/legacy111/overlap_dependencies.json new file mode 100644 index 0000000..df2d1ea --- /dev/null +++ b/tools/validation/legacy111/overlap_dependencies.json @@ -0,0 +1,5 @@ +[ + {"id":"electricadvantage","coordinate":"curse.maven:electric-advantage-240985:2323092","sha256":"61AE5343A79F518F54F1717D508EA3F6DA7209EE69B0C4433E0B7A9722213EE0","version":"2.2.0","repository":"https://www.cursemaven.com"}, + {"id":"poweradvantage","coordinate":"curse.maven:power-advantage-240982:2323095","sha256":"47CD509C1141095BFE1C8C7DD689C2ECD3F99C89195E34A88CC9E0B5FC12413E","version":"2.3.0","repository":"https://www.cursemaven.com"}, + {"id":"mineralogy","coordinate":"curse.maven:minecraft-mineralogy-240974:2561871","sha256":"EEDB5F9FBBCBA08E8F5DACC4CA9A5109A021DE781857F01E911B9A5122268328","version":"3.3.0.6","repository":"https://www.cursemaven.com"} +] diff --git a/tools/validation/legacy111/policy-fixture/review/minerals/SourceFixture.java b/tools/validation/legacy111/policy-fixture/review/minerals/SourceFixture.java new file mode 100644 index 0000000..fcd02b3 --- /dev/null +++ b/tools/validation/legacy111/policy-fixture/review/minerals/SourceFixture.java @@ -0,0 +1,34 @@ +package review.minerals; + +import net.minecraft.block.Block; +import net.minecraft.block.material.Material; +import net.minecraft.item.ItemBlock; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.event.*; +import net.minecraftforge.fml.common.registry.GameRegistry; +import net.minecraftforge.oredict.OreDictionary; +import zone.moddev.mc.orespawn.api.*; + +/** Synthetic provider contract fixture. This is not the Electric Advantage mod. */ +@Mod(modid="electricadvantage",name="Synthetic source-policy fixture",version="test",dependencies="required-after:baseminerals") +public final class SourceFixture { + @Mod.EventHandler public void preInit(FMLPreInitializationEvent event) { + for(String name:new String[]{"li_ore","sulfur_ore"}) { + Block block=new Block(Material.ROCK).setRegistryName("electricadvantage",name).setUnlocalizedName("policy_fixture."+name); + GameRegistry.register(block);GameRegistry.register(new ItemBlock(block).setRegistryName(block.getRegistryName())); + OreDictionary.registerOre("li_ore".equals(name)?"oreLithium":"oreSulfur",block); + } + } + @Mod.EventHandler public void init(FMLInitializationEvent event) { + WorldgenProvider.Builder provider=WorldgenProvider.builder("electricadvantage",1); + for(String material:new String[]{"lithium","sulfur"}) { + String block="lithium".equals(material)?"li_ore":"sulfur_ore"; + provider.ore(new ResourceLocation("electricadvantage","ore/"+material),new ResourceLocation("electricadvantage",block),ore->ore + .enabled(false).material(new ResourceLocation("orespawn",material)).nativeGeneration(false).retrogen(false) + .dimension(new ResourceLocation("minecraft","overworld"),p->p.yRange(1,31).attempts(1).quantity(8).pattern(OrePattern.DEFAULT) + .placementChannel(new ResourceLocation("orespawn","standard")).hostBlock(new ResourceLocation("minecraft","stone")))); + } + if(!OreSpawnApi.enqueue(provider.build()))throw new IllegalStateException("Synthetic provider rejected"); + } +} diff --git a/tools/validation/legacy111/reproduce_os1_reload.py b/tools/validation/legacy111/reproduce_os1_reload.py new file mode 100644 index 0000000..30e40fa --- /dev/null +++ b/tools/validation/legacy111/reproduce_os1_reload.py @@ -0,0 +1,29 @@ +"""Required regression gate: an OS1 import must not gain native placement on reload.""" +import argparse,json +from pathlib import Path +from run_runtime_checks import ROOT,dependency,prepare,run,write_json + +def main(): + parser=argparse.ArgumentParser(description=__doc__) + for name in ('java','template','output'):parser.add_argument('--'+name,type=Path,required=True) + parser.add_argument('--mirror',type=Path);args=parser.parse_args() + specs=json.loads((ROOT/'gradle/dependencies.json').read_text()) + jars=[dependency(s,args.mirror) for s in specs if s.get('runtime')]+[ROOT/'build/libs/BaseMinerals-2.5.1.111021.jar',ROOT/'build/test-mods/baseminerals-probe-packaged.jar'] + args.output.mkdir(parents=True,exist_ok=False) + initial=args.output/'initial';prepare(initial,args.template,jars) + source=initial/'config/orespawn/baseminerals.json' + write_json(source,{'dimensions':[{'dimension':'0','ores':[{'blockID':'baseminerals:lithium_ore','size':4,'variation':0,'frequency':1.5,'minHeight':8,'maxHeight':40}]}]}) + first=run(args.java,initial,'first') + before={k:v for k,v in first['profile']['ores'].items() if v.get('block')=='baseminerals:lithium_ore'} + assert len(before)==1,before + # Copy only the saved world, preserving the original world and OS1 file. + # A moved world must not need the old global file to preserve its budget. + target=args.output/'profile-only';prepare(target,args.template,jars,initial/'world') + reload=run(args.java,target,'later',{'minerals.sample':'true','minerals.width':'16'}) + after={k:v for k,v in reload['profile']['ores'].items() if v.get('block')=='baseminerals:lithium_ore'} + passed=set(before)==set(after) + write_json(args.output/'required-migration-gate.json',{'passed':passed,'before':before,'after':after,'counts':reload['dimensions']['0']['counts']}) + assert passed,('OS1 reload adds native placement alongside the imported rule',list(before),list(after)) + print('BASE_MINERALS_OS1_RELOAD_PASS',flush=True) + +if __name__=='__main__':main() diff --git a/tools/validation/legacy111/run_continuity_checks.py b/tools/validation/legacy111/run_continuity_checks.py new file mode 100644 index 0000000..690962c --- /dev/null +++ b/tools/validation/legacy111/run_continuity_checks.py @@ -0,0 +1,57 @@ +"""Census copied imported/saved profiles and explicit positive/negative hosts.""" +import argparse,copy,json +from pathlib import Path +from run_runtime_checks import ROOT,dependency,prepare,run,write_json +from run_packaged_suite import PREFIX,NAMES,placements,fingerprint,subset + +def main(): + parser=argparse.ArgumentParser(description=__doc__) + for name in ('java','template','output','suite','selector-fixture'):parser.add_argument('--'+name,type=Path,required=True) + parser.add_argument('--mirror',type=Path);args=parser.parse_args();args.output.mkdir(parents=True,exist_ok=False) + specs=json.loads((ROOT/'gradle/dependencies.json').read_text()) + jars=[dependency(s,args.mirror) for s in specs if s.get('runtime')]+[ROOT/'build/libs/BaseMinerals-2.5.1.111021.jar',ROOT/'build/test-mods/baseminerals-probe-packaged.jar'] + results={};fixtures={};failures={} + for name,source in [('os1',args.suite/'os1-import/world'),('os3',args.suite/'os3-import/world'),('saved-selector',args.selector_fixture)]: + before=fingerprint(source);target=args.output/name;prepare(target,args.template,jars,source) + original=json.loads((source/'serverconfig/orespawn-worldgen.json').read_text()) + if name=='saved-selector': + for ore in original['ores'].values(): + if ore.get('source_mod')=='baseminerals':assert not ore.get('dimensions'),ore + override=json.loads((ROOT/'build/reports/native-provider.json').read_text()) + for p in placements(override['ores'][PREFIX+'salt_ore']):p['frequency']=9 + write_json(target/'config/baseminerals-orespawn.json',override) + report=run(args.java,target,'later',{'minerals.sample':'true','minerals.width':'16'}) + for block in {ore.get('block') for ore in original['ores'].values() if ore.get('source_mod')=='baseminerals'}: + old_ids={key for key,ore in original['ores'].items() if ore.get('block')==block} + new_ids={key for key,ore in report['profile']['ores'].items() if ore.get('block')==block} + if old_ids!=new_ids:failures[name]={'block':block,'original_ids':sorted(old_ids),'reloaded_ids':sorted(new_ids)} + for key,ore in original['ores'].items(): + if ore.get('source_mod')=='baseminerals': + actual=report['profile']['ores'][key];subset(ore,actual) + for section in ('dimensions','dimension_selectors'): + assert ore.get(section,{})==actual.get(section,{}),('Saved dimension declarations changed',name,key,section) + assert fingerprint(source)==before,'Source fixture changed' + results[name]=report;fixtures[name]={'path':str(source),'files':before} + native=json.loads((ROOT/'build/reports/native-provider.json').read_text()) + for name,properties in [('stone-variants',{'minerals.variantHosts':'true'}),('negative-hosts',{'minerals.negativeHosts':'true'}),('decoded-shapes',{'minerals.record':'true'})]: + target=args.output/name;prepare(target,args.template,jars) + if name=='decoded-shapes': + definition=copy.deepcopy(native) + for material in NAMES: + for p in placements(definition['ores'][PREFIX+material+'_ore']): + settings={k:p[k] for k in ('spread','vertical_spread','node_size')};settings['length']=8 + p['pattern']={'type':'basemineralsprobe:'+material,'settings':settings} + write_json(target/'config/baseminerals-orespawn.json',definition) + report=run(args.java,target,'first',{'minerals.custom':'true','minerals.sample':'true','minerals.width':'16',**properties}) + if name=='decoded-shapes': + for material,stats in report['recording'].items(): + p=placements(native['ores'][PREFIX+material+'_ore'])[0] + subset({**{k:p[k] for k in ('spread','vertical_spread','node_size')},'length':8},stats['settings']) + results[name]=report + write_json(args.output/'source-fixtures.json',fixtures) + write_json(args.output/'results.json',results) + write_json(args.output/'continuity-gates.json',{'passed':not failures,'failures':failures}) + assert not failures,('Imported rules gained an extra placement budget',failures) + print('BASE_MINERALS_CONTINUITY_PASS',len(results),'runs',flush=True) + +if __name__=='__main__':main() diff --git a/tools/validation/legacy111/run_eclipse_profile.py b/tools/validation/legacy111/run_eclipse_profile.py new file mode 100644 index 0000000..a379d69 --- /dev/null +++ b/tools/validation/legacy111/run_eclipse_profile.py @@ -0,0 +1,72 @@ +"""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=["first", "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", "-Dminerals.phase=" + args.phase, "-Dminerals.saved=true"] + client = "Client" in profile.stem + if client: + command += ["-Dminerals.client=true"] + 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 + ("-client.json" if client else "-minerals.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 client: + world_report=json.loads((directory/(args.phase+'-minerals.json')).read_text()) + if not world_report['passed']:raise SystemExit(str(world_report['failures'])) + + +if __name__ == "__main__": + main() diff --git a/tools/validation/legacy111/run_mineralogy_policy.py b/tools/validation/legacy111/run_mineralogy_policy.py new file mode 100644 index 0000000..083de7d --- /dev/null +++ b/tools/validation/legacy111/run_mineralogy_policy.py @@ -0,0 +1,46 @@ +"""Review real public Mineralogy sulfur policies on disposable world copies.""" +import argparse,json +from pathlib import Path +from run_runtime_checks import ROOT,dependency,prepare,run,write_json,digest +from run_packaged_suite import fingerprint + +def main(): + parser=argparse.ArgumentParser(description=__doc__) + for name in ('java','template','output','integration'):parser.add_argument('--'+name,type=Path,required=True) + parser.add_argument('--mirror',type=Path);args=parser.parse_args();args.output.mkdir(parents=True,exist_ok=False) + specs=json.loads((ROOT/'gradle/dependencies.json').read_text()) + jars=[dependency(s,args.mirror) for s in specs if s.get('runtime')]+[ROOT/'build/libs/BaseMinerals-2.5.1.111021.jar',ROOT/'build/test-mods/baseminerals-probe-packaged.jar'] + mineralogy=next(p for p in (args.integration/'mods').glob('*.jar') if 'mineralogy' in p.name.lower()) + pin=next(s for s in json.loads((ROOT/'tools/validation/legacy111/overlap_dependencies.json').read_text()) if s['id']=='mineralogy') + assert digest(mineralogy).upper()==pin['sha256'];jars.append(mineralogy) + source=args.integration/'world';before=fingerprint(source);results={} + key='orespawn:sulfur|minecraft:overworld' + for label in ('balanced','single-base','single-mineralogy','weighted'): + target=args.output/label;prepare(target,args.template,jars,source) + path=target/'world/serverconfig/orespawn-worldgen.json';profile=json.loads(path.read_text());policy=profile['ore_source_policies'][key] + candidates={c['owner']:c['source_id'] for c in policy['candidates'] if c['loaded'] and not c['external']} + assert set(candidates)=={'baseminerals','mineralogy'},('A managed Mineralogy sulfur provider is required',candidates) + outputs={candidates['baseminerals']:1,candidates['mineralogy']:3 if label=='weighted' else 1} + if label.startswith('single-'):outputs={candidates['baseminerals' if label=='single-base' else 'mineralogy']:1} + policy.update(mode='consolidated',output_mode='balanced' if label=='balanced' else 'custom' if label=='weighted' else 'single',outputs=outputs, + placement_sources={'orespawn:standard':candidates['mineralogy']},review_required=False) + write_json(path,profile) + report=run(args.java,target,'later',{'minerals.sample':'true','minerals.shared':'true','minerals.width':'16'}) + active=report['profile']['ore_source_policies'][key] + assert active['mode']=='consolidated' and active['placement_sources']==policy['placement_sources'],active + physical=report['dimensions']['0']['sharedOutputs'];own=physical.get('baseminerals:sulfur_ore',0);other=physical.get('mineralogy:sulfur_ore',0) + assert own+other>0,physical + if label=='single-base':assert own>0 and other==0 + if label=='single-mineralogy':assert own==0 and other>0 + if label=='balanced':assert own>0 and other>0 and 0.70 and 2.5started,report + assert json.loads(report.read_text())['passed'],report + assert result.returncode==0 + console=(args.output/(args.phase+'-console.log')).read_text(errors='replace') + assert '[TEXTURE ERRORS]' not in console,'Missing textures in the installed client' + assert "Can't find the resource index file" not in console,'Minecraft assets were not loaded' + print('BASE_MINERALS_PACKAGED_CLIENT_PASS',args.phase,flush=True) + +if __name__=='__main__':main() diff --git a/tools/validation/legacy111/run_packaged_suite.py b/tools/validation/legacy111/run_packaged_suite.py new file mode 100644 index 0000000..7f2370b --- /dev/null +++ b/tools/validation/legacy111/run_packaged_suite.py @@ -0,0 +1,174 @@ +"""Qualify installed Base Minerals in disposable Forge servers; never use a played world.""" +import argparse, copy, gzip, hashlib, json, math, shutil, struct +from pathlib import Path +from run_runtime_checks import ROOT, dependency, prepare, run, write_json, digest + +PREFIX='baseminerals:legacy/' +SELECTOR='orespawn:all_except_nether_end' +NAMES=['lithium','niter','salt','sulfur','phosphorus','potash','saltpeter'] +def fingerprint(path): + return {str(p.relative_to(path)):digest(p) for p in path.rglob('*') if p.is_file()} +def seed_zero(directory): + # A dedicated server treats level-seed=0 as random. Supply valid fresh world + # metadata with RandomSeed=0 so this test actually exercises seed zero. + def name(text): + value=text.encode();return struct.pack('>H',len(value))+value + def tag(kind,key,value):return bytes([kind])+name(key)+value + data=tag(4,'RandomSeed',struct.pack('>q',0))+tag(8,'generatorName',name('default')) + for key,value in [('generatorVersion',1),('GameType',1),('version',19133),('SpawnX',0),('SpawnY',64),('SpawnZ',0)]:data+=tag(3,key,struct.pack('>i',value)) + data+=tag(8,'LevelName',name('Base Minerals seed zero'))+tag(1,'MapFeatures',b'\0')+tag(1,'initialized',b'\x01')+b'\0' + world=directory/'world';world.mkdir(exist_ok=True) + with gzip.open(world/'level.dat','wb') as stream:stream.write(b'\x0a\0\0'+tag(10,'Data',data)+b'\0') +def placement(ore):return ore['dimension_selectors'][SELECTOR] +def placements(ore):return [placement(ore)]+list(ore.get('dimensions',{}).values()) +def subset(expected,actual): + if isinstance(expected,dict): + for key,value in expected.items():assert key in actual,key;subset(value,actual[key]) + else:assert expected==actual,(expected,actual) + +def main(): + parser=argparse.ArgumentParser(description=__doc__) + for name in ('java','template','output'):parser.add_argument('--'+name,type=Path,required=True) + parser.add_argument('--mirror',type=Path);parser.add_argument('--quick',action='store_true') + parser.add_argument('--prior-suite',type=Path,help='Reuse completed census and compatibility reports with identical input jars, then revalidate recording and run timings in a new directory.') + args=parser.parse_args();args.output.mkdir(parents=True,exist_ok=False) + specs=json.loads((ROOT/'gradle/dependencies.json').read_text()) + base=[dependency(s,args.mirror) for s in specs if s.get('runtime')] + base += [ROOT/'build/libs/BaseMinerals-2.5.1.111021.jar',ROOT/'build/test-mods/baseminerals-probe-packaged.jar'] + native=json.loads((ROOT/'build/reports/native-provider.json').read_text()) + write_json(args.output/'source-fixtures.json',{str(p.relative_to(ROOT)):digest(p) for p in [Path(__file__).resolve(),ROOT/'build/reports/native-provider.json',ROOT/'tools/validation/legacy111/fixtures/orespawn3-baseminerals.json']}) + results={} + migration_failures=[] + def verify_os1_reload(report): + matches=[key for key,ore in report['profile']['ores'].items() if ore.get('block')=='baseminerals:lithium_ore'] + if len(matches)!=1: + migration_failures.append({'message':'OS1 reload reinserted the native lithium budget','rules':matches}) + write_json(args.output/'migration-failures.json',migration_failures) + def setup(name,seed=8675309,extra=(),world=None,override=None,config=None): + directory=args.output/name;prepare(directory,args.template,base+list(extra),world) + properties=directory/'server.properties';text=properties.read_text().replace('level-seed=8675309','level-seed='+str(seed));properties.write_text(text) + if seed==0 and world is None:seed_zero(directory) + if override is not None:write_json(directory/'config/baseminerals-orespawn.json',override) + if config is not None: + path=directory/'config/BaseMinerals.cfg';path.parent.mkdir(parents=True,exist_ok=True);path.write_text(config) + return directory + def check(directory,phase='first',properties=None): + result=run(args.java,directory,phase,properties or {}) + results[directory.name+':'+phase]=result + write_json(args.output/'results.json',results) + return result + if args.prior_suite: + prior=json.loads((args.prior_suite/'results.json').read_text()) + results={name:report for name,report in prior.items() if not name.startswith('timing-')} + assert len(results)==34 and all(r['passed'] for r in results.values()),'Prior suite must contain the 34 completed pre-benchmark reports' + expected={p.name:digest(p) for p in base} + for directory in args.prior_suite.iterdir(): + if directory.is_dir() and (directory/'input-jars.json').exists(): + inputs=json.loads((directory/'input-jars.json').read_text()) + assert all(inputs.get(name)==sha for name,sha in expected.items()),('Changed input jar',directory) + source=args.prior_suite/'seed-8675309/world';before=fingerprint(source) + overworld=results['recorded-overworld:first'];fixture=results['recorded-host-fixture:first'] + write_json(args.output/'reused-evidence.json',{'path':str(args.prior_suite),'results_sha256':digest(args.prior_suite/'results.json'),'source_files':before}) + # Reusing old reports cannot bypass the subsequently discovered OS1 reload gate. + imported=setup('os1-reload',world=args.prior_suite/'os1-import/world') + reloaded=check(imported,'later',{'minerals.sample':'true','minerals.width':'16'}) + verify_os1_reload(reloaded) + else: + width=3 if args.quick else 16 + properties={'minerals.sample':'true','minerals.width':str(width),'minerals.saved':'true'} + for seed in ([8675309] if args.quick else [0,1,8675309,123456789]): + directory=setup('seed-'+str(seed),seed) + first=check(directory,properties=properties) + assert first['seed']==seed,('Wrong world seed',seed,first['seed']) + subset(native['ores'],first['profile']['ores']) + reload=check(directory,'reload',properties) + assert {d:v['digest'] for d,v in first['dimensions'].items()}=={d:v['digest'] for d,v in reload['dimensions'].items()},'Reload changed ore census' + repeat=check(setup('repeat-'+str(seed),seed),properties=properties) + assert {d:v['digest'] for d,v in first['dimensions'].items()}=={d:v['digest'] for d,v in repeat['dimensions'].items()},'Seed is not deterministic' + if args.quick: + print('BASE_MINERALS_QUICK_SUITE_PASS',flush=True);return + source=args.output/'seed-8675309'/'world';before=fingerprint(source) + later=setup('later-chunks',world=source);check(later,'later',properties) + unchanged=check(later,'reload',properties) + original=results['seed-8675309:first'] + assert {d:v['digest'] for d,v in original['dimensions'].items()}=={d:v['digest'] for d,v in unchanged['dimensions'].items()},'Later generation changed old ore coordinates' + assert fingerprint(source)==before,'Copied source fixture was modified' + override=copy.deepcopy(native) + for ore in override['ores'].values():ore['enabled']=False + empty=check(setup('generation-disabled',override=override),properties={'minerals.sample':'true','minerals.noOres':'true','minerals.width':'16'}) + assert all(not value['counts'] for value in empty['dimensions'].values()) + for name in ['all']+NAMES+['silicon','charcoal']: + disabled=NAMES+['silicon','charcoal'] if name=='all' else [name] + config='Minerals {\n'+''.join(' B:Enable'+n.capitalize()+'=false\n' for n in disabled)+'}\n' + check(setup('disabled-'+name,config=config),properties={'minerals.sample':'true','minerals.width':'16','minerals.noOres':str(name=='all').lower()}) + optional=[dependency(s,args.mirror) for s in specs if s['id'] in ['tconstruct','mantle']] + check(setup('tinkers',extra=optional)) + # Imported OS3 rules must retain historical defaults/custom values, not rebalance. + legacy=json.loads((ROOT/'tools/validation/legacy111/fixtures/orespawn3-baseminerals.json').read_text()) + legacy['spawns']['lithium_ore']['parameters'].update(size=5,variation=0,frequency=2.75,minHeight=10,maxHeight=50) + legacy['spawns']['sulfur_ore']['enabled']=False + imported=setup('os3-import');write_json(imported/'config/orespawn3/baseminerals.json',legacy) + old_hash=digest(imported/'config/orespawn3/baseminerals.json') + result=check(imported) + p=placement(result['profile']['ores'][PREFIX+'lithium_ore']);subset({'min_y':10,'max_y':49,'frequency':2.75,'quantity':5},p) + assert not result['profile']['ores'][PREFIX+'sulfur_ore']['enabled'] + assert digest(imported/'config/orespawn3/baseminerals.json')==old_hash + check(imported,'reload') + os1=setup('os1-import');write_json(os1/'config/orespawn/baseminerals.json',{'dimensions':[{'dimension':'0','ores':[{'blockID':'baseminerals:lithium_ore','size':4,'variation':0,'frequency':1.5,'minHeight':8,'maxHeight':40}]}]}) + result=check(os1);matches=[o for o in result['profile']['ores'].values() if o.get('block')=='baseminerals:lithium_ore'];assert len(matches)==1 + assert '1.5' in json.dumps(matches) + # Saved world settings must remain authoritative without the old global import file. + reloaded=check(setup('os1-profile-reload',world=os1/'world'),'later',{'minerals.sample':'true','minerals.width':'16'}) + verify_os1_reload(reloaded) + existing=setup('saved-profile-precedence',world=source) + profile=existing/'world/serverconfig/orespawn-worldgen.json';value=json.loads(profile.read_text()) + for p in placements(value['ores'][PREFIX+'salt_ore']):p['frequency']=1.25 + write_json(profile,value) + override=copy.deepcopy(native) + for p in placements(override['ores'][PREFIX+'salt_ore']):p['frequency']=9 + write_json(existing/'config/baseminerals-orespawn.json',override);write_json(existing/'config/orespawn3/baseminerals.json',legacy) + result=check(existing);assert placement(result['profile']['ores'][PREFIX+'salt_ore'])['frequency']==1.25 + future=check(setup('native-precedence',override=override));assert placement(future['profile']['ores'][PREFIX+'salt_ore'])['frequency']==9 + # Record the actual baked attempts while delegating shape placement to OS4. + recorded=copy.deepcopy(native) + for name in NAMES: + for p in placements(recorded['ores'][PREFIX+name+'_ore']): + settings={k:p[k] for k in ('spread','vertical_spread','node_size')} + settings['length']=8;p['pattern']={'type':'basemineralsprobe:'+name,'settings':settings} + overworld=check(setup('recorded-overworld',override=recorded),properties={'minerals.record':'true','minerals.sample':'true','minerals.width':'16'}) + fixture=check(setup('recorded-host-fixture',override=recorded),properties={'minerals.custom':'true','minerals.record':'true','minerals.sample':'true','minerals.negativeHosts':'true','minerals.width':'16'}) + for name in NAMES: + p=placement(native['ores'][PREFIX+name+'_ore']);rate=p['frequency'];fraction=rate%1 + tolerance=5*math.sqrt(256*fraction*(1-fraction)) if fraction else 0 + # Both exact-domain and selector routes receive one placement budget. + # Different dimension population paths need not consume identical RNG. + for report in (overworld,fixture): + stats=report['recording'][name] + assert abs(stats['attempts']-256*rate)<=tolerance,(name,stats['attempts'],rate) + assert p['min_quantity']<=stats['minQuantity']<=stats['maxQuantity']<=p['max_quantity'] + assert not any(n for y,n in enumerate(stats['heights']) if yp['max_y']) + if p['height_distribution']=='triangle': + low,high=p['min_y'],p['max_y'];middle=(high-low+1)//3 + assert sum(stats['heights'][low+middle:high-middle+1])>sum(stats['heights'][low:low+middle]),name + if p['height_distribution']=='bottom_triangle': + midpoint=(p['min_y']+p['max_y'])//2 + assert sum(stats['heights'][:midpoint+1])>sum(stats['heights'][midpoint+1:]),name + # Same engine/JVM/seed, three isolated runs for both sets of declarations. + historical=copy.deepcopy(native) + for name in NAMES: + for p in placements(historical['ores'][PREFIX+name+'_ore']):p.update(min_y=0,max_y=31 if name=='sulfur' else 95,frequency=8 if name in ('lithium','niter','salt') else 6,min_quantity=4,max_quantity=11,pattern='default',height_distribution='uniform') + timings={} + import statistics + for label,definition in [('historical',historical),('rebalanced',native)]: + times=[] + for index in range(3): + result=check(setup('timing-'+label+'-'+str(index),override=definition),properties={'minerals.custom':'true','minerals.sample':'true','minerals.width':'16'}) + times.append(result['dimensions']['42']['generationNanos']) + timings[label]={'runs':times,'median':statistics.median(times)} + timings['ratio']=timings['rebalanced']['median']/timings['historical']['median'];write_json(args.output/'timings.json',timings) + assert timings['ratio']<=1.2,('Generation regression needs investigation',timings) + assert fingerprint(source)==before + assert not migration_failures,migration_failures + print('BASE_MINERALS_PACKAGED_SUITE_PASS',len(results),'runs',flush=True) + +if __name__=='__main__':main() diff --git a/tools/validation/legacy111/run_policy_fixture.py b/tools/validation/legacy111/run_policy_fixture.py new file mode 100644 index 0000000..0a6e0b3 --- /dev/null +++ b/tools/validation/legacy111/run_policy_fixture.py @@ -0,0 +1,64 @@ +"""Test the OS4 managed-source contract using an explicitly synthetic sibling.""" +import argparse,copy,json +from pathlib import Path +from run_runtime_checks import ROOT,dependency,prepare,run,write_json +from run_packaged_suite import fingerprint + +def main(): + parser=argparse.ArgumentParser() + for name in ('java','template','output'):parser.add_argument('--'+name,type=Path,required=True) + parser.add_argument('--mirror',type=Path) + args=parser.parse_args();args.output.mkdir(parents=True,exist_ok=False) + specs=json.loads((ROOT/'gradle/dependencies.json').read_text()) + jars=[dependency(s,args.mirror) for s in specs if s.get('runtime')]+[ROOT/'build/libs/BaseMinerals-2.5.1.111021.jar',ROOT/'build/test-mods/baseminerals-probe-packaged.jar'] + fixture=ROOT/'build/test-mods/synthetic-source-policy-fixture-packaged.jar' + properties={'minerals.sample':'true','minerals.shared':'true','minerals.width':'16'} + balanced=args.output/'balanced';prepare(balanced,args.template,jars+[fixture]) + result=run(args.java,balanced,'first',properties) + policies=result['profile']['ore_source_policies'];keys=['orespawn:'+m+'|minecraft:overworld' for m in ('sulfur','lithium')] + for key in keys: + policy=policies[key];assert policy['mode']=='consolidated' and policy['output_mode']=='balanced',policy + assert len(policy['outputs'])==2 and len(policy['placement_sources'])==1,policy + assert next(iter(policy['placement_sources'].values())).startswith('baseminerals:'),policy + source=balanced/'world';before=fingerprint(source);results={'balanced':result} + for label,mode,owner in [('single-base','single','baseminerals'),('single-sibling','single','electricadvantage'),('custom','custom','electricadvantage'),('weighted','custom','electricadvantage'),('original','custom','baseminerals')]: + target=args.output/label;prepare(target,args.template,jars+[fixture],source) + path=target/'world/serverconfig/orespawn-worldgen.json';profile=json.loads(path.read_text()) + for key in keys: + policy=profile['ore_source_policies'][key] + selected=next(c['source_id'] for c in policy['candidates'] if c['owner']==owner) + policy.update(mode='keep_separate' if label=='original' else 'consolidated',output_mode=mode,outputs={selected:1},review_required=False) + if label=='weighted': + policy['outputs']={c['source_id']:3 if c['owner']=='electricadvantage' else 1 for c in policy['candidates']} + write_json(path,profile);report=run(args.java,target,'later',properties);results[label]=report + outputs=report['dimensions']['0']['sharedOutputs'] + if label not in ('original','weighted'): + assert any(key.startswith(owner+':') and count>0 for key,count in outputs.items()),outputs + assert not any(not key.startswith(owner+':') and count>0 for key,count in outputs.items()),outputs + for material,foreign in [('lithium','li'),('sulfur','sulfur')]: + def total(label): + values=results[label]['dimensions']['0']['sharedOutputs'] + return values.get('baseminerals:'+material+'_ore',0)+values.get('electricadvantage:'+foreign+'_ore',0) + assert total('single-base')==total('single-sibling')==total('original'),material + weighted=results['weighted']['dimensions']['0']['sharedOutputs'] + own=weighted.get('baseminerals:'+material+'_ore',0);sibling=weighted.get('electricadvantage:'+foreign+'_ore',0) + assert own>0 and 2.5 oldConsumer=Class.forName("review.minerals.LegacyConsumer"); + require(oldConsumer.getMethod("material").invoke(null)==Materials.getMaterialByName("lithium"),"Old binary linked to a different material registry",failures); + require(oldConsumer.getMethod("proxy").invoke(null)==zone.moddev.mc.baseminerals.BaseMinerals.proxy,"Old binary linked to a different proxy",failures); + require(oldConsumer.getMethod("reflected").invoke(null)==zone.moddev.mc.baseminerals.BaseMinerals.class,"Old reflection name did not relink",failures); + require(com.mcmoddev.lib.util.ConfigBase.class.isAssignableFrom(Class.forName("review.minerals.LegacyConfiguration")),"Old configuration subclass lost its shared API",failures); + report.addProperty("legacyBinarySharedState",failures.size()==0); + for(String name:ORES) { + boolean enabled=Config.isEnabled(name); + require(Materials.hasMaterial(name)==enabled,"Material switch: "+name,failures); + require(ForgeRegistries.BLOCKS.containsKey(new ResourceLocation("baseminerals",name+"_ore"))==enabled,"Ore registry: "+name,failures); + if(enabled)require(Materials.getMaterialByName(name).hasItem(Names.POWDER),"Missing powder: "+name,failures); + } + for(String name:new String[]{"lithium","silicon"}) if(Config.isEnabled(name)) { + MMDMaterial material=Materials.getMaterialByName(name); + require(material.hasItem(Names.INGOT)&&material.hasItem(Names.NUGGET),"Metal forms missing: "+name,failures); + require(material.getFluid()!=null&&material.getFluidBlock()!=null,"Molten forms missing: "+name,failures); + if(material.getFluid()!=null) require(net.minecraftforge.fluids.FluidRegistry.getBucketFluids().contains(material.getFluid()),"Bucket missing: "+name,failures); + } + require(!ForgeRegistries.BLOCKS.containsKey(new ResourceLocation("baseminerals","charcoal_ore")),"Charcoal ore registered",failures); + require(!ForgeRegistries.BLOCKS.containsKey(new ResourceLocation("baseminerals","silicon_ore")),"Silicon ore registered",failures); + require(Materials.hasMaterial("charcoal"),"Base Metals shared charcoal missing",failures); + if(Loader.isModLoaded("tconstruct")) CastingChecks.check(failures); + require(OreSpawnApi.getActiveProfile(server).isPresent(),"No active OreSpawn profile",failures); + if(OreSpawnApi.getActiveProfile(server).isPresent()) { + JsonObject profile=OreSpawnApi.getActiveProfile(server).get().toJson(); report.add("profile",profile); + if(!Loader.isModLoaded("mineralogy"))require(profile.getAsJsonObject("terrain_dimensions").entrySet().size()==0,"Ore-only mods activated strata",failures); + if(Boolean.getBoolean("minerals.sample"))sample(server,profile,report,failures); + } + if(Boolean.getBoolean("minerals.saved"))saved(server.getWorld(0),phase,failures); + report.add("recording",RecordingPatterns.report(failures)); + report.add("failures",failures);report.addProperty("passed",failures.size()==0); + Files.write(Paths.get(phase+"-minerals.json"),(new GsonBuilder().setPrettyPrinting().create().toJson(report)+"\n").getBytes(StandardCharsets.UTF_8)); + System.out.println("BASE_MINERALS_RUNTIME_"+(failures.size()==0?"PASS":"FAIL")+" "+failures); + } + private static void sample(MinecraftServer server,JsonObject profile,JsonObject report,JsonArray failures) throws Exception { + JsonObject dimensions=new JsonObject(); int width=Integer.getInteger("minerals.width",16); + int origin="later".equals(System.getProperty("minerals.phase"))?128:64; + int[] ids=Boolean.getBoolean("minerals.custom")?new int[]{42}:new int[]{0,-1,1}; + for(int id:ids) { + if(DimensionManager.getWorld(id)==null)DimensionManager.initDimension(id); + WorldServer world=server.getWorld(id); + long start=System.nanoTime(); + for(int x=origin-2;x=p.get("min_y").getAsInt()&&y<=p.get("max_y").getAsInt(),"Ore outside height: "+name+" at "+y,failures); + } + if(Boolean.getBoolean("minerals.negativeHosts"))require(y<21||y>28,"Ore replaced protected host at Y="+y,failures); + if(Boolean.getBoolean("minerals.variantHosts"))increment(variants,String.valueOf(1+(y-1)%6)); + } + } + if((id==0||id==42)&&!Boolean.getBoolean("minerals.noOres")) for(String name:ORES) + if(Config.isEnabled(name)) { + JsonObject rule=rule(profile,name+"_ore"); + if(rule!=null && rule.get("enabled").getAsBoolean() && !(Boolean.getBoolean("minerals.shared") && ("lithium".equals(name)||"sulfur".equals(name)))) require(counts.has(name+"_ore"),"No generated sample: "+name,failures); + } + JsonObject result=new JsonObject();result.add("counts",counts);result.add("sharedOutputs",shared);result.add("heights",heights);result.addProperty("chunks",width*width); + if(Boolean.getBoolean("minerals.variantHosts")) { + for(int variant=1;variant<=6;variant++)require(variants.has(String.valueOf(variant)),"No positive stone metadata sample: "+variant,failures); + result.add("hostVariants",variants); + } + result.addProperty("generationNanos",generated);result.addProperty("digest",hex(digest.digest()));dimensions.add(String.valueOf(id),result); + result.addProperty("sharedDigest",hex(sharedDigest.digest())); + System.out.println("BASE_MINERALS_CENSUS dimension="+id+" chunks="+(width*width)+" counts="+counts); + } + report.add("dimensions",dimensions); + } + private static JsonObject rule(JsonObject profile,String block) { + JsonObject ores=profile.getAsJsonObject("ores"); + JsonObject standard=ores.getAsJsonObject("baseminerals:legacy/"+block); + if(standard!=null)return standard; + // OS1 owns its converted IDs; match that saved declaration by its output. + for(Map.Entry entry:ores.entrySet()) { + JsonObject candidate=entry.getValue().getAsJsonObject(); + if(candidate.has("block")&&("baseminerals:"+block).equals(candidate.get("block").getAsString()))return candidate; + } + return null; + } + private static JsonObject placement(JsonObject rule,int dimension) { + String id=dimension==0?"minecraft:overworld":dimension==-1?"minecraft:the_nether":dimension==1?"minecraft:the_end":"minerals_fixture"; + if(rule.has("dimensions")&&rule.getAsJsonObject("dimensions").has(id))return rule.getAsJsonObject("dimensions").getAsJsonObject(id); + return rule.getAsJsonObject("dimension_selectors").getAsJsonObject("orespawn:all_except_nether_end"); + } + private static JsonObject activePlacement(JsonObject profile,JsonObject rule,int dimension) { + if(Boolean.getBoolean("minerals.shared")&&rule.has("material")) { + String domain=dimension==0?"minecraft:overworld":dimension==-1?"minecraft:the_nether":dimension==1?"minecraft:the_end":"minerals_fixture"; + JsonObject policies=profile.getAsJsonObject("ore_source_policies"); + JsonObject policy=policies==null?null:policies.getAsJsonObject(rule.get("material").getAsString()+"|"+domain); + if(policy!=null&&"consolidated".equals(policy.get("mode").getAsString())) { + JsonElement selected=policy.getAsJsonObject("placement_sources").get("orespawn:standard"); + if(selected!=null) { + JsonObject source=profile.getAsJsonObject("ores").getAsJsonObject(selected.getAsString()); + if(source!=null)return placement(source,dimension); + } + } + } + return placement(rule,dimension); + } + private static void increment(JsonObject object,String key){object.addProperty(key,object.has(key)?object.get(key).getAsInt()+1:1);} + private static String hex(byte[] data){StringBuilder s=new StringBuilder();for(byte b:data)s.append(String.format("%02x",b));return s.toString();} + private static void saved(WorldServer world,String phase,JsonArray failures) { + BlockPos position=new BlockPos(0,180,0); + if("first".equals(phase)) { + world.setBlockState(position,Blocks.CHEST.getDefaultState());TileEntityChest chest=(TileEntityChest)world.getTileEntity(position);int slot=0; + for(String name:ORES)if(Config.isEnabled(name)) { + ItemStack stack=Materials.getMaterialByName(name).getItemStack(Names.POWDER,3); + stack.setStackDisplayName("Saved "+name);stack.getTagCompound().setString("BaseMineralsCheck",name);chest.setInventorySlotContents(slot++,stack); + } + } + TileEntityChest chest=(TileEntityChest)world.getTileEntity(position);require(chest!=null,"Saved chest missing",failures);if(chest==null)return;int slot=0; + for(String name:ORES)if(Config.isEnabled(name)) { + ItemStack stack=chest.getStackInSlot(slot++); + require(!stack.isEmpty()&&stack.getCount()==3&&("Saved "+name).equals(stack.getDisplayName())&&name.equals(stack.getTagCompound().getString("BaseMineralsCheck")),"Saved stack changed: "+name,failures); + } + } + private static final class CastingChecks { + static void check(JsonArray failures) { + for(String name:new String[]{"lithium","silicon"})if(Config.isEnabled(name)) { + MMDMaterial material=Materials.getMaterialByName(name); + net.minecraftforge.fluids.Fluid fluid=material.getFluid(); + require(fluid!=null,"Casting fluid missing: "+name,failures); + if(fluid!=null) { + java.util.List table = + slimeknights.tconstruct.library.TinkerRegistry.getAllTableCastingRecipes(); + require(hasCasting(table,material.getItemStack(Names.INGOT),fluid,144),"Ingot casting missing: "+name,failures); + require(hasCasting(table,material.getItemStack(Names.NUGGET),fluid,16),"Nugget casting missing: "+name,failures); + require(hasCasting(slimeknights.tconstruct.library.TinkerRegistry.getAllBasinCastingRecipes(), + material.getBlockItemStack(Names.BLOCK),fluid,1296),"Block casting missing: "+name,failures); + } + slimeknights.tconstruct.library.smeltery.MeltingRecipe melting = + slimeknights.tconstruct.library.TinkerRegistry.getMelting(material.getItemStack(Names.INGOT)); + require(melting!=null&&melting.getResult().getFluid()==fluid&&melting.getResult().amount==144, + "Ingot melting missing: "+name,failures); + } + } + + private static boolean hasCasting(java.util.List recipes, + ItemStack output, net.minecraftforge.fluids.Fluid fluid, int amount) { + return recipes.stream().anyMatch(recipe -> recipe.matches(ItemStack.EMPTY,fluid) + && recipe.getFluidAmount()==amount + && ItemStack.areItemsEqual(recipe.getResult(ItemStack.EMPTY,fluid),output)); + } + } +} diff --git a/tools/validation/legacy111/runtime-probe/review/minerals/ClientChecks.java b/tools/validation/legacy111/runtime-probe/review/minerals/ClientChecks.java new file mode 100644 index 0000000..b87f1a7 --- /dev/null +++ b/tools/validation/legacy111/runtime-probe/review/minerals/ClientChecks.java @@ -0,0 +1,91 @@ +package review.minerals; + +import com.google.gson.*; +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; +import java.nio.file.*; +import java.util.concurrent.Future; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.*; +import net.minecraft.item.ItemStack; +import net.minecraft.world.*; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.client.FMLClientHandler; +import net.minecraftforge.fml.client.IModGuiFactory; +import net.minecraftforge.fml.client.config.GuiConfig; +import net.minecraftforge.fml.client.config.GuiConfigEntries.BooleanEntry; +import net.minecraftforge.fml.common.Loader; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.event.FMLInitializationEvent; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; +import zone.moddev.mc.baseminerals.util.Config; +import zone.moddev.mc.orespawn.api.client.*; + +/** Automated UI checks, separate from the distributable mod and manual acceptance. */ +@Mod(modid="basemineralsclientprobe",name="Base Minerals client checks",version="1",acceptableRemoteVersions="*",dependencies="required-after:basemineralsprobe") +public final class ClientChecks { + @Mod.EventHandler public void init(FMLInitializationEvent event){if(Boolean.getBoolean("minerals.client"))MinecraftForge.EVENT_BUS.register(new Checks());} + static void require(boolean value,String detail){if(!value)throw new AssertionError(detail);} + public static final class Checks { + long started=System.currentTimeMillis();int state,ticks;boolean finished;GuiScreen parent;GuiConfig screen;Future check;net.minecraft.server.MinecraftServer integrated; + @SubscribeEvent public void tick(TickEvent.ClientTickEvent event) throws Exception { + if(event.phase!=TickEvent.Phase.END||finished)return;Minecraft mc=Minecraft.getMinecraft(); + try { + if(state==0&&mc.currentScreen instanceof GuiMainMenu&&++ticks>=20) { + parent=mc.currentScreen; + IModGuiFactory factory=FMLClientHandler.instance().getGuiFactoryFor(Loader.instance().getIndexedModList().get("baseminerals")); + require(factory!=null&&factory.hasConfigGui(),"Forge config factory missing"); + screen=(GuiConfig)factory.createConfigGui(parent);mc.displayGuiScreen(screen);state=1;ticks=0; + } else if(state==1&&++ticks>=20) { + require(screen.parentScreen==parent&&screen.configElements.size()==9,"Wrong Forge parent or material switches"); + require(screen.allRequireMcRestart,"Switches must require restart"); + for(net.minecraftforge.fml.client.config.IConfigElement element:screen.configElements) + require(net.minecraft.client.resources.I18n.hasKey(element.getLanguageKey()),"Untranslated switch: "+element.getName()); + screenshot(mc,"forge-mods-config.png");entry(screen).valueButtonPressed(0); + invoke(screen,"keyTyped",new Class[]{char.class,int.class},'\0',1); + require(mc.currentScreen==parent,"Escape lost parent");require(Config.getConfiguration().get("Minerals","EnableLithium",true).getBoolean(),"Escape saved a cancelled switch"); + WorldSettingsExtension extension=WorldSettingsExtensionRegistry.extensions().stream().filter(e->"baseminerals".equals(e.id().getResourceDomain())).findFirst().orElse(null); + require(extension!=null,"OreSpawn config entry missing");screen=(GuiConfig)extension.createScreen(parent);mc.displayGuiScreen(screen);state=2;ticks=0; + } else if(state==2&&++ticks>=20) { + require(screen.parentScreen==parent&&screen.configElements.size()==9,"Wrong OreSpawn parent or switches");screenshot(mc,"orespawn-mods-config.png"); + entry(screen).valueButtonPressed(0);invoke(screen,"actionPerformed",new Class[]{GuiButton.class},new GuiButton(2000,0,0,"Done")); + require(!Config.getConfiguration().get("Minerals","EnableLithium",true).getBoolean(),"Done did not save requested switch"); + require(Config.isEnabled("lithium")&&com.mcmoddev.lib.init.Materials.hasMaterial("lithium"),"GUI changed active registries"); + require(new String(Files.readAllBytes(Paths.get("config/BaseMinerals.cfg")),StandardCharsets.UTF_8).contains("B:EnableLithium=false"),"Switch not persisted"); + screen=new zone.moddev.mc.baseminerals.client.BaseMineralsConfigScreen(parent);mc.displayGuiScreen(screen);entry(screen).valueButtonPressed(0); + invoke(screen,"actionPerformed",new Class[]{GuiButton.class},new GuiButton(2000,0,0,"Done"));mc.displayGuiScreen(parent); + mc.launchIntegratedServer("minerals-check","Base Minerals disposable checks",new WorldSettings(8675309L,GameType.CREATIVE,false,false,WorldType.DEFAULT));state=3; + } else if(state==3&&mc.world!=null&&mc.player!=null&&check==null) { + for(net.minecraft.block.Block block:net.minecraftforge.fml.common.registry.ForgeRegistries.BLOCKS.getValues()) { + if(!"baseminerals".equals(block.getRegistryName().getResourceDomain()))continue; + for(net.minecraft.block.state.IBlockState variant:block.getBlockState().getValidStates()) + require(mc.getBlockRendererDispatcher().getBlockModelShapes().getModelForState(variant)!=mc.getRenderItem().getItemModelMesher().getModelManager().getMissingModel(),"Missing baked block: "+variant); + } + for(net.minecraft.item.Item item:net.minecraftforge.fml.common.registry.ForgeRegistries.ITEMS.getValues()) { + if(!"baseminerals".equals(item.getRegistryName().getResourceDomain()))continue; + require(mc.getRenderItem().getItemModelMesher().getItemModel(new ItemStack(item))!=mc.getRenderItem().getItemModelMesher().getModelManager().getMissingModel(),"Missing baked item: "+item.getRegistryName()); + } + OreModelChecks.verify(mc); + check=mc.getIntegratedServer().addScheduledTask(()->{try{BaseMineralsProbe.run(mc.getIntegratedServer());}catch(Exception e){throw new RuntimeException(e);}}); + } else if(state==3&&check!=null&&check.isDone()) { + check.get();screenshot(mc,"integrated-world.png");integrated=mc.getIntegratedServer();integrated.initiateShutdown();state=4; + } else if(state==4&&integrated.isServerStopped()) { + report(true,"Config save/cancel/restart, both parents, baked models and integrated saved stacks");finished=true;mc.shutdown(); + } + if(System.currentTimeMillis()-started>240000)throw new AssertionError("Client timeout, state="+state); + } catch(Throwable failure){report(false,failure.toString());failure.printStackTrace();finished=true;mc.shutdown();} + } + static BooleanEntry entry(GuiConfig config){for(int i=0;i[] types,Object...args)throws Exception { + Method method;try{method=GuiConfig.class.getDeclaredMethod(name,types);}catch(NoSuchMethodException e){method=GuiConfig.class.getDeclaredMethod("keyTyped".equals(name)?"func_73869_a":"func_146284_a",types);} + method.setAccessible(true);method.invoke(config,args); + } + static void screenshot(Minecraft mc,String name){net.minecraft.util.ScreenShotHelper.saveScreenshot(mc.mcDataDir,name,mc.displayWidth,mc.displayHeight,mc.getFramebuffer());} + static void report(boolean pass,String detail)throws Exception { + JsonObject result=new JsonObject();result.addProperty("passed",pass);result.addProperty("detail",detail); + Files.write(Paths.get(System.getProperty("minerals.phase")+"-client.json"),(new GsonBuilder().setPrettyPrinting().create().toJson(result)+"\n").getBytes(StandardCharsets.UTF_8)); + System.out.println("BASE_MINERALS_CLIENT_"+(pass?"PASS":"FAIL")+" "+detail); + } + } +} diff --git a/tools/validation/legacy111/runtime-probe/review/minerals/LegacyFixtureGenerator.java b/tools/validation/legacy111/runtime-probe/review/minerals/LegacyFixtureGenerator.java new file mode 100644 index 0000000..5ea588b --- /dev/null +++ b/tools/validation/legacy111/runtime-probe/review/minerals/LegacyFixtureGenerator.java @@ -0,0 +1,27 @@ +package review.minerals; + +import java.nio.file.*; +import org.objectweb.asm.*; + +/** Creates a compiled old-namespace consumer without bundling a second library. */ +public final class LegacyFixtureGenerator implements Opcodes { + public static void main(String[] arguments) throws Exception { + ClassWriter writer=new ClassWriter(0); + writer.visit(V1_8,ACC_PUBLIC,"review/minerals/LegacyConsumer",null,"com/mcmoddev/baseminerals/proxy/CommonProxy",null); + MethodVisitor method=writer.visitMethod(ACC_PUBLIC|ACC_STATIC,"material","()Ljava/lang/Object;",null,null); + method.visitCode();method.visitLdcInsn("lithium"); + method.visitMethodInsn(INVOKESTATIC,"com/mcmoddev/baseminerals/init/Materials","getMaterialByName","(Ljava/lang/String;)Lcom/mcmoddev/lib/material/MMDMaterial;",false); + method.visitInsn(ARETURN);method.visitMaxs(1,0);method.visitEnd(); + method=writer.visitMethod(ACC_PUBLIC|ACC_STATIC,"proxy","()Ljava/lang/Object;",null,null); + method.visitCode();method.visitFieldInsn(GETSTATIC,"com/mcmoddev/baseminerals/BaseMinerals","proxy","Lcom/mcmoddev/baseminerals/proxy/CommonProxy;"); + method.visitInsn(ARETURN);method.visitMaxs(1,0);method.visitEnd(); + method=writer.visitMethod(ACC_PUBLIC|ACC_STATIC,"reflected","()Ljava/lang/Class;",null,new String[]{"java/lang/ClassNotFoundException"}); + method.visitCode();method.visitLdcInsn("com.mcmoddev.baseminerals.BaseMinerals"); + method.visitMethodInsn(INVOKESTATIC,"java/lang/Class","forName","(Ljava/lang/String;)Ljava/lang/Class;",false); + method.visitInsn(ARETURN);method.visitMaxs(1,0);method.visitEnd();writer.visitEnd(); + Path file=Paths.get(arguments[0],"review/minerals/LegacyConsumer.class");Files.createDirectories(file.getParent());Files.write(file,writer.toByteArray()); + ClassWriter config=new ClassWriter(0); + config.visit(V1_8,ACC_PUBLIC,"review/minerals/LegacyConfiguration",null,"com/mcmoddev/baseminerals/util/Config",null);config.visitEnd(); + Files.write(file.resolveSibling("LegacyConfiguration.class"),config.toByteArray()); + } +} diff --git a/tools/validation/legacy111/runtime-probe/review/minerals/OreModelChecks.java b/tools/validation/legacy111/runtime-probe/review/minerals/OreModelChecks.java new file mode 100644 index 0000000..dc46ed6 --- /dev/null +++ b/tools/validation/legacy111/runtime-probe/review/minerals/OreModelChecks.java @@ -0,0 +1,86 @@ +package review.minerals; + +import com.mcmoddev.lib.init.Materials; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.List; +import net.minecraft.block.Block; +import net.minecraft.block.state.IBlockState; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.block.model.BakedQuad; +import net.minecraft.client.renderer.block.model.IBakedModel; +import net.minecraft.client.resources.IResource; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.BlockRenderLayer; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.fml.common.registry.ForgeRegistries; +import net.minecraftforge.oredict.OreDictionary; +import zone.moddev.mc.baseminerals.util.Config; + +import static review.minerals.ClientChecks.require; + +/** Checks the texture and geometry of the baked block and inventory models. */ +final class OreModelChecks { + static void verify(Minecraft minecraft) throws IOException { + int overlays = 0; + int solids = 0; + for (String material : BaseMineralsProbe.ORES) { + if (!Config.isEnabled(material)) continue; + String name = material + "_ore"; + ResourceLocation id = new ResourceLocation("baseminerals", name); + Block ore = ForgeRegistries.BLOCKS.getValue(id); + require(ore != null && id.equals(ore.getRegistryName()), "Wrong ore block ID: " + id); + Item item = Item.getItemFromBlock(ore); + require(id.equals(item.getRegistryName()), "Wrong ore item ID: " + id); + String oreKey = "ore" + Materials.getMaterialByName(material).getCapitalizedName(); + require(OreDictionary.getOres(oreKey).stream().anyMatch(stack -> stack.getItem() == item), + "Missing Ore Dictionary entry: " + oreKey); + require(ore.getBlockLayer() == BlockRenderLayer.CUTOUT_MIPPED, "Wrong ore render layer: " + id); + + String overlay = "baseminerals:blocks/ore_overlays/" + name; + boolean layered = hasOverlay(minecraft, name); + String base = layered ? "minecraft:blocks/stone" : "baseminerals:blocks/" + name; + IBlockState state = ore.getDefaultState(); + IBakedModel placed = minecraft.getBlockRendererDispatcher().getBlockModelShapes().getModelForState(state); + IBakedModel inventory = minecraft.getRenderItem().getItemModelMesher().getItemModel(new ItemStack(item)); + verifyModel(placed, state, base, layered ? overlay : null); + verifyModel(inventory, null, base, layered ? overlay : null); + if (layered) overlays++; + else solids++; + } + System.out.println("BASE_MINERALS_ORE_MODELS_PASS solids=" + solids + " overlays=" + overlays); + } + + private static boolean hasOverlay(Minecraft minecraft, String name) throws IOException { + ResourceLocation path = new ResourceLocation("baseminerals", "textures/blocks/ore_overlays/" + name + ".png"); + try (IResource resource = minecraft.getResourceManager().getResource(path)) { + return true; + } catch (FileNotFoundException missing) { + return false; + } + } + + private static void verifyModel(IBakedModel model, IBlockState state, String base, String overlay) { + require(base.equals(model.getParticleTexture().getIconName()), "Wrong ore particles: " + base); + for (EnumFacing face : EnumFacing.values()) { + List quads = model.getQuads(state, face, 0); + require(quads.size() == (overlay == null ? 1 : 2), "Wrong ore face count: " + base + " " + face); + verifyFace(quads, face, base, 0.0F); + if (overlay != null) verifyFace(quads, face, overlay, 0.05F / 16.0F); + } + } + + private static void verifyFace(List quads, EnumFacing face, String texture, float offset) { + BakedQuad quad = quads.stream().filter(candidate -> texture.equals(candidate.getSprite().getIconName())) + .findFirst().orElseThrow(() -> new AssertionError("Missing ore sprite: " + texture)); + int axis = face.getAxis().ordinal(); + float expected = face.getAxisDirection() == EnumFacing.AxisDirection.POSITIVE ? 1.0F + offset : -offset; + int[] data = quad.getVertexData(); + for (int vertex = 0; vertex < 4; vertex++) { + float depth = Float.intBitsToFloat(data[vertex * (data.length / 4) + axis]); + require(Math.abs(depth - expected) < 0.0001F, "Wrong ore face depth: " + texture + " " + face); + } + } +} diff --git a/tools/validation/legacy111/runtime-probe/review/minerals/RecordingPatterns.java b/tools/validation/legacy111/runtime-probe/review/minerals/RecordingPatterns.java new file mode 100644 index 0000000..512ba58 --- /dev/null +++ b/tools/validation/legacy111/runtime-probe/review/minerals/RecordingPatterns.java @@ -0,0 +1,41 @@ +package review.minerals; + +import com.google.gson.*; +import java.util.*; +import net.minecraft.util.ResourceLocation; +import zone.moddev.mc.orespawn.api.*; + +/** Test-only counting wrapper; delegates placement to the published built-in codecs. */ +final class RecordingPatterns { + private static final Map COUNTS=new LinkedHashMap<>(); + private static final Map SETTINGS=new LinkedHashMap<>(); + static void register() { + for(String name:BaseMineralsProbe.ORES) { + OrePatternType type=OrePatternType.create(StandardPatternSettings.CODEC,settings->{ + JsonObject json=new JsonObject();json.addProperty("spread",settings.spread());json.addProperty("vertical_spread",settings.verticalSpread()); + json.addProperty("node_size",settings.nodeSize());json.addProperty("length",settings.length()); + SETTINGS.put(name,new JsonParser().parse(json.toString()).getAsJsonObject()); + CompiledOrePattern delegate=OreSpawnPatternRegistry.registry().getValue(new ResourceLocation("orespawn","lithium".equals(name)?"vein":"clusters")).decode(json); + return context->{ + int cx=Math.floorDiv(context.originX(),16),cz=Math.floorDiv(context.originZ(),16); + if(cx>=64&&cx<80&&cz>=64&&cz<80) { + Stats stats=COUNTS.computeIfAbsent(name,key->new Stats());stats.attempts++;stats.heights[context.originY()]++; + stats.min=Math.min(stats.min,context.quantity());stats.max=Math.max(stats.max,context.quantity()); + } + return delegate.place(context); + }; + }); + type.setRegistryName(new ResourceLocation("basemineralsprobe",name));OreSpawnPatternRegistry.registry().register(type); + } + } + static JsonObject report(JsonArray failures) { + JsonObject result=new JsonObject(); + for(Map.Entry entry:COUNTS.entrySet()) { + Stats stats=entry.getValue();JsonObject value=new JsonObject();value.addProperty("attempts",stats.attempts);value.addProperty("minQuantity",stats.min);value.addProperty("maxQuantity",stats.max); + JsonArray heights=new JsonArray();for(int n:stats.heights)heights.add(new JsonPrimitive(n));value.add("heights",heights);result.add(entry.getKey(),value); + value.add("settings",SETTINGS.get(entry.getKey())); + } + return result; + } + private static final class Stats {int attempts,min=Integer.MAX_VALUE,max;final int[] heights=new int[256];} +} diff --git a/tools/validation/requirements.txt b/tools/validation/requirements.txt new file mode 100644 index 0000000..ad5d466 --- /dev/null +++ b/tools/validation/requirements.txt @@ -0,0 +1,2 @@ +jsonschema==4.23.0 +PyYAML==6.0.3