diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml new file mode 100644 index 00000000..2640cdf6 --- /dev/null +++ b/.github/workflows/changelog.yml @@ -0,0 +1,72 @@ +# Generates a changelog between two version-tagged commits. +name: Generate Changelog + +# Controls when the action will run. Workflow runs when manually triggered using the UI +# or API. +on: workflow_dispatch + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + generate: + name: Generate Changelog + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Unshallow + run: git fetch --prune --unshallow + + - name: Find Current Tag + id: current + uses: jimschubert/query-tag-action@v1 + with: + include: 'v*' + exclude: '*-rc*' + commit-ish: '@' + skip-unshallow: 'true' + + - name: Previous Tag + id: last + uses: jimschubert/query-tag-action@v1 + with: + include: 'v*' + exclude: ${{steps.current.outputs.tag}} + skip-unshallow: 'true' + + - name: Generate changelog + uses: jimschubert/beast-changelog-action@v1 + with: + GITHUB_TOKEN: ${{github.token}} + CONFIG_LOCATION: .github/changelog.json + FROM: ${{steps.last.outputs.tag}} + TO: ${{steps.current.outputs.tag}} + OUTPUT: .github/CHANGELOG.md + + - name: Read CHANGELOG file + id: getchangelog + run: echo "::set-output name=changelog::$(cat .github/CHANGELOG.md)" + + - name: View Changelog + run: cat .github/CHANGELOG.md + + - name: Add Artifact + uses: actions/upload-artifact@v2 + with: + name: out + path: .github/CHANGELOG.md + + view: + name: View Changelog Output + runs-on: ubuntu-latest + needs: [generate] + steps: + - name: Download Build Results + uses: actions/download-artifact@v2 + with: + name: out + path: out + + - run: cat out/CHANGELOG.md + + # {needs.generate.outputs.changelog}} diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml new file mode 100644 index 00000000..a92077ee --- /dev/null +++ b/.github/workflows/ci-tests.yml @@ -0,0 +1,60 @@ +name: Gradle Tests (CI) + +on: push + +jobs: + vars: + name: Get Variables + runs-on: ubuntu-20.04 + outputs: + release_type: ${{steps.cf_release_type.outputs.value }} + mod_version: ${{steps.mod_version.outputs.value }} + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Release Type + id: cf_release_type + uses: christian-draeger/read-properties@1.0.1 + with: + path: './gradle.properties' + property: 'cf_release_type' + + - name: Mod Version + id: mod_version + uses: christian-draeger/read-properties@1.0.1 + with: + path: './gradle.properties' + property: 'mod_version' + + jar: + name: Publish JAR + runs-on: ubuntu-20.04 + needs: [vars] + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Set up JDK 8 + uses: actions/setup-java@v1 + with: + java-version: "8.0.282" + + - name: Cache Gradle packages + uses: actions/cache@v2 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: ${{ runner.os }}-gradle- + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Test JAR with Gradle + run: ./gradlew test +# run: ./gradlew publish + env: + GITHUB_ACTOR: ${{ secrets.GITHUB_ACTOR }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml deleted file mode 100644 index 8185f0ce..00000000 --- a/.github/workflows/gradle.yml +++ /dev/null @@ -1,58 +0,0 @@ -# This workflow will build a Java project with Gradle -# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle - -name: Java CI with Gradle - -on: - push: - tags: v* - workflow_dispatch: - -jobs: - build: - name: Gradle Build and Publish - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - name: Caches - uses: burrunan/gradle-cache-action@v1 - - name: Set up JDK 1.8 - uses: actions/setup-java@v1 - with: - java-version: 1.8 - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - name: Build with Gradle - run: ./gradlew publish - env: - GITHUB_ACTOR: ${{ secrets.GITHUB_ACTOR }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Add Artifact - uses: actions/upload-artifact@v2 - with: - name: libs - path: build/libs/*.jar - - release: - name: Make Release - runs-on: ubuntu-latest - needs: build - steps: - - name: Download Build Results - uses: actions/download-artifact@v2 - with: - name: libs - path: buildfiles - - - name: Create Release - uses: "marvinpinto/action-automatic-releases@latest" - with: - title: "Release ${{ github.event.release.tag_name }}" - repo_token: "${{ secrets.GITHUB_TOKEN }}" - prerelease: false - files: | - CHANGELOG.txt - LICENSE.txt - buildfiles/* - diff --git a/.github/workflows/manual-gh-packages.yml b/.github/workflows/manual-gh-packages.yml new file mode 100644 index 00000000..31f6a2fa --- /dev/null +++ b/.github/workflows/manual-gh-packages.yml @@ -0,0 +1,152 @@ +# Creates releases on GH Packages based on a version tag +name: Manual Tagged Github Packages Release + +on: + workflow_dispatch: + +jobs: + vars: + name: Get Variables + runs-on: ubuntu-20.04 + outputs: + release_type: ${{steps.cf_release_type.outputs.value }} + cf_project: ${{steps.cf_project.outputs.value }} + mod_version: ${{steps.mod_version.outputs.value }} + mod_id: ${{steps.mod_id.outputs.value }} + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Release Type + id: cf_release_type + uses: christian-draeger/read-properties@1.0.1 + with: + path: './gradle.properties' + property: 'cf_release_type' + + - name: Project ID + id: cf_project + uses: christian-draeger/read-properties@1.0.1 + with: + path: './gradle.properties' + property: 'cf_project' + + - name: Mod Version + id: mod_version + uses: christian-draeger/read-properties@1.0.1 + with: + path: './gradle.properties' + property: 'mod_version' + + - name: Mod ID + id: mod_id + uses: christian-draeger/read-properties@1.0.1 + with: + path: './gradle.properties' + property: 'mod_id' + + changelog: + name: Generate Changelog (tags) + runs-on: ubuntu-20.04 + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Unshallow + run: git fetch --prune --unshallow + + - name: Find Current Tag + id: current + uses: jimschubert/query-tag-action@v1 + with: + include: 'v*' + exclude: '*-rc*' + commit-ish: '@' + skip-unshallow: 'true' + + - name: Previous Tag + id: last + uses: jimschubert/query-tag-action@v1 + with: + include: 'v*' + exclude: ${{steps.current.outputs.tag}} + skip-unshallow: 'true' + + - name: Generate changelog + uses: jimschubert/beast-changelog-action@v1 + with: + GITHUB_TOKEN: ${{github.token}} + CONFIG_LOCATION: .github/changelog.json + FROM: ${{steps.last.outputs.tag}} + TO: ${{steps.current.outputs.tag}} + OUTPUT: .github/CHANGELOG.md + + - name: Read CHANGELOG file + id: getchangelog + run: echo "::set-output name=changelog::$(cat .github/CHANGELOG.md)" + + - name: View Changelog + run: cat .github/CHANGELOG.md + + - name: Add Artifact + uses: actions/upload-artifact@v2 + with: + name: out + path: .github/CHANGELOG.md + + jar: + name: Publish JAR + runs-on: ubuntu-20.04 + needs: [vars, changelog] + steps: + - name: Download Changelog Results + uses: actions/download-artifact@v2 + with: + name: out + path: changelog + + - name: Checkout + uses: actions/checkout@v2 + + - name: Set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: "8.0.282" + + - name: Cache Gradle packages + uses: actions/cache@v2 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: ${{ runner.os }}-gradle- + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Publish package + run: ./gradlew publish + env: + GITHUB_ACTOR: ${{ secrets.GITHUB_ACTOR }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Add Artifact + uses: actions/upload-artifact@v2 + with: + name: libs + path: | + build-out/${{ needs.vars.outputs.mod_id }}-${{ needs.vars.outputs.mod_version }}.jar + build-out/${{ needs.vars.outputs.mod_id }}-${{ needs.vars.outputs.mod_version }}-api.jar + + view: + name: View Changelog Output + runs-on: ubuntu-20.04 + needs: [changelog] + steps: + - name: Download Build Results + uses: actions/download-artifact@v2 + with: + name: out + path: changelog + - run: cat changelog/CHANGELOG.md \ No newline at end of file diff --git a/.github/workflows/tagged-release.yml b/.github/workflows/tagged-release.yml new file mode 100644 index 00000000..1b507b60 --- /dev/null +++ b/.github/workflows/tagged-release.yml @@ -0,0 +1,211 @@ +# Creates releases on Curseforge and Github Releases based on v* tags +name: Tagged Commit Release + +on: + push: + tags: v* + workflow_dispatch: + +jobs: + vars: + name: Get Variables + runs-on: ubuntu-20.04 + outputs: + release_type: ${{steps.cf_release_type.outputs.value }} + cf_project: ${{steps.cf_project.outputs.value }} + mod_version: ${{steps.mod_version.outputs.value }} + mod_id: ${{steps.mod_id.outputs.value }} + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Release Type + id: cf_release_type + uses: christian-draeger/read-properties@1.0.1 + with: + path: './gradle.properties' + property: 'cf_release_type' + + - name: Project ID + id: cf_project + uses: christian-draeger/read-properties@1.0.1 + with: + path: './gradle.properties' + property: 'cf_project' + + - name: Mod Version + id: mod_version + uses: christian-draeger/read-properties@1.0.1 + with: + path: './gradle.properties' + property: 'mod_version' + + - name: Mod ID + id: mod_id + uses: christian-draeger/read-properties@1.0.1 + with: + path: './gradle.properties' + property: 'mod_id' + + changelog: + name: Generate Changelog (tags) + runs-on: ubuntu-20.04 + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Unshallow + run: git fetch --prune --unshallow + + - name: Find Current Tag + id: current + uses: jimschubert/query-tag-action@v1 + with: + include: 'v*' + exclude: '*-rc*' + commit-ish: '@' + skip-unshallow: 'true' + + - name: Previous Tag + id: last + uses: jimschubert/query-tag-action@v1 + with: + include: 'v*' + exclude: ${{steps.current.outputs.tag}} + skip-unshallow: 'true' + + - name: Generate changelog + uses: jimschubert/beast-changelog-action@v1 + with: + GITHUB_TOKEN: ${{github.token}} + CONFIG_LOCATION: .github/changelog.json + FROM: ${{steps.last.outputs.tag}} + TO: ${{steps.current.outputs.tag}} + OUTPUT: .github/CHANGELOG.md + + - name: Read CHANGELOG file + id: getchangelog + run: echo "::set-output name=changelog::$(cat .github/CHANGELOG.md)" + + - name: View Changelog + run: cat .github/CHANGELOG.md + + - name: Add Artifact + uses: actions/upload-artifact@v2 + with: + name: out + path: .github/CHANGELOG.md + + jar: + name: Publish JAR + runs-on: ubuntu-20.04 + needs: [vars, changelog] + steps: + - name: Download Changelog Results + uses: actions/download-artifact@v2 + with: + name: out + path: changelog + + - name: Checkout + uses: actions/checkout@v2 + + - name: Set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: "8.0.282" + + - name: Cache Gradle packages + uses: actions/cache@v2 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: ${{ runner.os }}-gradle- + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Build JAR with Gradle + run: ./gradlew build + env: + GITHUB_ACTOR: ${{ secrets.GITHUB_ACTOR }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Add Artifact + uses: actions/upload-artifact@v2 + with: + name: libs + path: | + build-out/${{ needs.vars.outputs.mod_id }}-${{ needs.vars.outputs.mod_version }}.jar + build-out/${{ needs.vars.outputs.mod_id }}-${{ needs.vars.outputs.mod_version }}-api.jar + + view: + name: View Changelog Output + runs-on: ubuntu-20.04 + needs: [changelog] + steps: + - name: Download Build Results + uses: actions/download-artifact@v2 + with: + name: out + path: changelog + - run: cat changelog/CHANGELOG.md + + release: + name: Make Releases + runs-on: ubuntu-20.04 + needs: [changelog, vars, jar] + steps: + - name: Download Build Results + uses: actions/download-artifact@v2 + with: + name: libs + path: build-out + - name: Download Changelog Results + uses: actions/download-artifact@v2 + with: + name: out + path: changelog + + - name: Load Changelog File + id: changelog + run: echo ::set-output name=changelog::$(cat changelog/CHANGELOG.md) + + - name: Create GitHub Release + uses: "marvinpinto/action-automatic-releases@latest" + with: + title: "Release ${{ github.event.release.tag_name }}" + repo_token: "${{ secrets.GITHUB_TOKEN }}" + prerelease: false + files: | + changelog/CHANGELOG.md + build-out/${{ needs.vars.outputs.mod_id }}-${{ needs.vars.outputs.mod_version }}.jar + build-out/${{ needs.vars.outputs.mod_id }}-${{ needs.vars.outputs.mod_version }}-api.jar + + - name: Full File + id: filename + run: echo "::set-output name=fullpath::buildfiles/${{ needs.vars.outputs.mod_id }}-${{ needs.vars.outputs.mod_version }}.jar" + + - name: Create CurseForge Release + uses: itsmeow/curseforge-upload@master + with: + token: ${{ secrets.CF_API_TOKEN }} + project_id: ${{ needs.vars.outputs.cf_project }} + game_endpoint: minecraft + file_path: ${{ steps.filename.outputs.fullpath }} + changelog: ${{ steps.changelog.outputs.changelog }} + changelog_type: markdown + game_versions: java:Java 8,Forge + release_type: ${{ needs.vars.outputs.release_type }} + + publishMaven: + name: Maven Release (GH Packages) + runs-on: ubuntu-20.04 + needs: [changelog, vars, jar] + steps: + - name: Publish package + run: ./gradlew publish + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml new file mode 100644 index 00000000..8e13f758 --- /dev/null +++ b/.github/workflows/test-build.yml @@ -0,0 +1,153 @@ +name: Test Build and Changelog + +on: + workflow_dispatch: + +jobs: + vars: + name: Get Variables + runs-on: ubuntu-20.04 + outputs: + release_type: ${{steps.cf_release_type.outputs.value }} + cf_project: ${{steps.cf_project.outputs.value }} + mod_version: ${{steps.mod_version.outputs.value }} + mod_id: ${{steps.mod_id.outputs.value }} + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Release Type + id: cf_release_type + uses: christian-draeger/read-properties@1.0.1 + with: + path: './gradle.properties' + property: 'cf_release_type' + + - name: Project ID + id: cf_project + uses: christian-draeger/read-properties@1.0.1 + with: + path: './gradle.properties' + property: 'cf_project' + + - name: Mod Version + id: mod_version + uses: christian-draeger/read-properties@1.0.1 + with: + path: './gradle.properties' + property: 'mod_version' + + - name: Mod ID + id: mod_id + uses: christian-draeger/read-properties@1.0.1 + with: + path: './gradle.properties' + property: 'mod_id' + + changelog: + name: Generate Changelog (tags) + runs-on: ubuntu-20.04 + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Unshallow + run: git fetch --prune --unshallow + + - name: Find Current Tag + id: current + uses: jimschubert/query-tag-action@v1 + with: + include: 'v*' + exclude: '*-rc*' + commit-ish: '@' + skip-unshallow: 'true' + + - name: Previous Tag + id: last + uses: jimschubert/query-tag-action@v1 + with: + include: 'v*' + exclude: ${{steps.current.outputs.tag}} + skip-unshallow: 'true' + + - name: Generate changelog + uses: jimschubert/beast-changelog-action@v1 + with: + GITHUB_TOKEN: ${{github.token}} + CONFIG_LOCATION: .github/changelog.json + FROM: ${{steps.last.outputs.tag}} + TO: ${{steps.current.outputs.tag}} + OUTPUT: .github/CHANGELOG.md + + - name: Read CHANGELOG file + id: getchangelog + run: echo "::set-output name=changelog::$(cat .github/CHANGELOG.md)" + + - name: View Changelog + run: cat .github/CHANGELOG.md + + - name: Add Artifact + uses: actions/upload-artifact@v2 + with: + name: out + path: .github/CHANGELOG.md + + jar: + name: Publish JAR + runs-on: ubuntu-20.04 + needs: [vars, changelog] + steps: + - name: Download Changelog Results + uses: actions/download-artifact@v2 + with: + name: out + path: changelog + + - name: Checkout + uses: actions/checkout@v2 + + - name: Set up JDK + uses: actions/setup-java@v1 + with: + java-version: "8.0.282" + + - name: Cache Gradle packages + uses: actions/cache@v2 + with: + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} + restore-keys: ${{ runner.os }}-gradle + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Build JAR with Gradle + run: ./gradlew build +# run: ./gradlew publish + env: + GITHUB_ACTOR: ${{ secrets.GITHUB_ACTOR }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Output Dir Structure + run: ls -lhR build-out + + - name: Add Artifact + uses: actions/upload-artifact@v2 + with: + name: libs + path: | + build-out/${{ needs.vars.outputs.mod_id }}-${{ needs.vars.outputs.mod_version }}.jar + build-out/${{ needs.vars.outputs.mod_id }}-${{ needs.vars.outputs.mod_version }}-api.jar + + view: + name: View Changelog Output + runs-on: ubuntu-20.04 + needs: [changelog] + steps: + - name: Download Build Results + uses: actions/download-artifact@v2 + with: + name: out + path: changelog + - run: cat changelog/CHANGELOG.md diff --git a/.gitignore b/.gitignore index 7259f073..1a19cf39 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,11 @@ logs/ mods/ demo-files/ + +# MC Server Files +saves/ +config/ +.mixin.out/ +options.txt +usercache.json +usernamecache.json diff --git a/build.gradle b/build.gradle index d1dbc628..3875bbac 100644 --- a/build.gradle +++ b/build.gradle @@ -8,17 +8,31 @@ buildscript { classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '3.+', changing: true } } + +plugins { + id "idea" +} + apply plugin: 'net.minecraftforge.gradle' // Only edit below this line, the above code adds and enables the necessary things for Forge to be setup. apply plugin: 'eclipse' apply plugin: 'maven-publish' -version = "${mod_version}" -group = 'com.robotgryphon.compactcrafting' // http://maven.apache.org/guides/mini/guide-naming-conventions.html -archivesBaseName = 'compactcrafting' +version = mod_version +group = "com.robotgryphon" +archivesBaseName = mod_id sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly. +sourceSets { + main { + resources { + srcDir 'src/generated/resources' + } + } + test +} + println('Java: ' + System.getProperty('java.version') + ' JVM: ' + System.getProperty('java.vm.version') + '(' + System.getProperty('java.vendor') + ') Arch: ' + System.getProperty('os.arch')) minecraft { // The mappings can be changed at any time, and must be in the following format. @@ -83,11 +97,29 @@ minecraft { } } } + + unitTests { + parent runs.server // This run config inherits settings from the server config + workingDirectory project.file('run/server') + main 'com.alcatrazescapee.mcjunitlib.DedicatedTestServerLauncher' // The main class which launches a customized server which then runs JUnit tests + ideaModule "${project.name}.test" // Tell IDEA to use the classpath of the test module + property 'forge.logging.console.level', 'unittest' // This logging level prevents any other server information messages and leaves only the unit test output + environment 'MOD_CLASSES', String.join(File.pathSeparator, + "${mod_id}%%${sourceSets.main.output.resourcesDir}", + "${mod_id}%%${sourceSets.main.output.classesDir}", + "${mod_id}%%${sourceSets.test.output.resourcesDir}", + "${mod_id}%%${sourceSets.test.output.classesDir}", + ) // Forge will ignore all test sources unless we explicitly tell it to include them as mod sources + environment 'target', 'fmltestserver' // This is a custom service used to launch with ModLauncher's transforming class loader + mods { + compactcrafting { // The mod that is being tested - Replace this with your mod ID! + sources sourceSets.main + } + } + } } } -sourceSets.main.resources { srcDir 'src/generated/resources' } - def format(String jar_name) { def index = jar_name.lastIndexOf('-') index = jar_name.substring(0, index).lastIndexOf('-') @@ -117,6 +149,11 @@ repositories { name 'tterrag maven' url "http://maven.tterrag.com/" } + + maven { + name 'MCUnitTests' + url 'https://jitpack.io' + } } def dev_mods = fileTree(dev_mods_dir).filter { it -> it.isFile() }.files.name.collect( { format(it) } ) @@ -127,7 +164,7 @@ dependencies { // The userdev artifact is a special name and will get all sorts of transformations applied to it. minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}" - testImplementation 'org.junit.jupiter:junit-jupiter:5.6.2' + testImplementation fg.deobf("com.github.alcatrazEscapee:mcjunitlib:1.3.2-${minecraft_version}") // Deobfuscate each dev mod for runtime dev_mods.each { @@ -146,6 +183,8 @@ dependencies { // Example for how to get properties into the manifest for reading by the runtime.. jar { + destinationDir = file("$rootDir/build-out") + finalizedBy('reobfJar') manifest { attributes([ "Specification-Title": "compactcrafting", @@ -158,31 +197,36 @@ jar { } } -// Example configuration to allow publishing using the maven-publish task -// This is the preferred method to reobfuscate your jar file -jar.finalizedBy('reobfJar') -// However if you are in a multi-project build, dev time needs unobfed jar files, so you can delay the obfuscation until publishing by doing -//publish.dependsOn('reobfJar') +artifacts { + archives jar +} publishing { publications { - mavenJava(MavenPublication) { - artifact jar + maven(MavenPublication) { + artifactId = mod_id + artifacts { + artifact jar + } } } repositories { + // GitHub Packages maven { - name = "GitHubPackages" - url = "https://maven.pkg.github.com/robotgryphon/compact-crafting" - credentials { - username = System.getenv("GITHUB_ACTOR") - password = System.getenv("GITHUB_TOKEN") - } + name = "GitHubPackages" + url = "https://maven.pkg.github.com/CompactMods/CompactCrafting" + credentials { + username = System.getenv("GITHUB_ACTOR") + password = System.getenv("GITHUB_TOKEN") + } } } } test { useJUnitPlatform() + filter { + exclude "com/robotgryphon/compactcrafting/tests/minecraft/**" + } } diff --git a/gradle.properties b/gradle.properties index c22682d6..0e140524 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,8 +6,14 @@ org.gradle.daemon=false minecraft_version=1.16.4 forge_version=35.1.10 mappings_version=20201028-1.16.3 -mod_version=1.0.0-alpha.2 + +mod_id=compactcrafting +mod_version=1.0.0-beta.1 # Dependencies and Libs jei_version=7.6.0.58 -top_version=1.16-3.0.4-beta-7 \ No newline at end of file +top_version=1.16-3.0.4-beta-7 + +# Curseforge +cf_project=429735 +cf_release_type=beta \ No newline at end of file diff --git a/recipes/components.md b/recipes/components.md new file mode 100644 index 00000000..34846ce2 --- /dev/null +++ b/recipes/components.md @@ -0,0 +1,39 @@ +# Component Specification v1 + +```json +"components": [ + "{key}": { + /* component definition */ + }, + + ... +] +``` + + +## Component Definitions + +### Match Block with Properties +```json +{ + "type": "compactcrafting:block", + "block": "minecraft:block_id_here", + "properties": { + "{property}": ["accepted", "values", "here"] + } +} +``` + +### Match Itemstack Entity (Future) +```json +{ + "type": "compactcrafting:match_itemstack" +} +``` + +### Match Specific Entity (Future) +```json +{ + "type": "compactcrafting:match_entity" +} +``` \ No newline at end of file diff --git a/recipes/diamond_block.json b/recipes/diamond_block.json index 5931278e..ae5e407f 100644 --- a/recipes/diamond_block.json +++ b/recipes/diamond_block.json @@ -3,21 +3,15 @@ "recipeSize": 5, "layers": [ { - "type": "filled", + "type": "compactcrafting:filled", "component": "C" }, { - "type": "mixed", - "pattern": [ - ["C", "C", "C", "C", "C"], - ["C", "-", "-", "-", "C"], - ["C", "-", "-", "-", "C"], - ["C", "-", "-", "-", "C"], - ["C", "C", "C", "C", "C"] - ] + "type": "compactcrafting:hollow", + "wall": "C" }, { - "type": "mixed", + "type": "compactcrafting:mixed", "pattern": [ ["C", "C", "C", "C", "C"], ["C", "-", "-", "-", "C"], @@ -27,17 +21,11 @@ ] }, { - "type": "mixed", - "pattern": [ - ["C", "C", "C", "C", "C"], - ["C", "-", "-", "-", "C"], - ["C", "-", "-", "-", "C"], - ["C", "-", "-", "-", "C"], - ["C", "C", "C", "C", "C"] - ] + "type": "compactcrafting:hollow", + "wall": "C" }, { - "type": "filled", + "type": "compactcrafting:filled", "component": "C" } ], @@ -49,11 +37,13 @@ "components": { "C": { - "Name": "minecraft:coal_block" + "type": "compactcrafting:block", + "block": "minecraft:coal_block" }, "D": { - "Name": "minecraft:diamond_block" + "type": "compactcrafting:block", + "block": "minecraft:diamond_block" } }, diff --git a/recipes/large_machine.json b/recipes/large_machine.json deleted file mode 100644 index 309bcf6d..00000000 --- a/recipes/large_machine.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "structure": { - // if either size is not set, recipe loader will clip to the maximum dimensions of recipe itself - "minSize": 5, // optional; minimum size needed to create the pattern (gate keeping) - "maxSize": 5, // optional; maximum size needed to create - - // mixed (default, uses layers) or solid (uses one component) - "type": "mixed", - - // recipe components (required) - "components": { - "W": { - "item": "compactmachines:wall_breakable" - }, - - "D": { - "item": "minecraft:diamond_block" - } - }, - - // for mixed recipes, this is the layer specification - "layers": [ - { - "type": "filled", - "component": "W" - }, - - { - "type": "hollow", - "component": "W" - }, - - { - // patterns: mixed type, uses multiple components - // anything not in the component list is assumed to be minecraft:air - "type": "pattern", - "pattern": [ - "WWWWW", - "W---W", - "W-D-W", - "W---W", - "WWWWW" - ] - }, - - { - "type": "hollow", - "component": "W" - }, - - { - "type": "filled", - "component": "W" - } - ] - }, - - "catalyst": { - "item": "minecraft:ender_pearl" - }, - - "outputs": [ - { - "item": "compactmachines:machine_large", - "count": 1 - } - ] -} \ No newline at end of file diff --git a/src/main/java/com/robotgryphon/compactcrafting/CompactCrafting.java b/src/main/java/com/robotgryphon/compactcrafting/CompactCrafting.java index e8d66ded..f6fbcb3c 100644 --- a/src/main/java/com/robotgryphon/compactcrafting/CompactCrafting.java +++ b/src/main/java/com/robotgryphon/compactcrafting/CompactCrafting.java @@ -4,6 +4,7 @@ import com.robotgryphon.compactcrafting.client.render.RenderTickCounter; import com.robotgryphon.compactcrafting.config.ClientConfig; import com.robotgryphon.compactcrafting.core.Registration; +import com.robotgryphon.compactcrafting.network.NetworkHandler; import net.minecraft.item.ItemGroup; import net.minecraft.item.ItemStack; import net.minecraftforge.common.MinecraftForge; @@ -11,6 +12,8 @@ import net.minecraftforge.fml.ModLoadingContext; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.config.ModConfig; +import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -31,15 +34,21 @@ public ItemStack createIcon() { public CompactCrafting() { IEventBus forgeBus = MinecraftForge.EVENT_BUS; + IEventBus modBus = FMLJavaModLoadingContext.get().getModEventBus(); - forgeBus.register(this); forgeBus.register(RenderTickCounter.class); forgeBus.register(ClientSetup.class); + modBus.addListener(this::setup); ModLoadingContext mlCtx = ModLoadingContext.get(); mlCtx.registerConfig(ModConfig.Type.CLIENT, ClientConfig.CONFIG); Registration.init(); } + + private void setup(final FMLCommonSetupEvent event) + { + NetworkHandler.initialize(); + } } diff --git a/src/main/java/com/robotgryphon/compactcrafting/blocks/MainFieldProjectorTile.java b/src/main/java/com/robotgryphon/compactcrafting/blocks/MainFieldProjectorTile.java index f44f4311..eee97df7 100644 --- a/src/main/java/com/robotgryphon/compactcrafting/blocks/MainFieldProjectorTile.java +++ b/src/main/java/com/robotgryphon/compactcrafting/blocks/MainFieldProjectorTile.java @@ -4,17 +4,25 @@ import com.robotgryphon.compactcrafting.crafting.CraftingHelper; import com.robotgryphon.compactcrafting.crafting.EnumCraftingState; import com.robotgryphon.compactcrafting.field.FieldProjection; +import com.robotgryphon.compactcrafting.field.FieldProjectionSize; import com.robotgryphon.compactcrafting.field.MiniaturizationFieldBlockData; +import com.robotgryphon.compactcrafting.network.FieldActivatedPacket; +import com.robotgryphon.compactcrafting.network.FieldDeactivatedPacket; +import com.robotgryphon.compactcrafting.network.NetworkHandler; import com.robotgryphon.compactcrafting.recipes.MiniaturizationRecipe; import com.robotgryphon.compactcrafting.world.ProjectionFieldSavedData; import com.robotgryphon.compactcrafting.world.ProjectorFieldData; +import net.minecraft.block.BlockState; import net.minecraft.entity.item.ItemEntity; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.nbt.NBTUtil; import net.minecraft.tileentity.ITickableTileEntity; import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.BlockPos; import net.minecraft.world.server.ServerWorld; +import net.minecraftforge.fml.network.PacketDistributor; import java.util.List; import java.util.Optional; @@ -57,6 +65,12 @@ public void doFieldCheck() { ProjectionFieldSavedData data = ProjectionFieldSavedData.get((ServerWorld) world); data.ACTIVE_FIELDS.put(this.field.getCenterPosition(), ProjectorFieldData.fromInstance(this.field)); data.markDirty(); + + PacketDistributor.PacketTarget trk = PacketDistributor.TRACKING_CHUNK + .with(() -> world.getChunkAt(this.pos)); + + NetworkHandler.MAIN_CHANNEL + .send(trk, new FieldActivatedPacket(this.field.getCenterPosition(), this.field.getFieldSize())); } } else { this.invalidateField(); @@ -65,16 +79,25 @@ public void doFieldCheck() { } public void invalidateField() { - if(field == null) + if (field == null) return; if (world != null && !world.isRemote) { BlockPos center = this.field.getCenterPosition(); + FieldProjectionSize size = this.field.getFieldSize(); + ProjectionFieldSavedData data = ProjectionFieldSavedData.get((ServerWorld) world); data.unregister(center); + + PacketDistributor.PacketTarget trk = PacketDistributor.TRACKING_CHUNK + .with(() -> world.getChunkAt(this.pos)); + + NetworkHandler.MAIN_CHANNEL + .send(trk, new FieldDeactivatedPacket(center, size)); } this.field = null; + this.markDirty(); } /** @@ -139,6 +162,11 @@ public void doRecipeScan() { this.currentRecipe = matchedRecipe; } + public void setFieldInfo(FieldProjection field) { + this.field = field; + this.markDirty(); + } + private void tickCrafting() { if (this.field != null) { AxisAlignedBB fieldBounds = field.getBounds(); @@ -247,4 +275,61 @@ public void updateCraftingState(EnumCraftingState state) { public EnumCraftingState getCraftingState() { return this.craftingState; } + + @Override + public CompoundNBT getUpdateTag() { + CompoundNBT tag = super.getUpdateTag(); + + if (this.field != null) { + CompoundNBT fieldInfo = new CompoundNBT(); + fieldInfo.put("center", NBTUtil.writeBlockPos(this.field.getCenterPosition())); + fieldInfo.putString("size", this.field.getFieldSize().name()); + tag.put("fieldInfo", fieldInfo); + } + + return tag; + } + + @Override + public void handleUpdateTag(BlockState state, CompoundNBT tag) { + super.handleUpdateTag(state, tag); + if (tag.contains("fieldInfo")) { + CompoundNBT fieldInfo = tag.getCompound("fieldInfo"); + BlockPos fCenter = NBTUtil.readBlockPos(fieldInfo.getCompound("center")); + String sizeName = fieldInfo.getString("size"); + FieldProjectionSize size = FieldProjectionSize.valueOf(sizeName); + + this.field = FieldProjection.fromSizeAndCenter(size, fCenter); + } + } + + @Override + public CompoundNBT write(CompoundNBT compound) { + CompoundNBT nbt = super.write(compound); + + if (field != null) { + CompoundNBT fieldInfo = new CompoundNBT(); + fieldInfo.put("center", NBTUtil.writeBlockPos(this.field.getCenterPosition())); + nbt.put("fieldInfo", fieldInfo); + } + + return nbt; + } + + @Override + public void read(BlockState state, CompoundNBT nbt) { + super.read(state, nbt); + + if(nbt.contains("fieldInfo")) { + CompoundNBT fieldInfo = nbt.getCompound("fieldInfo"); + BlockPos center = NBTUtil.readBlockPos(fieldInfo.getCompound("center")); + + if(this.world != null && !this.world.isRemote) { + ProjectionFieldSavedData data = ProjectionFieldSavedData.get((ServerWorld) world); + ProjectorFieldData fieldData = data.ACTIVE_FIELDS.get(center); + + this.field = FieldProjection.fromSizeAndCenter(fieldData.size, fieldData.fieldCenter); + } + } + } } diff --git a/src/main/java/com/robotgryphon/compactcrafting/client/ClientPacketHandler.java b/src/main/java/com/robotgryphon/compactcrafting/client/ClientPacketHandler.java new file mode 100644 index 00000000..a854d4f6 --- /dev/null +++ b/src/main/java/com/robotgryphon/compactcrafting/client/ClientPacketHandler.java @@ -0,0 +1,39 @@ +package com.robotgryphon.compactcrafting.client; + +import com.robotgryphon.compactcrafting.blocks.MainFieldProjectorTile; +import com.robotgryphon.compactcrafting.field.FieldProjection; +import com.robotgryphon.compactcrafting.field.FieldProjectionSize; +import com.robotgryphon.compactcrafting.field.ProjectorHelper; +import net.minecraft.client.Minecraft; +import net.minecraft.util.Direction; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +public abstract class ClientPacketHandler { + + public static void handleFieldActivation(BlockPos center, FieldProjectionSize fieldSize) { + BlockPos mainProjector = ProjectorHelper.getProjectorLocationForDirection(center, Direction.NORTH, fieldSize); + + FieldProjection fp = FieldProjection.fromSizeAndCenter(fieldSize, center); + Minecraft mc = Minecraft.getInstance(); + mc.deferTask(() -> { + World w = Minecraft.getInstance().world; + MainFieldProjectorTile mainTile = (MainFieldProjectorTile) w.getTileEntity(mainProjector); + if (mainTile == null) + return; + + mainTile.setFieldInfo(fp); + }); + } + + public static void handleFieldDeactivation(BlockPos center, FieldProjectionSize fieldSize) { + BlockPos mainProjector = ProjectorHelper.getProjectorLocationForDirection(center, Direction.NORTH, fieldSize); + + World w = Minecraft.getInstance().world; + MainFieldProjectorTile mainTile = (MainFieldProjectorTile) w.getTileEntity(mainProjector); + if (mainTile == null) + return; + + mainTile.invalidateField(); + } +} diff --git a/src/main/java/com/robotgryphon/compactcrafting/client/render/FieldCraftingPreviewRenderer.java b/src/main/java/com/robotgryphon/compactcrafting/client/render/FieldCraftingPreviewRenderer.java index c0916c81..93d235de 100644 --- a/src/main/java/com/robotgryphon/compactcrafting/client/render/FieldCraftingPreviewRenderer.java +++ b/src/main/java/com/robotgryphon/compactcrafting/client/render/FieldCraftingPreviewRenderer.java @@ -3,7 +3,8 @@ import com.mojang.blaze3d.matrix.MatrixStack; import com.robotgryphon.compactcrafting.blocks.FieldCraftingPreviewTile; import com.robotgryphon.compactcrafting.recipes.MiniaturizationRecipe; -import com.robotgryphon.compactcrafting.recipes.layers.IRecipeLayer; +import com.robotgryphon.compactcrafting.recipes.components.RecipeBlockStateComponent; +import com.robotgryphon.compactcrafting.recipes.layers.RecipeLayer; import net.minecraft.block.BlockState; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.BlockRendererDispatcher; @@ -55,16 +56,18 @@ public void render(FieldCraftingPreviewTile tile, float partialTicks, MatrixStac mx.push(); mx.translate(0, y, 0); - Optional layer = rec.getLayer(y); + Optional layer = rec.getLayer(y); layer.ifPresent(l -> { - l.getNonAirPositions().forEach(filledPos -> { + l.getFilledPositions().forEach(filledPos -> { mx.push(); mx.translate(filledPos.getX(), 0, filledPos.getZ()); - String component = l.getRequiredComponentKeyForPosition(filledPos); - Optional recipeComponent = rec.getRecipeComponent(component); + Optional component = l.getRequiredComponentKeyForPosition(filledPos); + Optional recipeComponent = rec.getRecipeBlockComponent(component.get()); recipeComponent.ifPresent(state -> { - blockRenderer.renderBlock(state, mx, buffers, light, overlay, EmptyModelData.INSTANCE); + // TODO - Render switching + BlockState state1 = state.block.getDefaultState(); + blockRenderer.renderBlock(state1, mx, buffers, light, overlay, EmptyModelData.INSTANCE); }); mx.pop(); }); diff --git a/src/main/java/com/robotgryphon/compactcrafting/compat/jei/JeiMiniaturizationCraftingCategory.java b/src/main/java/com/robotgryphon/compactcrafting/compat/jei/JeiMiniaturizationCraftingCategory.java index eb304331..2ee7d3d3 100644 --- a/src/main/java/com/robotgryphon/compactcrafting/compat/jei/JeiMiniaturizationCraftingCategory.java +++ b/src/main/java/com/robotgryphon/compactcrafting/compat/jei/JeiMiniaturizationCraftingCategory.java @@ -8,7 +8,8 @@ import com.robotgryphon.compactcrafting.client.render.RenderTypesExtensions; import com.robotgryphon.compactcrafting.core.Registration; import com.robotgryphon.compactcrafting.recipes.MiniaturizationRecipe; -import com.robotgryphon.compactcrafting.recipes.layers.IRecipeLayer; +import com.robotgryphon.compactcrafting.recipes.components.RecipeBlockStateComponent; +import com.robotgryphon.compactcrafting.recipes.layers.RecipeLayer; import mezz.jei.api.constants.VanillaTypes; import mezz.jei.api.gui.IRecipeLayout; import mezz.jei.api.gui.drawable.IDrawable; @@ -120,7 +121,7 @@ public void setIngredients(MiniaturizationRecipe recipe, IIngredients ing) { List inputs = new ArrayList<>(); for (String compKey : recipe.getComponentKeys()) { - Optional requiredBlock = recipe.getRecipeComponent(compKey); + Optional requiredBlock = recipe.getRecipeBlockComponent(compKey); requiredBlock.ifPresent(bs -> { Item bi = Item.getItemFromBlock(bs.getBlock()); inputs.add(new ItemStack(bi)); @@ -204,7 +205,7 @@ private void addMaterialSlots(MiniaturizationRecipe recipe, int GUTTER_X, int OF int required = comp.getValue(); int finalInputOffset = inputOffset.get(); - BlockState bs = recipe.getRecipeComponent(component).get(); + RecipeBlockStateComponent bs = recipe.getRecipeBlockComponent(component).get(); Item bi = Item.getItemFromBlock(bs.getBlock()); guiItemStacks.set(finalInputOffset, new ItemStack(bi, required)); @@ -427,7 +428,7 @@ public void draw(MiniaturizationRecipe recipe, MatrixStack mx, double mouseX, do //} for (int y : renderLayers) { - Optional layer = recipe.getLayer(y); + Optional layer = recipe.getLayer(y); layer.ifPresent(l -> renderRecipeLayer(recipe, mx, buffers, l, y)); } @@ -442,11 +443,11 @@ public void draw(MiniaturizationRecipe recipe, MatrixStack mx, double mouseX, do } } - private void renderRecipeLayer(MiniaturizationRecipe recipe, MatrixStack mx, IRenderTypeBuffer.Impl buffers, IRecipeLayer l, int layerY) { + private void renderRecipeLayer(MiniaturizationRecipe recipe, MatrixStack mx, IRenderTypeBuffer.Impl buffers, RecipeLayer l, int layerY) { // Begin layer mx.push(); - for (BlockPos filledPos : l.getNonAirPositions()) { + for (BlockPos filledPos : l.getFilledPositions()) { mx.push(); mx.translate( @@ -455,14 +456,15 @@ private void renderRecipeLayer(MiniaturizationRecipe recipe, MatrixStack mx, IRe ((filledPos.getZ() + 0.5) * explodeMulti) ); - String component = l.getRequiredComponentKeyForPosition(filledPos); - Optional recipeComponent = recipe.getRecipeComponent(component); + String component = l.getRequiredComponentKeyForPosition(filledPos).get(); + Optional recipeComponent = recipe.getRecipeBlockComponent(component); recipeComponent.ifPresent(state -> { // renderer.render(renderTe, pos.getX(), pos.getY(), pos.getZ(), 0.0f); - + // TODO - Render switching at fixed interval + BlockState state1 = state.block.getDefaultState(); // Thanks Immersive, Astral, and others - blocks.renderBlock(state, mx, RenderTypesExtensions.disableLighting(buffers), + blocks.renderBlock(state1, mx, RenderTypesExtensions.disableLighting(buffers), 0xf000f0, OverlayTexture.NO_OVERLAY, EmptyModelData.INSTANCE); }); diff --git a/src/main/java/com/robotgryphon/compactcrafting/compat/jei/JeiMiniaturizationPlugin.java b/src/main/java/com/robotgryphon/compactcrafting/compat/jei/JeiMiniaturizationPlugin.java index 0889e783..de81c8bc 100644 --- a/src/main/java/com/robotgryphon/compactcrafting/compat/jei/JeiMiniaturizationPlugin.java +++ b/src/main/java/com/robotgryphon/compactcrafting/compat/jei/JeiMiniaturizationPlugin.java @@ -2,7 +2,7 @@ import com.robotgryphon.compactcrafting.CompactCrafting; import com.robotgryphon.compactcrafting.core.Registration; -import com.robotgryphon.compactcrafting.recipes.data.base.RecipeBase; +import com.robotgryphon.compactcrafting.recipes.setup.RecipeBase; import mezz.jei.api.IModPlugin; import mezz.jei.api.JeiPlugin; import mezz.jei.api.registration.IGuiHandlerRegistration; diff --git a/src/main/java/com/robotgryphon/compactcrafting/core/Registration.java b/src/main/java/com/robotgryphon/compactcrafting/core/Registration.java index 107611f4..e16fa5d1 100644 --- a/src/main/java/com/robotgryphon/compactcrafting/core/Registration.java +++ b/src/main/java/com/robotgryphon/compactcrafting/core/Registration.java @@ -4,12 +4,13 @@ import com.robotgryphon.compactcrafting.blocks.*; import com.robotgryphon.compactcrafting.items.FieldProjectorItem; import com.robotgryphon.compactcrafting.recipes.MiniaturizationRecipe; +import com.robotgryphon.compactcrafting.recipes.components.RecipeBlockStateComponent; +import com.robotgryphon.compactcrafting.recipes.components.RecipeComponentType; +import com.robotgryphon.compactcrafting.recipes.components.SimpleRecipeComponentType; import com.robotgryphon.compactcrafting.recipes.data.MiniaturizationRecipeSerializer; -import com.robotgryphon.compactcrafting.recipes.data.base.BaseRecipeType; -import com.robotgryphon.compactcrafting.recipes.data.serialization.layers.FilledLayerSerializer; -import com.robotgryphon.compactcrafting.recipes.data.serialization.layers.HollowLayerSerializer; -import com.robotgryphon.compactcrafting.recipes.data.serialization.layers.MixedLayerSerializer; -import com.robotgryphon.compactcrafting.recipes.data.serialization.layers.RecipeLayerSerializer; +import com.robotgryphon.compactcrafting.recipes.setup.BaseRecipeType; +import com.robotgryphon.compactcrafting.recipes.layers.SimpleRecipeLayerType; +import com.robotgryphon.compactcrafting.recipes.layers.RecipeLayerType; import com.robotgryphon.compactcrafting.recipes.layers.impl.FilledComponentRecipeLayer; import com.robotgryphon.compactcrafting.recipes.layers.impl.HollowComponentRecipeLayer; import com.robotgryphon.compactcrafting.recipes.layers.impl.MixedComponentRecipeLayer; @@ -28,7 +29,10 @@ import net.minecraftforge.fml.RegistryObject; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; -import net.minecraftforge.registries.*; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.IForgeRegistry; +import net.minecraftforge.registries.RegistryBuilder; import java.util.function.Supplier; @@ -48,14 +52,22 @@ public class Registration { private static final DeferredRegister> TILE_ENTITIES = DeferredRegister.create(ForgeRegistries.TILE_ENTITIES, MOD_ID); private static final DeferredRegister> RECIPES = DeferredRegister.create(ForgeRegistries.RECIPE_SERIALIZERS, MOD_ID); - public static DeferredRegister> RECIPE_LAYERS = DeferredRegister.create((Class) RecipeLayerSerializer.class, MOD_ID); - public static IForgeRegistry> RECIPE_SERIALIZERS; + public static DeferredRegister> RECIPE_LAYERS = DeferredRegister.create((Class) RecipeLayerType.class, MOD_ID); + public static IForgeRegistry> RECIPE_LAYER_TYPES; + + public static DeferredRegister> RECIPE_COMPONENTS = DeferredRegister.create((Class) RecipeComponentType.class, MOD_ID); + public static IForgeRegistry> RECIPE_COMPONENT_TYPES; static { - RECIPE_LAYERS.makeRegistry("recipe_layer_serializers", () -> new RegistryBuilder>() - .setName(new ResourceLocation(MOD_ID, "recipe_layer_serializers")) - .setType(c(RecipeLayerSerializer.class)) - .tagFolder("recipe_layer_serializers")); + RECIPE_LAYERS.makeRegistry("recipe_layers", () -> new RegistryBuilder>() + .setName(new ResourceLocation(MOD_ID, "recipe_layers")) + .setType(c(RecipeLayerType.class)) + .tagFolder("recipe_layers")); + + RECIPE_COMPONENTS.makeRegistry("recipe_components", () -> new RegistryBuilder>() + .setName(new ResourceLocation(MOD_ID, "recipe_components")) + .setType(c(RecipeComponentType.class)) + .tagFolder("recipe_components")); } // ================================================================================================================ @@ -119,14 +131,20 @@ public class Registration { // ================================================================================================================ // RECIPE LAYER SERIALIZERS // ================================================================================================================ - public static final RegistryObject> FILLED_LAYER_SERIALIZER = - RECIPE_LAYERS.register("filled", FilledLayerSerializer::new); + public static final RegistryObject> FILLED_LAYER_SERIALIZER = + RECIPE_LAYERS.register("filled", () -> new SimpleRecipeLayerType<>(FilledComponentRecipeLayer.CODEC)); + + public static final RegistryObject> HOLLOW_LAYER_TYPE = + RECIPE_LAYERS.register("hollow", () -> new SimpleRecipeLayerType<>(HollowComponentRecipeLayer.CODEC)); - public static final RegistryObject> HOLLOW_LAYER_SERIALIZER = - RECIPE_LAYERS.register("hollow", HollowLayerSerializer::new); + public static final RegistryObject> MIXED_LAYER_TYPE = + RECIPE_LAYERS.register("mixed", () -> new SimpleRecipeLayerType<>(MixedComponentRecipeLayer.CODEC)); - public static final RegistryObject> MIXED_LAYER_SERIALIZER = - RECIPE_LAYERS.register("mixed", MixedLayerSerializer::new); + // ================================================================================================================ + // RECIPE COMPONENTS + // ================================================================================================================ + public static final RegistryObject> BLOCKSTATE_COMPONENT = + RECIPE_COMPONENTS.register("block", () -> new SimpleRecipeComponentType<>(RecipeBlockStateComponent.CODEC)); // ================================================================================================================ // INITIALIZATION @@ -145,13 +163,18 @@ public static void init() { MINIATURIZATION_RECIPE_TYPE.register(); RECIPE_LAYERS.register(eventBus); + RECIPE_COMPONENTS.register(eventBus); } @SubscribeEvent @SuppressWarnings("unused") - public static void onRegistration(RegistryEvent.Register> evt) { - RECIPE_SERIALIZERS = evt.getRegistry(); + public static void onRegistration(RegistryEvent.Register> evt) { + RECIPE_LAYER_TYPES = evt.getRegistry(); } - + @SubscribeEvent + @SuppressWarnings("unused") + public static void onComponentRegistration(RegistryEvent.Register> evt) { + RECIPE_COMPONENT_TYPES = evt.getRegistry(); + } } diff --git a/src/main/java/com/robotgryphon/compactcrafting/events/EventHandler.java b/src/main/java/com/robotgryphon/compactcrafting/events/EventHandler.java index 5198d85e..3dee76bd 100644 --- a/src/main/java/com/robotgryphon/compactcrafting/events/EventHandler.java +++ b/src/main/java/com/robotgryphon/compactcrafting/events/EventHandler.java @@ -13,6 +13,9 @@ @Mod.EventBusSubscriber(modid = MOD_ID) public class EventHandler { + // TODO - Tinker with sending recipe updates at a further timespan away + // This is working with BG now, need to figure out why recipe isn't being matched + @SubscribeEvent public static void onBlockPlaced(final BlockEvent.EntityPlaceEvent blockPlaced) { // Check if block is in or around a projector field diff --git a/src/main/java/com/robotgryphon/compactcrafting/field/FieldProjection.java b/src/main/java/com/robotgryphon/compactcrafting/field/FieldProjection.java index fe584ed8..d1b51932 100644 --- a/src/main/java/com/robotgryphon/compactcrafting/field/FieldProjection.java +++ b/src/main/java/com/robotgryphon/compactcrafting/field/FieldProjection.java @@ -27,6 +27,10 @@ private FieldProjection(FieldProjectionSize size, BlockPos center) { this.size = size; } + public static FieldProjection fromSizeAndCenter(FieldProjectionSize fieldSize, BlockPos center) { + return new FieldProjection(fieldSize, center); + } + public FieldProjectionSize getFieldSize() { return this.size; } diff --git a/src/main/java/com/robotgryphon/compactcrafting/field/ProjectorHelper.java b/src/main/java/com/robotgryphon/compactcrafting/field/ProjectorHelper.java index c92f1812..669e68a4 100644 --- a/src/main/java/com/robotgryphon/compactcrafting/field/ProjectorHelper.java +++ b/src/main/java/com/robotgryphon/compactcrafting/field/ProjectorHelper.java @@ -37,7 +37,7 @@ private static Stream getProjectorsInDirection(IWorldReader world, Blo Set positions = new HashSet<>(); for (FieldProjectionSize fieldSize : FieldProjectionSize.values()) { BlockPos possibleLocation = getProjectorLocationForDirection(center, direction, fieldSize); - if(!hasProjectorInPositionForDirection(world, direction, possibleLocation)) + if (!hasProjectorInPositionForDirection(world, direction, possibleLocation)) continue; positions.add(possibleLocation); @@ -229,4 +229,13 @@ public static Stream getValidOppositePositions(IWorldReader world, Blo } + public static Optional findSizeByMainProjector(BlockPos fieldCenter, BlockPos aProjector) { + return Arrays.stream(FieldProjectionSize.values()) + .filter(size -> { + // Try to match the positions, if so we found a valid size + BlockPos location = fieldCenter.offset(Direction.NORTH, size.getProjectorDistance() + 1); + return location == aProjector; + }).findFirst(); + + } } diff --git a/src/main/java/com/robotgryphon/compactcrafting/network/FieldActivatedPacket.java b/src/main/java/com/robotgryphon/compactcrafting/network/FieldActivatedPacket.java new file mode 100644 index 00000000..f6e076e8 --- /dev/null +++ b/src/main/java/com/robotgryphon/compactcrafting/network/FieldActivatedPacket.java @@ -0,0 +1,50 @@ +package com.robotgryphon.compactcrafting.network; + +import com.robotgryphon.compactcrafting.client.ClientPacketHandler; +import com.robotgryphon.compactcrafting.field.FieldProjectionSize; +import net.minecraft.network.PacketBuffer; +import net.minecraft.util.math.BlockPos; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.fml.DistExecutor; +import net.minecraftforge.fml.network.NetworkEvent; + +import java.util.function.Supplier; + +public class FieldActivatedPacket { + private BlockPos position; + private FieldProjectionSize fieldSize; + + private FieldActivatedPacket() { + } + + public FieldActivatedPacket(BlockPos center, FieldProjectionSize fieldSize) { + this.position = center; + this.fieldSize = fieldSize; + } + + public static void handle(FieldActivatedPacket message, Supplier context) { + NetworkEvent.Context ctx = context.get(); + + ctx.enqueueWork(() -> { + DistExecutor.unsafeCallWhenOn(Dist.CLIENT, () -> () -> { + ClientPacketHandler.handleFieldActivation(message.position, message.fieldSize); + return null; + }); + }); + + ctx.setPacketHandled(true); + } + + public static void encode(FieldActivatedPacket pkt, PacketBuffer buf) { + buf.writeBlockPos(pkt.position); + buf.writeString(pkt.fieldSize.name()); + } + + public static FieldActivatedPacket decode(PacketBuffer buf) { + FieldActivatedPacket pkt = new FieldActivatedPacket(); + pkt.position = buf.readBlockPos(); + pkt.fieldSize = FieldProjectionSize.valueOf(buf.readString()); + + return pkt; + } +} diff --git a/src/main/java/com/robotgryphon/compactcrafting/network/FieldDeactivatedPacket.java b/src/main/java/com/robotgryphon/compactcrafting/network/FieldDeactivatedPacket.java new file mode 100644 index 00000000..da86dc41 --- /dev/null +++ b/src/main/java/com/robotgryphon/compactcrafting/network/FieldDeactivatedPacket.java @@ -0,0 +1,53 @@ +package com.robotgryphon.compactcrafting.network; + +import com.robotgryphon.compactcrafting.CompactCrafting; +import com.robotgryphon.compactcrafting.client.ClientPacketHandler; +import com.robotgryphon.compactcrafting.field.FieldProjectionSize; +import net.minecraft.network.PacketBuffer; +import net.minecraft.util.math.BlockPos; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.fml.DistExecutor; +import net.minecraftforge.fml.network.NetworkEvent; + +import java.util.function.Supplier; + +public class FieldDeactivatedPacket { + private BlockPos position; + private FieldProjectionSize fieldSize; + + private FieldDeactivatedPacket() { + } + + public FieldDeactivatedPacket(BlockPos center, FieldProjectionSize fieldSize) { + this.position = center; + this.fieldSize = fieldSize; + } + + public static void handle(FieldDeactivatedPacket message, Supplier context) { + NetworkEvent.Context ctx = context.get(); + + ctx.enqueueWork(() -> { + DistExecutor.unsafeCallWhenOn(Dist.CLIENT, () -> () -> { + ClientPacketHandler.handleFieldDeactivation(message.position, message.fieldSize); + return null; + }); + }); + + ctx.setPacketHandled(true); + } + + public static void encode(FieldDeactivatedPacket pkt, PacketBuffer buf) { + buf.writeBlockPos(pkt.position); + String n = pkt.fieldSize.name(); + CompactCrafting.LOGGER.debug("D: {}, N: {}", pkt.position, n); + buf.writeString(n); + } + + public static FieldDeactivatedPacket decode(PacketBuffer buf) { + FieldDeactivatedPacket pkt = new FieldDeactivatedPacket(); + pkt.position = buf.readBlockPos(); + pkt.fieldSize = FieldProjectionSize.valueOf(buf.readString()); + + return pkt; + } +} diff --git a/src/main/java/com/robotgryphon/compactcrafting/network/NetworkHandler.java b/src/main/java/com/robotgryphon/compactcrafting/network/NetworkHandler.java new file mode 100644 index 00000000..25fed467 --- /dev/null +++ b/src/main/java/com/robotgryphon/compactcrafting/network/NetworkHandler.java @@ -0,0 +1,30 @@ +package com.robotgryphon.compactcrafting.network; + +import com.robotgryphon.compactcrafting.CompactCrafting; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.fml.network.NetworkDirection; +import net.minecraftforge.fml.network.NetworkRegistry; +import net.minecraftforge.fml.network.simple.SimpleChannel; + +import java.util.Optional; + +public class NetworkHandler { + private static int index = 0; + private static final String PROTOCOL_VERSION = "1"; + public static final SimpleChannel MAIN_CHANNEL = NetworkRegistry.newSimpleChannel( + new ResourceLocation(CompactCrafting.MOD_ID, "main"), + () -> PROTOCOL_VERSION, + PROTOCOL_VERSION::equals, + PROTOCOL_VERSION::equals + ); + + public static void initialize() { + MAIN_CHANNEL.registerMessage(index++, FieldActivatedPacket.class, + FieldActivatedPacket::encode, FieldActivatedPacket::decode, + FieldActivatedPacket::handle, Optional.of(NetworkDirection.PLAY_TO_CLIENT)); + + MAIN_CHANNEL.registerMessage(index++, FieldDeactivatedPacket.class, + FieldDeactivatedPacket::encode, FieldDeactivatedPacket::decode, + FieldDeactivatedPacket::handle, Optional.of(NetworkDirection.PLAY_TO_CLIENT)); + } +} diff --git a/src/main/java/com/robotgryphon/compactcrafting/recipes/MiniaturizationRecipe.java b/src/main/java/com/robotgryphon/compactcrafting/recipes/MiniaturizationRecipe.java index d2723cfa..26ed82b2 100644 --- a/src/main/java/com/robotgryphon/compactcrafting/recipes/MiniaturizationRecipe.java +++ b/src/main/java/com/robotgryphon/compactcrafting/recipes/MiniaturizationRecipe.java @@ -1,11 +1,19 @@ package com.robotgryphon.compactcrafting.recipes; +import com.google.common.collect.ImmutableList; +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import com.robotgryphon.compactcrafting.CompactCrafting; import com.robotgryphon.compactcrafting.core.Registration; import com.robotgryphon.compactcrafting.field.FieldProjectionSize; import com.robotgryphon.compactcrafting.field.MiniaturizationFieldBlockData; -import com.robotgryphon.compactcrafting.recipes.data.base.RecipeBase; +import com.robotgryphon.compactcrafting.recipes.components.RecipeBlockStateComponent; +import com.robotgryphon.compactcrafting.recipes.components.RecipeComponent; +import com.robotgryphon.compactcrafting.recipes.components.RecipeComponentType; +import com.robotgryphon.compactcrafting.recipes.setup.RecipeBase; import com.robotgryphon.compactcrafting.recipes.exceptions.MiniaturizationRecipeException; -import com.robotgryphon.compactcrafting.recipes.layers.IRecipeLayer; +import com.robotgryphon.compactcrafting.recipes.layers.RecipeLayer; +import com.robotgryphon.compactcrafting.recipes.layers.RecipeLayerType; import com.robotgryphon.compactcrafting.recipes.layers.dim.IDynamicRecipeLayer; import com.robotgryphon.compactcrafting.recipes.layers.dim.IRigidRecipeLayer; import com.robotgryphon.compactcrafting.util.BlockSpaceUtil; @@ -26,30 +34,95 @@ public class MiniaturizationRecipe extends RecipeBase { - private ResourceLocation registryName; - private IRecipeLayer[] layers; + /** + * Only used for recipe dimension calculation from loading phase. + * Specifies the minimum field size required for fluid recipe layers. + */ + private int minRecipeDimensions; + private ResourceLocation id; + private Map layers; private ItemStack catalyst; private ItemStack[] outputs; private AxisAlignedBB dimensions; private Map cachedComponentTotals; + /** * Contains a mapping of all known components in the recipe. * Vanilla style; C = CHARCOAL_BLOCK */ - private final Map components; + private final Map blockComponents; + + private static final Codec LAYER_CODEC = + RecipeLayerType.CODEC.dispatchStable(RecipeLayer::getType, RecipeLayerType::getCodec); + + private static final Codec COMPONENT_CODEC = + RecipeComponentType.CODEC.dispatchStable(RecipeComponent::getType, RecipeComponentType::getCodec); + + public static final Codec CODEC = RecordCodecBuilder.create(i -> i.group( + Codec.INT.fieldOf("recipeSize").forGetter(MiniaturizationRecipe::getRecipeSize), + LAYER_CODEC.listOf().fieldOf("layers").forGetter(MiniaturizationRecipe::getLayerListForCodecWrite), + ItemStack.CODEC.fieldOf("catalyst").forGetter(MiniaturizationRecipe::getCatalyst), + ItemStack.CODEC.listOf().fieldOf("outputs").forGetter(MiniaturizationRecipe::getOutputList), + Codec.unboundedMap(Codec.STRING, COMPONENT_CODEC).fieldOf("components").forGetter(MiniaturizationRecipe::getComponentsForCodecWrite) + ).apply(i, MiniaturizationRecipe::new)); + + private int getRecipeSize() { + return this.minRecipeDimensions; + } public MiniaturizationRecipe(ResourceLocation rl) { - this.registryName = rl; - this.layers = new IRecipeLayer[0]; + this.id = rl; + this.layers = new HashMap<>(); this.outputs = new ItemStack[0]; - this.components = new HashMap<>(); + this.blockComponents = new HashMap<>(); recalculateDimensions(); } - public void setLayers(IRecipeLayer[] layers) { - this.layers = layers; - this.postLayerChange(); + public MiniaturizationRecipe(int minRecipeDimensions, List layers, + ItemStack catalyst, List outputs, Map compMap) { + this.minRecipeDimensions = minRecipeDimensions; + this.catalyst = catalyst; + this.outputs = outputs.toArray(new ItemStack[0]); + + this.layers = new HashMap<>(); + ArrayList rev = new ArrayList<>(layers); + Collections.reverse(rev); + for (int y = 0; y < rev.size(); y++) + this.layers.put(y, rev.get(y)); + + this.blockComponents = new HashMap<>(); + for (Map.Entry comp : compMap.entrySet()) { + // Map in block components + if(comp.getValue() instanceof RecipeBlockStateComponent) { + this.blockComponents.put(comp.getKey(), (RecipeBlockStateComponent) comp.getValue()); + } + } + + this.recalculateDimensions(); + } + + private ImmutableList getOutputList() { + return ImmutableList.copyOf(outputs.clone()); + } + + private List getLayerListForCodecWrite() { + ImmutableList.Builder l = ImmutableList.builder(); + for (int y = layers.size() - 1; y >= 0; y--) + l.add(layers.get(y)); + + return l.build(); + } + + private Map getComponentsForCodecWrite() { + Map allComponents = new HashMap<>(); + allComponents.putAll(blockComponents); + + return allComponents; + } + + private Map getLayers() { + return layers; } private void postLayerChange() { @@ -58,43 +131,42 @@ private void postLayerChange() { } private void recalculateDimensions() { - int height = this.layers.length; + int height = this.layers.size(); int x = 0; int z = 0; - for (IRecipeLayer layer : this.layers) { - // We only need to worry about fixed-dimension layers; the fluid layers will adapt - if (layer instanceof IRigidRecipeLayer) { - AxisAlignedBB dimensions = ((IRigidRecipeLayer) layer).getDimensions(); - if (dimensions.getXSize() > x) - x = (int) Math.ceil(dimensions.getXSize()); + boolean hasAnyRigidLayers = this.layers.values().stream().anyMatch(l -> l instanceof IRigidRecipeLayer); + if (!hasAnyRigidLayers) { + try { + setFluidDimensions(new AxisAlignedBB(0, 0, 0, minRecipeDimensions, height, minRecipeDimensions)); + } catch (MiniaturizationRecipeException e) { - if (dimensions.getZSize() > z) - z = (int) Math.ceil(dimensions.getZSize()); } - } + } else { + for (RecipeLayer l : this.layers.values()) { + // We only need to worry about fixed-dimension layers; the fluid layers will adapt + if (l instanceof IRigidRecipeLayer) { + AxisAlignedBB dimensions = ((IRigidRecipeLayer) l).getDimensions(); + if (dimensions.getXSize() > x) + x = (int) Math.ceil(dimensions.getXSize()); + + if (dimensions.getZSize() > z) + z = (int) Math.ceil(dimensions.getZSize()); + } + } - this.dimensions = new AxisAlignedBB(Vector3d.ZERO, new Vector3d(x, height, z)); + this.dimensions = new AxisAlignedBB(Vector3d.ZERO, new Vector3d(x, height, z)); + } updateFluidLayerDimensions(); } private void updateFluidLayerDimensions() { // Update all the dynamic recipe layers - Arrays.stream(this.layers) - .filter(layer -> layer instanceof IDynamicRecipeLayer) - .forEach(dynamicLayer -> { - ((IDynamicRecipeLayer) dynamicLayer).setRecipeDimensions(dimensions); - }); - } - - public boolean addComponent(String key, BlockState block) { - if (components.containsKey(key)) - return false; - - components.put(key, block); - this.cachedComponentTotals = null; - return true; + this.layers.values() + .stream() + .filter(l -> l instanceof IDynamicRecipeLayer) + .forEach(dl -> ((IDynamicRecipeLayer) dl).setRecipeDimensions(dimensions)); } /** @@ -139,7 +211,7 @@ private boolean checkRotation(IWorldReader world, Rotation rot, AxisAlignedBB fi int maxY = (int) dimensions.getYSize(); for (int offset = 0; offset < maxY; offset++) { - Optional layer = getLayer(offset); + Optional layer = getLayer(offset); BlockPos[] layerFilled = BlockSpaceUtil.getFilledBlocksByLayer(world, filledBounds, offset); @@ -164,8 +236,8 @@ private boolean checkRotation(IWorldReader world, Rotation rot, AxisAlignedBB fi BlockState actualState = world.getBlockState(unrotatedPos); - IRecipeLayer l = layer.get(); - String requiredComponentKeyForPosition = l.getRequiredComponentKeyForPosition(normalizedRotatedPos); + RecipeLayer l = layer.get(); + String requiredComponentKeyForPosition = l.getRequiredComponentKeyForPosition(normalizedRotatedPos).get(); Optional recipeComponentKey = this.getRecipeComponentKey(actualState); if (!recipeComponentKey.isPresent()) { @@ -188,11 +260,11 @@ public ItemStack[] getOutputs() { } public Map getRecipeComponentTotals() { - if(this.cachedComponentTotals != null) + if (this.cachedComponentTotals != null) return this.cachedComponentTotals; HashMap totals = new HashMap<>(); - this.components.keySet().forEach(comp -> { + this.blockComponents.keySet().forEach(comp -> { int count = this.getComponentRequiredCount(comp); totals.put(comp, count); }); @@ -201,9 +273,9 @@ public Map getRecipeComponentTotals() { return totals; } - public Optional getRecipeComponent(String i) { - if (this.components.containsKey(i)) { - BlockState component = components.get(i); + public Optional getRecipeBlockComponent(String i) { + if (this.blockComponents.containsKey(i)) { + RecipeBlockStateComponent component = blockComponents.get(i); return Optional.of(component); } @@ -211,12 +283,12 @@ public Optional getRecipeComponent(String i) { } public int getComponentRequiredCount(String i) { - if (!this.components.containsKey(i)) + if (!this.blockComponents.containsKey(i)) return 0; int required = 0; - for (IRecipeLayer layer : this.layers) { - if(layer == null) + for (RecipeLayer layer : this.layers.values()) { + if (layer == null) continue; Map layerTotals = layer.getComponentTotals(); @@ -229,8 +301,10 @@ public int getComponentRequiredCount(String i) { } public Optional getRecipeComponentKey(BlockState state) { - for (String comp : this.components.keySet()) { - if (components.get(comp) == state) + // TODO - This might conflict with multiple matching states, consider handling this in the codec loading process + for (String comp : this.blockComponents.keySet()) { + RecipeBlockStateComponent sComp = blockComponents.get(comp); + if(sComp.filterMatches(state)) return Optional.of(comp); } @@ -252,7 +326,7 @@ public AxisAlignedBB getDimensions() { * @param filledPositions The filled positions on the layer to check. * @return */ - public boolean areLayerPositionsCorrect(IRecipeLayer layer, AxisAlignedBB fieldFilledBounds, BlockPos[] filledPositions) { + public boolean areLayerPositionsCorrect(RecipeLayer layer, AxisAlignedBB fieldFilledBounds, BlockPos[] filledPositions) { // Recipe layers using this method must define at least one filled space if (filledPositions.length == 0) return false; @@ -277,34 +351,34 @@ public boolean areLayerPositionsCorrect(IRecipeLayer layer, AxisAlignedBB fieldF return Arrays.stream(fieldNormalizedPositionsLayerOffset) .parallel() - .allMatch(layer::isPositionRequired); + .allMatch(layer::isPositionFilled); } - public Optional getLayer(int y) { - if (y < 0 || y > this.layers.length - 1) + public Optional getLayer(int y) { + if (y < 0 || y > this.layers.size() - 1) return Optional.empty(); - return Optional.ofNullable(this.layers[y]); + return Optional.ofNullable(this.layers.get(y)); } - public Stream getLayers() { - return Arrays.stream(layers.clone()); + public Stream getLayerStream() { + return Arrays.stream(layers.values().toArray(new RecipeLayer[0]).clone()); } public int getNumberLayers() { - return layers.length; + return layers.size(); } - public void setLayer(int num, IRecipeLayer layer) { - if(num < 0 || num > layers.length - 1) + public void setLayer(int num, RecipeLayer layer) { + if (num < 0 || num > layers.size() - 1) return; - this.layers[num] = layer; + this.layers.put(num, layer); this.postLayerChange(); } public Set getComponentKeys() { - return this.components.keySet(); + return this.blockComponents.keySet(); } public void addOutput(ItemStack itemStack) { @@ -314,14 +388,6 @@ public void addOutput(ItemStack itemStack) { this.outputs = oTmp.toArray(new ItemStack[0]); } - public int getNumberComponents() { - return this.components.size(); - } - - public Map getComponents() { - return this.components; - } - public ItemStack getCatalyst() { return this.catalyst; } @@ -331,21 +397,19 @@ public void setCatalyst(ItemStack c) { } public void setFluidDimensions(AxisAlignedBB dimensions) throws MiniaturizationRecipeException { - if(Arrays.stream(this.layers).filter(Objects::nonNull).anyMatch(layer -> !(layer instanceof IDynamicRecipeLayer))) - throw new MiniaturizationRecipeException("Tried to set fluid dimensions when a non-fluid layer exists."); - - this.dimensions = dimensions; - updateFluidLayerDimensions(); + boolean hasRigidLayer = this.layers.values().stream().anyMatch(layer -> layer instanceof IRigidRecipeLayer); + if (!hasRigidLayer) { + this.dimensions = dimensions; + updateFluidLayerDimensions(); + } else { + CompactCrafting.LOGGER.warn("Tried to set fluid dimensions when a rigid layer is present in the layer set.", new MiniaturizationRecipeException("no. bad.")); + } } public int getTicks() { return 200; } - @Override - public ResourceLocation getId() { - return this.registryName; - } @Override public IRecipeSerializer getSerializer() { @@ -356,4 +420,14 @@ public IRecipeSerializer getSerializer() { public IRecipeType getType() { return Registration.MINIATURIZATION_RECIPE_TYPE; } + + @Override + public ResourceLocation getId() { + return this.id; + } + + @Override + public void setId(ResourceLocation recipeId) { + this.id = recipeId; + } } diff --git a/src/main/java/com/robotgryphon/compactcrafting/recipes/RecipeHelper.java b/src/main/java/com/robotgryphon/compactcrafting/recipes/RecipeHelper.java index f1a72479..7fe51af2 100644 --- a/src/main/java/com/robotgryphon/compactcrafting/recipes/RecipeHelper.java +++ b/src/main/java/com/robotgryphon/compactcrafting/recipes/RecipeHelper.java @@ -1,15 +1,10 @@ package com.robotgryphon.compactcrafting.recipes; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParseException; import com.robotgryphon.compactcrafting.field.MiniaturizationFieldBlockData; import com.robotgryphon.compactcrafting.util.BlockSpaceUtil; import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.BlockPos; -import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.stream.Stream; @@ -69,29 +64,4 @@ public static Map getComponentCounts(Map map) return counts; } - public static Map getComponentMapFromPattern(JsonObject layer) { - if(!layer.has("pattern")) - return Collections.emptyMap(); - - JsonArray layerPattern = layer.get("pattern").getAsJsonArray(); - int zSize = layerPattern.size(); - - String[][] mappedToArray = new String[zSize][]; - - for(int z = 0; z < zSize; z++) { - JsonElement jsonElement = layerPattern.get(z); - if(!jsonElement.isJsonArray()) - throw new JsonParseException("Mixed layer definition got a non-array in its pattern definition."); - - JsonArray el = jsonElement.getAsJsonArray(); - String[] xValues = new String[el.size()]; - for(int x = 0; x < el.size(); x++) { - xValues[x] = el.get(x).getAsString(); - } - - mappedToArray[z] = xValues; - } - - return convertMultiArrayToMap(mappedToArray); - } } diff --git a/src/main/java/com/robotgryphon/compactcrafting/recipes/components/RecipeBlockStateComponent.java b/src/main/java/com/robotgryphon/compactcrafting/recipes/components/RecipeBlockStateComponent.java new file mode 100644 index 00000000..59551e9a --- /dev/null +++ b/src/main/java/com/robotgryphon/compactcrafting/recipes/components/RecipeBlockStateComponent.java @@ -0,0 +1,124 @@ +package com.robotgryphon.compactcrafting.recipes.components; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import com.robotgryphon.compactcrafting.CompactCrafting; +import com.robotgryphon.compactcrafting.core.Registration; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.state.Property; +import net.minecraft.state.StateContainer; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.registries.ForgeRegistries; + +import java.util.*; +import java.util.function.Predicate; + +public class RecipeBlockStateComponent extends RecipeComponent { + + public Block block; + private final Map>> filters; + private final HashMap> allowedValues; + + public static final Codec CODEC = RecordCodecBuilder.create(i -> i.group( + ResourceLocation.CODEC.fieldOf("block").forGetter(RecipeBlockStateComponent::getBlockName), + Codec.unboundedMap(Codec.STRING, Codec.STRING.listOf()).optionalFieldOf("properties").forGetter(RecipeBlockStateComponent::getProperties) + ).apply(i, RecipeBlockStateComponent::new)); + + private Optional>> getProperties() { + return Optional.of(allowedValues); + } + + private ResourceLocation getBlockName() { + return block.getRegistryName(); + } + + public RecipeBlockStateComponent(Block b) { + this.block = b; + this.filters = new HashMap<>(); + this.allowedValues = new HashMap<>(); + } + + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") // yes I know + public RecipeBlockStateComponent(ResourceLocation blockId, Optional>> propertyRequirements) { + this.block = ForgeRegistries.BLOCKS.getValue(blockId); + if (this.block == null) + throw new IllegalArgumentException("Block identifier does not exist."); + + this.filters = new HashMap<>(); + this.allowedValues = new HashMap<>(); + + propertyRequirements.ifPresent(userRequestedValues -> { + StateContainer stateContainer = this.block.getStateContainer(); + for (Map.Entry> entry : userRequestedValues.entrySet()) { + String propertyName = entry.getKey(); + List userFilteredProps = entry.getValue(); + + Property prop = stateContainer.getProperty(propertyName); + if (prop != null) { + + List userAllowed = new ArrayList<>(); + List propertyAcceptableValues = new ArrayList<>(); + for (String userValue : userFilteredProps) { + prop.parseValue(userValue).ifPresent(u -> { + // We keep two values here - the actual property value for comparison, + // and the string value the user provided (for re-serialization in the CODEC) + propertyAcceptableValues.add(userValue); + userAllowed.add(u); + }); + } + + this.allowedValues.put(propertyName, propertyAcceptableValues); + this.filters.put(propertyName, userAllowed::contains); + } else { + CompactCrafting.LOGGER.warn("Not a valid property: " + propertyName); + } + } + }); + } + + public void setFilter(String property, Predicate> val) { + // Check property exists by name + Property property1 = block.getStateContainer().getProperty(property); + if (property1 == null) + throw new IllegalArgumentException(property); + + // Property exists in state container, we're good + Collection allowedValues = property1.getAllowedValues(); + boolean anyMatch = allowedValues.stream().anyMatch(v -> val.test((Comparable) v)); + if (!anyMatch) { + CompactCrafting.LOGGER.warn("Failed to allow filter: No values would be valid for property [{}]", property); + return; + } + + filters.put(property, val); + } + + public boolean filterMatches(BlockState state) { + if(state.getBlock().getRegistryName() != this.block.getRegistryName()) + return false; + + for (Property prop : state.getProperties()) { + String name = prop.getName(); + + // If it's not in the whitelist, we don't care about what the value is + if (!filters.containsKey(name)) + continue; + + Comparable val = state.get(prop); + boolean matches = filters.get(name).test(val); + if (!matches) return false; + } + + return true; + } + + @Override + public RecipeComponentType getType() { + return Registration.BLOCKSTATE_COMPONENT.get(); + } + + public Block getBlock() { + return this.block; + } +} diff --git a/src/main/java/com/robotgryphon/compactcrafting/recipes/components/RecipeComponent.java b/src/main/java/com/robotgryphon/compactcrafting/recipes/components/RecipeComponent.java new file mode 100644 index 00000000..9c8323ec --- /dev/null +++ b/src/main/java/com/robotgryphon/compactcrafting/recipes/components/RecipeComponent.java @@ -0,0 +1,5 @@ +package com.robotgryphon.compactcrafting.recipes.components; + +public abstract class RecipeComponent { + public abstract RecipeComponentType getType(); +} diff --git a/src/main/java/com/robotgryphon/compactcrafting/recipes/components/RecipeComponentType.java b/src/main/java/com/robotgryphon/compactcrafting/recipes/components/RecipeComponentType.java new file mode 100644 index 00000000..457df564 --- /dev/null +++ b/src/main/java/com/robotgryphon/compactcrafting/recipes/components/RecipeComponentType.java @@ -0,0 +1,34 @@ +package com.robotgryphon.compactcrafting.recipes.components; + +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.DynamicOps; +import com.robotgryphon.compactcrafting.core.Registration; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.registries.IForgeRegistryEntry; + +public interface RecipeComponentType + extends IForgeRegistryEntry> { + // Lifted and modified from a Forge PR #7668, temporary until Forge itself supports the Codec interface + Codec CODEC = new Codec() { + @Override + public DataResult> decode(DynamicOps ops, T input) { + return ResourceLocation.CODEC.decode(ops, input).flatMap(keyValuePair -> !Registration.RECIPE_COMPONENT_TYPES.containsKey(keyValuePair.getFirst()) ? + DataResult.error("Unknown registry key: " + keyValuePair.getFirst()) : + DataResult.success(keyValuePair.mapFirst(Registration.RECIPE_COMPONENT_TYPES::getValue))); + } + + @Override + public DataResult encode(RecipeComponentType input, DynamicOps ops, T prefix) { + ResourceLocation key = input.getRegistryName(); + if(key == null) + return DataResult.error("Unknown registry element " + input); + + T toMerge = ops.createString(key.toString()); + return ops.mergeToPrimitive(prefix, toMerge); + } + }; + + Codec getCodec(); +} diff --git a/src/main/java/com/robotgryphon/compactcrafting/recipes/components/SimpleRecipeComponentType.java b/src/main/java/com/robotgryphon/compactcrafting/recipes/components/SimpleRecipeComponentType.java new file mode 100644 index 00000000..fb3e48c8 --- /dev/null +++ b/src/main/java/com/robotgryphon/compactcrafting/recipes/components/SimpleRecipeComponentType.java @@ -0,0 +1,20 @@ +package com.robotgryphon.compactcrafting.recipes.components; + +import com.mojang.serialization.Codec; +import net.minecraftforge.registries.ForgeRegistryEntry; + +public class SimpleRecipeComponentType + extends ForgeRegistryEntry> + implements RecipeComponentType { + + private final Codec s; + + public SimpleRecipeComponentType(Codec comp) { + this.s = comp; + } + + @Override + public Codec getCodec() { + return this.s; + } +} diff --git a/src/main/java/com/robotgryphon/compactcrafting/recipes/data/MiniaturizationRecipeSerializer.java b/src/main/java/com/robotgryphon/compactcrafting/recipes/data/MiniaturizationRecipeSerializer.java index d53483e2..eee5753e 100644 --- a/src/main/java/com/robotgryphon/compactcrafting/recipes/data/MiniaturizationRecipeSerializer.java +++ b/src/main/java/com/robotgryphon/compactcrafting/recipes/data/MiniaturizationRecipeSerializer.java @@ -1,20 +1,18 @@ package com.robotgryphon.compactcrafting.recipes.data; +import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.JsonOps; import com.robotgryphon.compactcrafting.CompactCrafting; -import com.robotgryphon.compactcrafting.core.Registration; import com.robotgryphon.compactcrafting.recipes.MiniaturizationRecipe; -import com.robotgryphon.compactcrafting.recipes.data.json.MiniaturizationRecipeJsonSerializer; -import com.robotgryphon.compactcrafting.recipes.data.serialization.RecipeBufferData; -import com.robotgryphon.compactcrafting.recipes.data.serialization.layers.RecipeLayerSerializer; -import com.robotgryphon.compactcrafting.recipes.exceptions.MiniaturizationRecipeException; -import com.robotgryphon.compactcrafting.recipes.layers.IRecipeLayer; -import com.robotgryphon.compactcrafting.recipes.layers.dim.IDynamicRecipeLayer; import net.minecraft.item.crafting.IRecipeSerializer; import net.minecraft.nbt.CompoundNBT; +import net.minecraft.nbt.INBT; +import net.minecraft.nbt.NBTDynamicOps; import net.minecraft.network.PacketBuffer; import net.minecraft.util.ResourceLocation; -import net.minecraft.util.math.AxisAlignedBB; import net.minecraftforge.registries.ForgeRegistryEntry; import javax.annotation.Nullable; @@ -25,96 +23,58 @@ public class MiniaturizationRecipeSerializer extends ForgeRegistryEntry attempt = MiniaturizationRecipeJsonSerializer.deserialize(json, recipeId); - return attempt.orElse(null); + Optional> p = MiniaturizationRecipe.CODEC.decode(JsonOps.INSTANCE, json) + .resultOrPartial(err -> { + CompactCrafting.LOGGER.error("Error loading recipe: " + err); + }); + + MiniaturizationRecipe r = p.map(Pair::getFirst).orElse(null); + if (r != null) r.setId(recipeId); + return r; } @Nullable @Override public MiniaturizationRecipe read(ResourceLocation recipeId, PacketBuffer buffer) { CompactCrafting.LOGGER.debug("Starting recipe read: {}", recipeId); - MiniaturizationRecipe recipe = new MiniaturizationRecipe(recipeId); - CompoundNBT dim = buffer.readCompoundTag(); - AxisAlignedBB dims = new AxisAlignedBB( - 0, 0, 0, - dim.getDouble("x"), - dim.getDouble("y"), - dim.getDouble("z") - ); + CompoundNBT n = buffer.readCompoundTag(); + if (n != null && n.contains("recipe")) { + INBT recipeNbt = n.get("recipe"); - try { - RecipeBufferData.readRecipeCatalysts(recipe, buffer); - RecipeBufferData.readRecipeOutputs(recipe, buffer); - RecipeBufferData.readComponentInfo(recipe, buffer); - } catch (Exception e) { - CompactCrafting.LOGGER.error(e); - } - - CompactCrafting.LOGGER.debug("Done loading recipe meta, starting layer loading."); - - try { - int numLayers = buffer.readInt(); - recipe.setLayers(new IRecipeLayer[numLayers]); - for (int i = 0; i < numLayers; i++) { - ResourceLocation layerType = buffer.readResourceLocation(); - if (Registration.RECIPE_SERIALIZERS.containsKey(layerType)) { - RecipeLayerSerializer serializer = Registration.RECIPE_SERIALIZERS.getValue(layerType); - IRecipeLayer layer = serializer.readLayerData(buffer); - if (layer instanceof IDynamicRecipeLayer) - ((IDynamicRecipeLayer) layer).setRecipeDimensions(dims); + MiniaturizationRecipe rec = MiniaturizationRecipe.CODEC.decode(NBTDynamicOps.INSTANCE, recipeNbt) + .resultOrPartial(err -> { - recipe.setLayer(i, layer); - } - } - } + }).get().getFirst(); - catch(Exception e) { - CompactCrafting.LOGGER.error("Error loading layers.", e); + rec.setId(recipeId); + return rec; } - try { - /* - * If all layers in the recipe are dynamically-sized, set the dimensions based on - * what they are on the layer spec from the server (would be loaded from JSON) - */ - if(recipe.getLayers().allMatch(l -> l instanceof IDynamicRecipeLayer)) - recipe.setFluidDimensions(dims); - } catch (MiniaturizationRecipeException e) { - CompactCrafting.LOGGER.error("Unable to set fluid recipe dimensions.", e); - } - - return recipe; + CompactCrafting.LOGGER.error(String.format("Miniaturization recipe failed to decode: %s", recipeId)); + return null; } @Override public void write(PacketBuffer buffer, MiniaturizationRecipe recipe) { - AxisAlignedBB dimensions = recipe.getDimensions(); - CompoundNBT dim = new CompoundNBT(); - dim.putDouble("x", dimensions.getXSize()); - dim.putDouble("y", dimensions.getYSize()); - dim.putDouble("z", dimensions.getZSize()); - buffer.writeCompoundTag(dim); - - RecipeBufferData.writeRecipeCatalysts(recipe, buffer); - RecipeBufferData.writeRecipeOutputs(recipe, buffer); - RecipeBufferData.writeComponentInfo(recipe, buffer); - - int numLayers = recipe.getNumberLayers(); - buffer.writeInt(numLayers); - - recipe.getLayers().forEach(layer -> { - if(layer == null) { - buffer.writeResourceLocation(new ResourceLocation(CompactCrafting.MOD_ID, "blank")); - return; - } + NBTDynamicOps ops = NBTDynamicOps.INSTANCE; + try { + DataResult encode = MiniaturizationRecipe.CODEC.encodeStart(ops, recipe); + encode + .resultOrPartial(err -> { + CompactCrafting.LOGGER.error(String.format("Failed to write to packet for recipe: %s", recipe.getId())); + CompactCrafting.LOGGER.error(err); + }) + .ifPresent(nbt -> { + CompoundNBT n = new CompoundNBT(); + n.put("recipe", nbt); + buffer.writeCompoundTag(n); + }); + } - RecipeLayerSerializer serializer = layer.getSerializer(layer); - if(serializer != null) - serializer.writeLayerData(layer, buffer); - else - buffer.writeResourceLocation(new ResourceLocation(CompactCrafting.MOD_ID, "blank")); - }); + catch(NullPointerException npe) { + CompactCrafting.LOGGER.error(String.format("Whoops: %s", recipe.getId()), npe); + } } } diff --git a/src/main/java/com/robotgryphon/compactcrafting/recipes/data/json/LayerDeserializer.java b/src/main/java/com/robotgryphon/compactcrafting/recipes/data/json/LayerDeserializer.java deleted file mode 100644 index 5ab18cdf..00000000 --- a/src/main/java/com/robotgryphon/compactcrafting/recipes/data/json/LayerDeserializer.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.robotgryphon.compactcrafting.recipes.data.json; - -import com.google.gson.*; -import com.robotgryphon.compactcrafting.CompactCrafting; -import com.robotgryphon.compactcrafting.core.Registration; -import com.robotgryphon.compactcrafting.recipes.data.serialization.layers.RecipeLayerSerializer; -import com.robotgryphon.compactcrafting.recipes.exceptions.RecipeLoadingException; -import com.robotgryphon.compactcrafting.recipes.layers.IRecipeLayer; -import net.minecraft.util.ResourceLocation; - -import java.lang.reflect.Type; - -public class LayerDeserializer implements JsonDeserializer { - - @Override - public IRecipeLayer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { - JsonObject root = json.getAsJsonObject(); - - if(!root.has("type")) - throw new JsonParseException("Layer definition missing 'type' property."); - - String type = root.get("type").getAsString(); - ResourceLocation layerType = new ResourceLocation(type); - - if(!Registration.RECIPE_SERIALIZERS.containsKey(layerType)) - { - CompactCrafting.LOGGER.error("Unknown layer type '" + type + "'"); - return null; - } - - RecipeLayerSerializer serializer = Registration.RECIPE_SERIALIZERS.getValue(layerType); - try { - return serializer.readLayerData(root); - } catch (RecipeLoadingException e) { - CompactCrafting.LOGGER.error("Error while reading layer data.", e); - return null; - } - } -} diff --git a/src/main/java/com/robotgryphon/compactcrafting/recipes/data/json/MiniaturizationRecipeJsonSerializer.java b/src/main/java/com/robotgryphon/compactcrafting/recipes/data/json/MiniaturizationRecipeJsonSerializer.java deleted file mode 100644 index 6be4d23e..00000000 --- a/src/main/java/com/robotgryphon/compactcrafting/recipes/data/json/MiniaturizationRecipeJsonSerializer.java +++ /dev/null @@ -1,164 +0,0 @@ -package com.robotgryphon.compactcrafting.recipes.data.json; - -import com.google.gson.*; -import com.robotgryphon.compactcrafting.CompactCrafting; -import com.robotgryphon.compactcrafting.recipes.MiniaturizationRecipe; -import com.robotgryphon.compactcrafting.recipes.layers.IRecipeLayer; -import com.robotgryphon.compactcrafting.recipes.layers.dim.IDynamicRecipeLayer; -import com.robotgryphon.compactcrafting.util.JsonUtil; -import net.minecraft.block.BlockState; -import net.minecraft.item.ItemStack; -import net.minecraft.util.ResourceLocation; -import net.minecraft.util.math.AxisAlignedBB; - -import java.util.Arrays; -import java.util.Collections; -import java.util.Map; -import java.util.Optional; - -public abstract class MiniaturizationRecipeJsonSerializer { - - public static Optional deserialize(JsonObject root, ResourceLocation rl) { - if(!root.has("version")) { - CompactCrafting.LOGGER.debug("Skipping pattern loading for recipe " + rl.toString() + "; no version specified."); - return Optional.empty(); - } - - // TODO: Eventually we want to have a version spec here, but for now we're just going to require it for future updates - int version = root.get("version").getAsInt(); - if(version <= 0) { - CompactCrafting.LOGGER.debug("Skipping pattern loading for recipe " + rl.toString() + "; version must be at least 1."); - return Optional.empty(); - } - - MiniaturizationRecipe recipe = new MiniaturizationRecipe(rl); - - boolean layersLoaded = loadLayers(recipe, root); - if(!layersLoaded) - return Optional.empty(); - - // Load Components - If nothing was loaded, skip the recipe - boolean componentsLoaded = loadComponents(recipe, root); - if(!componentsLoaded || recipe.getNumberComponents() == 0) - return Optional.empty(); - - boolean catalystsLoaded = loadCatalyst(recipe, root); - if(!catalystsLoaded) - return Optional.empty(); - - loadOutputs(recipe, root); - - return Optional.of(recipe); - } - - private static boolean loadOutputs(MiniaturizationRecipe recipe, JsonObject root) { - if(!root.has("outputs")) { - CompactCrafting.LOGGER.warn("Warning: Recipe does not have outputs defined; not skipping here, but the recipe will not give anything!"); - return false; - } - - JsonArray outputs = root.getAsJsonArray("outputs"); - for(JsonElement output : outputs) { - if(!output.isJsonObject()) - continue; - - JsonObject op = output.getAsJsonObject(); - Optional oStack = JsonUtil.getItemStack(op); - if(!oStack.isPresent()) - continue; - - recipe.addOutput(oStack.get()); - } - - return true; - } - - private static boolean loadCatalyst(MiniaturizationRecipe recipe, JsonObject root) { - if(!root.has("catalyst")) { - CompactCrafting.LOGGER.warn("Catalyst entry not found for recipe {}; skipping rest of recipe loading.", recipe.getId()); - return false; - } - - JsonObject catalyst = root.getAsJsonObject("catalyst"); - Optional stack = JsonUtil.getItemStack(catalyst); - if(!stack.isPresent()) - return false; - - ItemStack c = stack.get(); - - if(c.getCount() != 1) { - CompactCrafting.LOGGER.warn("Catalyst definition called for a non-1 count; this is not yet supported."); - c.setCount(1); - } - - recipe.setCatalyst(c); - - return true; - } - - private static boolean loadLayers(MiniaturizationRecipe recipe, JsonObject root) { - String recipeRegName = recipe.getId().toString(); - if(!root.has("layers")) { - String msg = String.format("Skipping pattern loading for recipe %s; no layers defined.", recipeRegName); - CompactCrafting.LOGGER.debug(msg); - return false; - } - - JsonArray layers = root.get("layers").getAsJsonArray(); - LayerDeserializer layerJsonSerializer = new LayerDeserializer(); - Gson g = new GsonBuilder() - .registerTypeAdapter(IRecipeLayer.class, layerJsonSerializer) - .create(); - - IRecipeLayer[] iRecipeLayers = g.fromJson(layers, IRecipeLayer[].class); - Collections.reverse(Arrays.asList(iRecipeLayers)); - - recipe.setLayers(iRecipeLayers); - - boolean allDynamic = Arrays.stream(iRecipeLayers).allMatch(layer -> layer instanceof IDynamicRecipeLayer); - if(allDynamic) { - if(!root.has("recipeSize")) - { - String msg = String.format("Cannot finish recipe definition for %s: all recipe layers are dynamic and no defined size set (recipeSize).", recipeRegName); - CompactCrafting.LOGGER.warn(msg); - return false; - } - - try { - int size = root.get("recipeSize").getAsInt(); - recipe.setFluidDimensions(AxisAlignedBB.withSizeAtOrigin(size, size, size)); - } catch (Exception e) { - CompactCrafting.LOGGER.error("Error while trying to set fluid recipe dimensions.", e); - return false; - } - } - - return true; - } - - private static boolean loadComponents(MiniaturizationRecipe recipe, JsonObject root) { - JsonObject components = root.get("components").getAsJsonObject(); - if(components.size() == 0) { - throw new JsonParseException("Error: No components defined."); - } - - for(Map.Entry component : components.entrySet()) { - String key = component.getKey(); - JsonElement bsElement = component.getValue(); - if(bsElement.isJsonObject()) { - Optional state = JsonUtil.getBlockState(bsElement.getAsJsonObject()); - - if (key.isEmpty() || !state.isPresent()) { - CompactCrafting.LOGGER.warn("Failed to process blockstate for component {}; definition not found.", key); - continue; - } - - recipe.addComponent(key, state.get()); - } else { - CompactCrafting.LOGGER.warn("Failed to process blockstate for component {}; not a JSON object. Cannot decode.", key); - } - } - - return true; - } -} diff --git a/src/main/java/com/robotgryphon/compactcrafting/recipes/data/serialization/RecipeBufferData.java b/src/main/java/com/robotgryphon/compactcrafting/recipes/data/serialization/RecipeBufferData.java deleted file mode 100644 index b9e44bc6..00000000 --- a/src/main/java/com/robotgryphon/compactcrafting/recipes/data/serialization/RecipeBufferData.java +++ /dev/null @@ -1,147 +0,0 @@ -package com.robotgryphon.compactcrafting.recipes.data.serialization; - -import com.mojang.serialization.DataResult; -import com.robotgryphon.compactcrafting.CompactCrafting; -import com.robotgryphon.compactcrafting.recipes.MiniaturizationRecipe; -import net.minecraft.block.BlockState; -import net.minecraft.item.ItemStack; -import net.minecraft.nbt.CompoundNBT; -import net.minecraft.nbt.INBT; -import net.minecraft.nbt.ListNBT; -import net.minecraft.nbt.NBTDynamicOps; -import net.minecraft.network.PacketBuffer; -import net.minecraft.util.ResourceLocation; -import net.minecraftforge.common.util.Constants; - -import javax.annotation.Nonnull; - -public abstract class RecipeBufferData { - - public static final ResourceLocation TYPE_CATALYSTS = new ResourceLocation(CompactCrafting.MOD_ID, "catalysts"); - - /** - * Reads component information from a packet buffer, adding it to a recipe. - * - * @param recipe Recipe to add component information to. - * @param buffer Packet to read component information from. - */ - public static void readComponentInfo(MiniaturizationRecipe recipe, PacketBuffer buffer) { - CompoundNBT components = buffer.readCompoundTag(); - int numComponents = components.getInt("count"); - if(numComponents > 0) { - ListNBT compList = components.getList("components", Constants.NBT.TAG_COMPOUND); - compList.forEach(comp -> { - CompoundNBT compC = (CompoundNBT) comp; - String key = compC.getString("key"); - CompoundNBT stateTag = compC.getCompound("state"); - - BlockState.CODEC.decode(NBTDynamicOps.INSTANCE, stateTag) - .resultOrPartial(CompactCrafting.LOGGER::error) - .ifPresent(state -> { - BlockState compState = state.getFirst(); - recipe.addComponent(key, compState); - - CompactCrafting.LOGGER.debug("Got component: {} ({})", key, compState.toString()); - }); - }); - } - } - - /** - * Writes component information for a recipe to a compound NBT tag. - * @param recipe - * @return - */ - @Nonnull - public static void writeComponentInfo(MiniaturizationRecipe recipe, PacketBuffer buffer) { - CompoundNBT componentData = new CompoundNBT(); - int numComponents = recipe.getComponentKeys().size(); - componentData.putInt("count", numComponents); - - if (numComponents > 0) { - ListNBT compList = new ListNBT(); - recipe.getComponents().forEach((key, state) -> { - DataResult encode = BlockState.CODEC.encode(state, NBTDynamicOps.INSTANCE, null); - encode - .resultOrPartial(CompactCrafting.LOGGER::error) - .ifPresent(stateNbt -> { - CompoundNBT componentTag = new CompoundNBT(); - componentTag.put("state", stateNbt); - componentTag.putString("key", key); - - compList.add(componentTag); - }); - }); - - componentData.put("components", compList); - } - - buffer.writeCompoundTag(componentData); - } - - /** - * Reads recipe output information from a packet buffer. - * - * @param recipe Recipe to load output information into. - * @param buffer Buffer to pull data from. - */ - public static void readRecipeOutputs(MiniaturizationRecipe recipe, PacketBuffer buffer) throws Exception { - CompoundNBT outputMeta = buffer.readCompoundTag(); - if(outputMeta == null || outputMeta.isEmpty() || !outputMeta.contains("outputs")) - throw new Exception("Output information is not readable: no output count or compound not readable."); - - if (outputMeta.getInt("outputs") > 0) { - int numOutputs = outputMeta.getInt("outputs"); - for (int out = 0; out < numOutputs; out++) { - ItemStack output = buffer.readItemStack(); - recipe.addOutput(output); - } - } - } - - /** - * Writes recipe output information to a packet buffer. - * - * @param recipe Recipe to write output information for. - * @param buffer Buffer to write data to. - */ - public static void writeRecipeOutputs(MiniaturizationRecipe recipe, PacketBuffer buffer) { - ItemStack[] outputs = recipe.getOutputs(); - - CompoundNBT outputMeta = new CompoundNBT(); - outputMeta.putInt("outputs", outputs.length); - buffer.writeCompoundTag(outputMeta); - - if (outputs.length > 0) { - for (ItemStack out : outputs) buffer.writeItemStack(out); - } - } - - /** - * Reads catalyst information from a packet buffer. - * - * @param recipe The recipe to add catalyst information to. - * @param buffer The buffer to read information from. - * @throws Exception - */ - public static void readRecipeCatalysts(MiniaturizationRecipe recipe, PacketBuffer buffer) throws Exception { - CompoundNBT tag = buffer.readCompoundTag(); - if(tag == null || tag.isEmpty() || !tag.contains("type")) - throw new Exception("Tag information is not readable: no type tag or compound not readable."); - - if(!tag.getString("type").equals(TYPE_CATALYSTS.toString())) - throw new Exception("Tried to read a non-catalyst tag."); - - ItemStack output = buffer.readItemStack(); - recipe.setCatalyst(output); - } - - public static void writeRecipeCatalysts(MiniaturizationRecipe recipe, PacketBuffer buffer) { - ItemStack catalyst = recipe.getCatalyst(); - - CompoundNBT outputMeta = new CompoundNBT(); - outputMeta.putString("type", TYPE_CATALYSTS.toString()); - buffer.writeCompoundTag(outputMeta); - buffer.writeItemStack(catalyst); - } -} diff --git a/src/main/java/com/robotgryphon/compactcrafting/recipes/data/serialization/layers/FilledLayerSerializer.java b/src/main/java/com/robotgryphon/compactcrafting/recipes/data/serialization/layers/FilledLayerSerializer.java deleted file mode 100644 index 0aab15f8..00000000 --- a/src/main/java/com/robotgryphon/compactcrafting/recipes/data/serialization/layers/FilledLayerSerializer.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.robotgryphon.compactcrafting.recipes.data.serialization.layers; - -import com.google.gson.JsonObject; -import com.google.gson.JsonParseException; -import com.robotgryphon.compactcrafting.core.Registration; -import com.robotgryphon.compactcrafting.recipes.layers.impl.FilledComponentRecipeLayer; -import net.minecraft.network.PacketBuffer; - -public class FilledLayerSerializer extends RecipeLayerSerializer { - - /** - * Reads a layer's data from a packet buffer. - * - * @param buffer The buffer to pull data from. - */ - @Override - public FilledComponentRecipeLayer readLayerData(PacketBuffer buffer) { - String component = buffer.readString(); - FilledComponentRecipeLayer layer = new FilledComponentRecipeLayer(component); - return layer; - } - - @Override - public FilledComponentRecipeLayer readLayerData(JsonObject json) { - if(!json.has("component")) - throw new JsonParseException("Filled layer definition does not have an associated component key."); - - String component = json.get("component").getAsString(); - - return new FilledComponentRecipeLayer(component); - } - - /** - * Writes a layer's data to a packet buffer. - * - * @param layer The layer to write data for. - * @param buffer The buffer to write data to. - */ - @Override - public void writeLayerData(FilledComponentRecipeLayer layer, PacketBuffer buffer) { - String comp = layer.getComponent(); - buffer.writeResourceLocation(Registration.FILLED_LAYER_SERIALIZER.getId()); - buffer.writeString(comp); - } -} diff --git a/src/main/java/com/robotgryphon/compactcrafting/recipes/data/serialization/layers/HollowLayerSerializer.java b/src/main/java/com/robotgryphon/compactcrafting/recipes/data/serialization/layers/HollowLayerSerializer.java deleted file mode 100644 index da186ef6..00000000 --- a/src/main/java/com/robotgryphon/compactcrafting/recipes/data/serialization/layers/HollowLayerSerializer.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.robotgryphon.compactcrafting.recipes.data.serialization.layers; - -import com.google.gson.JsonObject; -import com.robotgryphon.compactcrafting.core.Registration; -import com.robotgryphon.compactcrafting.recipes.exceptions.RecipeLoadingException; -import com.robotgryphon.compactcrafting.recipes.layers.impl.HollowComponentRecipeLayer; -import net.minecraft.network.PacketBuffer; - -public class HollowLayerSerializer extends RecipeLayerSerializer { - - @Override - public HollowComponentRecipeLayer readLayerData(JsonObject json) throws RecipeLoadingException { - if(!json.has("wall")) - throw new RecipeLoadingException("Hollow layer definition does not have an associated component key (wall)."); - - String component = json.get("wall").getAsString(); - - return new HollowComponentRecipeLayer(component); - } - - @Override - public HollowComponentRecipeLayer readLayerData(PacketBuffer buffer) { - String component = buffer.readString(); - HollowComponentRecipeLayer layer = new HollowComponentRecipeLayer(component); - return layer; - } - - @Override - public void writeLayerData(HollowComponentRecipeLayer layer, PacketBuffer buffer) { - String comp = layer.getComponent(); - buffer.writeResourceLocation(Registration.HOLLOW_LAYER_SERIALIZER.getId()); - buffer.writeString(comp); - } -} diff --git a/src/main/java/com/robotgryphon/compactcrafting/recipes/data/serialization/layers/MixedLayerSerializer.java b/src/main/java/com/robotgryphon/compactcrafting/recipes/data/serialization/layers/MixedLayerSerializer.java deleted file mode 100644 index d68cd797..00000000 --- a/src/main/java/com/robotgryphon/compactcrafting/recipes/data/serialization/layers/MixedLayerSerializer.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.robotgryphon.compactcrafting.recipes.data.serialization.layers; - -import com.google.gson.JsonObject; -import com.robotgryphon.compactcrafting.core.Registration; -import com.robotgryphon.compactcrafting.recipes.RecipeHelper; -import com.robotgryphon.compactcrafting.recipes.exceptions.RecipeLoadingException; -import com.robotgryphon.compactcrafting.recipes.layers.impl.MixedComponentRecipeLayer; -import net.minecraft.network.PacketBuffer; -import net.minecraft.util.math.BlockPos; - -import java.util.*; - -public class MixedLayerSerializer extends RecipeLayerSerializer { - - @Override - public MixedComponentRecipeLayer readLayerData(JsonObject json) throws RecipeLoadingException { - if(!json.has("pattern")) - throw new RecipeLoadingException("Mixed layer definition does not have an associated pattern."); - - Map compMap = RecipeHelper.getComponentMapFromPattern(json); - - MixedComponentRecipeLayer mixed = new MixedComponentRecipeLayer(); - for(Map.Entry mapping : compMap.entrySet()) { - String comp = mapping.getValue(); - - // Skip empty and dashed components, treat them as air - if(comp.trim().isEmpty() || comp.equals("-") || comp.equals("_")) - continue; - - mixed.add(comp, mapping.getKey()); - } - - return mixed; - } - - @Override - public MixedComponentRecipeLayer readLayerData(PacketBuffer buffer) { - MixedComponentRecipeLayer mixed = new MixedComponentRecipeLayer(); - - int numberComponents = buffer.readInt(); - for(int pi = 0; pi < numberComponents; pi++) { - String reqComp = buffer.readString(); - int numberFilled = buffer.readInt(); - - List filledPositions = new ArrayList<>(numberFilled); - for(int ci = 0; ci < numberFilled; ci++) { - BlockPos filledPos = buffer.readBlockPos(); - filledPositions.add(filledPos); - } - - mixed.addMultiple(reqComp, filledPositions); - } - - return mixed; - } - - @Override - public void writeLayerData(MixedComponentRecipeLayer layer, PacketBuffer buffer) { - buffer.writeResourceLocation(Registration.MIXED_LAYER_SERIALIZER.getId()); - - Set componentKeys = layer.getComponentTotals().keySet(); - buffer.writeInt(componentKeys.size()); - - componentKeys.forEach(key -> { - buffer.writeString(key); - Collection positionsForComponent = layer.getPositionsForComponent(key); - buffer.writeInt(positionsForComponent.size()); - positionsForComponent.forEach(buffer::writeBlockPos); - }); - } -} diff --git a/src/main/java/com/robotgryphon/compactcrafting/recipes/data/serialization/layers/RecipeLayerSerializer.java b/src/main/java/com/robotgryphon/compactcrafting/recipes/data/serialization/layers/RecipeLayerSerializer.java deleted file mode 100644 index c62d2d6b..00000000 --- a/src/main/java/com/robotgryphon/compactcrafting/recipes/data/serialization/layers/RecipeLayerSerializer.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.robotgryphon.compactcrafting.recipes.data.serialization.layers; - -import com.google.gson.JsonObject; -import com.robotgryphon.compactcrafting.recipes.exceptions.RecipeLoadingException; -import com.robotgryphon.compactcrafting.recipes.layers.IRecipeLayer; -import net.minecraft.network.PacketBuffer; -import net.minecraftforge.registries.ForgeRegistryEntry; - -public class RecipeLayerSerializer - extends ForgeRegistryEntry> { - /** - * Reads a layer's data from a packet buffer. - * - * @param buffer The buffer to pull data from. - */ - public T readLayerData(PacketBuffer buffer) { - return null; - } - - /** - * Writes a layer's data to a packet buffer. - * - * @param layer The layer to write data for. - * @param buffer The buffer to write data to. - */ - public void writeLayerData(T layer, PacketBuffer buffer) { - - } - - /** - * Read a layer's data from a JSON object. - * - * @param json The root of a layer definition, in JSON. - * @return - */ - public T readLayerData(JsonObject json) throws RecipeLoadingException { - return null; - } -} diff --git a/src/main/java/com/robotgryphon/compactcrafting/recipes/exceptions/RecipeLoadingException.java b/src/main/java/com/robotgryphon/compactcrafting/recipes/exceptions/RecipeLoadingException.java deleted file mode 100644 index 65832262..00000000 --- a/src/main/java/com/robotgryphon/compactcrafting/recipes/exceptions/RecipeLoadingException.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.robotgryphon.compactcrafting.recipes.exceptions; - -public class RecipeLoadingException extends Throwable { - - public RecipeLoadingException(String reason) { - super(reason); - } -} diff --git a/src/main/java/com/robotgryphon/compactcrafting/recipes/layers/IRecipeLayer.java b/src/main/java/com/robotgryphon/compactcrafting/recipes/layers/RecipeLayer.java similarity index 55% rename from src/main/java/com/robotgryphon/compactcrafting/recipes/layers/IRecipeLayer.java rename to src/main/java/com/robotgryphon/compactcrafting/recipes/layers/RecipeLayer.java index 1298f288..5e9e4a71 100644 --- a/src/main/java/com/robotgryphon/compactcrafting/recipes/layers/IRecipeLayer.java +++ b/src/main/java/com/robotgryphon/compactcrafting/recipes/layers/RecipeLayer.java @@ -1,14 +1,13 @@ package com.robotgryphon.compactcrafting.recipes.layers; -import com.robotgryphon.compactcrafting.recipes.data.serialization.layers.RecipeLayerSerializer; import net.minecraft.util.math.BlockPos; import java.util.Collection; import java.util.Map; +import java.util.Optional; -public interface IRecipeLayer { - - Map getComponentTotals(); +public abstract class RecipeLayer { + public abstract Map getComponentTotals(); /** * Gets a component key for the given (normalized) position. @@ -16,7 +15,7 @@ public interface IRecipeLayer { * @param pos * @return */ - String getRequiredComponentKeyForPosition(BlockPos pos); + public abstract Optional getRequiredComponentKeyForPosition(BlockPos pos); /** * Get a collection of positions that are filled by a given component. @@ -24,7 +23,7 @@ public interface IRecipeLayer { * @param component * @return */ - Collection getPositionsForComponent(String component); + public abstract Collection getPositionsForComponent(String component); /** * Gets a set of non-air positions that are required for the layer to match. @@ -33,11 +32,11 @@ public interface IRecipeLayer { * * @return */ - Collection getNonAirPositions(); + public abstract Collection getFilledPositions(); - boolean isPositionRequired(BlockPos pos); + public abstract boolean isPositionFilled(BlockPos pos); - int getNumberFilledPositions(); + public abstract int getNumberFilledPositions(); - RecipeLayerSerializer getSerializer(T layer); + public abstract RecipeLayerType getType(); } diff --git a/src/main/java/com/robotgryphon/compactcrafting/recipes/layers/RecipeLayerComponentPositionLookup.java b/src/main/java/com/robotgryphon/compactcrafting/recipes/layers/RecipeLayerComponentPositionLookup.java new file mode 100644 index 00000000..8a45aabe --- /dev/null +++ b/src/main/java/com/robotgryphon/compactcrafting/recipes/layers/RecipeLayerComponentPositionLookup.java @@ -0,0 +1,150 @@ +package com.robotgryphon.compactcrafting.recipes.layers; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.DynamicOps; +import com.mojang.serialization.codecs.PrimitiveCodec; +import com.robotgryphon.compactcrafting.CompactCrafting; +import com.robotgryphon.compactcrafting.recipes.RecipeHelper; +import com.robotgryphon.compactcrafting.util.BlockSpaceUtil; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.BlockPos; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class RecipeLayerComponentPositionLookup { + private Map components; + private Map totalCache; + + public static final Codec CODEC = new PrimitiveCodec() { + @Override + public DataResult read(DynamicOps ops, T input) { + return Codec.STRING.listOf().listOf().decode(ops, input).flatMap(s -> { + List> layerList = s.getFirst(); + + RecipeLayerComponentPositionLookup lookup = new RecipeLayerComponentPositionLookup(); + + int zSize = layerList.size(); + + String[][] mappedToArray = new String[zSize][]; + + for(int z = 0; z < zSize; z++) { + List layerComponents = layerList.get(z); + String[] xValues = new String[layerComponents.size()]; + for(int x = 0; x < layerComponents.size(); x++) { + xValues[x] = layerComponents.get(x); + } + + mappedToArray[z] = xValues; + } + + lookup.components = RecipeHelper.convertMultiArrayToMap(mappedToArray); + + return DataResult.success(lookup); + }); + } + + @Override + public T write(DynamicOps ops, RecipeLayerComponentPositionLookup value) { + AxisAlignedBB boundsForBlocks = BlockSpaceUtil.getBoundsForBlocks(value.getAllPositions()); + + HashMap> revMap = new HashMap<>((int) boundsForBlocks.getXSize()); + for(int x = 0; x < boundsForBlocks.getXSize(); x++) + revMap.putIfAbsent(x, new ArrayList<>()); + + value.components.forEach((pos, comp) -> { + List xList = revMap.get(pos.getX()); + xList.add(pos.getZ(), comp); + }); + + List> fin = new ArrayList<>(revMap.size()); + revMap.forEach(fin::add); + + DataResult encoded = Codec.STRING.listOf().listOf().encode(fin, ops, ops.empty()); + + return encoded + .resultOrPartial(err -> CompactCrafting.LOGGER.error( + String.format("Failed to encode layer component position lookup: %s", err) + )) + .get(); + } + }; + + public RecipeLayerComponentPositionLookup() { + this.components = new HashMap<>(); + } + + public void add(BlockPos location, String component) { + components.putIfAbsent(location, component); + } + + public Collection getComponents() { + return components.values(); + } + + public BlockPos[] getAllPositions() { + return components.keySet().toArray(new BlockPos[0]); + } + + public boolean containsLocation(BlockPos location) { + return components.containsKey(location); + } + + public Stream> stream() { + return this.components.entrySet().stream(); + } + + public Map getComponentTotals() { + if(this.totalCache != null) + return this.totalCache; + + Map totals = new HashMap<>(); + components.forEach((pos, comp) -> { + int prev = 0; + if(!totals.containsKey(comp)) + totals.put(comp, 0); + else + prev = totals.get(comp); + + totals.replace(comp, prev + 1); + }); + + this.totalCache = totals; + + return this.totalCache; + } + + public Optional getRequiredComponentKeyForPosition(BlockPos pos) { + if(components.containsKey(pos)) + return Optional.ofNullable(components.get(pos)); + + return Optional.empty(); + } + + /** + * Get a collection of positions that are filled by a given component. + * + * @param component + * @return + */ + public Collection getPositionsForComponent(String component) { + if(component == null) + return Collections.emptySet(); + + return components.entrySet() + .stream() + .filter(e -> Objects.equals(e.getValue(), component)) + .map(Map.Entry::getKey) + .collect(Collectors.toSet()); + } + + public Collection getFilledPositions() { + return components.keySet(); + } + + public boolean isPositionFilled(BlockPos pos) { + return components.containsKey(pos); + } +} diff --git a/src/main/java/com/robotgryphon/compactcrafting/recipes/layers/RecipeLayerType.java b/src/main/java/com/robotgryphon/compactcrafting/recipes/layers/RecipeLayerType.java new file mode 100644 index 00000000..96e06223 --- /dev/null +++ b/src/main/java/com/robotgryphon/compactcrafting/recipes/layers/RecipeLayerType.java @@ -0,0 +1,34 @@ +package com.robotgryphon.compactcrafting.recipes.layers; + +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.DynamicOps; +import com.robotgryphon.compactcrafting.core.Registration; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.registries.IForgeRegistryEntry; + +public interface RecipeLayerType extends IForgeRegistryEntry> { + + // Lifted and modified from a Forge PR #7668, temporary until Forge itself supports the Codec interface + Codec CODEC = new Codec() { + @Override + public DataResult> decode(DynamicOps ops, T input) { + return ResourceLocation.CODEC.decode(ops, input).flatMap(keyValuePair -> !Registration.RECIPE_LAYER_TYPES.containsKey(keyValuePair.getFirst()) ? + DataResult.error("Unknown registry key: " + keyValuePair.getFirst()) : + DataResult.success(keyValuePair.mapFirst(Registration.RECIPE_LAYER_TYPES::getValue))); + } + + @Override + public DataResult encode(RecipeLayerType input, DynamicOps ops, T prefix) { + ResourceLocation key = input.getRegistryName(); + if(key == null) + return DataResult.error("Unknown registry element " + input); + + T toMerge = ops.createString(key.toString()); + return ops.mergeToPrimitive(prefix, toMerge); + } + }; + + Codec getCodec(); +} diff --git a/src/main/java/com/robotgryphon/compactcrafting/recipes/layers/SimpleRecipeLayerType.java b/src/main/java/com/robotgryphon/compactcrafting/recipes/layers/SimpleRecipeLayerType.java new file mode 100644 index 00000000..45724f5e --- /dev/null +++ b/src/main/java/com/robotgryphon/compactcrafting/recipes/layers/SimpleRecipeLayerType.java @@ -0,0 +1,19 @@ +package com.robotgryphon.compactcrafting.recipes.layers; + +import com.mojang.serialization.Codec; +import net.minecraftforge.registries.ForgeRegistryEntry; + +public class SimpleRecipeLayerType + extends ForgeRegistryEntry> + implements RecipeLayerType { + + private final Codec s; + public SimpleRecipeLayerType(Codec s) { + this.s = s; + } + + @Override + public Codec getCodec() { + return s; + } +} diff --git a/src/main/java/com/robotgryphon/compactcrafting/recipes/layers/impl/FilledComponentRecipeLayer.java b/src/main/java/com/robotgryphon/compactcrafting/recipes/layers/impl/FilledComponentRecipeLayer.java index 08a39109..8302f193 100644 --- a/src/main/java/com/robotgryphon/compactcrafting/recipes/layers/impl/FilledComponentRecipeLayer.java +++ b/src/main/java/com/robotgryphon/compactcrafting/recipes/layers/impl/FilledComponentRecipeLayer.java @@ -1,8 +1,10 @@ package com.robotgryphon.compactcrafting.recipes.layers.impl; +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; import com.robotgryphon.compactcrafting.core.Registration; -import com.robotgryphon.compactcrafting.recipes.data.serialization.layers.RecipeLayerSerializer; -import com.robotgryphon.compactcrafting.recipes.layers.IRecipeLayer; +import com.robotgryphon.compactcrafting.recipes.layers.RecipeLayerType; +import com.robotgryphon.compactcrafting.recipes.layers.RecipeLayer; import com.robotgryphon.compactcrafting.recipes.layers.dim.IDynamicRecipeLayer; import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.BlockPos; @@ -10,13 +12,18 @@ import java.util.Collection; import java.util.Collections; import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; -public class FilledComponentRecipeLayer implements IRecipeLayer, IDynamicRecipeLayer { +public class FilledComponentRecipeLayer extends RecipeLayer implements IDynamicRecipeLayer { private String componentKey; private AxisAlignedBB recipeDimensions; + public static final Codec CODEC = RecordCodecBuilder.create(in -> in.group( + Codec.STRING.fieldOf("component").forGetter(FilledComponentRecipeLayer::getComponent) + ).apply(in, FilledComponentRecipeLayer::new)); + public FilledComponentRecipeLayer(String component) { this.componentKey = component; } @@ -25,14 +32,12 @@ public String getComponent() { return this.componentKey; } - @Override public Map getComponentTotals() { return Collections.singletonMap(componentKey, getNumberFilledPositions()); } - @Override - public String getRequiredComponentKeyForPosition(BlockPos pos) { - return componentKey; + public Optional getRequiredComponentKeyForPosition(BlockPos pos) { + return Optional.ofNullable(componentKey); } /** @@ -41,10 +46,9 @@ public String getRequiredComponentKeyForPosition(BlockPos pos) { * @param component * @return */ - @Override public Collection getPositionsForComponent(String component) { - if(component == this.componentKey) - return getNonAirPositions(); + if (component == this.componentKey) + return getFilledPositions(); return Collections.emptySet(); } @@ -56,27 +60,24 @@ public Collection getPositionsForComponent(String component) { * * @return */ - @Override - public Collection getNonAirPositions() { + public Collection getFilledPositions() { AxisAlignedBB layerBounds = new AxisAlignedBB(0, 0, 0, recipeDimensions.getXSize() - 1, 1, recipeDimensions.getZSize() - 1); return BlockPos.getAllInBox(layerBounds) .map(BlockPos::toImmutable) .collect(Collectors.toSet()); } - @Override - public boolean isPositionRequired(BlockPos pos) { + public boolean isPositionFilled(BlockPos pos) { return true; } - @Override public int getNumberFilledPositions() { - return (int) Math.ceil(recipeDimensions.getXSize() * recipeDimensions.getYSize()); + return (int) Math.ceil(recipeDimensions.getXSize() * recipeDimensions.getZSize()); } @Override - public RecipeLayerSerializer getSerializer(T layer) { - return (RecipeLayerSerializer) Registration.FILLED_LAYER_SERIALIZER.get(); + public RecipeLayerType getType() { + return Registration.FILLED_LAYER_SERIALIZER.get(); } public void setComponent(String component) { diff --git a/src/main/java/com/robotgryphon/compactcrafting/recipes/layers/impl/HollowComponentRecipeLayer.java b/src/main/java/com/robotgryphon/compactcrafting/recipes/layers/impl/HollowComponentRecipeLayer.java index 1aeb5a25..82eac2bf 100644 --- a/src/main/java/com/robotgryphon/compactcrafting/recipes/layers/impl/HollowComponentRecipeLayer.java +++ b/src/main/java/com/robotgryphon/compactcrafting/recipes/layers/impl/HollowComponentRecipeLayer.java @@ -1,36 +1,42 @@ package com.robotgryphon.compactcrafting.recipes.layers.impl; +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; import com.robotgryphon.compactcrafting.core.Registration; -import com.robotgryphon.compactcrafting.recipes.data.serialization.layers.RecipeLayerSerializer; -import com.robotgryphon.compactcrafting.recipes.layers.IRecipeLayer; +import com.robotgryphon.compactcrafting.recipes.layers.RecipeLayer; +import com.robotgryphon.compactcrafting.recipes.layers.RecipeLayerType; import com.robotgryphon.compactcrafting.recipes.layers.dim.IDynamicRecipeLayer; import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.BlockPos; -import java.util.Collection; -import java.util.Collections; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; -public class HollowComponentRecipeLayer implements IRecipeLayer, IDynamicRecipeLayer { +public class HollowComponentRecipeLayer extends RecipeLayer implements IDynamicRecipeLayer { private String componentKey; private AxisAlignedBB recipeDimensions; private Collection filledPositions; + public static final Codec CODEC = RecordCodecBuilder.create(i -> i.group( + Codec.STRING.fieldOf("wall").forGetter(HollowComponentRecipeLayer::getComponent) + ).apply(i, HollowComponentRecipeLayer::new)); + public HollowComponentRecipeLayer(String component) { this.componentKey = component; } @Override + public RecipeLayerType getType() { + return Registration.HOLLOW_LAYER_TYPE.get(); + } + public Map getComponentTotals() { return Collections.singletonMap(componentKey, getNumberFilledPositions()); } - @Override - public String getRequiredComponentKeyForPosition(BlockPos pos) { - return componentKey; + public Optional getRequiredComponentKeyForPosition(BlockPos pos) { + return Optional.ofNullable(componentKey); } /** @@ -39,31 +45,22 @@ public String getRequiredComponentKeyForPosition(BlockPos pos) { * @param component * @return */ - @Override public Collection getPositionsForComponent(String component) { return filledPositions; } - @Override - public Collection getNonAirPositions() { + public Collection getFilledPositions() { return this.filledPositions; } - @Override - public boolean isPositionRequired(BlockPos pos) { + public boolean isPositionFilled(BlockPos pos) { return true; } - @Override public int getNumberFilledPositions() { return filledPositions.size(); } - @Override - public RecipeLayerSerializer getSerializer(T layer) { - return (RecipeLayerSerializer) Registration.HOLLOW_LAYER_SERIALIZER.get(); - } - public void setComponent(String component) { this.componentKey = component; } diff --git a/src/main/java/com/robotgryphon/compactcrafting/recipes/layers/impl/MixedComponentRecipeLayer.java b/src/main/java/com/robotgryphon/compactcrafting/recipes/layers/impl/MixedComponentRecipeLayer.java index 6b8cc6d7..fe6ad1fb 100644 --- a/src/main/java/com/robotgryphon/compactcrafting/recipes/layers/impl/MixedComponentRecipeLayer.java +++ b/src/main/java/com/robotgryphon/compactcrafting/recipes/layers/impl/MixedComponentRecipeLayer.java @@ -1,76 +1,80 @@ package com.robotgryphon.compactcrafting.recipes.layers.impl; +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; import com.robotgryphon.compactcrafting.core.Registration; -import com.robotgryphon.compactcrafting.recipes.data.serialization.layers.RecipeLayerSerializer; -import com.robotgryphon.compactcrafting.recipes.layers.IRecipeLayer; +import com.robotgryphon.compactcrafting.recipes.layers.RecipeLayer; +import com.robotgryphon.compactcrafting.recipes.layers.RecipeLayerComponentPositionLookup; +import com.robotgryphon.compactcrafting.recipes.layers.RecipeLayerType; import com.robotgryphon.compactcrafting.recipes.layers.dim.IRigidRecipeLayer; import com.robotgryphon.compactcrafting.util.BlockSpaceUtil; import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.BlockPos; -import java.util.*; -import java.util.stream.Collectors; +import java.util.Collection; +import java.util.Map; +import java.util.Optional; -public class MixedComponentRecipeLayer implements IRecipeLayer, IRigidRecipeLayer { +public class MixedComponentRecipeLayer extends RecipeLayer implements IRigidRecipeLayer { private AxisAlignedBB dimensions; - private Map componentLookup; - private Map totalCache; + private RecipeLayerComponentPositionLookup componentLookup; + + public static final Codec CODEC = RecordCodecBuilder.create(i -> i.group( + RecipeLayerComponentPositionLookup.CODEC + .fieldOf("pattern") + .forGetter(MixedComponentRecipeLayer::getComponentLookup) + ).apply(i, MixedComponentRecipeLayer::new)); public MixedComponentRecipeLayer() { this.dimensions = AxisAlignedBB.withSizeAtOrigin(0, 0, 0); - this.componentLookup = new HashMap<>(); + this.componentLookup = new RecipeLayerComponentPositionLookup(); + } + + public MixedComponentRecipeLayer(RecipeLayerComponentPositionLookup components) { + this.componentLookup = components; + this.dimensions = BlockSpaceUtil.getBoundsForBlocks(componentLookup.getAllPositions()); } - public void add(String component, BlockPos location) { - componentLookup.putIfAbsent(location, component); - this.dimensions = BlockSpaceUtil.getBoundsForBlocks(componentLookup.keySet()); + public RecipeLayerComponentPositionLookup getComponentLookup() { + return this.componentLookup; + } + + public void addComponentAtLocation(String component, BlockPos location) { + componentLookup.add(location, component); + this.dimensions = BlockSpaceUtil.getBoundsForBlocks(componentLookup.getAllPositions()); } public void addMultiple(String component, Collection locations) { boolean recalc = false; for(BlockPos loc : locations) { - if(!componentLookup.containsKey(loc)) { - componentLookup.put(loc, component); + if(!componentLookup.containsLocation(loc)) { + componentLookup.add(loc, component); recalc = true; } } if(recalc) - this.dimensions = BlockSpaceUtil.getBoundsForBlocks(componentLookup.keySet()); + this.dimensions = BlockSpaceUtil.getBoundsForBlocks(componentLookup.getAllPositions()); } - @Override public AxisAlignedBB getDimensions() { return this.dimensions; } @Override public Map getComponentTotals() { - if(this.totalCache != null) - return this.totalCache; - - Map totals = new HashMap<>(); - componentLookup.forEach((pos, comp) -> { - int prev = 0; - if(!totals.containsKey(comp)) - totals.put(comp, 0); - else - prev = totals.get(comp); - - totals.replace(comp, prev + 1); - }); - - this.totalCache = totals; - - return this.totalCache; + return componentLookup.getComponentTotals(); } + /** + * Gets a component key for the given (normalized) position. + * + * @param pos + * @return + */ @Override - public String getRequiredComponentKeyForPosition(BlockPos pos) { - if(componentLookup.containsKey(pos)) - return componentLookup.get(pos); - - return null; + public Optional getRequiredComponentKeyForPosition(BlockPos pos) { + return componentLookup.getRequiredComponentKeyForPosition(pos); } /** @@ -81,27 +85,26 @@ public String getRequiredComponentKeyForPosition(BlockPos pos) { */ @Override public Collection getPositionsForComponent(String component) { - if(component == null) - return Collections.emptySet(); - - return componentLookup.entrySet() - .stream() - .filter(e -> Objects.equals(e.getValue(), component)) - .map(Map.Entry::getKey) - .collect(Collectors.toSet()); + return componentLookup.getPositionsForComponent(component); } + /** + * Gets a set of non-air positions that are required for the layer to match. + * This is expected to trim the air positions off the edges and return the positions with NW + * in the 0, 0 position. + * + * @return + */ @Override - public Collection getNonAirPositions() { - return componentLookup.keySet(); + public Collection getFilledPositions() { + return componentLookup.getFilledPositions(); } @Override - public boolean isPositionRequired(BlockPos pos) { - return componentLookup.containsKey(pos); + public boolean isPositionFilled(BlockPos pos) { + return componentLookup.isPositionFilled(pos); } - @Override public int getNumberFilledPositions() { return getComponentTotals() .values() @@ -110,7 +113,7 @@ public int getNumberFilledPositions() { } @Override - public RecipeLayerSerializer getSerializer(T layer) { - return (RecipeLayerSerializer) Registration.MIXED_LAYER_SERIALIZER.get(); + public RecipeLayerType getType() { + return Registration.MIXED_LAYER_TYPE.get(); } } diff --git a/src/main/java/com/robotgryphon/compactcrafting/recipes/layers/impl/SingleComponentRecipeLayer.java b/src/main/java/com/robotgryphon/compactcrafting/recipes/layers/impl/SingleComponentRecipeLayer.java deleted file mode 100644 index 74fd4dca..00000000 --- a/src/main/java/com/robotgryphon/compactcrafting/recipes/layers/impl/SingleComponentRecipeLayer.java +++ /dev/null @@ -1,91 +0,0 @@ -package com.robotgryphon.compactcrafting.recipes.layers.impl; - -import com.robotgryphon.compactcrafting.recipes.data.serialization.layers.RecipeLayerSerializer; -import com.robotgryphon.compactcrafting.recipes.layers.IRecipeLayer; -import com.robotgryphon.compactcrafting.recipes.layers.dim.IRigidRecipeLayer; -import com.robotgryphon.compactcrafting.util.BlockSpaceUtil; -import net.minecraft.util.math.AxisAlignedBB; -import net.minecraft.util.math.BlockPos; - -import java.util.Collection; -import java.util.Collections; -import java.util.Map; - -public class SingleComponentRecipeLayer implements IRecipeLayer, IRigidRecipeLayer { - - private String componentKey; - private AxisAlignedBB dimensions; - - /** - * Relative positions that are filled by the component. - * Example: - * - * X -----> - * Z X-X - * | -X- - * | -X-X - * - * = [0, 0], [2, 0], [1, 1], [0, 2], [2, 2] - */ - private Collection filledPositions; - - protected SingleComponentRecipeLayer(String key) { - this.componentKey = key; - } - - public SingleComponentRecipeLayer(String key, Collection filledPositions) { - this.componentKey = key; - this.filledPositions = filledPositions; - this.dimensions = BlockSpaceUtil.getBoundsForBlocks(filledPositions); - } - - @Override - public AxisAlignedBB getDimensions() { - return dimensions; - } - - @Override - public Map getComponentTotals() { - double volume = dimensions.getXSize() * dimensions.getYSize() * dimensions.getZSize(); - return Collections.singletonMap(componentKey, (int) Math.ceil(volume)); - } - - @Override - public String getRequiredComponentKeyForPosition(BlockPos pos) { - return filledPositions.contains(pos) ? componentKey : null; - } - - /** - * Get a collection of positions that are filled by a given component. - * - * @param component - * @return - */ - @Override - public Collection getPositionsForComponent(String component) { - if(component == this.componentKey) - return filledPositions; - - return Collections.emptySet(); - } - - @Override - public Collection getNonAirPositions() { - return filledPositions; - } - - @Override - public boolean isPositionRequired(BlockPos pos) { - return filledPositions.contains(pos); - } - - @Override - public int getNumberFilledPositions() { - return filledPositions.size(); - } - - @Override - public RecipeLayerSerializer getSerializer(T layer) { - return null; - } -} diff --git a/src/main/java/com/robotgryphon/compactcrafting/recipes/data/base/BaseRecipeType.java b/src/main/java/com/robotgryphon/compactcrafting/recipes/setup/BaseRecipeType.java similarity index 90% rename from src/main/java/com/robotgryphon/compactcrafting/recipes/data/base/BaseRecipeType.java rename to src/main/java/com/robotgryphon/compactcrafting/recipes/setup/BaseRecipeType.java index 08369e64..3ffe5689 100644 --- a/src/main/java/com/robotgryphon/compactcrafting/recipes/data/base/BaseRecipeType.java +++ b/src/main/java/com/robotgryphon/compactcrafting/recipes/setup/BaseRecipeType.java @@ -1,4 +1,4 @@ -package com.robotgryphon.compactcrafting.recipes.data.base; +package com.robotgryphon.compactcrafting.recipes.setup; import net.minecraft.item.crafting.IRecipeType; import net.minecraft.util.ResourceLocation; diff --git a/src/main/java/com/robotgryphon/compactcrafting/recipes/data/base/RecipeBase.java b/src/main/java/com/robotgryphon/compactcrafting/recipes/setup/RecipeBase.java similarity index 89% rename from src/main/java/com/robotgryphon/compactcrafting/recipes/data/base/RecipeBase.java rename to src/main/java/com/robotgryphon/compactcrafting/recipes/setup/RecipeBase.java index 22cbad77..493d095a 100644 --- a/src/main/java/com/robotgryphon/compactcrafting/recipes/data/base/RecipeBase.java +++ b/src/main/java/com/robotgryphon/compactcrafting/recipes/setup/RecipeBase.java @@ -1,8 +1,9 @@ -package com.robotgryphon.compactcrafting.recipes.data.base; +package com.robotgryphon.compactcrafting.recipes.setup; import com.robotgryphon.compactcrafting.recipes.FakeInventory; import net.minecraft.item.ItemStack; import net.minecraft.item.crafting.IRecipe; +import net.minecraft.util.ResourceLocation; import net.minecraft.world.World; public abstract class RecipeBase implements IRecipe { @@ -52,4 +53,6 @@ public ItemStack getRecipeOutput() { public boolean isDynamic() { return true; } + + public abstract void setId(ResourceLocation recipeId); } diff --git a/src/main/java/com/robotgryphon/compactcrafting/util/JsonUtil.java b/src/main/java/com/robotgryphon/compactcrafting/util/JsonUtil.java index 67942019..ccc3bb21 100644 --- a/src/main/java/com/robotgryphon/compactcrafting/util/JsonUtil.java +++ b/src/main/java/com/robotgryphon/compactcrafting/util/JsonUtil.java @@ -1,13 +1,25 @@ package com.robotgryphon.compactcrafting.util; +import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.mojang.datafixers.util.Either; import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.DataResult; import com.mojang.serialization.JsonOps; import com.robotgryphon.compactcrafting.CompactCrafting; +import com.robotgryphon.compactcrafting.recipes.components.RecipeBlockStateComponent; +import net.minecraft.block.Block; import net.minecraft.block.BlockState; import net.minecraft.item.ItemStack; +import net.minecraft.state.Property; +import net.minecraft.state.StateContainer; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.registries.ForgeRegistries; +import java.util.Locale; import java.util.Optional; +import java.util.function.Predicate; public abstract class JsonUtil { @@ -20,9 +32,105 @@ public static Optional getItemStack(JsonObject json) { } public static Optional getBlockState(JsonObject json) { - return BlockState.CODEC.decode(JsonOps.INSTANCE, json) - .get().ifRight(error -> { - CompactCrafting.LOGGER.warn("Failed to load blockstate from JSON: {}", error.message()); - }).mapLeft(Pair::getFirst).left(); + Either< + Pair, + DataResult.PartialResult> + > either = BlockState.CODEC.decode(JsonOps.INSTANCE, json).get(); + + Optional>> partial = either.right(); + if(partial.isPresent()) { + String error = partial.get().message(); + ; + + DataResult.PartialResult result = partial.get().map(b -> b.getFirst().getBlockState()); + return Optional.empty(); + } + + return either.mapLeft(Pair::getFirst).left(); + } + + public static Optional getPossibleStates(JsonObject root) { + // Get block information for node + ResourceLocation blockId = new ResourceLocation(root.get("Name").getAsString()); + if(!ForgeRegistries.BLOCKS.containsKey(blockId)) + return Optional.empty(); + + Block b = ForgeRegistries.BLOCKS.getValue(blockId); + if(b == null) + return Optional.empty(); + + RecipeBlockStateComponent matcher = new RecipeBlockStateComponent(b); + if(!root.has("Properties")) { + // Save block itself and mark that all properties are valid + return Optional.of(matcher); + } + + // Get state information for block + StateContainer states = b.getStateContainer(); + + JsonObject properties = root.get("Properties").getAsJsonObject(); + + properties.entrySet().forEach(jsonProperty -> { + String propertyName = jsonProperty.getKey(); + + // Get actual state property from state container on block + Property stateProperty = states.getProperty(propertyName); + if (stateProperty == null) + return; + + JsonElement valueRaw = jsonProperty.getValue(); + if(valueRaw.isJsonObject()) { + JsonObject propMap = valueRaw.getAsJsonObject(); + + // Need to specify property mapping type -- ALLOW, DISALLOW, MATCH + if(!propMap.has("type")) { + return; + } + + String propMapType = propMap.get("type").getAsString(); + switch(propMapType.toLowerCase(Locale.ROOT)) { + case "allow": + if(!propMap.has("values")) { + CompactCrafting.LOGGER.warn("No value specified for property {}", propertyName); + } + + Predicate> pred = (val) -> true; + for (JsonElement acceptedValue : propMap.get("values").getAsJsonArray()) { + if (!acceptedValue.isJsonPrimitive()) continue; + JsonPrimitive prim = acceptedValue.getAsJsonPrimitive(); + if (!prim.isString()) continue; + Optional parsed = stateProperty.parseValue(prim.getAsString()); + if (!parsed.isPresent()) continue; + pred = pred.and((stateValue) -> stateValue.equals(parsed.get())); + } + + matcher.setFilter(propertyName, pred); + break; + + case "disallow": + break; + + case "match": + if(propMap.has("value")) { + String propValue = propMap.get("value").getAsString(); + Optional parsed = stateProperty.parseValue(propValue); + + if(!parsed.isPresent()) { + CompactCrafting.LOGGER.warn("Value for {} is invalid. Allowed values: {}", propertyName, stateProperty.getAllowedValues()); + return; + } + + parsed.ifPresent(v -> { + matcher.setFilter(propertyName, (stateValue) -> stateValue.equals(v)); + }); + } else { + CompactCrafting.LOGGER.warn("No value specified for property {}", propertyName); + } + break; + } + } + }); + + return Optional.of(matcher); } } diff --git a/src/main/java/com/robotgryphon/compactcrafting/world/ProjectorFieldData.java b/src/main/java/com/robotgryphon/compactcrafting/world/ProjectorFieldData.java index b1783812..15b624ea 100644 --- a/src/main/java/com/robotgryphon/compactcrafting/world/ProjectorFieldData.java +++ b/src/main/java/com/robotgryphon/compactcrafting/world/ProjectorFieldData.java @@ -1,28 +1,38 @@ package com.robotgryphon.compactcrafting.world; import com.robotgryphon.compactcrafting.field.FieldProjection; +import com.robotgryphon.compactcrafting.field.FieldProjectionSize; +import com.robotgryphon.compactcrafting.field.ProjectorHelper; import net.minecraft.nbt.CompoundNBT; import net.minecraft.nbt.INBT; import net.minecraft.nbt.NBTUtil; import net.minecraft.util.Direction; import net.minecraft.util.math.BlockPos; +import java.util.Optional; + public class ProjectorFieldData { public BlockPos mainProjector; public BlockPos fieldCenter; + public FieldProjectionSize size; + + private ProjectorFieldData(BlockPos mainProjector, BlockPos fieldCenter) { + this.mainProjector = mainProjector; + this.fieldCenter = fieldCenter; + } - public ProjectorFieldData(BlockPos mainProjector, BlockPos fieldCenter) { + private ProjectorFieldData(FieldProjectionSize fieldSize, BlockPos mainProjector, BlockPos fieldCenter) { this.mainProjector = mainProjector; this.fieldCenter = fieldCenter; + this.size = fieldSize; } public static ProjectorFieldData fromInstance(FieldProjection pf) { BlockPos center = pf.getCenterPosition(); BlockPos main = pf.getProjectorInDirection(Direction.NORTH); - ProjectorFieldData fd = new ProjectorFieldData(main, center); - return fd; + return new ProjectorFieldData(pf.getFieldSize(), main, center); } public CompoundNBT serialize() { @@ -33,11 +43,14 @@ public CompoundNBT serialize() { tag.put("main", mainPos); tag.put("field", centerPos); + if(size != null) + tag.putString("size", size.name()); + return tag; } public static ProjectorFieldData deserialize(INBT fieldTag) { - if(fieldTag instanceof CompoundNBT) { + if (fieldTag instanceof CompoundNBT) { CompoundNBT fieldTagComp = (CompoundNBT) fieldTag; @@ -48,6 +61,17 @@ public static ProjectorFieldData deserialize(INBT fieldTag) { BlockPos fieldCenter = NBTUtil.readBlockPos(fieldCenterComp); ProjectorFieldData fd = new ProjectorFieldData(mainProjector, fieldCenter); + if (fieldTagComp.contains("size")) { + String sSize = fieldTagComp.getString("size"); + fd.size = FieldProjectionSize.valueOf(sSize); + } else { + // If there is no size in data, calculate it - we have the main (N) projector and center + Optional sizeByMainProjector = + ProjectorHelper.findSizeByMainProjector(fieldCenter, mainProjector); + + sizeByMainProjector.ifPresent(s -> fd.size = s); + } + return fd; } diff --git a/src/test/java/com/robotgryphon/compactcrafting/RotationsTest.java b/src/test/java/com/robotgryphon/compactcrafting/tests/RotationsTest.java similarity index 98% rename from src/test/java/com/robotgryphon/compactcrafting/RotationsTest.java rename to src/test/java/com/robotgryphon/compactcrafting/tests/RotationsTest.java index 19de9823..ca893123 100644 --- a/src/test/java/com/robotgryphon/compactcrafting/RotationsTest.java +++ b/src/test/java/com/robotgryphon/compactcrafting/tests/RotationsTest.java @@ -1,4 +1,4 @@ -package com.robotgryphon.compactcrafting; +package com.robotgryphon.compactcrafting.tests; import com.robotgryphon.compactcrafting.util.BlockSpaceUtil; import net.minecraft.util.Rotation; diff --git a/src/test/java/com/robotgryphon/compactcrafting/tests/minecraft/codecs/ComponentMatcherTests.java b/src/test/java/com/robotgryphon/compactcrafting/tests/minecraft/codecs/ComponentMatcherTests.java new file mode 100644 index 00000000..5bed3ab9 --- /dev/null +++ b/src/test/java/com/robotgryphon/compactcrafting/tests/minecraft/codecs/ComponentMatcherTests.java @@ -0,0 +1,106 @@ +package com.robotgryphon.compactcrafting.tests.minecraft.codecs; + +import com.google.gson.JsonElement; +import com.mojang.serialization.JsonOps; +import com.robotgryphon.compactcrafting.CompactCrafting; +import com.robotgryphon.compactcrafting.recipes.components.RecipeBlockStateComponent; +import com.robotgryphon.compactcrafting.tests.util.FileHelper; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.block.StairsBlock; +import net.minecraft.state.properties.Half; +import net.minecraft.state.properties.StairsShape; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; + +public class ComponentMatcherTests { + + @Test + void CanMatchBlock() { + JsonElement json = FileHelper.INSTANCE.getJsonFromFile("block_properties.json"); + + RecipeBlockStateComponent.CODEC.decode(JsonOps.INSTANCE, json) + .resultOrPartial(Assertions::fail) + .ifPresent(res -> { + RecipeBlockStateComponent matcher = res.getFirst(); + + BlockState[] tests = Blocks.COBBLESTONE_STAIRS + .getStateContainer() + .getValidStates() + .toArray(new BlockState[0]); + + Hashtable results = new Hashtable<>(); + for(BlockState stateTest : tests) { + boolean matched = matcher.filterMatches(stateTest); + results.put(stateTest, matched); + } + + List matched = new ArrayList<>(); + for(Map.Entry e : results.entrySet()) { + if(e.getValue()) + matched.add(e.getKey()); + } + + for(BlockState bs : matched) { + if(bs.get(StairsBlock.HALF) == Half.TOP) + Assertions.fail("Found a state with an invalid property TOP"); + + if(bs.get(StairsBlock.SHAPE) != StairsShape.STRAIGHT) + Assertions.fail("Found a state with a non-straight shape"); + } + }); + } + + @Test + void CanMatchBlockNoProperties() { + JsonElement json = FileHelper.INSTANCE.getJsonFromFile("block_no_properties.json"); + + RecipeBlockStateComponent.CODEC.decode(JsonOps.INSTANCE, json) + .resultOrPartial(Assertions::fail) + .ifPresent(res -> { + RecipeBlockStateComponent matcher = res.getFirst(); + + BlockState[] tests = Blocks.COBBLESTONE_STAIRS + .getStateContainer() + .getValidStates() + .toArray(new BlockState[0]); + + Hashtable results = new Hashtable<>(); + for(BlockState stateTest : tests) { + boolean matched = matcher.filterMatches(stateTest); + results.put(stateTest, matched); + } + + List matched = new ArrayList<>(); + for(Map.Entry e : results.entrySet()) { + if(e.getValue()) + matched.add(e.getKey()); + } + + Assertions.assertEquals(tests.length, matched.size(), "Matches does not equal number of states."); + }); + } + + @Test + void CanReserializeComponentMatcher() { + JsonElement json = FileHelper.INSTANCE.getJsonFromFile("block_properties.json"); + + RecipeBlockStateComponent.CODEC.decode(JsonOps.INSTANCE, json) + .resultOrPartial(Assertions::fail) + .ifPresent(res -> { + RecipeBlockStateComponent matcher = res.getFirst(); + + RecipeBlockStateComponent.CODEC + .encodeStart(JsonOps.INSTANCE, matcher) + .resultOrPartial(Assertions::fail) + .ifPresent(jsonE -> { + CompactCrafting.LOGGER.info(jsonE); + }); + }); + } +} diff --git a/src/test/java/com/robotgryphon/compactcrafting/tests/minecraft/codecs/MiniaturizationRecipeCodecTests.java b/src/test/java/com/robotgryphon/compactcrafting/tests/minecraft/codecs/MiniaturizationRecipeCodecTests.java new file mode 100644 index 00000000..f241a234 --- /dev/null +++ b/src/test/java/com/robotgryphon/compactcrafting/tests/minecraft/codecs/MiniaturizationRecipeCodecTests.java @@ -0,0 +1,111 @@ +package com.robotgryphon.compactcrafting.tests.minecraft.codecs; + +import com.google.gson.JsonElement; +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.JsonOps; +import com.robotgryphon.compactcrafting.CompactCrafting; +import com.robotgryphon.compactcrafting.recipes.MiniaturizationRecipe; +import com.robotgryphon.compactcrafting.recipes.layers.RecipeLayer; +import com.robotgryphon.compactcrafting.tests.util.FileHelper; +import net.minecraft.nbt.INBT; +import net.minecraft.nbt.NBTDynamicOps; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.Map; +import java.util.Optional; + +public class MiniaturizationRecipeCodecTests { + + private MiniaturizationRecipe getRecipeFromFile(String filename) { + JsonElement json = FileHelper.INSTANCE.getJsonFromFile(filename); + + Optional> loaded = MiniaturizationRecipe.CODEC.decode(JsonOps.INSTANCE, json) + .resultOrPartial(CompactCrafting.LOGGER::info); + + if (!loaded.isPresent()) { + Assertions.fail("Recipe did not load from file."); + return null; + } else { + return loaded.get().getFirst(); + } + } + + @Test + void LoadsRecipeFromJson() { + JsonElement json = FileHelper.INSTANCE.getJsonFromFile("layers.json"); + + MiniaturizationRecipe.CODEC.decode(JsonOps.INSTANCE, json) + .resultOrPartial(Assertions::fail) + .ifPresent(res -> { + MiniaturizationRecipe recipe = res.getFirst(); + Assertions.assertTrue(true); + }); + } + + @Test + void LoadsRecipeLayersCorrectly() { + MiniaturizationRecipe recipe = getRecipeFromFile("layers.json"); + if (recipe == null) { + Assertions.fail("No recipe was loaded."); + } else { + // There should only be two layers loaded from the file + Assertions.assertEquals(2, recipe.getNumberLayers()); + + Optional topLayer = recipe.getLayer(1); + if (!topLayer.isPresent()) { + Assertions.fail("No top layer loaded."); + return; + } + + RecipeLayer lay = topLayer.get(); + int filledCount = lay.getNumberFilledPositions(); + + // Layer only has one spot (redstone dust) + Assertions.assertEquals(1, filledCount); + + // Top Layer should be a redstone dust, so one 'R' component + Map componentTotals = lay.getComponentTotals(); + Assertions.assertTrue(componentTotals.containsKey("R"), "Expected redstone component in top layer; it does not exist."); + Assertions.assertEquals(1, componentTotals.get("R"), "Expected one redstone required in top layer."); + } + } + + @Test + void MakesRoundTripThroughNbtCorrectly() { + MiniaturizationRecipe recipe = getRecipeFromFile("layers.json"); + if (recipe == null) { + Assertions.fail("No recipe was loaded."); + } else { + DataResult dr = MiniaturizationRecipe.CODEC.encodeStart(NBTDynamicOps.INSTANCE, recipe); + Optional res = dr.resultOrPartial(CompactCrafting.LOGGER::error); + + INBT nbtRecipe = res.get(); + + MiniaturizationRecipe rFromNbt = MiniaturizationRecipe.CODEC.decode(NBTDynamicOps.INSTANCE, nbtRecipe) + .getOrThrow(false, CompactCrafting.LOGGER::info) + .getFirst(); + + // There should only be two layers loaded from the file + Assertions.assertEquals(2, rFromNbt.getNumberLayers()); + + Optional topLayer = rFromNbt.getLayer(1); + if (!topLayer.isPresent()) { + Assertions.fail("No top layer loaded."); + return; + } + + RecipeLayer lay = topLayer.get(); + int filledCount = lay.getNumberFilledPositions(); + + // Layer only has one spot (redstone dust) + Assertions.assertEquals(1, filledCount); + + // Top Layer should be a redstone dust, so one 'R' component + Map componentTotals = lay.getComponentTotals(); + Assertions.assertTrue(componentTotals.containsKey("R"), "Expected redstone component in top layer; it does not exist."); + Assertions.assertEquals(1, componentTotals.get("R"), "Expected one redstone required in top layer."); + } + } +} diff --git a/src/test/java/com/robotgryphon/compactcrafting/tests/util/FileHelper.java b/src/test/java/com/robotgryphon/compactcrafting/tests/util/FileHelper.java new file mode 100644 index 00000000..cb19f7d5 --- /dev/null +++ b/src/test/java/com/robotgryphon/compactcrafting/tests/util/FileHelper.java @@ -0,0 +1,34 @@ +package com.robotgryphon.compactcrafting.tests.util; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; + +public class FileHelper { + + public static final FileHelper INSTANCE = new FileHelper(); + + private FileHelper() {} + + public InputStreamReader openFile(String filename) { + URL res = getClass().getClassLoader().getResource(filename); + try { + InputStream inputStream = res.openStream(); + return new InputStreamReader(inputStream); + } catch (IOException e) { + e.printStackTrace(); + } + + return null; + } + + public JsonElement getJsonFromFile(String filename) { + Gson g = new Gson(); + InputStreamReader isr = openFile(filename); + return g.fromJson(isr, JsonElement.class); + } +} diff --git a/src/test/java/com/robotgryphon/compactcrafting/util/RecipeLoaderUtilTest.java b/src/test/java/com/robotgryphon/compactcrafting/tests/util/RecipeLoaderUtilTest.java similarity index 96% rename from src/test/java/com/robotgryphon/compactcrafting/util/RecipeLoaderUtilTest.java rename to src/test/java/com/robotgryphon/compactcrafting/tests/util/RecipeLoaderUtilTest.java index 763a60d6..c2b81f5b 100644 --- a/src/test/java/com/robotgryphon/compactcrafting/util/RecipeLoaderUtilTest.java +++ b/src/test/java/com/robotgryphon/compactcrafting/tests/util/RecipeLoaderUtilTest.java @@ -1,4 +1,4 @@ -package com.robotgryphon.compactcrafting.util; +package com.robotgryphon.compactcrafting.tests.util; import com.robotgryphon.compactcrafting.recipes.RecipeHelper; import net.minecraft.util.math.BlockPos; diff --git a/src/test/resources/block_no_properties.json b/src/test/resources/block_no_properties.json new file mode 100644 index 00000000..b50bd7ba --- /dev/null +++ b/src/test/resources/block_no_properties.json @@ -0,0 +1,3 @@ +{ + "block": "minecraft:cobblestone_stairs" +} \ No newline at end of file diff --git a/src/test/resources/block_properties.json b/src/test/resources/block_properties.json new file mode 100644 index 00000000..a2e21369 --- /dev/null +++ b/src/test/resources/block_properties.json @@ -0,0 +1,7 @@ +{ + "block": "minecraft:cobblestone_stairs", + "properties": { + "half": ["bottom"], + "shape": ["straight"] + } +} \ No newline at end of file diff --git a/src/test/resources/layers.json b/src/test/resources/layers.json new file mode 100644 index 00000000..e451c776 --- /dev/null +++ b/src/test/resources/layers.json @@ -0,0 +1,21 @@ +{ + "type": "compactcrafting:miniaturization", + "version": 1, + "recipeSize": 1, + "layers": [ + { + "type": "compactcrafting:filled", + "component": "R" + }, + { + "type": "compactcrafting:filled", + "component": "I" + } + ], + "catalyst": { + "id": "minecraft:redstone", + "Count": 1 + }, + "components": {}, + "outputs": [ ] +} \ No newline at end of file