diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a6e4b6676a4..e56255d4b60 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -42,8 +42,8 @@ jobs: - name: Build PR with Gradle run: ./gradlew build if: github.event_name != 'pull_request' - - name: Run Game Tests - run: ./gradlew runGametest + #- name: Run Game Tests + # run: ./gradlew runGametest - name: Upload new Source Text Strings to Crowdin run: ./gradlew uploadToCrowdin env: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 03aacf1953e..d838b7f9f44 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,12 +33,12 @@ jobs: - name: Prepare artifact metadata. Note that VERSION is set by the gradle script. id: prepare_artifact_metadata run: | - echo ARTIFACT_PATH=./build/libs/appliedenergistics2-fabric-${VERSION}.jar >> $GITHUB_OUTPUT - echo ARTIFACT_NAME=appliedenergistics2-fabric-${VERSION}.jar >> $GITHUB_OUTPUT - echo JAVADOC_PATH=./build/libs/appliedenergistics2-fabric-${VERSION}-javadoc.jar >> $GITHUB_OUTPUT - echo JAVADOC_NAME=appliedenergistics2-fabric-${VERSION}-javadoc.jar >> $GITHUB_OUTPUT - echo API_PATH=./build/libs/appliedenergistics2-fabric-${VERSION}-api.jar >> $GITHUB_OUTPUT - echo API_NAME=appliedenergistics2-fabric-${VERSION}-api.jar >> $GITHUB_OUTPUT + echo ARTIFACT_PATH=./build/libs/appliedenergistics2-forge-${VERSION}.jar >> $GITHUB_OUTPUT + echo ARTIFACT_NAME=appliedenergistics2-forge-${VERSION}.jar >> $GITHUB_OUTPUT + echo JAVADOC_PATH=./build/libs/appliedenergistics2-forge-${VERSION}-javadoc.jar >> $GITHUB_OUTPUT + echo JAVADOC_NAME=appliedenergistics2-forge-${VERSION}-javadoc.jar >> $GITHUB_OUTPUT + echo API_PATH=./build/libs/appliedenergistics2-forge-${VERSION}-api.jar >> $GITHUB_OUTPUT + echo API_NAME=appliedenergistics2-forge-${VERSION}-api.jar >> $GITHUB_OUTPUT echo VERSION=${VERSION} >> $GITHUB_OUTPUT - name: Archive build results # It is important to archive .gradle as well since gradle stores the incremental build state there @@ -126,7 +126,7 @@ jobs: - name: Upload to Curseforge uses: Kir-Antipov/mc-publish@4db8bd126f74d5e143822181a2b1201308e2c796 with: - name: AE2 ${{ needs.build.outputs.VERSION }} [FABRIC] + name: AE2 ${{ needs.build.outputs.VERSION }} [NEOFORGE] version: ${{ needs.build.outputs.VERSION }} files: ${{ needs.build.outputs.ARTIFACT_PATH }} dependencies: | @@ -134,8 +134,13 @@ jobs: wthit(optional){modrinth:6AQIaxuO}{curseforge:440979} rei(optional){modrinth:nfn13YXA}{curseforge:310111} jei(optional){modrinth:u6dRKJwZ}{curseforge:238222} + optifine(incompatible) + theoneprobe(optional){modrinth:Eyw0UxEx}{curseforge:245211} curseforge-id: 223794 curseforge-token: ${{ secrets.CURSEFORGE }} + loaders: | + neoforge + forge deploy-modmaven: name: Deploy to Modmaven @@ -174,7 +179,7 @@ jobs: - name: Upload to Modrinth uses: Kir-Antipov/mc-publish@4db8bd126f74d5e143822181a2b1201308e2c796 with: - name: AE2 ${{ needs.build.outputs.VERSION }} [FABRIC] + name: AE2 ${{ needs.build.outputs.VERSION }} [NEOFORGE] version: ${{ needs.build.outputs.VERSION }} files: ${{ needs.build.outputs.ARTIFACT_PATH }} dependencies: | @@ -182,5 +187,10 @@ jobs: wthit(optional){modrinth:6AQIaxuO}{curseforge:440979} rei(optional){modrinth:nfn13YXA}{curseforge:310111} jei(optional){modrinth:u6dRKJwZ}{curseforge:238222} + optifine(incompatible) + theoneprobe(optional){modrinth:Eyw0UxEx}{curseforge:245211} modrinth-id: XxWD5pD3 modrinth-token: ${{ secrets.MODRINTH }} + loaders: | + neoforge + forge diff --git a/build.gradle b/build.gradle index 300d4d533b1..8265888fb5e 100644 --- a/build.gradle +++ b/build.gradle @@ -17,29 +17,27 @@ */ plugins { - id "fabric-loom" + id "net.neoforged.gradle.userdev" id "maven-publish" id "com.diffplug.spotless" - id "io.github.juuxel.loom-quiltflower" id "com.github.johnrengelman.shadow" id "de.undercouch.download" } +evaluationDependsOn(":libs:markdown") + sourceSets { - portaforgy main { - compileClasspath += sourceSets.portaforgy.output - runtimeClasspath += sourceSets.portaforgy.output java { srcDir 'src/main/flatbuffers/generated' + exclude "**/integration/modules/rei/**" + exclude "**/integration/modules/jei/**" } resources { srcDir 'src/generated/resources' } } test { - compileClasspath += sourceSets.portaforgy.output - runtimeClasspath += sourceSets.portaforgy.output } buildtools } @@ -48,7 +46,6 @@ configurations { shaded { transitive = false } - portaforgyImplementation.extendsFrom(compileClasspath) buildtoolsImplementation.extendsFrom(compileClasspath) internal { @@ -62,6 +59,10 @@ configurations { configurations.testRuntimeClasspath.extendsFrom(internal) } +// All jar files from this folder will be added automatically as runtime mod dependencies +def extraModsDir = "extra-mods-${minecraft_version}" +file(extraModsDir).mkdir() + dependencies { // To be copied into the jar file shaded project(path: ':libs:markdown', configuration: "archives") @@ -69,106 +70,77 @@ dependencies { shaded "org.yaml:snakeyaml:${snakeyaml_version}" shaded "com.google.flatbuffers:flatbuffers-java:${flatbuffers_version}" - internal project(':libs:markdown') + implementation(project(':libs:markdown')) { + transitive = false + } // Do not inherit any transitive dependencies here since we rely on those dependencies being // present in Minecrafts own dependencies already. - internal("io.methvin:directory-watcher:${directory_watcher_version}") { + implementation("io.methvin:directory-watcher:${directory_watcher_version}") { transitive = false } - internal("org.yaml:snakeyaml:${snakeyaml_version}") { + implementation("org.yaml:snakeyaml:${snakeyaml_version}") { transitive = false } - internal("com.google.flatbuffers:flatbuffers-java:${flatbuffers_version}") { + implementation("com.google.flatbuffers:flatbuffers-java:${flatbuffers_version}") { transitive = false } // Used for the guide export - internal("org.bytedeco:ffmpeg-platform:6.0-1.5.9") + implementation("org.bytedeco:ffmpeg-platform:6.0-1.5.9") - // To change the versions see the gradle.properties file - minecraft "com.mojang:minecraft:${project.minecraft_version}" - mappings loom.layered() { - officialMojangMappings() - parchment("org.parchmentmc.data:parchment-1.19.3:2023.02.05@zip") - } + implementation "net.neoforged:neoforge:${neoforge_version}" - modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" - testImplementation "net.fabricmc:fabric-loader-junit:${project.loader_version}" + // compile against provided APIs + compileOnly "mezz.jei:jei-${jei_minecraft_version}-common-api:${jei_version}" + compileOnly "mezz.jei:jei-${jei_minecraft_version}-forge-api:${jei_version}" // Always depend on the REI API to compile - modCompileOnly("me.shedaniel:RoughlyEnoughItems-api-fabric:${project.rei_version}") { - exclude group: "net.fabricmc" - exclude group: "net.fabricmc.fabric-api" - exclude group: "org.yaml" // snakeyaml - exclude group: "blue.endless" // jankson - } - modCompileOnly("me.shedaniel:RoughlyEnoughItems-default-plugin-fabric:${project.rei_version}") { - exclude group: "net.fabricmc" - exclude group: "net.fabricmc.fabric-api" - exclude group: "org.yaml" // snakeyaml - exclude group: "blue.endless" // jankson - } - if (project.runtime_itemlist_mod == "jei") { - modCompileOnly("me.shedaniel:RoughlyEnoughItems-fabric:${project.rei_version}") { - exclude group: "net.fabricmc" - exclude group: "net.fabricmc.fabric-api" - } - modLocalRuntime modCompileOnly("mezz.jei:jei-${jei_minecraft_version}-fabric:${jei_version}") { - exclude group: "mezz.jei" - } + runtimeOnly "mezz.jei:jei-${jei_minecraft_version}-forge:${jei_version}" + + compileOnly "me.shedaniel.cloth:basic-math:0.6.1" + compileOnly ("dev.architectury:architectury-forge:${project.architectury_version}") + compileOnly ("me.shedaniel:RoughlyEnoughItems-forge:${project.rei_version}") } else if (project.runtime_itemlist_mod == "rei") { - modCompileOnly("mezz.jei:jei-${jei_minecraft_version}-fabric:${jei_version}") { - exclude group: "mezz.jei" - } - modLocalRuntime modCompileOnly("me.shedaniel:RoughlyEnoughItems-fabric:${project.rei_version}") { - exclude group: "net.fabricmc" - exclude group: "net.fabricmc.fabric-api" - } + implementation "me.shedaniel.cloth:basic-math:0.6.1" + implementation ("me.shedaniel.cloth:cloth-config-forge:${project.cloth_config_version}") + implementation ("dev.architectury:architectury-forge:${project.architectury_version}") + implementation ("me.shedaniel:RoughlyEnoughItems-forge:${project.rei_version}") } else { - modCompileOnly("me.shedaniel:RoughlyEnoughItems-fabric:${project.rei_version}") { - exclude group: "net.fabricmc" - exclude group: "net.fabricmc.fabric-api" - } - modCompileOnly("mezz.jei:jei-${jei_minecraft_version}-fabric:${jei_version}") { - exclude group: "mezz.jei" - } + compileOnly ("me.shedaniel.cloth:cloth-config-forge:${project.cloth_config_version}") + compileOnly ("dev.architectury:architectury-forge:${project.architectury_version}") + compileOnly ("me.shedaniel:RoughlyEnoughItems-forge:${project.rei_version}") } - modCompileOnly("mcp.mobius.waila:wthit-api:fabric-${project.wthit_version}") { - exclude group: "net.fabricmc" - exclude group: "net.fabricmc.fabric-api" + // Locally sourced extra mods for runtime (i.e. testing) + for (extraModJar in fileTree(dir: extraModsDir, include: '*.jar')) { + def basename = extraModJar.name.substring(0, extraModJar.name.length() - ".jar".length()) + def versionSep = basename.lastIndexOf('-') + assert versionSep != -1 + def artifactId = basename.substring(0, versionSep) + def version = basename.substring(versionSep + 1) + runtimeOnly ("extra-mods:$artifactId:$version") } if (project.runtime_tooltip_mod == "wthit") { - modLocalRuntime("mcp.mobius.waila:wthit:fabric-${project.wthit_version}") { - exclude group: "net.fabricmc" - exclude group: "net.fabricmc.fabric-api" - } - } - - modCompileOnly("curse.maven:jade-324717:${project.jade_file_id}") { - exclude group: "net.fabricmc" - exclude group: "net.fabricmc.fabric-api" + runtimeOnly ("mcp.mobius.waila:wthit:forge-${project.wthit_version}") + runtimeOnly ("lol.bai:badpackets:forge-0.1.2") + } else { + compileOnly "mcp.mobius.waila:wthit-api:forge-${project.wthit_version}" } if (project.runtime_tooltip_mod == "jade") { - modLocalRuntime("curse.maven:jade-324717:${project.jade_file_id}") { - exclude group: "net.fabricmc" - exclude group: "net.fabricmc.fabric-api" - } + implementation ("curse.maven:jade-324717:${project.jade_file_id}") + } else { + compileOnly ("curse.maven:jade-324717:${project.jade_file_id}") } - // Fabric API. This is technically optional, but you probably want it anyway. - modImplementation("net.fabricmc.fabric-api:fabric-api:${project.fabric_version}") - include modImplementation("teamreborn:energy:${project.tr_energy_version}") { - exclude group: "net.fabricmc" - exclude group: "net.fabricmc.fabric-api" + def topDependency = "curse.maven:the-one-probe-245211:4159743" + if (project.runtime_tooltip_mod == "top") { + implementation (topDependency) + } else { + compileOnly topDependency } - // TODO 1.20 - // Include No Indium? https://github.com/Luligabi1/NoIndium - //include "me.luligabi:NoIndium:${project.no_indium_version}" - // unit test dependencies testImplementation(platform("org.junit:junit-bom:${project.junit_version}")) testImplementation("org.junit.jupiter:junit-jupiter-api") @@ -181,7 +153,6 @@ dependencies { compileOnly 'org.apache.commons:commons-configuration2:2.9.0' } - archivesBaseName = artifact_basename allprojects { @@ -205,7 +176,7 @@ allprojects { } } maven { - url "https://maven.bai.lol" + url "https://maven2.bai.lol" content { includeGroup "mcp.mobius.waila" includeGroup "lol.bai" @@ -247,6 +218,18 @@ allprojects { includeGroup "vazkii.patchouli" } } + + flatDir { + name "extra-mods" + dir file(extraModsDir) + } + maven { // for TOP + url "https://maven.k-4u.nl/" + content { + includeGroup "mcjty" + } + } + maven { url = 'https://repo.spongepowered.org/maven' } } // ensure everything uses UTF-8 and not some random codepage chosen by gradle @@ -274,11 +257,11 @@ if (ext.branch) { ext.tag = System.getenv('TAG') ?: "" if (ext.tag) { - if (!ext.tag.startsWith("fabric/v")) { - throw new GradleException("Tags for the fabric version should start with fabric/: ${ext.tag}") + if (!ext.tag.startsWith("forge/v")) { + throw new GradleException("Tags for the forge version should start with forge/: ${ext.tag}") } - version = ext.tag.substring("fabric/v".length()) + version = ext.tag.substring("forge/v".length()) // Validate that the rest is a semver version if (version ==~ /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/) { if (System.getenv("GITHUB_ENV")) { @@ -302,75 +285,137 @@ dependencies { buildtoolsImplementation 'com.google.code.gson:gson:2.8.9' } -//////////////////// -// Forge/Minecraft -loom { - accessWidenerPath = file("src/main/resources/ae2.accesswidener") - - runs { - client { - programArgs "--username", "AE2Dev" - property "appeng.tests", "true" - property "guideDev.ae2guide.sources", file("guidebook").absolutePath - } - gametestWorld { - client() - programArgs "--username", "AE2Dev", "--quickPlaySingleplayer", "GametestWorld" - property "appeng.tests", "true" - property "guideDev.ae2guide.sources", file("guidebook").absolutePath - } - guide { - client() - programArgs "--username", "AE2Dev" - property "appeng.tests", "true" - property "guideDev.ae2guide.sources", file("guidebook").absolutePath - property "guideDev.ae2guide.startupPage", "ae2:index.md" - } - datagen { - client() - property "fabric-api.datagen" - property "fabric-api.datagen.output-dir", file("src/generated/resources").absolutePath - property "appeng.datagen.existingData", file("src/main/resources").absolutePath - } - guideexport { - client() - property "appeng.runGuideExportAndExit", "true" - property "appeng.guideExportFolder", file("$buildDir/guide").absolutePath - property "guideDev.ae2guide.sources", file("guidebook").absolutePath - } +/** + * Configures properties common to all run configurations + */ +def commonSystemProperties = [ + 'forge.logging.console.level': 'debug', + 'fml.earlyprogresswindow': 'false', + 'appeng.tests': 'true', +] - // Use to run the tests - gametest { - server() - name "Game Test" - property "appeng.tests", "true" - vmArg "-Dfabric-api.gametest" - runDir "build/gametest" - source sourceSets.test - } +// NG hacks to add our deps to the legacy classpath +// If anything changes here you have to delete the build folder for the neoforge:...:writeMinecraftClasspath task +import net.neoforged.gradle.userdev.runtime.tasks.ClasspathSerializer + +def libSrcSets = [ + project(":libs:markdown").sourceSets.main +] + +def legacyCPLibs = [ + "io.methvin:directory-watcher:${directory_watcher_version}", + "org.yaml:snakeyaml:${snakeyaml_version}", + "com.google.flatbuffers:flatbuffers-java:${flatbuffers_version}", + "org.bytedeco:ffmpeg-platform:6.0-1.5.9", +] + +tasks.withType(ClasspathSerializer).configureEach { + legacyCPLibs.each { lib -> + def libArtifact = lib.split(":")[1] + def libVersion = lib.split(":")[2] + def libJar = libArtifact + "-" + libVersion + ".jar" + + def libFile = project.configurations.runtimeClasspath.find({ + return it.name.equals(libJar) + }) + + it.inputFiles.from libFile + } + libSrcSets.each { srcSet -> + it.inputFiles.from srcSet.output } } -// For reproducible results, the world should be deleted before each run -task cleanGametestWorld(type: Delete) { - delete "build/gametest" +def commonRunProperties = { + workingDirectory = project.file('run') +// jvmArgs "--add-opens", "java.base/sun.security.util=ALL-UNNAMED" +// jvmArgs "--add-opens", "java.base/java.util.jar=ALL-UNNAMED" + systemProperties = commonSystemProperties + // property "mixin.debug.export", "true" + modSources = [sourceSets.main] +} + +//////////////////// +// Forge/Minecraft +minecraft { + accessTransformers { + file('src/main/resources/META-INF/accesstransformer.cfg') + } +} + +runs { + client { + with commonRunProperties + systemProperties = [ + *:commonSystemProperties, + "appeng.tests": "true", + "guideDev.ae2guide.sources": file("guidebook").absolutePath, + ] + } + gametestWorld { + configure("client") + programArguments = ["--username", "AE2Dev", "--quickPlaySingleplayer", "GametestWorld"] + systemProperties = [ + "appeng.tests": "true", + "guideDev.ae2guide.sources": file("guidebook").absolutePath, + ] + } + guide { + configure("client") + with commonRunProperties + systemProperties = [ + "guideDev.ae2guide.sources": file("guidebook").absolutePath, + "guideDev.ae2guide.startupPage": "ae2:index.md" + ] + } + server { + with commonRunProperties + } + data { + with commonRunProperties + programArguments = [ + '--mod', 'ae2', + '--all', + '--output', file('src/generated/resources/').absolutePath, + '--existing', file('src/main/resources').absolutePath + ] + } + guideexport { + configure("client") + with commonRunProperties + systemProperties = [ + "appeng.runGuideExportAndExit": "true", + "appeng.guideExportFolder": file("$buildDir/guide").absolutePath, + "guideDev.ae2guide.sources": file("guidebook").absolutePath + ] + } + // Use to run the tests + gametest { + configure("server") + with commonRunProperties + workingDirectory = project.file("build/gametest") + } } -tasks.runGametest.dependsOn cleanGametestWorld ////////////// // Artifacts processResources { exclude '.cache' - filesMatching("fabric.mod.json") { - expand "version": project.version + filesMatching("META-INF/mods.toml") { + expand 'minecraft_version': project.minecraft_version_range, 'neoforge_version': project.neoforge_version_range, + 'jei_version': project.jei_version_range, 'top_version': project.top_version_range, + 'jade_version': project.jade_version_range + filter { line -> + line.replace('version="0.0.0"', "version=\"$version\"") + } } } jar.enabled = false // Replaced by "shadowJar" shadowJar { - finalizedBy 'remapJar' + finalizedBy 'reobfShadowJar' from sourceSets.main.output.classesDirs from sourceSets.main.output.resourcesDir @@ -387,22 +432,25 @@ shadowJar { "Implementation-Title" : "${project.name}", "Implementation-Version": "${project.version}", "Implementation-Vendor" : "TeamAppliedEnergistics", - "MixinConfigs" : "ae2.mixins.json" ]) } relocate "io.methvin", "appeng.shaded.methvin" relocate "org.yaml.snakeyaml", "appeng.shaded.snakeyaml" - relocate "com.google.flatbuffers", "appeng.shaded.flatbuffers" configurations = [project.configurations.shaded] - archiveClassifier = jar.archiveClassifier - destinationDirectory = jar.destinationDirectory + archiveClassifier = null } -remapJar { - inputFile = shadowJar.archiveFile -} +//reobf { +// shadowJar {} +//} + +//afterEvaluate { +// tasks.reobfJar.enabled = false +//} + +//tasks.assemble.dependsOn reobfShadowJar def publicApiIncludePatterns = { exclude "**/*Internal.*" @@ -410,26 +458,9 @@ def publicApiIncludePatterns = { include "appeng/api/**" } -javadoc { - source = sourceSets.main.allJava - classpath = sourceSets.main.compileClasspath + sourceSets.main.output - - options.addStringOption('Xdoclint:none', '-quiet') - options.encoding = 'UTF-8' - options.charSet = 'UTF-8' -} -javadoc publicApiIncludePatterns - -task javadocJar(type: Jar, dependsOn: javadoc, group: "build") { - archiveClassifier = "javadoc" - from javadoc.destinationDir -} -// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task -// if it is present. -// If you remove this task, sources will not be generated. -task sourcesJar(type: Jar, dependsOn: classes) { +task sourcesJar(type: Jar) { archiveClassifier = "sources" - from sourceSets.main.allSource + from sourceSets.main.allJava } task apiJar(type: Jar, group: "build") { @@ -444,7 +475,6 @@ task apiJar(type: Jar, group: "build") { apiJar publicApiIncludePatterns artifacts { - archives javadocJar archives sourcesJar archives apiJar } @@ -452,23 +482,22 @@ artifacts { ////////////////// // Maven publish publishing { - if (!version.endsWith("-SNAPSHOT")) { - publications { - maven(MavenPublication) { - groupId = project.group - artifactId = 'appliedenergistics2-fabric' - version = project.version - - // add all the jars that should be included when publishing to maven - artifact(remapJar) { - builtBy remapJar - } - artifact(sourcesJar) { - builtBy remapSourcesJar - } - artifact javadocJar - artifact apiJar + publications { + maven(MavenPublication) { + groupId = project.group + artifactId = project.archivesBaseName + version = project.version + + // ForgeGradle will generate wild dependency definitions, see https://github.com/MinecraftForge/ForgeGradle/issues/584 + // Since we don't actually depend on anything, just remove the entire node. + pom.withXml { + asNode().remove(asNode().dependencies) } + + from components.java + artifact shadowJar + artifact sourcesJar + artifact apiJar } } repositories { @@ -543,18 +572,6 @@ task downloadFromCrowdin(type: JavaExec) { workingDir "." } -//////////////// -// IntelliJ Project Import -// The Mixin annotation process does not have an obfuscation source when running through the IntelliJ compiler, -// thus we have to prevent it from being activated as part of importing this Gradle project into IntelliJ. -if (System.getProperty("idea.sync.active") == "true") { - afterEvaluate { - tasks.withType(JavaCompile).all { - it.options.annotationProcessorPath = files() - } - } -} - // See https://github.com/AppliedEnergistics/Applied-Energistics-2/issues/5259 // Gradle module metadata contains mapped dependencies, making our artifacts unconsumable tasks.withType(GenerateModuleMetadata) { diff --git a/gradle.properties b/gradle.properties index d5c3faf5ec6..a015fc8af6d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,14 +3,16 @@ version_minor=0 version_patch=0 artifact_group=appeng -artifact_basename=appliedenergistics2-fabric +artifact_basename=appliedenergistics2-neoforge ######################################################### # Minecraft Versions # ######################################################### -minecraft_release=1.20.1 -minecraft_version=1.20.1 -loader_version=0.14.21 +minecraft_release=1.20 +minecraft_version=1.20.2 +minecraft_version_range=[1.20.2,1.20.3) +neoforge_version=20.2.43-beta +neoforge_version_range=[20.2.43-beta,) ######################################################### # Provided APIs # @@ -18,20 +20,24 @@ loader_version=0.14.21 jei_minecraft_version=1.20.1 jei_version=15.0.0.12 jei_version_range=[15.0.0,16.0.0) -fabric_version=0.83.1+1.20.1 +top_minecraft_release=1.20 +top_version=9.0.0 +# please learn how to use semver... +top_version_range=[1.20.0,) +jade_version_range=[11.0.0,) +cloth_config_version=9.0.94 +architectury_version=7.0.66 rei_version=12.0.622 wthit_version=8.1.0 -jade_file_id=4586193 -tr_energy_version=3.0.0 -no_indium_version=1.1.0+1.19.3 +jade_file_id=4573193 # Pick which item list mod gets picked at runtime in dev # Available options: jei, rei, none runtime_itemlist_mod=none -# Set to wthit or jade to pick which tooltip mod gets picked at runtime +# Set to wthit, jade, or top to pick which tooltip mod gets picked at runtime # for the dev environment. -runtime_tooltip_mod=jade +runtime_tooltip_mod=none ######################################################### # Third party dependencies @@ -49,7 +55,7 @@ org.gradle.daemon=false # Temp fix for Spotless / Remove Unused Imports: # https://github.com/diffplug/spotless/issues/834 -org.gradle.jvmargs=-Xmx2G \ +org.gradle.jvmargs=-Xmx4G \ --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \ --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED \ --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED \ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 37aef8d3f0c..744c64d1277 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/settings.gradle b/settings.gradle index ec47daf449f..a026cd36009 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,14 +1,22 @@ - pluginManagement { repositories { maven { - name = 'Fabric' - url = 'https://maven.fabricmc.net/' + name = 'NeoForged' + url = 'https://maven.neoforged.net/releases' + } + maven { + name = 'NeoGradle' + url = 'https://ldtteam.jfrog.io/artifactory/neogradle-next' + } + maven { + name = "Sponge" + url = 'https://repo.spongepowered.org/maven' } gradlePluginPortal() + mavenCentral() } plugins { - id 'fabric-loom' version '1.3-SNAPSHOT' + id 'net.neoforged.gradle.userdev' version '7.0.41' id 'com.diffplug.spotless' version '5.12.4' id 'io.github.juuxel.loom-quiltflower' version '1.10.0' id 'com.github.johnrengelman.shadow' version '7.0.0' diff --git a/src/buildtools/java/ValidateResourceIds.java b/src/buildtools/java/ValidateResourceIds.java index 0b9832cd019..1750561a4e1 100644 --- a/src/buildtools/java/ValidateResourceIds.java +++ b/src/buildtools/java/ValidateResourceIds.java @@ -1,3 +1,4 @@ + import net.minecraft.resources.ResourceLocation; import java.io.IOException; diff --git a/src/main/java/appeng/api/IAEAddonEntrypoint.java b/src/main/java/appeng/api/IAEAddonEntrypoint.java index 959da0bc110..e69de29bb2d 100644 --- a/src/main/java/appeng/api/IAEAddonEntrypoint.java +++ b/src/main/java/appeng/api/IAEAddonEntrypoint.java @@ -1,19 +0,0 @@ -package appeng.api; - -/** - * If your addons needs to be notified when AE2 is fully initialized (and has registered all of its items and fluids), - * implement this class and register it as an entrypoint in your mod. - *

- * Entrypoint IDs supported by AE2: - *

- *

- * See the Fabric Wiki for an explanation of - * entrypoints. - */ -public interface IAEAddonEntrypoint { - void onAe2Initialized(); -} diff --git a/src/main/java/appeng/api/LICENSE b/src/main/java/appeng/api/LICENSE index 643181ca87e..8a2d5083b7e 100644 --- a/src/main/java/appeng/api/LICENSE +++ b/src/main/java/appeng/api/LICENSE @@ -1,6 +1,12 @@ + +Some packages have a different license, please see the corresponding LICENSE files +in those packages. + +---- + The MIT License (MIT) -Copyright (c) 2013 AlgorithmX2 +Copyright (c) 2023 Team Applied Energistics Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/src/main/java/appeng/api/behaviors/FluidContainerItemStrategy.java b/src/main/java/appeng/api/behaviors/FluidContainerItemStrategy.java index 7faa0f74982..a47be5b7532 100644 --- a/src/main/java/appeng/api/behaviors/FluidContainerItemStrategy.java +++ b/src/main/java/appeng/api/behaviors/FluidContainerItemStrategy.java @@ -1,17 +1,14 @@ package appeng.api.behaviors; +import com.google.common.primitives.Ints; + import org.jetbrains.annotations.Nullable; -import net.fabricmc.fabric.api.transfer.v1.context.ContainerItemContext; -import net.fabricmc.fabric.api.transfer.v1.fluid.FluidStorage; -import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant; -import net.fabricmc.fabric.api.transfer.v1.item.PlayerInventoryStorage; -import net.fabricmc.fabric.api.transfer.v1.storage.Storage; -import net.fabricmc.fabric.api.transfer.v1.storage.StorageUtil; -import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction; import net.minecraft.world.entity.player.Player; import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.item.ItemStack; +import net.neoforged.neoforge.common.capabilities.Capabilities; +import net.neoforged.neoforge.items.ItemHandlerHelper; import appeng.api.config.Actionable; import appeng.api.stacks.AEFluidKey; @@ -19,43 +16,62 @@ import appeng.util.GenericContainerHelper; import appeng.util.fluid.FluidSoundHelper; -class FluidContainerItemStrategy implements ContainerItemStrategy> { +class FluidContainerItemStrategy + implements ContainerItemStrategy { @Override public @Nullable GenericStack getContainedStack(ItemStack stack) { return GenericContainerHelper.getContainedFluidStack(stack); } @Override - public @Nullable Storage findCarriedContext(Player player, AbstractContainerMenu menu) { - return ContainerItemContext.ofPlayerCursor(player, menu).find(FluidStorage.ITEM); + public @Nullable Context findCarriedContext(Player player, AbstractContainerMenu menu) { + if (menu.getCarried().getCapability(Capabilities.FLUID_HANDLER_ITEM).isPresent()) { + return new CarriedContext(player, menu); + } + return null; } @Override - public @Nullable Storage findPlayerSlotContext(Player player, int slot) { - var playerInv = PlayerInventoryStorage.of(player.getInventory()); - return ContainerItemContext.ofPlayerSlot(player, playerInv.getSlots().get(slot)).find(FluidStorage.ITEM); + public @Nullable Context findPlayerSlotContext(Player player, int slot) { + if (player.getInventory().getItem(slot).getCapability(Capabilities.FLUID_HANDLER_ITEM).isPresent()) { + return new PlayerInvContext(player, slot); + } + + return null; } @Override - public long extract(Storage context, AEFluidKey what, long amount, Actionable mode) { - try (var tx = Transaction.openOuter()) { - var extracted = context.extract(what.toVariant(), amount, tx); - if (mode == Actionable.MODULATE) { - tx.commit(); - } - return extracted; + public long extract(Context context, AEFluidKey what, long amount, Actionable mode) { + var stack = context.getStack(); + var copy = ItemHandlerHelper.copyStackWithSize(stack, 1); + var fluidHandler = copy.getCapability(Capabilities.FLUID_HANDLER_ITEM).orElse(null); + if (fluidHandler == null) { + return 0; + } + + int extracted = fluidHandler.drain(what.toStack(Ints.saturatedCast(amount)), mode.getFluidAction()).getAmount(); + if (mode == Actionable.MODULATE) { + stack.shrink(1); + context.addOverflow(fluidHandler.getContainer()); } + return extracted; } @Override - public long insert(Storage context, AEFluidKey what, long amount, Actionable mode) { - try (var tx = Transaction.openOuter()) { - var inserted = context.insert(what.toVariant(), amount, tx); - if (mode == Actionable.MODULATE) { - tx.commit(); - } - return inserted; + public long insert(Context context, AEFluidKey what, long amount, Actionable mode) { + var stack = context.getStack(); + var copy = ItemHandlerHelper.copyStackWithSize(stack, 1); + var fluidHandler = copy.getCapability(Capabilities.FLUID_HANDLER_ITEM).orElse(null); + if (fluidHandler == null) { + return 0; + } + + int filled = fluidHandler.fill(what.toStack(Ints.saturatedCast(amount)), mode.getFluidAction()); + if (mode == Actionable.MODULATE) { + stack.shrink(1); + context.addOverflow(fluidHandler.getContainer()); } + return filled; } @Override @@ -69,11 +85,51 @@ public void playEmptySound(Player player, AEFluidKey what) { } @Override - public @Nullable GenericStack getExtractableContent(Storage context) { - var resourceAmount = StorageUtil.findExtractableContent(context, null); - if (resourceAmount == null) { - return null; + public @Nullable GenericStack getExtractableContent(Context context) { + return getContainedStack(context.getStack()); + } + + interface Context { + ItemStack getStack(); + + void setStack(ItemStack stack); + + void addOverflow(ItemStack stack); + } + + private record CarriedContext(Player player, AbstractContainerMenu menu) implements Context { + @Override + public ItemStack getStack() { + return menu.getCarried(); + } + + @Override + public void setStack(ItemStack stack) { + menu.setCarried(stack); + } + + public void addOverflow(ItemStack stack) { + if (menu.getCarried().isEmpty()) { + menu.setCarried(stack); + } else { + player.getInventory().placeItemBackInInventory(stack); + } + } + } + + private record PlayerInvContext(Player player, int slot) implements Context { + @Override + public ItemStack getStack() { + return player.getInventory().getItem(slot); + } + + @Override + public void setStack(ItemStack stack) { + player.getInventory().setItem(slot, stack); + } + + public void addOverflow(ItemStack stack) { + player.getInventory().placeItemBackInInventory(stack); } - return new GenericStack(AEFluidKey.of(resourceAmount.resource()), resourceAmount.amount()); } } diff --git a/src/main/java/appeng/api/behaviors/GenericInternalInventory.java b/src/main/java/appeng/api/behaviors/GenericInternalInventory.java index 7d8eed9f3b2..0fba3f91694 100644 --- a/src/main/java/appeng/api/behaviors/GenericInternalInventory.java +++ b/src/main/java/appeng/api/behaviors/GenericInternalInventory.java @@ -3,13 +3,7 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; -import net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup; -import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext; -import net.minecraft.core.Direction; -import net.minecraft.resources.ResourceLocation; - import appeng.api.config.Actionable; -import appeng.api.ids.AEConstants; import appeng.api.stacks.AEKey; import appeng.api.stacks.AEKeyType; import appeng.api.stacks.GenericStack; @@ -20,13 +14,6 @@ */ @ApiStatus.Experimental public interface GenericInternalInventory { - /** - * Use this lookup to access instances of generic inventories. Use a fallback to wrap them on-the-fly. - */ - BlockApiLookup SIDED = BlockApiLookup.get( - new ResourceLocation(AEConstants.MOD_ID, "genericinternalinventory"), GenericInternalInventory.class, - Direction.class); - /** * @return The number of slots in this inventory. Never changes. */ @@ -90,9 +77,4 @@ public interface GenericInternalInventory { * Send a change notification manually, for example because the automatic notification was suppressed. */ void onChange(); - - /** - * Fabric only: call this before modifying a slot as part of a transaction. - */ - void updateSnapshots(int slot, TransactionContext transaction); } diff --git a/src/main/java/appeng/api/client/AEKeyRenderHandler.java b/src/main/java/appeng/api/client/AEKeyRenderHandler.java index 6e832536e21..41a1c6d2b33 100644 --- a/src/main/java/appeng/api/client/AEKeyRenderHandler.java +++ b/src/main/java/appeng/api/client/AEKeyRenderHandler.java @@ -27,13 +27,13 @@ import com.mojang.blaze3d.vertex.PoseStack; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.network.chat.Component; import net.minecraft.world.level.Level; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.api.distmarker.OnlyIn; import appeng.api.stacks.AEKey; import appeng.util.Platform; @@ -41,7 +41,7 @@ /** * Client-side rendering of AE stacks. Must be registered in {@link AEKeyRendering} for each storage channel! */ -@Environment(EnvType.CLIENT) +@OnlyIn(Dist.CLIENT) public interface AEKeyRenderHandler { /** * Draw the stack, for example the item or the fluid sprite, but not the amount. diff --git a/src/main/java/appeng/api/client/AEKeyRendering.java b/src/main/java/appeng/api/client/AEKeyRendering.java index 2e74794fc30..e03b2cbaa12 100644 --- a/src/main/java/appeng/api/client/AEKeyRendering.java +++ b/src/main/java/appeng/api/client/AEKeyRendering.java @@ -34,13 +34,13 @@ import org.jetbrains.annotations.Nullable; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.network.chat.Component; import net.minecraft.world.level.Level; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.api.distmarker.OnlyIn; import appeng.api.stacks.AEKey; import appeng.api.stacks.AEKeyType; @@ -49,7 +49,7 @@ * Registry for {@link AEKeyRenderHandler}. Also contains convenience functions to render a stack without having to * query the render handler first. */ -@Environment(EnvType.CLIENT) +@OnlyIn(Dist.CLIENT) public class AEKeyRendering { private static volatile Map> renderers = new IdentityHashMap<>(); diff --git a/src/main/java/appeng/api/config/Actionable.java b/src/main/java/appeng/api/config/Actionable.java index 509622fd8d7..316a0014fef 100644 --- a/src/main/java/appeng/api/config/Actionable.java +++ b/src/main/java/appeng/api/config/Actionable.java @@ -23,14 +23,41 @@ package appeng.api.config; +import net.neoforged.neoforge.fluids.capability.IFluidHandler.FluidAction; + public enum Actionable { /** * Perform the intended action. */ - MODULATE, + MODULATE(FluidAction.EXECUTE), /** * Pretend to perform the action. */ - SIMULATE; + SIMULATE(FluidAction.SIMULATE); + + private final FluidAction fluidAction; + + Actionable(FluidAction fluidAction) { + this.fluidAction = fluidAction; + } + + public static Actionable of(FluidAction action) { + return switch (action) { + case EXECUTE -> MODULATE; + case SIMULATE -> SIMULATE; + }; + } + + public static Actionable ofSimulate(boolean simulate) { + return simulate ? SIMULATE : MODULATE; + } + + public FluidAction getFluidAction() { + return fluidAction; + } + + public boolean isSimulate() { + return this == SIMULATE; + } } diff --git a/src/main/java/appeng/api/config/PowerUnits.java b/src/main/java/appeng/api/config/PowerUnits.java index ebbf0e27c0e..7db907221ea 100644 --- a/src/main/java/appeng/api/config/PowerUnits.java +++ b/src/main/java/appeng/api/config/PowerUnits.java @@ -27,7 +27,7 @@ public enum PowerUnits { AE("gui.ae2.units.appliedenergistics", "AE"), // Native Units - AE Energy - TR("gui.ae2.units.tr", "E"); // TR - TechReborn energy + RF("gui.ae2.units.rf", "RF"); // RF - Redstone Flux /** * unlocalized name for the power unit. diff --git a/src/main/java/appeng/api/crafting/PatternDetailsHelper.java b/src/main/java/appeng/api/crafting/PatternDetailsHelper.java index 657fe11b5f6..efc29a01650 100644 --- a/src/main/java/appeng/api/crafting/PatternDetailsHelper.java +++ b/src/main/java/appeng/api/crafting/PatternDetailsHelper.java @@ -33,6 +33,7 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.crafting.CraftingRecipe; +import net.minecraft.world.item.crafting.RecipeHolder; import net.minecraft.world.item.crafting.SmithingRecipe; import net.minecraft.world.item.crafting.StonecutterRecipe; import net.minecraft.world.level.Level; @@ -115,7 +116,7 @@ public static ItemStack encodeProcessingPattern(GenericStack[] in, GenericStack[ * @param allowFluidSubstitutes Controls whether the ME system will allow the use of equivalent fluids. * @throws IllegalArgumentException If either in or out contain only empty ItemStacks. */ - public static ItemStack encodeCraftingPattern(CraftingRecipe recipe, ItemStack[] in, + public static ItemStack encodeCraftingPattern(RecipeHolder recipe, ItemStack[] in, ItemStack out, boolean allowSubstitutes, boolean allowFluidSubstitutes) { return AEItems.CRAFTING_PATTERN.asItem().encode(recipe, in, out, allowSubstitutes, allowFluidSubstitutes); } @@ -131,7 +132,8 @@ public static ItemStack encodeCraftingPattern(CraftingRecipe recipe, ItemStack[] * @param allowSubstitutes Controls whether the ME system will allow the use of equivalent items to craft this * recipe. */ - public static ItemStack encodeStonecuttingPattern(StonecutterRecipe recipe, AEItemKey in, AEItemKey out, + public static ItemStack encodeStonecuttingPattern(RecipeHolder recipe, AEItemKey in, + AEItemKey out, boolean allowSubstitutes) { Preconditions.checkNotNull(recipe, "recipe"); Preconditions.checkNotNull(in, "in"); @@ -153,7 +155,7 @@ public static ItemStack encodeStonecuttingPattern(StonecutterRecipe recipe, AEIt * @param allowSubstitutes Controls whether the ME system will allow the use of equivalent items to craft this * recipe. */ - public static ItemStack encodeSmithingTablePattern(SmithingRecipe recipe, + public static ItemStack encodeSmithingTablePattern(RecipeHolder recipe, AEItemKey template, AEItemKey base, AEItemKey addition, diff --git a/src/main/java/appeng/api/events/ArrayBackedEvent.java b/src/main/java/appeng/api/events/ArrayBackedEvent.java new file mode 100644 index 00000000000..d2ced7360b4 --- /dev/null +++ b/src/main/java/appeng/api/events/ArrayBackedEvent.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package appeng.api.events; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.minecraft.resources.ResourceLocation; + +class ArrayBackedEvent extends Event { + static final Logger LOGGER = LoggerFactory.getLogger("fabric-api-base"); + + private final Function invokerFactory; + private final Object lock = new Object(); + private T[] handlers; + /** + * Registered event phases. + */ + private final Map> phases = new LinkedHashMap<>(); + /** + * Phases sorted in the correct dependency order. + */ + private final List> sortedPhases = new ArrayList<>(); + + @SuppressWarnings("unchecked") + ArrayBackedEvent(Class type, Function invokerFactory) { + this.invokerFactory = invokerFactory; + this.handlers = (T[]) Array.newInstance(type, 0); + update(); + } + + void update() { + this.invoker = invokerFactory.apply(handlers); + } + + @Override + public void register(T listener) { + register(DEFAULT_PHASE, listener); + } + + @Override + public void register(ResourceLocation phaseIdentifier, T listener) { + Objects.requireNonNull(phaseIdentifier, "Tried to register a listener for a null phase!"); + Objects.requireNonNull(listener, "Tried to register a null listener!"); + + synchronized (lock) { + getOrCreatePhase(phaseIdentifier, true).addListener(listener); + rebuildInvoker(handlers.length + 1); + } + } + + private EventPhaseData getOrCreatePhase(ResourceLocation id, boolean sortIfCreate) { + EventPhaseData phase = phases.get(id); + + if (phase == null) { + phase = new EventPhaseData<>(id, handlers.getClass().getComponentType()); + phases.put(id, phase); + sortedPhases.add(phase); + + if (sortIfCreate) { + PhaseSorting.sortPhases(sortedPhases); + } + } + + return phase; + } + + private void rebuildInvoker(int newLength) { + // Rebuild handlers. + if (sortedPhases.size() == 1) { + // Special case with a single phase: use the array of the phase directly. + handlers = sortedPhases.get(0).listeners; + } else { + @SuppressWarnings("unchecked") + T[] newHandlers = (T[]) Array.newInstance(handlers.getClass().getComponentType(), newLength); + int newHandlersIndex = 0; + + for (EventPhaseData existingPhase : sortedPhases) { + int length = existingPhase.listeners.length; + System.arraycopy(existingPhase.listeners, 0, newHandlers, newHandlersIndex, length); + newHandlersIndex += length; + } + + handlers = newHandlers; + } + + // Rebuild invoker. + update(); + } + + @Override + public void addPhaseOrdering(ResourceLocation firstPhase, ResourceLocation secondPhase) { + Objects.requireNonNull(firstPhase, "Tried to add an ordering for a null phase."); + Objects.requireNonNull(secondPhase, "Tried to add an ordering for a null phase."); + if (firstPhase.equals(secondPhase)) + throw new IllegalArgumentException("Tried to add a phase that depends on itself."); + + synchronized (lock) { + EventPhaseData first = getOrCreatePhase(firstPhase, false); + EventPhaseData second = getOrCreatePhase(secondPhase, false); + first.subsequentPhases.add(second); + second.previousPhases.add(first); + PhaseSorting.sortPhases(this.sortedPhases); + rebuildInvoker(handlers.length); + } + } +} diff --git a/src/main/java/appeng/api/events/AutoInvokingEvent.java b/src/main/java/appeng/api/events/AutoInvokingEvent.java new file mode 100644 index 00000000000..7f7d5bc144b --- /dev/null +++ b/src/main/java/appeng/api/events/AutoInvokingEvent.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package appeng.api.events; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates that this {@link Event} is auto-invoking: it calls the event callback implemented by a context parameter + * type automatically and without registration. + * + *

+ * This means that this event can be listened to in two ways: + *

+ * + *

+ * Do note that there may be more than one context parameter. + * + *

+ * A typical use case is feature augmentation, for example to expose raw clicks to slots. The event callback has a slot + * parameter - the context parameter - and the event itself is carrying this annotation. All the slot needs to receive + * slot clicks is to implement {@code SlotClickCallback} on itself. It shouldn't do any explicit event registration like + * {@code SLOT_CLICK_EVENT.register(this::onSlotClick)}, otherwise it will see extraneous callback invocations. + * + *

+ * In general, an auto-invoking event bridges the gap between the flexibility of an event with global reach, and the + * convenience of implementing an interface that gets detected automatically. + * + *

+ * This is a documentation-only annotation, the event factory has to implement the functionality explicitly by checking + * the parameter type and invoking it. On top of adding this annotation, the event field or method should document which + * parameters are context parameters, and under which circumstances they are invoked. + */ +// TODO: explore enforcing that auto-invoked listeners don't register themselves. +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.FIELD, ElementType.METHOD }) +public @interface AutoInvokingEvent { +} diff --git a/src/main/java/appeng/api/events/Event.java b/src/main/java/appeng/api/events/Event.java new file mode 100644 index 00000000000..63c5cf7e69b --- /dev/null +++ b/src/main/java/appeng/api/events/Event.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package appeng.api.events; + +import org.jetbrains.annotations.ApiStatus; + +import net.minecraft.resources.ResourceLocation; + +/** + * Base class for Fabric's event implementations. + * + * @param The listener type. + * @see EventFactory + */ +@ApiStatus.NonExtendable // Should only be extended by fabric API. +public abstract class Event { + /** + * The invoker field. This should be updated by the implementation to always refer to an instance containing all + * code that should be executed upon event emission. + */ + protected volatile T invoker; + + /** + * Returns the invoker instance. + * + *

+ * An "invoker" is an object which hides multiple registered listeners of type T under one instance of type T, + * executing them and leaving early as necessary. + * + * @return The invoker instance. + */ + public final T invoker() { + return invoker; + } + + /** + * Register a listener to the event, in the default phase. Have a look at {@link #addPhaseOrdering} for an + * explanation of event phases. + * + * @param listener The desired listener. + */ + public abstract void register(T listener); + + /** + * The identifier of the default phase. Have a look at {@link EventFactory#createWithPhases} for an explanation of + * event phases. + */ + public static final ResourceLocation DEFAULT_PHASE = new ResourceLocation("fabric", "default"); + + /** + * Register a listener to the event for the specified phase. Have a look at {@link EventFactory#createWithPhases} + * for an explanation of event phases. + * + * @param phase Identifier of the phase this listener should be registered for. It will be created if it didn't + * exist yet. + * @param listener The desired listener. + */ + public void register(ResourceLocation phase, T listener) { + // This is done to keep compatibility with existing Event subclasses, but they should really not be subclassing + // Event. + register(listener); + } + + /** + * Request that listeners registered for one phase be executed before listeners registered for another phase. + * Relying on the default phases supplied to {@link EventFactory#createWithPhases} should be preferred over manually + * registering phase ordering dependencies. + * + *

+ * Incompatible ordering constraints such as cycles will lead to inconsistent behavior: some constraints will be + * respected and some will be ignored. If this happens, a warning will be logged. + * + * @param firstPhase The identifier of the phase that should run before the other. It will be created if it didn't + * exist yet. + * @param secondPhase The identifier of the phase that should run after the other. It will be created if it didn't + * exist yet. + */ + public void addPhaseOrdering(ResourceLocation firstPhase, ResourceLocation secondPhase) { + // This is not abstract to avoid breaking existing Event subclasses, but they should really not be subclassing + // Event. + } +} diff --git a/src/main/java/appeng/api/events/EventFactory.java b/src/main/java/appeng/api/events/EventFactory.java new file mode 100644 index 00000000000..385215bbca1 --- /dev/null +++ b/src/main/java/appeng/api/events/EventFactory.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package appeng.api.events; + +import java.util.function.Function; + +import net.minecraft.resources.ResourceLocation; + +/** + * Helper for creating {@link Event} classes. + */ +public final class EventFactory { + private EventFactory() { + } + + /** + * Create an "array-backed" Event instance. + * + *

+ * If your factory simply delegates to the listeners without adding custom behavior, consider using + * {@linkplain #createArrayBacked(Class, Object, Function) the other overload} if performance of this event is + * critical. + * + * @param type The listener class type. + * @param invokerFactory The invoker factory, combining multiple listeners into one instance. + * @param The listener type. + * @return The Event instance. + */ + public static Event createArrayBacked(Class type, Function invokerFactory) { + return EventFactoryImpl.createArrayBacked(type, invokerFactory); + } + + /** + * Create an "array-backed" Event instance with a custom empty invoker, for an event whose {@code invokerFactory} + * only delegates to the listeners. + *

+ * + *

+ * Having a custom empty invoker (of type (...) -> {}) increases performance relative to iterating over an empty + * array; however, it only really matters if the event is executed thousands of times a second. + * + * @param type The listener class type. + * @param emptyInvoker The custom empty invoker. + * @param invokerFactory The invoker factory, combining multiple listeners into one instance. + * @param The listener type. + * @return The Event instance. + */ + public static Event createArrayBacked(Class type, T emptyInvoker, Function invokerFactory) { + return createArrayBacked(type, listeners -> { + if (listeners.length == 0) { + return emptyInvoker; + } else if (listeners.length == 1) { + return listeners[0]; + } else { + return invokerFactory.apply(listeners); + } + }); + } + + /** + * Create an array-backed event with a list of default phases that get invoked in order. Exposing the identifiers of + * the default phases as {@code public static final} constants is encouraged. + * + *

+ * An event phase is a named group of listeners, which may be ordered before or after other groups of listeners. + * This allows some listeners to take priority over other listeners. Adding separate events should be considered + * before making use of multiple event phases. + * + *

+ * Phases may be freely added to events created with any of the factory functions, however using this function is + * preferred for widely used event phases. If more phases are necessary, discussion with the author of the Event is + * encouraged. + * + *

+ * Refer to {@link Event#addPhaseOrdering} for an explanation of event phases. + * + * @param type The listener class type. + * @param invokerFactory The invoker factory, combining multiple listeners into one instance. + * @param defaultPhases The default phases of this event, in the correct order. Must contain + * {@link Event#DEFAULT_PHASE}. + * @param The listener type. + * @return The Event instance. + */ + public static Event createWithPhases(Class type, Function invokerFactory, + ResourceLocation... defaultPhases) { + EventFactoryImpl.ensureContainsDefault(defaultPhases); + EventFactoryImpl.ensureNoDuplicates(defaultPhases); + + Event event = createArrayBacked(type, invokerFactory); + + for (int i = 1; i < defaultPhases.length; ++i) { + event.addPhaseOrdering(defaultPhases[i - 1], defaultPhases[i]); + } + + return event; + } + + /** + * @deprecated This is not to be used in events anymore. + */ + @Deprecated + public static String getHandlerName(Object handler) { + return handler.getClass().getName(); + } + + /** + * @deprecated Always returns {@code false}, do not use. This is not to be used in events anymore, standard Java + * profilers will do fine. + */ + @Deprecated + public static boolean isProfilingEnabled() { + return false; + } + + /** + * Invalidate and re-create all existing "invoker" instances across events created by this EventFactory. Use this + * if, for instance, the profilingEnabled field changes. + * + * @deprecated Do not use, will be removed in a future release. + */ + @Deprecated(forRemoval = true) + public static void invalidate() { + EventFactoryImpl.invalidate(); + } +} diff --git a/src/main/java/appeng/api/events/EventFactoryImpl.java b/src/main/java/appeng/api/events/EventFactoryImpl.java new file mode 100644 index 00000000000..1c111c61636 --- /dev/null +++ b/src/main/java/appeng/api/events/EventFactoryImpl.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package appeng.api.events; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Array; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Proxy; +import java.util.Collections; +import java.util.Set; +import java.util.function.Function; + +import com.google.common.collect.MapMaker; + +import net.minecraft.resources.ResourceLocation; + +public final class EventFactoryImpl { + private static final Set> ARRAY_BACKED_EVENTS = Collections + .newSetFromMap(new MapMaker().weakKeys().makeMap()); + + private EventFactoryImpl() { + } + + public static void invalidate() { + ARRAY_BACKED_EVENTS.forEach(ArrayBackedEvent::update); + } + + public static Event createArrayBacked(Class type, Function invokerFactory) { + ArrayBackedEvent event = new ArrayBackedEvent<>(type, invokerFactory); + ARRAY_BACKED_EVENTS.add(event); + return event; + } + + public static void ensureContainsDefault(ResourceLocation[] defaultPhases) { + for (ResourceLocation id : defaultPhases) { + if (id.equals(Event.DEFAULT_PHASE)) { + return; + } + } + + throw new IllegalArgumentException("The event phases must contain Event.DEFAULT_PHASE."); + } + + public static void ensureNoDuplicates(ResourceLocation[] defaultPhases) { + for (int i = 0; i < defaultPhases.length; ++i) { + for (int j = i + 1; j < defaultPhases.length; ++j) { + if (defaultPhases[i].equals(defaultPhases[j])) { + throw new IllegalArgumentException("Duplicate event phase: " + defaultPhases[i]); + } + } + } + } + + // Code originally by sfPlayer1. + // Unfortunately, it's slightly slower than just passing an empty array in the first place. + private static T buildEmptyInvoker(Class handlerClass, Function invokerSetup) { + // find the functional interface method + Method funcIfMethod = null; + + for (Method m : handlerClass.getMethods()) { + if ((m.getModifiers() & (Modifier.STRICT | Modifier.PRIVATE)) == 0) { + if (funcIfMethod != null) { + throw new IllegalStateException( + "Multiple virtual methods in " + handlerClass + "; cannot build empty invoker!"); + } + + funcIfMethod = m; + } + } + + if (funcIfMethod == null) { + throw new IllegalStateException("No virtual methods in " + handlerClass + "; cannot build empty invoker!"); + } + + Object defValue = null; + + try { + // concert to mh, determine its type without the "this" reference + MethodHandle target = MethodHandles.lookup().unreflect(funcIfMethod); + MethodType type = target.type().dropParameterTypes(0, 1); + + if (type.returnType() != void.class) { + // determine default return value by invoking invokerSetup.apply(T[0]) with all-jvm-default args (null + // for refs, false for boolean, etc.) + // explicitCastArguments is being used to cast Object=null to the jvm default value for the correct type + + // construct method desc (TLjava/lang/Object;Ljava/lang/Object;...)R where T = invoker ref ("this"), R = + // invoker ret type and args 1+ are Object for each non-"this" invoker arg + MethodType objTargetType = MethodType.genericMethodType(type.parameterCount()) + .changeReturnType(type.returnType()).insertParameterTypes(0, target.type().parameterType(0)); + // explicit cast to translate to the invoker args from Object to their real type, inferring jvm default + // values + MethodHandle objTarget = MethodHandles.explicitCastArguments(target, objTargetType); + + // build invocation args with 0 = "this", 1+ = null + Object[] args = new Object[target.type().parameterCount()]; + // noinspection unchecked + args[0] = invokerSetup.apply((T[]) Array.newInstance(handlerClass, 0)); + + // retrieve default by invoking invokerSetup.apply(T[0]).targetName(def,def,...) + defValue = objTarget.invokeWithArguments(args); + } + } catch (Throwable t) { + throw new RuntimeException(t); + } + + final Object returnValue = defValue; + // noinspection unchecked + return (T) Proxy.newProxyInstance(EventFactoryImpl.class.getClassLoader(), new Class[] { handlerClass }, + (proxy, method, args) -> returnValue); + } +} diff --git a/src/main/java/appeng/api/events/EventPhaseData.java b/src/main/java/appeng/api/events/EventPhaseData.java new file mode 100644 index 00000000000..90385f4dfd3 --- /dev/null +++ b/src/main/java/appeng/api/events/EventPhaseData.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package appeng.api.events; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import net.minecraft.resources.ResourceLocation; + +/** + * Data of an {@link ArrayBackedEvent} phase. + */ +class EventPhaseData { + final ResourceLocation id; + T[] listeners; + final List> subsequentPhases = new ArrayList<>(); + final List> previousPhases = new ArrayList<>(); + int visitStatus = 0; // 0: not visited, 1: visiting, 2: visited + + @SuppressWarnings("unchecked") + EventPhaseData(ResourceLocation id, Class listenerClass) { + this.id = id; + this.listeners = (T[]) Array.newInstance(listenerClass, 0); + } + + void addListener(T listener) { + int oldLength = listeners.length; + listeners = Arrays.copyOf(listeners, oldLength + 1); + listeners[oldLength] = listener; + } +} diff --git a/src/main/java/appeng/api/events/LICENSE b/src/main/java/appeng/api/events/LICENSE new file mode 100644 index 00000000000..c4c1e635172 --- /dev/null +++ b/src/main/java/appeng/api/events/LICENSE @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ diff --git a/src/main/java/appeng/api/events/PhaseSorting.java b/src/main/java/appeng/api/events/PhaseSorting.java new file mode 100644 index 00000000000..ea7bdac233e --- /dev/null +++ b/src/main/java/appeng/api/events/PhaseSorting.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package appeng.api.events; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.PriorityQueue; + +import com.google.common.annotations.VisibleForTesting; + +/** + * Contains phase-sorting logic for {@link ArrayBackedEvent}. + */ +public class PhaseSorting { + @VisibleForTesting + public static boolean ENABLE_CYCLE_WARNING = true; + + /** + * Deterministically sort a list of phases. 1) Compute phase SCCs (i.e. cycles). 2) Sort phases by id within SCCs. + * 3) Sort SCCs with respect to each other by respecting constraints, and by id in case of a tie. + */ + static void sortPhases(List> sortedPhases) { + // FIRST KOSARAJU SCC VISIT + List> toposort = new ArrayList<>(sortedPhases.size()); + + for (EventPhaseData phase : sortedPhases) { + forwardVisit(phase, null, toposort); + } + + clearStatus(toposort); + Collections.reverse(toposort); + + // SECOND KOSARAJU SCC VISIT + Map, PhaseScc> phaseToScc = new IdentityHashMap<>(); + + for (EventPhaseData phase : toposort) { + if (phase.visitStatus == 0) { + List> sccPhases = new ArrayList<>(); + // Collect phases in SCC. + backwardVisit(phase, sccPhases); + // Sort phases by id. + sccPhases.sort(Comparator.comparing(p -> p.id)); + // Mark phases as belonging to this SCC. + PhaseScc scc = new PhaseScc<>(sccPhases); + + for (EventPhaseData phaseInScc : sccPhases) { + phaseToScc.put(phaseInScc, scc); + } + } + } + + clearStatus(toposort); + + // Build SCC graph + for (PhaseScc scc : phaseToScc.values()) { + for (EventPhaseData phase : scc.phases) { + for (EventPhaseData subsequentPhase : phase.subsequentPhases) { + PhaseScc subsequentScc = phaseToScc.get(subsequentPhase); + + if (subsequentScc != scc) { + scc.subsequentSccs.add(subsequentScc); + subsequentScc.inDegree++; + } + } + } + } + + // Order SCCs according to priorities. When there is a choice, use the SCC with the lowest id. + // The priority queue contains all SCCs that currently have 0 in-degree. + PriorityQueue> pq = new PriorityQueue<>(Comparator.comparing(scc -> scc.phases.get(0).id)); + sortedPhases.clear(); + + for (PhaseScc scc : phaseToScc.values()) { + if (scc.inDegree == 0) { + pq.add(scc); + // Prevent adding the same SCC multiple times, as phaseToScc may contain the same value multiple times. + scc.inDegree = -1; + } + } + + while (!pq.isEmpty()) { + PhaseScc scc = pq.poll(); + sortedPhases.addAll(scc.phases); + + for (PhaseScc subsequentScc : scc.subsequentSccs) { + subsequentScc.inDegree--; + + if (subsequentScc.inDegree == 0) { + pq.add(subsequentScc); + } + } + } + } + + private static void forwardVisit(EventPhaseData phase, EventPhaseData parent, + List> toposort) { + if (phase.visitStatus == 0) { + // Not yet visited. + phase.visitStatus = 1; + + for (EventPhaseData data : phase.subsequentPhases) { + forwardVisit(data, phase, toposort); + } + + toposort.add(phase); + phase.visitStatus = 2; + } else if (phase.visitStatus == 1 && ENABLE_CYCLE_WARNING) { + // Already visiting, so we have found a cycle. + ArrayBackedEvent.LOGGER.warn(String.format( + "Event phase ordering conflict detected.%nEvent phase %s is ordered both before and after event phase %s.", + phase.id, + parent.id)); + } + } + + private static void clearStatus(List> phases) { + for (EventPhaseData phase : phases) { + phase.visitStatus = 0; + } + } + + private static void backwardVisit(EventPhaseData phase, List> sccPhases) { + if (phase.visitStatus == 0) { + phase.visitStatus = 1; + sccPhases.add(phase); + + for (EventPhaseData data : phase.previousPhases) { + backwardVisit(data, sccPhases); + } + } + } + + private static class PhaseScc { + final List> phases; + final List> subsequentSccs = new ArrayList<>(); + int inDegree = 0; + + private PhaseScc(List> phases) { + this.phases = phases; + } + } +} diff --git a/src/main/java/appeng/api/features/AEToolActions.java b/src/main/java/appeng/api/features/AEToolActions.java new file mode 100644 index 00000000000..5249681d425 --- /dev/null +++ b/src/main/java/appeng/api/features/AEToolActions.java @@ -0,0 +1,51 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021 TeamAppliedEnergistics + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package appeng.api.features; + +import org.jetbrains.annotations.ApiStatus; + +import net.neoforged.neoforge.common.ToolAction; + +/** + * Tool actions defined by AE. + * + * @deprecated Check for the standard {@code #forge:tools/wrench} tag instead. + */ +@ApiStatus.ScheduledForRemoval(inVersion = "1.19") +@Deprecated(forRemoval = true) +public final class AEToolActions { + private AEToolActions() { + } + + /** + * An action that is triggered by right-clicking a supported block or part, which will disassemble that block or + * part into its item form. + */ + public static final ToolAction WRENCH_DISASSEMBLE = ToolAction.get("wrench_disassemble"); + + /** + * An action that is triggered by shift-right-clicking a supported block or part, which will rotate that part. + */ + public static final ToolAction WRENCH_ROTATE = ToolAction.get("wrench_rotate"); +} diff --git a/src/main/java/appeng/api/features/P2PTunnelAttunement.java b/src/main/java/appeng/api/features/P2PTunnelAttunement.java index 8f59d381574..dc508fc48ee 100644 --- a/src/main/java/appeng/api/features/P2PTunnelAttunement.java +++ b/src/main/java/appeng/api/features/P2PTunnelAttunement.java @@ -28,11 +28,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.function.Function; -import net.fabricmc.fabric.api.lookup.v1.item.ItemApiLookup; -import net.fabricmc.fabric.api.transfer.v1.context.ContainerItemContext; -import net.fabricmc.fabric.api.transfer.v1.item.base.SingleStackStorage; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.Registries; import net.minecraft.network.chat.Component; @@ -41,6 +37,7 @@ import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.ItemLike; +import net.neoforged.neoforge.common.capabilities.Capability; import appeng.core.definitions.AEParts; import appeng.items.parts.PartItem; @@ -53,7 +50,7 @@ public final class P2PTunnelAttunement { private static final int INITIAL_CAPACITY = 40; static final Map, Item> tagTunnels = new IdentityHashMap<>(INITIAL_CAPACITY); - static final List> apiAttunements = new ArrayList<>(INITIAL_CAPACITY); + static final List apiAttunements = new ArrayList<>(INITIAL_CAPACITY); /** * The default tunnel part for ME tunnels. Use this to register additional attunement options. @@ -108,39 +105,15 @@ public synchronized static void registerAttunementTag(ItemLike tunnel) { } /** - * Attunement based on the ability of getting an API via Fabric API Lookup from the item. + * Attunement based on the ability of getting a capability from the item. * * @param tunnelPart The P2P-tunnel part item. * @param description Description for display in REI/JEI. */ - public synchronized static void registerAttunementApi(ItemLike tunnelPart, ItemApiLookup api, - Function contextProvider, Component description) { - Objects.requireNonNull(api, "api"); - Objects.requireNonNull(contextProvider, "contextProvider"); - apiAttunements.add(new ApiAttunement<>(api, contextProvider, validateTunnelPartItem(tunnelPart), description)); - } - - /** - * Attunement based on the ability of getting a storage container API via Fabric API Lookup from the item. - * - * @param tunnelPart The P2P-tunnel part item. - * @param description Description for display in REI/JEI. - */ - public synchronized static void registerAttunementApi(ItemLike tunnelPart, - ItemApiLookup api, Component description) { - registerAttunementApi(tunnelPart, api, stack -> ContainerItemContext.ofSingleSlot(new SingleStackStorage() { - ItemStack buffer = stack; - - @Override - protected ItemStack getStack() { - return buffer; - } - - @Override - protected void setStack(ItemStack stack) { - buffer = stack; - } - }), description); + public synchronized static void registerAttunementApi(ItemLike tunnelPart, Capability cap, + Component description) { + Objects.requireNonNull(cap, "cap"); + apiAttunements.add(new ApiAttunement(cap, validateTunnelPartItem(tunnelPart), description)); } /** @@ -186,13 +159,12 @@ private static Item validateTunnelPartItem(ItemLike itemLike) { return item; } - record ApiAttunement ( - ItemApiLookup api, - Function contextProvider, + record ApiAttunement( + Capability capability, Item tunnelType, Component component) { public boolean hasApi(ItemStack stack) { - return api.find(stack, contextProvider.apply(stack)) != null; + return stack.getCapability(capability).isPresent(); } } } diff --git a/src/main/java/appeng/api/features/P2PTunnelAttunementInternal.java b/src/main/java/appeng/api/features/P2PTunnelAttunementInternal.java index 86b3d73fac3..e566efa2c8c 100644 --- a/src/main/java/appeng/api/features/P2PTunnelAttunementInternal.java +++ b/src/main/java/appeng/api/features/P2PTunnelAttunementInternal.java @@ -24,12 +24,12 @@ import java.util.Set; import java.util.function.Predicate; -import net.fabricmc.fabric.api.lookup.v1.item.ItemApiLookup; import net.minecraft.network.chat.Component; import net.minecraft.tags.TagKey; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.ItemLike; +import net.neoforged.neoforge.common.capabilities.Capability; /** * Internal methods that complement {@link P2PTunnelAttunement} and which are not part of the public API. @@ -45,15 +45,15 @@ private P2PTunnelAttunementInternal() { public static AttunementInfo getAttunementInfo(ItemLike tunnelType) { var tunnelItem = tunnelType.asItem(); - Set> apis = new HashSet<>(); + Set> caps = new HashSet<>(); for (var entry : P2PTunnelAttunement.apiAttunements) { if (entry.tunnelType() == tunnelItem) { - apis.add(entry.api()); + caps.add(entry.capability()); } } - return new AttunementInfo(apis); + return new AttunementInfo(caps); } public static List getApiTunnels() { @@ -65,7 +65,7 @@ public static Map, Item> getTagTunnels() { return P2PTunnelAttunement.tagTunnels; } - public record AttunementInfo(Set> apis) { + public record AttunementInfo(Set> apis) { } public record Resultant(Component description, Item tunnelType, Predicate stackPredicate) { diff --git a/src/main/java/appeng/api/features/PlayerRegistryInternal.java b/src/main/java/appeng/api/features/PlayerRegistryInternal.java index 0f2fce767fd..fc375c63e7e 100644 --- a/src/main/java/appeng/api/features/PlayerRegistryInternal.java +++ b/src/main/java/appeng/api/features/PlayerRegistryInternal.java @@ -66,8 +66,10 @@ static PlayerRegistryInternal get(MinecraftServer server) { throw new IllegalStateException("Cannot retrieve player data for a server that has no overworld."); } return overworld.getDataStorage().computeIfAbsent( - nbt -> PlayerRegistryInternal.load(server, nbt), - () -> new PlayerRegistryInternal(server), + new Factory<>( + () -> new PlayerRegistryInternal(server), + nbt -> PlayerRegistryInternal.load(server, nbt), + null), PlayerRegistryInternal.NAME); } diff --git a/src/main/java/appeng/api/implementations/blockentities/ICraftingMachine.java b/src/main/java/appeng/api/implementations/blockentities/ICraftingMachine.java index 78de823d3f9..f5251f92517 100644 --- a/src/main/java/appeng/api/implementations/blockentities/ICraftingMachine.java +++ b/src/main/java/appeng/api/implementations/blockentities/ICraftingMachine.java @@ -25,16 +25,14 @@ import org.jetbrains.annotations.Nullable; -import net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; -import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.BlockEntity; import appeng.api.crafting.IPatternDetails; -import appeng.api.ids.AEConstants; import appeng.api.stacks.KeyCounter; +import appeng.capabilities.Capabilities; /** * Provides crafting services to adjacent pattern providers for automatic crafting. Can be provided via capability on @@ -42,9 +40,6 @@ */ public interface ICraftingMachine { - BlockApiLookup SIDED = BlockApiLookup.get( - new ResourceLocation(AEConstants.MOD_ID, "icraftingmachine"), ICraftingMachine.class, Direction.class); - @Nullable static ICraftingMachine of(@Nullable BlockEntity blockEntity, Direction side) { if (blockEntity == null) { @@ -56,8 +51,13 @@ static ICraftingMachine of(@Nullable BlockEntity blockEntity, Direction side) { @Nullable static ICraftingMachine of(Level level, BlockPos pos, Direction side, - @org.jetbrains.annotations.Nullable BlockEntity blockEntity) { - return SIDED.find(level, pos, null, blockEntity, side); + @Nullable BlockEntity blockEntity) { + if (blockEntity != null) { + return blockEntity.getCapability(Capabilities.CRAFTING_MACHINE, side).orElse(null); + } else { + return null; + } + } /** diff --git a/src/main/java/appeng/api/implementations/blockentities/ICrankable.java b/src/main/java/appeng/api/implementations/blockentities/ICrankable.java index e888b768dd4..28c3de7245a 100644 --- a/src/main/java/appeng/api/implementations/blockentities/ICrankable.java +++ b/src/main/java/appeng/api/implementations/blockentities/ICrankable.java @@ -25,12 +25,11 @@ import org.jetbrains.annotations.Nullable; -import net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.world.level.Level; -import appeng.core.AppEng; +import appeng.capabilities.Capabilities; /** * Crank/Crankable API, @@ -38,12 +37,9 @@ * Blocks that expose this interface via Api lookup can receive power from the crank. A block can return this interface * only on specific sides to control where it can attach to. *

- * Cranks obtain this interface from a block using {@link #LOOKUP}. + * Cranks obtain this interface from a block using a Forge capability. */ public interface ICrankable { - BlockApiLookup LOOKUP = BlockApiLookup.get(AppEng.makeId("crankable"), - ICrankable.class, Direction.class); - /** * Test if the crank can turn, return false if there is no work to be done. * @@ -58,6 +54,10 @@ public interface ICrankable { @Nullable static ICrankable get(Level level, BlockPos pos, Direction side) { - return LOOKUP.find(level, pos, null, null, side); + var be = level.getExistingBlockEntity(pos); + if (be != null) { + return be.getCapability(Capabilities.CRANKABLE, side).orElse(null); + } + return null; } } diff --git a/src/main/java/appeng/api/inventories/BaseInternalInventory.java b/src/main/java/appeng/api/inventories/BaseInternalInventory.java index 63f3fe57d7e..50ebb39236c 100644 --- a/src/main/java/appeng/api/inventories/BaseInternalInventory.java +++ b/src/main/java/appeng/api/inventories/BaseInternalInventory.java @@ -23,8 +23,7 @@ package appeng.api.inventories; -import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant; -import net.fabricmc.fabric.api.transfer.v1.storage.Storage; +import net.neoforged.neoforge.items.IItemHandler; /** * Implementation aid for {@link InternalInventory} that ensures the platorm adapter maintains its referential equality @@ -32,18 +31,17 @@ */ public abstract class BaseInternalInventory implements InternalInventory { - private Storage platformWrapper; + private IItemHandler platformWrapper; @Override - public final Storage toStorage() { + public final IItemHandler toItemHandler() { if (platformWrapper == null) { - platformWrapper = createStorage(); + // Porting note: On Fabric we need to maintain the specialized storage used by + // sub-inventories in case of combined internal inventories due to transactions. + // This is not needed on Forge. + platformWrapper = new InternalInventoryItemHandler(this); } return platformWrapper; } - protected Storage createStorage() { - return new InternalInventoryStorage(this); - } - } diff --git a/src/main/java/appeng/api/inventories/EmptyInternalInventory.java b/src/main/java/appeng/api/inventories/EmptyInternalInventory.java index 68436e46a74..4b6cd610a06 100644 --- a/src/main/java/appeng/api/inventories/EmptyInternalInventory.java +++ b/src/main/java/appeng/api/inventories/EmptyInternalInventory.java @@ -26,9 +26,9 @@ import java.util.Collections; import java.util.Iterator; -import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant; -import net.fabricmc.fabric.api.transfer.v1.storage.Storage; import net.minecraft.world.item.ItemStack; +import net.neoforged.neoforge.items.IItemHandler; +import net.neoforged.neoforge.items.wrapper.EmptyHandler; class EmptyInternalInventory implements InternalInventory { static final EmptyInternalInventory INSTANCE = new EmptyInternalInventory(); @@ -42,8 +42,8 @@ public boolean isEmpty() { } @Override - public Storage toStorage() { - return Storage.empty(); + public IItemHandler toItemHandler() { + return EmptyHandler.INSTANCE; } @Override diff --git a/src/main/java/appeng/api/inventories/IDynamicPartBakedModel.java b/src/main/java/appeng/api/inventories/IDynamicPartBakedModel.java deleted file mode 100644 index e0e783c2ff8..00000000000 --- a/src/main/java/appeng/api/inventories/IDynamicPartBakedModel.java +++ /dev/null @@ -1,46 +0,0 @@ -package appeng.api.inventories; - -import java.util.Collections; -import java.util.List; -import java.util.function.Supplier; - -import org.jetbrains.annotations.Nullable; - -import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; -import net.minecraft.client.renderer.block.model.BakedQuad; -import net.minecraft.client.resources.model.BakedModel; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.util.RandomSource; -import net.minecraft.world.level.BlockAndTintGetter; -import net.minecraft.world.level.block.state.BlockState; - -import appeng.api.parts.IPart; - -/** - * This interface can be implemented by baked models returned by {@link IPart#getStaticModels()} to indicate that they - * would like to use the model data returned by {@link IPart#getRenderAttachmentData()}. - */ -public interface IDynamicPartBakedModel extends BakedModel { - - /** - * See {@link net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel#emitBlockQuads} for context. - *

- * The given context will already have been transformed so that the model renders from the rotated - * location the part is attached to. - * - * @param partSide The side of the cable bus that the part is attached to. - * @param modelData The model data returned by {@link IPart#getRenderAttachmentData()} - */ - void emitQuads(BlockAndTintGetter blockView, BlockState state, BlockPos pos, Supplier randomSupplier, - RenderContext context, Direction partSide, @Nullable Object modelData); - - /** - * Unless you use your dynamic model for other purposes, this method will not be called. - */ - @Override - default List getQuads(@Nullable BlockState state, @Nullable Direction face, RandomSource random) { - return Collections.emptyList(); - } - -} diff --git a/src/main/java/appeng/api/inventories/InternalInventory.java b/src/main/java/appeng/api/inventories/InternalInventory.java index 7a790ce298c..6abc93500cf 100644 --- a/src/main/java/appeng/api/inventories/InternalInventory.java +++ b/src/main/java/appeng/api/inventories/InternalInventory.java @@ -31,9 +31,6 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; -import net.fabricmc.fabric.api.transfer.v1.item.ItemStorage; -import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant; -import net.fabricmc.fabric.api.transfer.v1.storage.Storage; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.world.Container; @@ -41,6 +38,8 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.BlockEntity; +import net.neoforged.neoforge.common.capabilities.Capabilities; +import net.neoforged.neoforge.items.IItemHandler; import appeng.api.config.FuzzyMode; import appeng.util.helpers.ItemComparisonHelper; @@ -53,12 +52,9 @@ static ItemTransfer wrapExternal(@Nullable BlockEntity be, Direction side) { return null; } - var storage = ItemStorage.SIDED.find(be.getLevel(), be.getBlockPos(), be.getBlockState(), be, side); - if (storage != null) { - return new PlatformInventoryWrapper(storage); - } - - return null; + return be.getCapability(Capabilities.ITEM_HANDLER, side) + .map(PlatformInventoryWrapper::new) + .orElse(null); } @Nullable @@ -80,8 +76,8 @@ default boolean isEmpty() { return !iterator().hasNext(); } - default Storage toStorage() { - return new InternalInventoryStorage(this); + default IItemHandler toItemHandler() { + return new InternalInventoryItemHandler(this); } default Container toContainer() { diff --git a/src/main/java/appeng/api/inventories/InternalInventoryItemHandler.java b/src/main/java/appeng/api/inventories/InternalInventoryItemHandler.java index e69de29bb2d..9c47d014fc9 100644 --- a/src/main/java/appeng/api/inventories/InternalInventoryItemHandler.java +++ b/src/main/java/appeng/api/inventories/InternalInventoryItemHandler.java @@ -0,0 +1,77 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021 TeamAppliedEnergistics + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package appeng.api.inventories; + +import net.minecraft.world.item.ItemStack; +import net.neoforged.neoforge.items.IItemHandlerModifiable; + +import appeng.api.stacks.GenericStack; + +class InternalInventoryItemHandler implements IItemHandlerModifiable { + private final InternalInventory inventory; + + public InternalInventoryItemHandler(InternalInventory inventory) { + this.inventory = inventory; + } + + @Override + public int getSlots() { + return inventory.size(); + } + + @Override + public ItemStack getStackInSlot(int slot) { + return inventory.getStackInSlot(slot); + } + + @Override + public void setStackInSlot(int slot, ItemStack stack) { + inventory.setItemDirect(slot, stack); + } + + @Override + public ItemStack insertItem(int slot, ItemStack stack, boolean simulate) { + return inventory.insertItem(slot, stack, simulate); + } + + @Override + public ItemStack extractItem(int slot, int amount, boolean simulate) { + // Do not allow extraction of wrapped stacks because they're an internal detail + if (GenericStack.isWrapped(inventory.getStackInSlot(slot))) { + return ItemStack.EMPTY; + } + + return inventory.extractItem(slot, amount, simulate); + } + + @Override + public int getSlotLimit(int slot) { + return inventory.getSlotLimit(slot); + } + + @Override + public boolean isItemValid(int slot, ItemStack stack) { + return inventory.isItemValid(slot, stack); + } +} diff --git a/src/main/java/appeng/api/inventories/InternalInventoryStorage.java b/src/main/java/appeng/api/inventories/InternalInventoryStorage.java deleted file mode 100644 index 1501e65eba1..00000000000 --- a/src/main/java/appeng/api/inventories/InternalInventoryStorage.java +++ /dev/null @@ -1,180 +0,0 @@ -package appeng.api.inventories; - -import java.util.Iterator; -import java.util.NoSuchElementException; - -import com.google.common.base.Preconditions; - -import org.jetbrains.annotations.Nullable; - -import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant; -import net.fabricmc.fabric.api.transfer.v1.storage.Storage; -import net.fabricmc.fabric.api.transfer.v1.storage.StoragePreconditions; -import net.fabricmc.fabric.api.transfer.v1.storage.StorageView; -import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext; -import net.fabricmc.fabric.api.transfer.v1.transaction.base.SnapshotParticipant; -import net.minecraft.world.item.ItemStack; - -import appeng.core.definitions.AEItems; - -class InternalInventoryStorage extends SnapshotParticipant - implements Storage { - private final InternalInventory inventory; - @Nullable - private Snapshot lastReleasedSnapshot; - - public InternalInventoryStorage(InternalInventory inventory) { - this.inventory = inventory; - } - - @Override - public long insert(ItemVariant resource, long maxAmount, TransactionContext transaction) { - StoragePreconditions.notBlankNotNegative(resource, maxAmount); - - var stack = resource.toStack((int) Math.min(Integer.MAX_VALUE, maxAmount)); - - updateSnapshots(transaction); - - var overflow = inventory.addItems(stack); - return maxAmount - overflow.getCount(); - } - - @Override - public long extract(ItemVariant resource, long maxAmount, TransactionContext transaction) { - StoragePreconditions.notBlankNotNegative(resource, maxAmount); - - // Do not allow extraction of wrapped fluid stacks because they're an internal detail - if (resource.getItem() == AEItems.WRAPPED_GENERIC_STACK.asItem()) { - return 0; - } - - updateSnapshots(transaction); - - var amt = (int) Math.min(Integer.MAX_VALUE, maxAmount); - ItemStack extracted = inventory.removeItems(amt, resource.toStack(), null); - - return extracted.getCount(); - } - - @Override - public Iterator> iterator() { - return new InventoryIterator(); - } - - private class InventoryIterator implements Iterator> { - private int currentSlot = -1; - - @Override - public boolean hasNext() { - return currentSlot + 1 < inventory.size(); - } - - @Override - public StorageView next() { - if (!hasNext()) { - throw new NoSuchElementException(); - } - - currentSlot++; - int slot = currentSlot; - - return new StorageView<>() { - @Override - public long extract(ItemVariant resource, long maxAmount, TransactionContext transaction) { - StoragePreconditions.notBlankNotNegative(resource, maxAmount); - - // Do not allow extraction of wrapped fluid stacks because they're an internal detail - if (resource.getItem() == AEItems.WRAPPED_GENERIC_STACK.asItem()) { - return 0; - } - - updateSnapshots(transaction); - - return inventory.extractItem(slot, (int) Math.min(Integer.MAX_VALUE, maxAmount), false).getCount(); - } - - @Override - public boolean isResourceBlank() { - return inventory.getStackInSlot(slot).isEmpty(); - } - - @Override - public ItemVariant getResource() { - return ItemVariant.of(inventory.getStackInSlot(slot)); - } - - @Override - public long getAmount() { - return inventory.getStackInSlot(slot).getCount(); - } - - @Override - public long getCapacity() { - return inventory.getSlotLimit(slot); - } - }; - } - } - - @Override - protected Snapshot createSnapshot() { - Snapshot snapshot; - if (this.lastReleasedSnapshot != null && this.lastReleasedSnapshot.items.length == inventory.size()) { - snapshot = this.lastReleasedSnapshot; - this.lastReleasedSnapshot = null; - } else { - snapshot = new Snapshot(); - } - - for (int i = 0; i < inventory.size(); i++) { - var stack = inventory.getStackInSlot(i); - snapshot.items[i] = stack; - snapshot.counts[i] = stack.getCount(); - } - return snapshot; - } - - @Override - protected void readSnapshot(Snapshot snapshot) { - var items = snapshot.items; - var counts = snapshot.counts; - for (int i = 0; i < items.length; i++) { - var stack = items[i]; - // Restore the previous count as well, the inventory might mutate the stack count for extract/insert - // We do not restore NBT since the Storage API does not give access to the original NBT and the inventory - // doesn't mutate it itself - if (stack.getCount() != counts[i]) { - stack.setCount(counts[i]); - } - inventory.setItemDirect(i, stack); - } - } - - @Override - protected void releaseSnapshot(Snapshot snapshot) { - this.lastReleasedSnapshot = snapshot; - } - - public class Snapshot { - ItemStack[] items; - int[] counts; - - public Snapshot() { - this.items = new ItemStack[inventory.size()]; - this.counts = new int[inventory.size()]; - } - } - - @Override - protected void onFinalCommit() { - // Diff the last snapshot against the inventory to collect change notifications - Preconditions.checkState(lastReleasedSnapshot != null, "There should have been at least one snapshot"); - - for (int i = 0; i < lastReleasedSnapshot.items.length; i++) { - var current = inventory.getStackInSlot(i); - if (current != lastReleasedSnapshot.items[i] || current.getCount() != lastReleasedSnapshot.counts[i]) { - inventory.sendChangeNotification(i); - } - } - } -} diff --git a/src/main/java/appeng/api/inventories/PartApiLookup.java b/src/main/java/appeng/api/inventories/PartApiLookup.java deleted file mode 100644 index 97f120b9581..00000000000 --- a/src/main/java/appeng/api/inventories/PartApiLookup.java +++ /dev/null @@ -1,170 +0,0 @@ -package appeng.api.inventories; - -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Function; - -import org.jetbrains.annotations.Nullable; - -import net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup; -import net.fabricmc.fabric.api.lookup.v1.custom.ApiProviderMap; -import net.minecraft.core.Direction; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.block.entity.BlockEntityType; - -import appeng.api.parts.IPart; -import appeng.api.parts.IPartHost; - -/** - * Exposes {@linkplain BlockApiLookup Block APIs} for parts. This allows parts to answer to API queries made via - * {@link BlockApiLookup#find} on AE2's multipart blocks. - */ -public final class PartApiLookup { - - private PartApiLookup() { - } - - // These two are used as identity hash maps - BlockApiLookup has identity semantics. - // We use ApiProviderMap because it ensures non-null keys and values. - private static final ApiProviderMap, Function> mappings = ApiProviderMap - .create(); - private static final ApiProviderMap, ApiProviderMap, PartApiProvider>> providers = ApiProviderMap - .create(); - private static final Set> cableBusRegisteredLookups = ConcurrentHashMap.newKeySet(); - private static final Set> hostTypes = ConcurrentHashMap.newKeySet(); - - /** - * Register a function mapping the context of the passed lookup to {@link Direction}. This is necessary when the - * context is not {@link Direction}. - *

- * The location is only used to resolve which part the query targets; the api provider will receive the original - * context. If the mapping function returns null, no API will be returned for the query. - *

- * If multiple mapping functions are registered for a given lookup, it is the first that will be used. - */ - public static void registerCustomContext(BlockApiLookup lookup, - Function mappingFunction) { - mappings.putIfAbsent(lookup, mappingFunction); - } - - /** - * Expose an API for a part class. - *

- * When looking for an API instance, providers are queried starting from the class of the part, and then moving up - * to its superclass, and so on, until a provider returning a nonnull API is found. - *

- * If the context of the lookup is not {@link Direction}, you need to register a mapping function for your custom - * context! That must be done before this function is called. Currently the query will fail silently, but IT WILL - * throw an exception in the future! - */ - @SuppressWarnings("ConstantConditions") - public static void register(BlockApiLookup lookup, PartApiProvider provider, - Class

partClass) { - Objects.requireNonNull(lookup, "Registered lookup may not be null."); - - if (partClass.isInterface()) { - throw new IllegalArgumentException( - "Part lookup cannot be registered for interface:" + partClass.getCanonicalName()); - } - - providers.putIfAbsent(lookup, ApiProviderMap.create()); - ApiProviderMap, PartApiProvider> toProviderMap = providers.get(lookup); - - if (toProviderMap.putIfAbsent(partClass, provider) != null) { - throw new IllegalArgumentException( - "Duplicate provider registration for part class " + partClass.getCanonicalName()); - } - - if (cableBusRegisteredLookups.add(lookup)) { - for (var hostType : hostTypes) { - registerLookup(hostType, lookup); - } - } - } - - private static void registerLookup(BlockEntityType hostType, - BlockApiLookup lookup) { - - lookup.registerForBlockEntities((be, context) -> { - @Nullable - Direction location = mapContext(lookup, context); - - if (location == null) { - return null; - } else { - var partHost = (IPartHost) be; - var part = partHost.getPart(location); - - if (part != null) { - return find(lookup, context, part); - } else { - return null; - } - } - }, hostType); - } - - /** - * Adds a new type of block entity that will participate in forwarding API lookups to its attached parts. - */ - public static void addHostType(BlockEntityType hostType) { - if (hostTypes.add(hostType)) { - for (var api : cableBusRegisteredLookups) { - registerLookup(hostType, api); - } - } - } - - @SuppressWarnings("unchecked") - @Nullable - public static Direction mapContext(BlockApiLookup lookup, C context) { - Function mapping = (Function) mappings.get(lookup); - - if (mapping != null) { - return mapping.apply(context); - } else if (context instanceof Direction direction) { - return direction; - } else { - return null; - } - } - - @SuppressWarnings("unchecked") - @Nullable - public static A find(BlockApiLookup lookup, C context, IPart part) { - ApiProviderMap, PartApiProvider> toProviderMap = providers.get(lookup); - - if (lookup == null) - return null; - - for (Class klass = part.getClass(); klass != Object.class; klass = klass.getSuperclass()) { - PartApiProvider provider = (PartApiProvider) toProviderMap.get(klass); - - if (provider != null) { - A instance = provider.find(part, context); - - if (instance != null) { - return instance; - } - } - } - - return null; - } - - @FunctionalInterface - public interface PartApiProvider { - /** - * Return an API of type {@code A} if available in the given part with the given context, or {@code null} - * otherwise. - * - * @param part The part. - * @param context Additional context passed to the query. - * @return An API of type {@code A}, or {@code null} if no API is available. - */ - @Nullable - A find(P part, C context); - } - -} diff --git a/src/main/java/appeng/api/inventories/PlatformInventoryWrapper.java b/src/main/java/appeng/api/inventories/PlatformInventoryWrapper.java index 76ab58f12d7..af7d98aeb61 100644 --- a/src/main/java/appeng/api/inventories/PlatformInventoryWrapper.java +++ b/src/main/java/appeng/api/inventories/PlatformInventoryWrapper.java @@ -23,183 +23,64 @@ package appeng.api.inventories; -import java.util.function.Predicate; - -import org.jetbrains.annotations.NotNull; - -import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant; -import net.fabricmc.fabric.api.transfer.v1.storage.Storage; -import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction; import net.minecraft.world.item.ItemStack; - -import appeng.api.config.FuzzyMode; -import appeng.util.Platform; -import appeng.util.helpers.ItemComparisonHelper; +import net.neoforged.neoforge.items.IItemHandler; +import net.neoforged.neoforge.items.IItemHandlerModifiable; /** * Wraps an inventory implementing the platforms standard inventory interface (i.e. IItemHandler on Forge) such that it * can be used as an {@link InternalInventory}. */ -class PlatformInventoryWrapper implements ItemTransfer { - private final Storage storage; +public class PlatformInventoryWrapper implements InternalInventory { + private final IItemHandler handler; - public PlatformInventoryWrapper(Storage storage) { - this.storage = storage; + public PlatformInventoryWrapper(IItemHandler handler) { + this.handler = handler; } @Override - public boolean mayAllowInsertion() { - return this.storage.supportsInsertion(); + public IItemHandler toItemHandler() { + return handler; } @Override - public ItemStack removeItems(int amount, ItemStack filter, Predicate destination) { - ItemStack result; - try (var tx = Platform.openOrJoinTx()) { - result = innerRemoveItems(amount, filter, destination, tx); - tx.commit(); - } - return result; + public int size() { + return handler.getSlots(); } @Override - public ItemStack simulateRemove(int amount, ItemStack filter, Predicate destination) { - ItemStack result; - try (var tx = Platform.openOrJoinTx()) { - result = innerRemoveItems(amount, filter, destination, tx); - } - return result; + public int getSlotLimit(int slot) { + return handler.getSlotLimit(slot); } - private ItemStack innerRemoveItems(int amount, ItemStack filter, Predicate destination, Transaction tx) { - ItemVariant rv = ItemVariant.blank(); - long extractedAmount = 0; - - var it = this.storage.iterator(); - while (it.hasNext() && extractedAmount < amount) { - var view = it.next(); - - var is = view.getResource(); - if (is.isBlank()) { - continue; - } - - // Haven't decided what to extract yet - if (rv.isBlank()) { - if (!filter.isEmpty() && !is.matches(filter)) { - continue; // Doesn't match ItemStack template - } - - if (destination != null && !destination.test(is.toStack())) { - continue; // Doesn't match filter - } - - long actualAmount = view.extract(is, amount - extractedAmount, tx); - if (actualAmount <= 0) { - continue; // Apparently not extractable - } - - rv = is; // we've decided what to extract - extractedAmount += actualAmount; - } else { - if (!rv.equals(is)) { - continue; // Once we've decided what to extract, we need to stick to it - } - - extractedAmount += view.extract(is, amount, tx); - } - } - - // If any of the slots returned more than what we requested, it'll be voided here - if (extractedAmount > amount) { - // TODO -// AELog.warn( -// "An inventory returned more (%d) than we requested (%d) during extraction. Excess will be voided.", -// extractedAmount, amount); - } - return rv.toStack((int) Math.min(amount, extractedAmount)); + @Override + public ItemStack getStackInSlot(int slotIndex) { + return handler.getStackInSlot(slotIndex); } - /** - * For fuzzy extract, we will only ever extract one slot, since we're afraid of merging two item stacks with - * different damage values. - */ @Override - public ItemStack removeSimilarItems(int amount, ItemStack filter, FuzzyMode fuzzyMode, - Predicate destination) { - ItemStack result; - try (var tx = Platform.openOrJoinTx()) { - result = innerRemoveSimilarItems(amount, filter, fuzzyMode, destination, tx); - tx.commit(); + public void setItemDirect(int slotIndex, ItemStack stack) { + if (handler instanceof IItemHandlerModifiable modifiableHandler) { + modifiableHandler.setStackInSlot(slotIndex, stack); + } else { + handler.extractItem(slotIndex, Integer.MAX_VALUE, false); + handler.insertItem(slotIndex, stack, false); } - return result; } @Override - public ItemStack simulateSimilarRemove(int amount, ItemStack filter, FuzzyMode fuzzyMode, - Predicate destination) { - ItemStack result; - try (var tx = Platform.openOrJoinTx()) { - result = innerRemoveSimilarItems(amount, filter, fuzzyMode, destination, tx); - } - return result; + public boolean isItemValid(int slot, ItemStack stack) { + return handler.isItemValid(slot, stack); } - private ItemStack innerRemoveSimilarItems(int amount, ItemStack filter, FuzzyMode fuzzyMode, - Predicate destination, Transaction tx) { - - for (var view : this.storage) { - var is = view.getResource(); - if (is.isBlank()) { - continue; - } - - if (!filter.isEmpty() && !ItemComparisonHelper.isFuzzyEqualItem(is.toStack(), filter, fuzzyMode)) { - continue; // Doesn't match ItemStack template - } - - if (destination != null && !destination.test(is.toStack())) { - continue; // Doesn't match filter - } - - long actualAmount = view.extract(is, amount, tx); - if (actualAmount <= 0) { - continue; // Apparently not extractable - } - - // If any of the slots returned more than what we requested, it'll be voided here - if (actualAmount > amount) { - // TODO AELog.warn( - // "An inventory returned more (%d) than we requested (%d) during extraction. Excess will be voided.", -// actualAmount, amount); - actualAmount = amount; - } - - return is.toStack((int) actualAmount); - } - - return ItemStack.EMPTY; + @Override + public ItemStack insertItem(int slot, ItemStack stack, boolean simulate) { + return handler.insertItem(slot, stack, simulate); } - @NotNull @Override - public ItemStack addItems(ItemStack itemsToAdd, boolean simulate) { - if (itemsToAdd.isEmpty()) { - return ItemStack.EMPTY; - } - - try (var tx = Platform.openOrJoinTx()) { - ItemStack remainder = itemsToAdd.copy(); - - var inserted = storage.insert(ItemVariant.of(itemsToAdd), itemsToAdd.getCount(), tx); - - if (!simulate) { - tx.commit(); - } - - remainder.shrink((int) inserted); - return remainder.isEmpty() ? ItemStack.EMPTY : remainder; - } + public ItemStack extractItem(int slot, int amount, boolean simulate) { + return handler.extractItem(slot, amount, simulate); } } diff --git a/src/main/java/appeng/api/networking/GridHelper.java b/src/main/java/appeng/api/networking/GridHelper.java index c0273a3fe36..e410c815608 100644 --- a/src/main/java/appeng/api/networking/GridHelper.java +++ b/src/main/java/appeng/api/networking/GridHelper.java @@ -34,6 +34,7 @@ import net.minecraft.world.level.block.entity.BlockEntity; import appeng.api.networking.events.GridEvent; +import appeng.capabilities.Capabilities; import appeng.hooks.ticking.TickHandler; import appeng.me.GridConnection; import appeng.me.GridEventBus; @@ -133,7 +134,7 @@ public static IInWorldGridNodeHost getNodeHost(Level level, BlockPos pos) { if (be instanceof IInWorldGridNodeHost host) { return host; } - return IInWorldGridNodeHost.LOOKUP.find(level, pos, null, be, null); + return be != null ? be.getCapability(Capabilities.IN_WORLD_GRID_NODE_HOST).orElse(null) : null; } /** diff --git a/src/main/java/appeng/api/networking/IInWorldGridNodeHost.java b/src/main/java/appeng/api/networking/IInWorldGridNodeHost.java index 60fc6fccd20..35ce332727b 100644 --- a/src/main/java/appeng/api/networking/IInWorldGridNodeHost.java +++ b/src/main/java/appeng/api/networking/IInWorldGridNodeHost.java @@ -25,12 +25,11 @@ import org.jetbrains.annotations.Nullable; -import net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup; import net.minecraft.core.Direction; import net.minecraft.world.level.block.entity.BlockEntity; +import net.neoforged.neoforge.common.capabilities.AutoRegisterCapability; import appeng.api.util.AECableType; -import appeng.core.AppEng; /** * Implement to create a networked {@link BlockEntity}. Must be implemented for a block entity to be available for @@ -38,10 +37,8 @@ *

* Can either be implemented by the block entity itself, or provided via a lookup/capability with null direction. */ +@AutoRegisterCapability public interface IInWorldGridNodeHost { - BlockApiLookup LOOKUP = BlockApiLookup.get(AppEng.makeId("iinworldgridnodehost"), - IInWorldGridNodeHost.class, Void.class); - /** * get the grid node for a particular side of a block, you can return null, by returning a valid node later and * calling updateState, you can join the Grid when your block is ready. diff --git a/src/main/java/appeng/api/networking/crafting/UnsuitableCpus.java b/src/main/java/appeng/api/networking/crafting/UnsuitableCpus.java index 6d678d75d14..a92be5bdfac 100644 --- a/src/main/java/appeng/api/networking/crafting/UnsuitableCpus.java +++ b/src/main/java/appeng/api/networking/crafting/UnsuitableCpus.java @@ -2,7 +2,7 @@ /** * Details about unsuitable crafting CPUs unavailable for a job. Detail for - * {@link appeng.api.networking.crafting.CraftingSubmitErrorCode#NO_SUITABLE_CPU_FOUND}. + * {@link CraftingSubmitErrorCode#NO_SUITABLE_CPU_FOUND}. */ public record UnsuitableCpus(int offline, int busy, int tooSmall, int excluded) { } diff --git a/src/main/java/appeng/api/networking/storage/IStorageService.java b/src/main/java/appeng/api/networking/storage/IStorageService.java index 3232b52264a..06e4ee74a4b 100644 --- a/src/main/java/appeng/api/networking/storage/IStorageService.java +++ b/src/main/java/appeng/api/networking/storage/IStorageService.java @@ -52,11 +52,11 @@ public interface IStorageService extends IGridService { KeyCounter getCachedInventory(); /** - * Adds a {@link IStorageProvider} that is not associated with a specific {@link appeng.api.networking.IGridNode }. - * This is for adding storage provided by {@link IGridService}s for examples. + * Adds a {@link IStorageProvider} that is not associated with a specific {@link IGridNode }. This is for adding + * storage provided by {@link IGridService}s for examples. *

- * THIS IT NOT FOR USE BY {@link appeng.api.networking.IGridNode NODES} THAT PROVIDE THE {@link IStorageProvider} - * SERVICE. Those are automatically handled by the storage system. + * THIS IT NOT FOR USE BY {@link IGridNode NODES} THAT PROVIDE THE {@link IStorageProvider} SERVICE. Those are + * automatically handled by the storage system. * * @param cc to be added cell provider */ diff --git a/src/main/java/appeng/api/parts/IPart.java b/src/main/java/appeng/api/parts/IPart.java index 0a27b7bc673..b3634139637 100644 --- a/src/main/java/appeng/api/parts/IPart.java +++ b/src/main/java/appeng/api/parts/IPart.java @@ -32,8 +32,6 @@ import org.jetbrains.annotations.MustBeInvokedByOverriders; import org.jetbrains.annotations.Nullable; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; import net.minecraft.CrashReportCategory; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.core.BlockPos; @@ -51,6 +49,12 @@ import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.phys.Vec3; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.api.distmarker.OnlyIn; +import net.neoforged.neoforge.client.model.data.ModelData; +import net.neoforged.neoforge.common.capabilities.Capability; +import net.neoforged.neoforge.common.capabilities.CapabilityProvider; +import net.neoforged.neoforge.common.util.LazyOptional; import appeng.api.networking.IGridNode; import appeng.api.networking.IManagedGridNode; @@ -73,7 +77,7 @@ public interface IPart extends ICustomCableConnection, Clearable { * Render dynamic portions of this part, as part of the cable bus TESR. This part has to return true for * {@link #requireDynamicRender()} in order for this method to be called. */ - @Environment(EnvType.CLIENT) + @OnlyIn(Dist.CLIENT) default void renderDynamic(float partialTicks, PoseStack poseStack, MultiBufferSource buffers, int combinedLightIn, int combinedOverlayIn) { } @@ -418,14 +422,25 @@ default IPartModel getStaticModels() { }; } + /** + * Implement this method if your part exposes capabilitys. Any requests for capabilities on the cable bus will be + * forwarded to parts on the appropriate side. + * + * @see CapabilityProvider#getCapability(Capability, Direction) + * + * @return The capability + */ + default LazyOptional getCapability(Capability capabilityClass) { + return LazyOptional.empty(); + } + /** * Additional model data to be passed to the models for rendering this part. * * @return The model data to pass to the model. Only useful if custom models are used. */ - @Nullable - default Object getRenderAttachmentData() { - return null; + default ModelData getModelData() { + return ModelData.EMPTY; } /** diff --git a/src/main/java/appeng/api/parts/PartModels.java b/src/main/java/appeng/api/parts/PartModels.java index 5d30ff32eda..157aec00bf0 100644 --- a/src/main/java/appeng/api/parts/PartModels.java +++ b/src/main/java/appeng/api/parts/PartModels.java @@ -29,7 +29,7 @@ * Allows registration of part models that can then be used in {@link IPart#getStaticModels()}. *

* The models will automatically be added as dependencies to the model of the cable bus, and registered with - * {@link ModelLoader#addSpecialModel(net.minecraft.resources.ResourceLocation)}. + * {@link ModelLoader#addSpecialModel(ResourceLocation)}. */ public final class PartModels { diff --git a/src/main/java/appeng/api/stacks/AEFluidKey.java b/src/main/java/appeng/api/stacks/AEFluidKey.java index 87501e42b28..98d64d5a55c 100644 --- a/src/main/java/appeng/api/stacks/AEFluidKey.java +++ b/src/main/java/appeng/api/stacks/AEFluidKey.java @@ -5,8 +5,6 @@ import org.jetbrains.annotations.Nullable; -import net.fabricmc.fabric.api.transfer.v1.fluid.FluidConstants; -import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant; import net.minecraft.core.BlockPos; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.nbt.CompoundTag; @@ -17,14 +15,15 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; import net.minecraft.world.level.material.Fluid; +import net.neoforged.neoforge.fluids.FluidStack; import appeng.api.storage.AEKeyFilter; import appeng.core.AELog; import appeng.util.Platform; public final class AEFluidKey extends AEKey { - public static final int AMOUNT_BUCKET = (int) FluidConstants.BUCKET; - public static final int AMOUNT_BLOCK = (int) FluidConstants.BLOCK; + public static final int AMOUNT_BUCKET = 1000; + public static final int AMOUNT_BLOCK = 1000; private final Fluid fluid; @Nullable @@ -47,14 +46,14 @@ public static AEFluidKey of(Fluid fluid) { } @Nullable - public static AEFluidKey of(FluidVariant fluidVariant) { - if (fluidVariant.isBlank()) { + public static AEFluidKey of(FluidStack fluidVariant) { + if (fluidVariant.isEmpty()) { return null; } - return of(fluidVariant.getFluid(), fluidVariant.getNbt()); + return of(fluidVariant.getFluid(), fluidVariant.getTag()); } - public static boolean matches(AEKey what, FluidVariant fluid) { + public static boolean matches(AEKey what, FluidStack fluid) { return what instanceof AEFluidKey fluidKey && fluidKey.matches(fluid); } @@ -66,8 +65,8 @@ public static AEKeyFilter filter() { return AEFluidKey::is; } - public boolean matches(FluidVariant variant) { - return !variant.isBlank() && fluid.isSame(variant.getFluid()) && variant.nbtMatches(tag); + public boolean matches(FluidStack variant) { + return !variant.isEmpty() && fluid.isSame(variant.getFluid()) && Objects.equals(tag, variant.getTag()); } @Override @@ -147,8 +146,8 @@ public boolean isTagged(TagKey tag) { return fluid.builtInRegistryHolder().is((TagKey) tag); } - public FluidVariant toVariant() { - return FluidVariant.of(fluid, tag); + public FluidStack toStack(int amount) { + return new FluidStack(fluid, amount, tag); } public Fluid getFluid() { diff --git a/src/main/java/appeng/api/stacks/AEItemKey.java b/src/main/java/appeng/api/stacks/AEItemKey.java index 78ffba09739..583c91a2f0b 100644 --- a/src/main/java/appeng/api/stacks/AEItemKey.java +++ b/src/main/java/appeng/api/stacks/AEItemKey.java @@ -1,5 +1,7 @@ package appeng.api.stacks; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; import java.lang.ref.WeakReference; import java.util.List; import java.util.Objects; @@ -7,7 +9,6 @@ import org.jetbrains.annotations.Nullable; -import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant; import net.minecraft.core.BlockPos; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.nbt.CompoundTag; @@ -20,21 +21,46 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.ItemLike; import net.minecraft.world.level.Level; +import net.neoforged.neoforge.common.capabilities.CapabilityProvider; import appeng.api.storage.AEKeyFilter; import appeng.core.AELog; import appeng.util.Platform; public final class AEItemKey extends AEKey { + private static final MethodHandle SERIALIZE_CAPS_HANDLE; + static { + try { + var method = CapabilityProvider.class.getDeclaredMethod("serializeCaps"); + method.setAccessible(true); + SERIALIZE_CAPS_HANDLE = MethodHandles.lookup().unreflect(method); + } catch (Exception exception) { + throw new RuntimeException("Failed to create serializeCaps method handle", exception); + } + } + + @Nullable + private static CompoundTag serializeStackCaps(ItemStack stack) { + try { + var caps = (CompoundTag) SERIALIZE_CAPS_HANDLE.invokeExact((CapabilityProvider) stack); + // Ensure stacks with no serializable cap providers are treated the same as stacks with no caps! + return caps == null || caps.isEmpty() ? null : caps; + } catch (Throwable ex) { + throw new RuntimeException("Failed to call serializeCaps", ex); + } + } + private final Item item; private final InternedTag internedTag; + private final InternedTag internedCaps; private final int hashCode; private final int cachedDamage; - private AEItemKey(Item item, InternedTag internedTag) { + private AEItemKey(Item item, InternedTag internedTag, InternedTag internedCaps) { this.item = item; this.internedTag = internedTag; - this.hashCode = item.hashCode() * 31 + internedTag.hashCode; + this.internedCaps = internedCaps; + this.hashCode = Objects.hash(item, internedTag, internedCaps); if (internedTag.tag != null && internedTag.tag.get("Damage") instanceof NumericTag numericTag) { this.cachedDamage = numericTag.getAsInt(); } else { @@ -42,20 +68,12 @@ private AEItemKey(Item item, InternedTag internedTag) { } } - @Nullable - public static AEItemKey of(ItemVariant variant) { - if (variant.isBlank()) { - return null; - } - return of(variant.getItem(), variant.getNbt()); - } - @Nullable public static AEItemKey of(ItemStack stack) { if (stack.isEmpty()) { return null; } - return of(stack.getItem(), stack.getTag()); + return of(stack.getItem(), stack.getTag(), serializeStackCaps(stack)); } public static boolean matches(AEKey what, ItemStack itemStack) { @@ -87,7 +105,7 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; AEItemKey aeItemKey = (AEItemKey) o; - return item == aeItemKey.item && internedTag == aeItemKey.internedTag; + return item == aeItemKey.item && internedTag == aeItemKey.internedTag && internedCaps == aeItemKey.internedCaps; } @Override @@ -100,11 +118,17 @@ public static AEItemKey of(ItemLike item) { } public static AEItemKey of(ItemLike item, @Nullable CompoundTag tag) { - return new AEItemKey(item.asItem(), InternedTag.of(tag, false)); + return of(item, tag, null); + } + + private static AEItemKey of(ItemLike item, @Nullable CompoundTag tag, @Nullable CompoundTag caps) { + return new AEItemKey(item.asItem(), InternedTag.of(tag, false), InternedTag.of(caps, false)); } public boolean matches(ItemStack stack) { - return !stack.isEmpty() && stack.is(item) && Objects.equals(stack.getTag(), internedTag.tag); + // TODO: remove or optimize cap check if it becomes too slow >:-( + return !stack.isEmpty() && stack.is(item) && Objects.equals(stack.getTag(), internedTag.tag) + && Objects.equals(serializeStackCaps(stack), internedCaps.tag); } public ItemStack toStack() { @@ -116,9 +140,8 @@ public ItemStack toStack(int count) { return ItemStack.EMPTY; } - var result = new ItemStack(item); + var result = new ItemStack(item, count, internedCaps.tag); result.setTag(copyTag()); - result.setCount(count); return result; } @@ -132,7 +155,8 @@ public static AEItemKey fromTag(CompoundTag tag) { var item = BuiltInRegistries.ITEM.getOptional(new ResourceLocation(tag.getString("id"))) .orElseThrow(() -> new IllegalArgumentException("Unknown item id.")); var extraTag = tag.contains("tag") ? tag.getCompound("tag") : null; - return of(item, extraTag); + var extraCaps = tag.contains("caps") ? tag.getCompound("caps") : null; + return of(item, extraTag, extraCaps); } catch (Exception e) { AELog.debug("Tried to load an invalid item key from NBT: %s", tag, e); return null; @@ -147,6 +171,9 @@ public CompoundTag toTag() { if (internedTag.tag != null) { result.put("tag", internedTag.tag.copy()); } + if (internedCaps.tag != null) { + result.put("caps", internedCaps.tag.copy()); + } return result; } @@ -177,10 +204,6 @@ public ResourceLocation getId() { return BuiltInRegistries.ITEM.getKey(item); } - public ItemVariant toVariant() { - return ItemVariant.of(item, internedTag.tag); - } - /** * @return NEVER MODIFY THE RETURNED TAG */ @@ -241,7 +264,7 @@ public void writeToPacket(FriendlyByteBuf data) { data.writeVarInt(Item.getId(item)); CompoundTag compoundTag = null; if (item.canBeDepleted() || item.shouldOverrideMultiplayerNbt()) { - compoundTag = internedTag.tag; + compoundTag = item.getShareTag(toStack()); } data.writeNbt(compoundTag); } @@ -249,8 +272,11 @@ public void writeToPacket(FriendlyByteBuf data) { public static AEItemKey fromPacket(FriendlyByteBuf data) { int i = data.readVarInt(); var item = Item.byId(i); - var tag = data.readNbt(); - return new AEItemKey(item, InternedTag.of(tag, true)); + var shareTag = data.readNbt(); + var stack = new ItemStack(item); + stack.readShareTag(shareTag); + return new AEItemKey(item, InternedTag.of(stack.getTag(), true), + InternedTag.of(serializeStackCaps(stack), true)); } @Override diff --git a/src/main/java/appeng/api/stacks/AEKey.java b/src/main/java/appeng/api/stacks/AEKey.java index f228fb3aef6..9b7c42605db 100644 --- a/src/main/java/appeng/api/stacks/AEKey.java +++ b/src/main/java/appeng/api/stacks/AEKey.java @@ -22,8 +22,8 @@ * Uniquely identifies something that "stacks" within an ME inventory. *

* For example for items, this is the combination of an {@link net.minecraft.world.item.Item} and optional - * {@link net.minecraft.nbt.CompoundTag}. To account for common indexing scenarios, a key is (optionally) split into a - * primary and secondary component, which serves two purposes: + * {@link CompoundTag}. To account for common indexing scenarios, a key is (optionally) split into a primary and + * secondary component, which serves two purposes: *

*/ public class CompatLayerHelper { - public static final boolean IS_LOADED = FabricLoader.getInstance().isModLoaded("rei_plugin_compatibilities"); + public static final boolean IS_LOADED = ModList.get().isLoaded("rei_plugin_compatibilities"); } diff --git a/src/main/java/appeng/integration/modules/jeirei/CraftingHelper.java b/src/main/java/appeng/integration/modules/jeirei/CraftingHelper.java index fe50155db1c..7d9a2576ce4 100644 --- a/src/main/java/appeng/integration/modules/jeirei/CraftingHelper.java +++ b/src/main/java/appeng/integration/modules/jeirei/CraftingHelper.java @@ -6,6 +6,7 @@ import net.minecraft.core.NonNullList; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.crafting.Recipe; +import net.minecraft.world.item.crafting.RecipeHolder; import appeng.api.stacks.AEItemKey; import appeng.core.AELog; @@ -23,15 +24,15 @@ public final class CraftingHelper { private CraftingHelper() { } - public static void performTransfer(CraftingTermMenu menu, Recipe recipe, boolean craftMissing) { + public static void performTransfer(CraftingTermMenu menu, RecipeHolder recipe, boolean craftMissing) { // We send the items in the recipe in any case to serve as a fallback in case the recipe is transient - var templateItems = findGoodTemplateItems(recipe, menu); + var templateItems = findGoodTemplateItems(recipe.value(), menu); - var recipeId = recipe.getId(); + var recipeId = recipe.id(); // Don't transmit a recipe id to the server in case the recipe is not actually resolvable // this is the case for recipes synthetically generated for JEI - if (menu.getPlayer().level().getRecipeManager().byKey(recipe.getId()).isEmpty()) { + if (menu.getPlayer().level().getRecipeManager().byKey(recipe.id()).isEmpty()) { AELog.debug("Cannot send recipe id %s to server because it's transient", recipeId); recipeId = null; } diff --git a/src/main/java/appeng/integration/modules/jeirei/EncodingHelper.java b/src/main/java/appeng/integration/modules/jeirei/EncodingHelper.java index 15661f4c109..cb3fc6b8542 100644 --- a/src/main/java/appeng/integration/modules/jeirei/EncodingHelper.java +++ b/src/main/java/appeng/integration/modules/jeirei/EncodingHelper.java @@ -16,6 +16,7 @@ import net.minecraft.core.NonNullList; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.crafting.Recipe; +import net.minecraft.world.item.crafting.RecipeHolder; import net.minecraft.world.item.crafting.RecipeType; import appeng.api.stacks.AEItemKey; @@ -96,13 +97,13 @@ public static boolean isSupportedCraftingRecipe(@Nullable Recipe recipe) { } public static void encodeCraftingRecipe(PatternEncodingTermMenu menu, - @Nullable Recipe recipe, + @Nullable RecipeHolder recipe, List> genericIngredients, Predicate visiblePredicate) { - if (recipe != null && recipe.getType().equals(RecipeType.STONECUTTING)) { + if (recipe != null && recipe.value().getType().equals(RecipeType.STONECUTTING)) { menu.setMode(EncodingMode.STONECUTTING); - menu.setStonecuttingRecipeId(recipe.getId()); - } else if (recipe != null && recipe.getType().equals(RecipeType.SMITHING)) { + menu.setStonecuttingRecipeId(recipe.id()); + } else if (recipe != null && recipe.value().getType().equals(RecipeType.SMITHING)) { menu.setMode(EncodingMode.SMITHING_TABLE); } else { menu.setMode(EncodingMode.CRAFTING); @@ -116,7 +117,7 @@ public static void encodeCraftingRecipe(PatternEncodingTermMenu menu, if (recipe != null) { // When we have access to a crafting recipe, we'll switch modes and try to find suitable // ingredients based on the recipe ingredients, which allows for fuzzy-matching. - var ingredients3x3 = CraftingRecipeUtil.ensure3by3CraftingMatrix(recipe); + var ingredients3x3 = CraftingRecipeUtil.ensure3by3CraftingMatrix(recipe.value()); // Find a good match for every ingredient for (int slot = 0; slot < ingredients3x3.size(); slot++) { diff --git a/src/main/java/appeng/integration/modules/rei/AttunementCategory.java b/src/main/java/appeng/integration/modules/rei/AttunementCategory.java index fca919f0161..8f0b2bf34ba 100644 --- a/src/main/java/appeng/integration/modules/rei/AttunementCategory.java +++ b/src/main/java/appeng/integration/modules/rei/AttunementCategory.java @@ -46,10 +46,10 @@ public List setupDisplay(AttunementDisplay display, Rectangle bounds) { widgets.add(Widgets.createRecipeBase(bounds)); widgets.add(Widgets.createArrow(new Point(startPoint.x + 27, startPoint.y + 4))); widgets.add(Widgets.createResultSlotBackground(new Point(startPoint.x + 61, startPoint.y + 5))); - widgets.add(Widgets.createSlot(new Point(startPoint.x + 4, startPoint.y + 5)) - .entries(display.getInputEntries().get(0)).markInput()); - widgets.add(Widgets.createSlot(new Point(startPoint.x + 61, startPoint.y + 5)) - .entries(display.getOutputEntries().get(0)).disableBackground().markOutput()); + // TODO 1.19.3 widgets.add(Widgets.createSlot(new Point(startPoint.x + 4, startPoint.y + 5)) + // TODO 1.19.3 .entries(display.getInputEntries().get(0)).markInput()); + // TODO 1.19.3 widgets.add(Widgets.createSlot(new Point(startPoint.x + 61, startPoint.y + 5)) + // TODO 1.19.3 .entries(display.getOutputEntries().get(0)).disableBackground().markOutput()); return widgets; } diff --git a/src/main/java/appeng/integration/modules/rei/ChargerCategory.java b/src/main/java/appeng/integration/modules/rei/ChargerCategory.java index a797f2d549d..6bd9bd08e15 100644 --- a/src/main/java/appeng/integration/modules/rei/ChargerCategory.java +++ b/src/main/java/appeng/integration/modules/rei/ChargerCategory.java @@ -12,13 +12,9 @@ import me.shedaniel.rei.api.client.gui.widgets.Widgets; import me.shedaniel.rei.api.client.registry.display.DisplayCategory; import me.shedaniel.rei.api.common.category.CategoryIdentifier; -import me.shedaniel.rei.api.common.util.EntryIngredients; import me.shedaniel.rei.api.common.util.EntryStacks; -import appeng.blockentity.misc.ChargerBlockEntity; -import appeng.blockentity.misc.CrankBlockEntity; import appeng.core.definitions.AEBlocks; -import appeng.core.localization.ItemModText; public class ChargerCategory implements DisplayCategory { private final Renderer icon; @@ -50,34 +46,34 @@ public List setupDisplay(ChargerDisplay display, Rectangle bounds) { var y = bounds.y; widgets.add(Widgets.createRecipeBase(bounds)); - widgets.add( - Widgets.createSlot(new Point(x + 31, y + 8)) - .markInput() - .backgroundEnabled(true) - .entries(EntryIngredients.ofIngredient(display.recipe().getIngredient()))); - widgets.add( - Widgets.createSlot(new Point(x + 81, y + 8)) - .markOutput() - .backgroundEnabled(true) - .entry(EntryStacks.of(display.recipe().getResultItem()))); - - widgets.add( - Widgets.createSlot(new Point(x + 3, y + 30)) - .unmarkInputOrOutput() - .backgroundEnabled(false) - .entry(EntryStacks.of(AEBlocks.CRANK.stack()))); + // TODO 1.19.3 widgets.add( + // TODO 1.19.3 Widgets.createSlot(new Point(x + 31, y + 8)) + // TODO 1.19.3 .markInput() + // TODO 1.19.3 .backgroundEnabled(true) + // TODO 1.19.3 .entries(EntryIngredients.ofIngredient(display.recipe().getIngredient()))); + // TODO 1.19.3 widgets.add( + // TODO 1.19.3 Widgets.createSlot(new Point(x + 81, y + 8)) + // TODO 1.19.3 .markOutput() + // TODO 1.19.3 .backgroundEnabled(true) + // TODO 1.19.3 .entry(EntryStacks.of(display.recipe().getResultItem()))); +// TODO 1.19.3 + // TODO 1.19.3 widgets.add( + // TODO 1.19.3 Widgets.createSlot(new Point(x + 3, y + 30)) + // TODO 1.19.3 .unmarkInputOrOutput() + // TODO 1.19.3 .backgroundEnabled(false) + // TODO 1.19.3 .entry(EntryStacks.of(AEBlocks.CRANK.stack()))); widgets.add(Widgets.createArrow(new Point(x + 52, y + 8))); - var turns = (ChargerBlockEntity.POWER_MAXIMUM_AMOUNT + CrankBlockEntity.POWER_PER_CRANK_TURN - 1) - / CrankBlockEntity.POWER_PER_CRANK_TURN; - widgets.add(Widgets - .createLabel(new Point(x + 20, y + 35), - ItemModText.CHARGER_REQUIRED_POWER.text(turns, ChargerBlockEntity.POWER_MAXIMUM_AMOUNT)) - .color(0x7E7E7E) - .noShadow() - .leftAligned()); - + // TODO 1.19.3 var turns = (ChargerBlockEntity.POWER_MAXIMUM_AMOUNT + CrankBlockEntity.POWER_PER_CRANK_TURN - 1) + // TODO 1.19.3 / CrankBlockEntity.POWER_PER_CRANK_TURN; + // TODO 1.19.3 widgets.add(Widgets + // TODO 1.19.3 .createLabel(new Point(x + 20, y + 35), + // TODO 1.19.3 ItemModText.CHARGER_REQUIRED_POWER.text(turns, ChargerBlockEntity.POWER_MAXIMUM_AMOUNT)) + // TODO 1.19.3 .color(0x7E7E7E) + // TODO 1.19.3 .noShadow() + // TODO 1.19.3 .leftAligned()); +// TODO 1.19.3 return widgets; } diff --git a/src/main/java/appeng/integration/modules/rei/CondenserCategory.java b/src/main/java/appeng/integration/modules/rei/CondenserCategory.java index d1c53111204..f9543e513a7 100644 --- a/src/main/java/appeng/integration/modules/rei/CondenserCategory.java +++ b/src/main/java/appeng/integration/modules/rei/CondenserCategory.java @@ -31,7 +31,6 @@ import me.shedaniel.math.Point; import me.shedaniel.math.Rectangle; import me.shedaniel.rei.api.client.gui.Renderer; -import me.shedaniel.rei.api.client.gui.widgets.Slot; import me.shedaniel.rei.api.client.gui.widgets.Tooltip; import me.shedaniel.rei.api.client.gui.widgets.Widget; import me.shedaniel.rei.api.client.gui.widgets.Widgets; @@ -102,13 +101,15 @@ public List setupDisplay(CondenserOutputDisplay recipeDisplay, Rectangle } })); - Slot outputSlot = Widgets.createSlot(new Point(origin.x + 55, origin.y + 27)).disableBackground().markOutput() - .entries(recipeDisplay.getOutputEntries().get(0)); - widgets.add(outputSlot); - - Slot storageCellSlot = Widgets.createSlot(new Point(origin.x + 51, origin.y + 1)).disableBackground() - .markInput().entries(recipeDisplay.getViableStorageComponents()); - widgets.add(storageCellSlot); + // TODO 1.19.3 Slot outputSlot = Widgets.createSlot(new Point(origin.x + 55, origin.y + + // 27)).disableBackground().markOutput() + // TODO 1.19.3 .entries(recipeDisplay.getOutputEntries().get(0)); + // TODO 1.19.3 widgets.add(outputSlot); +// TODO 1.19.3 + // TODO 1.19.3 Slot storageCellSlot = Widgets.createSlot(new Point(origin.x + 51, origin.y + + // 1)).disableBackground() + // TODO 1.19.3 .markInput().entries(recipeDisplay.getViableStorageComponents()); + // TODO 1.19.3 widgets.add(storageCellSlot); return widgets; diff --git a/src/main/java/appeng/integration/modules/rei/FacadeRegistryGenerator.java b/src/main/java/appeng/integration/modules/rei/FacadeRegistryGenerator.java index 9c23615cfae..c54f9f61624 100644 --- a/src/main/java/appeng/integration/modules/rei/FacadeRegistryGenerator.java +++ b/src/main/java/appeng/integration/modules/rei/FacadeRegistryGenerator.java @@ -23,8 +23,6 @@ import java.util.Optional; import net.minecraft.core.NonNullList; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.crafting.CraftingBookCategory; import net.minecraft.world.item.crafting.Ingredient; @@ -35,7 +33,6 @@ import me.shedaniel.rei.api.common.entry.type.VanillaEntryTypes; import me.shedaniel.rei.plugin.common.displays.crafting.DefaultShapedDisplay; -import appeng.core.AppEng; import appeng.core.definitions.AEItems; import appeng.core.definitions.AEParts; import appeng.items.parts.FacadeItem; @@ -90,9 +87,6 @@ public Optional> getUsageFor(EntryStack entry) { } private DefaultShapedDisplay make(ItemStack textureItem, ItemStack cableAnchor, ItemStack result) { - // This id should only be used within JEI and not really matter - ResourceLocation id = AppEng.makeId("facade/" + Item.getId(textureItem.getItem())); - var ingredients = NonNullList.withSize(9, Ingredient.EMPTY); ingredients.set(1, Ingredient.of(cableAnchor)); ingredients.set(3, Ingredient.of(cableAnchor)); @@ -102,7 +96,7 @@ private DefaultShapedDisplay make(ItemStack textureItem, ItemStack cableAnchor, result.setCount(4); - return new DefaultShapedDisplay(new ShapedRecipe(id, "", CraftingBookCategory.MISC, 3, 3, ingredients, result)); + return new DefaultShapedDisplay(new ShapedRecipe("", CraftingBookCategory.MISC, 3, 3, ingredients, result)); } } diff --git a/src/main/java/appeng/integration/modules/rei/InscriberRecipeCategory.java b/src/main/java/appeng/integration/modules/rei/InscriberRecipeCategory.java index 3e03fdf62eb..b11d2995f8f 100644 --- a/src/main/java/appeng/integration/modules/rei/InscriberRecipeCategory.java +++ b/src/main/java/appeng/integration/modules/rei/InscriberRecipeCategory.java @@ -24,7 +24,6 @@ import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; -import me.shedaniel.math.Point; import me.shedaniel.math.Rectangle; import me.shedaniel.rei.api.client.gui.Renderer; import me.shedaniel.rei.api.client.gui.widgets.Widget; @@ -76,14 +75,14 @@ public List setupDisplay(InscriberRecipeWrapper recipeDisplay, Rectangle List ingredients = recipeDisplay.getInputEntries(); EntryIngredient output = recipeDisplay.getOutputEntries().get(0); - widgets.add(Widgets.createSlot(new Point(innerX + 1, innerY + 1)).disableBackground().markInput() - .entries(ingredients.get(SLOT_INPUT_TOP))); - widgets.add(Widgets.createSlot(new Point(innerX + 19, innerY + 24)).disableBackground().markInput() - .entries(ingredients.get(SLOT_INPUT_MIDDLE))); - widgets.add(Widgets.createSlot(new Point(innerX + 1, innerY + 47)).disableBackground().markInput() - .entries(ingredients.get(SLOT_INPUT_BOTTOM))); - widgets.add(Widgets.createSlot(new Point(innerX + 69, innerY + 25)).disableBackground().markOutput() - .entries(output)); +// TODO 1.19.3 widgets.add(Widgets.createSlot(new Point(innerX + 1, innerY + 1)).disableBackground().markInput() +// TODO 1.19.3 .entries(ingredients.get(SLOT_INPUT_TOP))); +// TODO 1.19.3 widgets.add(Widgets.createSlot(new Point(innerX + 19, innerY + 24)).disableBackground().markInput() +// TODO 1.19.3 .entries(ingredients.get(SLOT_INPUT_MIDDLE))); +// TODO 1.19.3 widgets.add(Widgets.createSlot(new Point(innerX + 1, innerY + 47)).disableBackground().markInput() +// TODO 1.19.3 .entries(ingredients.get(SLOT_INPUT_BOTTOM))); +// TODO 1.19.3 widgets.add(Widgets.createSlot(new Point(innerX + 69, innerY + 25)).disableBackground().markOutput() +// TODO 1.19.3 .entries(output)); return widgets; } diff --git a/src/main/java/appeng/integration/modules/rei/InscriberRecipeWrapper.java b/src/main/java/appeng/integration/modules/rei/InscriberRecipeWrapper.java index 5dbbbe5d563..6d2046b27aa 100644 --- a/src/main/java/appeng/integration/modules/rei/InscriberRecipeWrapper.java +++ b/src/main/java/appeng/integration/modules/rei/InscriberRecipeWrapper.java @@ -63,6 +63,6 @@ public CategoryIdentifier getCategoryIdentifier() { @Override public Optional getDisplayLocation() { - return Optional.of(recipe.getId()); + throw new UnsupportedOperationException(); // TODO } } diff --git a/src/main/java/appeng/integration/modules/rei/ReiPlugin.java b/src/main/java/appeng/integration/modules/rei/ReiPlugin.java index f5302b7982d..af544e7415c 100644 --- a/src/main/java/appeng/integration/modules/rei/ReiPlugin.java +++ b/src/main/java/appeng/integration/modules/rei/ReiPlugin.java @@ -31,6 +31,7 @@ import net.minecraft.network.chat.Component; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.crafting.Ingredient; +import net.neoforged.api.distmarker.Dist; import dev.architectury.event.CompoundEventResult; import me.shedaniel.math.Rectangle; @@ -48,6 +49,7 @@ import me.shedaniel.rei.api.common.entry.type.VanillaEntryTypes; import me.shedaniel.rei.api.common.util.EntryIngredients; import me.shedaniel.rei.api.common.util.EntryStacks; +import me.shedaniel.rei.forge.REIPlugin; import me.shedaniel.rei.plugin.common.BuiltinPlugin; import me.shedaniel.rei.plugin.common.displays.DefaultInformationDisplay; @@ -77,6 +79,7 @@ import appeng.recipes.handlers.InscriberRecipe; import appeng.recipes.transform.TransformRecipe; +@REIPlugin(Dist.CLIENT) public class ReiPlugin implements REIClientPlugin { // Will be hidden if developer items are disabled in the config @@ -208,7 +211,7 @@ public void registerEntries(EntryRegistry registry) { if (AEConfig.instance().isEnableFacadesInJEI()) { registry.addEntries( - EntryIngredients.ofItemStacks(FacadeCreativeTab.getDisplayItems())); + EntryIngredients.ofItemStacks(new FacadeCreativeTab().getDisplayItems())); } } diff --git a/src/main/java/appeng/integration/modules/rei/ReiRuntimeAdapter.java b/src/main/java/appeng/integration/modules/rei/ReiRuntimeAdapter.java index 04ebfbe57d2..04900225d5d 100644 --- a/src/main/java/appeng/integration/modules/rei/ReiRuntimeAdapter.java +++ b/src/main/java/appeng/integration/modules/rei/ReiRuntimeAdapter.java @@ -59,6 +59,6 @@ public void setSearchText(String text) { @Override public boolean hasSearchFocus() { var searchField = this.runtime.getSearchTextField(); - return searchField != null && searchField.isFocused(); + return searchField != null && searchField.m_93696_(); } } diff --git a/src/main/java/appeng/integration/modules/rei/TransformCategory.java b/src/main/java/appeng/integration/modules/rei/TransformCategory.java index 103f9c51b84..59802e6b148 100644 --- a/src/main/java/appeng/integration/modules/rei/TransformCategory.java +++ b/src/main/java/appeng/integration/modules/rei/TransformCategory.java @@ -66,19 +66,19 @@ public List setupDisplay(TransformRecipeWrapper display, Rectangle bound // so ingredients lists with less than two rows get centered vertically y += 9 * (3 - nInputs); } - for (var input : display.getInputEntries()) { - var slot = Widgets.createSlot(new Point(x, y)) - .entries(input) - .markInput(); - y += slot.getBounds().height; - if (y >= bounds.y + 64) { - // we don't actually have room to make multiple columns of ingredients look nice, - // but this is better than just overflowing downwards. - y -= 54; - x += 18; - } - widgets.add(slot); - } + // TODO 1.19.3 for (var input : display.getInputEntries()) { + // TODO 1.19.3 var slot = Widgets.createSlot(new Point(x, y)) + // TODO 1.19.3 .entries(input) + // TODO 1.19.3 .markInput(); + // TODO 1.19.3 y += slot.getBounds().height; + // TODO 1.19.3 if (y >= bounds.y + 64) { + // TODO 1.19.3 // we don't actually have room to make multiple columns of ingredients look nice, + // TODO 1.19.3 // but this is better than just overflowing downwards. + // TODO 1.19.3 y -= 54; + // TODO 1.19.3 x += 18; + // TODO 1.19.3 } + // TODO 1.19.3 widgets.add(slot); + // TODO 1.19.3 } // To center everything but the ingredients vertically int yOffset = bounds.y + 28; @@ -88,37 +88,37 @@ public List setupDisplay(TransformRecipeWrapper display, Rectangle bound var arrow1 = Widgets.createArrow(new Point(col2, yOffset)); widgets.add(arrow1); - // Third column is water block - final int col3 = col2 + arrow1.getBounds().getWidth() + 6; - var catalystSlot = Widgets.createSlot(new Point(col3, yOffset)) - .entries(getCatalystForRendering(display)) - .markInput() - .backgroundEnabled(false); - widgets.add(catalystSlot); - - // Fourth column is arrow pointing to results - final int col4 = col3 + 16 + 5; - var arrow2 = Widgets.createArrow(new Point(col4, yOffset)); - widgets.add(arrow2); - - // Fifth column is the result - final int col5 = arrow2.getBounds().getMaxX() + 10; - var slot = Widgets.createSlot(new Point(col5, yOffset)) - .entries(display.getOutputEntries().get(0)) - .markOutput(); - widgets.add(slot); - - Component circumstance; - if (display.getTransformCircumstance().isExplosion()) { - circumstance = ItemModText.EXPLOSION.text(); - } else { - circumstance = ItemModText.SUBMERGE_IN.text(); - } - - widgets.add(Widgets.createLabel(new Point(bounds.getCenterX(), bounds.y + 15), circumstance) - .color(0x7E7E7E) - .noShadow()); - +// TODO 1.19.3 // Third column is water block +// TODO 1.19.3 final int col3 = col2 + arrow1.getBounds().getWidth() + 6; +// TODO 1.19.3 var catalystSlot = Widgets.createSlot(new Point(col3, yOffset)) +// TODO 1.19.3 .entries(getCatalystForRendering(display)) +// TODO 1.19.3 .markInput() +// TODO 1.19.3 .backgroundEnabled(false); +// TODO 1.19.3 widgets.add(catalystSlot); +// TODO 1.19.3 +// TODO 1.19.3 // Fourth column is arrow pointing to results +// TODO 1.19.3 final int col4 = col3 + 16 + 5; +// TODO 1.19.3 var arrow2 = Widgets.createArrow(new Point(col4, yOffset)); +// TODO 1.19.3 widgets.add(arrow2); +// TODO 1.19.3 +// TODO 1.19.3 // Fifth column is the result +// TODO 1.19.3 final int col5 = arrow2.getBounds().getMaxX() + 10; +// TODO 1.19.3 var slot = Widgets.createSlot(new Point(col5, yOffset)) +// TODO 1.19.3 .entries(display.getOutputEntries().get(0)) +// TODO 1.19.3 .markOutput(); +// TODO 1.19.3 widgets.add(slot); +// TODO 1.19.3 +// TODO 1.19.3 Component circumstance; +// TODO 1.19.3 if (display.getTransformCircumstance().isExplosion()) { +// TODO 1.19.3 circumstance = ItemModText.EXPLOSION.text(); +// TODO 1.19.3 } else { +// TODO 1.19.3 circumstance = ItemModText.SUBMERGE_IN.text(); +// TODO 1.19.3 } +// TODO 1.19.3 +// TODO 1.19.3 widgets.add(Widgets.createLabel(new Point(bounds.getCenterX(), bounds.y + 15), circumstance) +// TODO 1.19.3 .color(0x7E7E7E) +// TODO 1.19.3 .noShadow()); +// TODO 1.19.3 return widgets; } diff --git a/src/main/java/appeng/integration/modules/rei/transfer/UseCraftingRecipeTransfer.java b/src/main/java/appeng/integration/modules/rei/transfer/UseCraftingRecipeTransfer.java index d22f66088a1..d0598b5aa67 100644 --- a/src/main/java/appeng/integration/modules/rei/transfer/UseCraftingRecipeTransfer.java +++ b/src/main/java/appeng/integration/modules/rei/transfer/UseCraftingRecipeTransfer.java @@ -24,7 +24,6 @@ import me.shedaniel.rei.api.common.display.Display; import me.shedaniel.rei.api.common.entry.type.VanillaEntryTypes; -import appeng.core.AppEng; import appeng.core.localization.ItemModText; import appeng.integration.modules.jeirei.CraftingHelper; import appeng.integration.modules.jeirei.TransferHelper; @@ -109,7 +108,7 @@ private Recipe createFakeRecipe(Display display) { ingredients.set(i, ingredient); } - return new ShapedRecipe(AppEng.makeId("__fake_recipe"), "", CraftingBookCategory.MISC, CRAFTING_GRID_WIDTH, + return new ShapedRecipe("", CraftingBookCategory.MISC, CRAFTING_GRID_WIDTH, CRAFTING_GRID_HEIGHT, ingredients, ItemStack.EMPTY); } diff --git a/src/main/java/appeng/integration/modules/theoneprobe/AEConfigProvider.java b/src/main/java/appeng/integration/modules/theoneprobe/AEConfigProvider.java new file mode 100644 index 00000000000..a4b4f59d9c6 --- /dev/null +++ b/src/main/java/appeng/integration/modules/theoneprobe/AEConfigProvider.java @@ -0,0 +1,49 @@ +/* + * This file is part of Applied Energistics 2. + * Copyright (c) 2013 - 2015, AlgorithmX2, All rights reserved. + * + * Applied Energistics 2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Applied Energistics 2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Applied Energistics 2. If not, see . + */ + +package appeng.integration.modules.theoneprobe; + +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; + +import mcjty.theoneprobe.api.IProbeConfig; +import mcjty.theoneprobe.api.IProbeConfigProvider; +import mcjty.theoneprobe.api.IProbeHitData; +import mcjty.theoneprobe.api.IProbeHitEntityData; + +import appeng.blockentity.AEBaseBlockEntity; + +public class AEConfigProvider implements IProbeConfigProvider { + + @Override + public void getProbeConfig(IProbeConfig config, Player player, Level level, Entity entity, + IProbeHitEntityData data) { + // Still no AE entities. + } + + @Override + public void getProbeConfig(IProbeConfig config, Player player, Level level, BlockState blockState, + IProbeHitData data) { + if (level.getBlockEntity(data.getPos()) instanceof AEBaseBlockEntity) { + config.setRFMode(0); + } + } + +} diff --git a/src/main/java/appeng/integration/modules/theoneprobe/BlockEntityInfoProvider.java b/src/main/java/appeng/integration/modules/theoneprobe/BlockEntityInfoProvider.java new file mode 100644 index 00000000000..637b1447f6c --- /dev/null +++ b/src/main/java/appeng/integration/modules/theoneprobe/BlockEntityInfoProvider.java @@ -0,0 +1,255 @@ +/* + * This file is part of Applied Energistics 2. + * Copyright (c) 2013 - 2014, AlgorithmX2, All rights reserved. + * + * Applied Energistics 2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Applied Energistics 2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Applied Energistics 2. If not, see . + */ + +package appeng.integration.modules.theoneprobe; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; + +import mcjty.theoneprobe.api.CompoundText; +import mcjty.theoneprobe.api.IBlockDisplayOverride; +import mcjty.theoneprobe.api.IProbeHitData; +import mcjty.theoneprobe.api.IProbeInfo; +import mcjty.theoneprobe.api.IProbeInfoProvider; +import mcjty.theoneprobe.api.ProbeMode; +import mcjty.theoneprobe.api.TextStyleClass; + +import appeng.api.integrations.igtooltip.ClientRegistration; +import appeng.api.integrations.igtooltip.CommonRegistration; +import appeng.api.integrations.igtooltip.TooltipBuilder; +import appeng.api.integrations.igtooltip.TooltipContext; +import appeng.api.integrations.igtooltip.providers.BodyProvider; +import appeng.api.integrations.igtooltip.providers.IconProvider; +import appeng.api.integrations.igtooltip.providers.ModNameProvider; +import appeng.api.integrations.igtooltip.providers.NameProvider; +import appeng.api.integrations.igtooltip.providers.ServerDataProvider; +import appeng.core.AppEng; +import appeng.integration.modules.igtooltip.TooltipProviders; +import appeng.util.Platform; + +public final class BlockEntityInfoProvider implements IProbeInfoProvider, IBlockDisplayOverride { + private final List dataCollectors = new ArrayList<>(); + private final List> bodyCustomizers = new ArrayList<>(); + + private final List> nameCustomizers = new ArrayList<>(); + private final List> modNameCustomizers = new ArrayList<>(); + private final List> iconCustomizers = new ArrayList<>(); + + public BlockEntityInfoProvider() { + TooltipProviders.loadCommon(new CommonRegistration() { + @Override + public void addBlockEntityData(Class blockEntityClass, + ServerDataProvider provider) { + dataCollectors.add((blockEntity, player, serverData) -> { + if (blockEntityClass.isInstance(blockEntity)) { + var obj = blockEntityClass.cast(blockEntity); + provider.provideServerData(player, obj, serverData); + } + }); + } + }); + TooltipProviders.loadClient(new ClientRegistration() { + @Override + public void addBlockEntityBody(Class blockEntityClass, + Class blockClass, ResourceLocation id, BodyProvider provider, + int priority) { + bodyCustomizers.add(new BodyCustomizer<>(blockEntityClass, provider, priority)); + } + + @Override + public void addBlockEntityIcon(Class blockEntityClass, + Class blockClass, ResourceLocation id, IconProvider provider, + int priority) { + iconCustomizers.add(new IconCustomizer<>(blockEntityClass, provider, priority)); + } + + @Override + public void addBlockEntityName(Class blockEntityClass, + Class blockClass, ResourceLocation id, NameProvider provider, + int priority) { + nameCustomizers.add(new NameCustomizer<>(blockEntityClass, provider, priority)); + } + + @Override + public void addBlockEntityModName(Class blockEntityClass, + Class blockClass, ResourceLocation id, ModNameProvider provider, + int priority) { + modNameCustomizers.add(new ModNameCustomizer<>(blockEntityClass, provider, priority)); + } + }); + + nameCustomizers.sort(Comparator.comparingInt(NameCustomizer::priority)); + iconCustomizers.sort(Comparator.comparingInt(IconCustomizer::priority)); + modNameCustomizers.sort(Comparator.comparingInt(ModNameCustomizer::priority)); + bodyCustomizers.sort(Comparator.comparingInt(BodyCustomizer::priority)); + } + + @Override + public ResourceLocation getID() { + return AppEng.makeId("block-entity"); + } + + @Override + public void addProbeInfo(ProbeMode mode, IProbeInfo probeInfo, Player player, Level level, + BlockState blockState, IProbeHitData data) { + var blockEntity = level.getBlockEntity(data.getPos()); + if (blockEntity != null) { + var serverData = getServerData(player, blockEntity); + + // Then allow all providers to modify the probe info + var context = getContext(player, data, serverData); + var tooltipBuilder = new TopTooltipBuilder(probeInfo); + for (var customizer : bodyCustomizers) { + customizer.buildTooltip(blockEntity, context, tooltipBuilder); + } + } + } + + @Override + public boolean overrideStandardInfo(ProbeMode probeMode, IProbeInfo probeInfo, Player player, Level level, + BlockState blockState, IProbeHitData probeHitData) { + + var blockEntity = level.getBlockEntity(probeHitData.getPos()); + if (blockEntity == null) { + return false; + } + + var serverData = getServerData(player, blockEntity); + var context = getContext(player, probeHitData, serverData); + + // If any one of our providers customizes the info, we have to override it all + Component name = null; + String modName = null; + ItemStack icon = null; + + for (var customizer : nameCustomizers) { + name = customizer.getName(blockEntity, context); + if (name != null) { + break; + } + } + + for (var customizer : modNameCustomizers) { + modName = customizer.getModName(blockEntity, context); + if (modName != null) { + break; + } + } + + for (var customizer : iconCustomizers) { + icon = customizer.getIcon(blockEntity, context); + if (icon != null) { + break; + } + } + + if (name != null || modName != null || icon != null) { + // Fill out defaults + var pickBlock = probeHitData.getPickBlock(); + if (name == null) { + name = pickBlock.getHoverName(); + } + if (icon == null) { + icon = pickBlock; + } + if (modName == null) { + modName = Platform.getModName(BuiltInRegistries.ITEM.getKey(pickBlock.getItem()).getNamespace()); + } + + // TOP itself checks a config here to enable/disable the mod name, but I don't know how to get access to it + probeInfo.horizontal().item(icon).vertical().text(name) + .text(CompoundText.create().style(TextStyleClass.MODNAME).text(modName)); + return true; + } + + return false; + } + + @FunctionalInterface + private interface ServerDataCollector { + void collect(BlockEntity blockEntity, ServerPlayer player, CompoundTag serverData); + } + + private CompoundTag getServerData(Player player, BlockEntity blockEntity) { + var serverData = new CompoundTag(); + + // Emulate Jade/WTHIT/Waila model by collecting the server-data they would send to the client + // into a temporary compound tag. + if (player instanceof ServerPlayer serverPlayer) { + for (var dataCollector : dataCollectors) { + dataCollector.collect(blockEntity, serverPlayer, serverData); + } + } + return serverData; + } + + private static TooltipContext getContext(Player player, IProbeHitData data, CompoundTag serverData) { + return new TooltipContext( + serverData, + data.getHitVec(), + player); + } + + record NameCustomizer (Class beClass, NameProvider provider, int priority) { + public Component getName(BlockEntity blockEntity, TooltipContext context) { + if (beClass.isInstance(blockEntity)) { + return provider.getName(beClass.cast(blockEntity), context); + } + return null; + } + } + + record IconCustomizer (Class beClass, IconProvider provider, int priority) { + public ItemStack getIcon(BlockEntity blockEntity, TooltipContext context) { + if (beClass.isInstance(blockEntity)) { + return provider.getIcon(beClass.cast(blockEntity), context); + } + return null; + } + } + + record ModNameCustomizer (Class beClass, ModNameProvider provider, int priority) { + public String getModName(BlockEntity blockEntity, TooltipContext context) { + if (beClass.isInstance(blockEntity)) { + return provider.getModName(beClass.cast(blockEntity), context); + } + return null; + } + } + + record BodyCustomizer (Class beClass, BodyProvider provider, int priority) { + public void buildTooltip(BlockEntity blockEntity, TooltipContext context, TooltipBuilder tooltipBuilder) { + if (beClass.isInstance(blockEntity)) { + provider.buildTooltip(beClass.cast(blockEntity), context, tooltipBuilder); + } + } + } +} diff --git a/src/main/java/appeng/integration/modules/theoneprobe/TOP.java b/src/main/java/appeng/integration/modules/theoneprobe/TOP.java new file mode 100644 index 00000000000..771e0fc51aa --- /dev/null +++ b/src/main/java/appeng/integration/modules/theoneprobe/TOP.java @@ -0,0 +1,31 @@ +/* + * This file is part of Applied Energistics 2. + * Copyright (c) 2020, AlgorithmX2, All rights reserved. + * + * Applied Energistics 2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Applied Energistics 2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Applied Energistics 2. If not, see . + */ + +package appeng.integration.modules.theoneprobe; + +import net.neoforged.fml.InterModComms; +import net.neoforged.fml.ModList; +import net.neoforged.fml.event.lifecycle.InterModEnqueueEvent; + +public class TOP { + public static void enqueueIMC(final InterModEnqueueEvent event) { + if (ModList.get().isLoaded("theoneprobe")) { + InterModComms.sendTo("theoneprobe", "getTheOneProbe", TheOneProbeModule::new); + } + } +} diff --git a/src/main/java/appeng/integration/modules/theoneprobe/TheOneProbeModule.java b/src/main/java/appeng/integration/modules/theoneprobe/TheOneProbeModule.java new file mode 100644 index 00000000000..7dfab944da2 --- /dev/null +++ b/src/main/java/appeng/integration/modules/theoneprobe/TheOneProbeModule.java @@ -0,0 +1,36 @@ +/* + * This file is part of Applied Energistics 2. + * Copyright (c) 2020, AlgorithmX2, All rights reserved. + * + * Applied Energistics 2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Applied Energistics 2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Applied Energistics 2. If not, see . + */ + +package appeng.integration.modules.theoneprobe; + +import java.util.function.Function; + +import mcjty.theoneprobe.api.ITheOneProbe; + +public class TheOneProbeModule implements Function { + + @Override + public Void apply(ITheOneProbe input) { + input.registerProbeConfigProvider(new AEConfigProvider()); + var provider = new BlockEntityInfoProvider(); + input.registerProvider(provider); + input.registerBlockDisplayOverride(provider); + + return null; + } +} diff --git a/src/main/java/appeng/integration/modules/theoneprobe/TopTooltipBuilder.java b/src/main/java/appeng/integration/modules/theoneprobe/TopTooltipBuilder.java new file mode 100644 index 00000000000..51449da563c --- /dev/null +++ b/src/main/java/appeng/integration/modules/theoneprobe/TopTooltipBuilder.java @@ -0,0 +1,26 @@ +package appeng.integration.modules.theoneprobe; + +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; + +import mcjty.theoneprobe.api.IProbeInfo; + +import appeng.api.integrations.igtooltip.TooltipBuilder; + +public class TopTooltipBuilder implements TooltipBuilder { + private final IProbeInfo probeInfo; + + public TopTooltipBuilder(IProbeInfo probeInfo) { + this.probeInfo = probeInfo; + } + + @Override + public void addLine(Component line) { + probeInfo.mcText(line); + } + + @Override + public void addLine(Component line, ResourceLocation id) { + probeInfo.mcText(line); + } +} diff --git a/src/main/java/appeng/integration/modules/wthit/WthitModule.java b/src/main/java/appeng/integration/modules/wthit/WthitModule.java index 192f7adc5c1..7e2795e8172 100644 --- a/src/main/java/appeng/integration/modules/wthit/WthitModule.java +++ b/src/main/java/appeng/integration/modules/wthit/WthitModule.java @@ -17,6 +17,7 @@ import mcp.mobius.waila.api.IWailaPlugin; import mcp.mobius.waila.api.TooltipPosition; import mcp.mobius.waila.api.WailaConstants; +import mcp.mobius.waila.api.WailaPlugin; import mcp.mobius.waila.api.component.ItemComponent; import appeng.api.integrations.igtooltip.ClientRegistration; @@ -29,6 +30,7 @@ import appeng.api.integrations.igtooltip.providers.ServerDataProvider; import appeng.integration.modules.igtooltip.TooltipProviders; +@WailaPlugin(id = "ae2:wthit") public class WthitModule implements IWailaPlugin { public void register(IRegistrar registrar) { TooltipProviders.loadCommon(new CommonRegistration() { diff --git a/src/main/java/appeng/items/AEBaseItem.java b/src/main/java/appeng/items/AEBaseItem.java index b2360ca6784..b7a849ecf7b 100644 --- a/src/main/java/appeng/items/AEBaseItem.java +++ b/src/main/java/appeng/items/AEBaseItem.java @@ -20,15 +20,14 @@ import org.jetbrains.annotations.Nullable; -import net.fabricmc.fabric.api.item.v1.FabricItem; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.CreativeModeTab; import net.minecraft.world.item.Item; -public abstract class AEBaseItem extends Item implements FabricItem { +public abstract class AEBaseItem extends Item { - public AEBaseItem(Item.Properties properties) { + public AEBaseItem(Properties properties) { super(properties); } @@ -47,4 +46,5 @@ public String toString() { String regName = this.getRegistryName() != null ? this.getRegistryName().getPath() : "unregistered"; return this.getClass().getSimpleName() + "[" + regName + "]"; } + } diff --git a/src/main/java/appeng/items/contents/PortableCellMenuHost.java b/src/main/java/appeng/items/contents/PortableCellMenuHost.java index 0ab7ef2821e..bc00c5a19b0 100644 --- a/src/main/java/appeng/items/contents/PortableCellMenuHost.java +++ b/src/main/java/appeng/items/contents/PortableCellMenuHost.java @@ -48,7 +48,7 @@ import appeng.util.ConfigManager; /** - * Hosts the terminal interface for a {@link appeng.items.tools.powered.AbstractPortableCell}. + * Hosts the terminal interface for a {@link AbstractPortableCell}. */ public class PortableCellMenuHost extends ItemMenuHost implements IPortableTerminal { private final BiConsumer returnMainMenu; diff --git a/src/main/java/appeng/items/materials/MaterialItem.java b/src/main/java/appeng/items/materials/MaterialItem.java index d87b1e78acc..1eaa9f9c75b 100644 --- a/src/main/java/appeng/items/materials/MaterialItem.java +++ b/src/main/java/appeng/items/materials/MaterialItem.java @@ -18,12 +18,10 @@ package appeng.items.materials; -import net.minecraft.world.item.Item; - import appeng.items.AEBaseItem; public final class MaterialItem extends AEBaseItem { - public MaterialItem(Item.Properties properties) { + public MaterialItem(Properties properties) { super(properties); } } diff --git a/src/main/java/appeng/items/materials/NamePressItem.java b/src/main/java/appeng/items/materials/NamePressItem.java index f1c1dbe86ac..a517aa94309 100644 --- a/src/main/java/appeng/items/materials/NamePressItem.java +++ b/src/main/java/appeng/items/materials/NamePressItem.java @@ -20,14 +20,13 @@ import java.util.List; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.chat.Component; -import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.TooltipFlag; import net.minecraft.world.level.Level; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.api.distmarker.OnlyIn; import appeng.items.AEBaseItem; @@ -37,11 +36,11 @@ public class NamePressItem extends AEBaseItem { */ public static final String TAG_INSCRIBE_NAME = "InscribeName"; - public NamePressItem(Item.Properties properties) { + public NamePressItem(Properties properties) { super(properties); } - @Environment(EnvType.CLIENT) + @OnlyIn(Dist.CLIENT) @Override public void appendHoverText(ItemStack stack, Level level, List lines, TooltipFlag advancedTooltips) { diff --git a/src/main/java/appeng/items/materials/StorageComponentItem.java b/src/main/java/appeng/items/materials/StorageComponentItem.java index 0b1cd18c64c..c51cb87f06b 100644 --- a/src/main/java/appeng/items/materials/StorageComponentItem.java +++ b/src/main/java/appeng/items/materials/StorageComponentItem.java @@ -18,7 +18,6 @@ package appeng.items.materials; -import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import appeng.api.implementations.items.IStorageComponent; @@ -27,7 +26,7 @@ public class StorageComponentItem extends AEBaseItem implements IStorageComponent { private final int storageInKb; - public StorageComponentItem(Item.Properties properties, int storageInKb) { + public StorageComponentItem(Properties properties, int storageInKb) { super(properties); this.storageInKb = storageInKb; } diff --git a/src/main/java/appeng/items/materials/UpgradeCardItem.java b/src/main/java/appeng/items/materials/UpgradeCardItem.java index 980e579fa29..a6eda3f3d7c 100644 --- a/src/main/java/appeng/items/materials/UpgradeCardItem.java +++ b/src/main/java/appeng/items/materials/UpgradeCardItem.java @@ -20,18 +20,17 @@ import java.util.List; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; import net.minecraft.network.chat.Component; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.TooltipFlag; import net.minecraft.world.item.context.UseOnContext; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.BlockEntity; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.api.distmarker.OnlyIn; import appeng.api.parts.IPartHost; import appeng.api.parts.SelectedPart; @@ -40,17 +39,16 @@ import appeng.api.upgrades.Upgrades; import appeng.core.localization.ButtonToolTips; import appeng.core.localization.PlayerMessages; -import appeng.hooks.AEToolItem; import appeng.items.AEBaseItem; import appeng.util.InteractionUtil; -public class UpgradeCardItem extends AEBaseItem implements AEToolItem { +public class UpgradeCardItem extends AEBaseItem { - public UpgradeCardItem(Item.Properties properties) { + public UpgradeCardItem(Properties properties) { super(properties); } - @Environment(EnvType.CLIENT) + @OnlyIn(Dist.CLIENT) @Override public void appendHoverText(ItemStack stack, Level level, List lines, TooltipFlag advancedTooltips) { @@ -114,6 +112,6 @@ public InteractionResult onItemUseFirst(ItemStack stack, UseOnContext context) { } } - return InteractionResult.PASS; + return super.onItemUseFirst(stack, context); } } diff --git a/src/main/java/appeng/items/misc/PaintBallItem.java b/src/main/java/appeng/items/misc/PaintBallItem.java index 3c71492689e..dfb31002915 100644 --- a/src/main/java/appeng/items/misc/PaintBallItem.java +++ b/src/main/java/appeng/items/misc/PaintBallItem.java @@ -18,8 +18,6 @@ package appeng.items.misc; -import net.minecraft.world.item.Item; - import appeng.api.util.AEColor; import appeng.items.AEBaseItem; @@ -29,7 +27,7 @@ public class PaintBallItem extends AEBaseItem { private final boolean lumen; - public PaintBallItem(Item.Properties properties, AEColor color, boolean lumen) { + public PaintBallItem(Properties properties, AEColor color, boolean lumen) { super(properties); this.color = color; this.lumen = lumen; diff --git a/src/main/java/appeng/items/misc/WrappedGenericStack.java b/src/main/java/appeng/items/misc/WrappedGenericStack.java index f5608ae5c98..49260ae0ebe 100644 --- a/src/main/java/appeng/items/misc/WrappedGenericStack.java +++ b/src/main/java/appeng/items/misc/WrappedGenericStack.java @@ -64,7 +64,7 @@ public static ItemStack wrap(AEKey what, long amount) { return result; } - public WrappedGenericStack(Item.Properties properties) { + public WrappedGenericStack(Properties properties) { super(properties.stacksTo(1)); } diff --git a/src/main/java/appeng/items/parts/ColoredPartItem.java b/src/main/java/appeng/items/parts/ColoredPartItem.java index 9131f806ae0..2cb7b39fa74 100644 --- a/src/main/java/appeng/items/parts/ColoredPartItem.java +++ b/src/main/java/appeng/items/parts/ColoredPartItem.java @@ -20,8 +20,6 @@ import java.util.function.Function; -import net.minecraft.world.item.Item; - import appeng.api.parts.IPart; import appeng.api.util.AEColor; @@ -29,7 +27,7 @@ public class ColoredPartItem extends PartItem { private final AEColor color; - public ColoredPartItem(Item.Properties properties, Class partClass, Function, T> factory, + public ColoredPartItem(Properties properties, Class partClass, Function, T> factory, AEColor color) { super(properties, partClass, item -> factory.apply((ColoredPartItem) item)); this.color = color; diff --git a/src/main/java/appeng/items/parts/FacadeItem.java b/src/main/java/appeng/items/parts/FacadeItem.java index aae68b53a9e..3455760c9cc 100644 --- a/src/main/java/appeng/items/parts/FacadeItem.java +++ b/src/main/java/appeng/items/parts/FacadeItem.java @@ -46,14 +46,13 @@ import appeng.core.AEConfig; import appeng.core.definitions.AEItems; import appeng.facade.FacadePart; -import appeng.hooks.AEToolItem; import appeng.items.AEBaseItem; -public class FacadeItem extends AEBaseItem implements IFacadeItem, AEToolItem { +public class FacadeItem extends AEBaseItem implements IFacadeItem { private static final String NBT_ITEM_ID = "item"; - public FacadeItem(Item.Properties properties) { + public FacadeItem(Properties properties) { super(properties); } diff --git a/src/main/java/appeng/items/parts/PartItem.java b/src/main/java/appeng/items/parts/PartItem.java index 8ac31e7f870..d8306ab08b3 100644 --- a/src/main/java/appeng/items/parts/PartItem.java +++ b/src/main/java/appeng/items/parts/PartItem.java @@ -21,7 +21,6 @@ import java.util.function.Function; import net.minecraft.world.InteractionResult; -import net.minecraft.world.item.Item; import net.minecraft.world.item.context.UseOnContext; import appeng.api.parts.IPart; @@ -34,7 +33,7 @@ public class PartItem extends AEBaseItem implements IPartItem partClass; private final Function, T> factory; - public PartItem(Item.Properties properties, Class partClass, Function, T> factory) { + public PartItem(Properties properties, Class partClass, Function, T> factory) { super(properties); this.partClass = partClass; this.factory = factory; diff --git a/src/main/java/appeng/items/storage/BasicStorageCell.java b/src/main/java/appeng/items/storage/BasicStorageCell.java index 4311aa6beac..08ec5e26bed 100644 --- a/src/main/java/appeng/items/storage/BasicStorageCell.java +++ b/src/main/java/appeng/items/storage/BasicStorageCell.java @@ -21,8 +21,6 @@ import java.util.List; import java.util.Optional; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; import net.minecraft.network.chat.Component; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; @@ -30,12 +28,13 @@ import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Player; import net.minecraft.world.inventory.tooltip.TooltipComponent; -import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.TooltipFlag; import net.minecraft.world.item.context.UseOnContext; import net.minecraft.world.level.ItemLike; import net.minecraft.world.level.Level; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.api.distmarker.OnlyIn; import appeng.api.config.FuzzyMode; import appeng.api.stacks.AEKeyType; @@ -63,7 +62,7 @@ public class BasicStorageCell extends AEBaseItem implements IBasicCellItem, AETo protected final int totalTypes; private final AEKeyType keyType; - public BasicStorageCell(Item.Properties properties, + public BasicStorageCell(Properties properties, ItemLike coreItem, ItemLike housingItem, double idleDrain, @@ -81,7 +80,7 @@ public BasicStorageCell(Item.Properties properties, this.keyType = keyType; } - @Environment(EnvType.CLIENT) + @OnlyIn(Dist.CLIENT) @Override public void appendHoverText(ItemStack stack, Level level, diff --git a/src/main/java/appeng/items/storage/CreativeCellItem.java b/src/main/java/appeng/items/storage/CreativeCellItem.java index 2b9847ff88d..72d0a82b0c6 100644 --- a/src/main/java/appeng/items/storage/CreativeCellItem.java +++ b/src/main/java/appeng/items/storage/CreativeCellItem.java @@ -21,16 +21,17 @@ import java.util.List; import java.util.Optional; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; import net.minecraft.client.gui.screens.Screen; import net.minecraft.network.chat.Component; import net.minecraft.world.inventory.tooltip.TooltipComponent; +import net.minecraft.world.item.Item.Properties; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.TooltipFlag; import net.minecraft.world.level.ItemLike; import net.minecraft.world.level.Level; import net.minecraft.world.level.material.Fluid; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.api.distmarker.OnlyIn; import appeng.api.client.AEKeyRendering; import appeng.api.config.FuzzyMode; @@ -65,7 +66,7 @@ public FuzzyMode getFuzzyMode(ItemStack is) { public void setFuzzyMode(ItemStack is, FuzzyMode fzMode) { } - @Environment(EnvType.CLIENT) + @OnlyIn(Dist.CLIENT) @Override public void appendHoverText(ItemStack stack, Level level, List lines, TooltipFlag advancedTooltips) { var inventory = StorageCells.getCellInventory(stack, null); diff --git a/src/main/java/appeng/items/storage/SpatialStorageCellItem.java b/src/main/java/appeng/items/storage/SpatialStorageCellItem.java index 7768cde9b9f..6bba34cc469 100644 --- a/src/main/java/appeng/items/storage/SpatialStorageCellItem.java +++ b/src/main/java/appeng/items/storage/SpatialStorageCellItem.java @@ -22,18 +22,17 @@ import java.util.List; import java.util.Locale; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; import net.minecraft.ChatFormatting; import net.minecraft.core.BlockPos; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.Tag; import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.TooltipFlag; import net.minecraft.world.level.Level; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.api.distmarker.OnlyIn; import appeng.api.implementations.items.ISpatialStorageCell; import appeng.core.AELog; @@ -55,12 +54,12 @@ public class SpatialStorageCellItem extends AEBaseItem implements ISpatialStorag private final int maxRegion; - public SpatialStorageCellItem(Item.Properties props, int spatialScale) { + public SpatialStorageCellItem(Properties props, int spatialScale) { super(props); this.maxRegion = spatialScale; } - @Environment(EnvType.CLIENT) + @OnlyIn(Dist.CLIENT) @Override public void appendHoverText(ItemStack stack, Level level, List lines, TooltipFlag advancedTooltips) { diff --git a/src/main/java/appeng/items/storage/ViewCellItem.java b/src/main/java/appeng/items/storage/ViewCellItem.java index 74db76ed6de..60455869acd 100644 --- a/src/main/java/appeng/items/storage/ViewCellItem.java +++ b/src/main/java/appeng/items/storage/ViewCellItem.java @@ -20,6 +20,7 @@ import java.util.Collection; +import net.minecraft.world.item.Item.Properties; import net.minecraft.world.item.ItemStack; import appeng.api.config.FuzzyMode; diff --git a/src/main/java/appeng/items/tools/MemoryCardItem.java b/src/main/java/appeng/items/tools/MemoryCardItem.java index 3fe1c4ca223..b1fc542c554 100644 --- a/src/main/java/appeng/items/tools/MemoryCardItem.java +++ b/src/main/java/appeng/items/tools/MemoryCardItem.java @@ -26,11 +26,10 @@ import org.jetbrains.annotations.Nullable; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; import net.minecraft.ChatFormatting; import net.minecraft.ResourceLocationException; import net.minecraft.client.resources.language.I18n; +import net.minecraft.core.BlockPos; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.Tag; @@ -46,7 +45,9 @@ import net.minecraft.world.item.TooltipFlag; import net.minecraft.world.item.context.UseOnContext; import net.minecraft.world.level.Level; -import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.level.LevelReader; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.api.distmarker.OnlyIn; import appeng.api.implementations.items.IMemoryCard; import appeng.api.implementations.items.MemoryCardMessages; @@ -60,13 +61,12 @@ import appeng.core.localization.Tooltips; import appeng.helpers.IConfigInvHost; import appeng.helpers.IPriorityHost; -import appeng.hooks.AEToolItem; import appeng.items.AEBaseItem; import appeng.util.InteractionUtil; import appeng.util.Platform; import appeng.util.inv.PlayerInternalInventory; -public class MemoryCardItem extends AEBaseItem implements IMemoryCard, AEToolItem, DyeableLeatherItem { +public class MemoryCardItem extends AEBaseItem implements IMemoryCard, DyeableLeatherItem { private static final int DEFAULT_BASE_COLOR = 0xDDDDDD; @@ -74,7 +74,7 @@ public class MemoryCardItem extends AEBaseItem implements IMemoryCard, AEToolIte AEColor.TRANSPARENT, AEColor.TRANSPARENT, AEColor.TRANSPARENT, AEColor.TRANSPARENT, AEColor.TRANSPARENT, AEColor.TRANSPARENT, }; - public MemoryCardItem(Item.Properties properties) { + public MemoryCardItem(Properties properties) { super(properties); } @@ -273,7 +273,7 @@ private static boolean restoreUpgrades(Player player, CompoundTag input, IUpgrad } @Override - @Environment(EnvType.CLIENT) + @OnlyIn(Dist.CLIENT) public void appendHoverText(ItemStack stack, Level level, List lines, TooltipFlag advancedTooltips) { @@ -373,25 +373,21 @@ public void notifyUser(Player player, MemoryCardMessages msg) { } @Override - public InteractionResult onItemUseFirst(ItemStack stack, UseOnContext context) { - var player = context.getPlayer(); - if (player != null && InteractionUtil.isInAlternateUseMode(player)) { - var level = context.getLevel(); + public InteractionResult useOn(UseOnContext context) { + if (InteractionUtil.isInAlternateUseMode(context.getPlayer())) { + Level level = context.getLevel(); if (!level.isClientSide()) { - var state = context.getLevel().getBlockState(context.getClickedPos()); - var useResult = state.use(context.getLevel(), context.getPlayer(), - context.getHand(), - new BlockHitResult(context.getClickLocation(), context.getClickedFace(), - context.getClickedPos(), - context.isInside())); - if (!useResult.consumesAction()) { - clearCard(context.getPlayer(), context.getLevel(), context.getHand()); - } + this.clearCard(context.getPlayer(), context.getLevel(), context.getHand()); } return InteractionResult.sidedSuccess(level.isClientSide()); + } else { + return super.useOn(context); } + } - return InteractionResult.PASS; + @Override + public boolean doesSneakBypassUse(ItemStack stack, LevelReader level, BlockPos pos, Player player) { + return true; } @Override diff --git a/src/main/java/appeng/items/tools/NetworkToolItem.java b/src/main/java/appeng/items/tools/NetworkToolItem.java index dd48afcad54..65710b4e615 100644 --- a/src/main/java/appeng/items/tools/NetworkToolItem.java +++ b/src/main/java/appeng/items/tools/NetworkToolItem.java @@ -35,7 +35,6 @@ import net.minecraft.world.inventory.ClickAction; import net.minecraft.world.inventory.Slot; import net.minecraft.world.inventory.tooltip.TooltipComponent; -import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.context.UseOnContext; import net.minecraft.world.level.Level; @@ -59,7 +58,7 @@ public class NetworkToolItem extends AEBaseItem implements IMenuItem, AEToolItem { - public NetworkToolItem(Item.Properties properties) { + public NetworkToolItem(Properties properties) { super(properties); } diff --git a/src/main/java/appeng/items/tools/fluix/FluixAxeItem.java b/src/main/java/appeng/items/tools/fluix/FluixAxeItem.java index b3d5d04175c..00ec4628365 100644 --- a/src/main/java/appeng/items/tools/fluix/FluixAxeItem.java +++ b/src/main/java/appeng/items/tools/fluix/FluixAxeItem.java @@ -6,6 +6,7 @@ import net.minecraft.network.chat.Component; import net.minecraft.world.item.AxeItem; +import net.minecraft.world.item.Item.Properties; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.TooltipFlag; import net.minecraft.world.item.enchantment.Enchantment; diff --git a/src/main/java/appeng/items/tools/fluix/FluixHoeItem.java b/src/main/java/appeng/items/tools/fluix/FluixHoeItem.java index d55733f1d69..f7c8d6ee64f 100644 --- a/src/main/java/appeng/items/tools/fluix/FluixHoeItem.java +++ b/src/main/java/appeng/items/tools/fluix/FluixHoeItem.java @@ -6,6 +6,7 @@ import net.minecraft.network.chat.Component; import net.minecraft.world.item.HoeItem; +import net.minecraft.world.item.Item.Properties; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.TooltipFlag; import net.minecraft.world.item.enchantment.Enchantment; diff --git a/src/main/java/appeng/items/tools/fluix/FluixPickaxeItem.java b/src/main/java/appeng/items/tools/fluix/FluixPickaxeItem.java index a6cf0764b44..a7ab37ee184 100644 --- a/src/main/java/appeng/items/tools/fluix/FluixPickaxeItem.java +++ b/src/main/java/appeng/items/tools/fluix/FluixPickaxeItem.java @@ -5,6 +5,7 @@ import org.jetbrains.annotations.Nullable; import net.minecraft.network.chat.Component; +import net.minecraft.world.item.Item.Properties; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.PickaxeItem; import net.minecraft.world.item.TooltipFlag; diff --git a/src/main/java/appeng/items/tools/fluix/FluixSpadeItem.java b/src/main/java/appeng/items/tools/fluix/FluixSpadeItem.java index d1d75e61381..23a014164f2 100644 --- a/src/main/java/appeng/items/tools/fluix/FluixSpadeItem.java +++ b/src/main/java/appeng/items/tools/fluix/FluixSpadeItem.java @@ -5,6 +5,7 @@ import org.jetbrains.annotations.Nullable; import net.minecraft.network.chat.Component; +import net.minecraft.world.item.Item.Properties; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ShovelItem; import net.minecraft.world.item.TooltipFlag; diff --git a/src/main/java/appeng/items/tools/fluix/FluixSwordItem.java b/src/main/java/appeng/items/tools/fluix/FluixSwordItem.java index b26f922619f..502d7abcc98 100644 --- a/src/main/java/appeng/items/tools/fluix/FluixSwordItem.java +++ b/src/main/java/appeng/items/tools/fluix/FluixSwordItem.java @@ -5,6 +5,7 @@ import org.jetbrains.annotations.Nullable; import net.minecraft.network.chat.Component; +import net.minecraft.world.item.Item.Properties; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.SwordItem; import net.minecraft.world.item.TooltipFlag; diff --git a/src/main/java/appeng/items/tools/fluix/FluixToolType.java b/src/main/java/appeng/items/tools/fluix/FluixToolType.java index be8abfaba4c..7ef4e6966b5 100644 --- a/src/main/java/appeng/items/tools/fluix/FluixToolType.java +++ b/src/main/java/appeng/items/tools/fluix/FluixToolType.java @@ -13,9 +13,11 @@ public enum FluixToolType { FLUIX("fluix", () -> Ingredient.of(ConventionTags.FLUIX_CRYSTAL)), ; + private final String name; private final Tier toolTier; FluixToolType(String name, Supplier repairIngredient) { + this.name = name; this.toolTier = new Tier() { @Override public int getUses() { @@ -55,6 +57,10 @@ public String toString() { }; } + public final String getName() { + return name; + } + public final Tier getToolTier() { return toolTier; } diff --git a/src/main/java/appeng/items/tools/powered/AbstractPortableCell.java b/src/main/java/appeng/items/tools/powered/AbstractPortableCell.java index e6c9f9f0d93..a324bb8ccbb 100644 --- a/src/main/java/appeng/items/tools/powered/AbstractPortableCell.java +++ b/src/main/java/appeng/items/tools/powered/AbstractPortableCell.java @@ -91,9 +91,8 @@ public PortableCellMenuHost getMenuHost(Player player, int inventorySlot, ItemSt } @Override - public boolean allowNbtUpdateAnimation(Player player, InteractionHand hand, ItemStack oldStack, - ItemStack newStack) { - return false; + public boolean shouldCauseReequipAnimation(ItemStack oldStack, ItemStack newStack, boolean slotChanged) { + return slotChanged; } // Override to change the default color @@ -133,7 +132,7 @@ private boolean disassembleDrive(ItemStack stack, Level level, Player player) { // We refund the crafting recipe ingredients (the first one each) var recipe = level.getRecipeManager().byKey(getRecipeId()).orElse(null); - if (!(recipe instanceof CraftingRecipe craftingRecipe)) { + if (!(recipe.value() instanceof CraftingRecipe craftingRecipe)) { AELog.debug("Cannot disassemble portable cell because it's crafting recipe doesn't exist: %s", getRecipeId()); return false; diff --git a/src/main/java/appeng/items/tools/powered/ChargedStaffItem.java b/src/main/java/appeng/items/tools/powered/ChargedStaffItem.java index 793e2fe2aa6..273c041c272 100644 --- a/src/main/java/appeng/items/tools/powered/ChargedStaffItem.java +++ b/src/main/java/appeng/items/tools/powered/ChargedStaffItem.java @@ -19,7 +19,6 @@ package appeng.items.tools.powered; import net.minecraft.world.entity.LivingEntity; -import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.phys.AABB; @@ -32,7 +31,7 @@ public class ChargedStaffItem extends AEBasePoweredItem { - public ChargedStaffItem(Item.Properties props) { + public ChargedStaffItem(Properties props) { super(AEConfig.instance().getChargedStaffBattery(), props); } diff --git a/src/main/java/appeng/items/tools/powered/ColorApplicatorItem.java b/src/main/java/appeng/items/tools/powered/ColorApplicatorItem.java index 4a3dedb8fd5..162e9ed9e66 100644 --- a/src/main/java/appeng/items/tools/powered/ColorApplicatorItem.java +++ b/src/main/java/appeng/items/tools/powered/ColorApplicatorItem.java @@ -31,8 +31,6 @@ import org.jetbrains.annotations.Nullable; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.nbt.CompoundTag; @@ -60,6 +58,8 @@ import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.properties.Property; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.api.distmarker.OnlyIn; import appeng.api.config.Actionable; import appeng.api.config.FuzzyMode; @@ -126,7 +126,7 @@ public class ColorApplicatorItem extends AEBasePoweredItem private static final String TAG_COLOR = "color"; - public ColorApplicatorItem(Item.Properties props) { + public ColorApplicatorItem(Properties props) { super(AEConfig.instance().getColorApplicatorBattery(), props); } @@ -456,7 +456,7 @@ public void cycleColors(ItemStack is, ItemStack paintBall, int i) { } @Override - @Environment(EnvType.CLIENT) + @OnlyIn(Dist.CLIENT) public void appendHoverText(ItemStack stack, Level level, List lines, TooltipFlag advancedTooltips) { super.appendHoverText(stack, level, lines, advancedTooltips); diff --git a/src/main/java/appeng/items/tools/powered/EntropyManipulatorItem.java b/src/main/java/appeng/items/tools/powered/EntropyManipulatorItem.java index 55d104120f1..cfd0086d77d 100644 --- a/src/main/java/appeng/items/tools/powered/EntropyManipulatorItem.java +++ b/src/main/java/appeng/items/tools/powered/EntropyManipulatorItem.java @@ -39,9 +39,9 @@ import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.BlockItem; -import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.context.UseOnContext; +import net.minecraft.world.item.crafting.RecipeHolder; import net.minecraft.world.item.crafting.RecipeType; import net.minecraft.world.item.crafting.SmeltingRecipe; import net.minecraft.world.level.ClipContext.Fluid; @@ -72,7 +72,7 @@ public class EntropyManipulatorItem extends AEBasePoweredItem implements IBlockT */ public static final int ENERGY_PER_USE = 1600; - public EntropyManipulatorItem(Item.Properties props) { + public EntropyManipulatorItem(Properties props) { super(AEConfig.instance().getEntropyManipulatorBattery(), props); } @@ -167,9 +167,9 @@ private boolean tryApplyEffect(Level level, ItemStack item, BlockPos pos, Direct } if (tryBoth || !InteractionUtil.isInAlternateUseMode(p)) { - if (block instanceof TntBlock tntBlock) { - tntBlock.playerWillDestroy(level, pos, level.getBlockState(pos), p); + if (block instanceof TntBlock) { level.removeBlock(pos, false); + block.onCaughtFire(level.getBlockState(pos), level, pos, side, p); return true; } @@ -234,7 +234,7 @@ private boolean performInWorldSmelting(ItemStack item, Level level, Player p, Bl for (ItemStack i : drops) { tempInv.setItem(0, i); Optional recipe = level.getRecipeManager().getRecipeFor(RecipeType.SMELTING, tempInv, - level); + level).map(RecipeHolder::value); if (!recipe.isPresent()) { return false; @@ -283,7 +283,8 @@ private boolean performInWorldSmelting(ItemStack item, Level level, Player p, Bl @Nullable private static EntropyRecipe findRecipe(Level level, EntropyMode mode, BlockState blockState, FluidState fluidState) { - for (var recipe : level.getRecipeManager().byType(EntropyRecipe.TYPE).values()) { + for (var holder : level.getRecipeManager().byType(EntropyRecipe.TYPE).values()) { + var recipe = holder.value(); if (recipe.matches(mode, blockState, fluidState)) { return recipe; } diff --git a/src/main/java/appeng/items/tools/powered/MatterCannonItem.java b/src/main/java/appeng/items/tools/powered/MatterCannonItem.java index 683864ff2b7..998f6262fc5 100644 --- a/src/main/java/appeng/items/tools/powered/MatterCannonItem.java +++ b/src/main/java/appeng/items/tools/powered/MatterCannonItem.java @@ -23,8 +23,6 @@ import org.jetbrains.annotations.Nullable; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.network.chat.Component; @@ -38,7 +36,6 @@ import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.inventory.tooltip.TooltipComponent; -import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.TooltipFlag; import net.minecraft.world.level.ClipContext; @@ -53,6 +50,8 @@ import net.minecraft.world.phys.HitResult; import net.minecraft.world.phys.HitResult.Type; import net.minecraft.world.phys.Vec3; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.api.distmarker.OnlyIn; import appeng.api.config.Actionable; import appeng.api.config.FuzzyMode; @@ -92,7 +91,7 @@ public class MatterCannonItem extends AEBasePoweredItem implements IBasicCellIte */ private static final int ENERGY_PER_SHOT = 1600; - public MatterCannonItem(Item.Properties props) { + public MatterCannonItem(Properties props) { super(AEConfig.instance().getMatterCannonBattery(), props); } @@ -101,7 +100,7 @@ public double getChargeRate(ItemStack stack) { return 800d + 800d * Upgrades.getEnergyCardMultiplier(getUpgrades(stack)); } - @Environment(EnvType.CLIENT) + @OnlyIn(Dist.CLIENT) @Override public void appendHoverText(ItemStack stack, Level level, List lines, TooltipFlag advancedTooltips) { @@ -456,7 +455,8 @@ private float getPenetration(ItemStack itemStack) { } var recipes = server.getRecipeManager().byType(MatterCannonAmmo.TYPE); - for (var ammoRecipe : recipes.values()) { + for (var holder : recipes.values()) { + var ammoRecipe = holder.value(); if (ammoRecipe.getAmmo().test(itemStack)) { return ammoRecipe.getWeight(); } diff --git a/src/main/java/appeng/items/tools/powered/PortableCellItem.java b/src/main/java/appeng/items/tools/powered/PortableCellItem.java index 347bbfea6fc..7c563775329 100644 --- a/src/main/java/appeng/items/tools/powered/PortableCellItem.java +++ b/src/main/java/appeng/items/tools/powered/PortableCellItem.java @@ -22,15 +22,16 @@ import java.util.Objects; import java.util.Optional; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.inventory.MenuType; import net.minecraft.world.inventory.tooltip.TooltipComponent; +import net.minecraft.world.item.Item.Properties; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.TooltipFlag; import net.minecraft.world.level.Level; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.api.distmarker.OnlyIn; import appeng.api.config.FuzzyMode; import appeng.api.stacks.AEKeyType; @@ -68,7 +69,7 @@ public ResourceLocation getRecipeId() { } @Override - @Environment(EnvType.CLIENT) + @OnlyIn(Dist.CLIENT) public void appendHoverText(ItemStack stack, Level level, List lines, TooltipFlag advancedTooltips) { super.appendHoverText(stack, level, lines, advancedTooltips); diff --git a/src/main/java/appeng/items/tools/powered/WirelessCraftingTerminalItem.java b/src/main/java/appeng/items/tools/powered/WirelessCraftingTerminalItem.java index e676901c7c4..4ceedfbfed5 100644 --- a/src/main/java/appeng/items/tools/powered/WirelessCraftingTerminalItem.java +++ b/src/main/java/appeng/items/tools/powered/WirelessCraftingTerminalItem.java @@ -7,6 +7,7 @@ import net.minecraft.core.BlockPos; import net.minecraft.world.entity.player.Player; import net.minecraft.world.inventory.MenuType; +import net.minecraft.world.item.Item.Properties; import net.minecraft.world.item.ItemStack; import appeng.api.implementations.menuobjects.ItemMenuHost; diff --git a/src/main/java/appeng/items/tools/powered/WirelessTerminalItem.java b/src/main/java/appeng/items/tools/powered/WirelessTerminalItem.java index ffc0a2546cb..17e62e2610f 100644 --- a/src/main/java/appeng/items/tools/powered/WirelessTerminalItem.java +++ b/src/main/java/appeng/items/tools/powered/WirelessTerminalItem.java @@ -27,8 +27,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; import net.minecraft.Util; import net.minecraft.core.BlockPos; import net.minecraft.core.GlobalPos; @@ -42,10 +40,11 @@ import net.minecraft.world.InteractionResultHolder; import net.minecraft.world.entity.player.Player; import net.minecraft.world.inventory.MenuType; -import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.TooltipFlag; import net.minecraft.world.level.Level; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.api.distmarker.OnlyIn; import appeng.api.config.Actionable; import appeng.api.config.Settings; @@ -82,7 +81,7 @@ public class WirelessTerminalItem extends AEBasePoweredItem implements IMenuItem private static final String TAG_ACCESS_POINT_POS = "accessPoint"; - public WirelessTerminalItem(DoubleSupplier powerCapacity, Item.Properties props) { + public WirelessTerminalItem(DoubleSupplier powerCapacity, Properties props) { super(powerCapacity, props); } @@ -134,7 +133,7 @@ public InteractionResultHolder use(Level level, Player player, Intera } @Override - @Environment(EnvType.CLIENT) + @OnlyIn(Dist.CLIENT) public void appendHoverText(ItemStack stack, Level level, List lines, TooltipFlag advancedTooltips) { super.appendHoverText(stack, level, lines, advancedTooltips); @@ -211,9 +210,8 @@ public MenuType getMenuType() { } @Override - public boolean allowNbtUpdateAnimation(Player player, InteractionHand hand, ItemStack oldStack, - ItemStack newStack) { - return false; + public boolean shouldCauseReequipAnimation(ItemStack oldStack, ItemStack newStack, boolean slotChanged) { + return slotChanged; } @Nullable diff --git a/src/main/java/appeng/items/tools/powered/powersink/AEBasePoweredItem.java b/src/main/java/appeng/items/tools/powered/powersink/AEBasePoweredItem.java index 33e4f5187e7..20205cbb910 100644 --- a/src/main/java/appeng/items/tools/powered/powersink/AEBasePoweredItem.java +++ b/src/main/java/appeng/items/tools/powered/powersink/AEBasePoweredItem.java @@ -21,19 +21,17 @@ import java.util.List; import java.util.function.DoubleSupplier; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.Tag; import net.minecraft.network.chat.Component; import net.minecraft.util.Mth; -import net.minecraft.world.InteractionHand; -import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.CreativeModeTab; -import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.TooltipFlag; import net.minecraft.world.level.Level; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.api.distmarker.OnlyIn; +import net.neoforged.neoforge.common.capabilities.ICapabilityProvider; import appeng.api.config.AccessRestriction; import appeng.api.config.Actionable; @@ -48,12 +46,12 @@ public abstract class AEBasePoweredItem extends AEBaseItem implements IAEItemPow private static final String MAX_POWER_NBT_KEY = "internalMaxPower"; private final DoubleSupplier powerCapacity; - public AEBasePoweredItem(DoubleSupplier powerCapacity, Item.Properties props) { + public AEBasePoweredItem(DoubleSupplier powerCapacity, Properties props) { super(props); this.powerCapacity = powerCapacity; } - @Environment(EnvType.CLIENT) + @OnlyIn(Dist.CLIENT) @Override public void appendHoverText(ItemStack stack, Level level, List lines, TooltipFlag advancedTooltips) { @@ -85,8 +83,7 @@ public boolean isBarVisible(ItemStack stack) { } @Override - public boolean allowNbtUpdateAnimation(Player player, InteractionHand hand, ItemStack oldStack, - ItemStack newStack) { + public boolean shouldCauseReequipAnimation(ItemStack oldStack, ItemStack newStack, boolean slotChanged) { return getAECurrentPower(oldStack) == getAECurrentPower(newStack); } @@ -199,4 +196,8 @@ public AccessRestriction getPowerFlow(ItemStack is) { return AccessRestriction.WRITE; } + @Override + public ICapabilityProvider initCapabilities(ItemStack stack, CompoundTag nbt) { + return new PoweredItemCapabilities(stack, this); + } } diff --git a/src/main/java/appeng/items/tools/powered/powersink/PoweredItemCapabilities.java b/src/main/java/appeng/items/tools/powered/powersink/PoweredItemCapabilities.java index 63eb5432f62..3d688b7a121 100644 --- a/src/main/java/appeng/items/tools/powered/powersink/PoweredItemCapabilities.java +++ b/src/main/java/appeng/items/tools/powered/powersink/PoweredItemCapabilities.java @@ -18,72 +18,75 @@ package appeng.items.tools.powered.powersink; -import net.fabricmc.fabric.api.transfer.v1.context.ContainerItemContext; -import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant; -import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext; +import javax.annotation.Nullable; -import team.reborn.energy.api.EnergyStorage; +import net.minecraft.core.Direction; +import net.minecraft.world.item.ItemStack; +import net.neoforged.neoforge.common.capabilities.Capability; +import net.neoforged.neoforge.common.capabilities.ICapabilityProvider; +import net.neoforged.neoforge.common.util.LazyOptional; +import net.neoforged.neoforge.energy.IEnergyStorage; import appeng.api.config.Actionable; import appeng.api.config.PowerUnits; import appeng.api.implementations.items.IAEItemPowerStorage; +import appeng.capabilities.Capabilities; /** * The capability provider to expose chargable items to other mods. */ -public class PoweredItemCapabilities implements EnergyStorage { +class PoweredItemCapabilities implements ICapabilityProvider, IEnergyStorage { - private final ContainerItemContext context; + private final ItemStack is; - public PoweredItemCapabilities(ContainerItemContext context) { - this.context = context; + private final IAEItemPowerStorage item; + + PoweredItemCapabilities(ItemStack is, IAEItemPowerStorage item) { + this.is = is; + this.item = item; } + @SuppressWarnings("unchecked") @Override - public long insert(long maxReceive, TransactionContext transaction) { - var current = context.getItemVariant(); - if (current.getItem() instanceof IAEItemPowerStorage powerStorage) { - var is = current.toStack(); - - var convertedOffer = PowerUnits.TR.convertTo(PowerUnits.AE, maxReceive); - var overflow = powerStorage.injectAEPower(is, convertedOffer, Actionable.MODULATE); - long inserted = maxReceive - (long) PowerUnits.AE.convertTo(PowerUnits.TR, overflow); - - if (context.exchange(ItemVariant.of(is), 1, transaction) == 1) { - return inserted; - } + public LazyOptional getCapability(Capability capability, @Nullable Direction facing) { + if (capability == Capabilities.FORGE_ENERGY) { + return (LazyOptional) LazyOptional.of(() -> this); } + return LazyOptional.empty(); + } - return 0; + @Override + public int receiveEnergy(int maxReceive, boolean simulate) { + final double convertedOffer = PowerUnits.RF.convertTo(PowerUnits.AE, maxReceive); + final double overflow = this.item.injectAEPower(this.is, convertedOffer, + simulate ? Actionable.SIMULATE : Actionable.MODULATE); + + return maxReceive - (int) PowerUnits.AE.convertTo(PowerUnits.RF, overflow); } @Override - public long extract(long maxAmount, TransactionContext transaction) { + public int extractEnergy(int maxExtract, boolean simulate) { return 0; } @Override - public long getAmount() { - var current = context.getItemVariant(); - if (current.getItem() instanceof IAEItemPowerStorage powerStorage) { - return (long) PowerUnits.AE.convertTo(PowerUnits.TR, powerStorage.getAECurrentPower(current.toStack())); - } - - return 0; + public int getEnergyStored() { + return (int) PowerUnits.AE.convertTo(PowerUnits.RF, this.item.getAECurrentPower(this.is)); } @Override - public long getCapacity() { - var current = context.getItemVariant(); - if (current.getItem() instanceof IAEItemPowerStorage powerStorage) { - return (int) PowerUnits.AE.convertTo(PowerUnits.TR, powerStorage.getAEMaxPower(current.toStack())); - } - return 0; + public int getMaxEnergyStored() { + return (int) PowerUnits.AE.convertTo(PowerUnits.RF, this.item.getAEMaxPower(this.is)); } @Override - public boolean supportsExtraction() { + public boolean canExtract() { return false; } + @Override + public boolean canReceive() { + return true; + } + } diff --git a/src/main/java/appeng/items/tools/quartz/QuartzAxeItem.java b/src/main/java/appeng/items/tools/quartz/QuartzAxeItem.java index d06824c9fcc..6a092f1fc55 100644 --- a/src/main/java/appeng/items/tools/quartz/QuartzAxeItem.java +++ b/src/main/java/appeng/items/tools/quartz/QuartzAxeItem.java @@ -19,10 +19,9 @@ package appeng.items.tools.quartz; import net.minecraft.world.item.AxeItem; -import net.minecraft.world.item.Item; public class QuartzAxeItem extends AxeItem { - public QuartzAxeItem(Item.Properties props, QuartzToolType type) { + public QuartzAxeItem(Properties props, QuartzToolType type) { super(type.getToolTier(), 6.0F, -3.1F, props); } } diff --git a/src/main/java/appeng/items/tools/quartz/QuartzCuttingKnifeItem.java b/src/main/java/appeng/items/tools/quartz/QuartzCuttingKnifeItem.java index 671e0b7c9c9..c92c5e101ff 100644 --- a/src/main/java/appeng/items/tools/quartz/QuartzCuttingKnifeItem.java +++ b/src/main/java/appeng/items/tools/quartz/QuartzCuttingKnifeItem.java @@ -21,11 +21,11 @@ import org.jetbrains.annotations.Nullable; import net.minecraft.core.BlockPos; +import net.minecraft.util.RandomSource; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.InteractionResultHolder; import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.context.UseOnContext; import net.minecraft.world.level.Level; @@ -38,11 +38,10 @@ import appeng.menu.locator.MenuLocators; public class QuartzCuttingKnifeItem extends AEBaseItem implements IMenuItem { + private final RandomSource random = RandomSource.create(); - public QuartzCuttingKnifeItem(Item.Properties props, QuartzToolType type) { + public QuartzCuttingKnifeItem(Properties props, QuartzToolType type) { super(props); - // TODO FABRIC 117 This means knife doesnt lose durability when used in normal crafting - this.craftingRemainingItem = this; } @Override @@ -66,21 +65,20 @@ public InteractionResultHolder use(Level level, Player p, Interaction p.getItemInHand(hand)); } - // TODO FABRIC 117 recipe remainders -// @Override -// public ItemStack getContainerItem(ItemStack itemStack) { -// ItemStack damagedStack = itemStack.copy(); -// if (damagedStack.hurt(1, random, null)) { -// return ItemStack.EMPTY; -// } else { -// return damagedStack; -// } -// } -// -// @Override -// public boolean hasContainerItem(ItemStack stack) { -// return true; -// } + @Override + public ItemStack getCraftingRemainingItem(ItemStack itemStack) { + ItemStack damagedStack = itemStack.copy(); + if (damagedStack.hurt(1, random, null)) { + return ItemStack.EMPTY; + } else { + return damagedStack; + } + } + + @Override + public boolean hasCraftingRemainingItem(ItemStack stack) { + return true; + } @Nullable @Override diff --git a/src/main/java/appeng/items/tools/quartz/QuartzHoeItem.java b/src/main/java/appeng/items/tools/quartz/QuartzHoeItem.java index ae8258a61d5..6b99a29e9ba 100644 --- a/src/main/java/appeng/items/tools/quartz/QuartzHoeItem.java +++ b/src/main/java/appeng/items/tools/quartz/QuartzHoeItem.java @@ -19,10 +19,9 @@ package appeng.items.tools.quartz; import net.minecraft.world.item.HoeItem; -import net.minecraft.world.item.Item; public class QuartzHoeItem extends HoeItem { - public QuartzHoeItem(Item.Properties props, QuartzToolType type) { + public QuartzHoeItem(Properties props, QuartzToolType type) { super(type.getToolTier(), -2, -1.0F, props); } } diff --git a/src/main/java/appeng/items/tools/quartz/QuartzPickaxeItem.java b/src/main/java/appeng/items/tools/quartz/QuartzPickaxeItem.java index cc048af8f79..844b0649324 100644 --- a/src/main/java/appeng/items/tools/quartz/QuartzPickaxeItem.java +++ b/src/main/java/appeng/items/tools/quartz/QuartzPickaxeItem.java @@ -18,11 +18,10 @@ package appeng.items.tools.quartz; -import net.minecraft.world.item.Item; import net.minecraft.world.item.PickaxeItem; public class QuartzPickaxeItem extends PickaxeItem { - public QuartzPickaxeItem(Item.Properties props, QuartzToolType type) { + public QuartzPickaxeItem(Properties props, QuartzToolType type) { super(type.getToolTier(), 1, -2.8F, props); } } diff --git a/src/main/java/appeng/items/tools/quartz/QuartzSpadeItem.java b/src/main/java/appeng/items/tools/quartz/QuartzSpadeItem.java index c640bc2840b..52cb36c9ea5 100644 --- a/src/main/java/appeng/items/tools/quartz/QuartzSpadeItem.java +++ b/src/main/java/appeng/items/tools/quartz/QuartzSpadeItem.java @@ -18,11 +18,10 @@ package appeng.items.tools.quartz; -import net.minecraft.world.item.Item; import net.minecraft.world.item.ShovelItem; public class QuartzSpadeItem extends ShovelItem { - public QuartzSpadeItem(Item.Properties props, QuartzToolType type) { + public QuartzSpadeItem(Properties props, QuartzToolType type) { super(type.getToolTier(), 1.5F, -3.0F, props); } } diff --git a/src/main/java/appeng/items/tools/quartz/QuartzSwordItem.java b/src/main/java/appeng/items/tools/quartz/QuartzSwordItem.java index 804a0aca56b..e9aa00f8fef 100644 --- a/src/main/java/appeng/items/tools/quartz/QuartzSwordItem.java +++ b/src/main/java/appeng/items/tools/quartz/QuartzSwordItem.java @@ -18,11 +18,10 @@ package appeng.items.tools.quartz; -import net.minecraft.world.item.Item; import net.minecraft.world.item.SwordItem; public class QuartzSwordItem extends SwordItem { - public QuartzSwordItem(Item.Properties props, QuartzToolType type) { + public QuartzSwordItem(Properties props, QuartzToolType type) { super(type.getToolTier(), 3, -2.4F, props); } } diff --git a/src/main/java/appeng/items/tools/quartz/QuartzToolType.java b/src/main/java/appeng/items/tools/quartz/QuartzToolType.java index 72b97e0d6f8..0173655b249 100644 --- a/src/main/java/appeng/items/tools/quartz/QuartzToolType.java +++ b/src/main/java/appeng/items/tools/quartz/QuartzToolType.java @@ -32,9 +32,11 @@ public enum QuartzToolType { NETHER("nether_quartz", () -> Ingredient.of(ConventionTags.NETHER_QUARTZ)), ; + private final String name; private final Tier toolTier; QuartzToolType(String name, Supplier repairIngredient) { + this.name = name; this.toolTier = new Tier() { @Override public int getUses() { @@ -74,6 +76,10 @@ public String toString() { }; } + public String getName() { + return name; + } + public final Tier getToolTier() { return toolTier; } diff --git a/src/main/java/appeng/items/tools/quartz/QuartzWrenchItem.java b/src/main/java/appeng/items/tools/quartz/QuartzWrenchItem.java index 1e909ae08a1..ff771626f50 100644 --- a/src/main/java/appeng/items/tools/quartz/QuartzWrenchItem.java +++ b/src/main/java/appeng/items/tools/quartz/QuartzWrenchItem.java @@ -18,12 +18,10 @@ package appeng.items.tools.quartz; -import net.minecraft.world.item.Item; - import appeng.items.AEBaseItem; public class QuartzWrenchItem extends AEBaseItem { - public QuartzWrenchItem(Item.Properties props) { + public QuartzWrenchItem(Properties props) { super(props); } } diff --git a/src/main/java/appeng/me/GridNode.java b/src/main/java/appeng/me/GridNode.java index a2369d768dd..60ee5e410d7 100644 --- a/src/main/java/appeng/me/GridNode.java +++ b/src/main/java/appeng/me/GridNode.java @@ -234,7 +234,7 @@ protected final void updateState() { * security system. Called instead of loadFromNBT when initially placed, once set never required again, the value is * saved with the Node NBT. * - * @param ownerPlayerId ME player id of the owner. See {@link appeng.api.features.IPlayerRegistry}. + * @param ownerPlayerId ME player id of the owner. See {@link IPlayerRegistry}. */ public void setOwningPlayerId(int ownerPlayerId) { if (ownerPlayerId >= 0 && this.owningPlayerId != ownerPlayerId) { diff --git a/src/main/java/appeng/me/cluster/implementations/QuantumCluster.java b/src/main/java/appeng/me/cluster/implementations/QuantumCluster.java index 471719e77cb..17e204b7bcf 100644 --- a/src/main/java/appeng/me/cluster/implementations/QuantumCluster.java +++ b/src/main/java/appeng/me/cluster/implementations/QuantumCluster.java @@ -18,19 +18,16 @@ package appeng.me.cluster.implementations; -import java.util.ArrayList; -import java.util.HashSet; import java.util.Iterator; -import java.util.Set; import org.jetbrains.annotations.Nullable; -import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; -import net.fabricmc.fabric.api.event.lifecycle.v1.ServerWorldEvents; import net.minecraft.core.BlockPos; -import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.BlockEntity; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.level.LevelEvent; import appeng.api.features.Locatables; import appeng.api.networking.GridHelper; @@ -45,20 +42,6 @@ public class QuantumCluster implements IAECluster, IActionHost { - private static final Set ACTIVE_CLUSTERS = new HashSet<>(); - - static { - ServerLifecycleEvents.SERVER_STOPPED.register(server -> { - ACTIVE_CLUSTERS.clear(); - }); - ServerWorldEvents.UNLOAD.register((server, level) -> { - var iteration = new ArrayList<>(ACTIVE_CLUSTERS); - for (QuantumCluster activeCluster : iteration) { - activeCluster.onUnload(level); - } - }); - } - private final BlockPos boundsMin; private final BlockPos boundsMax; private boolean isDestroyed = false; @@ -76,8 +59,9 @@ public QuantumCluster(BlockPos min, BlockPos max) { this.setRing(new QuantumBridgeBlockEntity[8]); } - private void onUnload(ServerLevel level) { - if (this.center.getLevel() == level) { + @SubscribeEvent + public void onUnload(final LevelEvent.Unload e) { + if (this.center.getLevel() == e.getLevel()) { this.setUpdateStatus(false); this.destroy(); } @@ -218,7 +202,7 @@ public void destroy() { MBCalculator.setModificationInProgress(this); try { if (this.registered) { - ACTIVE_CLUSTERS.remove(this); + NeoForge.EVENT_BUS.unregister(this); this.registered = false; } @@ -261,7 +245,7 @@ public QuantumBridgeBlockEntity getCenter() { void setCenter(QuantumBridgeBlockEntity c) { this.registered = true; - ACTIVE_CLUSTERS.add(this); + NeoForge.EVENT_BUS.register(this); this.center = c; } diff --git a/src/main/java/appeng/me/energy/StoredEnergyAmount.java b/src/main/java/appeng/me/energy/StoredEnergyAmount.java index c2f7909299f..a687a7fc5a0 100644 --- a/src/main/java/appeng/me/energy/StoredEnergyAmount.java +++ b/src/main/java/appeng/me/energy/StoredEnergyAmount.java @@ -19,14 +19,12 @@ public final class StoredEnergyAmount { /** * If we store more energy than this, we are capable of providing energy to the grid. If we change from being below - * to being above the threshold, we emit a - * {@link appeng.api.networking.events.GridPowerStorageStateChanged.PowerEventType#PROVIDE_POWER} event. + * to being above the threshold, we emit a {@link GridPowerStorageStateChanged.PowerEventType#PROVIDE_POWER} event. */ private final double provideThreshold; /** * If we have more unfilled storage than this, we are capable of receiving energy. If we change from being below to - * being above this threshold, we emit a - * {@link appeng.api.networking.events.GridPowerStorageStateChanged.PowerEventType#RECEIVE_POWER} event. + * being above this threshold, we emit a {@link GridPowerStorageStateChanged.PowerEventType#RECEIVE_POWER} event. */ private final double receiveThreshold; private double maximum; diff --git a/src/main/java/appeng/me/helpers/BlockEntityNodeListener.java b/src/main/java/appeng/me/helpers/BlockEntityNodeListener.java index 7dd074cd151..b0f51870fa9 100644 --- a/src/main/java/appeng/me/helpers/BlockEntityNodeListener.java +++ b/src/main/java/appeng/me/helpers/BlockEntityNodeListener.java @@ -20,6 +20,7 @@ import appeng.api.networking.IGridNode; import appeng.api.networking.IGridNodeListener; +import appeng.api.networking.IGridNodeListener.State; /** * A simple node listener for {@link IGridConnectedBlockEntity} that host nodes and don't have special requirements. diff --git a/src/main/java/appeng/me/storage/ExternalInventoryCache.java b/src/main/java/appeng/me/storage/ExternalInventoryCache.java new file mode 100644 index 00000000000..a438f833b83 --- /dev/null +++ b/src/main/java/appeng/me/storage/ExternalInventoryCache.java @@ -0,0 +1,94 @@ +package appeng.me.storage; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import appeng.api.stacks.AEKey; +import appeng.api.stacks.GenericStack; +import appeng.api.stacks.KeyCounter; + +class ExternalInventoryCache { + private GenericStack[] cached = new GenericStack[0]; + private final ExternalStorageFacade facade; + + private ExternalInventoryCache(ExternalStorageFacade facade) { + this.facade = facade; + } + + public static ExternalInventoryCache of(ExternalStorageFacade facade) { + return new ExternalInventoryCache(facade); + } + + public void getAvailableItems(KeyCounter out) { + for (GenericStack stack : cached) { + out.add(stack.what(), stack.amount()); + } + } + + public Set update() { + var changes = new HashSet(); + final int slots = this.facade.getSlots(); + + // Make room for new slots + if (slots > this.cached.length) { + this.cached = Arrays.copyOf(this.cached, slots); + } + + for (int slot = 0; slot < slots; slot++) { + // Save the old stuff + var oldGenericStack = this.cached[slot]; + var newGenericStack = facade.getStackInSlot(slot); + + this.handlePossibleSlotChanges(slot, oldGenericStack, newGenericStack, changes); + } + + // Handle cases where the number of slots actually is lower now than before + if (slots < this.cached.length) { + for (int slot = slots; slot < this.cached.length; slot++) { + final GenericStack aeStack = this.cached[slot]; + + if (aeStack != null) { + changes.add(aeStack.what()); + } + } + + // Reduce the cache size + this.cached = Arrays.copyOf(this.cached, slots); + } + + return changes; + } + + private void handlePossibleSlotChanges(int slot, GenericStack oldStack, GenericStack newStack, Set changes) { + if (oldStack != null && newStack != null && oldStack.what().equals(newStack.what())) { + handleAmountChanged(slot, oldStack, newStack, changes); + } else { + handleItemChanged(slot, oldStack, newStack, changes); + } + } + + private void handleAmountChanged(int slot, GenericStack oldStack, GenericStack newStack, Set changes) { + // Still the same item, but amount might have changed + if (newStack.amount() != oldStack.amount()) { + this.cached[slot] = newStack; + changes.add(newStack.what()); + } + } + + private void handleItemChanged(int slot, GenericStack oldStack, GenericStack newStack, Set changes) { + // Completely different item + this.cached[slot] = newStack; + + // If we had a stack previously in this slot, notify the network about its disappearance + if (oldStack != null) { + changes.add(oldStack.what()); + } + + // Notify the network about the new stack + if (newStack != null) { + changes.add(newStack.what()); + } + } + +} diff --git a/src/main/java/appeng/me/storage/ExternalStorageFacade.java b/src/main/java/appeng/me/storage/ExternalStorageFacade.java new file mode 100644 index 00000000000..6b4d94794a7 --- /dev/null +++ b/src/main/java/appeng/me/storage/ExternalStorageFacade.java @@ -0,0 +1,345 @@ +package appeng.me.storage; + +import java.util.Set; + +import javax.annotation.Nullable; + +import com.google.common.primitives.Ints; + +import net.minecraft.network.chat.Component; +import net.minecraft.world.item.ItemStack; +import net.neoforged.neoforge.fluids.FluidStack; +import net.neoforged.neoforge.fluids.capability.IFluidHandler; +import net.neoforged.neoforge.items.IItemHandler; + +import appeng.api.config.Actionable; +import appeng.api.networking.security.IActionSource; +import appeng.api.stacks.AEFluidKey; +import appeng.api.stacks.AEItemKey; +import appeng.api.stacks.AEKey; +import appeng.api.stacks.AEKeyType; +import appeng.api.stacks.GenericStack; +import appeng.api.stacks.KeyCounter; +import appeng.api.storage.MEStorage; +import appeng.core.AELog; +import appeng.core.localization.GuiText; + +/** + * Adapts external platform storage to behave like an {@link MEStorage}. + */ +public abstract class ExternalStorageFacade implements MEStorage { + /** + * Clamp reported values to avoid overflows when amounts get too close to Long.MAX_VALUE. + */ + private static final long MAX_REPORTED_AMOUNT = 1L << 42; + + @Nullable + private Runnable changeListener; + + protected boolean extractableOnly; + + public void setChangeListener(@Nullable Runnable listener) { + this.changeListener = listener; + } + + public abstract int getSlots(); + + @Nullable + public abstract GenericStack getStackInSlot(int slot); + + public abstract AEKeyType getKeyType(); + + @Override + public long insert(AEKey what, long amount, Actionable mode, IActionSource source) { + var inserted = insertExternal(what, Ints.saturatedCast(amount), mode); + if (inserted > 0 && mode == Actionable.MODULATE) { + if (this.changeListener != null) { + this.changeListener.run(); + } + } + return inserted; + } + + @Override + public long extract(AEKey what, long amount, Actionable mode, IActionSource source) { + var extracted = extractExternal(what, Ints.saturatedCast(amount), mode); + if (extracted > 0 && mode == Actionable.MODULATE) { + if (this.changeListener != null) { + this.changeListener.run(); + } + } + return extracted; + } + + @Override + public Component getDescription() { + return GuiText.ExternalStorage.text(AEKeyType.fluids().getDescription()); + } + + protected abstract int insertExternal(AEKey what, int amount, Actionable mode); + + protected abstract int extractExternal(AEKey what, int amount, Actionable mode); + + public abstract boolean containsAnyFuzzy(Set keys); + + public static ExternalStorageFacade of(IFluidHandler handler) { + return new FluidHandlerFacade(handler); + } + + public static ExternalStorageFacade of(IItemHandler handler) { + return new ItemHandlerFacade(handler); + } + + public void setExtractableOnly(boolean extractableOnly) { + this.extractableOnly = extractableOnly; + } + + private static class ItemHandlerFacade extends ExternalStorageFacade { + private final IItemHandler handler; + + public ItemHandlerFacade(IItemHandler handler) { + this.handler = handler; + } + + @Override + public int getSlots() { + return handler.getSlots(); + } + + @Nullable + @Override + public GenericStack getStackInSlot(int slot) { + return GenericStack.fromItemStack(handler.getStackInSlot(slot)); + } + + @Override + public AEKeyType getKeyType() { + return AEKeyType.items(); + } + + @Override + public int insertExternal(AEKey what, int amount, Actionable mode) { + if (!(what instanceof AEItemKey itemKey)) { + return 0; + } + + ItemStack orgInput = itemKey.toStack(Ints.saturatedCast(amount)); + ItemStack remaining = orgInput; + + int slotCount = handler.getSlots(); + boolean simulate = mode == Actionable.SIMULATE; + + // This uses a brute force approach and tries to jam it in every slot the inventory exposes. + for (int i = 0; i < slotCount && !remaining.isEmpty(); i++) { + remaining = handler.insertItem(i, remaining, simulate); + } + + // At this point, we still have some items left... + if (remaining == orgInput) { + // The stack remained unmodified, target inventory is full + return 0; + } + + return amount - remaining.getCount(); + } + + @Override + public int extractExternal(AEKey what, int amount, Actionable mode) { + if (!(what instanceof AEItemKey itemKey)) { + return 0; + } + + int remainingSize = Ints.saturatedCast(amount); + + // Use this to gather the requested items + ItemStack gathered = ItemStack.EMPTY; + + final boolean simulate = mode == Actionable.SIMULATE; + + for (int i = 0; i < handler.getSlots(); i++) { + ItemStack stackInInventorySlot = handler.getStackInSlot(i); + + if (!itemKey.matches(stackInInventorySlot)) { + continue; + } + + ItemStack extracted; + int stackSizeCurrentSlot = stackInInventorySlot.getCount(); + int remainingCurrentSlot = Math.min(remainingSize, stackSizeCurrentSlot); + + // We have to loop here because according to the docs, the handler shouldn't return a stack with + // size > maxSize, even if we request more. So even if it returns a valid stack, it might have more + // stuff. + do { + extracted = handler.extractItem(i, remainingCurrentSlot, simulate); + if (!extracted.isEmpty()) { + // In order to guard against broken IItemHandler implementations, we'll try to guess if the + // returned + // stack (especially in simulate mode) is the same that was returned by getStackInSlot. This is + // obviously not a precise science, but it would catch the previous Forge bug: + // https://github.com/MinecraftForge/MinecraftForge/pull/6580 + if (extracted == stackInInventorySlot) { + extracted = extracted.copy(); + } + + if (extracted.getCount() > remainingCurrentSlot) { + // Something broke. It should never return more than we requested... + // We're going to silently eat the remainder + AELog.warn( + "Mod that provided item handler %s is broken. Returned %s items while only requesting %d.", + handler.getClass().getName(), extracted.toString(), remainingCurrentSlot); + extracted.setCount(remainingCurrentSlot); + } + + // Heuristic for simulation: looping in case of simulations is pointless, since the state of the + // underlying inventory does not change after a simulated extraction. To still support + // inventories + // that report stacks that are larger than maxStackSize, we use this heuristic + if (simulate && extracted.getCount() == extracted.getMaxStackSize() + && remainingCurrentSlot > extracted.getMaxStackSize()) { + extracted.setCount(remainingCurrentSlot); + } + + // We're just gonna use the first stack we get our hands on as the template for the rest. + if (gathered.isEmpty()) { + gathered = extracted; + } else { + gathered.grow(extracted.getCount()); + } + remainingCurrentSlot -= extracted.getCount(); + } + } while (!simulate && !extracted.isEmpty() && remainingCurrentSlot > 0); + + remainingSize -= stackSizeCurrentSlot - remainingCurrentSlot; + + // Done? + if (remainingSize <= 0) { + break; + } + } + + if (!gathered.isEmpty()) { + return gathered.getCount(); + } + + return 0; + } + + @Override + public boolean containsAnyFuzzy(Set keys) { + for (int i = 0; i < handler.getSlots(); i++) { + var what = AEItemKey.of(handler.getStackInSlot(i)); + if (what != null) { + if (keys.contains(what.dropSecondary())) { + return true; + } + } + } + return false; + } + + @Override + public void getAvailableStacks(KeyCounter out) { + for (int i = 0; i < handler.getSlots(); i++) { + // Skip resources that cannot be extracted if that filter was enabled + var stack = handler.getStackInSlot(i); + if (stack.isEmpty()) { + continue; + } + + if (extractableOnly) { + if (handler.extractItem(i, 1, true).isEmpty()) { + if (handler.extractItem(i, stack.getCount(), true).isEmpty()) { + continue; + } + } + } + + out.add(AEItemKey.of(stack), stack.getCount()); + } + } + } + + private static class FluidHandlerFacade extends ExternalStorageFacade { + private final IFluidHandler handler; + + public FluidHandlerFacade(IFluidHandler handler) { + this.handler = handler; + } + + @Override + public int getSlots() { + return handler.getTanks(); + } + + @Nullable + @Override + public GenericStack getStackInSlot(int slot) { + return GenericStack.fromFluidStack(handler.getFluidInTank(slot)); + } + + @Override + public AEKeyType getKeyType() { + return AEKeyType.fluids(); + } + + @Override + protected int insertExternal(AEKey what, int amount, Actionable mode) { + if (!(what instanceof AEFluidKey fluidKey)) { + return 0; + } + + return handler.fill(fluidKey.toStack(amount), mode.getFluidAction()); + } + + @Override + public int extractExternal(AEKey what, int amount, Actionable mode) { + if (!(what instanceof AEFluidKey fluidKey)) { + return 0; + } + + var fluidStack = fluidKey.toStack(Ints.saturatedCast(amount)); + + // Drain the fluid from the tank + FluidStack gathered = handler.drain(fluidStack, mode.getFluidAction()); + if (gathered.isEmpty()) { + // If nothing was pulled from the tank, return null + return 0; + } + + return gathered.getAmount(); + } + + @Override + public boolean containsAnyFuzzy(Set keys) { + for (int i = 0; i < handler.getTanks(); i++) { + var what = AEFluidKey.of(handler.getFluidInTank(i)); + if (what != null) { + if (keys.contains(what.dropSecondary())) { + return true; + } + } + } + return false; + } + + @Override + public void getAvailableStacks(KeyCounter out) { + for (int i = 0; i < handler.getTanks(); i++) { + // Skip resources that cannot be extracted if that filter was enabled + var stack = handler.getFluidInTank(i); + if (stack.isEmpty()) { + continue; + } + + if (extractableOnly) { + if (handler.drain(stack, IFluidHandler.FluidAction.SIMULATE).isEmpty()) { + continue; + } + } + + out.add(AEFluidKey.of(stack), stack.getAmount()); + } + } + } +} diff --git a/src/main/java/appeng/me/storage/StorageAdapter.java b/src/main/java/appeng/me/storage/StorageAdapter.java deleted file mode 100644 index 157c97d5c62..00000000000 --- a/src/main/java/appeng/me/storage/StorageAdapter.java +++ /dev/null @@ -1,140 +0,0 @@ -package appeng.me.storage; - -import java.util.function.Supplier; - -import org.jetbrains.annotations.Nullable; - -import net.fabricmc.fabric.api.transfer.v1.storage.Storage; -import net.fabricmc.fabric.api.transfer.v1.storage.TransferVariant; -import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction; -import net.minecraft.network.chat.Component; - -import appeng.api.config.Actionable; -import appeng.api.networking.security.IActionSource; -import appeng.api.stacks.AEKey; -import appeng.api.stacks.KeyCounter; -import appeng.api.storage.MEStorage; -import appeng.core.localization.GuiText; -import appeng.util.IVariantConversion; -import appeng.util.Platform; - -/** - * Adapts platform storage to {@link MEStorage} without monitoring capabilities. - */ -public class StorageAdapter> implements MEStorage { - /** - * Clamp reported values to avoid overflows when amounts get too close to Long.MAX_VALUE. - */ - private static final long MAX_REPORTED_AMOUNT = 1L << 42; - private final IVariantConversion conversion; - private boolean extractableOnly; - private final Supplier<@Nullable Storage> storageSupplier; - - public StorageAdapter(IVariantConversion conversion, Supplier<@Nullable Storage> storageSupplier) { - this.conversion = conversion; - this.storageSupplier = storageSupplier; - } - - public IVariantConversion getConversion() { - return conversion; - } - - public void setExtractableOnly(boolean extractableOnly) { - this.extractableOnly = extractableOnly; - } - - /** - * Called after successful inject or extract, use to schedule a cache rebuild (storage bus), or rebuild it directly - * (interface). - */ - protected void onInjectOrExtract() { - } - - @Override - public long insert(AEKey what, long amount, Actionable type, IActionSource src) { - var storage = this.storageSupplier.get(); - if (storage == null) { - return 0; - } - - var variant = conversion.getVariant(what); - if (variant.isBlank()) { - return 0; - } - - try (var tx = Platform.openOrJoinTx()) { - var inserted = storage.insert(variant, amount, tx); - - if (inserted > 0 && type == Actionable.MODULATE) { - tx.commit(); - this.onInjectOrExtract(); - } - - return inserted; - } - } - - @Override - public long extract(AEKey what, long amount, Actionable mode, IActionSource source) { - var storage = this.storageSupplier.get(); - if (storage == null) { - return 0; - } - - var variant = conversion.getVariant(what); - if (variant.isBlank()) { - return 0; - } - - try (var tx = Platform.openOrJoinTx()) { - var extracted = storage.extract(variant, amount, tx); - - if (extracted > 0 && mode == Actionable.MODULATE) { - tx.commit(); - this.onInjectOrExtract(); - } - - return extracted; - } - } - - @Override - public void getAvailableStacks(KeyCounter out) { - var storage = this.storageSupplier.get(); - if (storage != null) { - for (var view : storage) { - var resource = view.getResource(); - - if (resource.isBlank()) { - continue; - } - - // Skip resources that cannot be extracted if that filter was enabled - if (extractableOnly) { - try (var tx = Transaction.openOuter()) { - var extracted = view.extract(resource, 1, tx); - // If somehow extracting the minimal amount doesn't work, check if everything could be - // extracted because the tank might have a minimum (or fixed) allowed extraction amount. - // In addition, re-check if the resource is now blank since the inventory may have performed - // cleanup on our failed extraction attempt. - if (extracted == 0) { - extracted = view.extract(resource, view.getAmount(), tx); - } - if (extracted == 0) { - // We weren't able to simulate extraction of any fluid, so skip this one - continue; - } - } - } - - long amount = Math.min(view.getAmount(), MAX_REPORTED_AMOUNT); - out.add(conversion.getKey(resource), amount); - } - } - } - - @Override - public Component getDescription() { - return GuiText.ExternalStorage.text(conversion.getKeyType().getDescription()); - } -} diff --git a/src/main/java/appeng/menu/AEBaseMenu.java b/src/main/java/appeng/menu/AEBaseMenu.java index 093ce52466b..00b7f6e597d 100644 --- a/src/main/java/appeng/menu/AEBaseMenu.java +++ b/src/main/java/appeng/menu/AEBaseMenu.java @@ -324,7 +324,7 @@ private boolean isPlayerSideSlot(Slot slot) { || slotSemantic == SlotSemantics.CRAFTING_GRID; } - @org.jetbrains.annotations.Nullable + @Nullable public SlotSemantic getSlotSemantic(Slot s) { return semanticBySlot.get(s); } diff --git a/src/main/java/appeng/menu/implementations/IOBusMenu.java b/src/main/java/appeng/menu/implementations/IOBusMenu.java index 00636bdb82b..f87198cfd17 100644 --- a/src/main/java/appeng/menu/implementations/IOBusMenu.java +++ b/src/main/java/appeng/menu/implementations/IOBusMenu.java @@ -28,7 +28,7 @@ import appeng.parts.automation.ImportBusPart; /** - * Used for {@link ImportBusPart}, {@link appeng.parts.automation.ExportBusPart}. + * Used for {@link ImportBusPart}, {@link ExportBusPart}. * * @see IOBusScreen */ diff --git a/src/main/java/appeng/menu/implementations/MenuTypeBuilder.java b/src/main/java/appeng/menu/implementations/MenuTypeBuilder.java index 24ac572783c..2298900b645 100644 --- a/src/main/java/appeng/menu/implementations/MenuTypeBuilder.java +++ b/src/main/java/appeng/menu/implementations/MenuTypeBuilder.java @@ -24,19 +24,21 @@ import org.jetbrains.annotations.Nullable; -import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerFactory; -import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerType; import net.minecraft.client.Minecraft; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.chat.Component; import net.minecraft.network.protocol.game.ServerboundContainerClosePacket; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.MenuProvider; import net.minecraft.world.Nameable; +import net.minecraft.world.SimpleMenuProvider; import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Player; import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.inventory.MenuType; +import net.neoforged.neoforge.common.extensions.IMenuTypeExtension; +import net.neoforged.neoforge.network.NetworkHooks; import appeng.core.AppEng; import appeng.init.InitMenuTypes; @@ -147,61 +149,22 @@ private boolean open(Player player, MenuLocator locator, boolean fromSubMenu) { Component title = menuTitleStrategy.apply(accessInterface); - player.openMenu(new HandlerFactory(locator, title, accessInterface, initialDataSerializer, fromSubMenu)); - - return true; - } - - private class HandlerFactory implements ExtendedScreenHandlerFactory { - - private final MenuLocator locator; - - private final I accessInterface; - - private final Component title; - - private final InitialDataSerializer initialDataSerializer; - - private final boolean fromSubMenu; - - public HandlerFactory(MenuLocator locator, Component title, I accessInterface, - InitialDataSerializer initialDataSerializer, boolean fromSubMenu) { - this.locator = locator; - this.title = title; - this.accessInterface = accessInterface; - this.initialDataSerializer = initialDataSerializer; - this.fromSubMenu = fromSubMenu; - } - - @Override - public void writeScreenOpeningData(ServerPlayer player, FriendlyByteBuf buf) { - MenuLocators.writeToPacket(buf, locator); - buf.writeBoolean(fromSubMenu); - if (initialDataSerializer != null) { - initialDataSerializer.serializeInitialData(accessInterface, buf); - } - } - - @Override - public Component getDisplayName() { - return title; - } - - @Override - public boolean shouldCloseCurrentScreen() { - return false; // Stops the cursor from re-centering - } - - @Nullable - @Override - public AbstractContainerMenu createMenu(int wnd, Inventory inv, Player p) { - M m = factory.create(wnd, inv, accessInterface); + MenuProvider menu = new SimpleMenuProvider((wnd, p, pl) -> { + M m = factory.create(wnd, p, accessInterface); // Set the original locator on the opened server-side menu for it to more // easily remember how to re-open after being closed. m.setLocator(locator); return m; - } + }, title); + NetworkHooks.openScreen((ServerPlayer) player, menu, buffer -> { + MenuLocators.writeToPacket(buffer, locator); + buffer.writeBoolean(fromSubMenu); + if (initialDataSerializer != null) { + initialDataSerializer.serializeInitialData(accessInterface, buffer); + } + }); + return true; } /** @@ -212,7 +175,7 @@ public MenuType build(String id) { Preconditions.checkState(this.id == null, "id should not be set"); this.id = AppEng.makeId(id); - menuType = new ExtendedScreenHandlerType<>(this::fromNetwork); + menuType = IMenuTypeExtension.create(this::fromNetwork); InitMenuTypes.queueRegistration(this.id, menuType); MenuOpener.addOpener(menuType, this::open); return menuType; diff --git a/src/main/java/appeng/menu/implementations/QuartzKnifeMenu.java b/src/main/java/appeng/menu/implementations/QuartzKnifeMenu.java index efed77fe128..7467cdf37c1 100644 --- a/src/main/java/appeng/menu/implementations/QuartzKnifeMenu.java +++ b/src/main/java/appeng/menu/implementations/QuartzKnifeMenu.java @@ -23,6 +23,8 @@ import net.minecraft.world.entity.player.Player; import net.minecraft.world.inventory.MenuType; import net.minecraft.world.item.ItemStack; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.entity.player.PlayerDestroyItemEvent; import appeng.api.implementations.menuobjects.ItemMenuHost; import appeng.api.inventories.InternalInventory; @@ -128,8 +130,7 @@ private void makePlate() { Inventory playerInv = QuartzKnifeMenu.this.getPlayerInventory(); item.hurtAndBreak(1, playerInv.player, p -> { playerInv.setItem(playerInv.selected, ItemStack.EMPTY); - // FIXME FABRIC no such event - // MinecraftForge.EVENT_BUS.post(new PlayerDestroyItemEvent(playerInv.player, before, null)); + NeoForge.EVENT_BUS.post(new PlayerDestroyItemEvent(playerInv.player, before, null)); }); QuartzKnifeMenu.this.broadcastChanges(); diff --git a/src/main/java/appeng/menu/implementations/StorageBusMenu.java b/src/main/java/appeng/menu/implementations/StorageBusMenu.java index c8e17acf784..677a14e451e 100644 --- a/src/main/java/appeng/menu/implementations/StorageBusMenu.java +++ b/src/main/java/appeng/menu/implementations/StorageBusMenu.java @@ -44,7 +44,7 @@ /** * @see StorageBusScreen - * @see appeng.client.gui.implementations.StorageBusScreen + * @see StorageBusScreen */ public class StorageBusMenu extends UpgradeableMenu { diff --git a/src/main/java/appeng/menu/locator/MenuItemLocator.java b/src/main/java/appeng/menu/locator/MenuItemLocator.java index 1fc05d69417..43782e62b47 100644 --- a/src/main/java/appeng/menu/locator/MenuItemLocator.java +++ b/src/main/java/appeng/menu/locator/MenuItemLocator.java @@ -12,7 +12,7 @@ import appeng.core.AELog; /** - * Locates a menu host based on {@link appeng.api.implementations.menuobjects.IMenuItem} in the player inventory. + * Locates a menu host based on {@link IMenuItem} in the player inventory. *

* Optionally also contains a block position and side in case the menu is to be opened by the item but for a clicked * host (i.e. network tool). diff --git a/src/main/java/appeng/menu/me/common/MEStorageMenu.java b/src/main/java/appeng/menu/me/common/MEStorageMenu.java index faf7120222b..1e4443c877e 100644 --- a/src/main/java/appeng/menu/me/common/MEStorageMenu.java +++ b/src/main/java/appeng/menu/me/common/MEStorageMenu.java @@ -22,6 +22,7 @@ import java.util.Collections; import java.util.List; import java.util.Set; +import java.util.function.Predicate; import java.util.stream.Collectors; import com.google.common.base.Preconditions; @@ -573,7 +574,7 @@ protected void handleNetworkInteraction(ServerPlayer player, @Nullable AEKey cli } } - private void tryFillContainerItem(@org.jetbrains.annotations.Nullable AEKey clickedKey, boolean moveToPlayer) { + private void tryFillContainerItem(@Nullable AEKey clickedKey, boolean moveToPlayer) { // Special handling for fluids to facilitate filling water/lava buckets which are often // needed for crafting and placement in-world. boolean grabbedEmptyBucket = false; @@ -715,13 +716,15 @@ protected ItemStack transferStackToMenu(ItemStack input) { /** * Checks if the terminal has a given amount of the requested item. Used to determine for REI/JEI if a recipe is * potentially craftable based on the available items. + *

+ * This method is slow, but it is client-only and thus doesn't scale with the player count. */ - public boolean hasItemType(ItemStack itemStack, int amount) { + public boolean hasIngredient(Predicate ingredient, int amount) { var clientRepo = getClientRepo(); if (clientRepo != null) { for (var stack : clientRepo.getAllEntries()) { - if (AEItemKey.matches(stack.getWhat(), itemStack)) { + if (stack.getWhat() instanceof AEItemKey itemKey && ingredient.test(itemKey.toStack())) { if (stack.getStoredAmount() >= amount) { return true; } diff --git a/src/main/java/appeng/menu/me/items/CraftingTermMenu.java b/src/main/java/appeng/menu/me/items/CraftingTermMenu.java index 2b9ee809ec1..b8c4d841706 100644 --- a/src/main/java/appeng/menu/me/items/CraftingTermMenu.java +++ b/src/main/java/appeng/menu/me/items/CraftingTermMenu.java @@ -22,6 +22,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Predicate; import com.google.common.base.Preconditions; @@ -30,11 +31,11 @@ import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.inventory.CraftingContainer; import net.minecraft.world.inventory.MenuType; -import net.minecraft.world.inventory.Slot; import net.minecraft.world.inventory.TransientCraftingContainer; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.CraftingRecipe; import net.minecraft.world.item.crafting.Ingredient; -import net.minecraft.world.item.crafting.Recipe; +import net.minecraft.world.item.crafting.RecipeHolder; import net.minecraft.world.item.crafting.RecipeType; import net.minecraft.world.level.Level; @@ -47,6 +48,7 @@ import appeng.core.sync.network.NetworkHandler; import appeng.core.sync.packets.InventoryActionPacket; import appeng.helpers.IMenuCraftingPacket; +import appeng.helpers.IMenuCraftingPacket.AutoCraftEntry; import appeng.helpers.InventoryAction; import appeng.menu.SlotSemantics; import appeng.menu.implementations.MenuTypeBuilder; @@ -76,7 +78,7 @@ public class CraftingTermMenu extends MEStorageMenu implements IMenuCraftingPack private final CraftingContainer recipeTestContainer = new TransientCraftingContainer(this, 3, 3); private final CraftingTermSlot outputSlot; - private Recipe currentRecipe; + private RecipeHolder currentRecipe; public CraftingTermMenu(int id, Inventory ip, ITerminalHost host) { this(TYPE, id, ip, host, true); @@ -134,7 +136,7 @@ private void updateCurrentRecipeAndOutput(boolean forceUpdate) { if (this.currentRecipe == null) { this.outputSlot.set(ItemStack.EMPTY); } else { - this.outputSlot.set(this.currentRecipe.assemble(recipeTestContainer, level.registryAccess())); + this.outputSlot.set(this.currentRecipe.value().assemble(recipeTestContainer, level.registryAccess())); } } @@ -153,7 +155,7 @@ public void startAutoCrafting(List toCraft) { CraftConfirmMenu.openWithCraftingList(getActionHost(), (ServerPlayer) getPlayer(), getLocator(), toCraft); } - public Recipe getCurrentRecipe() { + public RecipeHolder getCurrentRecipe() { return this.currentRecipe; } @@ -168,21 +170,21 @@ public void clearCraftingGrid() { } @Override - public boolean hasItemType(ItemStack itemStack, int amount) { + public boolean hasIngredient(Predicate predicate, int amount) { // In addition to the base item repo, also check the crafting grid if it // already contains some of the needed items - for (Slot slot : getSlots(SlotSemantics.CRAFTING_GRID)) { - ItemStack stackInSlot = slot.getItem(); - if (!stackInSlot.isEmpty() && ItemStack.isSameItemSameTags(itemStack, stackInSlot)) { - if (itemStack.getCount() >= amount) { + for (var slot : getSlots(SlotSemantics.CRAFTING_GRID)) { + var stackInSlot = slot.getItem(); + if (!stackInSlot.isEmpty() && predicate.test(stackInSlot)) { + if (stackInSlot.getCount() >= amount) { return true; } - amount -= itemStack.getCount(); + amount -= stackInSlot.getCount(); } } - return super.hasItemType(itemStack, amount); + return super.hasIngredient(predicate, amount); } /** @@ -227,15 +229,11 @@ public MissingIngredientSlots findMissingIngredients(Map in // Then check the terminal screen's repository of network items if (!found) { - for (var stack : ingredient.getItems()) { - // We use AE stacks to get an easily comparable item type key that ignores stack size - var itemKey = AEItemKey.of(stack); - int reservedAmount = reservedGridAmounts.getOrDefault(itemKey, 0) + 1; - if (hasItemType(stack, reservedAmount)) { - reservedGridAmounts.put(itemKey, reservedAmount); - found = true; - break; - } + // We use AE stacks to get an easily comparable item type key that ignores stack size + int neededAmount = reservedGridAmounts.getOrDefault(ingredient, 0) + 1; + if (hasIngredient(ingredient, neededAmount)) { + reservedGridAmounts.put(ingredient, neededAmount); + found = true; } } diff --git a/src/main/java/appeng/menu/me/items/PatternEncodingTermMenu.java b/src/main/java/appeng/menu/me/items/PatternEncodingTermMenu.java index 310a72e5084..f524dec598c 100644 --- a/src/main/java/appeng/menu/me/items/PatternEncodingTermMenu.java +++ b/src/main/java/appeng/menu/me/items/PatternEncodingTermMenu.java @@ -35,6 +35,7 @@ import net.minecraft.world.inventory.TransientCraftingContainer; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.crafting.CraftingRecipe; +import net.minecraft.world.item.crafting.RecipeHolder; import net.minecraft.world.item.crafting.RecipeType; import net.minecraft.world.item.crafting.StonecutterRecipe; @@ -102,7 +103,7 @@ public class PatternEncodingTermMenu extends MEStorageMenu implements IMenuCraft private final ConfigInventory encodedInputsInv; private final ConfigInventory encodedOutputsInv; - private CraftingRecipe currentRecipe; + private RecipeHolder currentRecipe; // The current mode is essentially the last-known client-side version of mode private EncodingMode currentMode; @@ -116,7 +117,7 @@ public class PatternEncodingTermMenu extends MEStorageMenu implements IMenuCraft @Nullable public ResourceLocation stonecuttingRecipeId; - private final List stonecuttingRecipes = new ArrayList<>(); + private final List> stonecuttingRecipes = new ArrayList<>(); /** * Whether fluids can be substituted or not depends on the recipe. This set contains the slots of the crafting @@ -219,7 +220,7 @@ private ItemStack getAndUpdateOutput() { } } - if (this.currentRecipe == null || !this.currentRecipe.matches(ic, level)) { + if (this.currentRecipe == null || !this.currentRecipe.value().matches(ic, level)) { if (invalidIngredients) { this.currentRecipe = null; } else { @@ -234,7 +235,7 @@ private ItemStack getAndUpdateOutput() { if (this.currentRecipe == null) { is = ItemStack.EMPTY; } else { - is = this.currentRecipe.assemble(ic, level.registryAccess()); + is = this.currentRecipe.value().assemble(ic, level.registryAccess()); } this.craftOutputSlot.setDisplayedCraftingOutput(is); @@ -392,7 +393,7 @@ private ItemStack encodeSmithingTablePattern() { return null; } - var output = AEItemKey.of(recipe.assemble(container, level.registryAccess())); + var output = AEItemKey.of(recipe.value().assemble(container, level.registryAccess())); return PatternDetailsHelper.encodeSmithingTablePattern(recipe, template, base, addition, output, encodingLogic.isSubstitution()); @@ -421,7 +422,7 @@ private ItemStack encodeStonecuttingPattern() { return null; } - var output = AEItemKey.of(recipe.getResultItem(level.registryAccess())); + var output = AEItemKey.of(recipe.value().getResultItem(level.registryAccess())); return PatternDetailsHelper.encodeStonecuttingPattern(recipe, input, output, encodingLogic.isSubstitution()); } @@ -516,7 +517,7 @@ private void updateStonecuttingRecipes() { // Deselect a recipe that is now unavailable if (stonecuttingRecipeId != null - && stonecuttingRecipes.stream().noneMatch(r -> r.getId().equals(stonecuttingRecipeId))) { + && stonecuttingRecipes.stream().noneMatch(r -> r.id().equals(stonecuttingRecipeId))) { stonecuttingRecipeId = null; } } @@ -705,7 +706,7 @@ public boolean canCycleProcessingOutputs() { && Arrays.stream(processingOutputSlots).filter(s -> !s.getItem().isEmpty()).count() > 1; } - public List getStonecuttingRecipes() { + public List> getStonecuttingRecipes() { return stonecuttingRecipes; } diff --git a/src/main/java/appeng/menu/me/networktool/MachineGroup.java b/src/main/java/appeng/menu/me/networktool/MachineGroup.java index 1b2027fd847..06d5c9beb26 100644 --- a/src/main/java/appeng/menu/me/networktool/MachineGroup.java +++ b/src/main/java/appeng/menu/me/networktool/MachineGroup.java @@ -23,11 +23,13 @@ import appeng.api.stacks.AEItemKey; /** - * Represents the status of machines grouped by their item representation. + * Represents the status of machines grouped by their {@link IGridBlock#getMachineRepresentation() item representation}. */ public class MachineGroup { /** * The item stack used for grouping machines together, which is also used for showing the group in the UI. + * + * @see IGridBlock#getMachineRepresentation() */ private final AEItemKey display; diff --git a/src/main/java/appeng/menu/slot/AppEngCraftingSlot.java b/src/main/java/appeng/menu/slot/AppEngCraftingSlot.java index bf43d660cac..97238b9547e 100644 --- a/src/main/java/appeng/menu/slot/AppEngCraftingSlot.java +++ b/src/main/java/appeng/menu/slot/AppEngCraftingSlot.java @@ -25,6 +25,7 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.crafting.RecipeType; import net.minecraft.world.level.Level; +import net.neoforged.neoforge.common.CommonHooks; import appeng.api.inventories.InternalInventory; import appeng.crafting.CraftingEvent; @@ -83,8 +84,7 @@ public void onTake(Player playerIn, ItemStack stack) { CraftingEvent.fireCraftingEvent(playerIn, stack, this.craftingGrid.toContainer()); this.amountCrafted += stack.getCount(); this.checkTakeAchievements(stack); - // FIXME FABRIC no crafting hooks - // ForgeHooks.setCraftingPlayer(playerIn); + CommonHooks.setCraftingPlayer(playerIn); final CraftingContainer ic = new TransientCraftingContainer(this.getMenu(), 3, 3); for (int x = 0; x < this.craftingGrid.size(); x++) { @@ -95,8 +95,7 @@ public void onTake(Player playerIn, ItemStack stack) { Inventories.copy(ic, this.craftingGrid, false); - // FIXME FABRIC no crafting hooks - // ForgeHooks.setCraftingPlayer(null); + CommonHooks.setCraftingPlayer(null); for (int i = 0; i < aitemstack.size(); ++i) { final ItemStack itemstack1 = this.craftingGrid.getStackInSlot(i); @@ -140,7 +139,7 @@ public ItemStack remove(int par1) { // refactoring. protected NonNullList getRemainingItems(CraftingContainer ic, Level level) { return level.getRecipeManager().getRecipeFor(RecipeType.CRAFTING, ic, level) - .map(iCraftingRecipe -> iCraftingRecipe.getRemainingItems(ic)) + .map(recipe -> recipe.value().getRemainingItems(ic)) .orElse(NonNullList.withSize(9, ItemStack.EMPTY)); } } diff --git a/src/main/java/appeng/menu/slot/CraftingTermSlot.java b/src/main/java/appeng/menu/slot/CraftingTermSlot.java index e634bfc0799..97fc2140415 100644 --- a/src/main/java/appeng/menu/slot/CraftingTermSlot.java +++ b/src/main/java/appeng/menu/slot/CraftingTermSlot.java @@ -28,7 +28,8 @@ import net.minecraft.world.inventory.CraftingContainer; import net.minecraft.world.inventory.TransientCraftingContainer; import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.crafting.Recipe; +import net.minecraft.world.item.crafting.CraftingRecipe; +import net.minecraft.world.item.crafting.RecipeHolder; import net.minecraft.world.item.crafting.RecipeType; import net.minecraft.world.level.Level; @@ -148,11 +149,11 @@ public void doClick(InventoryAction action, Player who) { // TODO: This is really hacky and NEEDS to be solved with a full menu/gui // refactoring. - protected Recipe findRecipe(CraftingContainer ic, Level level) { + protected RecipeHolder findRecipe(CraftingContainer ic, Level level) { if (this.menu instanceof CraftingTermMenu terminalMenu) { var recipe = terminalMenu.getCurrentRecipe(); - if (recipe != null && recipe.matches(ic, level)) { + if (recipe != null && recipe.value().matches(ic, level)) { return terminalMenu.getCurrentRecipe(); } } @@ -167,8 +168,8 @@ protected NonNullList getRemainingItems(CraftingContainer ic, Level l if (this.menu instanceof CraftingTermMenu terminalMenu) { var recipe = terminalMenu.getCurrentRecipe(); - if (recipe != null && recipe.matches(ic, level)) { - return terminalMenu.getCurrentRecipe().getRemainingItems(ic); + if (recipe != null && recipe.value().matches(ic, level)) { + return terminalMenu.getCurrentRecipe().value().getRemainingItems(ic); } } @@ -220,13 +221,14 @@ private ItemStack craftItem(Player p, MEStorage inv, KeyCounter all) { return ItemStack.EMPTY; } - is = r.assemble(ic, level.registryAccess()); + is = r.value().assemble(ic, level.registryAccess()); if (inv != null) { var filter = ViewCellItem.createItemFilter(this.menu.getViewCells()); for (var x = 0; x < this.getPattern().size(); x++) { if (!this.getPattern().getStackInSlot(x).isEmpty()) { - set[x] = Platform.extractItemsByRecipe(this.energySrc, this.mySrc, inv, level, r, is, ic, + set[x] = Platform.extractItemsByRecipe(this.energySrc, this.mySrc, inv, level, r.value(), is, + ic, this.getPattern().getStackInSlot(x), x, all, Actionable.MODULATE, filter); ic.setItem(x, set[x]); diff --git a/src/main/java/appeng/menu/slot/IOptionalSlot.java b/src/main/java/appeng/menu/slot/IOptionalSlot.java index a71838ef1d3..85f69c26894 100644 --- a/src/main/java/appeng/menu/slot/IOptionalSlot.java +++ b/src/main/java/appeng/menu/slot/IOptionalSlot.java @@ -18,8 +18,8 @@ package appeng.menu.slot; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.api.distmarker.OnlyIn; import appeng.client.Point; @@ -35,7 +35,7 @@ default boolean isRenderDisabled() { boolean isSlotEnabled(); - @Environment(EnvType.CLIENT) + @OnlyIn(Dist.CLIENT) Point getBackgroundPos(); } diff --git a/src/main/java/appeng/menu/slot/MolecularAssemblerPatternSlot.java b/src/main/java/appeng/menu/slot/MolecularAssemblerPatternSlot.java index 0db9ca139f9..2c984725745 100644 --- a/src/main/java/appeng/menu/slot/MolecularAssemblerPatternSlot.java +++ b/src/main/java/appeng/menu/slot/MolecularAssemblerPatternSlot.java @@ -36,7 +36,7 @@ public MolecularAssemblerPatternSlot(MolecularAssemblerMenu mac, InternalInvento @Override public boolean mayPlace(ItemStack stack) { - return super.mayPlace(stack) && this.mac.isValidItemForSlot(this.slot, stack); + return super.mayPlace(stack) && this.mac.isValidItemForSlot(this.getSlotIndex(), stack); } @Override diff --git a/src/main/java/appeng/mixins/AnnihilationPlaneEnchantmentMixin.java b/src/main/java/appeng/mixins/AnnihilationPlaneEnchantmentMixin.java deleted file mode 100644 index 09ae91acc92..00000000000 --- a/src/main/java/appeng/mixins/AnnihilationPlaneEnchantmentMixin.java +++ /dev/null @@ -1,24 +0,0 @@ -package appeng.mixins; - -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -import net.minecraft.world.item.Item; - -import appeng.core.definitions.AEParts; - -/** - * Adds the ME Annihilation Plane to the items to check for when applying - * {@link net.minecraft.world.item.enchantment.EnchantmentCategory#DIGGER} enchantments. - */ -@Mixin(targets = "net/minecraft/world/item/enchantment/EnchantmentCategory$7") -public class AnnihilationPlaneEnchantmentMixin { - @Inject(method = "canEnchant", at = @At("RETURN"), cancellable = true) - public void enchantPlane(Item item, CallbackInfoReturnable cir) { - if (item == AEParts.ANNIHILATION_PLANE.asItem()) { - cir.setReturnValue(true); - } - } -} diff --git a/src/main/java/appeng/mixins/BlockBreakParticleMixin.java b/src/main/java/appeng/mixins/BlockBreakParticleMixin.java deleted file mode 100644 index 5b3d1ca0689..00000000000 --- a/src/main/java/appeng/mixins/BlockBreakParticleMixin.java +++ /dev/null @@ -1,24 +0,0 @@ -package appeng.mixins; - -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import org.spongepowered.asm.mixin.injection.callback.LocalCapture; - -import net.minecraft.client.Minecraft; -import net.minecraft.world.phys.BlockHitResult; - -/** - * Replicates Forge's addHitEffects patch. - */ -@Mixin(Minecraft.class) -@SuppressWarnings("ConstantConditions") -public class BlockBreakParticleMixin { - - @Inject(method = "continueAttack", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/particle/ParticleEngine;crack(Lnet/minecraft/core/BlockPos;Lnet/minecraft/core/Direction;)V"), cancellable = true, locals = LocalCapture.CAPTURE_FAILHARD) - public void customBreakEffect(boolean bl, CallbackInfo ci, BlockHitResult blockHitResult) { - var self = (Minecraft) (Object) this; - } - -} diff --git a/src/main/java/appeng/mixins/BreakSpeedMixin.java b/src/main/java/appeng/mixins/BreakSpeedMixin.java deleted file mode 100644 index ca2b1b1c425..00000000000 --- a/src/main/java/appeng/mixins/BreakSpeedMixin.java +++ /dev/null @@ -1,26 +0,0 @@ -package appeng.mixins; - -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.level.block.state.BlockState; - -import appeng.hooks.SkyStoneBreakSpeed; - -@Mixin(value = Player.class) -public class BreakSpeedMixin { - - @SuppressWarnings("ConstantConditions") - @Inject(method = "getDestroySpeed", at = @At("RETURN"), cancellable = true) - public void modifyBreakSpeed(BlockState blockState, CallbackInfoReturnable cri) { - var self = (Player) (Object) this; - var result = SkyStoneBreakSpeed.handleBreakFaster(self, blockState, cri.getReturnValue()); - if (result != null) { - cri.setReturnValue(result); - } - } - -} diff --git a/src/main/java/appeng/mixins/ConfigPlugin.java b/src/main/java/appeng/mixins/ConfigPlugin.java index 930dd289b3a..0cf6ff02899 100644 --- a/src/main/java/appeng/mixins/ConfigPlugin.java +++ b/src/main/java/appeng/mixins/ConfigPlugin.java @@ -7,7 +7,9 @@ import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; import org.spongepowered.asm.mixin.extensibility.IMixinInfo; -import net.fabricmc.loader.api.FabricLoader; +import net.neoforged.fml.ModList; +import net.neoforged.fml.loading.LoadingModList; +import net.neoforged.fml.loading.moddiscovery.ModInfo; /** * Disables the Create compatibility mixin if create isn't loaded. @@ -25,8 +27,7 @@ public String getRefMapperConfig() { @Override public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { if ("appeng.mixins.PonderWorldMixin".equals(mixinClassName)) { - var instance = FabricLoader.getInstance(); - return instance.isModLoaded("create"); + return isModLoaded("create"); } return true; } @@ -47,4 +48,11 @@ public void preApply(String targetClassName, ClassNode targetClass, String mixin @Override public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { } + + private static boolean isModLoaded(String modId) { + if (ModList.get() == null) { + return LoadingModList.get().getMods().stream().map(ModInfo::getModId).anyMatch(modId::equals); + } + return ModList.get().isLoaded(modId); + } } diff --git a/src/main/java/appeng/mixins/DynamicLadderMixin.java b/src/main/java/appeng/mixins/DynamicLadderMixin.java deleted file mode 100644 index 93ecec37378..00000000000 --- a/src/main/java/appeng/mixins/DynamicLadderMixin.java +++ /dev/null @@ -1,39 +0,0 @@ -package appeng.mixins; - -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -import net.minecraft.core.BlockPos; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.EntityType; -import net.minecraft.world.entity.LivingEntity; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.state.BlockState; - -import appeng.hooks.IDynamicLadder; - -@SuppressWarnings("ConstantConditions") -@Mixin(LivingEntity.class) -public abstract class DynamicLadderMixin extends Entity { - - protected DynamicLadderMixin(EntityType entityType, Level level) { - super(entityType, level); - } - - @Inject(method = "onClimbable", cancellable = true, at = @At("HEAD")) - public void onClimbable(CallbackInfoReturnable cri) { - if (this.isSpectator()) { - return; - } - - BlockPos blockPos = this.blockPosition(); - BlockState blockState = this.getFeetBlockState(); - - if (blockState.getBlock() instanceof IDynamicLadder dynamicLadder) { - cri.setReturnValue(dynamicLadder.isLadder(blockState, level(), blockPos, (LivingEntity) (Object) this)); - } - } - -} diff --git a/src/main/java/appeng/mixins/EarlyStartupMixin.java b/src/main/java/appeng/mixins/EarlyStartupMixin.java index 26314c3d4e8..9b45fb86fad 100644 --- a/src/main/java/appeng/mixins/EarlyStartupMixin.java +++ b/src/main/java/appeng/mixins/EarlyStartupMixin.java @@ -34,7 +34,9 @@ @Mixin(Bootstrap.class) public abstract class EarlyStartupMixin { - @Inject(at = @At("TAIL"), method = "bootStrap") + // Don't inject at TAIL because Citadel (possibly other mods too) cause bootStrap() to invoke itself, + // and we don't want this to be invoked from the nested call since MC isn't fully initialized by then. + @Inject(at = @At(value = "INVOKE", target = "net/neoforged/neoforge/registries/GameData.vanillaSnapshot()V", shift = At.Shift.AFTER, by = 1), method = "bootStrap") private static void initRegistries(CallbackInfo ci) { AppEngBootstrap.runEarlyStartup(); } diff --git a/src/main/java/appeng/mixins/ModelBakeryMixin.java b/src/main/java/appeng/mixins/ModelBakeryMixin.java new file mode 100644 index 00000000000..9bc1c211de9 --- /dev/null +++ b/src/main/java/appeng/mixins/ModelBakeryMixin.java @@ -0,0 +1,33 @@ +package appeng.mixins; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import net.minecraft.client.resources.model.ModelBakery; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.resources.ResourceLocation; + +import appeng.hooks.BuiltInModelHooks; + +/** + * Replicates the part of the Fabric API for adding built-in models that we actually use. + */ +@Mixin(ModelBakery.class) +public class ModelBakeryMixin { + @Inject(at = @At("HEAD"), method = "loadModel", cancellable = true) + private void loadModelHook(ResourceLocation id, CallbackInfo ci) { + var model = BuiltInModelHooks.getBuiltInModel(id); + + if (model != null) { + cacheAndQueueDependencies(id, model); + ci.cancel(); + } + } + + @Shadow + protected void cacheAndQueueDependencies(ResourceLocation id, UnbakedModel unbakedModel) { + } +} diff --git a/src/main/java/appeng/mixins/MouseWheelMixin.java b/src/main/java/appeng/mixins/MouseWheelMixin.java deleted file mode 100644 index 9820bd8ccf1..00000000000 --- a/src/main/java/appeng/mixins/MouseWheelMixin.java +++ /dev/null @@ -1,29 +0,0 @@ -package appeng.mixins; - -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import org.spongepowered.asm.mixin.injection.callback.LocalCapture; - -import net.minecraft.client.MouseHandler; - -import appeng.hooks.MouseWheelScrolled; - -/** - * Emulates the Forge MouseWheel-Event that is triggered outside of UIs - */ -@Mixin(MouseHandler.class) -public class MouseWheelMixin { - - /** - * Inject right before the slot-cycling that would normally be caused by the scroll-wheel - */ - @Inject(method = "onScroll", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Inventory;swapPaint(D)V"), cancellable = true, locals = LocalCapture.CAPTURE_FAILHARD) - public void onScrollWithoutScreen(long windowId, double x, double y, CallbackInfo ci, double verticalAmount) { - if (MouseWheelScrolled.EVENT.invoker().onWheelScrolled(verticalAmount)) { - ci.cancel(); - } - } - -} diff --git a/src/main/java/appeng/mixins/OnNeighborUpdateMixin.java b/src/main/java/appeng/mixins/OnNeighborUpdateMixin.java deleted file mode 100644 index b86d4abcec6..00000000000 --- a/src/main/java/appeng/mixins/OnNeighborUpdateMixin.java +++ /dev/null @@ -1,47 +0,0 @@ -package appeng.mixins; - -import java.util.Iterator; - -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import org.spongepowered.asm.mixin.injection.callback.LocalCapture; - -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.state.BlockState; - -import appeng.hooks.INeighborChangeSensitive; - -/** - * Replicates Forge's callback for non-comparators to get neighboring block changes similar to a comparator. - */ -@Mixin(Level.class) -public abstract class OnNeighborUpdateMixin { - - /** - * This targets the first getBlockState in the method and injects right after. We want to capture the return value, - * essentially so that we do not have to get the blockstate again. - */ - @SuppressWarnings("ConstantConditions") - @Inject(method = "updateNeighbourForOutputSignal", at = @At(value = "INVOKE_ASSIGN", ordinal = 0, target = "Lnet/minecraft/world/level/Level;getBlockState(Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/level/block/state/BlockState;"), locals = LocalCapture.CAPTURE_FAILHARD) - public void triggerOnNeighborChange(BlockPos srcPos, Block srcBlock, CallbackInfo ci, - // Iterator over all directions on the horizontal plane - Iterator it, - // Current direction of iterator it - Direction direction, - // Position derived from srcPos using direction - BlockPos blockPos, - // The block state @ blockPos - BlockState blockState) { - Level world = (Level) (Object) this; - Block block = blockState.getBlock(); - if (block instanceof INeighborChangeSensitive) { - ((INeighborChangeSensitive) block).onNeighborChange(blockState, world, blockPos, srcPos); - } - } - -} diff --git a/src/main/java/appeng/mixins/TextureAtlasMixin.java b/src/main/java/appeng/mixins/TextureAtlasMixin.java new file mode 100644 index 00000000000..3657c2b95e0 --- /dev/null +++ b/src/main/java/appeng/mixins/TextureAtlasMixin.java @@ -0,0 +1,56 @@ +package appeng.mixins; +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.util.Map; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import net.minecraft.client.renderer.texture.SpriteLoader; +import net.minecraft.client.renderer.texture.TextureAtlas; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.resources.ResourceLocation; + +import appeng.thirdparty.fabric.SpriteFinderImpl; + +@Mixin(TextureAtlas.class) +public class TextureAtlasMixin implements SpriteFinderImpl.SpriteFinderAccess { + @Shadow + private Map texturesByName; + + private SpriteFinderImpl fabric_spriteFinder = null; + + @Inject(at = @At("TAIL"), method = "upload") + private void uploadHook(SpriteLoader.Preparations input, CallbackInfo info) { + fabric_spriteFinder = null; + } + + @Override + public SpriteFinderImpl fabric_spriteFinder() { + SpriteFinderImpl result = fabric_spriteFinder; + + if (result == null) { + result = new SpriteFinderImpl(texturesByName, (TextureAtlas) (Object) this); + fabric_spriteFinder = result; + } + + return result; + } +} diff --git a/src/main/java/appeng/mixins/chunkloading/MinecraftServerMixin.java b/src/main/java/appeng/mixins/chunkloading/MinecraftServerMixin.java deleted file mode 100644 index 63ca616c4ce..00000000000 --- a/src/main/java/appeng/mixins/chunkloading/MinecraftServerMixin.java +++ /dev/null @@ -1,35 +0,0 @@ -package appeng.mixins.chunkloading; - -import java.util.Map; - -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import net.minecraft.resources.ResourceKey; -import net.minecraft.server.MinecraftServer; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.server.level.progress.ChunkProgressListener; -import net.minecraft.world.level.Level; - -import appeng.server.services.ChunkLoadingService; - -/** - * Notify spatial anchors of the chunks they are loading. - */ -@Mixin(MinecraftServer.class) -public class MinecraftServerMixin { - @Final - @Shadow - private Map, ServerLevel> levels; - - @Inject(method = "prepareLevels", at = @At("RETURN")) - private void validateForcedChunks(ChunkProgressListener chunkProgressListener, CallbackInfo ci) { - for (ServerLevel serverLevel : this.levels.values()) { - ChunkLoadingService.getInstance().validateTickets(serverLevel); - } - } -} diff --git a/src/main/java/appeng/mixins/chunkloading/ServerLevelMixin.java b/src/main/java/appeng/mixins/chunkloading/ServerLevelMixin.java deleted file mode 100644 index 1ab25450c5f..00000000000 --- a/src/main/java/appeng/mixins/chunkloading/ServerLevelMixin.java +++ /dev/null @@ -1,27 +0,0 @@ -package appeng.mixins.chunkloading; - -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -import net.minecraft.server.level.ServerLevel; - -import appeng.server.services.ChunkLoadingService; - -/** - * Prevent unforcing a chunk if we are still forcing it (e.g. because another chunk loader got removed). - */ -@Mixin(ServerLevel.class) -public class ServerLevelMixin { - @Inject(at = @At("HEAD"), method = "setChunkForced", cancellable = true) - public void preventChunkUnforcing(int chunkX, int chunkZ, boolean add, CallbackInfoReturnable cir) { - if (!add) { - // What if You wanted to un-forceload a chunk - if (ChunkLoadingService.getInstance().isChunkForced((ServerLevel) (Object) this, chunkX, chunkZ)) { - // But god said no - cir.setReturnValue(false); - } - } - } -} diff --git a/src/main/java/appeng/mixins/spatial/MinecraftServerMixin.java b/src/main/java/appeng/mixins/spatial/MinecraftServerMixin.java index f145aadf3e7..e9148e9b85e 100644 --- a/src/main/java/appeng/mixins/spatial/MinecraftServerMixin.java +++ b/src/main/java/appeng/mixins/spatial/MinecraftServerMixin.java @@ -11,7 +11,6 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import net.fabricmc.fabric.api.event.lifecycle.v1.ServerWorldEvents; import net.minecraft.core.LayeredRegistryAccess; import net.minecraft.core.registries.Registries; import net.minecraft.resources.ResourceKey; @@ -25,6 +24,8 @@ import net.minecraft.world.level.storage.DerivedLevelData; import net.minecraft.world.level.storage.LevelStorageSource; import net.minecraft.world.level.storage.WorldData; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.level.LevelEvent; import appeng.spatial.SpatialStorageChunkGenerator; import appeng.spatial.SpatialStorageDimensionIds; @@ -85,7 +86,7 @@ public void injectSpatialLevel(ChunkProgressListener chunkProgressListener, Call // NOTE: We don't register the spatial dimension for the world-border. Players can't move freely in that // dimension anyway. this.levels.put(SpatialStorageDimensionIds.WORLD_ID, level); - // Fabric hooks the "levels.put" call to emit the world load event. We have to trigger it manually here. - ServerWorldEvents.LOAD.invoker().onWorldLoad((MinecraftServer) (Object) this, level); + // Emulate the Forge world load event + NeoForge.EVENT_BUS.post(new LevelEvent.Load(level)); } } diff --git a/src/main/java/appeng/mixins/unlitquad/ModelManagerMixin.java b/src/main/java/appeng/mixins/unlitquad/ModelManagerMixin.java index 753b4b5913e..24f41c0b506 100644 --- a/src/main/java/appeng/mixins/unlitquad/ModelManagerMixin.java +++ b/src/main/java/appeng/mixins/unlitquad/ModelManagerMixin.java @@ -40,13 +40,13 @@ @Mixin(ModelManager.class) public class ModelManagerMixin { - @Inject(method = "method_45898", at = @At("HEAD"), allow = 1, remap = false) + @Inject(method = { "lambda$loadBlockModels$8", "m_246478_" }, at = @At("HEAD"), allow = 1) private static void onBeginLoadModel(Map.Entry entry, CallbackInfoReturnable> cri) { UnlitQuadHooks.beginDeserializingModel(entry.getKey()); } - @Inject(method = "method_45898", at = @At("RETURN"), remap = false) + @Inject(method = { "lambda$loadBlockModels$8", "m_246478_" }, at = @At("RETURN")) private static void onEndLoadModel(Map.Entry entry, CallbackInfoReturnable> cri) { UnlitQuadHooks.endDeserializingModel(); diff --git a/src/main/java/appeng/parts/AEBasePart.java b/src/main/java/appeng/parts/AEBasePart.java index 335f3c915a9..3ea6e67e622 100644 --- a/src/main/java/appeng/parts/AEBasePart.java +++ b/src/main/java/appeng/parts/AEBasePart.java @@ -48,6 +48,7 @@ import appeng.api.networking.GridHelper; import appeng.api.networking.IGridNode; import appeng.api.networking.IGridNodeListener; +import appeng.api.networking.IGridNodeListener.State; import appeng.api.networking.IManagedGridNode; import appeng.api.networking.security.IActionHost; import appeng.api.parts.IPart; @@ -96,9 +97,9 @@ protected IManagedGridNode createMainNode() { * @param reason Indicates which of the properties has changed. */ @MustBeInvokedByOverriders - protected void onMainNodeStateChanged(IGridNodeListener.State reason) { + protected void onMainNodeStateChanged(State reason) { // Client flags shouldn't depend on grid boot, optimize! - if (reason != IGridNodeListener.State.GRID_BOOT) { + if (reason != State.GRID_BOOT) { markForUpdateIfClientFlagsChanged(); } } diff --git a/src/main/java/appeng/parts/CableBusContainer.java b/src/main/java/appeng/parts/CableBusContainer.java index e86d893ff62..8a32b985c76 100644 --- a/src/main/java/appeng/parts/CableBusContainer.java +++ b/src/main/java/appeng/parts/CableBusContainer.java @@ -758,7 +758,7 @@ public boolean readFromStream(FriendlyByteBuf data) { return updateBlock; } - private static int getSideIndex(@org.jetbrains.annotations.Nullable Direction side) { + private static int getSideIndex(@Nullable Direction side) { return side == null ? 6 : side.ordinal(); } @@ -958,7 +958,7 @@ public CableBusRenderState getRenderState() { continue; } - renderState.getPartModelData().put(side, part.getRenderAttachmentData()); + renderState.getPartModelData().put(side, part.getModelData()); // This will add the part's bounding boxes to the render state, which is // required for facades diff --git a/src/main/java/appeng/parts/ICableBusContainer.java b/src/main/java/appeng/parts/ICableBusContainer.java index 6dcc39f4f8a..27c7aae10d1 100644 --- a/src/main/java/appeng/parts/ICableBusContainer.java +++ b/src/main/java/appeng/parts/ICableBusContainer.java @@ -18,8 +18,6 @@ package appeng.parts; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.util.RandomSource; @@ -30,6 +28,8 @@ import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.Level; import net.minecraft.world.phys.Vec3; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.api.distmarker.OnlyIn; import appeng.api.parts.SelectedPart; import appeng.api.util.AEColor; @@ -57,7 +57,7 @@ public interface ICableBusContainer { boolean isLadder(LivingEntity entity); - @Environment(EnvType.CLIENT) + @OnlyIn(Dist.CLIENT) void animateTick(Level level, BlockPos pos, RandomSource r); int getLightValue(); diff --git a/src/main/java/appeng/parts/PartAdjacentApi.java b/src/main/java/appeng/parts/PartAdjacentApi.java index d9365329829..affd04162aa 100644 --- a/src/main/java/appeng/parts/PartAdjacentApi.java +++ b/src/main/java/appeng/parts/PartAdjacentApi.java @@ -2,30 +2,27 @@ import org.jetbrains.annotations.Nullable; -import net.fabricmc.fabric.api.lookup.v1.block.BlockApiCache; -import net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup; -import net.minecraft.core.Direction; import net.minecraft.server.level.ServerLevel; +import net.neoforged.neoforge.common.capabilities.Capability; +import appeng.util.BlockApiCache; import appeng.util.Platform; /** * Utility class to cache an API that is adjacent to a part. - * - * @param */ -public class PartAdjacentApi { +public class PartAdjacentApi { private final AEBasePart part; - private final BlockApiLookup apiLookup; - private BlockApiCache apiCache; + private final Capability apiLookup; + private BlockApiCache apiCache; - public PartAdjacentApi(AEBasePart part, BlockApiLookup apiLookup) { + public PartAdjacentApi(AEBasePart part, Capability apiLookup) { this.apiLookup = apiLookup; this.part = part; } @Nullable - public A find() { + public C find() { if (!(part.getLevel() instanceof ServerLevel serverLevel)) { return null; } diff --git a/src/main/java/appeng/parts/automation/AnnihilationPlanePart.java b/src/main/java/appeng/parts/automation/AnnihilationPlanePart.java index 765a2664c88..de2c0f2df68 100644 --- a/src/main/java/appeng/parts/automation/AnnihilationPlanePart.java +++ b/src/main/java/appeng/parts/automation/AnnihilationPlanePart.java @@ -31,6 +31,7 @@ import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.enchantment.Enchantment; import net.minecraft.world.level.BlockGetter; +import net.neoforged.neoforge.client.model.data.ModelData; import appeng.api.behaviors.PickupStrategy; import appeng.api.config.Actionable; @@ -335,8 +336,10 @@ public IPartModel getStaticModels() { } @Override - public Object getRenderAttachmentData() { - return getConnections(); + public ModelData getModelData() { + return ModelData.builder() + .with(PlaneModelData.CONNECTIONS, getConnections()) + .build(); } private record ContinuousGeneration( diff --git a/src/main/java/appeng/parts/automation/AnnihilationPlanePartItem.java b/src/main/java/appeng/parts/automation/AnnihilationPlanePartItem.java index d37539bb076..6f8dab7dac9 100644 --- a/src/main/java/appeng/parts/automation/AnnihilationPlanePartItem.java +++ b/src/main/java/appeng/parts/automation/AnnihilationPlanePartItem.java @@ -5,8 +5,11 @@ import org.jetbrains.annotations.Nullable; import net.minecraft.network.chat.Component; +import net.minecraft.world.item.Item.Properties; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.item.enchantment.Enchantment; +import net.minecraft.world.item.enchantment.EnchantmentCategory; import net.minecraft.world.item.enchantment.EnchantmentHelper; import net.minecraft.world.level.Level; @@ -40,7 +43,17 @@ public int getEnchantmentValue() { } @Override - public int getMaxDamage() { + public boolean canApplyAtEnchantingTable(ItemStack stack, Enchantment enchantment) { + return enchantment.category == EnchantmentCategory.DIGGER; + } + + @Override + public boolean isBookEnchantable(ItemStack stack, ItemStack book) { + return true; + } + + @Override + public int getMaxDamage(ItemStack stack) { return CALLING_DAMAGEABLE_FROM_ANVIL.get() != null ? 1 : super.getMaxDamage(); } diff --git a/src/main/java/appeng/parts/automation/FabricExternalStorageStrategy.java b/src/main/java/appeng/parts/automation/FabricExternalStorageStrategy.java index bad567c737b..e69de29bb2d 100644 --- a/src/main/java/appeng/parts/automation/FabricExternalStorageStrategy.java +++ b/src/main/java/appeng/parts/automation/FabricExternalStorageStrategy.java @@ -1,71 +0,0 @@ -package appeng.parts.automation; - -import org.jetbrains.annotations.Nullable; - -import net.fabricmc.fabric.api.lookup.v1.block.BlockApiCache; -import net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup; -import net.fabricmc.fabric.api.transfer.v1.fluid.FluidStorage; -import net.fabricmc.fabric.api.transfer.v1.item.ItemStorage; -import net.fabricmc.fabric.api.transfer.v1.storage.Storage; -import net.fabricmc.fabric.api.transfer.v1.storage.TransferVariant; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.server.level.ServerLevel; - -import appeng.api.behaviors.ExternalStorageStrategy; -import appeng.api.storage.MEStorage; -import appeng.me.storage.StorageAdapter; -import appeng.util.IVariantConversion; - -public class FabricExternalStorageStrategy> implements ExternalStorageStrategy { - private final BlockApiCache, Direction> apiCache; - private final Direction fromSide; - private final IVariantConversion conversion; - - public FabricExternalStorageStrategy(BlockApiLookup, Direction> apiLookup, - IVariantConversion conversion, - ServerLevel level, - BlockPos fromPos, - Direction fromSide) { - this.apiCache = BlockApiCache.create(apiLookup, level, fromPos); - this.fromSide = fromSide; - this.conversion = conversion; - } - - @Nullable - @Override - public MEStorage createWrapper(boolean extractableOnly, Runnable injectOrExtractCallback) { - var storage = apiCache.find(fromSide); - if (storage == null) { - // If storage is absent, never query again until the next update. - return null; - } - - var result = new StorageAdapter<>(conversion, () -> apiCache.find(fromSide)) { - @Override - protected void onInjectOrExtract() { - injectOrExtractCallback.run(); - } - }; - result.setExtractableOnly(extractableOnly); - return result; - } - - public static ExternalStorageStrategy createItem(ServerLevel level, BlockPos fromPos, Direction fromSide) { - return new FabricExternalStorageStrategy<>( - ItemStorage.SIDED, - IVariantConversion.ITEM, - level, - fromPos, - fromSide); - } - - public static ExternalStorageStrategy createFluid(ServerLevel level, BlockPos fromPos, Direction fromSide) { - return new FabricExternalStorageStrategy<>( - FluidStorage.SIDED, - IVariantConversion.FLUID, - level, - fromPos, - fromSide); - } -} diff --git a/src/main/java/appeng/parts/automation/FluidPickupStrategy.java b/src/main/java/appeng/parts/automation/FluidPickupStrategy.java index 2401bc57905..012f52afda2 100644 --- a/src/main/java/appeng/parts/automation/FluidPickupStrategy.java +++ b/src/main/java/appeng/parts/automation/FluidPickupStrategy.java @@ -16,6 +16,7 @@ import appeng.api.behaviors.PickupSink; import appeng.api.behaviors.PickupStrategy; +import appeng.api.behaviors.PickupStrategy.Result; import appeng.api.config.Actionable; import appeng.api.ids.AETags; import appeng.api.networking.energy.IEnergySource; @@ -23,11 +24,14 @@ import appeng.core.AppEng; import appeng.core.sync.packets.BlockTransitionEffectPacket; import appeng.util.GenericContainerHelper; +import appeng.util.Platform; public class FluidPickupStrategy implements PickupStrategy { private final ServerLevel level; private final BlockPos pos; private final Direction side; + @Nullable + private final UUID owningPlayerId; /** * {@link System#currentTimeMillis()} of when the last sound/visual effect was played by this plane. @@ -39,6 +43,7 @@ public FluidPickupStrategy(ServerLevel level, BlockPos pos, Direction side, Bloc this.level = level; this.pos = pos; this.side = side; + this.owningPlayerId = owningPlayerId; } @Override @@ -74,7 +79,8 @@ public Result tryPickup(IEnergySource energySource, PickupSink sink) { // bucket // This _MIGHT_ change the liquid, and if it does, and we dont have enough // space, tough luck. you loose the source block. - var fluidContainer = bucketPickup.pickupBlock(level, pos, blockstate); + var fakePlayer = Platform.getFakePlayer(level, owningPlayerId); + var fluidContainer = bucketPickup.pickupBlock(fakePlayer, level, pos, blockstate); var pickedUpStack = GenericContainerHelper.getContainedFluidStack(fluidContainer); if (pickedUpStack != null && pickedUpStack.what() instanceof AEFluidKey fluidKey) { this.storeFluid(sink, fluidKey, pickedUpStack.amount(), true); diff --git a/src/main/java/appeng/parts/automation/FluidPlacementStrategy.java b/src/main/java/appeng/parts/automation/FluidPlacementStrategy.java index 3b5a8104b91..dc7ecbe8637 100644 --- a/src/main/java/appeng/parts/automation/FluidPlacementStrategy.java +++ b/src/main/java/appeng/parts/automation/FluidPlacementStrategy.java @@ -28,11 +28,14 @@ import appeng.api.config.Actionable; import appeng.api.stacks.AEFluidKey; import appeng.api.stacks.AEKey; +import appeng.util.Platform; public class FluidPlacementStrategy implements PlacementStrategy { private final ServerLevel level; private final BlockPos pos; private final Direction side; + @Nullable + private final UUID owningPlayerId; /** * The fluids that we tried to place unsuccessfully. */ @@ -47,6 +50,7 @@ public FluidPlacementStrategy(ServerLevel level, BlockPos pos, Direction side, B this.level = level; this.pos = pos; this.side = side; + this.owningPlayerId = owningPlayerId; } @Override @@ -143,7 +147,7 @@ private void playEvaporationEffect(Level level, BlockPos pos) { /** * Checks from {@link net.minecraft.world.item.BucketItem#emptyContents} */ - private boolean canPlace(Level level, BlockState state, BlockPos pos, Fluid fluid) { + private boolean canPlace(ServerLevel level, BlockState state, BlockPos pos, Fluid fluid) { if (!(fluid instanceof FlowingFluid)) { return false; } @@ -154,10 +158,20 @@ private boolean canPlace(Level level, BlockState state, BlockPos pos, Fluid flui return false; } - return state.isAir() - || state.canBeReplaced(fluid) - || state.getBlock() instanceof LiquidBlockContainer liquidBlockContainer - && liquidBlockContainer.canPlaceLiquid(level, pos, state, fluid); + if (state.isAir()) { + return true; + } + + if (state.canBeReplaced(fluid)) { + return true; + } + + if (state.getBlock() instanceof LiquidBlockContainer liquidBlockContainer) { + var fakePlayer = Platform.getFakePlayer(level, owningPlayerId); + return liquidBlockContainer.canPlaceLiquid(fakePlayer, level, pos, state, fluid); + } + + return false; } /** diff --git a/src/main/java/appeng/parts/automation/ForgeExternalStorageStrategy.java b/src/main/java/appeng/parts/automation/ForgeExternalStorageStrategy.java new file mode 100644 index 00000000000..ca6785d5b6e --- /dev/null +++ b/src/main/java/appeng/parts/automation/ForgeExternalStorageStrategy.java @@ -0,0 +1,61 @@ +package appeng.parts.automation; + +import org.jetbrains.annotations.Nullable; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.server.level.ServerLevel; +import net.neoforged.neoforge.common.capabilities.Capabilities; +import net.neoforged.neoforge.common.capabilities.Capability; + +import appeng.api.behaviors.ExternalStorageStrategy; +import appeng.api.storage.MEStorage; +import appeng.util.BlockApiCache; + +public class ForgeExternalStorageStrategy implements ExternalStorageStrategy { + private final BlockApiCache apiCache; + private final Direction fromSide; + private final HandlerStrategy conversion; + + public ForgeExternalStorageStrategy(Capability capability, + HandlerStrategy conversion, + ServerLevel level, + BlockPos fromPos, + Direction fromSide) { + this.apiCache = BlockApiCache.create(capability, level, fromPos); + this.fromSide = fromSide; + this.conversion = conversion; + } + + @Nullable + @Override + public MEStorage createWrapper(boolean extractableOnly, Runnable injectOrExtractCallback) { + var storage = apiCache.find(fromSide); + if (storage == null) { + return null; + } + + var result = conversion.getFacade(storage); + result.setChangeListener(injectOrExtractCallback); + result.setExtractableOnly(extractableOnly); + return result; + } + + public static ExternalStorageStrategy createItem(ServerLevel level, BlockPos fromPos, Direction fromSide) { + return new ForgeExternalStorageStrategy<>( + Capabilities.ITEM_HANDLER, + HandlerStrategy.ITEMS, + level, + fromPos, + fromSide); + } + + public static ExternalStorageStrategy createFluid(ServerLevel level, BlockPos fromPos, Direction fromSide) { + return new ForgeExternalStorageStrategy<>( + Capabilities.FLUID_HANDLER, + HandlerStrategy.FLUIDS, + level, + fromPos, + fromSide); + } +} diff --git a/src/main/java/appeng/parts/automation/FormationPlanePart.java b/src/main/java/appeng/parts/automation/FormationPlanePart.java index 714c6bccb07..e7187d8b6d6 100644 --- a/src/main/java/appeng/parts/automation/FormationPlanePart.java +++ b/src/main/java/appeng/parts/automation/FormationPlanePart.java @@ -32,6 +32,7 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.phys.Vec3; +import net.neoforged.neoforge.client.model.data.ModelData; import appeng.api.behaviors.PlacementStrategy; import appeng.api.config.Actionable; @@ -292,8 +293,10 @@ public IPartModel getStaticModels() { } @Override - public Object getRenderAttachmentData() { - return getConnections(); + public ModelData getModelData() { + return ModelData.builder() + .with(PlaneModelData.CONNECTIONS, getConnections()) + .build(); } } diff --git a/src/main/java/appeng/parts/automation/HandlerStrategy.java b/src/main/java/appeng/parts/automation/HandlerStrategy.java new file mode 100644 index 00000000000..030bba61e94 --- /dev/null +++ b/src/main/java/appeng/parts/automation/HandlerStrategy.java @@ -0,0 +1,105 @@ +package appeng.parts.automation; + +import javax.annotation.Nullable; + +import com.google.common.primitives.Ints; + +import net.minecraft.world.item.ItemStack; +import net.neoforged.neoforge.fluids.FluidStack; +import net.neoforged.neoforge.fluids.capability.IFluidHandler; +import net.neoforged.neoforge.items.IItemHandler; +import net.neoforged.neoforge.items.ItemHandlerHelper; + +import appeng.api.config.Actionable; +import appeng.api.stacks.AEFluidKey; +import appeng.api.stacks.AEItemKey; +import appeng.api.stacks.AEKey; +import appeng.api.stacks.AEKeyType; +import appeng.me.storage.ExternalStorageFacade; + +public abstract class HandlerStrategy { + private final AEKeyType keyType; + + public HandlerStrategy(AEKeyType keyType) { + this.keyType = keyType; + } + + public boolean isSupported(AEKey what) { + return what.getType() == keyType; + } + + public AEKeyType getKeyType() { + return keyType; + } + + public abstract ExternalStorageFacade getFacade(C handler); + + @Nullable + public abstract S getStack(AEKey what, long amount); + + public abstract long insert(C handler, AEKey what, long amount, Actionable mode); + + public static final HandlerStrategy ITEMS = new HandlerStrategy<>(AEKeyType.items()) { + @Override + public boolean isSupported(AEKey what) { + return AEItemKey.is(what); + } + + @Override + public ExternalStorageFacade getFacade(IItemHandler handler) { + return ExternalStorageFacade.of(handler); + } + + @Override + public long insert(IItemHandler handler, AEKey what, long amount, Actionable mode) { + if (what instanceof AEItemKey itemKey) { + var stack = itemKey.toStack(Ints.saturatedCast(amount)); + + var remainder = ItemHandlerHelper.insertItem(handler, stack, mode.isSimulate()); + return amount - remainder.getCount(); + } + + return 0; + } + + @org.jetbrains.annotations.Nullable + @Override + public ItemStack getStack(AEKey what, long amount) { + if (what instanceof AEItemKey itemKey) { + return itemKey.toStack(Ints.saturatedCast(amount)); + } + return null; + } + }; + + public static final HandlerStrategy FLUIDS = new HandlerStrategy<>(AEKeyType.fluids()) { + @Override + public boolean isSupported(AEKey what) { + return AEFluidKey.is(what); + } + + @Override + public ExternalStorageFacade getFacade(IFluidHandler handler) { + return ExternalStorageFacade.of(handler); + } + + @Override + public long insert(IFluidHandler handler, AEKey what, long amount, Actionable mode) { + if (what instanceof AEFluidKey itemKey && amount > 0) { + var stack = itemKey.toStack(Ints.saturatedCast(amount)); + return handler.fill(stack, mode.getFluidAction()); + } + + return 0; + } + + @Override + public FluidStack getStack(AEKey what, long amount) { + if (what instanceof AEFluidKey fluidKey) { + return fluidKey.toStack(Ints.saturatedCast(amount)); + } + return null; + } + }; + +} diff --git a/src/main/java/appeng/parts/automation/ItemPickupStrategy.java b/src/main/java/appeng/parts/automation/ItemPickupStrategy.java index 19ac6539bca..cff77752e28 100644 --- a/src/main/java/appeng/parts/automation/ItemPickupStrategy.java +++ b/src/main/java/appeng/parts/automation/ItemPickupStrategy.java @@ -30,6 +30,7 @@ import appeng.api.behaviors.PickupSink; import appeng.api.behaviors.PickupStrategy; +import appeng.api.behaviors.PickupStrategy.Result; import appeng.api.config.Actionable; import appeng.api.config.PowerMultiplier; import appeng.api.networking.energy.IEnergySource; diff --git a/src/main/java/appeng/parts/automation/PlaneBakedModel.java b/src/main/java/appeng/parts/automation/PlaneBakedModel.java index 34b37ad62bd..d62b927c0f1 100644 --- a/src/main/java/appeng/parts/automation/PlaneBakedModel.java +++ b/src/main/java/appeng/parts/automation/PlaneBakedModel.java @@ -18,56 +18,48 @@ package appeng.parts.automation; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.function.Supplier; + +import com.google.common.collect.ImmutableList; import org.jetbrains.annotations.Nullable; -import net.fabricmc.fabric.api.renderer.v1.Renderer; -import net.fabricmc.fabric.api.renderer.v1.RendererAccess; -import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh; -import net.fabricmc.fabric.api.renderer.v1.mesh.MeshBuilder; -import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; +import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.block.model.BakedQuad; import net.minecraft.client.renderer.block.model.ItemOverrides; -import net.minecraft.client.renderer.block.model.ItemTransforms; import net.minecraft.client.renderer.texture.TextureAtlasSprite; -import net.minecraft.client.resources.model.BakedModel; -import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.util.RandomSource; -import net.minecraft.world.level.BlockAndTintGetter; import net.minecraft.world.level.block.state.BlockState; +import net.neoforged.neoforge.client.model.IDynamicBakedModel; +import net.neoforged.neoforge.client.model.data.ModelData; -import appeng.api.inventories.IDynamicPartBakedModel; import appeng.client.render.cablebus.CubeBuilder; /** * Built-in model for annihilation planes that supports connected textures. */ -public class PlaneBakedModel implements BakedModel, IDynamicPartBakedModel { +public class PlaneBakedModel implements IDynamicBakedModel { private static final PlaneConnections DEFAULT_PERMUTATION = PlaneConnections.of(false, false, false, false); private final TextureAtlasSprite frontTexture; - private final Map meshes; + private final Map> quads; PlaneBakedModel(TextureAtlasSprite frontTexture, TextureAtlasSprite sidesTexture, TextureAtlasSprite backTexture) { this.frontTexture = frontTexture; - Renderer renderer = RendererAccess.INSTANCE.getRenderer(); - - meshes = new HashMap<>(PlaneConnections.PERMUTATIONS.size()); + quads = new HashMap<>(PlaneConnections.PERMUTATIONS.size()); // Create all possible permutations (16) for (PlaneConnections permutation : PlaneConnections.PERMUTATIONS) { + List quads = new ArrayList<>(4 * 6); - MeshBuilder meshBuilder = renderer.meshBuilder(); - - CubeBuilder builder = new CubeBuilder(meshBuilder.getEmitter()); + CubeBuilder builder = new CubeBuilder(quads); builder.setTextures(sidesTexture, sidesTexture, frontTexture, backTexture, sidesTexture, sidesTexture); @@ -81,31 +73,22 @@ public class PlaneBakedModel implements BakedModel, IDynamicPartBakedModel { builder.addCube(minX, minY, 0, maxX, maxY, 1); - this.meshes.put(permutation, meshBuilder.build()); + this.quads.put(permutation, ImmutableList.copyOf(quads)); } } @Override - public void emitQuads(BlockAndTintGetter blockView, BlockState state, BlockPos pos, - Supplier randomSupplier, - RenderContext context, Direction partSide, @Nullable Object modelData) { - PlaneConnections connections = DEFAULT_PERMUTATION; - - if (modelData instanceof PlaneConnections) { - connections = (PlaneConnections) modelData; + public List getQuads(@Nullable BlockState state, @Nullable Direction side, RandomSource rand, + ModelData modelData, RenderType renderType) { + if (side == null) { + PlaneConnections connections = DEFAULT_PERMUTATION; + if (modelData.has(PlaneModelData.CONNECTIONS)) { + connections = modelData.get(PlaneModelData.CONNECTIONS); + } + return this.quads.get(connections); + } else { + return Collections.emptyList(); } - - context.meshConsumer().accept(this.meshes.get(connections)); - } - - @Override - public ItemTransforms getTransforms() { - return ItemTransforms.NO_TRANSFORMS; - } - - @Override - public List getQuads(@Nullable BlockState state, @Nullable Direction side, RandomSource rand) { - return Collections.emptyList(); } @Override diff --git a/src/main/java/appeng/parts/automation/PlaneConnectionHelper.java b/src/main/java/appeng/parts/automation/PlaneConnectionHelper.java index 97b2d45fdc4..fe94714870f 100644 --- a/src/main/java/appeng/parts/automation/PlaneConnectionHelper.java +++ b/src/main/java/appeng/parts/automation/PlaneConnectionHelper.java @@ -148,7 +148,10 @@ public void getBoxes(IPartCollisionHelper bch) { * Call this when an adjacent block has changed since the connections need to be recalculated. */ public void updateConnections() { - // Not needed in Fabric, since model data is automatically updated + BlockEntity host = getHostBlockEntity(); + if (host != null) { + host.requestModelDataUpdate(); + } } private boolean isCompatiblePlaneAdjacent(@Nullable BlockEntity adjacentBlockEntity) { diff --git a/src/main/java/appeng/parts/reporting/ReportingModelData.java b/src/main/java/appeng/parts/automation/PlaneModelData.java similarity index 58% rename from src/main/java/appeng/parts/reporting/ReportingModelData.java rename to src/main/java/appeng/parts/automation/PlaneModelData.java index d9ff74f1d47..16c79e720a4 100644 --- a/src/main/java/appeng/parts/reporting/ReportingModelData.java +++ b/src/main/java/appeng/parts/automation/PlaneModelData.java @@ -16,35 +16,15 @@ * along with Applied Energistics 2. If not, see . */ -package appeng.parts.reporting; +package appeng.parts.automation; -public class ReportingModelData { +import net.neoforged.neoforge.client.model.data.ModelProperty; - private final byte spin; +public final class PlaneModelData { - public ReportingModelData(byte spin) { - this.spin = spin; - } - - public byte getSpin() { - return spin; - } - - @Override - public int hashCode() { - return Byte.hashCode(spin); - } + public static final ModelProperty CONNECTIONS = new ModelProperty<>(); - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - ReportingModelData that = (ReportingModelData) o; - return spin == that.spin; + private PlaneModelData() { } } diff --git a/src/main/java/appeng/parts/automation/StackWorldBehaviors.java b/src/main/java/appeng/parts/automation/StackWorldBehaviors.java index 984d920fb19..7e1d3d8f3e2 100644 --- a/src/main/java/appeng/parts/automation/StackWorldBehaviors.java +++ b/src/main/java/appeng/parts/automation/StackWorldBehaviors.java @@ -36,8 +36,8 @@ public final class StackWorldBehaviors { registerImportStrategy(AEKeyType.fluids(), StorageImportStrategy::createFluid); registerExportStrategy(AEKeyType.items(), StorageExportStrategy::createItem); registerExportStrategy(AEKeyType.fluids(), StorageExportStrategy::createFluid); - registerExternalStorageStrategy(AEKeyType.items(), FabricExternalStorageStrategy::createItem); - registerExternalStorageStrategy(AEKeyType.fluids(), FabricExternalStorageStrategy::createFluid); + registerExternalStorageStrategy(AEKeyType.items(), ForgeExternalStorageStrategy::createItem); + registerExternalStorageStrategy(AEKeyType.fluids(), ForgeExternalStorageStrategy::createFluid); registerPlacementStrategy(AEKeyType.fluids(), FluidPlacementStrategy::new); registerPlacementStrategy(AEKeyType.items(), ItemPlacementStrategy::new); registerPickupStrategy(AEKeyType.fluids(), FluidPickupStrategy::new); diff --git a/src/main/java/appeng/parts/automation/StorageExportStrategy.java b/src/main/java/appeng/parts/automation/StorageExportStrategy.java index dee0f519bce..0d7777b9d93 100644 --- a/src/main/java/appeng/parts/automation/StorageExportStrategy.java +++ b/src/main/java/appeng/parts/automation/StorageExportStrategy.java @@ -3,44 +3,38 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import net.fabricmc.fabric.api.lookup.v1.block.BlockApiCache; -import net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup; -import net.fabricmc.fabric.api.transfer.v1.fluid.FluidStorage; -import net.fabricmc.fabric.api.transfer.v1.item.ItemStorage; -import net.fabricmc.fabric.api.transfer.v1.storage.Storage; -import net.fabricmc.fabric.api.transfer.v1.storage.TransferVariant; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.server.level.ServerLevel; +import net.neoforged.neoforge.common.capabilities.Capabilities; +import net.neoforged.neoforge.common.capabilities.Capability; import appeng.api.behaviors.StackExportStrategy; import appeng.api.behaviors.StackTransferContext; import appeng.api.config.Actionable; import appeng.api.stacks.AEKey; import appeng.api.storage.StorageHelper; -import appeng.util.IVariantConversion; -import appeng.util.Platform; +import appeng.util.BlockApiCache; -public class StorageExportStrategy> implements StackExportStrategy { +public class StorageExportStrategy implements StackExportStrategy { private static final Logger LOGGER = LoggerFactory.getLogger(StorageExportStrategy.class); - private final BlockApiCache, Direction> apiCache; + private final BlockApiCache apiCache; private final Direction fromSide; - private final IVariantConversion conversion; + private final HandlerStrategy handlerStrategy; - public StorageExportStrategy(BlockApiLookup, Direction> apiLookup, - IVariantConversion conversion, + protected StorageExportStrategy(Capability apiLookup, + HandlerStrategy handlerStrategy, ServerLevel level, BlockPos fromPos, Direction fromSide) { + this.handlerStrategy = handlerStrategy; this.apiCache = BlockApiCache.create(apiLookup, level, fromPos); this.fromSide = fromSide; - this.conversion = conversion; } @Override public long transfer(StackTransferContext context, AEKey what, long amount) { - var variant = conversion.getVariant(what); - if (variant.isBlank()) { + if (!handlerStrategy.isSupported(what)) { return 0; } @@ -59,19 +53,9 @@ public long transfer(StackTransferContext context, AEKey what, long amount) { context.getActionSource(), Actionable.SIMULATE); - if (extracted <= 0) { - return 0; - } - - // Determine how much we're allowed to insert - long wasInserted; - try (var tx = Platform.openOrJoinTx()) { - wasInserted = adjacentStorage.insert(variant, extracted, tx); - } + long wasInserted = handlerStrategy.insert(adjacentStorage, what, extracted, Actionable.SIMULATE); if (wasInserted > 0) { - // Now *really* extract because the simulate may have returned more items than we can actually get - // (i.e. two storage buses on the same chest). extracted = StorageHelper.poweredExtraction( context.getEnergySource(), inv.getInventory(), @@ -80,10 +64,7 @@ public long transfer(StackTransferContext context, AEKey what, long amount) { context.getActionSource(), Actionable.MODULATE); - try (var tx = Platform.openOrJoinTx()) { - wasInserted = adjacentStorage.insert(variant, extracted, tx); - tx.commit(); - } + wasInserted = handlerStrategy.insert(adjacentStorage, what, extracted, Actionable.MODULATE); if (wasInserted < extracted) { // Be nice and try to give the overflow back @@ -101,8 +82,7 @@ public long transfer(StackTransferContext context, AEKey what, long amount) { @Override public long push(AEKey what, long amount, Actionable mode) { - var variant = conversion.getVariant(what); - if (variant.isBlank()) { + if (!handlerStrategy.isSupported(what)) { return 0; } @@ -111,25 +91,13 @@ public long push(AEKey what, long amount, Actionable mode) { return 0; } - try (var tx = Platform.openOrJoinTx()) { - long wasInserted = adjacentStorage.insert(variant, amount, tx); - - if (wasInserted > 0) { - if (mode == Actionable.MODULATE) { - tx.commit(); - } - - return wasInserted; - } - } - - return 0; + return handlerStrategy.insert(adjacentStorage, what, amount, mode); } public static StackExportStrategy createItem(ServerLevel level, BlockPos fromPos, Direction fromSide) { return new StorageExportStrategy<>( - ItemStorage.SIDED, - IVariantConversion.ITEM, + Capabilities.ITEM_HANDLER, + HandlerStrategy.ITEMS, level, fromPos, fromSide); @@ -137,8 +105,8 @@ public static StackExportStrategy createItem(ServerLevel level, BlockPos fromPos public static StackExportStrategy createFluid(ServerLevel level, BlockPos fromPos, Direction fromSide) { return new StorageExportStrategy<>( - FluidStorage.SIDED, - IVariantConversion.FLUID, + Capabilities.FLUID_HANDLER, + HandlerStrategy.FLUIDS, level, fromPos, fromSide); diff --git a/src/main/java/appeng/parts/automation/StorageImportStrategy.java b/src/main/java/appeng/parts/automation/StorageImportStrategy.java index 0bb1ce50f22..455960128f2 100644 --- a/src/main/java/appeng/parts/automation/StorageImportStrategy.java +++ b/src/main/java/appeng/parts/automation/StorageImportStrategy.java @@ -1,38 +1,32 @@ package appeng.parts.automation; -import net.fabricmc.fabric.api.lookup.v1.block.BlockApiCache; -import net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup; -import net.fabricmc.fabric.api.transfer.v1.fluid.FluidStorage; -import net.fabricmc.fabric.api.transfer.v1.item.ItemStorage; -import net.fabricmc.fabric.api.transfer.v1.storage.Storage; -import net.fabricmc.fabric.api.transfer.v1.storage.TransferVariant; -import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.server.level.ServerLevel; +import net.neoforged.neoforge.common.capabilities.Capabilities; +import net.neoforged.neoforge.common.capabilities.Capability; import appeng.api.behaviors.StackImportStrategy; import appeng.api.behaviors.StackTransferContext; import appeng.api.config.Actionable; -import appeng.api.stacks.AEKey; import appeng.core.AELog; -import appeng.util.IVariantConversion; +import appeng.util.BlockApiCache; /** * Strategy for efficiently importing stacks from external storage into an internal * {@link appeng.api.storage.MEStorage}. */ -public class StorageImportStrategy> implements StackImportStrategy { - private final BlockApiCache, Direction> apiCache; +public class StorageImportStrategy implements StackImportStrategy { + private final BlockApiCache apiCache; private final Direction fromSide; - private final IVariantConversion conversion; + private final HandlerStrategy conversion; - public StorageImportStrategy(BlockApiLookup, Direction> apiLookup, - IVariantConversion conversion, + public StorageImportStrategy(Capability capability, + HandlerStrategy conversion, ServerLevel level, BlockPos fromPos, Direction fromSide) { - this.apiCache = BlockApiCache.create(apiLookup, level, fromPos); + this.apiCache = BlockApiCache.create(capability, level, fromPos); this.fromSide = fromSide; this.conversion = conversion; } @@ -43,86 +37,64 @@ public boolean transfer(StackTransferContext context) { return false; } - var adjacentStorage = apiCache.find(fromSide); - if (adjacentStorage == null) { + var adjacentHandler = apiCache.find(fromSide); + if (adjacentHandler == null) { return false; } + var adjacentStorage = conversion.getFacade(adjacentHandler); + long remainingTransferAmount = context.getOperationsRemaining() * (long) conversion.getKeyType().getAmountPerOperation(); var inv = context.getInternalStorage(); - try (var tx = Transaction.openOuter()) { - - // Try to find an extractable resource that fits our filter, and if we've found at least one, - // continue until we've filled the desired amount per transfer - AEKey extractable = null; - long extractableAmount = 0; - for (var view : adjacentStorage) { - var resource = view.getResource(); - var resourceKey = conversion.getKey(resource); - if (resourceKey == null - // After the first extractable resource, we're just trying to get enough to fill our - // transfer quota. - || extractable != null && !extractable.equals(resourceKey) - // Regard a filter that is set on the bus - || context.isInFilter(resourceKey) == context.isInverted()) { - continue; - } - - // Check how much of *this* resource we can actually insert into the network, it might be 0 - // if the cells are partitioned or there's not enough types left, etc. - var amountForThisResource = inv.getInventory().insert(resourceKey, remainingTransferAmount, - Actionable.SIMULATE, - context.getActionSource()); - // Try to extract it - var amount = view.extract(resource, amountForThisResource, tx); - if (amount > 0) { - if (extractable != null) { - extractableAmount += amount; - } else { - extractable = resourceKey; - extractableAmount += amount; - } - remainingTransferAmount -= amount; - if (remainingTransferAmount <= 0) { - // We got enough to fill our transfer quota - break; - } - } + // Try to find an extractable resource that fits our filter + for (int i = 0; i < adjacentStorage.getSlots() && remainingTransferAmount > 0; i++) { + var resource = adjacentStorage.getStackInSlot(i); + if (resource == null + // Regard a filter that is set on the bus + || context.isInFilter(resource.what()) == context.isInverted()) { + continue; } - // We might have found nothing to extract - if (extractable == null) { - return false; - } + // Check how much of *this* resource we can actually insert into the network, it might be 0 + // if the cells are partitioned or there's not enough types left, etc. + var amountForThisResource = inv.getInventory().insert(resource.what(), remainingTransferAmount, + Actionable.SIMULATE, + context.getActionSource()); - var inserted = inv.getInventory().insert(extractable, extractableAmount, Actionable.MODULATE, + // Try to simulate-extract it + var amount = adjacentStorage.extract(resource.what(), amountForThisResource, Actionable.MODULATE, context.getActionSource()); + if (amount > 0) { + var inserted = inv.getInventory().insert(resource.what(), amount, Actionable.MODULATE, + context.getActionSource()); - if (inserted < extractableAmount) { - // Be nice and try to give the overflow back - long leftover = extractableAmount - inserted; - leftover -= adjacentStorage.insert(conversion.getVariant(extractable), leftover, tx); - if (leftover > 0) { - AELog.warn("Extracted %dx%s from adjacent storage and voided it because network refused insert", - leftover, extractable); + if (inserted < amount) { + // Be nice and try to give the overflow back + long leftover = amount - inserted; + leftover -= adjacentStorage.insert(resource.what(), leftover, Actionable.MODULATE, + context.getActionSource()); + if (leftover > 0) { + AELog.warn("Extracted %dx%s from adjacent storage and voided it because network refused insert", + leftover, resource.what()); + } } - } - var opsUsed = Math.max(1, inserted / conversion.getKeyType().getAmountPerOperation()); - context.reduceOperationsRemaining(opsUsed); - - tx.commit(); - return true; + var opsUsed = Math.max(1, inserted / conversion.getKeyType().getAmountPerOperation()); + context.reduceOperationsRemaining(opsUsed); + remainingTransferAmount -= inserted; + } } + + return false; } public static StackImportStrategy createItem(ServerLevel level, BlockPos fromPos, Direction fromSide) { return new StorageImportStrategy<>( - ItemStorage.SIDED, - IVariantConversion.ITEM, + Capabilities.ITEM_HANDLER, + HandlerStrategy.ITEMS, level, fromPos, fromSide); @@ -130,8 +102,8 @@ public static StackImportStrategy createItem(ServerLevel level, BlockPos fromPos public static StackImportStrategy createFluid(ServerLevel level, BlockPos fromPos, Direction fromSide) { return new StorageImportStrategy<>( - FluidStorage.SIDED, - IVariantConversion.FLUID, + Capabilities.FLUID_HANDLER, + HandlerStrategy.FLUIDS, level, fromPos, fromSide); diff --git a/src/main/java/appeng/parts/crafting/PatternProviderPart.java b/src/main/java/appeng/parts/crafting/PatternProviderPart.java index 53f42902347..8ae3491fdcd 100644 --- a/src/main/java/appeng/parts/crafting/PatternProviderPart.java +++ b/src/main/java/appeng/parts/crafting/PatternProviderPart.java @@ -32,6 +32,8 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.phys.Vec3; +import net.neoforged.neoforge.common.capabilities.Capability; +import net.neoforged.neoforge.common.util.LazyOptional; import appeng.api.networking.IGridNodeListener; import appeng.api.parts.IPartCollisionHelper; @@ -190,4 +192,9 @@ public IPartModel getStaticModels() { public ItemStack getMainMenuIcon() { return AEParts.PATTERN_PROVIDER.stack(); } + + @Override + public LazyOptional getCapability(Capability capabilityClass) { + return this.logic.getCapability(capabilityClass); + } } diff --git a/src/main/java/appeng/parts/encoding/PatternEncodingTerminalPart.java b/src/main/java/appeng/parts/encoding/PatternEncodingTerminalPart.java index 8cee00bd3cb..92ac32051fc 100644 --- a/src/main/java/appeng/parts/encoding/PatternEncodingTerminalPart.java +++ b/src/main/java/appeng/parts/encoding/PatternEncodingTerminalPart.java @@ -25,6 +25,9 @@ import net.minecraft.world.entity.player.Player; import net.minecraft.world.inventory.MenuType; import net.minecraft.world.item.ItemStack; +import net.neoforged.neoforge.common.capabilities.Capabilities; +import net.neoforged.neoforge.common.capabilities.Capability; +import net.neoforged.neoforge.common.util.LazyOptional; import appeng.api.parts.IPartItem; import appeng.api.parts.IPartModel; @@ -106,4 +109,12 @@ public PatternEncodingLogic getLogic() { public void markForSave() { getHost().markForSave(); } + + @Override + public LazyOptional getCapability(Capability cap) { + if (cap == Capabilities.ITEM_HANDLER) { + return LazyOptional.of(() -> logic.getBlankPatternInv().toItemHandler()).cast(); + } + return super.getCapability(cap); + } } diff --git a/src/main/java/appeng/parts/misc/InterfacePart.java b/src/main/java/appeng/parts/misc/InterfacePart.java index 6ca5218e20b..55898c38dc1 100644 --- a/src/main/java/appeng/parts/misc/InterfacePart.java +++ b/src/main/java/appeng/parts/misc/InterfacePart.java @@ -28,6 +28,8 @@ import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.phys.Vec3; +import net.neoforged.neoforge.common.capabilities.Capability; +import net.neoforged.neoforge.common.util.LazyOptional; import appeng.api.inventories.InternalInventory; import appeng.api.networking.GridHelper; @@ -51,7 +53,7 @@ public class InterfacePart extends AEBasePart implements InterfaceLogicHost { public static final ResourceLocation MODEL_BASE = new ResourceLocation(AppEng.MOD_ID, "part/interface_base"); - private static final IGridNodeListener NODE_LISTENER = new AEBasePart.NodeListener<>() { + private static final IGridNodeListener NODE_LISTENER = new NodeListener<>() { @Override public void onGridChanged(InterfacePart nodeOwner, IGridNode node) { super.onGridChanged(nodeOwner, node); @@ -186,4 +188,9 @@ public InternalInventory getSubInventory(ResourceLocation id) { public ItemStack getMainMenuIcon() { return new ItemStack(getPartItem()); } + + @Override + public LazyOptional getCapability(Capability capabilityClass) { + return this.logic.getCapability(capabilityClass, this.getSide()); + } } diff --git a/src/main/java/appeng/parts/networking/CablePart.java b/src/main/java/appeng/parts/networking/CablePart.java index 752b4f90881..0f689a7e816 100644 --- a/src/main/java/appeng/parts/networking/CablePart.java +++ b/src/main/java/appeng/parts/networking/CablePart.java @@ -54,6 +54,7 @@ import appeng.items.parts.ColoredPartItem; import appeng.items.tools.powered.ColorApplicatorItem; import appeng.parts.AEBasePart; +import appeng.parts.AEBasePart.NodeListener; public abstract class CablePart extends AEBasePart implements ICablePart { diff --git a/src/main/java/appeng/parts/networking/EnergyAcceptorPart.java b/src/main/java/appeng/parts/networking/EnergyAcceptorPart.java index 460a7bccaa0..fda9474644a 100644 --- a/src/main/java/appeng/parts/networking/EnergyAcceptorPart.java +++ b/src/main/java/appeng/parts/networking/EnergyAcceptorPart.java @@ -19,6 +19,8 @@ package appeng.parts.networking; import net.minecraft.resources.ResourceLocation; +import net.neoforged.neoforge.common.capabilities.Capability; +import net.neoforged.neoforge.common.util.LazyOptional; import appeng.api.config.AccessRestriction; import appeng.api.config.Actionable; @@ -29,6 +31,7 @@ import appeng.api.parts.IPartModel; import appeng.api.util.AECableType; import appeng.blockentity.powersink.IExternalPowerSink; +import appeng.capabilities.Capabilities; import appeng.core.AppEng; import appeng.helpers.ForgeEnergyAdapter; import appeng.items.parts.PartModels; @@ -39,16 +42,23 @@ public class EnergyAcceptorPart extends AEBasePart implements IExternalPowerSink @PartModels private static final IPartModel MODELS = new PartModel(new ResourceLocation(AppEng.MOD_ID, "part/energy_acceptor")); - private final ForgeEnergyAdapter energyAdapter; + private ForgeEnergyAdapter forgeEnergyAdapter; + private LazyOptional forgeEnergyAdapterOptional; public EnergyAcceptorPart(IPartItem partItem) { super(partItem); this.getMainNode().setIdlePowerUsage(0); - this.energyAdapter = new ForgeEnergyAdapter(this); + this.forgeEnergyAdapter = new ForgeEnergyAdapter(this); + this.forgeEnergyAdapterOptional = LazyOptional.of(() -> forgeEnergyAdapter); } - public ForgeEnergyAdapter getEnergyAdapter() { - return energyAdapter; + @Override + public LazyOptional getCapability(Capability capability) { + if (capability == Capabilities.FORGE_ENERGY) { + return (LazyOptional) this.forgeEnergyAdapterOptional; + } + + return super.getCapability(capability); } @Override diff --git a/src/main/java/appeng/parts/networking/QuartzFiberPart.java b/src/main/java/appeng/parts/networking/QuartzFiberPart.java index 52539fefdf3..07f6631f332 100644 --- a/src/main/java/appeng/parts/networking/QuartzFiberPart.java +++ b/src/main/java/appeng/parts/networking/QuartzFiberPart.java @@ -44,6 +44,7 @@ import appeng.me.energy.IEnergyOverlayGridConnection; import appeng.me.service.EnergyService; import appeng.parts.AEBasePart; +import appeng.parts.AEBasePart.NodeListener; import appeng.parts.PartModel; /** diff --git a/src/main/java/appeng/parts/p2p/CapabilityP2PTunnelPart.java b/src/main/java/appeng/parts/p2p/CapabilityP2PTunnelPart.java index 535a89a7846..aaaa883db08 100644 --- a/src/main/java/appeng/parts/p2p/CapabilityP2PTunnelPart.java +++ b/src/main/java/appeng/parts/p2p/CapabilityP2PTunnelPart.java @@ -18,21 +18,20 @@ package appeng.parts.p2p; -import net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup; import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; import net.minecraft.world.level.BlockGetter; +import net.neoforged.neoforge.common.capabilities.Capability; +import net.neoforged.neoforge.common.util.LazyOptional; import appeng.api.parts.IPartItem; import appeng.hooks.ticking.TickHandler; -import appeng.parts.PartAdjacentApi; /** * Base class for simple capability-based p2p tunnels. Don't forget to set the 3 handlers in the constructor of the * child class! */ public abstract class CapabilityP2PTunnelPart

, A> extends P2PTunnelPart

{ - private final PartAdjacentApi targetApiCache; + private final Capability capability; // Prevents recursive block updates. private boolean inBlockUpdate = false; // Prevents recursive access to the adjacent capability in case P2P input/output faces touch @@ -43,9 +42,9 @@ public abstract class CapabilityP2PTunnelPart

partItem, BlockApiLookup api) { + public CapabilityP2PTunnelPart(IPartItem partItem, Capability capability) { super(partItem); - this.targetApiCache = new PartAdjacentApi(this, api); + this.capability = capability; } @Override @@ -53,12 +52,15 @@ protected float getPowerDrainPerTick() { return 2.0f; } - public A getExposedApi() { - if (isOutput()) { - return outputHandler; - } else { - return inputHandler; + public final LazyOptional getCapability(Capability capabilityClass) { + if (capabilityClass == capability) { + if (isOutput()) { + return LazyOptional.of(() -> outputHandler).cast(); + } else { + return LazyOptional.of(() -> inputHandler).cast(); + } } + return LazyOptional.empty(); } /** @@ -90,9 +92,12 @@ public A get() { throw new IllegalStateException("get was called after closing the wrapper"); } else if (accessDepth == 1) { if (isActive()) { - var result = targetApiCache.find(); - if (result != null) { - return result; + var self = getBlockEntity(); + var te = self.getLevel().getBlockEntity(getFacingPos()); + + if (te != null) { + return te.getCapability(capability, getSide().getOpposite()) + .orElse(emptyHandler); } } diff --git a/src/main/java/appeng/parts/p2p/FEP2PTunnelPart.java b/src/main/java/appeng/parts/p2p/FEP2PTunnelPart.java index a926b82d432..4db99b46d33 100644 --- a/src/main/java/appeng/parts/p2p/FEP2PTunnelPart.java +++ b/src/main/java/appeng/parts/p2p/FEP2PTunnelPart.java @@ -20,19 +20,18 @@ import java.util.List; -import net.fabricmc.fabric.api.transfer.v1.storage.StoragePreconditions; -import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext; - -import team.reborn.energy.api.EnergyStorage; +import net.neoforged.neoforge.energy.IEnergyStorage; import appeng.api.config.PowerUnits; import appeng.api.parts.IPartItem; import appeng.api.parts.IPartModel; +import appeng.capabilities.Capabilities; import appeng.core.AppEng; import appeng.items.parts.PartModels; -public class FEP2PTunnelPart extends CapabilityP2PTunnelPart { +public class FEP2PTunnelPart extends CapabilityP2PTunnelPart { private static final P2PModels MODELS = new P2PModels(AppEng.makeId("part/p2p/p2p_tunnel_fe")); + private static final IEnergyStorage NULL_ENERGY_STORAGE = new NullEnergyStorage(); @PartModels public static List getModels() { @@ -40,10 +39,10 @@ public static List getModels() { } public FEP2PTunnelPart(IPartItem partItem) { - super(partItem, EnergyStorage.SIDED); + super(partItem, Capabilities.FORGE_ENERGY); inputHandler = new InputEnergyStorage(); outputHandler = new OutputEnergyStorage(); - emptyHandler = EnergyStorage.EMPTY; + emptyHandler = NULL_ENERGY_STORAGE; } @Override @@ -51,124 +50,156 @@ public IPartModel getStaticModels() { return MODELS.getModel(this.isPowered(), this.isActive()); } - private class InputEnergyStorage implements EnergyStorage { + private class InputEnergyStorage implements IEnergyStorage { @Override - public boolean supportsInsertion() { - for (var output : getOutputs()) { - try (var capabilityGuard = output.getAdjacentCapability()) { - if (capabilityGuard.get().supportsInsertion()) { - return true; - } - } - } - - return false; + public int extractEnergy(int maxExtract, boolean simulate) { + return 0; } @Override - public long insert(long maxAmount, TransactionContext transaction) { - StoragePreconditions.notNegative(maxAmount); - long total = 0; + public int receiveEnergy(int maxReceive, boolean simulate) { + int total = 0; - final int outputTunnels = getOutputs().size(); - final long amount = maxAmount; + final int outputTunnels = FEP2PTunnelPart.this.getOutputs().size(); - if (outputTunnels == 0 || amount == 0) { + if (outputTunnels == 0 | maxReceive == 0) { return 0; } - final long amountPerOutput = amount / outputTunnels; - long overflow = amountPerOutput == 0 ? amount : amount % amountPerOutput; + final int amountPerOutput = maxReceive / outputTunnels; + int overflow = amountPerOutput == 0 ? maxReceive : maxReceive % amountPerOutput; - for (var target : getOutputs()) { + for (FEP2PTunnelPart target : FEP2PTunnelPart.this.getOutputs()) { try (CapabilityGuard capabilityGuard = target.getAdjacentCapability()) { - final EnergyStorage output = capabilityGuard.get(); - final long toSend = amountPerOutput + overflow; - - final long received = output.insert(toSend, transaction); + final IEnergyStorage output = capabilityGuard.get(); + final int toSend = amountPerOutput + overflow; + final int received = output.receiveEnergy(toSend, simulate); overflow = toSend - received; total += received; } } - queueTunnelDrain(PowerUnits.TR, total, transaction); + if (!simulate) { + FEP2PTunnelPart.this.queueTunnelDrain(PowerUnits.RF, total); + } return total; } @Override - public boolean supportsExtraction() { + public boolean canExtract() { return false; } @Override - public long extract(long maxAmount, TransactionContext transaction) { - return 0; + public boolean canReceive() { + return true; } @Override - public long getAmount() { - long tot = 0; - for (var output : getOutputs()) { - try (var capabilityGuard = output.getAdjacentCapability()) { - tot += capabilityGuard.get().getAmount(); + public int getMaxEnergyStored() { + int total = 0; + + for (FEP2PTunnelPart t : FEP2PTunnelPart.this.getOutputs()) { + try (CapabilityGuard capabilityGuard = t.getAdjacentCapability()) { + total += capabilityGuard.get().getMaxEnergyStored(); } } - return tot; + + return total; } @Override - public long getCapacity() { - long tot = 0; - for (var output : getOutputs()) { - try (var capabilityGuard = output.getAdjacentCapability()) { - tot += capabilityGuard.get().getCapacity(); + public int getEnergyStored() { + int total = 0; + + for (FEP2PTunnelPart t : FEP2PTunnelPart.this.getOutputs()) { + try (CapabilityGuard capabilityGuard = t.getAdjacentCapability()) { + total += capabilityGuard.get().getEnergyStored(); } } - return tot; + + return total; } } - private class OutputEnergyStorage implements EnergyStorage { + private class OutputEnergyStorage implements IEnergyStorage { @Override - public boolean supportsInsertion() { - return false; + public int extractEnergy(int maxExtract, boolean simulate) { + try (CapabilityGuard input = getInputCapability()) { + final int total = input.get().extractEnergy(maxExtract, simulate); + + if (!simulate) { + FEP2PTunnelPart.this.queueTunnelDrain(PowerUnits.RF, total); + } + + return total; + } } @Override - public long insert(long maxAmount, TransactionContext transaction) { + public int receiveEnergy(int maxReceive, boolean simulate) { return 0; } @Override - public boolean supportsExtraction() { - try (var input = getInputCapability()) { - return input.get().supportsExtraction(); + public boolean canExtract() { + try (CapabilityGuard input = getInputCapability()) { + return input.get().canExtract(); } } @Override - public long extract(long maxAmount, TransactionContext transaction) { - try (var input = getInputCapability()) { - long extracted = input.get().extract(maxAmount, transaction); - queueTunnelDrain(PowerUnits.TR, extracted, transaction); - return extracted; - } + public boolean canReceive() { + return false; } @Override - public long getAmount() { - try (var input = getInputCapability()) { - return input.get().getAmount(); + public int getMaxEnergyStored() { + try (CapabilityGuard input = getInputCapability()) { + return input.get().getMaxEnergyStored(); } } @Override - public long getCapacity() { - try (var input = getInputCapability()) { - return input.get().getCapacity(); + public int getEnergyStored() { + try (CapabilityGuard input = getInputCapability()) { + return input.get().getEnergyStored(); } } } + + private static class NullEnergyStorage implements IEnergyStorage { + + @Override + public int receiveEnergy(int maxReceive, boolean simulate) { + return 0; + } + + @Override + public int extractEnergy(int maxExtract, boolean simulate) { + return 0; + } + + @Override + public int getEnergyStored() { + return 0; + } + + @Override + public int getMaxEnergyStored() { + return 0; + } + + @Override + public boolean canExtract() { + return false; + } + + @Override + public boolean canReceive() { + return false; + } + } } diff --git a/src/main/java/appeng/parts/p2p/FluidP2PTunnelPart.java b/src/main/java/appeng/parts/p2p/FluidP2PTunnelPart.java index a47e092a5cc..ac50a5de313 100644 --- a/src/main/java/appeng/parts/p2p/FluidP2PTunnelPart.java +++ b/src/main/java/appeng/parts/p2p/FluidP2PTunnelPart.java @@ -20,18 +20,21 @@ import java.util.List; -import net.fabricmc.fabric.api.transfer.v1.fluid.FluidStorage; -import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant; +import net.neoforged.neoforge.common.capabilities.Capabilities; +import net.neoforged.neoforge.fluids.FluidStack; +import net.neoforged.neoforge.fluids.capability.IFluidHandler; +import appeng.api.config.PowerUnits; import appeng.api.parts.IPartItem; import appeng.api.parts.IPartModel; import appeng.api.stacks.AEKeyType; import appeng.core.AppEng; import appeng.items.parts.PartModels; -public class FluidP2PTunnelPart extends StorageP2PTunnelPart { +public class FluidP2PTunnelPart extends CapabilityP2PTunnelPart { private static final P2PModels MODELS = new P2PModels(AppEng.makeId("part/p2p/p2p_tunnel_fluids")); + private static final IFluidHandler NULL_FLUID_HANDLER = new NullFluidHandler(); @PartModels public static List getModels() { @@ -39,11 +42,186 @@ public static List getModels() { } public FluidP2PTunnelPart(IPartItem partItem) { - super(partItem, FluidStorage.SIDED, AEKeyType.fluids()); + super(partItem, Capabilities.FLUID_HANDLER); + inputHandler = new InputFluidHandler(); + outputHandler = new OutputFluidHandler(); + emptyHandler = NULL_FLUID_HANDLER; } @Override public IPartModel getStaticModels() { return MODELS.getModel(this.isPowered(), this.isActive()); } + + private class InputFluidHandler implements IFluidHandler { + + @Override + public int getTanks() { + return 1; + } + + @Override + public FluidStack getFluidInTank(int tank) { + return FluidStack.EMPTY; + } + + @Override + public int getTankCapacity(int tank) { + return Integer.MAX_VALUE; + } + + @Override + public boolean isFluidValid(int tank, FluidStack stack) { + return true; + } + + @Override + public int fill(FluidStack resource, FluidAction action) { + int total = 0; + + final int outputTunnels = FluidP2PTunnelPart.this.getOutputs().size(); + final int amount = resource.getAmount(); + + if (outputTunnels == 0 || amount == 0) { + return 0; + } + + final int amountPerOutput = amount / outputTunnels; + int overflow = amountPerOutput == 0 ? amount : amount % amountPerOutput; + + for (FluidP2PTunnelPart target : FluidP2PTunnelPart.this.getOutputs()) { + try (CapabilityGuard capabilityGuard = target.getAdjacentCapability()) { + final IFluidHandler output = capabilityGuard.get(); + final int toSend = amountPerOutput + overflow; + final FluidStack fillWithFluidStack = resource.copy(); + fillWithFluidStack.setAmount(toSend); + + final int received = output.fill(fillWithFluidStack, action); + + overflow = toSend - received; + total += received; + } + } + + if (action == FluidAction.EXECUTE) { + FluidP2PTunnelPart.this.queueTunnelDrain(PowerUnits.RF, + (double) total / AEKeyType.fluids().getAmountPerOperation()); + } + + return total; + } + + @Override + public FluidStack drain(FluidStack resource, FluidAction action) { + return FluidStack.EMPTY; + } + + @Override + public FluidStack drain(int maxDrain, FluidAction action) { + return FluidStack.EMPTY; + } + + } + + private class OutputFluidHandler implements IFluidHandler { + @Override + public int getTanks() { + try (CapabilityGuard input = getInputCapability()) { + return input.get().getTanks(); + } + } + + @Override + public FluidStack getFluidInTank(int tank) { + try (CapabilityGuard input = getInputCapability()) { + return input.get().getFluidInTank(tank); + } + } + + @Override + public int getTankCapacity(int tank) { + try (CapabilityGuard input = getInputCapability()) { + return input.get().getTankCapacity(tank); + } + } + + @Override + public boolean isFluidValid(int tank, FluidStack stack) { + try (CapabilityGuard input = getInputCapability()) { + return input.get().isFluidValid(tank, stack); + } + } + + @Override + public int fill(FluidStack resource, FluidAction action) { + return 0; + } + + @Override + public FluidStack drain(FluidStack resource, FluidAction action) { + try (CapabilityGuard input = getInputCapability()) { + FluidStack result = input.get().drain(resource, action); + + if (action.execute()) { + queueTunnelDrain(PowerUnits.RF, + (double) result.getAmount() / AEKeyType.fluids().getAmountPerOperation()); + } + + return result; + } + } + + @Override + public FluidStack drain(int maxDrain, FluidAction action) { + try (CapabilityGuard input = getInputCapability()) { + FluidStack result = input.get().drain(maxDrain, action); + + if (action.execute()) { + queueTunnelDrain(PowerUnits.RF, + (double) result.getAmount() / AEKeyType.fluids().getAmountPerOperation()); + } + + return result; + } + } + } + + private static class NullFluidHandler implements IFluidHandler { + + @Override + public int getTanks() { + return 0; + } + + @Override + public FluidStack getFluidInTank(int tank) { + return FluidStack.EMPTY; + } + + @Override + public int getTankCapacity(int tank) { + return 0; + } + + @Override + public boolean isFluidValid(int tank, FluidStack stack) { + return false; + } + + @Override + public int fill(FluidStack resource, FluidAction action) { + return 0; + } + + @Override + public FluidStack drain(FluidStack resource, FluidAction action) { + return FluidStack.EMPTY; + } + + @Override + public FluidStack drain(int maxDrain, FluidAction action) { + return FluidStack.EMPTY; + } + } + } diff --git a/src/main/java/appeng/parts/p2p/ItemP2PTunnelPart.java b/src/main/java/appeng/parts/p2p/ItemP2PTunnelPart.java index d52dfee4f9b..8ae9225f002 100644 --- a/src/main/java/appeng/parts/p2p/ItemP2PTunnelPart.java +++ b/src/main/java/appeng/parts/p2p/ItemP2PTunnelPart.java @@ -20,18 +20,21 @@ import java.util.List; -import net.fabricmc.fabric.api.transfer.v1.item.ItemStorage; -import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant; +import net.minecraft.world.item.ItemStack; +import net.neoforged.neoforge.common.capabilities.Capabilities; +import net.neoforged.neoforge.items.IItemHandler; +import net.neoforged.neoforge.items.ItemHandlerHelper; +import appeng.api.config.PowerUnits; import appeng.api.parts.IPartItem; import appeng.api.parts.IPartModel; -import appeng.api.stacks.AEKeyType; import appeng.core.AppEng; import appeng.items.parts.PartModels; -public class ItemP2PTunnelPart extends StorageP2PTunnelPart { +public class ItemP2PTunnelPart extends CapabilityP2PTunnelPart { private static final P2PModels MODELS = new P2PModels(AppEng.makeId("part/p2p/p2p_tunnel_items")); + private static final IItemHandler NULL_ITEM_HANDLER = new NullItemHandler(); @PartModels public static List getModels() { @@ -39,11 +42,176 @@ public static List getModels() { } public ItemP2PTunnelPart(IPartItem partItem) { - super(partItem, ItemStorage.SIDED, AEKeyType.items()); + super(partItem, Capabilities.ITEM_HANDLER); + inputHandler = new InputItemHandler(); + outputHandler = new OutputItemHandler(); + emptyHandler = NULL_ITEM_HANDLER; } @Override public IPartModel getStaticModels() { return MODELS.getModel(this.isPowered(), this.isActive()); } + + private class InputItemHandler implements IItemHandler { + + @Override + public int getSlots() { + return 1; + } + + @Override + public ItemStack getStackInSlot(int slot) { + return ItemStack.EMPTY; + } + + @Override + public ItemStack insertItem(int slot, ItemStack stack, boolean simulate) { + int remainder = stack.getCount(); + + final int outputTunnels = ItemP2PTunnelPart.this.getOutputs().size(); + final int amount = stack.getCount(); + + if (outputTunnels == 0 || amount == 0) { + return stack; + } + + final int amountPerOutput = amount / outputTunnels; + int overflow = amountPerOutput == 0 ? amount : amount % amountPerOutput; + + for (ItemP2PTunnelPart target : ItemP2PTunnelPart.this.getOutputs()) { + try (CapabilityGuard capabilityGuard = target.getAdjacentCapability()) { + final IItemHandler output = capabilityGuard.get(); + final int toSend = amountPerOutput + overflow; + + if (toSend <= 0) { + // Both overflow and amountPerOutput are 0, so they will be for further outputs as well. + break; + } + + // So the documentation says that copying the stack should not be necessary because it is not + // supposed to be stored or modifed by insertItem. However, ItemStackHandler will gladly store + // the stack so we need to do a defensive copy. Forgecord says this is the intended behavior, + // and the documentation is wrong. + ItemStack stackCopy = stack.copy(); + stackCopy.setCount(toSend); + final int sent = toSend - ItemHandlerHelper.insertItem(output, stackCopy, simulate).getCount(); + + overflow = toSend - sent; + remainder -= sent; + } + } + + if (!simulate) { + ItemP2PTunnelPart.this.queueTunnelDrain(PowerUnits.RF, amount - remainder); + } + + if (remainder == stack.getCount()) { + return stack; + } else if (remainder == 0) { + return ItemStack.EMPTY; + } else { + ItemStack copy = stack.copy(); + copy.setCount(remainder); + return copy; + } + } + + @Override + public ItemStack extractItem(int slot, int amount, boolean simulate) { + return ItemStack.EMPTY; + } + + @Override + public int getSlotLimit(int slot) { + return Integer.MAX_VALUE; + } + + @Override + public boolean isItemValid(int slot, ItemStack stack) { + return true; + } + + } + + private class OutputItemHandler implements IItemHandler { + @Override + public int getSlots() { + try (CapabilityGuard input = getInputCapability()) { + return input.get().getSlots(); + } + } + + @Override + public ItemStack getStackInSlot(int slot) { + try (CapabilityGuard input = getInputCapability()) { + return input.get().getStackInSlot(slot); + } + } + + @Override + public ItemStack insertItem(int slot, ItemStack stack, boolean simulate) { + return stack; + } + + @Override + public ItemStack extractItem(int slot, int amount, boolean simulate) { + try (CapabilityGuard input = getInputCapability()) { + ItemStack result = input.get().extractItem(slot, amount, simulate); + + if (!simulate) { + queueTunnelDrain(PowerUnits.RF, result.getCount()); + } + + return result; + } + } + + @Override + public int getSlotLimit(int slot) { + try (CapabilityGuard input = getInputCapability()) { + return input.get().getSlotLimit(slot); + } + } + + @Override + public boolean isItemValid(int slot, ItemStack stack) { + try (CapabilityGuard input = getInputCapability()) { + return input.get().isItemValid(slot, stack); + } + } + } + + private static class NullItemHandler implements IItemHandler { + + @Override + public int getSlots() { + return 0; + } + + @Override + public ItemStack getStackInSlot(int slot) { + return ItemStack.EMPTY; + } + + @Override + public ItemStack insertItem(int slot, ItemStack stack, boolean simulate) { + return stack; + } + + @Override + public ItemStack extractItem(int slot, int amount, boolean simulate) { + return ItemStack.EMPTY; + } + + @Override + public int getSlotLimit(int slot) { + return 0; + } + + @Override + public boolean isItemValid(int slot, ItemStack stack) { + return false; + } + } } diff --git a/src/main/java/appeng/parts/p2p/MEP2PTunnelPart.java b/src/main/java/appeng/parts/p2p/MEP2PTunnelPart.java index a5eaf1e9c1d..617dbf65db2 100644 --- a/src/main/java/appeng/parts/p2p/MEP2PTunnelPart.java +++ b/src/main/java/appeng/parts/p2p/MEP2PTunnelPart.java @@ -44,6 +44,7 @@ import appeng.core.settings.TickRates; import appeng.hooks.ticking.TickHandler; import appeng.items.parts.PartModels; +import appeng.parts.AEBasePart.NodeListener; public class MEP2PTunnelPart extends P2PTunnelPart implements IGridTickable { diff --git a/src/main/java/appeng/parts/p2p/P2PTunnelPart.java b/src/main/java/appeng/parts/p2p/P2PTunnelPart.java index a6c1a336857..8995eda1b82 100644 --- a/src/main/java/appeng/parts/p2p/P2PTunnelPart.java +++ b/src/main/java/appeng/parts/p2p/P2PTunnelPart.java @@ -23,8 +23,6 @@ import org.jetbrains.annotations.Nullable; -import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext; -import net.fabricmc.fabric.api.transfer.v1.transaction.base.SnapshotParticipant; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.Tag; import net.minecraft.network.FriendlyByteBuf; @@ -33,6 +31,7 @@ import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.phys.Vec3; +import net.neoforged.neoforge.client.model.data.ModelData; import appeng.api.config.Actionable; import appeng.api.config.PowerMultiplier; @@ -45,6 +44,7 @@ import appeng.api.parts.IPartCollisionHelper; import appeng.api.parts.IPartItem; import appeng.api.util.AECableType; +import appeng.client.render.cablebus.P2PTunnelFrequencyModelData; import appeng.core.AEConfig; import appeng.me.service.P2PService; import appeng.parts.AEBasePart; @@ -57,7 +57,6 @@ public abstract class P2PTunnelPart> extends AEBasePa private boolean output; private short freq; - private final EnergyDrainHandler energyDrainHandler = new EnergyDrainHandler(); public P2PTunnelPart(IPartItem partItem) { super(partItem); @@ -298,13 +297,6 @@ public void onTunnelNetworkChange() { } - protected void queueTunnelDrain(PowerUnits unit, double f, TransactionContext transaction) { - final double ae_to_tax = unit.convertTo(PowerUnits.AE, f * AEConfig.TUNNEL_POWER_LOSS); - - energyDrainHandler.updateSnapshots(transaction); - energyDrainHandler.pendingEnergy += ae_to_tax; - } - protected void queueTunnelDrain(PowerUnits unit, double f) { final double ae_to_tax = unit.convertTo(PowerUnits.AE, f * AEConfig.TUNNEL_POWER_LOSS); @@ -336,37 +328,15 @@ void setOutput(boolean output) { } @Override - public Object getRenderAttachmentData() { + public ModelData getModelData() { long ret = Short.toUnsignedLong(this.getFrequency()); if (this.isActive() && this.isPowered()) { ret |= 0x10000L; } - return ret; - } - - private class EnergyDrainHandler extends SnapshotParticipant { - private double pendingEnergy; - - @Override - protected Double createSnapshot() { - return pendingEnergy; - } - - @Override - protected void readSnapshot(Double snapshot) { - pendingEnergy = snapshot; - } - - @Override - protected void onFinalCommit() { - if (pendingEnergy > 0) { - getMainNode().ifPresent(grid -> { - grid.getEnergyService().extractAEPower(pendingEnergy, Actionable.MODULATE, PowerMultiplier.ONE); - }); - pendingEnergy = 0; - } - } + return ModelData.builder() + .with(P2PTunnelFrequencyModelData.FREQUENCY, ret) + .build(); } } diff --git a/src/main/java/appeng/parts/p2p/StorageP2PTunnelPart.java b/src/main/java/appeng/parts/p2p/StorageP2PTunnelPart.java deleted file mode 100644 index 947fd7b1edf..00000000000 --- a/src/main/java/appeng/parts/p2p/StorageP2PTunnelPart.java +++ /dev/null @@ -1,142 +0,0 @@ -package appeng.parts.p2p; - -import java.util.Collections; -import java.util.Iterator; - -import com.google.common.collect.Iterators; - -import net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup; -import net.fabricmc.fabric.api.transfer.v1.storage.Storage; -import net.fabricmc.fabric.api.transfer.v1.storage.StoragePreconditions; -import net.fabricmc.fabric.api.transfer.v1.storage.StorageView; -import net.fabricmc.fabric.api.transfer.v1.storage.TransferVariant; -import net.fabricmc.fabric.api.transfer.v1.storage.base.ExtractionOnlyStorage; -import net.fabricmc.fabric.api.transfer.v1.storage.base.InsertionOnlyStorage; -import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext; -import net.minecraft.core.Direction; - -import appeng.api.config.PowerUnits; -import appeng.api.parts.IPartItem; -import appeng.api.stacks.AEKeyType; - -/** - * Base class for P2P tunnels that work with {@code Storage}. - */ -public abstract class StorageP2PTunnelPart

, T extends TransferVariant> - extends CapabilityP2PTunnelPart> { - - private AEKeyType keyType; - - public StorageP2PTunnelPart(IPartItem partItem, BlockApiLookup, Direction> api, AEKeyType keyType) { - super(partItem, api); - this.inputHandler = new InputStorage(); - this.outputHandler = new OutputStorage(); - this.emptyHandler = Storage.empty(); - this.keyType = keyType; - } - - private class InputStorage implements InsertionOnlyStorage { - @Override - public long insert(T resource, long maxAmount, TransactionContext transaction) { - StoragePreconditions.notBlankNotNegative(resource, maxAmount); - long total = 0; - - var outputs = getOutputs(); - final int outputTunnels = outputs.size(); - final long amount = maxAmount; - - if (outputTunnels == 0 || amount == 0) { - return 0; - } - - final long amountPerOutput = amount / outputTunnels; - long overflow = amountPerOutput == 0 ? amount : amount % amountPerOutput; - - for (var target : outputs) { - try (CapabilityGuard capabilityGuard = target.getAdjacentCapability()) { - final Storage output = capabilityGuard.get(); - final long toSend = amountPerOutput + overflow; - - final long received = output.insert(resource, toSend, transaction); - - overflow = toSend - received; - total += received; - } - } - - var energyDrain = ((double) total) / ((double) keyType.getAmountPerOperation()); - queueTunnelDrain(PowerUnits.AE, energyDrain, transaction); - - return total; - } - - @Override - public Iterator> iterator() { - return Collections.emptyIterator(); - } - } - - private class OutputStorage implements ExtractionOnlyStorage { - @Override - public long extract(T resource, long maxAmount, TransactionContext transaction) { - try (CapabilityGuard input = getInputCapability()) { - long extracted = input.get().extract(resource, maxAmount, transaction); - - var energyDrain = ((double) extracted) / ((double) keyType.getAmountPerOperation()); - queueTunnelDrain(PowerUnits.AE, energyDrain, transaction); - - return extracted; - } - } - - @Override - public Iterator> iterator() { - try (CapabilityGuard input = getInputCapability()) { - return Iterators.transform( - input.get().iterator(), - PowerDrainingStorageView::new); - } - } - } - - /** - * Queues power drain when resources are extracted through this. - */ - private class PowerDrainingStorageView implements StorageView { - private final StorageView delegate; - - public PowerDrainingStorageView(StorageView delegate) { - this.delegate = delegate; - } - - @Override - public long extract(T resource, long maxAmount, TransactionContext transaction) { - long extracted = delegate.extract(resource, maxAmount, transaction); - - var energyDrain = ((double) extracted) / ((double) keyType.getAmountPerOperation()); - queueTunnelDrain(PowerUnits.AE, energyDrain, transaction); - - return extracted; - } - - @Override - public boolean isResourceBlank() { - return delegate.isResourceBlank(); - } - - @Override - public T getResource() { - return delegate.getResource(); - } - - @Override - public long getAmount() { - return delegate.getAmount(); - } - - @Override - public long getCapacity() { - return delegate.getCapacity(); - } - } -} diff --git a/src/main/java/appeng/parts/reporting/AbstractMonitorPart.java b/src/main/java/appeng/parts/reporting/AbstractMonitorPart.java index f9d58f13d09..b9126d5f9a7 100644 --- a/src/main/java/appeng/parts/reporting/AbstractMonitorPart.java +++ b/src/main/java/appeng/parts/reporting/AbstractMonitorPart.java @@ -22,8 +22,6 @@ import org.jetbrains.annotations.Nullable; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.Tag; @@ -32,6 +30,8 @@ import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.context.UseOnContext; import net.minecraft.world.phys.Vec3; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.api.distmarker.OnlyIn; import appeng.api.behaviors.ContainerItemStrategies; import appeng.api.implementations.parts.IStorageMonitorPart; @@ -286,7 +286,7 @@ protected void updateReportingValue(IGrid grid) { } @Override - @Environment(EnvType.CLIENT) + @OnlyIn(Dist.CLIENT) public void renderDynamic(float partialTicks, PoseStack poseStack, MultiBufferSource buffers, int combinedLightIn, int combinedOverlayIn) { diff --git a/src/main/java/appeng/parts/reporting/AbstractReportingPart.java b/src/main/java/appeng/parts/reporting/AbstractReportingPart.java index 0ce21dccdd9..74c3eed8522 100644 --- a/src/main/java/appeng/parts/reporting/AbstractReportingPart.java +++ b/src/main/java/appeng/parts/reporting/AbstractReportingPart.java @@ -28,12 +28,14 @@ import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.Level; import net.minecraft.world.phys.Vec3; +import net.neoforged.neoforge.client.model.data.ModelData; import appeng.api.implementations.parts.IMonitorPart; import appeng.api.networking.GridFlags; import appeng.api.parts.IPartCollisionHelper; import appeng.api.parts.IPartItem; import appeng.api.parts.IPartModel; +import appeng.client.render.model.AEModelData; import appeng.parts.AEBasePart; import appeng.util.InteractionUtil; @@ -161,8 +163,10 @@ protected IPartModel selectModel(IPartModel offModels, IPartModel onModels, IPar } @Override - public Object getRenderAttachmentData() { - return new ReportingModelData(getSpin()); + public ModelData getModelData() { + return ModelData.builder() + .with(AEModelData.SPIN, getSpin()) + .build(); } public final byte getSpin() { diff --git a/src/main/java/appeng/parts/storagebus/StorageBusPart.java b/src/main/java/appeng/parts/storagebus/StorageBusPart.java index f67d4b1b7b7..4bef0305a3c 100644 --- a/src/main/java/appeng/parts/storagebus/StorageBusPart.java +++ b/src/main/java/appeng/parts/storagebus/StorageBusPart.java @@ -61,6 +61,7 @@ import appeng.api.storage.MEStorage; import appeng.api.util.AECableType; import appeng.api.util.IConfigManager; +import appeng.capabilities.Capabilities; import appeng.core.AppEng; import appeng.core.definitions.AEItems; import appeng.core.settings.TickRates; @@ -125,7 +126,7 @@ public class StorageBusPart extends UpgradeablePart public StorageBusPart(IPartItem partItem) { super(partItem); - this.adjacentStorageAccessor = new PartAdjacentApi<>(this, MEStorage.SIDED); + this.adjacentStorageAccessor = new PartAdjacentApi<>(this, Capabilities.STORAGE); this.getConfigManager().registerSetting(Settings.ACCESS, AccessRestriction.READ_WRITE); this.getConfigManager().registerSetting(Settings.FUZZY_MODE, FuzzyMode.IGNORE_ALL); this.getConfigManager().registerSetting(Settings.STORAGE_FILTER, StorageFilter.EXTRACTABLE_ONLY); diff --git a/src/main/java/appeng/recipes/entropy/EntropyRecipe.java b/src/main/java/appeng/recipes/entropy/EntropyRecipe.java index b8912b005e5..9e2956aaf0a 100644 --- a/src/main/java/appeng/recipes/entropy/EntropyRecipe.java +++ b/src/main/java/appeng/recipes/entropy/EntropyRecipe.java @@ -43,6 +43,7 @@ import net.minecraft.world.level.material.FluidState; import appeng.core.AppEng; +import appeng.init.InitRecipeTypes; import appeng.items.tools.powered.EntropyManipulatorItem; /** @@ -52,9 +53,7 @@ public class EntropyRecipe implements Recipe { public static final ResourceLocation TYPE_ID = AppEng.makeId("entropy"); - public static final RecipeType TYPE = RecipeType.register(TYPE_ID.toString()); - - private final ResourceLocation id; + public static final RecipeType TYPE = InitRecipeTypes.register(TYPE_ID.toString()); private final EntropyMode mode; @@ -81,14 +80,13 @@ public class EntropyRecipe implements Recipe { private final List drops; - public EntropyRecipe(ResourceLocation id, EntropyMode mode, Block inputBlock, List inputBlockMatchers, + public EntropyRecipe(EntropyMode mode, Block inputBlock, List inputBlockMatchers, Fluid inputFluid, List inputFluidMatchers, Block outputBlock, List> outputBlockStateAppliers, boolean outputBlockKeep, Fluid outputFluid, List> outputFluidStateAppliers, boolean outputFluidKeep, List drops) { Preconditions.checkArgument(inputBlock != null || inputFluid != null, "One of inputBlock or inputFluid must not be null"); - this.id = Objects.requireNonNull(id, "id must not be null"); this.mode = Objects.requireNonNull(mode, "mode must not be null"); this.inputBlock = inputBlock; @@ -130,11 +128,6 @@ public ItemStack getResultItem(RegistryAccess registryAccess) { return ItemStack.EMPTY; } - @Override - public ResourceLocation getId() { - return id; - } - @Override public RecipeSerializer getSerializer() { return EntropyRecipeSerializer.INSTANCE; diff --git a/src/main/java/appeng/recipes/entropy/EntropyRecipeBuilder.java b/src/main/java/appeng/recipes/entropy/EntropyRecipeBuilder.java index 62e48e8b5a2..fdc0fc6e021 100644 --- a/src/main/java/appeng/recipes/entropy/EntropyRecipeBuilder.java +++ b/src/main/java/appeng/recipes/entropy/EntropyRecipeBuilder.java @@ -23,14 +23,15 @@ import java.util.Collections; import java.util.List; import java.util.Objects; -import java.util.function.Consumer; import com.google.common.base.Preconditions; import com.google.gson.JsonObject; import org.jetbrains.annotations.Nullable; +import net.minecraft.advancements.AdvancementHolder; import net.minecraft.data.recipes.FinishedRecipe; +import net.minecraft.data.recipes.RecipeOutput; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.crafting.RecipeSerializer; @@ -38,7 +39,6 @@ import net.minecraft.world.level.material.Fluid; public class EntropyRecipeBuilder { - private ResourceLocation id; private EntropyMode mode; private Block inputBlock; @@ -55,18 +55,12 @@ public class EntropyRecipeBuilder { private boolean outputFluidKeep; private List drops = Collections.emptyList(); - public static EntropyRecipeBuilder cool(ResourceLocation id) { - return new EntropyRecipeBuilder().setId(id).setMode(EntropyMode.COOL); + public static EntropyRecipeBuilder cool() { + return new EntropyRecipeBuilder().setMode(EntropyMode.COOL); } - public static EntropyRecipeBuilder heat(ResourceLocation id) { - return new EntropyRecipeBuilder().setId(id).setMode(EntropyMode.HEAT); - } - - public EntropyRecipeBuilder setId(ResourceLocation id) { - Preconditions.checkArgument(id != null); - this.id = id; - return this; + public static EntropyRecipeBuilder heat() { + return new EntropyRecipeBuilder().setMode(EntropyMode.HEAT); } public EntropyRecipeBuilder setMode(EntropyMode mode) { @@ -168,45 +162,44 @@ public EntropyRecipeBuilder addFluidStateAppliers(StateApplier applier) { } public EntropyRecipe build() { - Preconditions.checkState(id != null); Preconditions.checkState(mode != null); Preconditions.checkState(inputBlock != null || inputFluid != null, "Either inputBlock or inputFluid needs to be not null"); - return new EntropyRecipe(id, mode, inputBlock, inputBlockMatchers, inputFluid, inputFluidMatchers, outputBlock, + return new EntropyRecipe(mode, inputBlock, inputBlockMatchers, inputFluid, inputFluidMatchers, outputBlock, outputBlockStateAppliers, outputBlockKeep, outputFluid, outputFluidStateAppliers, outputFluidKeep, drops); } - public void save(Consumer consumer) { - consumer.accept(new Result()); + public void save(RecipeOutput consumer, ResourceLocation id) { + consumer.accept(new Result(id)); } private class Result implements FinishedRecipe { + private final ResourceLocation id; + + public Result(ResourceLocation id) { + this.id = id; + } + @Override public void serializeRecipeData(JsonObject json) { - EntropyRecipeSerializer.INSTANCE.toJson(build(), json); + EntropyRecipeSerializer.writeToJson(json, build()); } @Override - public ResourceLocation getId() { + public ResourceLocation id() { return id; } @Override - public RecipeSerializer getType() { + public RecipeSerializer type() { return EntropyRecipeSerializer.INSTANCE; } @Nullable @Override - public JsonObject serializeAdvancement() { - return null; - } - - @Nullable - @Override - public ResourceLocation getAdvancementId() { + public AdvancementHolder advancement() { return null; } } diff --git a/src/main/java/appeng/recipes/entropy/EntropyRecipeSerializer.java b/src/main/java/appeng/recipes/entropy/EntropyRecipeSerializer.java index fb0dbf0b67a..d943ca3ca99 100644 --- a/src/main/java/appeng/recipes/entropy/EntropyRecipeSerializer.java +++ b/src/main/java/appeng/recipes/entropy/EntropyRecipeSerializer.java @@ -27,6 +27,7 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; +import com.mojang.serialization.Codec; import org.jetbrains.annotations.Nullable; @@ -34,6 +35,7 @@ import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.ExtraCodecs; import net.minecraft.util.GsonHelper; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; @@ -46,15 +48,23 @@ public class EntropyRecipeSerializer implements RecipeSerializer public static final EntropyRecipeSerializer INSTANCE = new EntropyRecipeSerializer(); + private static final Codec CODEC = ExtraCodecs.adaptJsonSerializer( + EntropyRecipeSerializer::fromJson, + EntropyRecipeSerializer::toJson); + private EntropyRecipeSerializer() { } @Override - public EntropyRecipe fromJson(ResourceLocation recipeId, JsonObject json) { + public Codec codec() { + return CODEC; + } + + private static EntropyRecipe fromJson(JsonElement jsonEl) { + var json = jsonEl.getAsJsonObject(); EntropyRecipeBuilder builder = new EntropyRecipeBuilder(); // Set id and mode - builder.setId(recipeId); builder.setMode(EntropyMode.valueOf(GsonHelper.getAsString(json, "mode").toUpperCase(Locale.ROOT))); //// Parse inputs @@ -136,10 +146,9 @@ private static T getRequiredEntry(Registry registry, String id) { @Nullable @Override - public EntropyRecipe fromNetwork(ResourceLocation recipeId, FriendlyByteBuf buffer) { + public EntropyRecipe fromNetwork(FriendlyByteBuf buffer) { EntropyRecipeBuilder builder = new EntropyRecipeBuilder(); - builder.setId(recipeId); builder.setMode(buffer.readEnum(EntropyMode.class)); if (buffer.readBoolean()) { @@ -295,13 +304,19 @@ private static void parseStateAppliers(StateDefinition stateDefinition, Js }); } - public void toJson(EntropyRecipe recipe, JsonObject json) { + public static void writeToJson(JsonObject json, EntropyRecipe recipe) { json.addProperty("mode", recipe.getMode().name().toLowerCase(Locale.ROOT)); json.add("input", serializeInput(recipe)); json.add("output", serializeOutput(recipe)); } - private JsonObject serializeInput(EntropyRecipe recipe) { + private static JsonElement toJson(EntropyRecipe recipe) { + JsonObject json = new JsonObject(); + writeToJson(json, recipe); + return json; + } + + private static JsonObject serializeInput(EntropyRecipe recipe) { var input = new JsonObject(); if (recipe.getInputBlock() != null) { var jsonBlock = new JsonObject(); @@ -319,7 +334,7 @@ private JsonObject serializeInput(EntropyRecipe recipe) { return input; } - private JsonElement serializeOutput(EntropyRecipe recipe) { + private static JsonElement serializeOutput(EntropyRecipe recipe) { var output = new JsonObject(); if (recipe.getOutputBlock() != null) { var jsonBlock = new JsonObject(); @@ -358,7 +373,7 @@ private JsonElement serializeOutput(EntropyRecipe recipe) { return output; } - private void serializeStateMatchers(List matchers, JsonObject json) { + private static void serializeStateMatchers(List matchers, JsonObject json) { if (matchers.isEmpty()) { return; } @@ -389,7 +404,7 @@ private void serializeStateMatchers(List matchers, JsonObject json json.add("properties", properties); } - private void serializeStateAppliers(List> appliers, JsonObject json) { + private static void serializeStateAppliers(List> appliers, JsonObject json) { if (appliers.isEmpty()) { return; } diff --git a/src/main/java/appeng/recipes/entropy/MultipleValuesMatcher.java b/src/main/java/appeng/recipes/entropy/MultipleValuesMatcher.java index db2493a2e0a..fef85c1799a 100644 --- a/src/main/java/appeng/recipes/entropy/MultipleValuesMatcher.java +++ b/src/main/java/appeng/recipes/entropy/MultipleValuesMatcher.java @@ -24,12 +24,12 @@ import java.util.Set; import java.util.stream.Collectors; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.world.level.block.state.StateDefinition; import net.minecraft.world.level.block.state.StateHolder; import net.minecraft.world.level.block.state.properties.Property; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.api.distmarker.OnlyIn; /** * Matches against a list of values. @@ -75,7 +75,7 @@ public static MultipleValuesMatcher create(StateDefinition stateDefinit return new MultipleValuesMatcher<>(property, values); } - @Environment(EnvType.CLIENT) + @OnlyIn(Dist.CLIENT) public static MultipleValuesMatcher readFromPacket(StateDefinition stateDefinition, FriendlyByteBuf buffer) { String propertyName = buffer.readUtf(); diff --git a/src/main/java/appeng/recipes/entropy/RangeValueMatcher.java b/src/main/java/appeng/recipes/entropy/RangeValueMatcher.java index 3244c97067a..0413d006f0a 100644 --- a/src/main/java/appeng/recipes/entropy/RangeValueMatcher.java +++ b/src/main/java/appeng/recipes/entropy/RangeValueMatcher.java @@ -20,12 +20,12 @@ import java.util.Objects; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.world.level.block.state.StateDefinition; import net.minecraft.world.level.block.state.StateHolder; import net.minecraft.world.level.block.state.properties.Property; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.api.distmarker.OnlyIn; /** * Matches a range between a min and max value (inclusive). @@ -74,7 +74,7 @@ public static StateMatcher create(StateDefinition stateDefinition, String return new RangeValueMatcher<>(property, minValueName, maxValueName); } - @Environment(EnvType.CLIENT) + @OnlyIn(Dist.CLIENT) public static StateMatcher readFromPacket(StateDefinition stateDefinition, FriendlyByteBuf buffer) { String propertyName = buffer.readUtf(); String minName = buffer.readUtf(); diff --git a/src/main/java/appeng/recipes/entropy/SingleValueMatcher.java b/src/main/java/appeng/recipes/entropy/SingleValueMatcher.java index b57e38c9be8..7e8e81a36de 100644 --- a/src/main/java/appeng/recipes/entropy/SingleValueMatcher.java +++ b/src/main/java/appeng/recipes/entropy/SingleValueMatcher.java @@ -20,12 +20,12 @@ import java.util.Objects; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.world.level.block.state.StateDefinition; import net.minecraft.world.level.block.state.StateHolder; import net.minecraft.world.level.block.state.properties.Property; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.api.distmarker.OnlyIn; /** * Matches an exact value. @@ -67,7 +67,7 @@ public static SingleValueMatcher create(StateDefinition stateDefinition return new SingleValueMatcher<>(property, value); } - @Environment(EnvType.CLIENT) + @OnlyIn(Dist.CLIENT) public static SingleValueMatcher readFromPacket(StateDefinition stateDefinition, FriendlyByteBuf buffer) { String propertyName = buffer.readUtf(); String value = buffer.readUtf(); diff --git a/src/main/java/appeng/recipes/entropy/StateApplier.java b/src/main/java/appeng/recipes/entropy/StateApplier.java index d32effbce1d..adc43c73dcc 100644 --- a/src/main/java/appeng/recipes/entropy/StateApplier.java +++ b/src/main/java/appeng/recipes/entropy/StateApplier.java @@ -20,12 +20,12 @@ import java.util.Objects; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.world.level.block.state.StateDefinition; import net.minecraft.world.level.block.state.StateHolder; import net.minecraft.world.level.block.state.properties.Property; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.api.distmarker.OnlyIn; /** * Generic template to apply named properties to block and fluid states. @@ -53,7 +53,7 @@ static StateApplier create(StateDefinition stateDefinition, String prop return new StateApplier<>(property, value); } - @Environment(EnvType.CLIENT) + @OnlyIn(Dist.CLIENT) static StateApplier readFromPacket(StateDefinition stateDefinition, FriendlyByteBuf buffer) { String propertyName = buffer.readUtf(); String value = buffer.readUtf(); diff --git a/src/main/java/appeng/recipes/game/FacadeRecipe.java b/src/main/java/appeng/recipes/game/FacadeRecipe.java index 1f170bb840e..79928bc5178 100644 --- a/src/main/java/appeng/recipes/game/FacadeRecipe.java +++ b/src/main/java/appeng/recipes/game/FacadeRecipe.java @@ -19,7 +19,6 @@ package appeng.recipes.game; import net.minecraft.core.RegistryAccess; -import net.minecraft.resources.ResourceLocation; import net.minecraft.world.Container; import net.minecraft.world.inventory.CraftingContainer; import net.minecraft.world.item.ItemStack; @@ -39,8 +38,8 @@ public final class FacadeRecipe extends CustomRecipe { private final ItemDefinition anchor = AEParts.CABLE_ANCHOR; private final FacadeItem facade; - public FacadeRecipe(ResourceLocation id, CraftingBookCategory category, FacadeItem facade) { - super(id, category); + public FacadeRecipe(CraftingBookCategory category, FacadeItem facade) { + super(category); this.facade = facade; } @@ -82,7 +81,7 @@ public RecipeSerializer getSerializer() { public static RecipeSerializer getSerializer(FacadeItem facade) { if (SERIALIZER == null) { - SERIALIZER = new SimpleCraftingRecipeSerializer<>((id, category) -> new FacadeRecipe(id, category, facade)); + SERIALIZER = new SimpleCraftingRecipeSerializer<>((category) -> new FacadeRecipe(category, facade)); } return SERIALIZER; } diff --git a/src/main/java/appeng/recipes/handlers/ChargerRecipe.java b/src/main/java/appeng/recipes/handlers/ChargerRecipe.java index 1db28cc6f21..fd412fbb651 100644 --- a/src/main/java/appeng/recipes/handlers/ChargerRecipe.java +++ b/src/main/java/appeng/recipes/handlers/ChargerRecipe.java @@ -1,11 +1,15 @@ package appeng.recipes.handlers; +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; + import net.minecraft.core.NonNullList; import net.minecraft.core.RegistryAccess; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.Container; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.CraftingRecipeCodecs; import net.minecraft.world.item.crafting.Ingredient; import net.minecraft.world.item.crafting.Recipe; import net.minecraft.world.item.crafting.RecipeSerializer; @@ -13,19 +17,27 @@ import net.minecraft.world.level.Level; import appeng.core.AppEng; +import appeng.init.InitRecipeTypes; public class ChargerRecipe implements Recipe { public static final ResourceLocation TYPE_ID = AppEng.makeId("charger"); - public static final RecipeType TYPE = RecipeType.register(TYPE_ID.toString()); + public static final RecipeType TYPE = InitRecipeTypes.register(TYPE_ID.toString()); - private final ResourceLocation id; public final Ingredient ingredient; public final NonNullList ingredients; public final Item result; - public ChargerRecipe(ResourceLocation id, Ingredient ingredient, Item result) { - this.id = id; + public static final Codec CODEC = RecordCodecBuilder.create( + builder -> builder + .group( + Ingredient.CODEC_NONEMPTY.fieldOf("ingredient").forGetter(ChargerRecipe::getIngredient), + // We only support items for now + CraftingRecipeCodecs.ITEMSTACK_OBJECT_CODEC.fieldOf("result") + .xmap(ItemStack::getItem, ItemStack::new).forGetter(cr -> cr.result)) + .apply(builder, ChargerRecipe::new)); + + public ChargerRecipe(Ingredient ingredient, Item result) { this.ingredient = ingredient; this.result = result; this.ingredients = NonNullList.of(Ingredient.EMPTY, ingredient); @@ -55,11 +67,6 @@ public ItemStack getResultItem() { return new ItemStack(result); } - @Override - public ResourceLocation getId() { - return id; - } - @Override public RecipeSerializer getSerializer() { return ChargerRecipeSerializer.INSTANCE; @@ -78,4 +85,9 @@ public Ingredient getIngredient() { public NonNullList getIngredients() { return ingredients; } + + @Override + public boolean isSpecial() { + return true; + } } diff --git a/src/main/java/appeng/recipes/handlers/ChargerRecipeBuilder.java b/src/main/java/appeng/recipes/handlers/ChargerRecipeBuilder.java index 04940d0bf41..8728acd9594 100644 --- a/src/main/java/appeng/recipes/handlers/ChargerRecipeBuilder.java +++ b/src/main/java/appeng/recipes/handlers/ChargerRecipeBuilder.java @@ -1,13 +1,13 @@ package appeng.recipes.handlers; -import java.util.function.Consumer; - import com.google.gson.JsonObject; import org.jetbrains.annotations.Nullable; +import net.minecraft.advancements.AdvancementHolder; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.data.recipes.FinishedRecipe; +import net.minecraft.data.recipes.RecipeOutput; import net.minecraft.resources.ResourceLocation; import net.minecraft.tags.TagKey; import net.minecraft.world.item.Item; @@ -17,16 +17,16 @@ public class ChargerRecipeBuilder { - public static void charge(Consumer consumer, ResourceLocation id, ItemLike input, ItemLike output) { + public static void charge(RecipeOutput consumer, ResourceLocation id, ItemLike input, ItemLike output) { consumer.accept(new Result(id, Ingredient.of(input), output)); } - public static void charge(Consumer consumer, ResourceLocation id, TagKey input, + public static void charge(RecipeOutput consumer, ResourceLocation id, TagKey input, ItemLike output) { consumer.accept(new Result(id, Ingredient.of(input), output)); } - public static void charge(Consumer consumer, ResourceLocation id, Ingredient input, + public static void charge(RecipeOutput consumer, ResourceLocation id, Ingredient input, ItemLike output) { consumer.accept(new Result(id, input, output)); } @@ -34,31 +34,25 @@ public static void charge(Consumer consumer, ResourceLocation id record Result(ResourceLocation id, Ingredient input, ItemLike output) implements FinishedRecipe { @Override public void serializeRecipeData(JsonObject json) { - json.add("ingredient", input.toJson()); + json.add("ingredient", input.toJson(false)); var stackObj = new JsonObject(); stackObj.addProperty("item", BuiltInRegistries.ITEM.getKey(output.asItem()).toString()); json.add("result", stackObj); } @Override - public ResourceLocation getId() { + public ResourceLocation id() { return id; } @Override - public RecipeSerializer getType() { + public RecipeSerializer type() { return ChargerRecipeSerializer.INSTANCE; } @Nullable @Override - public JsonObject serializeAdvancement() { - return null; - } - - @Nullable - @Override - public ResourceLocation getAdvancementId() { + public AdvancementHolder advancement() { return null; } } diff --git a/src/main/java/appeng/recipes/handlers/ChargerRecipeSerializer.java b/src/main/java/appeng/recipes/handlers/ChargerRecipeSerializer.java index 28f12e59ea2..941b6929df7 100644 --- a/src/main/java/appeng/recipes/handlers/ChargerRecipeSerializer.java +++ b/src/main/java/appeng/recipes/handlers/ChargerRecipeSerializer.java @@ -1,35 +1,27 @@ package appeng.recipes.handlers; -import com.google.gson.JsonObject; +import com.mojang.serialization.Codec; import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.util.GsonHelper; -import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.crafting.Ingredient; import net.minecraft.world.item.crafting.RecipeSerializer; -import net.minecraft.world.item.crafting.ShapedRecipe; public class ChargerRecipeSerializer implements RecipeSerializer { public static final ChargerRecipeSerializer INSTANCE = new ChargerRecipeSerializer(); @Override - public ChargerRecipe fromJson(ResourceLocation recipeId, JsonObject serializedRecipe) { - - Ingredient ingredient = Ingredient.fromJson(serializedRecipe.get("ingredient")); - Item result = ShapedRecipe.itemFromJson(GsonHelper.getAsJsonObject(serializedRecipe, "result")); - - return new ChargerRecipe(recipeId, ingredient, result); + public Codec codec() { + return ChargerRecipe.CODEC; } @Override - public ChargerRecipe fromNetwork(ResourceLocation recipeId, FriendlyByteBuf buffer) { + public ChargerRecipe fromNetwork(FriendlyByteBuf buffer) { Ingredient ingredient = Ingredient.fromNetwork(buffer); ItemStack result = buffer.readItem(); - return new ChargerRecipe(recipeId, ingredient, result.getItem()); + return new ChargerRecipe(ingredient, result.getItem()); } @Override diff --git a/src/main/java/appeng/recipes/handlers/InscriberRecipe.java b/src/main/java/appeng/recipes/handlers/InscriberRecipe.java index f3264e9708b..bc799da89a9 100644 --- a/src/main/java/appeng/recipes/handlers/InscriberRecipe.java +++ b/src/main/java/appeng/recipes/handlers/InscriberRecipe.java @@ -18,11 +18,16 @@ package appeng.recipes.handlers; +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; + import net.minecraft.core.NonNullList; import net.minecraft.core.RegistryAccess; import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.ExtraCodecs; import net.minecraft.world.Container; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.CraftingRecipeCodecs; import net.minecraft.world.item.crafting.Ingredient; import net.minecraft.world.item.crafting.Recipe; import net.minecraft.world.item.crafting.RecipeSerializer; @@ -30,13 +35,36 @@ import net.minecraft.world.level.Level; import appeng.core.AppEng; +import appeng.init.InitRecipeTypes; public class InscriberRecipe implements Recipe { - public static final ResourceLocation TYPE_ID = AppEng.makeId("inscriber"); - public static final RecipeType TYPE = RecipeType.register(TYPE_ID.toString()); + private static final Codec MODE_CODEC = ExtraCodecs.stringResolverCodec( + mode -> switch (mode) { + case INSCRIBE -> "inscribe"; + case PRESS -> "press"; + }, + mode -> switch (mode) { + default -> InscriberProcessType.INSCRIBE; + case "press" -> InscriberProcessType.PRESS; + }); + + public static final Codec CODEC = RecordCodecBuilder.create( + builder -> builder + .group( + Ingredient.CODEC_NONEMPTY.fieldOf("middle") + .fieldOf("ingredients").forGetter(ir -> ir.middleInput), + CraftingRecipeCodecs.ITEMSTACK_OBJECT_CODEC.fieldOf("result").forGetter(ir -> ir.output), + ExtraCodecs.strictOptionalField(Ingredient.CODEC, "top", Ingredient.EMPTY) + .fieldOf("ingredients").forGetter(ir -> ir.topOptional), + ExtraCodecs.strictOptionalField(Ingredient.CODEC, "bottom", Ingredient.EMPTY) + .fieldOf("ingredients").forGetter(ir -> ir.bottomOptional), + MODE_CODEC.fieldOf("mode").forGetter(ir -> ir.processType)) + .apply(builder, InscriberRecipe::new)); + + public static final ResourceLocation TYPE_ID = AppEng.makeId("inscriber"); - private final ResourceLocation id; + public static final RecipeType TYPE = InitRecipeTypes.register(TYPE_ID.toString()); private final Ingredient middleInput; private final Ingredient topOptional; @@ -44,9 +72,8 @@ public class InscriberRecipe implements Recipe { private final ItemStack output; private final InscriberProcessType processType; - public InscriberRecipe(ResourceLocation id, Ingredient middleInput, ItemStack output, + public InscriberRecipe(Ingredient middleInput, ItemStack output, Ingredient topOptional, Ingredient bottomOptional, InscriberProcessType processType) { - this.id = id; this.middleInput = middleInput; this.output = output; this.topOptional = topOptional; @@ -78,11 +105,6 @@ public ItemStack getResultItem() { return this.output; } - @Override - public ResourceLocation getId() { - return id; - } - @Override public RecipeSerializer getSerializer() { return InscriberRecipeSerializer.INSTANCE; diff --git a/src/main/java/appeng/recipes/handlers/InscriberRecipeBuilder.java b/src/main/java/appeng/recipes/handlers/InscriberRecipeBuilder.java index 5fc007bc787..f2593dd19f0 100644 --- a/src/main/java/appeng/recipes/handlers/InscriberRecipeBuilder.java +++ b/src/main/java/appeng/recipes/handlers/InscriberRecipeBuilder.java @@ -1,14 +1,15 @@ package appeng.recipes.handlers; import java.util.Locale; -import java.util.function.Consumer; import com.google.gson.JsonObject; import org.jetbrains.annotations.Nullable; +import net.minecraft.advancements.AdvancementHolder; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.data.recipes.FinishedRecipe; +import net.minecraft.data.recipes.RecipeOutput; import net.minecraft.resources.ResourceLocation; import net.minecraft.tags.TagKey; import net.minecraft.world.item.Item; @@ -57,7 +58,7 @@ public InscriberRecipeBuilder setMode(InscriberProcessType processType) { return this; } - public void save(Consumer consumer, ResourceLocation id) { + public void save(RecipeOutput consumer, ResourceLocation id) { consumer.accept(new Result(id)); } @@ -80,35 +81,29 @@ public void serializeRecipeData(JsonObject json) { json.add("result", result); var ingredients = new JsonObject(); - ingredients.add("middle", middleInput.toJson()); + ingredients.add("middle", middleInput.toJson(false)); if (topOptional != null) { - ingredients.add("top", topOptional.toJson()); + ingredients.add("top", topOptional.toJson(true)); } if (bottomOptional != null) { - ingredients.add("bottom", bottomOptional.toJson()); + ingredients.add("bottom", bottomOptional.toJson(true)); } json.add("ingredients", ingredients); } @Override - public ResourceLocation getId() { + public ResourceLocation id() { return id; } @Override - public RecipeSerializer getType() { + public RecipeSerializer type() { return InscriberRecipeSerializer.INSTANCE; } @Nullable @Override - public JsonObject serializeAdvancement() { - return null; - } - - @Nullable - @Override - public ResourceLocation getAdvancementId() { + public AdvancementHolder advancement() { return null; } } diff --git a/src/main/java/appeng/recipes/handlers/InscriberRecipeSerializer.java b/src/main/java/appeng/recipes/handlers/InscriberRecipeSerializer.java index 83ba894de25..2d7bf18d3b6 100644 --- a/src/main/java/appeng/recipes/handlers/InscriberRecipeSerializer.java +++ b/src/main/java/appeng/recipes/handlers/InscriberRecipeSerializer.java @@ -18,17 +18,14 @@ package appeng.recipes.handlers; -import com.google.gson.JsonObject; +import com.mojang.serialization.Codec; import org.jetbrains.annotations.Nullable; import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.util.GsonHelper; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.crafting.Ingredient; import net.minecraft.world.item.crafting.RecipeSerializer; -import net.minecraft.world.item.crafting.ShapedRecipe; public class InscriberRecipeSerializer implements RecipeSerializer { @@ -37,48 +34,21 @@ public class InscriberRecipeSerializer implements RecipeSerializer InscriberProcessType.INSCRIBE; - case "press" -> InscriberProcessType.PRESS; - default -> throw new IllegalStateException("Unknown mode for inscriber recipe: " + mode); - }; - - } - @Override - public InscriberRecipe fromJson(ResourceLocation recipeId, JsonObject json) { - - InscriberProcessType mode = getMode(json); - - ItemStack result = ShapedRecipe.itemStackFromJson(GsonHelper.getAsJsonObject(json, "result")); - - // Deserialize the three parts of the input - JsonObject ingredients = GsonHelper.getAsJsonObject(json, "ingredients"); - Ingredient middle = Ingredient.fromJson(ingredients.get("middle")); - Ingredient top = Ingredient.EMPTY; - if (ingredients.has("top")) { - top = Ingredient.fromJson(ingredients.get("top")); - } - Ingredient bottom = Ingredient.EMPTY; - if (ingredients.has("bottom")) { - bottom = Ingredient.fromJson(ingredients.get("bottom")); - } - - return new InscriberRecipe(recipeId, middle, result, top, bottom, mode); + public Codec codec() { + return InscriberRecipe.CODEC; } @Nullable @Override - public InscriberRecipe fromNetwork(ResourceLocation recipeId, FriendlyByteBuf buffer) { + public InscriberRecipe fromNetwork(FriendlyByteBuf buffer) { Ingredient middle = Ingredient.fromNetwork(buffer); ItemStack result = buffer.readItem(); Ingredient top = Ingredient.fromNetwork(buffer); Ingredient bottom = Ingredient.fromNetwork(buffer); InscriberProcessType mode = buffer.readEnum(InscriberProcessType.class); - return new InscriberRecipe(recipeId, middle, result, top, bottom, mode); + return new InscriberRecipe(middle, result, top, bottom, mode); } @Override diff --git a/src/main/java/appeng/recipes/mattercannon/MatterCannonAmmo.java b/src/main/java/appeng/recipes/mattercannon/MatterCannonAmmo.java index 7d178fb7113..776226ff5a8 100644 --- a/src/main/java/appeng/recipes/mattercannon/MatterCannonAmmo.java +++ b/src/main/java/appeng/recipes/mattercannon/MatterCannonAmmo.java @@ -18,19 +18,22 @@ package appeng.recipes.mattercannon; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; -import java.util.function.Consumer; import com.google.common.base.Preconditions; import com.google.gson.JsonObject; +import com.mojang.serialization.JsonOps; import org.jetbrains.annotations.Nullable; -import net.fabricmc.fabric.api.resource.conditions.v1.ConditionJsonProvider; -import net.fabricmc.fabric.api.resource.conditions.v1.DefaultResourceConditions; +import net.minecraft.Util; +import net.minecraft.advancements.AdvancementHolder; import net.minecraft.core.NonNullList; import net.minecraft.core.RegistryAccess; import net.minecraft.data.recipes.FinishedRecipe; +import net.minecraft.data.recipes.RecipeOutput; import net.minecraft.resources.ResourceLocation; import net.minecraft.tags.TagKey; import net.minecraft.world.Container; @@ -42,8 +45,12 @@ import net.minecraft.world.item.crafting.RecipeType; import net.minecraft.world.level.ItemLike; import net.minecraft.world.level.Level; +import net.neoforged.neoforge.common.conditions.ICondition; +import net.neoforged.neoforge.common.conditions.NotCondition; +import net.neoforged.neoforge.common.conditions.TagEmptyCondition; import appeng.core.AppEng; +import appeng.init.InitRecipeTypes; /** * Defines a type of ammo that can be used for the {@link appeng.items.tools.powered.MatterCannonItem}. @@ -52,30 +59,27 @@ public class MatterCannonAmmo implements Recipe { public static final ResourceLocation TYPE_ID = AppEng.makeId("matter_cannon"); - public static final RecipeType TYPE = RecipeType.register(TYPE_ID.toString()); - - private final ResourceLocation id; + public static final RecipeType TYPE = InitRecipeTypes.register(TYPE_ID.toString()); private final Ingredient ammo; private final float weight; - public MatterCannonAmmo(ResourceLocation id, Ingredient ammo, float weight) { + public MatterCannonAmmo(Ingredient ammo, float weight) { Preconditions.checkArgument(weight >= 0, "Weight must not be negative"); - this.id = Objects.requireNonNull(id, "id must not be null"); this.ammo = Objects.requireNonNull(ammo, "ammo must not be null"); this.weight = weight; } - public static void ammo(Consumer consumer, ResourceLocation id, ItemLike item, float weight) { + public static void ammo(RecipeOutput consumer, ResourceLocation id, ItemLike item, float weight) { consumer.accept(new Ammo(id, null, Ingredient.of(item), weight)); } - public static void ammo(Consumer consumer, ResourceLocation id, Ingredient ammo, float weight) { + public static void ammo(RecipeOutput consumer, ResourceLocation id, Ingredient ammo, float weight) { consumer.accept(new Ammo(id, null, ammo, weight)); } - public static void ammo(Consumer consumer, ResourceLocation id, TagKey tag, float weight) { + public static void ammo(RecipeOutput consumer, ResourceLocation id, TagKey tag, float weight) { consumer.accept(new Ammo(id, tag, null, weight)); } @@ -99,11 +103,6 @@ public ItemStack getResultItem(RegistryAccess registryAccess) { return ItemStack.EMPTY; } - @Override - public ResourceLocation getId() { - return id; - } - @Override public RecipeSerializer getSerializer() { return MatterCannonAmmoSerializer.INSTANCE; @@ -130,36 +129,35 @@ public float getWeight() { public record Ammo(ResourceLocation id, TagKey tag, Ingredient nonTag, float weight) implements FinishedRecipe { - @Override public void serializeRecipeData(JsonObject json) { + List conditions = new ArrayList<>(); if (tag != null) { - json.add("ammo", Ingredient.of(tag).toJson()); - ConditionJsonProvider.write(json, DefaultResourceConditions.tagsPopulated(tag)); + json.add("ammo", Ingredient.of(tag).toJson(false)); + conditions.add(new NotCondition( + new TagEmptyCondition(tag.location()))); } else if (nonTag != null) { - json.add("ammo", nonTag.toJson()); + json.add("ammo", nonTag.toJson(false)); } + json.addProperty("weight", this.weight); + var conditionsJson = Util.getOrThrow(ICondition.LIST_CODEC.encodeStart(JsonOps.INSTANCE, conditions), + IllegalStateException::new); + json.add("conditions", conditionsJson); } @Override - public ResourceLocation getId() { + public ResourceLocation id() { return this.id; } @Override - public RecipeSerializer getType() { + public RecipeSerializer type() { return MatterCannonAmmoSerializer.INSTANCE; } @Nullable @Override - public JsonObject serializeAdvancement() { - return null; - } - - @Nullable - @Override - public ResourceLocation getAdvancementId() { + public AdvancementHolder advancement() { return null; } } diff --git a/src/main/java/appeng/recipes/mattercannon/MatterCannonAmmoSerializer.java b/src/main/java/appeng/recipes/mattercannon/MatterCannonAmmoSerializer.java index c7b943b1e35..e48b14ce311 100644 --- a/src/main/java/appeng/recipes/mattercannon/MatterCannonAmmoSerializer.java +++ b/src/main/java/appeng/recipes/mattercannon/MatterCannonAmmoSerializer.java @@ -18,12 +18,12 @@ package appeng.recipes.mattercannon; -import com.google.gson.JsonObject; +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; import org.jetbrains.annotations.Nullable; import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.crafting.Ingredient; import net.minecraft.world.item.crafting.RecipeSerializer; @@ -31,22 +31,27 @@ public class MatterCannonAmmoSerializer implements RecipeSerializer CODEC = RecordCodecBuilder.create((builder) -> { + return builder.group( + Ingredient.CODEC_NONEMPTY.fieldOf("ammo").forGetter(MatterCannonAmmo::getAmmo), + Codec.FLOAT.fieldOf("weight").forGetter(MatterCannonAmmo::getWeight)) + .apply(builder, MatterCannonAmmo::new); + }); + private MatterCannonAmmoSerializer() { } @Override - public MatterCannonAmmo fromJson(ResourceLocation recipeId, JsonObject json) { - var ammo = Ingredient.fromJson(json.get("ammo")); - var weight = json.get("weight").getAsFloat(); - return new MatterCannonAmmo(recipeId, ammo, weight); + public Codec codec() { + return CODEC; } @Nullable @Override - public MatterCannonAmmo fromNetwork(ResourceLocation recipeId, FriendlyByteBuf buffer) { + public MatterCannonAmmo fromNetwork(FriendlyByteBuf buffer) { var ammo = Ingredient.fromNetwork(buffer); var weight = buffer.readFloat(); - return new MatterCannonAmmo(recipeId, ammo, weight); + return new MatterCannonAmmo(ammo, weight); } @Override diff --git a/src/main/java/appeng/recipes/transform/TransformCircumstance.java b/src/main/java/appeng/recipes/transform/TransformCircumstance.java index 27394a67d01..87fa2838754 100644 --- a/src/main/java/appeng/recipes/transform/TransformCircumstance.java +++ b/src/main/java/appeng/recipes/transform/TransformCircumstance.java @@ -5,6 +5,8 @@ import com.google.gson.JsonObject; import com.google.gson.JsonParseException; +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; import io.netty.handler.codec.DecoderException; @@ -18,7 +20,20 @@ import net.minecraft.world.level.material.FluidState; public class TransformCircumstance { + public static final TransformCircumstance EXPLOSION = new TransformCircumstance("explosion"); + + private static final Codec EXPLOSION_CODEC = Codec.unit(EXPLOSION); + + private static final Codec FLUID_CODEC = RecordCodecBuilder.create(builder -> builder.group( + TagKey.codec(Registries.FLUID).fieldOf("tag").forGetter(f -> f.fluidTag)).apply(builder, FluidType::new)); + + public static final Codec CODEC = Codec.STRING.dispatch(t -> t.type, type -> switch (type) { + case "explosion" -> EXPLOSION_CODEC; + case "fluid" -> FLUID_CODEC; + default -> throw new IllegalStateException("Invalid type: " + type); + }); + private final String type; public TransformCircumstance(String type) { diff --git a/src/main/java/appeng/recipes/transform/TransformLogic.java b/src/main/java/appeng/recipes/transform/TransformLogic.java index 8330ea13162..e143314182f 100644 --- a/src/main/java/appeng/recipes/transform/TransformLogic.java +++ b/src/main/java/appeng/recipes/transform/TransformLogic.java @@ -9,8 +9,6 @@ import com.google.common.collect.Lists; -import net.fabricmc.fabric.api.event.lifecycle.v1.CommonLifecycleEvents; -import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.minecraft.world.SimpleContainer; import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.item.Item; @@ -20,6 +18,10 @@ import net.minecraft.world.level.material.Fluid; import net.minecraft.world.level.material.FluidState; import net.minecraft.world.phys.AABB; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.neoforge.client.event.RecipesUpdatedEvent; +import net.neoforged.neoforge.event.AddReloadListenerEvent; +import net.neoforged.neoforge.event.server.ServerStartedEvent; import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; @@ -44,11 +46,12 @@ public static boolean tryTransform(ItemEntity entity, Predicate itemEntities = level.getEntities(null, region).stream() .filter(e -> e instanceof ItemEntity && !e.isRemoved()).map(e -> (ItemEntity) e).toList(); - for (var recipe : level.getRecipeManager().byType(TransformRecipe.TYPE).values()) { + for (var holder : level.getRecipeManager().byType(TransformRecipe.TYPE).values()) { + var recipe = holder.value(); if (!circumstancePredicate.test(recipe.circumstance)) continue; - if (recipe.ingredients.size() == 0) + if (recipe.ingredients.isEmpty()) continue; List missingIngredients = Lists.newArrayList(recipe.ingredients); @@ -122,7 +125,8 @@ private static void clearCache() { private static Set getTransformableItems(Level level, Fluid fluid) { return fluidCache.computeIfAbsent(fluid, f -> { Set ret = Collections.newSetFromMap(new IdentityHashMap<>()); - for (var recipe : level.getRecipeManager().getAllRecipesFor(TransformRecipe.TYPE)) { + for (var holder : level.getRecipeManager().getAllRecipesFor(TransformRecipe.TYPE)) { + var recipe = holder.value(); if (!(recipe.circumstance.isFluid(fluid))) continue; for (var ingredient : recipe.ingredients) { @@ -140,7 +144,8 @@ private static Set getTransformableItemsAnyFluid(Level level) { Set ret = anyFluidCache; if (ret == null) { ret = Collections.newSetFromMap(new IdentityHashMap<>()); - for (var recipe : level.getRecipeManager().getAllRecipesFor(TransformRecipe.TYPE)) { + for (var holder : level.getRecipeManager().getAllRecipesFor(TransformRecipe.TYPE)) { + var recipe = holder.value(); if (!recipe.circumstance.isFluid()) continue; for (var ingredient : recipe.ingredients) { @@ -159,7 +164,8 @@ private static Set getTransformableItemsExplosion(Level level) { Set ret = explosionCache; if (ret == null) { ret = Collections.newSetFromMap(new IdentityHashMap<>()); - for (var recipe : level.getRecipeManager().getAllRecipesFor(TransformRecipe.TYPE)) { + for (var holder : level.getRecipeManager().getAllRecipesFor(TransformRecipe.TYPE)) { + var recipe = holder.value(); if (!recipe.circumstance.isExplosion()) continue; for (var ingredient : recipe.ingredients) { @@ -174,19 +180,19 @@ private static Set getTransformableItemsExplosion(Level level) { return ret; } - static { - ServerLifecycleEvents.SERVER_STARTED.register(server -> clearCache()); - ServerLifecycleEvents.END_DATA_PACK_RELOAD.register((server, resourceManager, success) -> { - if (success) - clearCache(); - }); - CommonLifecycleEvents.TAGS_LOADED.register((registries, client) -> { - if (client) { - // a bit cheesy, but probably fine (technically we should be listening for recipes, not tags) - // TODO: PR client recipe load callback to fabric - clearCache(); - } - }); + @SubscribeEvent + public static void onServerStarted(ServerStartedEvent e) { + clearCache(); + } + + @SubscribeEvent + public static void onReloadServerResources(AddReloadListenerEvent e) { + clearCache(); + } + + @SubscribeEvent + public static void onClientRecipesUpdated(RecipesUpdatedEvent e) { + clearCache(); } private TransformLogic() { diff --git a/src/main/java/appeng/recipes/transform/TransformRecipe.java b/src/main/java/appeng/recipes/transform/TransformRecipe.java index 66046f4ffe8..694cfc38ec3 100644 --- a/src/main/java/appeng/recipes/transform/TransformRecipe.java +++ b/src/main/java/appeng/recipes/transform/TransformRecipe.java @@ -14,19 +14,18 @@ import appeng.blockentity.qnb.QuantumBridgeBlockEntity; import appeng.core.AppEng; import appeng.core.definitions.AEItems; +import appeng.init.InitRecipeTypes; public final class TransformRecipe implements Recipe { public static final ResourceLocation TYPE_ID = AppEng.makeId("transform"); - public static final RecipeType TYPE = RecipeType.register(TYPE_ID.toString()); + public static final RecipeType TYPE = InitRecipeTypes.register(TYPE_ID.toString()); - private final ResourceLocation id; public final NonNullList ingredients; public final ItemStack output; public final TransformCircumstance circumstance; - public TransformRecipe(ResourceLocation id, NonNullList ingredients, ItemStack output, + public TransformRecipe(NonNullList ingredients, ItemStack output, TransformCircumstance circumstance) { - this.id = id; this.ingredients = ingredients; this.output = output; this.circumstance = circumstance; @@ -65,11 +64,6 @@ public ItemStack getResultItem() { return output; } - @Override - public ResourceLocation getId() { - return id; - } - @Override public RecipeSerializer getSerializer() { return TransformRecipeSerializer.INSTANCE; diff --git a/src/main/java/appeng/recipes/transform/TransformRecipeBuilder.java b/src/main/java/appeng/recipes/transform/TransformRecipeBuilder.java index 7e330fd4a08..f681f2f24a3 100644 --- a/src/main/java/appeng/recipes/transform/TransformRecipeBuilder.java +++ b/src/main/java/appeng/recipes/transform/TransformRecipeBuilder.java @@ -1,7 +1,6 @@ package appeng.recipes.transform; import java.util.List; -import java.util.function.Consumer; import java.util.stream.Stream; import com.google.gson.JsonArray; @@ -10,8 +9,10 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import net.minecraft.advancements.AdvancementHolder; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.data.recipes.FinishedRecipe; +import net.minecraft.data.recipes.RecipeOutput; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.crafting.Ingredient; import net.minecraft.world.item.crafting.RecipeSerializer; @@ -19,12 +20,12 @@ public class TransformRecipeBuilder { - public static void transform(Consumer consumer, ResourceLocation id, ItemLike output, int count, + public static void transform(RecipeOutput consumer, ResourceLocation id, ItemLike output, int count, TransformCircumstance circumstance, ItemLike... inputs) { consumer.accept(new Result(id, Stream.of(inputs).map(Ingredient::of).toList(), output, count, circumstance)); } - public static void transform(Consumer consumer, ResourceLocation id, ItemLike output, int count, + public static void transform(RecipeOutput consumer, ResourceLocation id, ItemLike output, int count, TransformCircumstance circumstance, Ingredient... inputs) { consumer.accept(new Result(id, List.of(inputs), output, count, circumstance)); } @@ -42,30 +43,24 @@ public void serializeRecipeData(@NotNull JsonObject json) { json.add("result", stackObj); JsonArray inputs = new JsonArray(); - ingredients.forEach(ingredient -> inputs.add(ingredient.toJson())); + ingredients.forEach(ingredient -> inputs.add(ingredient.toJson(false))); json.add("ingredients", inputs); json.add("circumstance", circumstance.toJson()); } @Override - public ResourceLocation getId() { + public ResourceLocation id() { return id; } @Override - public RecipeSerializer getType() { + public RecipeSerializer type() { return TransformRecipeSerializer.INSTANCE; } @Nullable @Override - public JsonObject serializeAdvancement() { - return null; - } - - @Nullable - @Override - public ResourceLocation getAdvancementId() { + public AdvancementHolder advancement() { return null; } } diff --git a/src/main/java/appeng/recipes/transform/TransformRecipeSerializer.java b/src/main/java/appeng/recipes/transform/TransformRecipeSerializer.java index cc9a94dba71..8b3accc1b56 100644 --- a/src/main/java/appeng/recipes/transform/TransformRecipeSerializer.java +++ b/src/main/java/appeng/recipes/transform/TransformRecipeSerializer.java @@ -18,43 +18,54 @@ package appeng.recipes.transform; -import com.google.gson.JsonObject; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.codecs.RecordCodecBuilder; import org.jetbrains.annotations.Nullable; import net.minecraft.core.NonNullList; import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.resources.ResourceLocation; import net.minecraft.tags.FluidTags; -import net.minecraft.util.GsonHelper; +import net.minecraft.util.ExtraCodecs; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.CraftingRecipeCodecs; import net.minecraft.world.item.crafting.Ingredient; import net.minecraft.world.item.crafting.RecipeSerializer; -import net.minecraft.world.item.crafting.ShapedRecipe; public class TransformRecipeSerializer implements RecipeSerializer { public static final TransformRecipeSerializer INSTANCE = new TransformRecipeSerializer(); + private static Codec CODEC = RecordCodecBuilder.create(builder -> { + return builder.group( + Ingredient.CODEC_NONEMPTY + .listOf() + .fieldOf("ingredients") + .flatXmap(ingredients -> { + return DataResult + .success(NonNullList.of(Ingredient.EMPTY, ingredients.toArray(Ingredient[]::new))); + }, DataResult::success) + .forGetter(r -> r.ingredients), + CraftingRecipeCodecs.ITEMSTACK_OBJECT_CODEC.fieldOf("result").forGetter(r -> r.output), + ExtraCodecs + .strictOptionalField(TransformCircumstance.CODEC, "circumstance", + TransformCircumstance.fluid(FluidTags.WATER)) + .forGetter(t -> t.circumstance)) + .apply(builder, TransformRecipe::new); + }); + private TransformRecipeSerializer() { } @Override - public TransformRecipe fromJson(ResourceLocation recipeId, JsonObject json) { - NonNullList ingredients = NonNullList.create(); - GsonHelper.getAsJsonArray(json, "ingredients") - .forEach(ingredient -> ingredients.add(Ingredient.fromJson(ingredient))); - - ItemStack result = ShapedRecipe.itemStackFromJson(GsonHelper.getAsJsonObject(json, "result")); - TransformCircumstance circumstance = json.has("circumstance") - ? TransformCircumstance.fromJson(GsonHelper.getAsJsonObject(json, "circumstance")) - : TransformCircumstance.fluid(FluidTags.WATER); - return new TransformRecipe(recipeId, ingredients, result, circumstance); + public Codec codec() { + return CODEC; } @Nullable @Override - public TransformRecipe fromNetwork(ResourceLocation recipeId, FriendlyByteBuf buffer) { + public TransformRecipe fromNetwork(FriendlyByteBuf buffer) { ItemStack output = buffer.readItem(); int size = buffer.readByte(); @@ -64,7 +75,7 @@ public TransformRecipe fromNetwork(ResourceLocation recipeId, FriendlyByteBuf bu } TransformCircumstance circumstance = TransformCircumstance.fromNetwork(buffer); - return new TransformRecipe(recipeId, ingredients, output, circumstance); + return new TransformRecipe(ingredients, output, circumstance); } @Override diff --git a/src/main/java/appeng/server/services/ChunkLoadState.java b/src/main/java/appeng/server/services/ChunkLoadState.java index 780f41da5d3..acefe10627c 100644 --- a/src/main/java/appeng/server/services/ChunkLoadState.java +++ b/src/main/java/appeng/server/services/ChunkLoadState.java @@ -12,6 +12,7 @@ import net.minecraft.nbt.Tag; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.saveddata.SavedData; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; @@ -25,11 +26,16 @@ * Implementation detail of {@link ChunkLoadingService} on Fabric, as {@code ForgeChunkManager} is not available there. */ class ChunkLoadState extends AESavedData { + public static final String NAME = AppEng.MOD_ID + "_chunk_load_state"; public static ChunkLoadState get(ServerLevel level) { - return level.getDataStorage().computeIfAbsent(tag -> new ChunkLoadState(level, tag), - () -> new ChunkLoadState(level), NAME); + return level.getDataStorage().computeIfAbsent( + new SavedData.Factory<>( + () -> new ChunkLoadState(level), + tag -> new ChunkLoadState(level, tag), + null), + NAME); } private final ServerLevel level; diff --git a/src/main/java/appeng/server/services/ChunkLoadingService.java b/src/main/java/appeng/server/services/ChunkLoadingService.java index 6d74e6c0135..b93a6a156dc 100644 --- a/src/main/java/appeng/server/services/ChunkLoadingService.java +++ b/src/main/java/appeng/server/services/ChunkLoadingService.java @@ -22,21 +22,31 @@ import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.block.entity.BlockEntity; +import net.neoforged.neoforge.common.world.ForcedChunkManager; +import net.neoforged.neoforge.common.world.ForcedChunkManager.LoadingValidationCallback; +import net.neoforged.neoforge.common.world.ForcedChunkManager.TicketHelper; +import net.neoforged.neoforge.event.server.ServerAboutToStartEvent; +import net.neoforged.neoforge.event.server.ServerStoppingEvent; import appeng.blockentity.spatial.SpatialAnchorBlockEntity; +import appeng.core.AppEng; -public class ChunkLoadingService { +public class ChunkLoadingService implements LoadingValidationCallback { private static final ChunkLoadingService INSTANCE = new ChunkLoadingService(); // Flag to ignore a server after it is stopping as grid nodes might reevaluate their grids during a shutdown. private boolean running = true; - public void onServerAboutToStart() { + public static void register() { + ForcedChunkManager.setForcedChunkLoadingCallback(AppEng.MOD_ID, INSTANCE); + } + + public void onServerAboutToStart(ServerAboutToStartEvent evt) { this.running = true; } - public void onServerStopping() { + public void onServerStopping(ServerStoppingEvent event) { this.running = false; } @@ -44,39 +54,35 @@ public static ChunkLoadingService getInstance() { return INSTANCE; } - public void validateTickets(ServerLevel level) { - var state = ChunkLoadState.get(level); - - for (var entry : state.getAllBlocks().entrySet()) { - var blockPos = entry.getKey(); - var chunks = entry.getValue(); + @Override + public void validateTickets(ServerLevel level, TicketHelper ticketHelper) { + // Iterate over all blockpos registered as chunk loader to initialize them + ticketHelper.getBlockTickets().forEach((blockPos, chunks) -> { BlockEntity blockEntity = level.getBlockEntity(blockPos); // Add all persisted chunks to the list of handled ones by each anchor. // Or remove all in case the anchor no longer exists. if (blockEntity instanceof SpatialAnchorBlockEntity anchor) { - for (long chunk : chunks) { - anchor.registerChunk(new ChunkPos(chunk)); + for (Long chunk : chunks.getSecond()) { + anchor.registerChunk(new ChunkPos(chunk.longValue())); } } else { - state.releaseAll(blockPos); + ticketHelper.removeAllTickets(blockPos); } - } + }); } - public boolean forceChunk(ServerLevel level, BlockPos owner, ChunkPos position) { + public boolean forceChunk(ServerLevel level, BlockPos owner, ChunkPos position, boolean ticking) { if (running) { - ChunkLoadState.get(level).forceChunk(position, owner); - return true; + return ForcedChunkManager.forceChunk(level, AppEng.MOD_ID, owner, position.x, position.z, true, true); } return false; } - public boolean releaseChunk(ServerLevel level, BlockPos owner, ChunkPos position) { + public boolean releaseChunk(ServerLevel level, BlockPos owner, ChunkPos position, boolean ticking) { if (running) { - ChunkLoadState.get(level).releaseChunk(position, owner); - return true; + return ForcedChunkManager.forceChunk(level, AppEng.MOD_ID, owner, position.x, position.z, false, true); } return false; diff --git a/src/main/java/appeng/server/services/compass/CompassRegion.java b/src/main/java/appeng/server/services/compass/CompassRegion.java index aeae7b9fe27..2ae612b6b27 100644 --- a/src/main/java/appeng/server/services/compass/CompassRegion.java +++ b/src/main/java/appeng/server/services/compass/CompassRegion.java @@ -26,6 +26,7 @@ import net.minecraft.nbt.CompoundTag; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.saveddata.SavedData; import appeng.core.AELog; import appeng.core.worlddata.AESavedData; @@ -34,6 +35,10 @@ * A compass region stores information about the occurrence of skystone blocks in a region of 1024x1024 chunks. */ final class CompassRegion extends AESavedData { + private static final SavedData.Factory FACTORY = new Factory<>( + CompassRegion::new, + CompassRegion::load, + null); /** * The number of chunks that get saved in a region on each axis. @@ -63,8 +68,7 @@ public static CompassRegion get(ServerLevel level, ChunkPos chunkPos) { var regionZ = chunkPos.z / CHUNKS_PER_REGION; return level.getDataStorage().computeIfAbsent( - CompassRegion::load, - CompassRegion::new, + FACTORY, getRegionSaveName(regionX, regionZ)); } diff --git a/src/main/java/appeng/server/subcommands/ChunkLogger.java b/src/main/java/appeng/server/subcommands/ChunkLogger.java index fb63a031747..f620ef6974f 100644 --- a/src/main/java/appeng/server/subcommands/ChunkLogger.java +++ b/src/main/java/appeng/server/subcommands/ChunkLogger.java @@ -20,13 +20,15 @@ import com.mojang.brigadier.context.CommandContext; -import net.fabricmc.fabric.api.event.lifecycle.v1.ServerChunkEvents; import net.minecraft.commands.CommandSourceStack; import net.minecraft.network.chat.Component; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.levelgen.Heightmap; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.level.ChunkEvent; import appeng.core.AEConfig; import appeng.core.AELog; @@ -34,7 +36,6 @@ public class ChunkLogger implements ISubCommand { - private boolean eventsRegistered = false; private boolean enabled = false; private void displayStack() { @@ -51,8 +52,10 @@ private void displayStack() { } } - private void onChunkLoadEvent(ServerLevel level, LevelChunk chunk) { - if (enabled) { + @SubscribeEvent + public void onChunkLoadEvent(final ChunkEvent.Load event) { + if (event.getLevel() instanceof ServerLevel level) { + var chunk = event.getChunk(); var chunkPos = chunk.getPos(); var center = getCenter(chunk); AELog.info("Loaded chunk " + chunkPos.x + "," + chunkPos.z + " [center: " + center + "] in " @@ -61,8 +64,10 @@ private void onChunkLoadEvent(ServerLevel level, LevelChunk chunk) { } } - private void onChunkUnloadEvent(ServerLevel level, LevelChunk chunk) { - if (enabled) { + @SubscribeEvent + public void onChunkUnloadEvent(final ChunkEvent.Unload event) { + if (event.getLevel() instanceof ServerLevel level) { + var chunk = event.getChunk(); var chunkPos = chunk.getPos(); var center = getCenter(chunk); AELog.info("Unloaded chunk " + chunkPos.x + "," + chunkPos.z + " [center: " + center + "] in " @@ -71,7 +76,7 @@ private void onChunkUnloadEvent(ServerLevel level, LevelChunk chunk) { } } - private static String getCenter(LevelChunk chunk) { + private static String getCenter(ChunkAccess chunk) { var chunkPos = chunk.getPos(); var x = chunkPos.getMiddleBlockX(); var z = chunkPos.getMiddleBlockZ(); @@ -82,16 +87,13 @@ private static String getCenter(LevelChunk chunk) { @Override public void call(MinecraftServer srv, CommandContext data, CommandSourceStack sender) { - if (!eventsRegistered) { - ServerChunkEvents.CHUNK_LOAD.register(this::onChunkLoadEvent); - ServerChunkEvents.CHUNK_UNLOAD.register(this::onChunkUnloadEvent); - } - this.enabled = !this.enabled; if (this.enabled) { + NeoForge.EVENT_BUS.register(this); sender.sendSuccess(() -> Component.translatable("commands.ae2.ChunkLoggerOn"), true); } else { + NeoForge.EVENT_BUS.unregister(this); sender.sendSuccess(() -> Component.translatable("commands.ae2.ChunkLoggerOff"), true); } } diff --git a/src/main/java/appeng/server/testplots/AutoCraftingTestPlots.java b/src/main/java/appeng/server/testplots/AutoCraftingTestPlots.java index a34d621fd23..4109e710573 100644 --- a/src/main/java/appeng/server/testplots/AutoCraftingTestPlots.java +++ b/src/main/java/appeng/server/testplots/AutoCraftingTestPlots.java @@ -236,7 +236,7 @@ private static ItemStack encodeCraftingPattern(ServerLevel level, var recipe = level.getRecipeManager().getRecipeFor(RecipeType.CRAFTING, c, level).orElseThrow(); - var result = recipe.assemble(c, level.registryAccess()); + var result = recipe.value().assemble(c, level.registryAccess()); return PatternDetailsHelper.encodeCraftingPattern( recipe, diff --git a/src/main/java/appeng/server/testplots/CraftingPatternHelper.java b/src/main/java/appeng/server/testplots/CraftingPatternHelper.java index 1ab4006173f..34f5753fdb8 100644 --- a/src/main/java/appeng/server/testplots/CraftingPatternHelper.java +++ b/src/main/java/appeng/server/testplots/CraftingPatternHelper.java @@ -26,7 +26,7 @@ public static ItemStack encodeShapelessCraftingRecipe(Level level, ItemStack... return PatternDetailsHelper.encodeCraftingPattern( recipe, actualInputs, - recipe.getResultItem(level.registryAccess()), + recipe.value().getResultItem(level.registryAccess()), false, false); } diff --git a/src/main/java/appeng/server/testplots/KitOutPlayerEvent.java b/src/main/java/appeng/server/testplots/KitOutPlayerEvent.java index 1c5f29a9104..09aa110f115 100644 --- a/src/main/java/appeng/server/testplots/KitOutPlayerEvent.java +++ b/src/main/java/appeng/server/testplots/KitOutPlayerEvent.java @@ -2,10 +2,11 @@ import java.util.function.Consumer; -import net.fabricmc.fabric.api.event.Event; -import net.fabricmc.fabric.api.event.EventFactory; import net.minecraft.server.level.ServerPlayer; +import appeng.api.events.Event; +import appeng.api.events.EventFactory; + public class KitOutPlayerEvent { public static final Event> EVENT = EventFactory.createArrayBacked( Consumer.class, diff --git a/src/main/java/appeng/server/testplots/P2PTestPlots.java b/src/main/java/appeng/server/testplots/P2PTestPlots.java index e60dc9bb429..87c49d17870 100644 --- a/src/main/java/appeng/server/testplots/P2PTestPlots.java +++ b/src/main/java/appeng/server/testplots/P2PTestPlots.java @@ -8,7 +8,6 @@ import org.apache.commons.lang3.mutable.MutableObject; import org.apache.commons.lang3.mutable.MutableShort; -import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.world.item.ItemStack; @@ -16,6 +15,7 @@ import net.minecraft.world.level.LightLayer; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.material.Fluids; +import net.neoforged.neoforge.fluids.FluidStack; import appeng.blockentity.networking.EnergyCellBlockEntity; import appeng.blockentity.storage.SkyStoneTankBlockEntity; @@ -88,10 +88,10 @@ public static void fluid(PlotBuilder plot) { var tank = (SkyStoneTankBlockEntity) helper.getBlockEntity(outputPos); var storage = tank.getStorage(); helper.check( - FluidVariant.of(Fluids.WATER).equals(storage.variant), + new FluidStack(Fluids.WATER, 1).isFluidEqual(storage.getFluid()), "No water stored"); helper.check( - storage.amount > 0, + storage.getFluidAmount() > 0, "No amount >0 stored"); }); }); diff --git a/src/main/java/appeng/server/testplots/TestPlots.java b/src/main/java/appeng/server/testplots/TestPlots.java index 240134b8efa..7a006d8dcfa 100644 --- a/src/main/java/appeng/server/testplots/TestPlots.java +++ b/src/main/java/appeng/server/testplots/TestPlots.java @@ -654,7 +654,8 @@ public static void maxChannelsAdHocTest(PlotBuilder plot) { Set neededIngredients = new HashSet<>(); Set providedResults = new HashSet<>(); - for (var recipe : craftingRecipes) { + for (var holder : craftingRecipes) { + var recipe = holder.value(); if (recipe.isSpecial()) { continue; } @@ -670,7 +671,7 @@ public static void maxChannelsAdHocTest(PlotBuilder plot) { } }).toArray(ItemStack[]::new); craftingPattern = PatternDetailsHelper.encodeCraftingPattern( - recipe, + holder, ingredients, recipe.getResultItem(node.getLevel().registryAccess()), false, @@ -850,9 +851,9 @@ public static void importLavaFromCauldron(PlotBuilder plot) { helper.succeedWhen(() -> { helper.assertBlockPresent(Blocks.CAULDRON, origin.east()); var tank = (SkyStoneTankBlockEntity) helper.getBlockEntity(origin.west()); - helper.check(tank.getStorage().amount == AEFluidKey.AMOUNT_BUCKET, + helper.check(tank.getStorage().getFluidAmount() == AEFluidKey.AMOUNT_BUCKET, "Less than a bucket stored"); - helper.check(tank.getStorage().variant.getFluid() == Fluids.LAVA, + helper.check(tank.getStorage().getFluid().getFluid() == Fluids.LAVA, "Something other than lava stored"); }); }); diff --git a/src/main/java/appeng/siteexport/FabricClientCommandSource.java b/src/main/java/appeng/siteexport/FabricClientCommandSource.java new file mode 100644 index 00000000000..072e449b39a --- /dev/null +++ b/src/main/java/appeng/siteexport/FabricClientCommandSource.java @@ -0,0 +1,19 @@ +package appeng.siteexport; + +import net.minecraft.network.chat.Component; + +public interface FabricClientCommandSource { + /** + * Sends a feedback message to the player. + * + * @param message the feedback message + */ + void sendFeedback(Component message); + + /** + * Sends an error message to the player. + * + * @param message the error message + */ + void sendError(Component message); +} diff --git a/src/main/java/appeng/siteexport/ResourceExporter.java b/src/main/java/appeng/siteexport/ResourceExporter.java index 107c7851de1..2b494358472 100644 --- a/src/main/java/appeng/siteexport/ResourceExporter.java +++ b/src/main/java/appeng/siteexport/ResourceExporter.java @@ -9,7 +9,7 @@ import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.crafting.Recipe; +import net.minecraft.world.item.crafting.RecipeHolder; import net.minecraft.world.level.ItemLike; import net.minecraft.world.level.material.Fluid; @@ -56,5 +56,5 @@ default String getPathRelativeFromOutputFolder(Path p) { */ ResourceLocation getPageSpecificResourceLocation(String suffix); - void referenceRecipe(Recipe recipe); + void referenceRecipe(RecipeHolder recipe); } diff --git a/src/main/java/appeng/siteexport/SiteExportWriter.java b/src/main/java/appeng/siteexport/SiteExportWriter.java index adc7907cd0e..16fd36e8a32 100644 --- a/src/main/java/appeng/siteexport/SiteExportWriter.java +++ b/src/main/java/appeng/siteexport/SiteExportWriter.java @@ -27,8 +27,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import net.fabricmc.fabric.api.transfer.v1.client.fluid.FluidVariantRendering; -import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.nbt.NbtIo; import net.minecraft.resources.ResourceLocation; @@ -46,6 +44,7 @@ import net.minecraft.world.item.crafting.StonecutterRecipe; import net.minecraft.world.level.material.Fluid; import net.minecraft.world.level.material.Fluids; +import net.neoforged.neoforge.fluids.FluidStack; import appeng.client.guidebook.Guide; import appeng.client.guidebook.compiler.MdAstNodeAdapter; @@ -161,15 +160,15 @@ public void addItem(String id, ItemStack stack, String iconPath) { siteExport.items.put(itemInfo.id, itemInfo); } - public void addFluid(String id, FluidVariant fluid, String iconPath) { + public void addFluid(String id, FluidStack fluid, String iconPath) { var fluidInfo = new FluidInfoJson(); fluidInfo.id = id; fluidInfo.icon = iconPath; - fluidInfo.displayName = FluidVariantRendering.getTooltip(fluid).get(0).getString(); + fluidInfo.displayName = fluid.getDisplayName().getString(); siteExport.fluids.put(fluidInfo.id, fluidInfo); } - public void addRecipe(CraftingRecipe recipe) { + public void addRecipe(ResourceLocation id, CraftingRecipe recipe) { Map fields = new HashMap<>(); if (recipe instanceof ShapedRecipe shapedRecipe) { fields.put("shapeless", false); @@ -184,12 +183,12 @@ public void addRecipe(CraftingRecipe recipe) { fields.put("resultCount", resultItem.getCount()); fields.put("ingredients", recipe.getIngredients()); - addRecipe(recipe, fields); + addRecipe(id, recipe, fields); } - public void addRecipe(InscriberRecipe recipe) { + public void addRecipe(ResourceLocation id, InscriberRecipe recipe) { var resultItem = recipe.getResultItem(); - addRecipe(recipe, Map.of( + addRecipe(id, recipe, Map.of( "top", recipe.getTopOptional(), "middle", recipe.getMiddleInput(), "bottom", recipe.getBottomOptional(), @@ -198,13 +197,13 @@ public void addRecipe(InscriberRecipe recipe) { "consumesTopAndBottom", recipe.getProcessType() == InscriberProcessType.PRESS)); } - public void addRecipe(AbstractCookingRecipe recipe) { - addRecipe(recipe, Map.of( + public void addRecipe(ResourceLocation id, AbstractCookingRecipe recipe) { + addRecipe(id, recipe, Map.of( "resultItem", recipe.getResultItem(null), "ingredient", recipe.getIngredients().get(0))); } - public void addRecipe(TransformRecipe recipe) { + public void addRecipe(ResourceLocation id, TransformRecipe recipe) { Map circumstanceJson = new HashMap<>(); var circumstance = recipe.circumstance; @@ -223,58 +222,56 @@ public void addRecipe(TransformRecipe recipe) { throw new IllegalStateException("Unknown circumstance: " + circumstance.toJson()); } - addRecipe(recipe, Map.of( + addRecipe(id, recipe, Map.of( "resultItem", recipe.getResultItem(null), "ingredients", recipe.getIngredients(), "circumstance", circumstanceJson)); } - public void addRecipe(EntropyRecipe recipe) { - addRecipe(recipe, Map.of( + public void addRecipe(ResourceLocation id, EntropyRecipe recipe) { + addRecipe(id, recipe, Map.of( "mode", recipe.getMode().name().toLowerCase(Locale.ROOT))); } - public void addRecipe(MatterCannonAmmo recipe) { - addRecipe(recipe, Map.of( + public void addRecipe(ResourceLocation id, MatterCannonAmmo recipe) { + addRecipe(id, recipe, Map.of( "ammo", recipe.getAmmo(), "damage", MatterCannonItem.getDamageFromPenetration(recipe.getWeight()))); } - public void addRecipe(ChargerRecipe recipe) { - addRecipe(recipe, Map.of( + public void addRecipe(ResourceLocation id, ChargerRecipe recipe) { + addRecipe(id, recipe, Map.of( "resultItem", recipe.getResultItem(), "ingredient", recipe.getIngredient())); } - public void addRecipe(SmithingTransformRecipe recipe) { - addRecipe(recipe, Map.of( + public void addRecipe(ResourceLocation id, SmithingTransformRecipe recipe) { + addRecipe(id, recipe, Map.of( "resultItem", recipe.getResultItem(null), "base", recipe.base, "addition", recipe.addition, "template", recipe.template)); } - public void addRecipe(SmithingTrimRecipe recipe) { - addRecipe(recipe, Map.of( + public void addRecipe(ResourceLocation id, SmithingTrimRecipe recipe) { + addRecipe(id, recipe, Map.of( "base", recipe.base, "addition", recipe.addition, "template", recipe.template)); } - public void addRecipe(StonecutterRecipe recipe) { - addRecipe( - recipe, + public void addRecipe(ResourceLocation id, StonecutterRecipe recipe) { + addRecipe(id, recipe, Map.of( "resultItem", recipe.getResultItem(null), "ingredient", recipe.getIngredients().get(0))); } - public void addRecipe(Recipe recipe, Map element) { + public void addRecipe(ResourceLocation id, Recipe recipe, Map element) { // Auto-transform ingredients var jsonElement = GSON.toJsonTree(element); - var id = recipe.getId(); var type = BuiltInRegistries.RECIPE_TYPE.getKey(recipe.getType()).toString(); jsonElement.getAsJsonObject().addProperty("type", type); diff --git a/src/main/java/appeng/siteexport/SiteExporter.java b/src/main/java/appeng/siteexport/SiteExporter.java index 7853aa59579..e68ae80267d 100644 --- a/src/main/java/appeng/siteexport/SiteExporter.java +++ b/src/main/java/appeng/siteexport/SiteExporter.java @@ -24,12 +24,6 @@ import org.jetbrains.annotations.Nullable; import org.lwjgl.opengl.GL11; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; -import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; -import net.fabricmc.fabric.api.transfer.v1.client.fluid.FluidVariantRendering; -import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant; import net.minecraft.ChatFormatting; import net.minecraft.DetectedVersion; import net.minecraft.client.Minecraft; @@ -44,13 +38,14 @@ import net.minecraft.network.chat.HoverEvent; import net.minecraft.resources.ResourceLocation; import net.minecraft.util.FastColor; +import net.minecraft.world.inventory.InventoryMenu; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.item.crafting.AbstractCookingRecipe; import net.minecraft.world.item.crafting.CraftingRecipe; import net.minecraft.world.item.crafting.Ingredient; -import net.minecraft.world.item.crafting.Recipe; +import net.minecraft.world.item.crafting.RecipeHolder; import net.minecraft.world.item.crafting.SmithingTransformRecipe; import net.minecraft.world.item.crafting.SmithingTrimRecipe; import net.minecraft.world.item.crafting.StonecutterRecipe; @@ -59,6 +54,12 @@ import net.minecraft.world.level.levelgen.SingleThreadedRandomSource; import net.minecraft.world.level.material.Fluid; import net.minecraft.world.level.material.Fluids; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.api.distmarker.OnlyIn; +import net.neoforged.neoforge.client.extensions.common.IClientFluidTypeExtensions; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.TickEvent; +import net.neoforged.neoforge.fluids.FluidStack; import appeng.api.features.P2PTunnelAttunement; import appeng.api.features.P2PTunnelAttunementInternal; @@ -87,7 +88,7 @@ /** * Exports a data package for use by the website. */ -@Environment(EnvType.CLIENT) +@OnlyIn(Dist.CLIENT) public final class SiteExporter implements ResourceExporter { private static final Logger LOGGER = LogManager.getLogger(); @@ -103,7 +104,7 @@ public final class SiteExporter implements ResourceExporter { private ParsedGuidePage currentPage; - private final Set> recipes = new HashSet<>(); + private final Set> recipes = new HashSet<>(); private final Set items = new HashSet<>(); @@ -128,19 +129,22 @@ public static void initialize() { if (Boolean.getBoolean("appeng.runGuideExportAndExit")) { Path outputFolder = Paths.get(System.getProperty("appeng.guideExportFolder")); - ClientTickEvents.END_CLIENT_TICK.register(client -> { - if (client.getOverlay() instanceof LoadingOverlay) { - return; // Do nothing while it's loading + NeoForge.EVENT_BUS.addListener((TickEvent.ClientTickEvent evt) -> { + if (evt.phase == TickEvent.Phase.END) { + var client = Minecraft.getInstance(); + if (client.getOverlay() instanceof LoadingOverlay) { + return; // Do nothing while it's loading + } + + var guide = AppEngClient.instance().getGuide(); + try { + export(client, outputFolder, guide); + } catch (Exception e) { + e.printStackTrace(); + System.exit(1); + } + System.exit(0); } - - var guide = AppEngClient.instance().getGuide(); - try { - export(client, outputFolder, guide); - } catch (Exception e) { - e.printStackTrace(); - System.exit(1); - } - System.exit(0); }); } } @@ -191,11 +195,13 @@ private void referenceIngredient(Ingredient ingredient) { } @Override - public void referenceRecipe(Recipe recipe) { - if (!recipes.add(recipe)) { + public void referenceRecipe(RecipeHolder holder) { + if (!recipes.add(holder)) { return; // Already added } + var recipe = holder.value(); + var registryAccess = Platform.getClientRegistryAccess(); var resultItem = recipe.getResultItem(registryAccess); if (!resultItem.isEmpty()) { @@ -207,32 +213,35 @@ public void referenceRecipe(Recipe recipe) { } private void dumpRecipes(SiteExportWriter writer) { - for (var recipe : recipes) { + for (var holder : recipes) { + var id = holder.id(); + var recipe = holder.value(); + if (recipe instanceof CraftingRecipe craftingRecipe) { if (craftingRecipe.isSpecial()) { continue; } - writer.addRecipe(craftingRecipe); + writer.addRecipe(id, craftingRecipe); } else if (recipe instanceof AbstractCookingRecipe cookingRecipe) { - writer.addRecipe(cookingRecipe); + writer.addRecipe(id, cookingRecipe); } else if (recipe instanceof InscriberRecipe inscriberRecipe) { - writer.addRecipe(inscriberRecipe); + writer.addRecipe(id, inscriberRecipe); } else if (recipe instanceof TransformRecipe transformRecipe) { - writer.addRecipe(transformRecipe); + writer.addRecipe(id, transformRecipe); } else if (recipe instanceof SmithingTransformRecipe smithingTransformRecipe) { - writer.addRecipe(smithingTransformRecipe); + writer.addRecipe(id, smithingTransformRecipe); } else if (recipe instanceof SmithingTrimRecipe smithingTrimRecipe) { - writer.addRecipe(smithingTrimRecipe); + writer.addRecipe(id, smithingTrimRecipe); } else if (recipe instanceof StonecutterRecipe stonecutterRecipe) { - writer.addRecipe(stonecutterRecipe); + writer.addRecipe(id, stonecutterRecipe); } else if (recipe instanceof EntropyRecipe entropyRecipe) { - writer.addRecipe(entropyRecipe); + writer.addRecipe(id, entropyRecipe); } else if (recipe instanceof MatterCannonAmmo ammoRecipe) { - writer.addRecipe(ammoRecipe); + writer.addRecipe(id, ammoRecipe); } else if (recipe instanceof ChargerRecipe chargerRecipe) { - writer.addRecipe(chargerRecipe); + writer.addRecipe(id, chargerRecipe); } else { - LOGGER.warn("Unable to handle recipe {} of type {}", recipe.getId(), recipe.getType()); + LOGGER.warn("Unable to handle recipe {} of type {}", holder.id(), recipe.getType()); } } } @@ -413,7 +422,7 @@ private static void dumpP2PTypes(Set usedVanillaItems, SiteExportWriter si // Export attunement info var attunementInfo = P2PTunnelAttunementInternal.getAttunementInfo(tunnelItem); - attunementInfo.apis().stream().map(lookup -> lookup.apiClass().getName()) + attunementInfo.apis().stream().map(lookup -> lookup.getName()) .forEach(typeInfo.attunementApiClasses::add); usedVanillaItems.addAll(items); @@ -511,12 +520,15 @@ private void processFluids(Minecraft client, LOGGER.info("Exporting fluids..."); for (var fluid : fluids) { - var fluidVariant = FluidVariant.of(fluid); + var fluidVariant = new FluidStack(fluid, 1); String fluidId = BuiltInRegistries.FLUID.getKey(fluid).toString(); - var sprites = FluidVariantRendering.getSprites(fluidVariant); - var sprite = sprites != null ? sprites[0] : null; - var color = FluidVariantRendering.getColor(fluidVariant); + var props = IClientFluidTypeExtensions.of(fluidVariant.getFluid()); + + var sprite = Minecraft.getInstance() + .getTextureAtlas(InventoryMenu.BLOCK_ATLAS) + .apply(props.getStillTexture(fluidVariant)); + var color = props.getTintColor(fluidVariant); var baseName = "!fluids/" + fluidId.replace(':', '/'); var iconPath = renderAndWrite( diff --git a/src/main/java/appeng/sounds/AppEngSounds.java b/src/main/java/appeng/sounds/AppEngSounds.java index 87049de9093..5a04d7718b0 100644 --- a/src/main/java/appeng/sounds/AppEngSounds.java +++ b/src/main/java/appeng/sounds/AppEngSounds.java @@ -1,8 +1,8 @@ package appeng.sounds; -import net.minecraft.core.Registry; import net.minecraft.resources.ResourceLocation; import net.minecraft.sounds.SoundEvent; +import net.neoforged.neoforge.registries.IForgeRegistry; import appeng.core.AppEng; @@ -10,7 +10,7 @@ public final class AppEngSounds { public static final ResourceLocation GUIDE_CLICK_ID = AppEng.makeId("guide.click"); public static SoundEvent GUIDE_CLICK_EVENT = SoundEvent.createVariableRangeEvent(GUIDE_CLICK_ID); - public static void register(Registry registry) { - Registry.register(registry, GUIDE_CLICK_ID, GUIDE_CLICK_EVENT); + public static void register(IForgeRegistry registry) { + registry.register(GUIDE_CLICK_ID, GUIDE_CLICK_EVENT); } } diff --git a/src/main/java/appeng/spatial/SpatialStorageHelper.java b/src/main/java/appeng/spatial/SpatialStorageHelper.java index 51c31834b39..d78e1a80f9b 100644 --- a/src/main/java/appeng/spatial/SpatialStorageHelper.java +++ b/src/main/java/appeng/spatial/SpatialStorageHelper.java @@ -20,8 +20,8 @@ import java.util.ArrayList; import java.util.List; +import java.util.function.Function; -import net.fabricmc.fabric.api.dimension.v1.FabricDimensions; import net.minecraft.core.BlockPos; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; @@ -37,6 +37,7 @@ import net.minecraft.world.level.portal.PortalInfo; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; +import net.neoforged.neoforge.common.util.ITeleporter; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongSet; @@ -111,7 +112,20 @@ private Entity teleportEntity(Entity entity, TelDestination link) { PortalInfo portalInfo = new PortalInfo(new Vec3(link.x, link.y, link.z), Vec3.ZERO, entity.getYRot(), entity.getXRot()); - entity = FabricDimensions.teleport(entity, link.dim, portalInfo); + entity = entity.changeDimension(link.dim, new ITeleporter() { + @Override + public Entity placeEntity(Entity entity, ServerLevel currentLevel, ServerLevel destLevel, float yaw, + Function repositionEntity) { + return repositionEntity.apply(false); + } + + @Override + public PortalInfo getPortalInfo(Entity entity, ServerLevel destLevel, + Function defaultPortalInfo) { + return portalInfo; + } + }); + if (entity != null && !passengersOnOtherSide.isEmpty()) { for (Entity passanger : passengersOnOtherSide) { passanger.startRiding(entity, true); diff --git a/src/main/java/appeng/spatial/SpatialStoragePlotManager.java b/src/main/java/appeng/spatial/SpatialStoragePlotManager.java index 27ffad8df76..6947206ad47 100644 --- a/src/main/java/appeng/spatial/SpatialStoragePlotManager.java +++ b/src/main/java/appeng/spatial/SpatialStoragePlotManager.java @@ -25,6 +25,7 @@ import net.minecraft.core.BlockPos; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.saveddata.SavedData; import appeng.core.AELog; import appeng.core.AppEng; @@ -35,6 +36,11 @@ */ public final class SpatialStoragePlotManager { + private static final SavedData.Factory FACTORY = new SavedData.Factory<>( + SpatialStorageWorldData::new, + SpatialStorageWorldData::load, + null); + public static final SpatialStoragePlotManager INSTANCE = new SpatialStoragePlotManager(); private SpatialStoragePlotManager() { @@ -57,8 +63,7 @@ public ServerLevel getLevel() { private SpatialStorageWorldData getWorldData() { return getLevel().getChunkSource().getDataStorage().computeIfAbsent( - SpatialStorageWorldData::load, - SpatialStorageWorldData::new, + FACTORY, SpatialStorageWorldData.ID); } diff --git a/src/main/java/appeng/spatial/SpatialStorageSkyProperties.java b/src/main/java/appeng/spatial/SpatialStorageSkyProperties.java index 01fbb38ba8e..3cd73e1e87b 100644 --- a/src/main/java/appeng/spatial/SpatialStorageSkyProperties.java +++ b/src/main/java/appeng/spatial/SpatialStorageSkyProperties.java @@ -20,16 +20,16 @@ import org.jetbrains.annotations.Nullable; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; import net.minecraft.client.renderer.DimensionSpecialEffects; import net.minecraft.client.renderer.DimensionSpecialEffects.SkyType; import net.minecraft.world.phys.Vec3; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.api.distmarker.OnlyIn; /** * Defines properties for how the sky in the spatial storage level is rendered. */ -@Environment(EnvType.CLIENT) +@OnlyIn(Dist.CLIENT) public class SpatialStorageSkyProperties { // See the fabric version of this to get any idea what its doing diff --git a/src/main/java/appeng/thirdparty/codechicken/lib/model/pipeline/transformers/QuadClamper.java b/src/main/java/appeng/thirdparty/codechicken/lib/model/pipeline/transformers/QuadClamper.java index d6008869890..248d50503e1 100644 --- a/src/main/java/appeng/thirdparty/codechicken/lib/model/pipeline/transformers/QuadClamper.java +++ b/src/main/java/appeng/thirdparty/codechicken/lib/model/pipeline/transformers/QuadClamper.java @@ -20,11 +20,12 @@ import org.joml.Vector3f; -import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView; -import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; import net.minecraft.util.Mth; import net.minecraft.world.phys.AABB; +import appeng.thirdparty.fabric.MutableQuadView; +import appeng.thirdparty.fabric.RenderContext; + /** * This transformer simply clamps the vertices inside the provided box. You probably want to Re-Interpolate the UV's, * Color, and Lmap, see {@link QuadReInterpolator} diff --git a/src/main/java/appeng/thirdparty/codechicken/lib/model/pipeline/transformers/QuadCornerKicker.java b/src/main/java/appeng/thirdparty/codechicken/lib/model/pipeline/transformers/QuadCornerKicker.java index ab4ac6a27b3..5be45971f33 100644 --- a/src/main/java/appeng/thirdparty/codechicken/lib/model/pipeline/transformers/QuadCornerKicker.java +++ b/src/main/java/appeng/thirdparty/codechicken/lib/model/pipeline/transformers/QuadCornerKicker.java @@ -21,13 +21,14 @@ import static net.minecraft.core.Direction.AxisDirection.NEGATIVE; import static net.minecraft.core.Direction.AxisDirection.POSITIVE; -import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView; -import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; import net.minecraft.core.Direction; import net.minecraft.core.Direction.AxisDirection; import net.minecraft.core.Vec3i; import net.minecraft.world.phys.AABB; +import appeng.thirdparty.fabric.MutableQuadView; +import appeng.thirdparty.fabric.RenderContext; + /** * This transformer is a little complicated. Basically a Facade / Cover can use this to 'kick' the edges in of quads to * fix z-Fighting in the corners. Use it by specifying the side of the block you are on, the bitmask for where the other diff --git a/src/main/java/appeng/thirdparty/codechicken/lib/model/pipeline/transformers/QuadFaceStripper.java b/src/main/java/appeng/thirdparty/codechicken/lib/model/pipeline/transformers/QuadFaceStripper.java index ec93cf30e1b..567339f1fa6 100644 --- a/src/main/java/appeng/thirdparty/codechicken/lib/model/pipeline/transformers/QuadFaceStripper.java +++ b/src/main/java/appeng/thirdparty/codechicken/lib/model/pipeline/transformers/QuadFaceStripper.java @@ -20,11 +20,12 @@ import static net.minecraft.core.Direction.AxisDirection.POSITIVE; -import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView; -import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; import net.minecraft.core.Direction; import net.minecraft.world.phys.AABB; +import appeng.thirdparty.fabric.MutableQuadView; +import appeng.thirdparty.fabric.RenderContext; + /** * This transformer strips quads that are on faces. Simply set the bounds for the faces, and the strip mask. * diff --git a/src/main/java/appeng/thirdparty/codechicken/lib/model/pipeline/transformers/QuadReInterpolator.java b/src/main/java/appeng/thirdparty/codechicken/lib/model/pipeline/transformers/QuadReInterpolator.java index dfd87de84f9..e61518e7581 100644 --- a/src/main/java/appeng/thirdparty/codechicken/lib/model/pipeline/transformers/QuadReInterpolator.java +++ b/src/main/java/appeng/thirdparty/codechicken/lib/model/pipeline/transformers/QuadReInterpolator.java @@ -18,11 +18,10 @@ package appeng.thirdparty.codechicken.lib.model.pipeline.transformers; -import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView; -import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView; -import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; - import appeng.thirdparty.codechicken.lib.math.InterpHelper; +import appeng.thirdparty.fabric.MutableQuadView; +import appeng.thirdparty.fabric.QuadView; +import appeng.thirdparty.fabric.RenderContext; /** * This transformer Re-Interpolates the Color, UV's and LightMaps. Use this after all transformations that translate diff --git a/src/main/java/appeng/thirdparty/codechicken/lib/model/pipeline/transformers/QuadTinter.java b/src/main/java/appeng/thirdparty/codechicken/lib/model/pipeline/transformers/QuadTinter.java index 7a7e61e9a7c..38e0ab8f15e 100644 --- a/src/main/java/appeng/thirdparty/codechicken/lib/model/pipeline/transformers/QuadTinter.java +++ b/src/main/java/appeng/thirdparty/codechicken/lib/model/pipeline/transformers/QuadTinter.java @@ -18,8 +18,8 @@ package appeng.thirdparty.codechicken.lib.model.pipeline.transformers; -import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView; -import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; +import appeng.thirdparty.fabric.MutableQuadView; +import appeng.thirdparty.fabric.RenderContext; /** * This transformer tints quads. diff --git a/src/main/java/appeng/thirdparty/fabric/ColorHelper.java b/src/main/java/appeng/thirdparty/fabric/ColorHelper.java new file mode 100644 index 00000000000..51a8a8bc330 --- /dev/null +++ b/src/main/java/appeng/thirdparty/fabric/ColorHelper.java @@ -0,0 +1,75 @@ +package appeng.thirdparty.fabric; +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.nio.ByteOrder; + +/** + * Static routines of general utility for renderer implementations. Renderers are not required to use these helpers, but + * they were designed to be usable without the default renderer. + */ +public abstract class ColorHelper { + private ColorHelper() { + } + + private static final boolean BIG_ENDIAN = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN; + + /* + * Renderer color format: ARGB (0xAARRGGBB) Vanilla color format (little endian): ABGR (0xAABBGGRR) Vanilla color + * format (big endian): RGBA (0xRRGGBBAA) + * + * Why does the vanilla color format change based on endianness? See VertexConsumer#quad. Quad data is loaded as + * integers into a native byte order buffer. Color is read directly from bytes 12, 13, 14 of each vertex. A + * different byte order will yield different results. + * + * The renderer always uses ARGB because the API color methods always consume and return ARGB. Vanilla block and + * item colors also use ARGB. + */ + + /** + * Converts from ARGB color to ABGR color if little endian or RGBA color if big endian. + */ + public static int toVanillaColor(int color) { + if (color == -1) { + return -1; + } + + if (BIG_ENDIAN) { + // ARGB to RGBA + return ((color & 0x00FFFFFF) << 8) | ((color & 0xFF000000) >>> 24); + } else { + // ARGB to ABGR + return (color & 0xFF00FF00) | ((color & 0x00FF0000) >>> 16) | ((color & 0x000000FF) << 16); + } + } + + /** + * Converts to ARGB color from ABGR color if little endian or RGBA color if big endian. + */ + public static int fromVanillaColor(int color) { + if (color == -1) { + return -1; + } + + if (BIG_ENDIAN) { + // RGBA to ARGB + return ((color & 0xFFFFFF00) >>> 8) | ((color & 0x000000FF) << 24); + } else { + // ABGR to ARGB + return (color & 0xFF00FF00) | ((color & 0x00FF0000) >>> 16) | ((color & 0x000000FF) << 16); + } + } +} diff --git a/src/main/java/appeng/thirdparty/fabric/EncodingFormat.java b/src/main/java/appeng/thirdparty/fabric/EncodingFormat.java new file mode 100644 index 00000000000..7c4c9fd7a56 --- /dev/null +++ b/src/main/java/appeng/thirdparty/fabric/EncodingFormat.java @@ -0,0 +1,133 @@ +package appeng.thirdparty.fabric; +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import com.google.common.base.Preconditions; +import com.mojang.blaze3d.vertex.DefaultVertexFormat; +import com.mojang.blaze3d.vertex.VertexFormat; + +import net.minecraft.core.Direction; +import net.minecraft.util.Mth; + +/** + * Holds all the array offsets and bit-wise encoders/decoders for packing/unpacking quad data in an array of integers. + * All of this is implementation-specific - that's why it isn't a "helper" class. + */ +public abstract class EncodingFormat { + private EncodingFormat() { + } + + static final int HEADER_BITS = 0; + static final int HEADER_COLOR_INDEX = 1; + static final int HEADER_TAG = 2; + public static final int HEADER_STRIDE = 3; + + static final int VERTEX_X; + static final int VERTEX_Y; + static final int VERTEX_Z; + static final int VERTEX_COLOR; + static final int VERTEX_U; + static final int VERTEX_V; + static final int VERTEX_LIGHTMAP; + static final int VERTEX_NORMAL; + public static final int VERTEX_STRIDE; + + public static final int QUAD_STRIDE; + public static final int QUAD_STRIDE_BYTES; + public static final int TOTAL_STRIDE; + + static { + final VertexFormat format = DefaultVertexFormat.BLOCK; + VERTEX_X = HEADER_STRIDE + 0; + VERTEX_Y = HEADER_STRIDE + 1; + VERTEX_Z = HEADER_STRIDE + 2; + VERTEX_COLOR = HEADER_STRIDE + 3; + VERTEX_U = HEADER_STRIDE + 4; + VERTEX_V = VERTEX_U + 1; + VERTEX_LIGHTMAP = HEADER_STRIDE + 6; + VERTEX_NORMAL = HEADER_STRIDE + 7; + VERTEX_STRIDE = format.getIntegerSize(); + QUAD_STRIDE = VERTEX_STRIDE * 4; + QUAD_STRIDE_BYTES = QUAD_STRIDE * 4; + TOTAL_STRIDE = HEADER_STRIDE + QUAD_STRIDE; + + Preconditions.checkState(VERTEX_STRIDE == QuadView.VANILLA_VERTEX_STRIDE, + "Indigo vertex stride (%s) mismatched with rendering API (%s)", VERTEX_STRIDE, + QuadView.VANILLA_VERTEX_STRIDE); + Preconditions.checkState(QUAD_STRIDE == QuadView.VANILLA_QUAD_STRIDE, + "Indigo quad stride (%s) mismatched with rendering API (%s)", QUAD_STRIDE, + QuadView.VANILLA_QUAD_STRIDE); + } + + /** used for quick clearing of quad buffers. */ + static final int[] EMPTY = new int[TOTAL_STRIDE]; + + private static final int DIRECTION_MASK = Mth.smallestEncompassingPowerOfTwo(ModelHelper.NULL_FACE_ID) - 1; + private static final int DIRECTION_BIT_COUNT = Integer.bitCount(DIRECTION_MASK); + private static final int CULL_SHIFT = 0; + private static final int CULL_INVERSE_MASK = ~(DIRECTION_MASK << CULL_SHIFT); + private static final int LIGHT_SHIFT = CULL_SHIFT + DIRECTION_BIT_COUNT; + private static final int LIGHT_INVERSE_MASK = ~(DIRECTION_MASK << LIGHT_SHIFT); + private static final int NORMALS_SHIFT = LIGHT_SHIFT + DIRECTION_BIT_COUNT; + private static final int NORMALS_COUNT = 4; + private static final int NORMALS_MASK = (1 << NORMALS_COUNT) - 1; + private static final int NORMALS_INVERSE_MASK = ~(NORMALS_MASK << NORMALS_SHIFT); + private static final int GEOMETRY_SHIFT = NORMALS_SHIFT + NORMALS_COUNT; + private static final int GEOMETRY_MASK = (1 << GeometryHelper.FLAG_BIT_COUNT) - 1; + private static final int GEOMETRY_INVERSE_MASK = ~(GEOMETRY_MASK << GEOMETRY_SHIFT); + private static final int MATERIAL_SHIFT = GEOMETRY_SHIFT + GeometryHelper.FLAG_BIT_COUNT; + private static final int MATERIAL_MASK = Mth.smallestEncompassingPowerOfTwo(1) - 1; + private static final int MATERIAL_BIT_COUNT = Integer.bitCount(MATERIAL_MASK); + private static final int MATERIAL_INVERSE_MASK = ~(MATERIAL_MASK << MATERIAL_SHIFT); + + static { + Preconditions.checkArgument(MATERIAL_SHIFT + MATERIAL_BIT_COUNT <= 32, + "Indigo header encoding bit count (%s) exceeds integer bit length)", TOTAL_STRIDE); + } + + static Direction cullFace(int bits) { + return ModelHelper.faceFromIndex((bits >> CULL_SHIFT) & DIRECTION_MASK); + } + + static int cullFace(int bits, Direction face) { + return (bits & CULL_INVERSE_MASK) | (ModelHelper.toFaceIndex(face) << CULL_SHIFT); + } + + static Direction lightFace(int bits) { + return ModelHelper.faceFromIndex((bits >> LIGHT_SHIFT) & DIRECTION_MASK); + } + + static int lightFace(int bits, Direction face) { + return (bits & LIGHT_INVERSE_MASK) | (ModelHelper.toFaceIndex(face) << LIGHT_SHIFT); + } + + /** indicate if vertex normal has been set - bits correspond to vertex ordinals. */ + static int normalFlags(int bits) { + return (bits >> NORMALS_SHIFT) & NORMALS_MASK; + } + + static int normalFlags(int bits, int normalFlags) { + return (bits & NORMALS_INVERSE_MASK) | ((normalFlags & NORMALS_MASK) << NORMALS_SHIFT); + } + + static int geometryFlags(int bits) { + return (bits >> GEOMETRY_SHIFT) & GEOMETRY_MASK; + } + + static int geometryFlags(int bits, int geometryFlags) { + return (bits & GEOMETRY_INVERSE_MASK) | ((geometryFlags & GEOMETRY_MASK) << GEOMETRY_SHIFT); + } +} diff --git a/src/main/java/appeng/thirdparty/fabric/GeometryHelper.java b/src/main/java/appeng/thirdparty/fabric/GeometryHelper.java new file mode 100644 index 00000000000..077a2938709 --- /dev/null +++ b/src/main/java/appeng/thirdparty/fabric/GeometryHelper.java @@ -0,0 +1,255 @@ +package appeng.thirdparty.fabric; +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import static net.minecraft.util.Mth.equal; + +import org.jetbrains.annotations.NotNull; +import org.joml.Vector3f; + +import net.minecraft.client.renderer.block.model.BakedQuad; +import net.minecraft.core.Direction; +import net.minecraft.core.Direction.Axis; +import net.minecraft.core.Direction.AxisDirection; + +/** + * Static routines of general utility for renderer implementations. Renderers are not required to use these helpers, but + * they were designed to be usable without the default renderer. + */ +public abstract class GeometryHelper { + private GeometryHelper() { + } + + /** set when a quad touches all four corners of a unit cube. */ + public static final int CUBIC_FLAG = 1; + + /** set when a quad is parallel to (but not necessarily on) a its light face. */ + public static final int AXIS_ALIGNED_FLAG = CUBIC_FLAG << 1; + + /** set when a quad is coplanar with its light face. Implies {@link #AXIS_ALIGNED_FLAG} */ + public static final int LIGHT_FACE_FLAG = AXIS_ALIGNED_FLAG << 1; + + /** how many bits quad header encoding should reserve for encoding geometry flags. */ + public static final int FLAG_BIT_COUNT = 3; + + private static final float EPS_MIN = 0.0001f; + private static final float EPS_MAX = 1.0f - EPS_MIN; + + /** + * Analyzes the quad and returns a value with some combination of {@link #AXIS_ALIGNED_FLAG}, + * {@link #LIGHT_FACE_FLAG} and {@link #CUBIC_FLAG}. Intended use is to optimize lighting when the geometry is + * regular. Expects convex quads with all points co-planar. + */ + public static int computeShapeFlags(QuadView quad) { + Direction lightFace = quad.lightFace(); + int bits = 0; + + if (isQuadParallelToFace(lightFace, quad)) { + bits |= AXIS_ALIGNED_FLAG; + + if (isParallelQuadOnFace(lightFace, quad)) { + bits |= LIGHT_FACE_FLAG; + } + } + + if (isQuadCubic(lightFace, quad)) { + bits |= CUBIC_FLAG; + } + + return bits; + } + + /** + * Returns true if quad is parallel to the given face. Does not validate quad winding order. Expects convex quads + * with all points co-planar. + */ + public static boolean isQuadParallelToFace(Direction face, QuadView quad) { + if (face == null) { + return false; + } + + int i = face.getAxis().ordinal(); + final float val = quad.posByIndex(0, i); + return equal(val, quad.posByIndex(1, i)) && equal(val, quad.posByIndex(2, i)) + && equal(val, quad.posByIndex(3, i)); + } + + /** + * True if quad - already known to be parallel to a face - is actually coplanar with it. For compatibility with + * vanilla resource packs, also true if quad is outside the face. + * + *

+ * Test will be unreliable if not already parallel, use {@link #isQuadParallelToFace(Direction, QuadView)} for that + * purpose. Expects convex quads with all points co-planar. + */ + public static boolean isParallelQuadOnFace(Direction lightFace, QuadView quad) { + if (lightFace == null) + return false; + + final float x = quad.posByIndex(0, lightFace.getAxis().ordinal()); + return lightFace.getAxisDirection() == AxisDirection.POSITIVE ? x >= EPS_MAX : x <= EPS_MIN; + } + + /** + * Returns true if quad is truly a quad (not a triangle) and fills a full block cross-section. If known to be true, + * allows use of a simpler/faster AO lighting algorithm. + * + *

+ * Does not check if quad is actually coplanar with the light face, nor does it check that all quad vertices are + * coplanar with each other. + * + *

+ * Expects convex quads with all points co-planar. + * + * @param lightFace MUST be non-null. + */ + public static boolean isQuadCubic(@NotNull Direction lightFace, QuadView quad) { + if (lightFace == null) { + return false; + } + + int a, b; + + switch (lightFace) { + case EAST: + case WEST: + a = 1; + b = 2; + break; + case UP: + case DOWN: + a = 0; + b = 2; + break; + case SOUTH: + case NORTH: + a = 1; + b = 0; + break; + default: + // handle WTF case + return false; + } + + return confirmSquareCorners(a, b, quad); + } + + /** + * Used by {@link #isQuadCubic(Direction, QuadView)}. True if quad touches all four corners of unit square. + * + *

+ * For compatibility with resource packs that contain models with quads exceeding block boundaries, considers + * corners outside the block to be at the corners. + */ + private static boolean confirmSquareCorners(int aCoordinate, int bCoordinate, QuadView quad) { + int flags = 0; + + for (int i = 0; i < 4; i++) { + final float a = quad.posByIndex(i, aCoordinate); + final float b = quad.posByIndex(i, bCoordinate); + + if (a <= EPS_MIN) { + if (b <= EPS_MIN) { + flags |= 1; + } else if (b >= EPS_MAX) { + flags |= 2; + } else { + return false; + } + } else if (a >= EPS_MAX) { + if (b <= EPS_MIN) { + flags |= 4; + } else if (b >= EPS_MAX) { + flags |= 8; + } else { + return false; + } + } else { + return false; + } + } + + return flags == 15; + } + + /** + * Identifies the face to which the quad is most closely aligned. This mimics the value that + * {@link BakedQuad#getDirection()} returns, and is used in the vanilla renderer for all diffuse lighting. + * + *

+ * Derived from the quad face normal and expects convex quads with all points co-planar. + */ + public static Direction lightFace(QuadView quad) { + final Vector3f normal = quad.faceNormal(); + switch (GeometryHelper.longestAxis(normal)) { + case X: + return normal.x() > 0 ? Direction.EAST : Direction.WEST; + + case Y: + return normal.y() > 0 ? Direction.UP : Direction.DOWN; + + case Z: + return normal.z() > 0 ? Direction.SOUTH : Direction.NORTH; + + default: + // handle WTF case + return Direction.UP; + } + } + + /** + * Simple 4-way compare, doesn't handle NaN values. + */ + public static float min(float a, float b, float c, float d) { + final float x = a < b ? a : b; + final float y = c < d ? c : d; + return x < y ? x : y; + } + + /** + * Simple 4-way compare, doesn't handle NaN values. + */ + public static float max(float a, float b, float c, float d) { + final float x = a > b ? a : b; + final float y = c > d ? c : d; + return x > y ? x : y; + } + + /** + * @see #longestAxis(float, float, float) + */ + public static Axis longestAxis(Vector3f vec) { + return longestAxis(vec.x(), vec.y(), vec.z()); + } + + /** + * Identifies the largest (max absolute magnitude) component (X, Y, Z) in the given vector. + */ + public static Axis longestAxis(float normalX, float normalY, float normalZ) { + Axis result = Axis.Y; + float longest = Math.abs(normalY); + float a = Math.abs(normalX); + + if (a > longest) { + result = Axis.X; + longest = a; + } + + return Math.abs(normalZ) > longest + ? Axis.Z + : result; + } +} diff --git a/src/main/java/appeng/thirdparty/fabric/Mesh.java b/src/main/java/appeng/thirdparty/fabric/Mesh.java new file mode 100644 index 00000000000..3bdbe899590 --- /dev/null +++ b/src/main/java/appeng/thirdparty/fabric/Mesh.java @@ -0,0 +1,50 @@ +package appeng.thirdparty.fabric; +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.util.ArrayList; +import java.util.Collection; +import java.util.function.Consumer; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.block.model.BakedQuad; +import net.minecraft.client.renderer.texture.TextureAtlas; + +/** + * A bundle of one or more {@link QuadView} instances encoded by the renderer. + * + *

+ * Similar in purpose to the {@code List} instances returned by BakedModel, but affords the renderer the + * ability to optimize the format for performance and memory allocation. + * + *

+ * Only the renderer should implement or extend this interface. + */ +public interface Mesh { + /** + * Use to access all of the quads encoded in this mesh. The quad instances sent to the consumer will likely be + * threadlocal/reused and should never be retained by the consumer. + */ + void forEach(Consumer consumer); + + default Collection toBakedBlockQuads() { + SpriteFinder finder = SpriteFinder + .get(Minecraft.getInstance().getModelManager().getAtlas(TextureAtlas.LOCATION_BLOCKS)); + var result = new ArrayList(); + forEach(qv -> result.add(qv.toBakedQuad(finder.find(qv)))); + return result; + } +} diff --git a/src/main/java/appeng/thirdparty/fabric/MeshBuilder.java b/src/main/java/appeng/thirdparty/fabric/MeshBuilder.java new file mode 100644 index 00000000000..b815fe0998c --- /dev/null +++ b/src/main/java/appeng/thirdparty/fabric/MeshBuilder.java @@ -0,0 +1,40 @@ +package appeng.thirdparty.fabric; +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import com.mojang.blaze3d.vertex.BufferBuilder; + +/** + * Similar in purpose to {@link BufferBuilder} but simpler and not tied to NIO or any other specific implementation, + * plus designed to handle both static and dynamic building. + * + *

+ * Decouples models from the vertex format(s) used by ModelRenderer to allow compatibility across diverse + * implementations. + */ +public interface MeshBuilder { + /** + * Returns the {@link QuadEmitter} used to append quad to this mesh. Calling this method a second time invalidates + * any prior result. Do not retain references outside the context of building the mesh. + */ + QuadEmitter getEmitter(); + + /** + * Returns a new {@link Mesh} instance containing all quads added to this builder and resets the builder to an empty + * state. + */ + Mesh build(); +} diff --git a/src/main/java/appeng/thirdparty/fabric/MeshBuilderImpl.java b/src/main/java/appeng/thirdparty/fabric/MeshBuilderImpl.java new file mode 100644 index 00000000000..573a2973d83 --- /dev/null +++ b/src/main/java/appeng/thirdparty/fabric/MeshBuilderImpl.java @@ -0,0 +1,71 @@ +package appeng.thirdparty.fabric; +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Our implementation of {@link MeshBuilder}, used for static mesh creation and baking. Not much to it - mainly it just + * needs to grow the int[] array as quads are appended and maintain/provide a properly-configured + * {@link MutableQuadView} instance. All the encoding and other work is handled in the quad base classes. The one + * interesting bit is in {@link Maker#emit()}. + */ +public class MeshBuilderImpl implements MeshBuilder { + int[] data = new int[256]; + private final Maker maker = new Maker(); + int index = 0; + int limit = data.length; + + protected void ensureCapacity(int stride) { + if (stride > limit - index) { + limit *= 2; + final int[] bigger = new int[limit]; + System.arraycopy(data, 0, bigger, 0, index); + data = bigger; + maker.data = bigger; + } + } + + @Override + public Mesh build() { + final int[] packed = new int[index]; + System.arraycopy(data, 0, packed, 0, index); + index = 0; + maker.begin(data, index); + return new MeshImpl(packed); + } + + @Override + public QuadEmitter getEmitter() { + ensureCapacity(EncodingFormat.TOTAL_STRIDE); + maker.begin(data, index); + return maker; + } + + /** + * Our base classes are used differently so we define final encoding steps in subtypes. This will be a static mesh + * used at render time so we want to capture all geometry now and apply non-location-dependent lighting. + */ + private class Maker extends MutableQuadViewImpl implements QuadEmitter { + @Override + public Maker emit() { + computeGeometry(); + index += EncodingFormat.TOTAL_STRIDE; + ensureCapacity(EncodingFormat.TOTAL_STRIDE); + baseIndex = index; + clear(); + return this; + } + } +} diff --git a/src/main/java/appeng/thirdparty/fabric/MeshImpl.java b/src/main/java/appeng/thirdparty/fabric/MeshImpl.java new file mode 100644 index 00000000000..a7857f32fcc --- /dev/null +++ b/src/main/java/appeng/thirdparty/fabric/MeshImpl.java @@ -0,0 +1,56 @@ +package appeng.thirdparty.fabric; +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.util.function.Consumer; + +/** + * Implementation of {@link Mesh}. The way we encode meshes makes it very simple. + */ +public class MeshImpl implements Mesh { + /** Used to satisfy external calls to {@link #forEach(Consumer)}. */ + ThreadLocal POOL = ThreadLocal.withInitial(QuadViewImpl::new); + + final int[] data; + + MeshImpl(int[] data) { + this.data = data; + } + + public int[] data() { + return data; + } + + @Override + public void forEach(Consumer consumer) { + forEach(consumer, POOL.get()); + } + + /** + * The renderer will call this with it's own cursor to avoid the performance hit of a thread-local lookup. Also + * means renderer can hold final references to quad buffers. + */ + void forEach(Consumer consumer, QuadViewImpl cursor) { + final int limit = data.length; + int index = 0; + + while (index < limit) { + cursor.load(data, index); + consumer.accept(cursor); + index += EncodingFormat.TOTAL_STRIDE; + } + } +} diff --git a/src/main/java/appeng/thirdparty/fabric/ModelHelper.java b/src/main/java/appeng/thirdparty/fabric/ModelHelper.java new file mode 100644 index 00000000000..18aa3d06dc1 --- /dev/null +++ b/src/main/java/appeng/thirdparty/fabric/ModelHelper.java @@ -0,0 +1,137 @@ +package appeng.thirdparty.fabric; +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.util.Arrays; +import java.util.List; + +import com.google.common.collect.ImmutableList; + +import org.jetbrains.annotations.Contract; +import org.joml.Vector3f; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.block.model.BakedQuad; +import net.minecraft.client.renderer.block.model.ItemTransform; +import net.minecraft.client.renderer.block.model.ItemTransforms; +import net.minecraft.client.renderer.texture.TextureAtlas; +import net.minecraft.core.Direction; +import net.minecraft.util.Mth; + +/** + * Collection of utilities for model implementations. + */ +public abstract class ModelHelper { + private ModelHelper() { + } + + /** Result from {@link #toFaceIndex(Direction)} for null values. */ + public static final int NULL_FACE_ID = 6; + + /** + * Convenient way to encode faces that may be null. Null is returned as {@link #NULL_FACE_ID}. Use + * {@link #faceFromIndex(int)} to retrieve encoded face. + */ + public static int toFaceIndex(Direction face) { + return face == null ? NULL_FACE_ID : face.get3DDataValue(); + } + + /** + * Use to decode a result from {@link #toFaceIndex(Direction)}. Return value will be null if encoded value was null. + * Can also be used for no-allocation iteration of {@link Direction#values()}, optionally including the null face. + * (Use < or <= {@link #NULL_FACE_ID} to exclude or include the null value, respectively.) + */ + @Contract("null -> null") + public static Direction faceFromIndex(int faceIndex) { + return FACES[faceIndex]; + } + + /** @see #faceFromIndex(int) */ + private static final Direction[] FACES = Arrays.copyOf(Direction.values(), 7); + + /** + * Converts a mesh into an array of lists of vanilla baked quads. Useful for creating vanilla baked models when + * required for compatibility. The array indexes correspond to {@link Direction#get3DDataValue()} with the addition + * of {@link #NULL_FACE_ID}. + * + *

+ * Retrieves sprites from the block texture atlas via {@link SpriteFinder}. + */ + public static List[] toQuadLists(Mesh mesh) { + SpriteFinder finder = SpriteFinder + .get(Minecraft.getInstance().getModelManager().getAtlas(TextureAtlas.LOCATION_BLOCKS)); + + @SuppressWarnings("unchecked") + final ImmutableList.Builder[] builders = new ImmutableList.Builder[7]; + + for (int i = 0; i < 7; i++) { + builders[i] = ImmutableList.builder(); + } + + if (mesh != null) { + mesh.forEach(q -> { + Direction cullFace = q.cullFace(); + builders[cullFace == null ? NULL_FACE_ID : cullFace.get3DDataValue()] + .add(q.toBakedQuad(finder.find(q))); + }); + } + + @SuppressWarnings("unchecked") + List[] result = new List[7]; + + for (int i = 0; i < 7; i++) { + result[i] = builders[i].build(); + } + + return result; + } + + /** + * The vanilla model transformation logic is closely coupled with model deserialization. That does little good for + * modded model loaders and procedurally generated models. This convenient construction method applies the same + * scaling factors used for vanilla models. This means you can use values from a vanilla JSON file as inputs to this + * method. + */ + private static ItemTransform makeTransform(float rotationX, float rotationY, float rotationZ, float translationX, + float translationY, float translationZ, float scaleX, float scaleY, float scaleZ) { + Vector3f translation = new Vector3f(translationX, translationY, translationZ); + translation.mul(0.0625f); + translation = new Vector3f( + Mth.clamp(translation.x, -5f, 5f), + Mth.clamp(translation.y, -5f, 5f), + Mth.clamp(translation.z, -5f, 5f)); + return new ItemTransform(new Vector3f(rotationX, rotationY, rotationZ), translation, + new Vector3f(scaleX, scaleY, scaleZ)); + } + + public static final ItemTransform TRANSFORM_BLOCK_GUI = makeTransform(30, 225, 0, 0, 0, 0, 0.625f, 0.625f, 0.625f); + public static final ItemTransform TRANSFORM_BLOCK_GROUND = makeTransform(0, 0, 0, 0, 3, 0, 0.25f, 0.25f, 0.25f); + public static final ItemTransform TRANSFORM_BLOCK_FIXED = makeTransform(0, 0, 0, 0, 0, 0, 0.5f, 0.5f, 0.5f); + public static final ItemTransform TRANSFORM_BLOCK_3RD_PERSON_RIGHT = makeTransform(75, 45, 0, 0, 2.5f, 0, 0.375f, + 0.375f, 0.375f); + public static final ItemTransform TRANSFORM_BLOCK_1ST_PERSON_RIGHT = makeTransform(0, 45, 0, 0, 0, 0, 0.4f, 0.4f, + 0.4f); + public static final ItemTransform TRANSFORM_BLOCK_1ST_PERSON_LEFT = makeTransform(0, 225, 0, 0, 0, 0, 0.4f, 0.4f, + 0.4f); + + /** + * Mimics the vanilla model transformation used for most vanilla blocks, and should be suitable for most custom + * block-like models. + */ + public static final ItemTransforms MODEL_TRANSFORM_BLOCK = new ItemTransforms(TRANSFORM_BLOCK_3RD_PERSON_RIGHT, + TRANSFORM_BLOCK_3RD_PERSON_RIGHT, TRANSFORM_BLOCK_1ST_PERSON_LEFT, TRANSFORM_BLOCK_1ST_PERSON_RIGHT, + ItemTransform.NO_TRANSFORM, TRANSFORM_BLOCK_GUI, TRANSFORM_BLOCK_GROUND, TRANSFORM_BLOCK_FIXED); +} diff --git a/src/main/java/appeng/thirdparty/fabric/MutableQuadView.java b/src/main/java/appeng/thirdparty/fabric/MutableQuadView.java new file mode 100644 index 00000000000..69610b8e071 --- /dev/null +++ b/src/main/java/appeng/thirdparty/fabric/MutableQuadView.java @@ -0,0 +1,267 @@ +package appeng.thirdparty.fabric; + +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import org.jetbrains.annotations.Nullable; +import org.joml.Vector2f; +import org.joml.Vector3f; + +import net.minecraft.client.renderer.block.model.BakedQuad; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.core.Direction; + +/** + * A mutable {@link QuadView} instance. The base interface for {@link QuadEmitter} and for dynamic renders/mesh + * transforms. + * + *

+ * Instances of {@link MutableQuadView} will practically always be thread local and/or reused - do not retain + * references. + * + *

+ * Only the renderer should implement or extend this interface. + */ +public interface MutableQuadView extends QuadView { + static MutableQuadView getInstance() { + return MutableQuadViewImpl.THREAD_LOCAL.get(); + } + + /** + * Causes texture to appear with no rotation. Pass in bakeFlags parameter to + * {@link #spriteBake(TextureAtlasSprite, int)}. + */ + int BAKE_ROTATE_NONE = 0; + + /** + * Causes texture to appear rotated 90 deg. clockwise relative to nominal face. Pass in bakeFlags parameter to + * {@link #spriteBake(TextureAtlasSprite, int)}. + */ + int BAKE_ROTATE_90 = 1; + + /** + * Causes texture to appear rotated 180 deg. relative to nominal face. Pass in bakeFlags parameter to + * {@link #spriteBake(TextureAtlasSprite, int)}. + */ + int BAKE_ROTATE_180 = 2; + + /** + * Causes texture to appear rotated 270 deg. clockwise relative to nominal face. Pass in bakeFlags parameter to + * {@link #spriteBake(TextureAtlasSprite, int)}. + */ + int BAKE_ROTATE_270 = 3; + + /** + * When enabled, texture coordinate are assigned based on vertex position. Any existing UV coordinates will be + * replaced. Pass in bakeFlags parameter to {@link #spriteBake(TextureAtlasSprite, int)}. + * + *

+ * UV lock always derives texture coordinates based on nominal face, even when the quad is not co-planar with that + * face, and the result is the same as if the quad were projected onto the nominal face, which is usually the + * desired result. + */ + int BAKE_LOCK_UV = 4; + + /** + * When set, U texture coordinates for the given sprite are flipped as part of baking. Can be useful for some + * randomization and texture mapping scenarios. Results are different from what can be obtained via rotation and + * both can be applied. Pass in bakeFlags parameter to {@link #spriteBake(TextureAtlasSprite, int)}. + */ + int BAKE_FLIP_U = 8; + + /** + * Same as {@link #BAKE_FLIP_U} but for V coordinate. + */ + int BAKE_FLIP_V = 16; + + /** + * UV coordinates by default are assumed to be 0-16 scale for consistency with conventional Minecraft model format. + * This is scaled to 0-1 during baking before interpolation. Model loaders that already have 0-1 coordinates can + * avoid wasteful multiplication/division by passing 0-1 coordinates directly. Pass in bakeFlags parameter to + * {@link #spriteBake(TextureAtlasSprite, int)}. + */ + int BAKE_NORMALIZED = 32; + + /** + * Sets the geometric vertex position for the given vertex, relative to block origin, (0,0,0). Minecraft rendering + * is designed for models that fit within a single block space and is recommended that coordinates remain in the 0-1 + * range, with multi-block meshes split into multiple per-block models. + */ + MutableQuadView pos(int vertexIndex, float x, float y, float z); + + /** + * Same as {@link #pos(int, float, float, float)} but accepts vector type. + */ + default MutableQuadView pos(int vertexIndex, Vector3f pos) { + return pos(vertexIndex, pos.x(), pos.y(), pos.z()); + } + + /** + * Set vertex color. + */ + MutableQuadView color(int vertexIndex, int color); + + /** + * Convenience: set vertex color for all vertices at once. + */ + default MutableQuadView color(int c0, int c1, int c2, int c3) { + color(0, c0); + color(1, c1); + color(2, c2); + color(3, c3); + return this; + } + + /** + * Set texture coordinates. + */ + MutableQuadView uv(int vertexIndex, float u, float v); + + /** + * Set texture coordinates. + * + *

+ * Only use this function if you already have a {@link Vector2f}. Otherwise, see + * {@link MutableQuadView#uv(int, float, float)}. + */ + default MutableQuadView uv(int vertexIndex, Vector2f uv) { + return uv(vertexIndex, uv.x, uv.y); + } + + /** + * Assigns sprite atlas u,v coordinates to this quad for the given sprite. Can handle UV locking, rotation, + * interpolation, etc. Control this behavior by passing additive combinations of the BAKE_ flags defined in this + * interface. + */ + MutableQuadView spriteBake(TextureAtlasSprite sprite, int bakeFlags); + + /** + * Accept vanilla lightmap values. Input values will override lightmap values computed from world state if input + * values are higher. Exposed for completeness but some rendering implementations with non-standard lighting model + * may not honor it. + * + *

+ * For emissive rendering, it is better to use {@link MaterialFinder#emissive(boolean)}. + */ + MutableQuadView lightmap(int vertexIndex, int lightmap); + + /** + * Convenience: set lightmap for all vertices at once. + * + *

+ * For emissive rendering, it is better to use {@link MaterialFinder#emissive(boolean)}. See + * {@link #lightmap(int, int)}. + */ + default MutableQuadView lightmap(int b0, int b1, int b2, int b3) { + lightmap(0, b0); + lightmap(1, b1); + lightmap(2, b2); + lightmap(3, b3); + return this; + } + + /** + * Adds a vertex normal. Models that have per-vertex normals should include them to get correct lighting when it + * matters. Computed face normal is used when no vertex normal is provided. + * + *

+ * {@link Renderer} implementations should honor vertex normals for diffuse lighting - modifying vertex color(s) or + * packing normals in the vertex buffer as appropriate for the rendering method/vertex format in effect. + */ + MutableQuadView normal(int vertexIndex, float x, float y, float z); + + /** + * Same as {@link #normal(int, float, float, float)} but accepts vector type. + */ + default MutableQuadView normal(int vertexIndex, Vector3f normal) { + return normal(vertexIndex, normal.x(), normal.y(), normal.z()); + } + + /** + * If non-null, quad is coplanar with a block face which, if known, simplifies or shortcuts geometric analysis that + * might otherwise be needed. Set to null if quad is not coplanar or if this is not known. Also controls face + * culling during block rendering. + * + *

+ * Null by default. + * + *

+ * When called with a non-null value, also sets {@link #nominalFace(Direction)} to the same value. + * + *

+ * This is different from the value reported by {@link BakedQuad#getDirection()}. That value is computed based on + * face geometry and must be non-null in vanilla quads. That computed value is returned by {@link #lightFace()}. + */ + MutableQuadView cullFace(@Nullable Direction face); + + /** + * Provides a hint to renderer about the facing of this quad. Not required, but if provided can shortcut some + * geometric analysis if the quad is parallel to a block face. Should be the expected value of {@link #lightFace()}. + * Value will be confirmed and if invalid the correct light face will be calculated. + * + *

+ * Null by default, and set automatically by {@link #cullFace()}. + * + *

+ * Models may also find this useful as the face for texture UV locking and rotation semantics. + * + *

+ * Note: This value is not persisted independently when the quad is encoded. When reading encoded quads, this value + * will always be the same as {@link #lightFace()}. + */ + MutableQuadView nominalFace(@Nullable Direction face); + + /** + * Value functions identically to {@link BakedQuad#getTintIndex()} and is used by renderer / model builder in same + * way. Default value is -1. + */ + MutableQuadView colorIndex(int colorIndex); + + /** + * Encodes an integer tag with this quad that can later be retrieved via {@link QuadView#tag()}. Useful for models + * that want to perform conditional transformation or filtering on static meshes. + */ + MutableQuadView tag(int tag); + + /** + * Enables bulk vertex data transfer using the standard Minecraft vertex formats. Only the + * {@link BakedQuad#getVertices() quad vertex data} is copied. This method should be performant whenever caller's + * vertex representation makes it feasible. + * + *

+ * Use {@link #fromVanilla(BakedQuad, RenderMaterial, Direction) the other overload} which has better encapsulation + * unless you have a specific reason to use this one. + * + *

+ * Calling this method does not emit the quad. + */ + MutableQuadView fromVanilla(int[] quadData, int startIndex); + + /** + * Enables bulk vertex data transfer using the standard Minecraft quad format. + * + *

+ * Calling this method does not emit the quad. + * + *

+ * The material applied to this quad view might be slightly different from the {@code material} parameter regarding + * diffuse shading. If either the baked quad {@link BakedQuad#isShade() does not have shade} or the material + * {@link MaterialFinder#disableDiffuse(boolean) does not have shade}, diffuse shading will be disabled for this + * quad view. This is reflected in the quad view's {@link #material()}, but the {@code material} parameter is + * unchanged (it is immutable anyway). + */ + MutableQuadView fromVanilla(BakedQuad quad, @Nullable Direction cullFace); +} diff --git a/src/main/java/appeng/thirdparty/fabric/MutableQuadViewImpl.java b/src/main/java/appeng/thirdparty/fabric/MutableQuadViewImpl.java new file mode 100644 index 00000000000..e6f98d41953 --- /dev/null +++ b/src/main/java/appeng/thirdparty/fabric/MutableQuadViewImpl.java @@ -0,0 +1,186 @@ +package appeng.thirdparty.fabric; +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import static appeng.thirdparty.fabric.EncodingFormat.EMPTY; +import static appeng.thirdparty.fabric.EncodingFormat.HEADER_BITS; +import static appeng.thirdparty.fabric.EncodingFormat.HEADER_COLOR_INDEX; +import static appeng.thirdparty.fabric.EncodingFormat.HEADER_STRIDE; +import static appeng.thirdparty.fabric.EncodingFormat.HEADER_TAG; +import static appeng.thirdparty.fabric.EncodingFormat.VERTEX_COLOR; +import static appeng.thirdparty.fabric.EncodingFormat.VERTEX_LIGHTMAP; +import static appeng.thirdparty.fabric.EncodingFormat.VERTEX_NORMAL; +import static appeng.thirdparty.fabric.EncodingFormat.VERTEX_STRIDE; +import static appeng.thirdparty.fabric.EncodingFormat.VERTEX_U; +import static appeng.thirdparty.fabric.EncodingFormat.VERTEX_X; + +import org.jetbrains.annotations.Nullable; + +import net.minecraft.client.renderer.block.model.BakedQuad; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.core.Direction; + +/** + * Almost-concrete implementation of a mutable quad. The only missing part is {@link #emit()}, because that depends on + * where/how it is used. (Mesh encoding vs. render-time transformation). + */ +public abstract class MutableQuadViewImpl extends QuadViewImpl implements QuadEmitter { + public static final ThreadLocal THREAD_LOCAL = ThreadLocal + .withInitial(() -> new MutableQuadViewImpl() { + { + begin(new int[EncodingFormat.TOTAL_STRIDE], 0); + } + + @Override + public QuadEmitter emit() { + throw new UnsupportedOperationException(); + } + }); + + public final void begin(int[] data, int baseIndex) { + this.data = data; + this.baseIndex = baseIndex; + clear(); + } + + public void clear() { + System.arraycopy(EMPTY, 0, data, baseIndex, EncodingFormat.TOTAL_STRIDE); + isGeometryInvalid = true; + nominalFace = null; + normalFlags(0); + tag(0); + colorIndex(-1); + cullFace(null); + } + + @Override + public MutableQuadViewImpl pos(int vertexIndex, float x, float y, float z) { + final int index = baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_X; + data[index] = Float.floatToRawIntBits(x); + data[index + 1] = Float.floatToRawIntBits(y); + data[index + 2] = Float.floatToRawIntBits(z); + isGeometryInvalid = true; + return this; + } + + @Override + public MutableQuadViewImpl color(int vertexIndex, int color) { + data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_COLOR] = color; + return this; + } + + @Override + public MutableQuadViewImpl uv(int vertexIndex, float u, float v) { + final int i = baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_U; + data[i] = Float.floatToRawIntBits(u); + data[i + 1] = Float.floatToRawIntBits(v); + return this; + } + + @Override + public MutableQuadViewImpl spriteBake(TextureAtlasSprite sprite, int bakeFlags) { + TextureHelper.bakeSprite(this, sprite, bakeFlags); + return this; + } + + @Override + public MutableQuadViewImpl lightmap(int vertexIndex, int lightmap) { + data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_LIGHTMAP] = lightmap; + return this; + } + + protected void normalFlags(int flags) { + data[baseIndex + HEADER_BITS] = EncodingFormat.normalFlags(data[baseIndex + HEADER_BITS], flags); + } + + @Override + public MutableQuadViewImpl normal(int vertexIndex, float x, float y, float z) { + normalFlags(normalFlags() | (1 << vertexIndex)); + data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_NORMAL] = NormalHelper.packNormal(x, y, z, 0); + return this; + } + + /** + * Internal helper method. Copies face normals to vertex normals lacking one. + */ + public final void populateMissingNormals() { + final int normalFlags = this.normalFlags(); + + if (normalFlags == 0b1111) + return; + + final int packedFaceNormal = NormalHelper.packNormal(faceNormal(), 0); + + for (int v = 0; v < 4; v++) { + if ((normalFlags & (1 << v)) == 0) { + data[baseIndex + v * VERTEX_STRIDE + VERTEX_NORMAL] = packedFaceNormal; + } + } + + normalFlags(0b1111); + } + + @Override + public final MutableQuadViewImpl cullFace(@Nullable Direction face) { + data[baseIndex + HEADER_BITS] = EncodingFormat.cullFace(data[baseIndex + HEADER_BITS], face); + nominalFace(face); + return this; + } + + @Override + public final MutableQuadViewImpl nominalFace(@Nullable Direction face) { + nominalFace = face; + return this; + } + + @Override + public final MutableQuadViewImpl colorIndex(int colorIndex) { + data[baseIndex + HEADER_COLOR_INDEX] = colorIndex; + return this; + } + + @Override + public final MutableQuadViewImpl tag(int tag) { + data[baseIndex + HEADER_TAG] = tag; + return this; + } + + @Override + public final MutableQuadViewImpl fromVanilla(int[] quadData, int startIndex) { + System.arraycopy(quadData, startIndex, data, baseIndex + HEADER_STRIDE, VANILLA_QUAD_STRIDE); + isGeometryInvalid = true; + + int colorIndex = baseIndex + VERTEX_COLOR; + + for (int i = 0; i < 4; i++) { + data[colorIndex] = ColorHelper.fromVanillaColor(data[colorIndex]); + colorIndex += VERTEX_STRIDE; + } + + return this; + } + + @Override + public final MutableQuadViewImpl fromVanilla(BakedQuad quad, @Nullable Direction cullFace) { + fromVanilla(quad.getVertices(), 0); + data[baseIndex + HEADER_BITS] = EncodingFormat.cullFace(0, cullFace); + nominalFace(quad.getDirection()); + colorIndex(quad.getTintIndex()); + + tag(0); + return this; + } +} diff --git a/src/main/java/appeng/thirdparty/fabric/NormalHelper.java b/src/main/java/appeng/thirdparty/fabric/NormalHelper.java new file mode 100644 index 00000000000..8e2a817bab0 --- /dev/null +++ b/src/main/java/appeng/thirdparty/fabric/NormalHelper.java @@ -0,0 +1,116 @@ +package appeng.thirdparty.fabric; +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import org.jetbrains.annotations.NotNull; +import org.joml.Vector3f; + +import net.minecraft.core.Direction; +import net.minecraft.core.Vec3i; +import net.minecraft.util.Mth; + +/** + * Static routines of general utility for renderer implementations. Renderers are not required to use these helpers, but + * they were designed to be usable without the default renderer. + */ +public abstract class NormalHelper { + private NormalHelper() { + } + + /** + * Stores a normal plus an extra value as a quartet of signed bytes. This is the same normal format that vanilla + * item rendering expects. The extra value is for use by shaders. + */ + public static int packNormal(float x, float y, float z, float w) { + x = Mth.clamp(x, -1, 1); + y = Mth.clamp(y, -1, 1); + z = Mth.clamp(z, -1, 1); + w = Mth.clamp(w, -1, 1); + + return ((int) (x * 127) & 255) | (((int) (y * 127) & 255) << 8) | (((int) (z * 127) & 255) << 16) + | (((int) (w * 127) & 255) << 24); + } + + /** + * Version of {@link #packNormal(float, float, float, float)} that accepts a vector type. + */ + public static int packNormal(Vector3f normal, float w) { + return packNormal(normal.x(), normal.y(), normal.z(), w); + } + + /** + * Retrieves values packed by {@link #packNormal(float, float, float, float)}. + * + *

+ * Components are x, y, z, w - zero based. + */ + public static float getPackedNormalComponent(int packedNormal, int component) { + return ((byte) (packedNormal >> (8 * component))) / 127f; + } + + /** + * Computes the face normal of the given quad and saves it in the provided non-null vector. If + * {@link QuadView#nominalFace()} is set will optimize by confirming quad is parallel to that face and, if so, use + * the standard normal for that face direction. + * + *

+ * Will work with triangles also. Assumes counter-clockwise winding order, which is the norm. Expects convex quads + * with all points co-planar. + */ + public static void computeFaceNormal(@NotNull Vector3f saveTo, QuadView q) { + final Direction nominalFace = q.nominalFace(); + + if (GeometryHelper.isQuadParallelToFace(nominalFace, q)) { + Vec3i vec = nominalFace.getNormal(); + saveTo.set(vec.getX(), vec.getY(), vec.getZ()); + return; + } + + final float x0 = q.x(0); + final float y0 = q.y(0); + final float z0 = q.z(0); + final float x1 = q.x(1); + final float y1 = q.y(1); + final float z1 = q.z(1); + final float x2 = q.x(2); + final float y2 = q.y(2); + final float z2 = q.z(2); + final float x3 = q.x(3); + final float y3 = q.y(3); + final float z3 = q.z(3); + + final float dx0 = x2 - x0; + final float dy0 = y2 - y0; + final float dz0 = z2 - z0; + final float dx1 = x3 - x1; + final float dy1 = y3 - y1; + final float dz1 = z3 - z1; + + float normX = dy0 * dz1 - dz0 * dy1; + float normY = dz0 * dx1 - dx0 * dz1; + float normZ = dx0 * dy1 - dy0 * dx1; + + float l = (float) Math.sqrt(normX * normX + normY * normY + normZ * normZ); + + if (l != 0) { + normX /= l; + normY /= l; + normZ /= l; + } + + saveTo.set(normX, normY, normZ); + } +} diff --git a/src/main/java/appeng/thirdparty/fabric/QuadEmitter.java b/src/main/java/appeng/thirdparty/fabric/QuadEmitter.java new file mode 100644 index 00000000000..5c857c1e9d4 --- /dev/null +++ b/src/main/java/appeng/thirdparty/fabric/QuadEmitter.java @@ -0,0 +1,164 @@ +package appeng.thirdparty.fabric; + +import org.joml.Vector2f; +import org.joml.Vector3f; + +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.core.Direction; + +/** + * Specialized {@link MutableQuadView} obtained via {@link MeshBuilder#getEmitter()} to append quads during mesh + * building. + * + *

+ * Instances of {@link QuadEmitter} will practically always be threadlocal and/or reused - do not retain references. + * + *

+ * Only the renderer should implement or extend this interface. + */ +public interface QuadEmitter extends MutableQuadView { + @Override + QuadEmitter pos(int vertexIndex, float x, float y, float z); + + @Override + default QuadEmitter pos(int vertexIndex, Vector3f pos) { + MutableQuadView.super.pos(vertexIndex, pos); + return this; + } + + @Override + QuadEmitter color(int vertexIndex, int color); + + @Override + default QuadEmitter color(int c0, int c1, int c2, int c3) { + MutableQuadView.super.color(c0, c1, c2, c3); + return this; + } + + @Override + QuadEmitter uv(int vertexIndex, float u, float v); + + @Override + default QuadEmitter uv(int vertexIndex, Vector2f uv) { + MutableQuadView.super.uv(vertexIndex, uv); + return this; + } + + @Override + QuadEmitter spriteBake(TextureAtlasSprite sprite, int bakeFlags); + + default QuadEmitter uvUnitSquare() { + uv(0, 0, 0); + uv(1, 0, 1); + uv(2, 1, 1); + uv(3, 1, 0); + return this; + } + + @Override + QuadEmitter lightmap(int vertexIndex, int lightmap); + + @Override + default QuadEmitter lightmap(int b0, int b1, int b2, int b3) { + MutableQuadView.super.lightmap(b0, b1, b2, b3); + return this; + } + + @Override + QuadEmitter normal(int vertexIndex, float x, float y, float z); + + @Override + default QuadEmitter normal(int vertexIndex, Vector3f normal) { + MutableQuadView.super.normal(vertexIndex, normal); + return this; + } + + @Override + QuadEmitter cullFace(Direction face); + + @Override + QuadEmitter nominalFace(Direction face); + + @Override + QuadEmitter colorIndex(int colorIndex); + + @Override + QuadEmitter tag(int tag); + + @Override + QuadEmitter fromVanilla(int[] quadData, int startIndex); + + /** + * Tolerance for determining if the depth parameter to {@link #square(Direction, float, float, float, float, float)} + * is effectively zero - meaning the face is a cull face. + */ + float CULL_FACE_EPSILON = 0.00001f; + + /** + * Helper method to assign vertex coordinates for a square aligned with the given face. Ensures that vertex order is + * consistent with vanilla convention. (Incorrect order can lead to bad AO lighting unless enhanced lighting logic + * is available/enabled.) + * + *

+ * Square will be parallel to the given face and coplanar with the face (and culled if the face is occluded) if the + * depth parameter is approximately zero. See {@link #CULL_FACE_EPSILON}. + * + *

+ * All coordinates should be normalized (0-1). + */ + default QuadEmitter square(Direction nominalFace, float left, float bottom, float right, float top, float depth) { + if (Math.abs(depth) < CULL_FACE_EPSILON) { + cullFace(nominalFace); + depth = 0; // avoid any inconsistency for face quads + } else { + cullFace(null); + } + + nominalFace(nominalFace); + switch (nominalFace) { + case UP: + depth = 1 - depth; + top = 1 - top; + bottom = 1 - bottom; + + case DOWN: + pos(0, left, depth, top); + pos(1, left, depth, bottom); + pos(2, right, depth, bottom); + pos(3, right, depth, top); + break; + + case EAST: + depth = 1 - depth; + left = 1 - left; + right = 1 - right; + + case WEST: + pos(0, depth, top, left); + pos(1, depth, bottom, left); + pos(2, depth, bottom, right); + pos(3, depth, top, right); + break; + + case SOUTH: + depth = 1 - depth; + left = 1 - left; + right = 1 - right; + + case NORTH: + pos(0, 1 - left, top, depth); + pos(1, 1 - left, bottom, depth); + pos(2, 1 - right, bottom, depth); + pos(3, 1 - right, top, depth); + break; + } + + return this; + } + + /** + * In static mesh building, causes quad to be appended to the mesh being built. In a dynamic render context, create + * a new quad to be output to rendering. In both cases, current instance is reset to default values. + */ + QuadEmitter emit(); +} diff --git a/src/main/java/appeng/thirdparty/fabric/QuadView.java b/src/main/java/appeng/thirdparty/fabric/QuadView.java new file mode 100644 index 00000000000..5cc93fbc072 --- /dev/null +++ b/src/main/java/appeng/thirdparty/fabric/QuadView.java @@ -0,0 +1,208 @@ +package appeng.thirdparty.fabric; +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import com.mojang.blaze3d.vertex.DefaultVertexFormat; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.joml.Vector2f; +import org.joml.Vector3f; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.block.model.BakedQuad; +import net.minecraft.client.renderer.texture.TextureAtlas; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.core.Direction; + +/** + * Interface for reading quad data encoded by {@link MeshBuilder}. Enables models to do analysis, re-texturing or + * translation without knowing the renderer's vertex formats and without retaining redundant information. + * + *

+ * Only the renderer should implement or extend this interface. + */ +public interface QuadView { + /** Count of integers in a conventional (un-modded) block or item vertex. */ + int VANILLA_VERTEX_STRIDE = DefaultVertexFormat.BLOCK.getIntegerSize(); + + /** Count of integers in a conventional (un-modded) block or item quad. */ + int VANILLA_QUAD_STRIDE = VANILLA_VERTEX_STRIDE * 4; + + /** + * Retrieve geometric position, x coordinate. + */ + float x(int vertexIndex); + + /** + * Retrieve geometric position, y coordinate. + */ + float y(int vertexIndex); + + /** + * Retrieve geometric position, z coordinate. + */ + float z(int vertexIndex); + + /** + * Convenience: access x, y, z by index 0-2. + */ + float posByIndex(int vertexIndex, int coordinateIndex); + + /** + * Pass a non-null target to avoid allocation - will be returned with values. Otherwise returns a new instance. + */ + Vector3f copyPos(int vertexIndex, @Nullable Vector3f target); + + /** + * Retrieve vertex color. + */ + int color(int vertexIndex); + + /** + * Retrieve horizontal texture coordinates. + */ + float u(int vertexIndex); + + /** + * Retrieve vertical texture coordinates. + */ + float v(int vertexIndex); + + /** + * Pass a non-null target to avoid allocation - will be returned with values. Otherwise returns a new instance. + */ + default Vector2f copyUv(int vertexIndex, @Nullable Vector2f target) { + if (target == null) { + target = new Vector2f(); + } + + target.set(u(vertexIndex), v(vertexIndex)); + return target; + } + + /** + * Minimum block brightness. Zero if not set. + */ + int lightmap(int vertexIndex); + + /** + * If false, no vertex normal was provided. Lighting should use face normal in that case. + */ + boolean hasNormal(int vertexIndex); + + /** + * Will return {@link Float#NaN} if normal not present. + */ + float normalX(int vertexIndex); + + /** + * Will return {@link Float#NaN} if normal not present. + */ + float normalY(int vertexIndex); + + /** + * Will return {@link Float#NaN} if normal not present. + */ + float normalZ(int vertexIndex); + + /** + * Pass a non-null target to avoid allocation - will be returned with values. Otherwise returns a new instance. + * Returns null if normal not present. + */ + @Nullable + Vector3f copyNormal(int vertexIndex, @Nullable Vector3f target); + + /** + * If non-null, quad should not be rendered in-world if the opposite face of a neighbor block occludes it. + * + * @see MutableQuadView#cullFace(Direction) + */ + @Nullable + Direction cullFace(); + + /** + * Equivalent to {@link BakedQuad#getDirection()}. This is the face used for vanilla lighting calculations and will + * be the block face to which the quad is most closely aligned. Always the same as cull face for quads that are on a + * block face, but never null. + */ + @NotNull + Direction lightFace(); + + /** + * See {@link MutableQuadView#nominalFace(Direction)}. + */ + Direction nominalFace(); + + /** + * Normal of the quad as implied by geometry. Will be invalid if quad vertices are not co-planar. Typically computed + * lazily on demand and not encoded. + * + *

+ * Not typically needed by models. Exposed to enable standard lighting utility functions for use by renderers. + */ + Vector3f faceNormal(); + + /** + * Retrieves the quad color index serialized with the quad. + */ + int colorIndex(); + + /** + * Retrieves the integer tag encoded with this quad via {@link MutableQuadView#tag(int)}. Will return zero if no tag + * was set. For use by models. + */ + int tag(); + + /** + * Extracts all quad properties except material to the given {@link MutableQuadView} instance. Must be used before + * calling {link QuadEmitter#emit()} on the target instance. Meant for re-texturing, analysis and static + * transformation use cases. + */ + void copyTo(MutableQuadView target); + + /** + * Reads baked vertex data and outputs standard {@link BakedQuad#getVertices() baked quad vertex data} in the given + * array and location. + * + * @param target Target array for the baked quad data. + * + * @param targetIndex Starting position in target array - array must have at least 28 elements available at this + * index. + */ + void toVanilla(int[] target, int targetIndex); + + /** + * Generates a new BakedQuad instance with texture coordinates and colors from the given sprite. + * + * @param sprite {@link MutableQuadView} does not serialize sprites so the sprite must be provided by the caller. + * + * @return A new baked quad instance with the closest-available appearance supported by vanilla features. Will + * retain emissive light maps, for example, but the standard Minecraft renderer will not use them. + */ + default BakedQuad toBakedQuad(TextureAtlasSprite sprite) { + int[] vertexData = new int[VANILLA_QUAD_STRIDE]; + toVanilla(vertexData, 0); + // TODO material inspection: set shade as !disableDiffuse + // TODO material inspection: set color index to -1 if the material disables it + return new BakedQuad(vertexData, colorIndex(), lightFace(), sprite, true); + } + + default BakedQuad toBlockBakedQuad() { + var finder = SpriteFinder.get(Minecraft.getInstance().getModelManager().getAtlas(TextureAtlas.LOCATION_BLOCKS)); + return toBakedQuad(finder.find(this)); + } +} diff --git a/src/main/java/appeng/thirdparty/fabric/QuadViewImpl.java b/src/main/java/appeng/thirdparty/fabric/QuadViewImpl.java new file mode 100644 index 00000000000..208cad5a298 --- /dev/null +++ b/src/main/java/appeng/thirdparty/fabric/QuadViewImpl.java @@ -0,0 +1,275 @@ +package appeng.thirdparty.fabric; +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import static appeng.thirdparty.fabric.EncodingFormat.HEADER_BITS; +import static appeng.thirdparty.fabric.EncodingFormat.HEADER_COLOR_INDEX; +import static appeng.thirdparty.fabric.EncodingFormat.HEADER_STRIDE; +import static appeng.thirdparty.fabric.EncodingFormat.HEADER_TAG; +import static appeng.thirdparty.fabric.EncodingFormat.QUAD_STRIDE; +import static appeng.thirdparty.fabric.EncodingFormat.VERTEX_COLOR; +import static appeng.thirdparty.fabric.EncodingFormat.VERTEX_LIGHTMAP; +import static appeng.thirdparty.fabric.EncodingFormat.VERTEX_NORMAL; +import static appeng.thirdparty.fabric.EncodingFormat.VERTEX_STRIDE; +import static appeng.thirdparty.fabric.EncodingFormat.VERTEX_U; +import static appeng.thirdparty.fabric.EncodingFormat.VERTEX_V; +import static appeng.thirdparty.fabric.EncodingFormat.VERTEX_X; +import static appeng.thirdparty.fabric.EncodingFormat.VERTEX_Y; +import static appeng.thirdparty.fabric.EncodingFormat.VERTEX_Z; + +import org.joml.Vector3f; + +import net.minecraft.core.Direction; + +/** + * Base class for all quads / quad makers. Handles the ugly bits of maintaining and encoding the quad state. + */ +public class QuadViewImpl implements QuadView { + protected Direction nominalFace; + /** True when geometry flags or light face may not match geometry. */ + protected boolean isGeometryInvalid = true; + protected final Vector3f faceNormal = new Vector3f(); + private boolean shade = true; + + /** Size and where it comes from will vary in subtypes. But in all cases quad is fully encoded to array. */ + protected int[] data; + + /** Beginning of the quad. Also the header index. */ + protected int baseIndex = 0; + + /** + * Use when subtype is "attached" to a pre-existing array. Sets data reference and index and decodes state from + * array. + */ + final void load(int[] data, int baseIndex) { + this.data = data; + this.baseIndex = baseIndex; + load(); + } + + /** + * Like {@link #load(int[], int)} but assumes array and index already set. Only does the decoding part. + */ + public final void load() { + isGeometryInvalid = false; + nominalFace = lightFace(); + + // face normal isn't encoded + NormalHelper.computeFaceNormal(faceNormal, this); + } + + /** Reference to underlying array. Use with caution. Meant for fast renderer access */ + public int[] data() { + return data; + } + + public int normalFlags() { + return EncodingFormat.normalFlags(data[baseIndex + HEADER_BITS]); + } + + /** True if any vertex normal has been set. */ + public boolean hasVertexNormals() { + return normalFlags() != 0; + } + + /** gets flags used for lighting - lazily computed via {@link GeometryHelper#computeShapeFlags(QuadView)}. */ + public int geometryFlags() { + computeGeometry(); + return EncodingFormat.geometryFlags(data[baseIndex + HEADER_BITS]); + } + + protected void computeGeometry() { + if (isGeometryInvalid) { + isGeometryInvalid = false; + + NormalHelper.computeFaceNormal(faceNormal, this); + + // depends on face normal + data[baseIndex + HEADER_BITS] = EncodingFormat.lightFace(data[baseIndex + HEADER_BITS], + GeometryHelper.lightFace(this)); + + // depends on light face + data[baseIndex + HEADER_BITS] = EncodingFormat.geometryFlags(data[baseIndex + HEADER_BITS], + GeometryHelper.computeShapeFlags(this)); + } + } + + @Override + public final int colorIndex() { + return data[baseIndex + HEADER_COLOR_INDEX]; + } + + @Override + public final int tag() { + return data[baseIndex + HEADER_TAG]; + } + + @Override + public final Direction lightFace() { + computeGeometry(); + return EncodingFormat.lightFace(data[baseIndex + HEADER_BITS]); + } + + @Override + public final Direction cullFace() { + return EncodingFormat.cullFace(data[baseIndex + HEADER_BITS]); + } + + @Override + public final Direction nominalFace() { + return nominalFace; + } + + @Override + public final Vector3f faceNormal() { + computeGeometry(); + return faceNormal; + } + + @Override + public void copyTo(MutableQuadView target) { + computeGeometry(); + + final MutableQuadViewImpl quad = (MutableQuadViewImpl) target; + // copy everything except the material + System.arraycopy(data, baseIndex, quad.data, quad.baseIndex, EncodingFormat.TOTAL_STRIDE); + quad.faceNormal.set(faceNormal.x(), faceNormal.y(), faceNormal.z()); + quad.nominalFace = this.nominalFace; + quad.isGeometryInvalid = false; + } + + @Override + public Vector3f copyPos(int vertexIndex, Vector3f target) { + if (target == null) { + target = new Vector3f(); + } + + final int index = baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_X; + target.set(Float.intBitsToFloat(data[index]), Float.intBitsToFloat(data[index + 1]), + Float.intBitsToFloat(data[index + 2])); + return target; + } + + @Override + public float posByIndex(int vertexIndex, int coordinateIndex) { + return Float.intBitsToFloat(data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_X + coordinateIndex]); + } + + @Override + public float x(int vertexIndex) { + return Float.intBitsToFloat(data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_X]); + } + + @Override + public float y(int vertexIndex) { + return Float.intBitsToFloat(data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_Y]); + } + + @Override + public float z(int vertexIndex) { + return Float.intBitsToFloat(data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_Z]); + } + + @Override + public boolean hasNormal(int vertexIndex) { + return (normalFlags() & (1 << vertexIndex)) != 0; + } + + protected final int normalIndex(int vertexIndex) { + return baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_NORMAL; + } + + @Override + public Vector3f copyNormal(int vertexIndex, Vector3f target) { + if (hasNormal(vertexIndex)) { + if (target == null) { + target = new Vector3f(); + } + + final int normal = data[normalIndex(vertexIndex)]; + target.set(NormalHelper.getPackedNormalComponent(normal, 0), + NormalHelper.getPackedNormalComponent(normal, 1), NormalHelper.getPackedNormalComponent(normal, 2)); + return target; + } else { + return null; + } + } + + @Override + public float normalX(int vertexIndex) { + return hasNormal(vertexIndex) ? NormalHelper.getPackedNormalComponent(data[normalIndex(vertexIndex)], 0) + : Float.NaN; + } + + @Override + public float normalY(int vertexIndex) { + return hasNormal(vertexIndex) ? NormalHelper.getPackedNormalComponent(data[normalIndex(vertexIndex)], 1) + : Float.NaN; + } + + @Override + public float normalZ(int vertexIndex) { + return hasNormal(vertexIndex) ? NormalHelper.getPackedNormalComponent(data[normalIndex(vertexIndex)], 2) + : Float.NaN; + } + + @Override + public int lightmap(int vertexIndex) { + return data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_LIGHTMAP]; + } + + @Override + public int color(int vertexIndex) { + return data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_COLOR]; + } + + @Override + public float u(int vertexIndex) { + return Float.intBitsToFloat(data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_U]); + } + + @Override + public float v(int vertexIndex) { + return Float.intBitsToFloat(data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_V]); + } + + public int vertexStart() { + return baseIndex + HEADER_STRIDE; + } + + public boolean hasShade() { + return shade; + } + + public void shade(boolean shade) { + this.shade = shade; + } + + @Override + public final void toVanilla(int[] target, int targetIndex) { + System.arraycopy(data, baseIndex + HEADER_STRIDE, target, targetIndex, QUAD_STRIDE); + + // The color is the fourth integer in each vertex. + // EncodingFormat.VERTEX_COLOR is not used because it also + // contains the header size; vanilla quads do not have a header. + int colorIndex = targetIndex + 3; + + for (int i = 0; i < 4; i++) { + target[colorIndex] = ColorHelper.toVanillaColor(target[colorIndex]); + colorIndex += VANILLA_VERTEX_STRIDE; + } + } +} diff --git a/src/main/java/appeng/thirdparty/fabric/RenderContext.java b/src/main/java/appeng/thirdparty/fabric/RenderContext.java new file mode 100644 index 00000000000..76e033f1a83 --- /dev/null +++ b/src/main/java/appeng/thirdparty/fabric/RenderContext.java @@ -0,0 +1,12 @@ +package appeng.thirdparty.fabric; + +public interface RenderContext { + @FunctionalInterface + public interface QuadTransform { + /** + * Return false to filter out quads from rendering. When more than one transform is in effect, returning false + * means unapplied transforms will not receive the quad. + */ + boolean transform(MutableQuadView quad); + } +} diff --git a/src/main/java/appeng/thirdparty/fabric/Renderer.java b/src/main/java/appeng/thirdparty/fabric/Renderer.java new file mode 100644 index 00000000000..1cb6c606289 --- /dev/null +++ b/src/main/java/appeng/thirdparty/fabric/Renderer.java @@ -0,0 +1,40 @@ +package appeng.thirdparty.fabric; +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Interface for rendering plug-ins that provide enhanced capabilities for model lighting, buffering and rendering. Such + * plug-ins implement the enhanced model rendering interfaces specified by the Fabric API. + */ +public interface Renderer { + /** + * Obtain a new {@link MeshBuilder} instance used to create baked models with enhanced features. + * + *

+ * Renderer does not retain a reference to returned instances and they should be re-used for multiple models when + * possible to avoid memory allocation overhead. + */ + MeshBuilder meshBuilder(); + + static Renderer getInstance() { + return new Renderer() { + @Override + public MeshBuilder meshBuilder() { + return new MeshBuilderImpl(); + } + }; + } +} diff --git a/src/main/java/appeng/thirdparty/fabric/SpriteFinder.java b/src/main/java/appeng/thirdparty/fabric/SpriteFinder.java new file mode 100644 index 00000000000..51165aa1fe1 --- /dev/null +++ b/src/main/java/appeng/thirdparty/fabric/SpriteFinder.java @@ -0,0 +1,58 @@ +package appeng.thirdparty.fabric; +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import net.minecraft.client.renderer.texture.TextureAtlas; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; + +/** + * Indexes a texture atlas to allow fast lookup of Sprites from baked vertex coordinates. Main use is for + * {@link Mesh}-based models to generate vanilla quads on demand without tracking and retaining the sprites that were + * baked into the mesh. In other words, this class supplies the sprite parameter for + * {@link QuadView#toBakedQuad(int, TextureAtlasSprite, boolean)}. + */ +public interface SpriteFinder { + /** + * Retrieves or creates the finder for the given atlas. Instances should not be retained as fields or they must be + * refreshed whenever there is a resource reload or other event that causes atlas textures to be re-stitched. + */ + static SpriteFinder get(TextureAtlas atlas) { + return SpriteFinderImpl.get(atlas); + } + + /** + * Finds the atlas sprite containing the vertex centroid of the quad. Vertex centroid is essentially the mean u,v + * coordinate - the intent being to find a point that is unambiguously inside the sprite (vs on an edge.) + * + *

+ * Should be reliable for any convex quad or triangle. May fail for non-convex quads. Note that all the above refers + * to u,v coordinates. Geometric vertex does not matter, except to the extent it was used to determine u,v. + */ + TextureAtlasSprite find(QuadView quad); + + /** + * Alternative to {@link #find(QuadView, int)} when vertex centroid is already known or unsuitable. Expects + * normalized (0-1) coordinates on the atlas texture, which should already be the case for u,v values in vanilla + * baked quads and in {@link QuadView} after calling + * {@link MutableQuadView#spriteBake(int, TextureAtlasSprite, int)}. + * + *

+ * Coordinates must be in the sprite interior for reliable results. Generally will be easier to use + * {@link #find(QuadView, int)} unless you know the vertex centroid will somehow not be in the quad interior. This + * method will be slightly faster if you already have the centroid or another appropriate value. + */ + TextureAtlasSprite find(float u, float v); +} diff --git a/src/main/java/appeng/thirdparty/fabric/SpriteFinderImpl.java b/src/main/java/appeng/thirdparty/fabric/SpriteFinderImpl.java new file mode 100644 index 00000000000..be1c02fcbbb --- /dev/null +++ b/src/main/java/appeng/thirdparty/fabric/SpriteFinderImpl.java @@ -0,0 +1,144 @@ +package appeng.thirdparty.fabric; +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.util.Map; +import java.util.function.Consumer; + +import net.minecraft.client.renderer.texture.MissingTextureAtlasSprite; +import net.minecraft.client.renderer.texture.TextureAtlas; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.resources.ResourceLocation; + +/** + * Indexes an atlas sprite to allow fast lookup of Sprites from baked vertex coordinates. Implementation is a + * straightforward quad tree. Other options that were considered were linear search (slow) and direct indexing of + * fixed-size cells. Direct indexing would be fastest but would be memory-intensive for large atlases and unsuitable for + * any atlas that isn't consistently aligned to a fixed cell size. + */ +public class SpriteFinderImpl implements SpriteFinder { + private final Node root; + private final TextureAtlas spriteAtlasTexture; + + public SpriteFinderImpl(Map sprites, TextureAtlas spriteAtlasTexture) { + root = new Node(0.5f, 0.5f, 0.25f); + this.spriteAtlasTexture = spriteAtlasTexture; + sprites.values().forEach(root::add); + } + + @Override + public TextureAtlasSprite find(QuadView quad) { + float u = 0; + float v = 0; + + for (int i = 0; i < 4; i++) { + u += quad.u(i); + v += quad.v(i); + } + + return find(u * 0.25f, v * 0.25f); + } + + @Override + public TextureAtlasSprite find(float u, float v) { + return root.find(u, v); + } + + private class Node { + final float midU; + final float midV; + final float cellRadius; + Object lowLow = null; + Object lowHigh = null; + Object highLow = null; + Object highHigh = null; + + Node(float midU, float midV, float radius) { + this.midU = midU; + this.midV = midV; + cellRadius = radius; + } + + static final float EPS = 0.00001f; + + void add(TextureAtlasSprite sprite) { + final boolean lowU = sprite.getU0() < midU - EPS; + final boolean highU = sprite.getU1() > midU + EPS; + final boolean lowV = sprite.getV0() < midV - EPS; + final boolean highV = sprite.getV1() > midV + EPS; + + if (lowU && lowV) { + addInner(sprite, lowLow, -1, -1, q -> lowLow = q); + } + + if (lowU && highV) { + addInner(sprite, lowHigh, -1, 1, q -> lowHigh = q); + } + + if (highU && lowV) { + addInner(sprite, highLow, 1, -1, q -> highLow = q); + } + + if (highU && highV) { + addInner(sprite, highHigh, 1, 1, q -> highHigh = q); + } + } + + private void addInner(TextureAtlasSprite sprite, Object quadrant, int uStep, int vStep, + Consumer setter) { + if (quadrant == null) { + setter.accept(sprite); + } else if (quadrant instanceof Node) { + ((Node) quadrant).add(sprite); + } else { + Node n = new Node(midU + cellRadius * uStep, midV + cellRadius * vStep, cellRadius * 0.5f); + + if (quadrant instanceof TextureAtlasSprite) { + n.add((TextureAtlasSprite) quadrant); + } + + n.add(sprite); + setter.accept(n); + } + } + + private TextureAtlasSprite find(float u, float v) { + if (u < midU) { + return v < midV ? findInner(lowLow, u, v) : findInner(lowHigh, u, v); + } else { + return v < midV ? findInner(highLow, u, v) : findInner(highHigh, u, v); + } + } + + private TextureAtlasSprite findInner(Object quadrant, float u, float v) { + if (quadrant instanceof TextureAtlasSprite) { + return (TextureAtlasSprite) quadrant; + } else if (quadrant instanceof Node) { + return ((Node) quadrant).find(u, v); + } else { + return spriteAtlasTexture.getSprite(MissingTextureAtlasSprite.getLocation()); + } + } + } + + public static SpriteFinderImpl get(TextureAtlas atlas) { + return ((SpriteFinderAccess) atlas).fabric_spriteFinder(); + } + + public interface SpriteFinderAccess { + SpriteFinderImpl fabric_spriteFinder(); + } +} diff --git a/src/main/java/appeng/thirdparty/fabric/TextureHelper.java b/src/main/java/appeng/thirdparty/fabric/TextureHelper.java new file mode 100644 index 00000000000..2cf3e200355 --- /dev/null +++ b/src/main/java/appeng/thirdparty/fabric/TextureHelper.java @@ -0,0 +1,111 @@ +package appeng.thirdparty.fabric; +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.core.Direction; + +/** + * Handles most texture-baking use cases for model loaders and model libraries via + * {@link #bakeSprite(MutableQuadView, TextureAtlasSprite, int)}. Also used by the API itself to implement automatic + * block-breaking models for enhanced models. + */ +public class TextureHelper { + private TextureHelper() { + } + + private static final float NORMALIZER = 1f / 16f; + + /** + * Bakes textures in the provided vertex data, handling UV locking, rotation, interpolation, etc. Textures must not + * be already baked. + */ + public static void bakeSprite(MutableQuadView quad, TextureAtlasSprite sprite, int bakeFlags) { + if (quad.nominalFace() != null && (MutableQuadView.BAKE_LOCK_UV & bakeFlags) != 0) { + // Assigns normalized UV coordinates based on vertex positions + applyModifier(quad, UVLOCKERS[quad.nominalFace().get3DDataValue()]); + } else if ((MutableQuadView.BAKE_NORMALIZED & bakeFlags) == 0) { // flag is NOT set, UVs are assumed to not be + // normalized yet as is the default, normalize + // through dividing by 16 + // Scales from 0-16 to 0-1 + applyModifier(quad, (q, i) -> q.uv(i, q.u(i) * NORMALIZER, q.v(i) * NORMALIZER)); + } + + final int rotation = bakeFlags & 3; + + if (rotation != 0) { + // Rotates texture around the center of sprite. + // Assumes normalized coordinates. + applyModifier(quad, ROTATIONS[rotation]); + } + + if ((MutableQuadView.BAKE_FLIP_U & bakeFlags) != 0) { + // Inverts U coordinates. Assumes normalized (0-1) values. + applyModifier(quad, (q, i) -> q.uv(i, 1 - q.u(i), q.v(i))); + } + + if ((MutableQuadView.BAKE_FLIP_V & bakeFlags) != 0) { + // Inverts V coordinates. Assumes normalized (0-1) values. + applyModifier(quad, (q, i) -> q.uv(i, q.u(i), 1 - q.v(i))); + } + + interpolate(quad, sprite); + } + + /** + * Faster than sprite method. Sprite computes span and normalizes inputs each call, so we'd have to denormalize + * before we called, only to have the sprite renormalize immediately. + */ + private static void interpolate(MutableQuadView q, TextureAtlasSprite sprite) { + final float uMin = sprite.getU0(); + final float uSpan = sprite.getU1() - uMin; + final float vMin = sprite.getV0(); + final float vSpan = sprite.getV1() - vMin; + + for (int i = 0; i < 4; i++) { + q.uv(i, uMin + q.u(i) * uSpan, vMin + q.v(i) * vSpan); + } + } + + @FunctionalInterface + private interface VertexModifier { + void apply(MutableQuadView quad, int vertexIndex); + } + + private static void applyModifier(MutableQuadView quad, VertexModifier modifier) { + for (int i = 0; i < 4; i++) { + modifier.apply(quad, i); + } + } + + private static final VertexModifier[] ROTATIONS = new VertexModifier[] { + null, + (q, i) -> q.uv(i, q.v(i), 1 - q.u(i)), // 90 + (q, i) -> q.uv(i, 1 - q.u(i), 1 - q.v(i)), // 180 + (q, i) -> q.uv(i, 1 - q.v(i), q.u(i)) // 270 + }; + + private static final VertexModifier[] UVLOCKERS = new VertexModifier[6]; + + static { + UVLOCKERS[Direction.EAST.get3DDataValue()] = (q, i) -> q.uv(i, 1 - q.z(i), 1 - q.y(i)); + UVLOCKERS[Direction.WEST.get3DDataValue()] = (q, i) -> q.uv(i, q.z(i), 1 - q.y(i)); + UVLOCKERS[Direction.NORTH.get3DDataValue()] = (q, i) -> q.uv(i, 1 - q.x(i), 1 - q.y(i)); + UVLOCKERS[Direction.SOUTH.get3DDataValue()] = (q, i) -> q.uv(i, q.x(i), 1 - q.y(i)); + UVLOCKERS[Direction.DOWN.get3DDataValue()] = (q, i) -> q.uv(i, q.x(i), 1 - q.z(i)); + UVLOCKERS[Direction.UP.get3DDataValue()] = (q, i) -> q.uv(i, q.x(i), q.z(i)); + } +} diff --git a/src/main/java/appeng/util/BlockApiCache.java b/src/main/java/appeng/util/BlockApiCache.java new file mode 100644 index 00000000000..708ec81a27e --- /dev/null +++ b/src/main/java/appeng/util/BlockApiCache.java @@ -0,0 +1,33 @@ +package appeng.util; + +import javax.annotation.Nullable; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.server.level.ServerLevel; +import net.neoforged.neoforge.common.capabilities.Capability; + +public class BlockApiCache { + private final ServerLevel level; + private final BlockPos fromPos; + private final Capability capability; + + private BlockApiCache(Capability capability, ServerLevel level, BlockPos fromPos) { + this.capability = capability; + this.level = level; + this.fromPos = fromPos; + } + + public static BlockApiCache create(Capability capability, ServerLevel level, BlockPos fromPos) { + return new BlockApiCache<>(capability, level, fromPos); + } + + @Nullable + public C find(Direction fromSide) { + var be = level.getBlockEntity(fromPos); + if (be != null) { + return be.getCapability(capability, fromSide).orElse(null); + } + return null; + } +} diff --git a/src/main/java/appeng/util/ConfigInventory.java b/src/main/java/appeng/util/ConfigInventory.java index b83fcc3e5cf..60c2f43dac0 100644 --- a/src/main/java/appeng/util/ConfigInventory.java +++ b/src/main/java/appeng/util/ConfigInventory.java @@ -17,6 +17,7 @@ import appeng.api.stacks.GenericStack; import appeng.api.storage.AEKeyFilter; import appeng.helpers.externalstorage.GenericStackInv; +import appeng.helpers.externalstorage.GenericStackInv.Mode; import appeng.me.helpers.BaseActionSource; /** diff --git a/src/main/java/appeng/util/ConfigMenuInventory.java b/src/main/java/appeng/util/ConfigMenuInventory.java index 448b2e20710..286ed304aef 100644 --- a/src/main/java/appeng/util/ConfigMenuInventory.java +++ b/src/main/java/appeng/util/ConfigMenuInventory.java @@ -16,9 +16,9 @@ import appeng.helpers.externalstorage.GenericStackInv; /** - * Wraps this configuration inventory as an {@link net.minecraft.world.item.ItemStack} based inventory for use in a - * menu. It will automatically convert appropriately from {@link net.minecraft.world.item.ItemStack}s set by the player - * to the internal key-based representation with the help of a matching {@link AEKeyType}. + * Wraps this configuration inventory as an {@link ItemStack} based inventory for use in a menu. It will automatically + * convert appropriately from {@link ItemStack}s set by the player to the internal key-based representation with the + * help of a matching {@link AEKeyType}. */ public class ConfigMenuInventory implements InternalInventory { private final GenericStackInv inv; diff --git a/src/main/java/appeng/util/CraftingRecipeUtil.java b/src/main/java/appeng/util/CraftingRecipeUtil.java index db0b89ceb51..66b3766f6d8 100644 --- a/src/main/java/appeng/util/CraftingRecipeUtil.java +++ b/src/main/java/appeng/util/CraftingRecipeUtil.java @@ -5,9 +5,9 @@ import net.minecraft.core.NonNullList; import net.minecraft.world.item.crafting.Ingredient; import net.minecraft.world.item.crafting.Recipe; -import net.minecraft.world.item.crafting.ShapedRecipe; import net.minecraft.world.item.crafting.SmithingTransformRecipe; import net.minecraft.world.item.crafting.SmithingTrimRecipe; +import net.neoforged.neoforge.common.crafting.IShapedRecipe; public final class CraftingRecipeUtil { private CraftingRecipeUtil() { @@ -27,9 +27,9 @@ public static NonNullList ensure3by3CraftingMatrix(Recipe recipe) // shaped recipes can be smaller than 3x3, expand to 3x3 to match the crafting // matrix - if (recipe instanceof ShapedRecipe shapedRecipe) { - var width = shapedRecipe.getWidth(); - var height = shapedRecipe.getHeight(); + if (recipe instanceof IShapedRecipeshapedRecipe) { + var width = shapedRecipe.getRecipeWidth(); + var height = shapedRecipe.getRecipeHeight(); Preconditions.checkArgument(width <= 3 && height <= 3); for (var h = 0; h < height; h++) { diff --git a/src/main/java/appeng/util/CustomNameUtil.java b/src/main/java/appeng/util/CustomNameUtil.java index e3672a94de8..15e30344169 100644 --- a/src/main/java/appeng/util/CustomNameUtil.java +++ b/src/main/java/appeng/util/CustomNameUtil.java @@ -10,8 +10,7 @@ import net.minecraft.world.item.ItemStack; /** - * Allows serializing custom names to and from NBT data in the same format as - * {@link net.minecraft.world.item.ItemStack}. + * Allows serializing custom names to and from NBT data in the same format as {@link ItemStack}. */ public final class CustomNameUtil { private CustomNameUtil() { diff --git a/src/main/java/appeng/util/FakePlayer.java b/src/main/java/appeng/util/FakePlayer.java new file mode 100644 index 00000000000..a3fe2a51510 --- /dev/null +++ b/src/main/java/appeng/util/FakePlayer.java @@ -0,0 +1,36 @@ +package appeng.util; + +import java.util.Objects; +import java.util.UUID; +import java.util.WeakHashMap; + +import com.mojang.authlib.GameProfile; + +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; + +public final class FakePlayer { + public static UUID DEFAULT_UUID = UUID.fromString("60C173A5-E1E6-4B87-85B1-272CE424521D"); + + private static final WeakHashMap FAKE_PLAYERS = new WeakHashMap<>(); + + private FakePlayer() { + } + + /** + * DO NOT COPY THE PLAYER ANYWHERE! It will keep the world alive, always call this method if you need it. + */ + static Player get(ServerLevel level, GameProfile profile) { + Objects.requireNonNull(level); + + var wrp = FAKE_PLAYERS.get(level); + if (wrp != null) { + return wrp; + } + + var p = new net.neoforged.neoforge.common.util.FakePlayer(level, profile); + FAKE_PLAYERS.put(level, p); + return p; + } +} diff --git a/src/main/java/appeng/util/GenericContainerHelper.java b/src/main/java/appeng/util/GenericContainerHelper.java index dac9b198a67..266e8d7d949 100644 --- a/src/main/java/appeng/util/GenericContainerHelper.java +++ b/src/main/java/appeng/util/GenericContainerHelper.java @@ -2,13 +2,8 @@ import org.jetbrains.annotations.Nullable; -import net.fabricmc.fabric.api.lookup.v1.item.ItemApiLookup; -import net.fabricmc.fabric.api.transfer.v1.context.ContainerItemContext; -import net.fabricmc.fabric.api.transfer.v1.fluid.FluidStorage; -import net.fabricmc.fabric.api.transfer.v1.storage.Storage; -import net.fabricmc.fabric.api.transfer.v1.storage.StorageUtil; -import net.fabricmc.fabric.api.transfer.v1.storage.TransferVariant; import net.minecraft.world.item.ItemStack; +import net.neoforged.neoforge.fluids.FluidUtil; import appeng.api.stacks.GenericStack; @@ -21,23 +16,13 @@ private GenericContainerHelper() { @Nullable public static GenericStack getContainedFluidStack(ItemStack stack) { - return getContainedStack(stack, FluidStorage.ITEM, IVariantConversion.FLUID); - } - - @Nullable - public static > GenericStack getContainedStack(ItemStack stack, - ItemApiLookup, ContainerItemContext> apiLookup, - IVariantConversion conversion) { if (stack.isEmpty()) { return null; } - var result = ContainerItemContext.withInitial(stack).find(apiLookup); - var content = StorageUtil.findExtractableContent(result, null); + var content = FluidUtil.getFluidContained(stack).orElse(null); if (content != null) { - return new GenericStack( - conversion.getKey(content.resource()), - content.amount()); + return GenericStack.fromFluidStack(content); } else { return null; } diff --git a/src/main/java/appeng/util/IVariantConversion.java b/src/main/java/appeng/util/IVariantConversion.java deleted file mode 100644 index ff18166ea04..00000000000 --- a/src/main/java/appeng/util/IVariantConversion.java +++ /dev/null @@ -1,80 +0,0 @@ -package appeng.util; - -import org.jetbrains.annotations.Nullable; - -import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant; -import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant; -import net.fabricmc.fabric.api.transfer.v1.storage.TransferVariant; - -import appeng.api.stacks.AEFluidKey; -import appeng.api.stacks.AEItemKey; -import appeng.api.stacks.AEKey; -import appeng.api.stacks.AEKeyType; - -// Consider moving to API? -public interface IVariantConversion> { - IVariantConversion ITEM = new Item(); - IVariantConversion FLUID = new Fluid(); - - AEKeyType getKeyType(); - - /** - * Convert key to variant. If the key is null or of the wrong type, return a blank variant. Keys are not always of - * this type, make sure to always check. - */ - V getVariant(@Nullable AEKey key); - - @Nullable - AEKey getKey(V variant); - - default boolean variantMatches(AEKey key, V variant) { - return getVariant(key).equals(variant); - } - - long getBaseSlotSize(V variant); - - class Fluid implements IVariantConversion { - @Override - public AEKeyType getKeyType() { - return AEKeyType.fluids(); - } - - @Override - public FluidVariant getVariant(AEKey key) { - return key instanceof AEFluidKey fluidKey ? fluidKey.toVariant() : FluidVariant.blank(); - } - - @Override - public AEKey getKey(FluidVariant variant) { - return AEFluidKey.of(variant); - } - - @Override - public long getBaseSlotSize(FluidVariant variant) { - return 4 * AEFluidKey.AMOUNT_BUCKET; - } - } - - class Item implements IVariantConversion { - @Override - public AEKeyType getKeyType() { - return AEKeyType.items(); - } - - @Override - public ItemVariant getVariant(AEKey key) { - return key instanceof AEItemKey itemKey ? itemKey.toVariant() : ItemVariant.blank(); - } - - @org.jetbrains.annotations.Nullable - @Override - public AEItemKey getKey(ItemVariant variant) { - return AEItemKey.of(variant); - } - - @Override - public long getBaseSlotSize(ItemVariant variant) { - return Math.min(64, variant.getItem().getMaxStackSize()); - } - } -} diff --git a/src/main/java/appeng/util/InteractionUtil.java b/src/main/java/appeng/util/InteractionUtil.java index a980012d2a8..e3058b6362a 100644 --- a/src/main/java/appeng/util/InteractionUtil.java +++ b/src/main/java/appeng/util/InteractionUtil.java @@ -33,6 +33,7 @@ import net.minecraft.world.phys.EntityHitResult; import net.minecraft.world.phys.HitResult; import net.minecraft.world.phys.Vec3; +import net.neoforged.neoforge.common.NeoForgeMod; import appeng.datagen.providers.tags.ConventionTags; import appeng.items.tools.NetworkToolItem; @@ -82,8 +83,7 @@ public static float getEyeOffset(Player player) { } public static LookDirection getPlayerRay(Player playerIn) { - // FIXME FABRIC 117 This can currently not be modded in API in Fabric - double reachDistance = 36.0D; + double reachDistance = playerIn.getAttribute(NeoForgeMod.BLOCK_REACH.get()).getValue(); return getPlayerRay(playerIn, reachDistance); } @@ -116,7 +116,7 @@ public static HitResult rayTrace(Player p, boolean hitBlocks, boolean hitEntitie float f1 = p.xRotO + (p.getXRot() - p.xRotO) * f; final float f2 = p.yRotO + (p.getYRot() - p.yRotO) * f; final double d0 = p.xo + (p.getX() - p.xo) * f; - final double d1 = p.yo + (p.getY() - p.yo) * f + 1.62D - p.getMyRidingOffset(); + final double d1 = p.yo + (p.getY() - p.yo) * f + 1.62D - p.getMyRidingOffset(p); final double d2 = p.zo + (p.getZ() - p.zo) * f; final Vec3 vec3 = new Vec3(d0, d1, d2); final float f3 = Mth.cos(-f2 * 0.017453292F - (float) Math.PI); diff --git a/src/main/java/appeng/util/Platform.java b/src/main/java/appeng/util/Platform.java index 8775fba097c..2fe0d0e47af 100644 --- a/src/main/java/appeng/util/Platform.java +++ b/src/main/java/appeng/util/Platform.java @@ -26,17 +26,12 @@ import java.util.UUID; import java.util.concurrent.TimeUnit; +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Iterables; import com.mojang.authlib.GameProfile; import org.jetbrains.annotations.Nullable; -import net.fabricmc.api.EnvType; -import net.fabricmc.fabric.api.entity.FakePlayer; -import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant; -import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariantAttributes; -import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction; -import net.fabricmc.loader.api.FabricLoader; import net.minecraft.ChatFormatting; import net.minecraft.client.Minecraft; import net.minecraft.core.BlockPos; @@ -61,6 +56,10 @@ import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.material.Fluid; +import net.neoforged.fml.ModList; +import net.neoforged.fml.loading.FMLEnvironment; +import net.neoforged.fml.util.thread.SidedThreadGroups; +import net.neoforged.neoforge.fluids.FluidStack; import appeng.api.config.AccessRestriction; import appeng.api.config.Actionable; @@ -76,7 +75,6 @@ import appeng.api.util.DimensionalBlockPos; import appeng.core.AEConfig; import appeng.core.AELog; -import appeng.core.AppEng; import appeng.hooks.VisualStateSaving; import appeng.hooks.ticking.TickHandler; import appeng.util.helpers.P2PHelper; @@ -84,7 +82,8 @@ public class Platform { - private static final FabricLoader FABRIC = FabricLoader.getInstance(); + @VisibleForTesting + public static ThreadGroup serverThreadGroup = SidedThreadGroups.SERVER; /* * random source, use it for item drop locations... @@ -100,7 +99,7 @@ public class Platform { * Class of the Create Ponder Level. Enables {@link VisualStateSaving} if a block entity is attached to a Ponder * level. */ - @org.jetbrains.annotations.Nullable + @Nullable private static final Class ponderLevelClass = findPonderLevelClass( "com.simibubi.create.foundation.ponder.PonderWorld"); @@ -217,15 +216,15 @@ public static Direction crossProduct(Direction forward, Direction up) { * @return True if client-side classes (such as Renderers) are available. */ public static boolean hasClientClasses() { - return FABRIC.getEnvironmentType() == EnvType.CLIENT; + // The null check is for tests + return FMLEnvironment.dist == null || FMLEnvironment.dist.isClient(); } /* * returns true if the code is on the client. */ public static boolean isClient() { - var currentServer = AppEng.instance().getCurrentServer(); - return currentServer == null || Thread.currentThread() != currentServer.getRunningThread(); + return Thread.currentThread().getThreadGroup() != SidedThreadGroups.SERVER; } public static boolean hasPermissions(DimensionalBlockPos dc, Player player) { @@ -250,21 +249,14 @@ public static void spawnDrops(Level level, BlockPos pos, List drops) * returns true if the code is on the server. */ public static boolean isServer() { - try { - var currentServer = AppEng.instance().getCurrentServer(); - return currentServer != null && Thread.currentThread() == currentServer.getRunningThread(); - } catch (NullPointerException npe) { - // FIXME TEST HACKS - // Running from tests: AppEng.instance() is null... :( - return false; - } + return Thread.currentThread().getThreadGroup() == SidedThreadGroups.SERVER; } /** * Throws an exception if the current thread is not one of the server threads. */ public static void assertServerThread() { - if (!isServer()) { + if (Thread.currentThread().getThreadGroup() != serverThreadGroup) { throw new UnsupportedOperationException( "This code can only be called server-side and this is most likely a bug."); } @@ -280,19 +272,22 @@ public static String formatModName(String modId) { @Nullable public static String getModName(String modId) { - return FABRIC.getModContainer(modId).map(mc -> mc.getMetadata().getName()).orElse(modId); + return ModList.get().getModContainerById(modId).map(mc -> mc.getModInfo().getDisplayName()) + .orElse(modId); } public static String getDescriptionId(Fluid fluid) { return fluid.defaultFluidState().createLegacyBlock().getBlock().getDescriptionId(); } - public static String getDescriptionId(FluidVariant fluid) { - return getDescriptionId(fluid.getFluid()); + public static String getDescriptionId(FluidStack fluid) { + return fluid.getDisplayName().getString(); } public static Component getFluidDisplayName(Fluid fluid, @Nullable CompoundTag tag) { - return FluidVariantAttributes.getName(FluidVariant.of(fluid, tag)); + var fluidStack = new FluidStack(fluid, 1); + fluidStack.setTag(tag); + return fluidStack.getDisplayName(); } // tag copy is not necessary, as the tag is not modified. @@ -456,10 +451,6 @@ public static boolean areBlockEntitiesTicking(@Nullable Level level, long chunkP return level instanceof ServerLevel serverLevel && serverLevel.getChunkSource().isPositionTicking(chunkPos); } - public static Transaction openOrJoinTx() { - return Transaction.openNested(Transaction.getCurrentUnsafe()); - } - /** * Create a full packet of the chunks data with lighting. */ @@ -511,6 +502,6 @@ public static boolean isPonderLevel(Level level) { * @return True if AE2 is being run within a dev environment. */ public static boolean isDevelopmentEnvironment() { - return FabricLoader.getInstance().isDevelopmentEnvironment(); + return !FMLEnvironment.production; } } diff --git a/src/main/java/appeng/util/SearchInventoryEvent.java b/src/main/java/appeng/util/SearchInventoryEvent.java index c062190124f..58e45ddc481 100644 --- a/src/main/java/appeng/util/SearchInventoryEvent.java +++ b/src/main/java/appeng/util/SearchInventoryEvent.java @@ -4,11 +4,12 @@ import java.util.List; import java.util.function.BiConsumer; -import net.fabricmc.fabric.api.event.Event; -import net.fabricmc.fabric.api.event.EventFactory; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; +import appeng.api.events.Event; +import appeng.api.events.EventFactory; + public class SearchInventoryEvent { /** * Event fired when AE2 is looking for ItemStacks in a player inventory. By default, AE2 only looks at the 36 usual diff --git a/src/main/java/appeng/util/UndoStack.java b/src/main/java/appeng/util/UndoStack.java deleted file mode 100644 index 0901b76b33d..00000000000 --- a/src/main/java/appeng/util/UndoStack.java +++ /dev/null @@ -1,35 +0,0 @@ -package appeng.util; - -import java.util.ArrayList; -import java.util.List; - -import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext; -import net.fabricmc.fabric.api.transfer.v1.transaction.base.SnapshotParticipant; - -public class UndoStack extends SnapshotParticipant { - public static void push(TransactionContext transaction, Runnable undoAction) { - INSTANCE.updateSnapshots(transaction); - INSTANCE.undoActions.add(undoAction); - } - - private static final UndoStack INSTANCE = new UndoStack(); - private final List undoActions = new ArrayList<>(); - - private UndoStack() { - } - - @Override - protected Integer createSnapshot() { - return undoActions.size(); - } - - @Override - protected void readSnapshot(Integer snapshot) { - undoActions.subList(snapshot, undoActions.size()).clear(); - } - - @Override - protected void onFinalCommit() { - undoActions.clear(); - } -} diff --git a/src/main/java/appeng/util/fluid/FluidSoundHelper.java b/src/main/java/appeng/util/fluid/FluidSoundHelper.java index 88eb46c222d..70b1d68d317 100644 --- a/src/main/java/appeng/util/fluid/FluidSoundHelper.java +++ b/src/main/java/appeng/util/fluid/FluidSoundHelper.java @@ -20,14 +20,12 @@ import org.jetbrains.annotations.Nullable; -import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant; -import net.fabricmc.fabric.api.transfer.v1.storage.Storage; import net.minecraft.sounds.SoundEvent; -import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundSource; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.material.Fluids; +import net.neoforged.neoforge.common.SoundActions; +import net.neoforged.neoforge.fluids.capability.IFluidHandler; import appeng.api.stacks.AEFluidKey; @@ -44,7 +42,12 @@ public static void playFillSound(Player player, @Nullable AEFluidKey fluid) { return; } - fluid.getFluid().getPickupSound().ifPresent(sound -> playSound(player, sound)); + SoundEvent fillSound = fluid.getFluid().getFluidType().getSound(player, SoundActions.BUCKET_FILL); + if (fillSound == null) { + return; + } + + playSound(player, fillSound); } public static void playEmptySound(Player player, @Nullable AEFluidKey fluid) { @@ -52,20 +55,16 @@ public static void playEmptySound(Player player, @Nullable AEFluidKey fluid) { return; } -// TODO: FABRIC 117 No equivalent available right now -// SoundEvent fillSound = fluid.getFluid().getAttributes().getEmptySound(fluid); -// if (fillSound == null) { -// return; -// } - if (fluid.getFluid() == Fluids.LAVA) { - playSound(player, SoundEvents.BUCKET_EMPTY_LAVA); - } else { - playSound(player, SoundEvents.BUCKET_EMPTY); + SoundEvent fillSound = fluid.getFluid().getFluidType().getSound(player, SoundActions.BUCKET_EMPTY); + if (fillSound == null) { + return; } + + playSound(player, fillSound); } /** - * @see net.minecraftforge.fluids.FluidUtil#tryFillContainer(ItemStack, Storage, int, Player, boolean) + * @see net.neoforged.neoforge.fluids.FluidUtil#tryFillContainer(ItemStack, IFluidHandler, int, Player, boolean) */ private static void playSound(Player player, SoundEvent fillSound) { // This should just play the sound for the player themselves diff --git a/src/main/java/appeng/util/inv/AppEngInternalInventory.java b/src/main/java/appeng/util/inv/AppEngInternalInventory.java index 7ca06a10aba..2ab73c4566f 100644 --- a/src/main/java/appeng/util/inv/AppEngInternalInventory.java +++ b/src/main/java/appeng/util/inv/AppEngInternalInventory.java @@ -25,7 +25,6 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; -import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction; import net.minecraft.core.NonNullList; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; @@ -84,16 +83,6 @@ public void setItemDirect(int slot, ItemStack stack) { } private void notifyContentsChanged(int slot) { - if (Transaction.isOpen()) { - // Notifications during transactions are handled in the adapter - return; - } - - onContentsChanged(slot); - } - - @Override - public void sendChangeNotification(int slot) { onContentsChanged(slot); } diff --git a/src/main/java/appeng/util/inv/CombinedInternalInventory.java b/src/main/java/appeng/util/inv/CombinedInternalInventory.java index f905960f3c4..353baca9897 100644 --- a/src/main/java/appeng/util/inv/CombinedInternalInventory.java +++ b/src/main/java/appeng/util/inv/CombinedInternalInventory.java @@ -18,12 +18,6 @@ package appeng.util.inv; -import java.util.ArrayList; -import java.util.List; - -import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant; -import net.fabricmc.fabric.api.transfer.v1.storage.Storage; -import net.fabricmc.fabric.api.transfer.v1.storage.base.CombinedStorage; import net.minecraft.world.item.ItemStack; import appeng.api.inventories.BaseInternalInventory; @@ -136,15 +130,4 @@ public void sendChangeNotification(int slot) { int targetSlot = this.getSlotFromIndex(slot, index); handler.sendChangeNotification(targetSlot); } - - @Override - protected Storage createStorage() { - List> parts = new ArrayList<>(this.inventories.length); - - for (InternalInventory inventory : this.inventories) { - parts.add(inventory.toStorage()); - } - - return new CombinedStorage<>(parts); - } } diff --git a/src/main/java/appeng/util/inv/SupplierInternalInventory.java b/src/main/java/appeng/util/inv/SupplierInternalInventory.java index de08d65f1d4..685a34561fe 100644 --- a/src/main/java/appeng/util/inv/SupplierInternalInventory.java +++ b/src/main/java/appeng/util/inv/SupplierInternalInventory.java @@ -21,15 +21,14 @@ import java.util.Iterator; import java.util.function.Supplier; -import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant; -import net.fabricmc.fabric.api.transfer.v1.storage.Storage; import net.minecraft.world.Container; import net.minecraft.world.item.ItemStack; +import net.neoforged.neoforge.items.IItemHandler; import appeng.api.inventories.InternalInventory; /** - * Wraps another {@link InternalInventory} in such a way that the underlying inventory is queried from a supplier, which + * Wraps another {@link IItemHandler} in such a way that the underlying item hander is queried from a supplier, which * allows it to be changed at any time. */ public class SupplierInternalInventory implements InternalInventory { @@ -45,8 +44,8 @@ public boolean isEmpty() { } @Override - public Storage toStorage() { - return delegate.get().toStorage(); + public IItemHandler toItemHandler() { + return delegate.get().toItemHandler(); } @Override diff --git a/src/main/java/appeng/worldgen/meteorite/MeteoriteStructure.java b/src/main/java/appeng/worldgen/meteorite/MeteoriteStructure.java index 23895397352..747fdcf2961 100644 --- a/src/main/java/appeng/worldgen/meteorite/MeteoriteStructure.java +++ b/src/main/java/appeng/worldgen/meteorite/MeteoriteStructure.java @@ -34,6 +34,9 @@ import net.minecraft.world.level.levelgen.LegacyRandomSource; import net.minecraft.world.level.levelgen.WorldgenRandom; import net.minecraft.world.level.levelgen.structure.Structure; +import net.minecraft.world.level.levelgen.structure.Structure.GenerationContext; +import net.minecraft.world.level.levelgen.structure.Structure.GenerationStub; +import net.minecraft.world.level.levelgen.structure.Structure.StructureSettings; import net.minecraft.world.level.levelgen.structure.StructureSet; import net.minecraft.world.level.levelgen.structure.StructureType; import net.minecraft.world.level.levelgen.structure.pieces.StructurePiecesBuilder; diff --git a/src/main/java/appeng/worldgen/meteorite/fallout/FalloutCopy.java b/src/main/java/appeng/worldgen/meteorite/fallout/FalloutCopy.java index 49ea66059c7..c50677d86c2 100644 --- a/src/main/java/appeng/worldgen/meteorite/fallout/FalloutCopy.java +++ b/src/main/java/appeng/worldgen/meteorite/fallout/FalloutCopy.java @@ -18,12 +18,13 @@ package appeng.worldgen.meteorite.fallout; -import net.fabricmc.fabric.api.tag.convention.v1.ConventionalBiomeTags; import net.minecraft.core.BlockPos; +import net.minecraft.tags.BiomeTags; import net.minecraft.util.RandomSource; import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; +import net.neoforged.neoforge.common.Tags; import appeng.worldgen.meteorite.MeteoriteBlockPutter; @@ -40,13 +41,13 @@ public FalloutCopy(LevelAccessor level, BlockPos pos, MeteoriteBlockPutter putte super(putter, skyStone, random); this.putter = putter; var biome = level.getBiome(pos); - if (biome.is(ConventionalBiomeTags.MESA)) { + if (biome.is(BiomeTags.IS_BADLANDS)) { block = Blocks.TERRACOTTA.defaultBlockState(); - } else if (biome.is(ConventionalBiomeTags.ICY)) { + } else if (biome.is(Tags.Biomes.IS_SNOWY)) { block = Blocks.SNOW_BLOCK.defaultBlockState(); - } else if (biome.is(ConventionalBiomeTags.BEACH) || biome.is(ConventionalBiomeTags.DESERT)) { + } else if (biome.is(BiomeTags.IS_BEACH) || biome.is(Tags.Biomes.IS_SANDY)) { block = Blocks.SAND.defaultBlockState(); - } else if (biome.is(ConventionalBiomeTags.PLAINS) || biome.is(ConventionalBiomeTags.FOREST)) { + } else if (biome.is(Tags.Biomes.IS_PLAINS) || biome.is(BiomeTags.IS_FOREST)) { block = Blocks.DIRT.defaultBlockState(); } else { block = Blocks.COBBLESTONE.defaultBlockState(); diff --git a/src/main/java/appeng/worldgen/meteorite/fallout/FalloutMode.java b/src/main/java/appeng/worldgen/meteorite/fallout/FalloutMode.java index 61031c31c73..99798777b4b 100644 --- a/src/main/java/appeng/worldgen/meteorite/fallout/FalloutMode.java +++ b/src/main/java/appeng/worldgen/meteorite/fallout/FalloutMode.java @@ -22,10 +22,11 @@ import com.google.common.collect.ImmutableList; -import net.fabricmc.fabric.api.tag.convention.v1.ConventionalBiomeTags; import net.minecraft.core.Holder; +import net.minecraft.tags.BiomeTags; import net.minecraft.tags.TagKey; import net.minecraft.world.level.biome.Biome; +import net.neoforged.neoforge.common.Tags; public enum FalloutMode { @@ -42,17 +43,17 @@ public enum FalloutMode { /** * For sandy terrain */ - SAND(ConventionalBiomeTags.DESERT, ConventionalBiomeTags.BEACH), + SAND(Tags.Biomes.IS_SANDY, BiomeTags.IS_BEACH), /** * For terracotta (mesa) */ - TERRACOTTA(ConventionalBiomeTags.MESA), + TERRACOTTA(BiomeTags.IS_BADLANDS), /** * Icy/snowy terrain */ - ICE_SNOW(ConventionalBiomeTags.SNOWY, ConventionalBiomeTags.ICY); + ICE_SNOW(Tags.Biomes.IS_COLD); private final List> biomeTags; diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg new file mode 100644 index 00000000000..c0473955659 --- /dev/null +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -0,0 +1,154 @@ +# GUI rendering +protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen renderSlot(Lnet/minecraft/client/gui/GuiGraphics;Lnet/minecraft/world/inventory/Slot;)V +public net.minecraft.client.gui.components.EditBox getMaxLength()I +public net.minecraft.client.gui.components.EditBox isEditable()Z + +public net.minecraft.world.inventory.Slot slot +protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen isHovering(Lnet/minecraft/world/inventory/Slot;DD)Z +protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen findSlot(DD)Lnet/minecraft/world/inventory/Slot; + +# Render block outlines +public net.minecraft.client.renderer.LevelRenderer renderShape(Lcom/mojang/blaze3d/vertex/PoseStack;Lcom/mojang/blaze3d/vertex/VertexConsumer;Lnet/minecraft/world/phys/shapes/VoxelShape;DDDFFFF)V + +# Baked Quad Winding +public net.minecraft.client.renderer.block.model.BlockModel FACE_BAKERY +public net.minecraft.client.renderer.block.model.FaceBakery recalculateWinding([ILnet/minecraft/core/Direction;)V + +# Structures +public net.minecraft.world.level.levelgen.structure.StructureType register(Ljava/lang/String;Lcom/mojang/serialization/Codec;)Lnet/minecraft/world/level/levelgen/structure/StructureType; +public net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings STRUCTURE_FEATURES + +# Worldgen +public net.minecraft.data.worldgen.placement.OrePlacements commonOrePlacement(ILnet/minecraft/world/level/levelgen/placement/PlacementModifier;)Ljava/util/List; + +# Tests +public net.minecraft.gametest.framework.GameTestHelper testInfo + +# Delay continued left clicks +public net.minecraft.client.multiplayer.MultiPlayerGameMode destroyDelay + +# We need to change yPos of existing slots to resize the container +public-f net.minecraft.world.inventory.Slot x +public-f net.minecraft.world.inventory.Slot y + +# For JEI registration +public net.minecraft.world.item.crafting.RecipeManager byType(Lnet/minecraft/world/item/crafting/RecipeType;)Ljava/util/Map; + +# To disable water-bobbing of item entities (for growing crystals) +protected net.minecraft.world.entity.item.ItemEntity setUnderwaterMovement()V + +# For structure registration +public net.minecraft.world.level.levelgen.feature.StructureFeature STEP +public net.minecraft.world.level.levelgen.structure.pieces.StructurePieceType setPieceId(Lnet/minecraft/world/level/levelgen/structure/pieces/StructurePieceType$ContextlessType;Ljava/lang/String;)Lnet/minecraft/world/level/levelgen/structure/pieces/StructurePieceType; +public net.minecraft.data.worldgen.Structures structure(Lnet/minecraft/tags/TagKey;Ljava/util/Map;Lnet/minecraft/world/level/levelgen/GenerationStep$Decoration;Lnet/minecraft/world/level/levelgen/structure/TerrainAdjustment;)Lnet/minecraft/world/level/levelgen/structure/Structure$StructureSettings; + +# For overlay rendering +public net.minecraft.client.renderer.RenderStateShard$LineStateShard + +# Data Gen +public net.minecraft.data.loot.BlockLoot HAS_NO_SILK_TOUCH +public-f net.minecraft.data.recipes.RecipeProvider getName()Ljava/lang/String; +public net.minecraft.data.models.BlockModelGenerators createRotatedPillar()Lnet/minecraft/data/models/blockstates/PropertyDispatch; + +# Tests +public net.minecraft.data.recipes.RecipeProvider has(Lnet/minecraft/world/level/ItemLike;)Lnet/minecraft/advancements/critereon/InventoryChangeTrigger$TriggerInstance; + +# Villager Trades +public net.minecraft.world.entity.npc.VillagerTrades$EmeraldForItems +public net.minecraft.world.entity.npc.VillagerTrades$ItemsForEmeralds + +# Guidebook +public net.minecraft.client.gui.Font getFontSet(Lnet/minecraft/resources/ResourceLocation;)Lnet/minecraft/client/gui/font/FontSet; +public net.minecraft.client.gui.screens.LoadingOverlay reload +public net.minecraft.world.item.crafting.SmithingTrimRecipe template +public net.minecraft.world.item.crafting.SmithingTrimRecipe base +public net.minecraft.world.item.crafting.SmithingTrimRecipe addition +public net.minecraft.world.item.crafting.SmithingTransformRecipe template +public net.minecraft.world.item.crafting.SmithingTransformRecipe base +public net.minecraft.world.item.crafting.SmithingTransformRecipe addition + +# Auto Rotating Model +public net.minecraft.client.resources.model.ModelManager$ReloadState + +# GUI +public net.minecraft.world.inventory.AbstractContainerMenu stateId +public net.minecraft.client.gui.screens.Screen setInitialFocus(Lnet/minecraft/client/gui/components/events/GuiEventListener;)V +public net.minecraft.client.gui.components.EditBox renderHighlight(Lnet/minecraft/client/gui/GuiGraphics;IIII)V + +# Matter Cannon Damage Source +public net.minecraft.world.damagesource.DamageSources source(Lnet/minecraft/resources/ResourceKey;Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/entity/Entity;)Lnet/minecraft/world/damagesource/DamageSource; +public net.minecraft.world.damagesource.DamageSources source(Lnet/minecraft/resources/ResourceKey;)Lnet/minecraft/world/damagesource/DamageSource; +public net.minecraft.world.damagesource.DamageSources source(Lnet/minecraft/resources/ResourceKey;Lnet/minecraft/world/entity/Entity;)Lnet/minecraft/world/damagesource/DamageSource; + +public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_LINES_SHADER +public net.minecraft.client.renderer.RenderStateShard VIEW_OFFSET_Z_LAYERING +public net.minecraft.client.renderer.RenderStateShard ITEM_ENTITY_TARGET +public net.minecraft.client.renderer.RenderStateShard COLOR_WRITE +public net.minecraft.client.renderer.RenderStateShard NO_CULL +public net.minecraft.client.renderer.RenderStateShard LIGHTMAP +public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_TRANSLUCENT_NO_CRUMBLING_SHADER + +public net.minecraft.client.renderer.RenderStateShard BLOCK_SHEET_MIPPED +public net.minecraft.client.renderer.RenderStateShard NO_TEXTURE + +public net.minecraft.client.renderer.RenderStateShard NO_TRANSPARENCY +public net.minecraft.client.renderer.RenderStateShard ADDITIVE_TRANSPARENCY +public net.minecraft.client.renderer.RenderStateShard LIGHTNING_TRANSPARENCY +public net.minecraft.client.renderer.RenderStateShard GLINT_TRANSPARENCY +public net.minecraft.client.renderer.RenderStateShard CRUMBLING_TRANSPARENCY +public net.minecraft.client.renderer.RenderStateShard TRANSLUCENT_TRANSPARENCY + +public net.minecraft.client.renderer.RenderStateShard NO_DEPTH_TEST +public net.minecraft.client.renderer.RenderStateShard EQUAL_DEPTH_TEST +public net.minecraft.client.renderer.RenderStateShard LEQUAL_DEPTH_TEST +public net.minecraft.client.renderer.RenderStateShard GREATER_DEPTH_TEST + +public net.minecraft.world.level.block.Blocks always(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/BlockGetter;Lnet/minecraft/core/BlockPos;)Z +public net.minecraft.world.level.block.Blocks never(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/BlockGetter;Lnet/minecraft/core/BlockPos;)Z + +public net.minecraft.client.gui.GuiGraphics renderItem(Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/world/level/Level;Lnet/minecraft/world/item/ItemStack;IIII)V + +# Introspection of RenderType +public net.minecraft.client.renderer.RenderStateShard name # name + +public net.minecraft.client.renderer.RenderType$CompositeRenderType +public net.minecraft.client.renderer.RenderType$CompositeRenderType state()Lnet/minecraft/client/renderer/RenderType$CompositeState; +public net.minecraft.client.renderer.RenderType$CompositeState states +public net.minecraft.client.renderer.RenderType$CompositeState shaderState +public net.minecraft.client.renderer.RenderType$CompositeState textureState +public net.minecraft.client.renderer.RenderType$CompositeState transparencyState +public net.minecraft.client.renderer.RenderType$CompositeState depthTestState +public net.minecraft.client.renderer.RenderType$CompositeState cullState +public net.minecraft.client.renderer.RenderType$CompositeState lightmapState +public net.minecraft.client.renderer.RenderType$CompositeState overlayState +public net.minecraft.client.renderer.RenderType$CompositeState layeringState +public net.minecraft.client.renderer.RenderType$CompositeState outputState +public net.minecraft.client.renderer.RenderType$CompositeState texturingState +public net.minecraft.client.renderer.RenderType$CompositeState writeMaskState +public net.minecraft.client.renderer.RenderType$CompositeState lineState +public net.minecraft.client.renderer.RenderStateShard$TextureStateShard mipmap +public net.minecraft.client.renderer.RenderStateShard$TextureStateShard blur +public net.minecraft.client.renderer.RenderStateShard$TextureStateShard texture +public net.minecraft.client.renderer.RenderStateShard$ShaderStateShard shader +public net.minecraft.client.renderer.texture.SpriteContents originalImage + +# Introspecting animated sprites +public net.minecraft.client.renderer.texture.SpriteContents$AnimatedTexture +public net.minecraft.client.renderer.texture.SpriteContents animatedTexture +public net.minecraft.client.renderer.texture.SpriteContents$AnimatedTexture frames +public net.minecraft.client.renderer.texture.SpriteContents$AnimatedTexture interpolateFrames +public net.minecraft.client.renderer.texture.SpriteContents$AnimatedTexture frameRowSize +public net.minecraft.client.renderer.texture.SpriteContents$FrameInfo +public net.minecraft.client.renderer.texture.SpriteContents$FrameInfo index +public net.minecraft.client.renderer.texture.SpriteContents$FrameInfo time + +public net.minecraft.client.renderer.texture.TextureAtlas sprites +public net.minecraft.client.renderer.texture.TextureAtlas getWidth()I +public net.minecraft.client.renderer.texture.TextureAtlas getHeight()I +public net.minecraft.client.renderer.texture.SpriteContents$AnimatedTexture uploadFrame(III)V + +# Entity forceloading +public net.minecraft.server.level.ServerLevel entityManager +public net.minecraft.world.level.entity.PersistentEntitySectionManager permanentStorage +public net.minecraft.world.level.entity.PersistentEntitySectionManager chunkVisibility + diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml new file mode 100644 index 00000000000..33b06eeab37 --- /dev/null +++ b/src/main/resources/META-INF/mods.toml @@ -0,0 +1,53 @@ +modLoader="javafml" +loaderVersion="[1,)" +#updateJSONURL="" +issueTrackerURL="https://github.com/AppliedEnergistics/Applied-Energistics-2/issues" +displayURL="https://github.com/AppliedEnergistics/Applied-Energistics-2" +logoFile="logo.png" +#credits="Thanks for this example mod goes to Java" +authors="TeamAppliedEnergistics" +license="See GitHub repository for details" + +[[mods]] +modId="ae2" +version="0.0.0" +displayName="Applied Energistics 2" +description="A Mod about Matter, Energy and using them to conquer the world.." + +[[mixins]] +config="ae2.mixins.json" + +[[dependencies.ae2]] + modId="neoforge" + mandatory=true + versionRange="${neoforge_version}" + ordering="NONE" + side="BOTH" + +[[dependencies.ae2]] + modId="minecraft" + mandatory=true + versionRange="${minecraft_version}" + ordering="NONE" + side="BOTH" + +[[dependencies.ae2]] + modId="jei" + mandatory=false + versionRange="${jei_version}" + ordering="AFTER" + side="CLIENT" + +[[dependencies.ae2]] + modId="theoneprobe" + mandatory=false + versionRange="${top_version}" + ordering="AFTER" + side="BOTH" + +[[dependencies.ae2]] + modId="jade" + mandatory=false + versionRange="${jade_version}" + ordering="AFTER" + side="BOTH" diff --git a/src/main/resources/ae2.accesswidener b/src/main/resources/ae2.accesswidener deleted file mode 100644 index 6a14ba8f64b..00000000000 --- a/src/main/resources/ae2.accesswidener +++ /dev/null @@ -1,163 +0,0 @@ -accessWidener v1 named - -accessible class net/minecraft/world/item/CreativeModeTab$Output -accessible class net/minecraft/client/resources/model/ModelManager$ReloadState - -# GUI rendering -extendable method net/minecraft/client/gui/screens/inventory/AbstractContainerScreen renderSlot (Lnet/minecraft/client/gui/GuiGraphics;Lnet/minecraft/world/inventory/Slot;)V -extendable method net/minecraft/client/gui/components/EditBox renderHighlight (Lnet/minecraft/client/gui/GuiGraphics;IIII)V -accessible method net/minecraft/client/gui/components/EditBox getMaxLength ()I -accessible method net/minecraft/client/gui/components/EditBox isEditable ()Z -accessible field net/minecraft/client/gui/components/AbstractWidget height I -extendable method net/minecraft/client/gui/screens/inventory/AbstractContainerScreen isHovering (Lnet/minecraft/world/inventory/Slot;DD)Z -accessible method net/minecraft/client/gui/screens/inventory/AbstractContainerScreen findSlot (DD)Lnet/minecraft/world/inventory/Slot; -accessible method net/minecraft/client/gui/GuiGraphics fillGradient (Lcom/mojang/blaze3d/vertex/VertexConsumer;IIIIIII)V -accessible method net/minecraft/client/gui/GuiGraphics renderItem (Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/world/level/Level;Lnet/minecraft/world/item/ItemStack;IIII)V - -# We need to change yPos of existing slots to resize the container -mutable field net/minecraft/world/inventory/Slot x I -mutable field net/minecraft/world/inventory/Slot y I - -# Custom slot init logic -accessible field net/minecraft/world/inventory/AbstractContainerMenu stateId I - -# For JEI registration -accessible method net/minecraft/world/item/crafting/RecipeManager byType (Lnet/minecraft/world/item/crafting/RecipeType;)Ljava/util/Map; - -# To disable water-bobbing of item entities (for growing crystals) -extendable method net/minecraft/world/entity/item/ItemEntity setUnderwaterMovement ()V - -# structures -accessible method net/minecraft/world/level/levelgen/structure/StructureType register (Ljava/lang/String;Lcom/mojang/serialization/Codec;)Lnet/minecraft/world/level/levelgen/structure/StructureType; -accessible method net/minecraft/world/level/levelgen/structure/pieces/StructurePieceType setPieceId (Lnet/minecraft/world/level/levelgen/structure/pieces/StructurePieceType$ContextlessType;Ljava/lang/String;)Lnet/minecraft/world/level/levelgen/structure/pieces/StructurePieceType; - -# features -accessible method net/minecraft/data/worldgen/placement/OrePlacements commonOrePlacement (ILnet/minecraft/world/level/levelgen/placement/PlacementModifier;)Ljava/util/List; - -# For overlay rendering -accessible class net/minecraft/client/renderer/RenderStateShard$LineStateShard - -# Rendering voxel shape outlines -accessible method net/minecraft/client/renderer/LevelRenderer renderShape (Lcom/mojang/blaze3d/vertex/PoseStack;Lcom/mojang/blaze3d/vertex/VertexConsumer;Lnet/minecraft/world/phys/shapes/VoxelShape;DDDFFFF)V - -# Fabric's Attack Block Hook doesn't set the interaction delay -accessible field net/minecraft/client/multiplayer/MultiPlayerGameMode destroyDelay I - -# Stairs -accessible method net/minecraft/world/level/block/StairBlock (Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;)V - -# BE registration -accessible class net/minecraft/world/level/block/entity/BlockEntityType$BlockEntitySupplier - -accessible class net/minecraft/client/renderer/RenderStateShard$TransparencyStateShard - -accessible field net/minecraft/client/gui/screens/Screen renderables Ljava/util/List; -accessible field net/minecraft/world/inventory/Slot slot I - -accessible field net/minecraft/client/Minecraft itemColors Lnet/minecraft/client/color/item/ItemColors; - -accessible field net/minecraft/client/resources/model/ModelManager bakedRegistry Ljava/util/Map; -accessible method net/minecraft/client/renderer/block/model/ItemOverrides ()V - -accessible field net/minecraft/client/renderer/block/model/BlockModel FACE_BAKERY Lnet/minecraft/client/renderer/block/model/FaceBakery; -accessible method net/minecraft/client/renderer/block/model/FaceBakery recalculateWinding ([ILnet/minecraft/core/Direction;)V - -accessible method net/minecraft/client/renderer/RenderType create (Ljava/lang/String;Lcom/mojang/blaze3d/vertex/VertexFormat;Lcom/mojang/blaze3d/vertex/VertexFormat$Mode;IZZLnet/minecraft/client/renderer/RenderType$CompositeState;)Lnet/minecraft/client/renderer/RenderType$CompositeRenderType; - -accessible class net/minecraft/client/renderer/RenderType$CompositeState -accessible field net/minecraft/client/renderer/RenderStateShard POSITION_COLOR_SHADER Lnet/minecraft/client/renderer/RenderStateShard$ShaderStateShard; - -extendable method net/minecraft/world/entity/item/PrimedTnt explode ()V - -accessible method net/minecraft/advancements/CriteriaTriggers register (Lnet/minecraft/advancements/CriterionTrigger;)Lnet/minecraft/advancements/CriterionTrigger; - -accessible field net/minecraft/world/entity/item/ItemEntity age I - -accessible field net/minecraft/world/item/Item craftingRemainingItem Lnet/minecraft/world/item/Item; -mutable field net/minecraft/world/item/Item craftingRemainingItem Lnet/minecraft/world/item/Item; - -accessible class net/minecraft/client/renderer/block/model/BlockElementFace$Deserializer - -accessible class net/minecraft/world/entity/npc/VillagerTrades$EmeraldForItems -accessible class net/minecraft/world/entity/npc/VillagerTrades$ItemsForEmeralds - -accessible method net/minecraft/world/damagesource/DamageSources source (Lnet/minecraft/resources/ResourceKey;Lnet/minecraft/world/entity/Entity;)Lnet/minecraft/world/damagesource/DamageSource; - -accessible method net/minecraft/client/gui/screens/Screen setInitialFocus (Lnet/minecraft/client/gui/components/events/GuiEventListener;)V -accessible method net/minecraft/client/gui/screens/Screen changeFocus (Lnet/minecraft/client/gui/ComponentPath;)V - -## Tests -accessible field net/minecraft/gametest/framework/GameTestHelper testInfo Lnet/minecraft/gametest/framework/GameTestInfo; - -### Datagens -accessible class net/minecraft/client/renderer/block/model/ItemTransform$Deserializer -accessible field net/minecraft/client/renderer/block/model/ItemTransform$Deserializer DEFAULT_ROTATION Lorg/joml/Vector3f; -accessible field net/minecraft/client/renderer/block/model/ItemTransform$Deserializer DEFAULT_TRANSLATION Lorg/joml/Vector3f; -accessible field net/minecraft/client/renderer/block/model/ItemTransform$Deserializer DEFAULT_SCALE Lorg/joml/Vector3f; -accessible field net/minecraft/client/renderer/block/model/BlockModel$GuiLight name Ljava/lang/String; -accessible method net/minecraft/client/renderer/block/model/BlockElement uvsByFace (Lnet/minecraft/core/Direction;)[F - -accessible method net/minecraft/client/gui/Font getFontSet (Lnet/minecraft/resources/ResourceLocation;)Lnet/minecraft/client/gui/font/FontSet; -accessible field net/minecraft/client/gui/screens/LoadingOverlay reload Lnet/minecraft/server/packs/resources/ReloadInstance; - -accessible field net/minecraft/data/DataGenerator vanillaPackOutput Lnet/minecraft/data/PackOutput; -extendable method net/minecraft/data/recipes/RecipeProvider getName ()Ljava/lang/String; - -accessible field net/minecraft/core/RegistrySetBuilder entries Ljava/util/List; -accessible class net/minecraft/core/RegistrySetBuilder$RegistryStub -accessible field net/minecraft/data/registries/VanillaRegistries BUILDER Lnet/minecraft/core/RegistrySetBuilder; - -# workaround to make annihilation plane combinable in anvils -extendable method net/minecraft/world/item/Item getMaxDamage ()I - -# Guidebook access to recipe fields -accessible field net/minecraft/world/item/crafting/SmithingTransformRecipe template Lnet/minecraft/world/item/crafting/Ingredient; -accessible field net/minecraft/world/item/crafting/SmithingTransformRecipe base Lnet/minecraft/world/item/crafting/Ingredient; -accessible field net/minecraft/world/item/crafting/SmithingTransformRecipe addition Lnet/minecraft/world/item/crafting/Ingredient; -accessible field net/minecraft/world/item/crafting/SmithingTrimRecipe template Lnet/minecraft/world/item/crafting/Ingredient; -accessible field net/minecraft/world/item/crafting/SmithingTrimRecipe base Lnet/minecraft/world/item/crafting/Ingredient; -accessible field net/minecraft/world/item/crafting/SmithingTrimRecipe addition Lnet/minecraft/world/item/crafting/Ingredient; - -accessible method net/minecraft/world/level/block/Blocks always (Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/BlockGetter;Lnet/minecraft/core/BlockPos;)Z -accessible method net/minecraft/world/level/block/Blocks never (Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/BlockGetter;Lnet/minecraft/core/BlockPos;)Z - -# Introspection of RenderType -accessible field net/minecraft/client/renderer/RenderStateShard name Ljava/lang/String; -accessible method net/minecraft/client/renderer/RenderType$CompositeRenderType state ()Lnet/minecraft/client/renderer/RenderType$CompositeState; -accessible field net/minecraft/client/renderer/RenderType$CompositeState states Lcom/google/common/collect/ImmutableList; -accessible field net/minecraft/client/renderer/RenderType$CompositeState shaderState Lnet/minecraft/client/renderer/RenderStateShard$ShaderStateShard; -accessible field net/minecraft/client/renderer/RenderType$CompositeState textureState Lnet/minecraft/client/renderer/RenderStateShard$EmptyTextureStateShard; -accessible field net/minecraft/client/renderer/RenderType$CompositeState transparencyState Lnet/minecraft/client/renderer/RenderStateShard$TransparencyStateShard; -accessible field net/minecraft/client/renderer/RenderType$CompositeState depthTestState Lnet/minecraft/client/renderer/RenderStateShard$DepthTestStateShard; -accessible field net/minecraft/client/renderer/RenderType$CompositeState cullState Lnet/minecraft/client/renderer/RenderStateShard$CullStateShard; -accessible field net/minecraft/client/renderer/RenderType$CompositeState lightmapState Lnet/minecraft/client/renderer/RenderStateShard$LightmapStateShard; -accessible field net/minecraft/client/renderer/RenderType$CompositeState overlayState Lnet/minecraft/client/renderer/RenderStateShard$OverlayStateShard; -accessible field net/minecraft/client/renderer/RenderType$CompositeState layeringState Lnet/minecraft/client/renderer/RenderStateShard$LayeringStateShard; -accessible field net/minecraft/client/renderer/RenderType$CompositeState outputState Lnet/minecraft/client/renderer/RenderStateShard$OutputStateShard; -accessible field net/minecraft/client/renderer/RenderType$CompositeState texturingState Lnet/minecraft/client/renderer/RenderStateShard$TexturingStateShard; -accessible field net/minecraft/client/renderer/RenderType$CompositeState writeMaskState Lnet/minecraft/client/renderer/RenderStateShard$WriteMaskStateShard; -accessible field net/minecraft/client/renderer/RenderType$CompositeState lineState Lnet/minecraft/client/renderer/RenderStateShard$LineStateShard; -accessible field net/minecraft/client/renderer/RenderStateShard$TextureStateShard mipmap Z -accessible field net/minecraft/client/renderer/RenderStateShard$TextureStateShard blur Z -accessible field net/minecraft/client/renderer/RenderStateShard$TextureStateShard texture Ljava/util/Optional; -accessible field net/minecraft/client/renderer/RenderStateShard$ShaderStateShard shader Ljava/util/Optional; -accessible field net/minecraft/client/renderer/texture/SpriteContents originalImage Lcom/mojang/blaze3d/platform/NativeImage; - -# Introspecting animated sprites -accessible class net/minecraft/client/renderer/texture/SpriteContents$AnimatedTexture -accessible field net/minecraft/client/renderer/texture/SpriteContents animatedTexture Lnet/minecraft/client/renderer/texture/SpriteContents$AnimatedTexture; -accessible field net/minecraft/client/renderer/texture/SpriteContents$AnimatedTexture frames Ljava/util/List; -accessible field net/minecraft/client/renderer/texture/SpriteContents$AnimatedTexture interpolateFrames Z -accessible field net/minecraft/client/renderer/texture/SpriteContents$AnimatedTexture frameRowSize I -accessible class net/minecraft/client/renderer/texture/SpriteContents$FrameInfo -accessible field net/minecraft/client/renderer/texture/SpriteContents$FrameInfo index I -accessible field net/minecraft/client/renderer/texture/SpriteContents$FrameInfo time I -accessible field net/minecraft/client/renderer/texture/TextureAtlas sprites Ljava/util/List; -accessible method net/minecraft/client/renderer/texture/TextureAtlas getWidth ()I -accessible method net/minecraft/client/renderer/texture/TextureAtlas getHeight ()I -accessible method net/minecraft/client/renderer/texture/SpriteContents$AnimatedTexture uploadFrame (III)V - - -accessible field net/minecraft/server/level/ServerLevel entityManager Lnet/minecraft/world/level/entity/PersistentEntitySectionManager; -accessible field net/minecraft/world/level/entity/PersistentEntitySectionManager permanentStorage Lnet/minecraft/world/level/entity/EntityPersistentStorage; -accessible field net/minecraft/world/level/entity/PersistentEntitySectionManager chunkVisibility Lit/unimi/dsi/fastutil/longs/Long2ObjectMap; diff --git a/src/main/resources/ae2.mixins.json b/src/main/resources/ae2.mixins.json index aa9b870511f..4cc0f60a52b 100644 --- a/src/main/resources/ae2.mixins.json +++ b/src/main/resources/ae2.mixins.json @@ -1,22 +1,16 @@ { "required": true, - "minVersion": "0.8.2", + "minVersion": "0.8.3", "package": "appeng.mixins", "compatibilityLevel": "JAVA_11", "plugin": "appeng.mixins.ConfigPlugin", "mixins": [ "chunkloading.ChunkMapMixin", - "chunkloading.MinecraftServerMixin", - "chunkloading.ServerLevelMixin", "spatial.MinecraftServerMixin", "AnvilMenuMixin", "EarlyStartupMixin", "EnchantmentHelperMixin", - "DynamicLadderMixin", "ItemEntityMixin", - "OnNeighborUpdateMixin", - "BreakSpeedMixin", - "AnnihilationPlaneEnchantmentMixin", "tests.StructureUtilsMixin", "StructureTemplateMixin", "UnbreakingMixin" @@ -28,13 +22,13 @@ "unlitquad.BlockModelMixin", "unlitquad.BlockPartFaceDeserializerMixin", "AbstractContainerScreenMixin", - "BlockBreakParticleMixin", - "MouseWheelMixin", "GuiGraphicsMixin", "WrappedGenericStackTooltipModIdMixin", "ResizableSlotHighlightMixin", "PickColorMixin", + "TextureAtlasMixin", "PonderWorldMixin", + "ModelBakeryMixin", "VariantDeserializerMixin" ], "server": [], diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json deleted file mode 100644 index 6bbb87763b8..00000000000 --- a/src/main/resources/fabric.mod.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "schemaVersion": 1, - "id": "ae2", - "version": "${version}", - "name": "Applied Energistics 2", - "description": "A Mod about matter, energy and using them to conquer the world..", - "authors": ["TeamAppliedEnergistics"], - "contact": { - "homepage": "https://appliedenergistics.github.io/", - "sources": "https://github.com/AppliedEnergistics/Applied-Energistics-2/" - }, - "license": "LGPL", - "icon": "assets/ae2/logo.png", - "environment": "*", - "entrypoints": { - "client": ["appeng.core.AppEngClientStartup"], - "server": ["appeng.core.AppEngServerStartup"], - "fabric-datagen": ["appeng.datagen.DatagenEntrypoint"], - "rei_client": ["appeng.integration.modules.rei.ReiPlugin"], - "jei_mod_plugin": ["appeng.integration.modules.jei.JEIPlugin"], - "jade": ["appeng.integration.modules.jade.JadeModule"] - }, - "mixins": ["ae2.mixins.json"], - "depends": { - "fabricloader": ">=0.14.21", - "fabric": ">=0.83.1", - "minecraft": "1.20.1" - }, - "conflicts": { - "appliedenergistics2": "*" - }, - "suggests": {}, - "accessWidener": "ae2.accesswidener", - "custom": { - "waila:plugins": { - "id": "ae2:wthit", - "initializer": "appeng.integration.modules.wthit.WthitModule" - } - } -} diff --git a/src/main/resources/assets/ae2/logo.png b/src/main/resources/logo.png similarity index 100% rename from src/main/resources/assets/ae2/logo.png rename to src/main/resources/logo.png diff --git a/src/main/resources/pack.mcmeta b/src/main/resources/pack.mcmeta new file mode 100644 index 00000000000..6999600183a --- /dev/null +++ b/src/main/resources/pack.mcmeta @@ -0,0 +1,6 @@ +{ + "pack": { + "pack_format": 8, + "description": "Resources used for Applied Energistics 2" + } +} diff --git a/src/portaforgy/LICENSE b/src/portaforgy/LICENSE deleted file mode 100644 index d045a72a7d5..00000000000 --- a/src/portaforgy/LICENSE +++ /dev/null @@ -1,24 +0,0 @@ -These files have been taken from the 1.17 branch of Minecraft Forge at https://github.com/MinecraftForge/MinecraftForge - -Each file retains the original license as stated in the license header of each file. - -For informational purposes: - -/* - * Minecraft Forge - * Copyright (c) 2016-2021. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation version 2.1 - * of the License. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ diff --git a/src/portaforgy/java/net/minecraftforge/client/model/generators/BlockModelBuilder.java b/src/portaforgy/java/net/minecraftforge/client/model/generators/BlockModelBuilder.java deleted file mode 100644 index 15e60781064..00000000000 --- a/src/portaforgy/java/net/minecraftforge/client/model/generators/BlockModelBuilder.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Minecraft Forge - * Copyright (c) 2016-2021. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation version 2.1 - * of the License. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -package net.minecraftforge.client.model.generators; - -import net.minecraft.resources.ResourceLocation; -import net.minecraftforge.common.data.ExistingFileHelper; - -/** - * Builder for block models, does not currently provide any additional - * functionality over {@link ModelBuilder}, purely a stub class with a concrete - * generic. - * - * @see ModelProvider - * @see ModelBuilder - */ -public class BlockModelBuilder extends ModelBuilder { - - public BlockModelBuilder(ResourceLocation outputLocation, ExistingFileHelper existingFileHelper) { - super(outputLocation, existingFileHelper); - } -} diff --git a/src/portaforgy/java/net/minecraftforge/client/model/generators/BlockModelProvider.java b/src/portaforgy/java/net/minecraftforge/client/model/generators/BlockModelProvider.java deleted file mode 100644 index b2b385dcb75..00000000000 --- a/src/portaforgy/java/net/minecraftforge/client/model/generators/BlockModelProvider.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Minecraft Forge - * Copyright (c) 2016-2021. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation version 2.1 - * of the License. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -package net.minecraftforge.client.model.generators; - -import net.minecraft.data.DataGenerator; -import net.minecraft.data.PackOutput; -import net.minecraftforge.common.data.ExistingFileHelper; - -/** - * Stub class to extend for block model data providers, eliminates some - * boilerplate constructor parameters. - */ -public abstract class BlockModelProvider extends ModelProvider { - - public BlockModelProvider(PackOutput output, String modid, ExistingFileHelper existingFileHelper) { - super(output, modid, BLOCK_FOLDER, BlockModelBuilder::new, existingFileHelper); - } - - @Override - public String getName() { - return "Block Models: " + modid; - } -} diff --git a/src/portaforgy/java/net/minecraftforge/client/model/generators/BlockStateProvider.java b/src/portaforgy/java/net/minecraftforge/client/model/generators/BlockStateProvider.java deleted file mode 100644 index 1cb23c16c06..00000000000 --- a/src/portaforgy/java/net/minecraftforge/client/model/generators/BlockStateProvider.java +++ /dev/null @@ -1,613 +0,0 @@ -/* - * Minecraft Forge - * Copyright (c) 2016-2021. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation version 2.1 - * of the License. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -package net.minecraftforge.client.model.generators; - -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.function.Function; - -import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.data.CachedOutput; -import net.minecraft.data.PackOutput; -import net.minecraft.resources.ResourceKey; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; - -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.DoorBlock; -import net.minecraft.world.level.block.FenceBlock; -import net.minecraft.world.level.block.FenceGateBlock; -import net.minecraft.world.level.block.CrossCollisionBlock; -import net.minecraft.world.level.block.IronBarsBlock; -import net.minecraft.world.level.block.RotatedPillarBlock; -import net.minecraft.world.level.block.PipeBlock; -import net.minecraft.world.level.block.SlabBlock; -import net.minecraft.world.level.block.StairBlock; -import net.minecraft.world.level.block.TrapDoorBlock; -import net.minecraft.world.level.block.WallBlock; -import net.minecraft.world.level.block.state.properties.WallSide; -import net.minecraft.data.DataGenerator; -import net.minecraft.data.DataProvider; -import net.minecraft.world.level.block.state.properties.Property; -import net.minecraft.world.level.block.state.properties.AttachFace; -import net.minecraft.world.level.block.state.properties.BlockStateProperties; -import net.minecraft.world.level.block.state.properties.DoorHingeSide; -import net.minecraft.world.level.block.state.properties.DoubleBlockHalf; -import net.minecraft.world.level.block.state.properties.Half; -import net.minecraft.world.level.block.state.properties.SlabType; -import net.minecraft.world.level.block.state.properties.StairsShape; -import net.minecraft.core.Direction; -import net.minecraft.core.Direction.Axis; -import net.minecraftforge.common.data.ExistingFileHelper; -import net.minecraft.resources.ResourceLocation; - -/** - * Data provider for blockstate files. Extends {@link BlockModelProvider} so that - * blockstates and their referenced models can be provided in tandem. - */ -public abstract class BlockStateProvider implements DataProvider { - - private static final Logger LOGGER = LogManager.getLogger(); - - @VisibleForTesting - protected final Map registeredBlocks = new LinkedHashMap<>(); - - private final PackOutput output; - private final String modid; - private final BlockModelProvider blockModels; - private final ItemModelProvider itemModels; - - public BlockStateProvider(PackOutput output, String modid, ExistingFileHelper exFileHelper) { - this.output = output; - this.modid = modid; - this.blockModels = new BlockModelProvider(output, modid, exFileHelper) { - @Override protected void registerModels() {} - }; - this.itemModels = new ItemModelProvider(output, modid, this.blockModels.existingFileHelper) { - @Override protected void registerModels() {} - }; - } - - @Override - public CompletableFuture run(CachedOutput cache) { - models().clear(); - itemModels().clear(); - registeredBlocks.clear(); - registerStatesAndModels(); - - var futures = new ArrayList>(); - futures.add(models().generateAll(cache)); - futures.add(itemModels().generateAll(cache)); - - for (var entry : registeredBlocks.entrySet()) { - futures.add(saveBlockState(cache, entry.getValue().toJson(), entry.getKey())); - } - return CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new)); - } - - protected abstract void registerStatesAndModels(); - - public VariantBlockStateBuilder getVariantBuilder(Block b) { - if (registeredBlocks.containsKey(b)) { - IGeneratedBlockstate old = registeredBlocks.get(b); - Preconditions.checkState(old instanceof VariantBlockStateBuilder); - return (VariantBlockStateBuilder) old; - } else { - VariantBlockStateBuilder ret = new VariantBlockStateBuilder(b); - registeredBlocks.put(b, ret); - return ret; - } - } - - public MultiPartBlockStateBuilder getMultipartBuilder(Block b) { - if (registeredBlocks.containsKey(b)) { - IGeneratedBlockstate old = registeredBlocks.get(b); - Preconditions.checkState(old instanceof MultiPartBlockStateBuilder); - return (MultiPartBlockStateBuilder) old; - } else { - MultiPartBlockStateBuilder ret = new MultiPartBlockStateBuilder(b); - registeredBlocks.put(b, ret); - return ret; - } - } - - - public BlockModelProvider models() { - return blockModels; - } - - public ItemModelProvider itemModels() { - return itemModels; - } - - public ResourceLocation modLoc(String name) { - return new ResourceLocation(modid, name); - } - - public ResourceLocation mcLoc(String name) { - return new ResourceLocation(name); - } - - private ResourceLocation key(Block block) { - return BuiltInRegistries.BLOCK.getKey(block); - } - - private String name(Block block) { - return key(block).getPath(); - } - - public ResourceLocation blockTexture(Block block) { - ResourceLocation name = getRegistryName(block); - return new ResourceLocation(name.getNamespace(), ModelProvider.BLOCK_FOLDER + "/" + name.getPath()); - } - - private ResourceLocation extend(ResourceLocation rl, String suffix) { - return new ResourceLocation(rl.getNamespace(), rl.getPath() + suffix); - } - - public ModelFile cubeAll(Block block) { - return models().cubeAll(name(block), blockTexture(block)); - } - - public void simpleBlock(Block block) { - simpleBlock(block, cubeAll(block)); - } - - public void simpleBlock(Block block, Function expander) { - simpleBlock(block, expander.apply(cubeAll(block))); - } - - public void simpleBlock(Block block, ModelFile model) { - simpleBlock(block, new ConfiguredModel(model)); - } - - public void simpleBlockItem(Block block, ModelFile model) { - itemModels().getBuilder(getRegistryName(block).getPath()).parent(model); - } - - public void simpleBlock(Block block, ConfiguredModel... models) { - getVariantBuilder(block) - .partialState().setModels(models); - } - - public void axisBlock(RotatedPillarBlock block) { - axisBlock(block, blockTexture(block)); - } - - public void logBlock(RotatedPillarBlock block) { - axisBlock(block, blockTexture(block), extend(blockTexture(block), "_top")); - } - - public void axisBlock(RotatedPillarBlock block, ResourceLocation baseName) { - axisBlock(block, extend(baseName, "_side"), extend(baseName, "_end")); - } - - public void axisBlock(RotatedPillarBlock block, ResourceLocation side, ResourceLocation end) { - axisBlock(block, models().cubeColumn(name(block), side, end), models().cubeColumnHorizontal(name(block) + "_horizontal", side, end)); - } - - public void axisBlock(RotatedPillarBlock block, ModelFile vertical, ModelFile horizontal) { - getVariantBuilder(block) - .partialState().with(RotatedPillarBlock.AXIS, Axis.Y) - .modelForState().modelFile(vertical).addModel() - .partialState().with(RotatedPillarBlock.AXIS, Axis.Z) - .modelForState().modelFile(horizontal).rotationX(90).addModel() - .partialState().with(RotatedPillarBlock.AXIS, Axis.X) - .modelForState().modelFile(horizontal).rotationX(90).rotationY(90).addModel(); - } - - private static final int DEFAULT_ANGLE_OFFSET = 180; - - public void horizontalBlock(Block block, ResourceLocation side, ResourceLocation front, ResourceLocation top) { - horizontalBlock(block, models().orientable(name(block), side, front, top)); - } - - public void horizontalBlock(Block block, ModelFile model) { - horizontalBlock(block, model, DEFAULT_ANGLE_OFFSET); - } - - public void horizontalBlock(Block block, ModelFile model, int angleOffset) { - horizontalBlock(block, $ -> model, angleOffset); - } - - public void horizontalBlock(Block block, Function modelFunc) { - horizontalBlock(block, modelFunc, DEFAULT_ANGLE_OFFSET); - } - - public void horizontalBlock(Block block, Function modelFunc, int angleOffset) { - getVariantBuilder(block) - .forAllStates(state -> ConfiguredModel.builder() - .modelFile(modelFunc.apply(state)) - .rotationY(((int) state.getValue(BlockStateProperties.HORIZONTAL_FACING).toYRot() + angleOffset) % 360) - .build() - ); - } - - public void horizontalFaceBlock(Block block, ModelFile model) { - horizontalFaceBlock(block, model, DEFAULT_ANGLE_OFFSET); - } - - public void horizontalFaceBlock(Block block, ModelFile model, int angleOffset) { - horizontalFaceBlock(block, $ -> model, angleOffset); - } - - public void horizontalFaceBlock(Block block, Function modelFunc) { - horizontalFaceBlock(block, modelFunc, DEFAULT_ANGLE_OFFSET); - } - - public void horizontalFaceBlock(Block block, Function modelFunc, int angleOffset) { - getVariantBuilder(block) - .forAllStates(state -> ConfiguredModel.builder() - .modelFile(modelFunc.apply(state)) - .rotationX(state.getValue(BlockStateProperties.ATTACH_FACE).ordinal() * 90) - .rotationY((((int) state.getValue(BlockStateProperties.HORIZONTAL_FACING).toYRot() + angleOffset) + (state.getValue(BlockStateProperties.ATTACH_FACE) == AttachFace.CEILING ? 180 : 0)) % 360) - .build() - ); - } - - public void directionalBlock(Block block, ModelFile model) { - directionalBlock(block, model, DEFAULT_ANGLE_OFFSET); - } - - public void directionalBlock(Block block, ModelFile model, int angleOffset) { - directionalBlock(block, $ -> model, angleOffset); - } - - public void directionalBlock(Block block, Function modelFunc) { - directionalBlock(block, modelFunc, DEFAULT_ANGLE_OFFSET); - } - - public void directionalBlock(Block block, Function modelFunc, int angleOffset) { - getVariantBuilder(block) - .forAllStates(state -> { - Direction dir = state.getValue(BlockStateProperties.FACING); - return ConfiguredModel.builder() - .modelFile(modelFunc.apply(state)) - .rotationX(dir == Direction.DOWN ? 180 : dir.getAxis().isHorizontal() ? 90 : 0) - .rotationY(dir.getAxis().isVertical() ? 0 : (((int) dir.toYRot()) + angleOffset) % 360) - .build(); - }); - } - - public void stairsBlock(StairBlock block, ResourceLocation texture) { - stairsBlock(block, texture, texture, texture); - } - - public void stairsBlock(StairBlock block, String name, ResourceLocation texture) { - stairsBlock(block, name, texture, texture, texture); - } - - public void stairsBlock(StairBlock block, ResourceLocation side, ResourceLocation bottom, ResourceLocation top) { - stairsBlockInternal(block, getRegistryName(block).toString(), side, bottom, top); - } - - public void stairsBlock(StairBlock block, String name, ResourceLocation side, ResourceLocation bottom, ResourceLocation top) { - stairsBlockInternal(block, name + "_stairs", side, bottom, top); - } - - private void stairsBlockInternal(StairBlock block, String baseName, ResourceLocation side, ResourceLocation bottom, ResourceLocation top) { - ModelFile stairs = models().stairs(baseName, side, bottom, top); - ModelFile stairsInner = models().stairsInner(baseName + "_inner", side, bottom, top); - ModelFile stairsOuter = models().stairsOuter(baseName + "_outer", side, bottom, top); - stairsBlock(block, stairs, stairsInner, stairsOuter); - } - - public void stairsBlock(StairBlock block, ModelFile stairs, ModelFile stairsInner, ModelFile stairsOuter) { - getVariantBuilder(block) - .forAllStatesExcept(state -> { - Direction facing = state.getValue(StairBlock.FACING); - Half half = state.getValue(StairBlock.HALF); - StairsShape shape = state.getValue(StairBlock.SHAPE); - int yRot = (int) facing.getClockWise().toYRot(); // Stairs model is rotated 90 degrees clockwise for some reason - if (shape == StairsShape.INNER_LEFT || shape == StairsShape.OUTER_LEFT) { - yRot += 270; // Left facing stairs are rotated 90 degrees clockwise - } - if (shape != StairsShape.STRAIGHT && half == Half.TOP) { - yRot += 90; // Top stairs are rotated 90 degrees clockwise - } - yRot %= 360; - boolean uvlock = yRot != 0 || half == Half.TOP; // Don't set uvlock for states that have no rotation - return ConfiguredModel.builder() - .modelFile(shape == StairsShape.STRAIGHT ? stairs : shape == StairsShape.INNER_LEFT || shape == StairsShape.INNER_RIGHT ? stairsInner : stairsOuter) - .rotationX(half == Half.BOTTOM ? 0 : 180) - .rotationY(yRot) - .uvLock(uvlock) - .build(); - }, StairBlock.WATERLOGGED); - } - - public void slabBlock(SlabBlock block, ResourceLocation doubleslab, ResourceLocation texture) { - slabBlock(block, doubleslab, texture, texture, texture); - } - - public void slabBlock(SlabBlock block, ResourceLocation doubleslab, ResourceLocation side, ResourceLocation bottom, ResourceLocation top) { - slabBlock(block, models().slab(name(block), side, bottom, top), models().slabTop(name(block) + "_top", side, bottom, top), models().getExistingFile(doubleslab)); - } - - public void slabBlock(SlabBlock block, ModelFile bottom, ModelFile top, ModelFile doubleslab) { - getVariantBuilder(block) - .partialState().with(SlabBlock.TYPE, SlabType.BOTTOM).addModels(new ConfiguredModel(bottom)) - .partialState().with(SlabBlock.TYPE, SlabType.TOP).addModels(new ConfiguredModel(top)) - .partialState().with(SlabBlock.TYPE, SlabType.DOUBLE).addModels(new ConfiguredModel(doubleslab)); - } - - public void fourWayBlock(CrossCollisionBlock block, ModelFile post, ModelFile side) { - MultiPartBlockStateBuilder builder = getMultipartBuilder(block) - .part().modelFile(post).addModel().end(); - fourWayMultipart(builder, side); - } - - public void fourWayMultipart(MultiPartBlockStateBuilder builder, ModelFile side) { - PipeBlock.PROPERTY_BY_DIRECTION.entrySet().forEach(e -> { - Direction dir = e.getKey(); - if (dir.getAxis().isHorizontal()) { - builder.part().modelFile(side).rotationY((((int) dir.toYRot()) + 180) % 360).uvLock(true).addModel() - .condition(e.getValue(), true); - } - }); - } - - public void fenceBlock(FenceBlock block, ResourceLocation texture) { - String baseName = getRegistryName(block).toString(); - fourWayBlock(block, models().fencePost(baseName + "_post", texture), models().fenceSide(baseName + "_side", texture)); - } - - public void fenceBlock(FenceBlock block, String name, ResourceLocation texture) { - fourWayBlock(block, models().fencePost(name + "_fence_post", texture), models().fenceSide(name + "_fence_side", texture)); - } - - public void fenceGateBlock(FenceGateBlock block, ResourceLocation texture) { - fenceGateBlockInternal(block, getRegistryName(block).toString(), texture); - } - - public void fenceGateBlock(FenceGateBlock block, String name, ResourceLocation texture) { - fenceGateBlockInternal(block, name + "_fence_gate", texture); - } - - private void fenceGateBlockInternal(FenceGateBlock block, String baseName, ResourceLocation texture) { - ModelFile gate = models().fenceGate(baseName, texture); - ModelFile gateOpen = models().fenceGateOpen(baseName + "_open", texture); - ModelFile gateWall = models().fenceGateWall(baseName + "_wall", texture); - ModelFile gateWallOpen = models().fenceGateWallOpen(baseName + "_wall_open", texture); - fenceGateBlock(block, gate, gateOpen, gateWall, gateWallOpen); - } - - public void fenceGateBlock(FenceGateBlock block, ModelFile gate, ModelFile gateOpen, ModelFile gateWall, ModelFile gateWallOpen) { - getVariantBuilder(block).forAllStatesExcept(state -> { - ModelFile model = gate; - if (state.getValue(FenceGateBlock.IN_WALL)) { - model = gateWall; - } - if (state.getValue(FenceGateBlock.OPEN)) { - model = model == gateWall ? gateWallOpen : gateOpen; - } - return ConfiguredModel.builder() - .modelFile(model) - .rotationY((int) state.getValue(FenceGateBlock.FACING).toYRot()) - .uvLock(true) - .build(); - }, FenceGateBlock.POWERED); - } - - public void wallBlock(WallBlock block, ResourceLocation texture) { - wallBlockInternal(block, getRegistryName(block).toString(), texture); - } - - public void wallBlock(WallBlock block, String name, ResourceLocation texture) { - wallBlockInternal(block, name + "_wall", texture); - } - - private void wallBlockInternal(WallBlock block, String baseName, ResourceLocation texture) { - wallBlock(block, models().wallPost(baseName + "_post", texture), models().wallSide(baseName + "_side", texture), models().wallSideTall(baseName + "_side_tall", texture)); - } - - public static final ImmutableMap> WALL_PROPS = ImmutableMap.>builder() - .put(Direction.EAST, BlockStateProperties.EAST_WALL) - .put(Direction.NORTH, BlockStateProperties.NORTH_WALL) - .put(Direction.SOUTH, BlockStateProperties.SOUTH_WALL) - .put(Direction.WEST, BlockStateProperties.WEST_WALL) - .build(); - - public void wallBlock(WallBlock block, ModelFile post, ModelFile side, ModelFile sideTall) { - MultiPartBlockStateBuilder builder = getMultipartBuilder(block) - .part().modelFile(post).addModel() - .condition(WallBlock.UP, true).end(); - WALL_PROPS.entrySet().stream() - .filter(e -> e.getKey().getAxis().isHorizontal()) - .forEach(e -> { - wallSidePart(builder, side, e, WallSide.LOW); - wallSidePart(builder, sideTall, e, WallSide.TALL); - }); - } - - private void wallSidePart(MultiPartBlockStateBuilder builder, ModelFile model, Map.Entry> entry, WallSide height) { - builder.part() - .modelFile(model) - .rotationY((((int) entry.getKey().toYRot()) + 180) % 360) - .uvLock(true) - .addModel() - .condition(entry.getValue(), height); - } - - public void paneBlock(IronBarsBlock block, ResourceLocation pane, ResourceLocation edge) { - paneBlockInternal(block, getRegistryName(block).toString(), pane, edge); - } - - public void paneBlock(IronBarsBlock block, String name, ResourceLocation pane, ResourceLocation edge) { - paneBlockInternal(block, name + "_pane", pane, edge); - } - - private void paneBlockInternal(IronBarsBlock block, String baseName, ResourceLocation pane, ResourceLocation edge) { - ModelFile post = models().panePost(baseName + "_post", pane, edge); - ModelFile side = models().paneSide(baseName + "_side", pane, edge); - ModelFile sideAlt = models().paneSideAlt(baseName + "_side_alt", pane, edge); - ModelFile noSide = models().paneNoSide(baseName + "_noside", pane); - ModelFile noSideAlt = models().paneNoSideAlt(baseName + "_noside_alt", pane); - paneBlock(block, post, side, sideAlt, noSide, noSideAlt); - } - - public void paneBlock(IronBarsBlock block, ModelFile post, ModelFile side, ModelFile sideAlt, ModelFile noSide, ModelFile noSideAlt) { - MultiPartBlockStateBuilder builder = getMultipartBuilder(block) - .part().modelFile(post).addModel().end(); - PipeBlock.PROPERTY_BY_DIRECTION.entrySet().forEach(e -> { - Direction dir = e.getKey(); - if (dir.getAxis().isHorizontal()) { - boolean alt = dir == Direction.SOUTH; - builder.part().modelFile(alt || dir == Direction.WEST ? sideAlt : side).rotationY(dir.getAxis() == Axis.X ? 90 : 0).addModel() - .condition(e.getValue(), true).end() - .part().modelFile(alt || dir == Direction.EAST ? noSideAlt : noSide).rotationY(dir == Direction.WEST ? 270 : dir == Direction.SOUTH ? 90 : 0).addModel() - .condition(e.getValue(), false); - } - }); - } - - public void doorBlock(DoorBlock block, ResourceLocation bottom, ResourceLocation top) { - doorBlockInternal(block, getRegistryName(block).toString(), bottom, top); - } - - public void doorBlock(DoorBlock block, String name, ResourceLocation bottom, ResourceLocation top) { - doorBlockInternal(block, name + "_door", bottom, top); - } - - private void doorBlockInternal(DoorBlock block, String baseName, ResourceLocation bottom, ResourceLocation top) { - ModelFile bottomLeft = models().doorBottomLeft(baseName + "_bottom", bottom, top); - ModelFile bottomRight = models().doorBottomRight(baseName + "_bottom_hinge", bottom, top); - ModelFile topLeft = models().doorTopLeft(baseName + "_top", bottom, top); - ModelFile topRight = models().doorTopRight(baseName + "_top_hinge", bottom, top); - doorBlock(block, bottomLeft, bottomRight, topLeft, topRight); - } - - public void doorBlock(DoorBlock block, ModelFile bottomLeft, ModelFile bottomRight, ModelFile topLeft, ModelFile topRight) { - getVariantBuilder(block).forAllStatesExcept(state -> { - int yRot = ((int) state.getValue(DoorBlock.FACING).toYRot()) + 90; - boolean rh = state.getValue(DoorBlock.HINGE) == DoorHingeSide.RIGHT; - boolean open = state.getValue(DoorBlock.OPEN); - boolean right = rh ^ open; - if (open) { - yRot += 90; - } - if (rh && open) { - yRot += 180; - } - yRot %= 360; - return ConfiguredModel.builder().modelFile(state.getValue(DoorBlock.HALF) == DoubleBlockHalf.LOWER ? (right ? bottomRight : bottomLeft) : (right ? topRight : topLeft)) - .rotationY(yRot) - .build(); - }, DoorBlock.POWERED); - } - - public void trapdoorBlock(TrapDoorBlock block, ResourceLocation texture, boolean orientable) { - trapdoorBlockInternal(block, getRegistryName(block).toString(), texture, orientable); - } - - public void trapdoorBlock(TrapDoorBlock block, String name, ResourceLocation texture, boolean orientable) { - trapdoorBlockInternal(block, name + "_trapdoor", texture, orientable); - } - - private void trapdoorBlockInternal(TrapDoorBlock block, String baseName, ResourceLocation texture, boolean orientable) { - ModelFile bottom = orientable ? models().trapdoorOrientableBottom(baseName + "_bottom", texture) : models().trapdoorBottom(baseName + "_bottom", texture); - ModelFile top = orientable ? models().trapdoorOrientableTop(baseName + "_top", texture) : models().trapdoorTop(baseName + "_top", texture); - ModelFile open = orientable ? models().trapdoorOrientableOpen(baseName + "_open", texture) : models().trapdoorOpen(baseName + "_open", texture); - trapdoorBlock(block, bottom, top, open, orientable); - } - - public void trapdoorBlock(TrapDoorBlock block, ModelFile bottom, ModelFile top, ModelFile open, boolean orientable) { - getVariantBuilder(block).forAllStatesExcept(state -> { - int xRot = 0; - int yRot = ((int) state.getValue(TrapDoorBlock.FACING).toYRot()) + 180; - boolean isOpen = state.getValue(TrapDoorBlock.OPEN); - if (orientable && isOpen && state.getValue(TrapDoorBlock.HALF) == Half.TOP) { - xRot += 180; - yRot += 180; - } - if (!orientable && !isOpen) { - yRot = 0; - } - yRot %= 360; - return ConfiguredModel.builder().modelFile(isOpen ? open : state.getValue(TrapDoorBlock.HALF) == Half.TOP ? top : bottom) - .rotationX(xRot) - .rotationY(yRot) - .build(); - }, TrapDoorBlock.POWERED, TrapDoorBlock.WATERLOGGED); - } - - private CompletableFuture saveBlockState(CachedOutput cache, JsonObject stateJson, Block owner) { - ResourceLocation blockName = Preconditions.checkNotNull(key(owner)); - Path outputPath = this.output.getOutputFolder(PackOutput.Target.RESOURCE_PACK) - .resolve(blockName.getNamespace()).resolve("blockstates").resolve(blockName.getPath() + ".json"); - return DataProvider.saveStable(cache, stateJson, outputPath); - } - - @Override - public String getName() { - return "Block States: " + modid; - } - - public static class ConfiguredModelList { - private final List models; - - private ConfiguredModelList(List models) { - Preconditions.checkArgument(!models.isEmpty()); - this.models = models; - } - - public ConfiguredModelList(ConfiguredModel model) { - this(ImmutableList.of(model)); - } - - public ConfiguredModelList(ConfiguredModel... models) { - this(Arrays.asList(models)); - } - - public JsonElement toJSON() { - if (models.size()==1) { - return models.get(0).toJSON(false); - } else { - JsonArray ret = new JsonArray(); - for (ConfiguredModel m:models) { - ret.add(m.toJSON(true)); - } - return ret; - } - } - - public ConfiguredModelList append(ConfiguredModel... models) { - return new ConfiguredModelList(ImmutableList.builder().addAll(this.models).add(models).build()); - } - } - - private static ResourceLocation getRegistryName(Block block) { - return BuiltInRegistries.BLOCK.getResourceKey(block).map(ResourceKey::location).orElse(null); - } -} diff --git a/src/portaforgy/java/net/minecraftforge/client/model/generators/ConfiguredModel.java b/src/portaforgy/java/net/minecraftforge/client/model/generators/ConfiguredModel.java deleted file mode 100644 index e8ec06bc837..00000000000 --- a/src/portaforgy/java/net/minecraftforge/client/model/generators/ConfiguredModel.java +++ /dev/null @@ -1,320 +0,0 @@ -/* - * Minecraft Forge - * Copyright (c) 2016-2021. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation version 2.1 - * of the License. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -package net.minecraftforge.client.model.generators; - -import java.util.Arrays; -import java.util.List; -import java.util.function.Function; -import java.util.stream.IntStream; - -import org.jetbrains.annotations.Nullable; - -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ObjectArrays; -import com.google.gson.JsonObject; - -import net.minecraft.client.resources.model.BlockModelRotation; -import net.minecraftforge.client.model.generators.MultiPartBlockStateBuilder.PartBuilder; -import net.minecraftforge.client.model.generators.VariantBlockStateBuilder.PartialBlockstate; - -/** - * Represents a model with blockstate configurations, e.g. rotation, uvlock, and - * random weight. - *

- * Can be manually constructed, created by static factory such as - * {@link #allYRotations(ModelFile, int, boolean)}, or created by builder via - * {@link #builder()}. - */ -public final class ConfiguredModel { - - /** - * The default random weight of configured models, used by convenience - * overloads. - */ - public static final int DEFAULT_WEIGHT = 1; - - public final ModelFile model; - public final int rotationX; - public final int rotationY; - public final boolean uvLock; - public final int weight; - - private static IntStream validRotations() { - return IntStream.range(0, 4).map(i -> i * 90); - } - - public static ConfiguredModel[] allYRotations(ModelFile model, int x, boolean uvlock) { - return allYRotations(model, x, uvlock, DEFAULT_WEIGHT); - } - - public static ConfiguredModel[] allYRotations(ModelFile model, int x, boolean uvlock, int weight) { - return validRotations() - .mapToObj(y -> new ConfiguredModel(model, x, y, uvlock, weight)) - .toArray(ConfiguredModel[]::new); - } - - public static ConfiguredModel[] allRotations(ModelFile model, boolean uvlock) { - return allRotations(model, uvlock, DEFAULT_WEIGHT); - } - - public static ConfiguredModel[] allRotations(ModelFile model, boolean uvlock, int weight) { - return validRotations() - .mapToObj(x -> allYRotations(model, x, uvlock, weight)) - .flatMap(Arrays::stream) - .toArray(ConfiguredModel[]::new); - } - - /** - * Construct a new {@link ConfiguredModel}. - * - * @param model the underlying model - * @param rotationX x-rotation to apply to the model - * @param rotationY y-rotation to apply to the model - * @param uvLock if uvlock should be enabled - * @param weight the random weight of the model - * - * @throws NullPointerException if {@code model} is {@code null} - * @throws IllegalArgumentException if x and/or y rotation are not valid (see - * {@link ModelRotation}) - * @throws IllegalArgumentException if weight is less than or equal to zero - */ - public ConfiguredModel(ModelFile model, int rotationX, int rotationY, boolean uvLock, int weight) { - Preconditions.checkNotNull(model); - this.model = model; - checkRotation(rotationX, rotationY); - this.rotationX = rotationX; - this.rotationY = rotationY; - this.uvLock = uvLock; - checkWeight(weight); - this.weight = weight; - } - - /** - * Construct a new {@link ConfiguredModel} with the {@link #DEFAULT_WEIGHT - * default random weight}. - * - * @param model the underlying model - * @param rotationX x-rotation to apply to the model - * @param rotationY y-rotation to apply to the model - * @param uvLock if uvlock should be enabled - * - * @throws NullPointerException if {@code model} is {@code null} - * @throws IllegalArgumentException if x and/or y rotation are not valid (see - * {@link ModelRotation}) - */ - public ConfiguredModel(ModelFile model, int rotationX, int rotationY, boolean uvLock) { - this(model, rotationX, rotationY, uvLock, DEFAULT_WEIGHT); - } - - /** - * Construct a new {@link ConfiguredModel} with the default rotation (0, 0), - * uvlock (false), and {@link #DEFAULT_WEIGHT default random weight}. - * - * @throws NullPointerException if {@code model} is {@code null} - */ - public ConfiguredModel(ModelFile model) { - this(model, 0, 0, false); - } - - static void checkRotation(int rotationX, int rotationY) { - Preconditions.checkArgument(BlockModelRotation.by(rotationX, rotationY) != null, "Invalid model rotation x=%d, y=%d", rotationX, rotationY); - } - - static void checkWeight(int weight) { - Preconditions.checkArgument(weight >= 1, "Model weight must be greater than or equal to 1. Found: %d", weight); - } - - JsonObject toJSON(boolean includeWeight) { - JsonObject modelJson = new JsonObject(); - modelJson.addProperty("model", model.getLocation().toString()); - if (rotationX != 0) - modelJson.addProperty("x", rotationX); - if (rotationY != 0) - modelJson.addProperty("y", rotationY); - if (uvLock) - modelJson.addProperty("uvlock", uvLock); - if (includeWeight && weight != DEFAULT_WEIGHT) - modelJson.addProperty("weight", weight); - return modelJson; - } - - /** - * Create a new unowned {@link Builder}. - * - * @return the builder - * @see Builder - */ - public static Builder builder() { - return new Builder<>(); - } - - static Builder builder(VariantBlockStateBuilder outer, PartialBlockstate state) { - return new Builder<>(models -> outer.setModels(state, models), ImmutableList.of()); - } - - static Builder builder(MultiPartBlockStateBuilder outer) { - return new Builder(models -> { - PartBuilder ret = outer.new PartBuilder(new BlockStateProvider.ConfiguredModelList(models)); - outer.addPart(ret); - return ret; - }, ImmutableList.of()); - } - - /** - * A builder for {@link ConfiguredModel}s, which can contain a callback for - * processing the finished result. If no callback is available (e.g. in the case - * of {@link ConfiguredModel#builder()}), some methods will not be available. - *

- * Multiple models can be configured at once through the use of - * {@link #nextModel()}. - * - * @param the type of the owning builder, which supplied the callback, and - * will be returned upon completion. - */ - public static class Builder { - - private ModelFile model; - @Nullable - private final Function callback; - private final List otherModels; - private int rotationX; - private int rotationY; - private boolean uvLock; - private int weight = DEFAULT_WEIGHT; - - Builder() { - this(null, ImmutableList.of()); - } - - Builder(@Nullable Function callback, List otherModels) { - this.callback = callback; - this.otherModels = otherModels; - } - - /** - * Set the underlying model object for this configured model. - * - * @param model the model - * @return this builder - * @throws NullPointerException if {@code model} is {@code null} - */ - public Builder modelFile(ModelFile model) { - Preconditions.checkNotNull(model, "Model must not be null"); - this.model = model; - return this; - } - - /** - * Set the x-rotation for this model. - * - * @param value the x-rotation value - * @return this builder - * @throws IllegalArgumentException if {@code value} is not a valid x-rotation - * (see {@link ModelRotation}) - */ - public Builder rotationX(int value) { - checkRotation(value, rotationY); - rotationX = value; - return this; - } - - /** - * Set the y-rotation for this model. - * - * @param value the y-rotation value - * @return this builder - * @throws IllegalArgumentException if {@code value} is not a valid y-rotation - * (see {@link ModelRotation}) - */ - public Builder rotationY(int value) { - checkRotation(rotationX, value); - rotationY = value; - return this; - } - - public Builder uvLock(boolean value) { - uvLock = value; - return this; - } - - /** - * Set the random weight for this model. - * - * @param value the weight value - * @return this builder - * @throws IllegalArgumentException if {@code value} is less than or equal to - * zero - */ - public Builder weight(int value) { - checkWeight(value); - weight = value; - return this; - } - - /** - * Build the most recent model, as if {@link #nextModel()} was never called. - * Useful for single-model builders. - * - * @return the most recently configured model - */ - public ConfiguredModel buildLast() { - return new ConfiguredModel(model, rotationX, rotationY, uvLock, weight); - } - - /** - * Build all configured models and return them as an array. - * - * @return the array of built models. - */ - public ConfiguredModel[] build() { - return ObjectArrays.concat(otherModels.toArray(new ConfiguredModel[0]), buildLast()); - } - - /** - * Apply the contained callback and return the owning builder object. What the - * callback does is not defined by this class, but most likely it adds the built - * models to the current variant being configured. - *

- * Known callbacks include: - *

    - *
  • {@link PartialBlockstate#modelForState()}
  • - *
  • {@link MultiPartBlockStateBuilder#part()}
  • - *
- * - * @return the owning builder object - * @throws NullPointerException if there is no owning builder (and thus no callback) - */ - public T addModel() { - Preconditions.checkNotNull(callback, "Cannot use addModel() without an owning builder present"); - return callback.apply(build()); - } - - /** - * Complete the current model and return a new builder instance with the same - * callback, and storing all previously built models. - * - * @return a new builder for configuring the next model - */ - public Builder nextModel() { - return new Builder<>(callback, Arrays.asList(build())); - } - } -} diff --git a/src/portaforgy/java/net/minecraftforge/client/model/generators/CustomLoaderBuilder.java b/src/portaforgy/java/net/minecraftforge/client/model/generators/CustomLoaderBuilder.java deleted file mode 100644 index 337b4d53e87..00000000000 --- a/src/portaforgy/java/net/minecraftforge/client/model/generators/CustomLoaderBuilder.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Minecraft Forge - * Copyright (c) 2016-2021. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation version 2.1 - * of the License. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -package net.minecraftforge.client.model.generators; - -import com.google.common.base.Preconditions; -import com.google.gson.JsonObject; -import net.minecraft.resources.ResourceLocation; -import net.minecraftforge.common.data.ExistingFileHelper; - -import java.util.LinkedHashMap; -import java.util.Map; - -public abstract class CustomLoaderBuilder> -{ - protected final ResourceLocation loaderId; - protected final T parent; - protected final ExistingFileHelper existingFileHelper; - protected final Map visibility = new LinkedHashMap<>(); - - protected CustomLoaderBuilder(ResourceLocation loaderId, T parent, ExistingFileHelper existingFileHelper) - { - this.loaderId = loaderId; - this.parent = parent; - this.existingFileHelper = existingFileHelper; - } - - public CustomLoaderBuilder visibility(String partName, boolean show) - { - Preconditions.checkNotNull(partName, "partName must not be null"); - this.visibility.put(partName, show); - return this; - } - - public T end() - { - return parent; - } - - public JsonObject toJson(JsonObject json) - { - json.addProperty("loader", loaderId.toString()); - - if (visibility.size() > 0) - { - JsonObject visibilityObj = new JsonObject(); - - for(Map.Entry entry : visibility.entrySet()) - { - visibilityObj.addProperty(entry.getKey(), entry.getValue()); - } - - json.add("visibility", visibilityObj); - } - - return json; - } -} diff --git a/src/portaforgy/java/net/minecraftforge/client/model/generators/IGeneratedBlockstate.java b/src/portaforgy/java/net/minecraftforge/client/model/generators/IGeneratedBlockstate.java deleted file mode 100644 index 5a0e4f25c70..00000000000 --- a/src/portaforgy/java/net/minecraftforge/client/model/generators/IGeneratedBlockstate.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Minecraft Forge - * Copyright (c) 2016-2021. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation version 2.1 - * of the License. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -package net.minecraftforge.client.model.generators; - -import com.google.common.annotations.VisibleForTesting; -import com.google.gson.JsonObject; - -@VisibleForTesting -public interface IGeneratedBlockstate { - - JsonObject toJson(); -} diff --git a/src/portaforgy/java/net/minecraftforge/client/model/generators/ItemModelBuilder.java b/src/portaforgy/java/net/minecraftforge/client/model/generators/ItemModelBuilder.java deleted file mode 100644 index 6de5ac45773..00000000000 --- a/src/portaforgy/java/net/minecraftforge/client/model/generators/ItemModelBuilder.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Minecraft Forge - * Copyright (c) 2016-2021. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation version 2.1 - * of the License. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -package net.minecraftforge.client.model.generators; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import com.google.common.base.Preconditions; -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; - -import net.minecraft.resources.ResourceLocation; -import net.minecraftforge.common.data.ExistingFileHelper; - -/** - * Builder for item models, adds the ability to build overrides via - * {@link #override()}. - */ -public class ItemModelBuilder extends ModelBuilder { - - protected List overrides = new ArrayList<>(); - - public ItemModelBuilder(ResourceLocation outputLocation, ExistingFileHelper existingFileHelper) { - super(outputLocation, existingFileHelper); - } - - public OverrideBuilder override() { - OverrideBuilder ret = new OverrideBuilder(); - overrides.add(ret); - return ret; - } - - /** - * Get an existing override builder - * - * @param index the index of the existing override builder - * @return the override builder - * @throws IndexOutOfBoundsException if {@code} index is out of bounds - */ - public OverrideBuilder override(int index) { - Preconditions.checkElementIndex(index, overrides.size(), "override"); - return overrides.get(index); - } - - @Override - public JsonObject toJson() { - JsonObject root = super.toJson(); - if (!overrides.isEmpty()) { - JsonArray overridesJson = new JsonArray(); - overrides.stream().map(OverrideBuilder::toJson).forEach(overridesJson::add); - root.add("overrides", overridesJson); - } - return root; - } - - public class OverrideBuilder { - - private ModelFile model; - private final Map predicates = new LinkedHashMap<>(); - - public OverrideBuilder model(ModelFile model) { - this.model = model; - model.assertExistence(); - return this; - } - - public OverrideBuilder predicate(ResourceLocation key, float value) { - this.predicates.put(key, value); - return this; - } - - public ItemModelBuilder end() { return ItemModelBuilder.this; } - - JsonObject toJson() { - JsonObject ret = new JsonObject(); - JsonObject predicatesJson = new JsonObject(); - predicates.forEach((key, val) -> predicatesJson.addProperty(key.toString(), val)); - ret.add("predicate", predicatesJson); - ret.addProperty("model", model.getLocation().toString()); - return ret; - } - } - -} diff --git a/src/portaforgy/java/net/minecraftforge/client/model/generators/ItemModelProvider.java b/src/portaforgy/java/net/minecraftforge/client/model/generators/ItemModelProvider.java deleted file mode 100644 index 39f9171e20b..00000000000 --- a/src/portaforgy/java/net/minecraftforge/client/model/generators/ItemModelProvider.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Minecraft Forge - * Copyright (c) 2016-2021. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation version 2.1 - * of the License. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -package net.minecraftforge.client.model.generators; - -import net.minecraft.data.DataGenerator; -import net.minecraft.data.PackOutput; -import net.minecraftforge.common.data.ExistingFileHelper; - -/** - * Stub class to extend for item model data providers, eliminates some - * boilerplate constructor parameters. - */ -public abstract class ItemModelProvider extends ModelProvider { - - public ItemModelProvider(PackOutput output, String modid, ExistingFileHelper existingFileHelper) { - super(output, modid, ITEM_FOLDER, ItemModelBuilder::new, existingFileHelper); - } - - - @Override - public String getName() { - return "Item Models: " + modid; - } -} diff --git a/src/portaforgy/java/net/minecraftforge/client/model/generators/ModelBuilder.java b/src/portaforgy/java/net/minecraftforge/client/model/generators/ModelBuilder.java deleted file mode 100644 index a3ead5c6aa8..00000000000 --- a/src/portaforgy/java/net/minecraftforge/client/model/generators/ModelBuilder.java +++ /dev/null @@ -1,683 +0,0 @@ -/* - * Minecraft Forge - * Copyright (c) 2016-2021. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation version 2.1 - * of the License. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -package net.minecraftforge.client.model.generators; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.function.BiConsumer; -import java.util.function.BiFunction; -import java.util.stream.Collectors; - -import org.jetbrains.annotations.Nullable; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Preconditions; -import com.google.gson.Gson; -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; - -import net.minecraft.client.renderer.block.model.BlockFaceUV; -import net.minecraft.client.renderer.block.model.BlockModel.GuiLight; -import net.minecraft.client.renderer.block.model.BlockElement; -import net.minecraft.client.renderer.block.model.BlockElementFace; -import net.minecraft.client.renderer.block.model.BlockElementRotation; -import net.minecraft.client.renderer.block.model.ItemTransform; -import net.minecraft.client.renderer.texture.MissingTextureAtlasSprite; -import net.minecraft.core.Direction; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.util.Mth; -import net.minecraft.world.item.ItemDisplayContext; -import org.joml.Vector3f; -import net.minecraftforge.common.data.ExistingFileHelper; - -/** - * General purpose model builder, contains all the commonalities between item - * and block models. - * - * @see ModelProvider - * @see BlockModelBuilder - * @see ItemModelBuilder - * - * @param Self type, for simpler chaining of methods. - */ -@SuppressWarnings("deprecation") -public class ModelBuilder> extends ModelFile { - @Nullable - protected ModelFile parent; - protected final Map textures = new LinkedHashMap<>(); - protected final TransformsBuilder transforms = new TransformsBuilder(); - protected final ExistingFileHelper existingFileHelper; - - protected boolean ambientOcclusion = true; - protected GuiLight guiLight = null; - - protected final List elements = new ArrayList<>(); - - protected CustomLoaderBuilder customLoader = null; - - protected ModelBuilder(ResourceLocation outputLocation, ExistingFileHelper existingFileHelper) { - super(outputLocation); - this.existingFileHelper = existingFileHelper; - } - - @SuppressWarnings("unchecked") - private T self() { return (T) this; } - - @Override - protected boolean exists() { - return true; - } - - /** - * Set the parent model for the current model. - * - * @param parent the parent model - * @return this builder - * @throws NullPointerException if {@code parent} is {@code null} - * @throws IllegalStateException if {@code parent} does not {@link ModelFile#assertExistence() exist} - */ - public T parent(ModelFile parent) { - Preconditions.checkNotNull(parent, "Parent must not be null"); - parent.assertExistence(); - this.parent = parent; - return self(); - } - - /** - * Set the texture for a given dictionary key. - * - * @param key the texture key - * @param texture the texture, can be another key e.g. {@code "#all"} - * @return this builder - * @throws NullPointerException if {@code key} is {@code null} - * @throws NullPointerException if {@code texture} is {@code null} - * @throws IllegalStateException if {@code texture} is not a key (does not start - * with {@code '#'}) and does not exist in any - * known resource pack - */ - public T texture(String key, String texture) { - Preconditions.checkNotNull(key, "Key must not be null"); - Preconditions.checkNotNull(texture, "Texture must not be null"); - if (texture.charAt(0) == '#') { - this.textures.put(key, texture); - return self(); - } else { - ResourceLocation asLoc; - if (texture.contains(":")) { - asLoc = new ResourceLocation(texture); - } else { - asLoc = new ResourceLocation(getLocation().getNamespace(), texture); - } - return texture(key, asLoc); - } - } - - /** - * Set the texture for a given dictionary key. - * - * @param key the texture key - * @param texture the texture - * @return this builder - * @throws NullPointerException if {@code key} is {@code null} - * @throws NullPointerException if {@code texture} is {@code null} - * @throws IllegalStateException if {@code texture} is not a key (does not start - * with {@code '#'}) and does not exist in any - * known resource pack - */ - public T texture(String key, ResourceLocation texture) { - Preconditions.checkNotNull(key, "Key must not be null"); - Preconditions.checkNotNull(texture, "Texture must not be null"); - Preconditions.checkArgument(existingFileHelper.exists(texture, ModelProvider.TEXTURE), - "Texture %s does not exist in any known resource pack", texture); - this.textures.put(key, texture.toString()); - return self(); - } - - public TransformsBuilder transforms() { - return transforms; - } - - public T ao(boolean ao) { - this.ambientOcclusion = ao; - return self(); - } - - /** - * @param gui3d - * @return this builder - * @deprecated Unused in 1.15, use {@link #guiLight(GuiLight)} instead. - */ - @Deprecated - public T gui3d(boolean gui3d) { - return self(); - } - - public T guiLight(GuiLight light) { - this.guiLight = light; - return self(); - } - - public ElementBuilder element() { - Preconditions.checkState(customLoader == null, "Cannot use elements and custom loaders at the same time"); - ElementBuilder ret = new ElementBuilder(); - elements.add(ret); - return ret; - } - - /** - * Get an existing element builder - * - * @param index the index of the existing element builder - * @return the element builder - * @throws IndexOutOfBoundsException if {@code} index is out of bounds - */ - public ElementBuilder element(int index) { - Preconditions.checkState(customLoader == null, "Cannot use elements and custom loaders at the same time"); - Preconditions.checkElementIndex(index, elements.size(), "Element index"); - return elements.get(index); - } - - /** - * Gets the number of elements in this model builder - * @return the number of elements in this model builder - */ - public int getElementCount() - { - return elements.size(); - } - - /** - * Use a custom loader instead of the vanilla elements. - * @param customLoaderFactory - * @return the custom loader builder - */ - public > L customLoader(BiFunction customLoaderFactory) - { - Preconditions.checkState(elements.size() == 0, "Cannot use elements and custom loaders at the same time"); - Preconditions.checkNotNull(customLoaderFactory, "customLoaderFactory must not be null"); - L customLoader = customLoaderFactory.apply(self(), existingFileHelper); - this.customLoader = customLoader; - return customLoader; - } - - @VisibleForTesting - public JsonObject toJson() { - JsonObject root = new JsonObject(); - - if (this.parent != null) { - root.addProperty("parent", this.parent.getLocation().toString()); - } - - if (!this.ambientOcclusion) { - root.addProperty("ambientocclusion", this.ambientOcclusion); - } - - if (this.guiLight != null) { - root.addProperty("gui_light", this.guiLight.name); - } - - Map transforms = this.transforms.build(); - if (!transforms.isEmpty()) { - JsonObject display = new JsonObject(); - for (Entry e : transforms.entrySet()) { - JsonObject transform = new JsonObject(); - ItemTransform vec = e.getValue(); - if (vec.equals(ItemTransform.NO_TRANSFORM)) continue; - if (!vec.rotation.equals(ItemTransform.Deserializer.DEFAULT_ROTATION)) { - transform.add("rotation", serializeVector3f(vec.rotation)); - } - if (!vec.translation.equals(ItemTransform.Deserializer.DEFAULT_TRANSLATION)) { - transform.add("translation", serializeVector3f(e.getValue().translation)); - } - if (!vec.scale.equals(ItemTransform.Deserializer.DEFAULT_SCALE)) { - transform.add("scale", serializeVector3f(e.getValue().scale)); - } - display.add(e.getKey().getSerializedName(), transform); - } - root.add("display", display); - } - - if (!this.textures.isEmpty()) { - JsonObject textures = new JsonObject(); - for (Entry e : this.textures.entrySet()) { - textures.addProperty(e.getKey(), serializeLocOrKey(e.getValue())); - } - root.add("textures", textures); - } - - if (!this.elements.isEmpty()) { - JsonArray elements = new JsonArray(); - this.elements.stream().map(ElementBuilder::build).forEach(part -> { - JsonObject partObj = new JsonObject(); - partObj.add("from", serializeVector3f(part.from)); - partObj.add("to", serializeVector3f(part.to)); - - if (part.rotation != null) { - JsonObject rotation = new JsonObject(); - rotation.add("origin", serializeVector3f(part.rotation.origin())); - rotation.addProperty("axis", part.rotation.axis().getSerializedName()); - rotation.addProperty("angle", part.rotation.angle()); - if (part.rotation.rescale()) { - rotation.addProperty("rescale", part.rotation.rescale()); - } - partObj.add("rotation", rotation); - } - - if (!part.shade) { - partObj.addProperty("shade", part.shade); - } - - JsonObject faces = new JsonObject(); - for (Direction dir : Direction.values()) { - BlockElementFace face = part.faces.get(dir); - if (face == null) continue; - - JsonObject faceObj = new JsonObject(); - faceObj.addProperty("texture", serializeLocOrKey(face.texture)); - if (!Arrays.equals(face.uv.uvs, part.uvsByFace(dir))) { - faceObj.add("uv", new Gson().toJsonTree(face.uv.uvs)); - } - if (face.cullForDirection != null) { - faceObj.addProperty("cullface", face.cullForDirection.getSerializedName()); - } - if (face.uv.rotation != 0) { - faceObj.addProperty("rotation", face.uv.rotation); - } - if (face.tintIndex != -1) { - faceObj.addProperty("tintindex", face.tintIndex); - } - faces.add(dir.getSerializedName(), faceObj); - } - if (!part.faces.isEmpty()) { - partObj.add("faces", faces); - } - elements.add(partObj); - }); - root.add("elements", elements); - } - - if (customLoader != null) - return customLoader.toJson(root); - - return root; - } - - private String serializeLocOrKey(String tex) { - if (tex.charAt(0) == '#') { - return tex; - } - return new ResourceLocation(tex).toString(); - } - - private JsonArray serializeVector3f(Vector3f vec) { - JsonArray ret = new JsonArray(); - ret.add(serializeFloat(vec.x())); - ret.add(serializeFloat(vec.y())); - ret.add(serializeFloat(vec.z())); - return ret; - } - - private Number serializeFloat(float f) { - if ((int) f == f) { - return (int) f; - } - return f; - } - - public class ElementBuilder { - - private Vector3f from = new Vector3f(); - private Vector3f to = new Vector3f(16, 16, 16); - private final Map faces = new LinkedHashMap<>(); - private RotationBuilder rotation; - private boolean shade = true; - - private void validateCoordinate(float coord, char name) { - Preconditions.checkArgument(!(coord < -16.0F) && !(coord > 32.0F), "Position " + name + " out of range, must be within [-16, 32]. Found: %d", coord); - } - - private void validatePosition(Vector3f pos) { - validateCoordinate(pos.x(), 'x'); - validateCoordinate(pos.y(), 'y'); - validateCoordinate(pos.z(), 'z'); - } - - /** - * Set the "from" position for this element. - * - * @param x x-position for this vector - * @param y y-position for this vector - * @param z z-position for this vector - * @return this builder - * @throws IllegalArgumentException if the vector is out of bounds (any - * coordinate not between -16 and 32, - * inclusive) - */ - public ElementBuilder from(float x, float y, float z) { - this.from = new Vector3f(x, y, z); - validatePosition(this.from); - return this; - } - - /** - * Set the "to" position for this element. - * - * @param x x-position for this vector - * @param y y-position for this vector - * @param z z-position for this vector - * @return this builder - * @throws IllegalArgumentException if the vector is out of bounds (any - * coordinate not between -16 and 32, - * inclusive) - */ - public ElementBuilder to(float x, float y, float z) { - this.to = new Vector3f(x, y, z); - validatePosition(this.to); - return this; - } - - /** - * Return or create the face builder for the given direction. - * - * @param dir the direction - * @return the face builder for the given direction - * @throws NullPointerException if {@code dir} is {@code null} - */ - public FaceBuilder face(Direction dir) { - Preconditions.checkNotNull(dir, "Direction must not be null"); - return faces.computeIfAbsent(dir, FaceBuilder::new); - } - - public RotationBuilder rotation() { - if (this.rotation == null) { - this.rotation = new RotationBuilder(); - } - return this.rotation; - } - - public ElementBuilder shade(boolean shade) { - this.shade = shade; - return this; - } - - /** - * Modify all possible faces dynamically using a function, creating new - * faces as necessary. - * - * @param action the function to apply to each direction - * @return this builder - * @throws NullPointerException if {@code action} is {@code null} - */ - public ElementBuilder allFaces(BiConsumer action) { - Arrays.stream(Direction.values()) - .forEach(d -> action.accept(d, face(d))); - return this; - } - - /** - * Modify all existing faces dynamically using a function. - * - * @param action the function to apply to each direction - * @return this builder - * @throws NullPointerException if {@code action} is {@code null} - */ - public ElementBuilder faces(BiConsumer action) { - faces.entrySet().stream() - .forEach(e -> action.accept(e.getKey(), e.getValue())); - return this; - } - - /** - * Texture all possible faces in the current element with the given - * texture, creating new faces where necessary. - * - * @param texture the texture - * @return this builder - * @throws NullPointerException if {@code texture} is {@code null} - */ - public ElementBuilder textureAll(String texture) { - return allFaces(addTexture(texture)); - } - - /** - * Texture all existing faces in the current element with the given - * texture. - * - * @param texture the texture - * @return this builder - * @throws NullPointerException if {@code texture} is {@code null} - */ - public ElementBuilder texture(String texture) { - return faces(addTexture(texture)); - } - - /** - * Create a typical cube element, creating new faces as needed, applying the - * given texture, and setting the cullface. - * - * @param texture the texture - * @return this builder - * @throws NullPointerException if {@code texture} is {@code null} - */ - public ElementBuilder cube(String texture) { - return allFaces(addTexture(texture).andThen((dir, f) -> f.cullface(dir))); - } - - private BiConsumer addTexture(String texture) { - return ($, f) -> f.texture(texture); - } - - BlockElement build() { - Map faces = this.faces.entrySet().stream() - .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().build(), (k1, k2) -> { throw new IllegalArgumentException(); }, LinkedHashMap::new)); - return new BlockElement(from, to, faces, rotation == null ? null : rotation.build(), shade); - } - - public T end() { return self(); } - - public class FaceBuilder { - - private Direction cullface; - private int tintindex = -1; - private String texture = MissingTextureAtlasSprite.getLocation().toString(); - private float[] uvs; - private FaceRotation rotation = FaceRotation.ZERO; - - FaceBuilder(Direction dir) { - // param unused for functional match - } - - public FaceBuilder cullface(@Nullable Direction dir) { - this.cullface = dir; - return this; - } - - public FaceBuilder tintindex(int index) { - this.tintindex = index; - return this; - } - - /** - * Set the texture for the current face. - * - * @param texture the texture - * @return this builder - * @throws NullPointerException if {@code texture} is {@code null} - */ - public FaceBuilder texture(String texture) { - Preconditions.checkNotNull(texture, "Texture must not be null"); - this.texture = texture; - return this; - } - - public FaceBuilder uvs(float u1, float v1, float u2, float v2) { - this.uvs = new float[] { u1, v1, u2, v2 }; - return this; - } - - /** - * Set the texture rotation for the current face. - * - * @param rot the rotation - * @return this builder - * @throws NullPointerException if {@code rot} is {@code null} - */ - public FaceBuilder rotation(FaceRotation rot) { - Preconditions.checkNotNull(rot, "Rotation must not be null"); - this.rotation = rot; - return this; - } - - BlockElementFace build() { - if (this.texture == null) { - throw new IllegalStateException("A model face must have a texture"); - } - return new BlockElementFace(cullface, tintindex, texture, new BlockFaceUV(uvs, rotation.rotation)); - } - - public ElementBuilder end() { return ElementBuilder.this; } - } - - public class RotationBuilder { - - private Vector3f origin; - private Direction.Axis axis; - private float angle; - private boolean rescale; - - public RotationBuilder origin(float x, float y, float z) { - this.origin = new Vector3f(x, y, z); - return this; - } - - /** - * @param axis the axis of rotation - * @return this builder - * @throws NullPointerException if {@code axis} is {@code null} - */ - public RotationBuilder axis(Direction.Axis axis) { - Preconditions.checkNotNull(axis, "Axis must not be null"); - this.axis = axis; - return this; - } - - /** - * @param angle the rotation angle - * @return this builder - * @throws IllegalArgumentException if {@code angle} is invalid (not one of 0, +/-22.5, +/-45) - */ - public RotationBuilder angle(float angle) { - // Same logic from BlockPart.Deserializer#parseAngle - Preconditions.checkArgument(angle == 0.0F || Mth.abs(angle) == 22.5F || Mth.abs(angle) == 45.0F, "Invalid rotation %f found, only -45/-22.5/0/22.5/45 allowed", angle); - this.angle = angle; - return this; - } - - public RotationBuilder rescale(boolean rescale) { - this.rescale = rescale; - return this; - } - - BlockElementRotation build() { - return new BlockElementRotation(origin, axis, angle, rescale); - } - - public ElementBuilder end() { return ElementBuilder.this; } - } - } - - public enum FaceRotation { - ZERO(0), - CLOCKWISE_90(90), - UPSIDE_DOWN(180), - COUNTERCLOCKWISE_90(270), - ; - - final int rotation; - - private FaceRotation(int rotation) { - this.rotation = rotation; - } - } - - public class TransformsBuilder { - - private final Map transforms = new LinkedHashMap<>(); - - /** - * Begin building a new transform for the given perspective. - * - * @param type the perspective to create or return the builder for - * @return the builder for the given perspective - * @throws NullPointerException if {@code type} is {@code null} - */ - public TransformVecBuilder transform(ItemDisplayContext type) { - Preconditions.checkNotNull(type, "Perspective cannot be null"); - return transforms.computeIfAbsent(type, TransformVecBuilder::new); - } - - Map build() { - return this.transforms.entrySet().stream() - .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().build(), (k1, k2) -> { throw new IllegalArgumentException(); }, LinkedHashMap::new)); - } - - public T end() { return self(); } - - public class TransformVecBuilder { - - private Vector3f rotation = new Vector3f(ItemTransform.Deserializer.DEFAULT_ROTATION); - private Vector3f translation = new Vector3f(ItemTransform.Deserializer.DEFAULT_TRANSLATION); - private Vector3f scale = new Vector3f(ItemTransform.Deserializer.DEFAULT_SCALE); - - TransformVecBuilder(ItemDisplayContext type) { - // param unused for functional match - } - - public TransformVecBuilder rotation(float x, float y, float z) { - this.rotation = new Vector3f(x, y, z); - return this; - } - - public TransformVecBuilder translation(float x, float y, float z) { - this.translation = new Vector3f(x, y, z); - return this; - } - - public TransformVecBuilder scale(float sc) { - return scale(sc, sc, sc); - } - - public TransformVecBuilder scale(float x, float y, float z) { - this.scale = new Vector3f(x, y, z); - return this; - } - - ItemTransform build() { - return new ItemTransform(rotation, translation, scale); - } - - public TransformsBuilder end() { return TransformsBuilder.this; } - } - } -} diff --git a/src/portaforgy/java/net/minecraftforge/client/model/generators/ModelFile.java b/src/portaforgy/java/net/minecraftforge/client/model/generators/ModelFile.java deleted file mode 100644 index d6d0c1a3026..00000000000 --- a/src/portaforgy/java/net/minecraftforge/client/model/generators/ModelFile.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Minecraft Forge - * Copyright (c) 2016-2021. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation version 2.1 - * of the License. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -package net.minecraftforge.client.model.generators; - -import com.google.common.base.Preconditions; - -import net.minecraft.resources.ResourceLocation; -import net.minecraftforge.common.data.ExistingFileHelper; - -public abstract class ModelFile { - - protected ResourceLocation location; - - protected ModelFile(ResourceLocation location) { - this.location = location; - } - - protected abstract boolean exists(); - - public ResourceLocation getLocation() { - assertExistence(); - return location; - } - - /** - * Assert that this model exists. - * @throws IllegalStateException if this model does not exist - */ - public void assertExistence() { - Preconditions.checkState(exists(), "Model at %s does not exist", location); - } - - public ResourceLocation getUncheckedLocation() { - return location; - } - - public static class UncheckedModelFile extends ModelFile { - - public UncheckedModelFile(String location) { - this(new ResourceLocation(location)); - } - public UncheckedModelFile(ResourceLocation location) { - super(location); - } - - @Override - protected boolean exists() { - return true; - } - } - - public static class ExistingModelFile extends ModelFile { - private final ExistingFileHelper existingHelper; - - public ExistingModelFile(ResourceLocation location, ExistingFileHelper existingHelper) { - super(location); - this.existingHelper = existingHelper; - } - - @Override - protected boolean exists() { - if (getUncheckedLocation().getPath().contains(".")) - return existingHelper.exists(getUncheckedLocation(), ModelProvider.MODEL_WITH_EXTENSION); - else - return existingHelper.exists(getUncheckedLocation(), ModelProvider.MODEL); - } - } -} diff --git a/src/portaforgy/java/net/minecraftforge/client/model/generators/ModelProvider.java b/src/portaforgy/java/net/minecraftforge/client/model/generators/ModelProvider.java deleted file mode 100644 index 950ad858b27..00000000000 --- a/src/portaforgy/java/net/minecraftforge/client/model/generators/ModelProvider.java +++ /dev/null @@ -1,388 +0,0 @@ -/* - * Minecraft Forge - * Copyright (c) 2016-2021. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation version 2.1 - * of the License. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -package net.minecraftforge.client.model.generators; - -import java.nio.file.Path; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.function.BiFunction; -import java.util.function.Function; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Preconditions; - -import net.minecraft.data.CachedOutput; -import net.minecraft.data.DataGenerator; -import net.minecraft.data.DataProvider; -import net.minecraft.data.PackOutput; -import net.minecraft.server.packs.PackType; -import net.minecraft.resources.ResourceLocation; -import net.minecraftforge.common.data.ExistingFileHelper; -import net.minecraftforge.common.data.ExistingFileHelper.ResourceType; - -public abstract class ModelProvider> implements DataProvider { - - public static final String BLOCK_FOLDER = "block"; - public static final String ITEM_FOLDER = "item"; - - protected static final ResourceType TEXTURE = new ResourceType(PackType.CLIENT_RESOURCES, ".png", "textures"); - protected static final ResourceType MODEL = new ResourceType(PackType.CLIENT_RESOURCES, ".json", "models"); - protected static final ResourceType MODEL_WITH_EXTENSION = new ResourceType(PackType.CLIENT_RESOURCES, "", "models"); - - protected final PackOutput output; - protected final String modid; - protected final String folder; - protected final Function factory; - @VisibleForTesting - public final Map generatedModels = new HashMap<>(); - @VisibleForTesting - public final ExistingFileHelper existingFileHelper; - - protected abstract void registerModels(); - - public ModelProvider(PackOutput output, String modid, String folder, Function factory, ExistingFileHelper existingFileHelper) { - Preconditions.checkNotNull(output); - this.output = output; - Preconditions.checkNotNull(modid); - this.modid = modid; - Preconditions.checkNotNull(folder); - this.folder = folder; - Preconditions.checkNotNull(factory); - this.factory = factory; - Preconditions.checkNotNull(existingFileHelper); - this.existingFileHelper = existingFileHelper; - } - - public ModelProvider(PackOutput output, String modid, String folder, BiFunction builderFromModId, ExistingFileHelper existingFileHelper) { - this(output, modid, folder, loc->builderFromModId.apply(loc, existingFileHelper), existingFileHelper); - } - - public T getBuilder(String path) { - Preconditions.checkNotNull(path, "Path must not be null"); - ResourceLocation outputLoc = extendWithFolder(path.contains(":") ? new ResourceLocation(path) : new ResourceLocation(modid, path)); - this.existingFileHelper.trackGenerated(outputLoc, MODEL); - return generatedModels.computeIfAbsent(outputLoc, factory); - } - - private ResourceLocation extendWithFolder(ResourceLocation rl) { - if (rl.getPath().contains("/")) { - return rl; - } - return new ResourceLocation(rl.getNamespace(), folder + "/" + rl.getPath()); - } - - public ResourceLocation modLoc(String name) { - return new ResourceLocation(modid, name); - } - - public ResourceLocation mcLoc(String name) { - return new ResourceLocation(name); - } - - public T withExistingParent(String name, String parent) { - return withExistingParent(name, mcLoc(parent)); - } - - public T withExistingParent(String name, ResourceLocation parent) { - return getBuilder(name).parent(getExistingFile(parent)); - } - - public T cube(String name, ResourceLocation down, ResourceLocation up, ResourceLocation north, ResourceLocation south, ResourceLocation east, ResourceLocation west, ResourceLocation particle) { - return withExistingParent(name, "cube") - .texture("down", down) - .texture("up", up) - .texture("north", north) - .texture("south", south) - .texture("east", east) - .texture("west", west) - .texture("particle", particle); - } - - private T singleTexture(String name, String parent, ResourceLocation texture) { - return singleTexture(name, mcLoc(parent), texture); - } - - public T singleTexture(String name, ResourceLocation parent, ResourceLocation texture) { - return singleTexture(name, parent, "texture", texture); - } - - private T singleTexture(String name, String parent, String textureKey, ResourceLocation texture) { - return singleTexture(name, mcLoc(parent), textureKey, texture); - } - - public T singleTexture(String name, ResourceLocation parent, String textureKey, ResourceLocation texture) { - return withExistingParent(name, parent) - .texture(textureKey, texture); - } - - public T cubeAll(String name, ResourceLocation texture) { - return singleTexture(name, BLOCK_FOLDER + "/cube_all", "all", texture); - } - - public T cubeTop(String name, ResourceLocation side, ResourceLocation top) { - return withExistingParent(name, BLOCK_FOLDER + "/cube_top") - .texture("side", side) - .texture("top", top); - } - - private T sideBottomTop(String name, String parent, ResourceLocation side, ResourceLocation bottom, ResourceLocation top) { - return withExistingParent(name, parent) - .texture("side", side) - .texture("bottom", bottom) - .texture("top", top); - } - - public T cubeBottomTop(String name, ResourceLocation side, ResourceLocation bottom, ResourceLocation top) { - return sideBottomTop(name, BLOCK_FOLDER + "/cube_bottom_top", side, bottom, top); - } - - public T cubeColumn(String name, ResourceLocation side, ResourceLocation end) { - return withExistingParent(name, BLOCK_FOLDER + "/cube_column") - .texture("side", side) - .texture("end", end); - } - - public T cubeColumnHorizontal(String name, ResourceLocation side, ResourceLocation end) { - return withExistingParent(name, BLOCK_FOLDER + "/cube_column_horizontal") - .texture("side", side) - .texture("end", end); - } - - public T orientableVertical(String name, ResourceLocation side, ResourceLocation front) { - return withExistingParent(name, BLOCK_FOLDER + "/orientable_vertical") - .texture("side", side) - .texture("front", front); - } - - public T orientableWithBottom(String name, ResourceLocation side, ResourceLocation front, ResourceLocation bottom, ResourceLocation top) { - return withExistingParent(name, BLOCK_FOLDER + "/orientable_with_bottom") - .texture("side", side) - .texture("front", front) - .texture("bottom", bottom) - .texture("top", top); - } - - public T orientable(String name, ResourceLocation side, ResourceLocation front, ResourceLocation top) { - return withExistingParent(name, BLOCK_FOLDER + "/orientable") - .texture("side", side) - .texture("front", front) - .texture("top", top); - } - - public T crop(String name, ResourceLocation crop) { - return singleTexture(name, BLOCK_FOLDER + "/crop", "crop", crop); - } - - public T cross(String name, ResourceLocation cross) { - return singleTexture(name, BLOCK_FOLDER + "/cross", "cross", cross); - } - - public T stairs(String name, ResourceLocation side, ResourceLocation bottom, ResourceLocation top) { - return sideBottomTop(name, BLOCK_FOLDER + "/stairs", side, bottom, top); - } - - public T stairsOuter(String name, ResourceLocation side, ResourceLocation bottom, ResourceLocation top) { - return sideBottomTop(name, BLOCK_FOLDER + "/outer_stairs", side, bottom, top); - } - - public T stairsInner(String name, ResourceLocation side, ResourceLocation bottom, ResourceLocation top) { - return sideBottomTop(name, BLOCK_FOLDER + "/inner_stairs", side, bottom, top); - } - - public T slab(String name, ResourceLocation side, ResourceLocation bottom, ResourceLocation top) { - return sideBottomTop(name, BLOCK_FOLDER + "/slab", side, bottom, top); - } - - public T slabTop(String name, ResourceLocation side, ResourceLocation bottom, ResourceLocation top) { - return sideBottomTop(name, BLOCK_FOLDER + "/slab_top", side, bottom, top); - } - - public T fencePost(String name, ResourceLocation texture) { - return singleTexture(name, BLOCK_FOLDER + "/fence_post", texture); - } - - public T fenceSide(String name, ResourceLocation texture) { - return singleTexture(name, BLOCK_FOLDER + "/fence_side", texture); - } - - public T fenceInventory(String name, ResourceLocation texture) { - return singleTexture(name, BLOCK_FOLDER + "/fence_inventory", texture); - } - - public T fenceGate(String name, ResourceLocation texture) { - return singleTexture(name, BLOCK_FOLDER + "/template_fence_gate", texture); - } - - public T fenceGateOpen(String name, ResourceLocation texture) { - return singleTexture(name, BLOCK_FOLDER + "/template_fence_gate_open", texture); - } - - public T fenceGateWall(String name, ResourceLocation texture) { - return singleTexture(name, BLOCK_FOLDER + "/template_fence_gate_wall", texture); - } - - public T fenceGateWallOpen(String name, ResourceLocation texture) { - return singleTexture(name, BLOCK_FOLDER + "/template_fence_gate_wall_open", texture); - } - - public T wallPost(String name, ResourceLocation wall) { - return singleTexture(name, BLOCK_FOLDER + "/template_wall_post", "wall", wall); - } - - public T wallSide(String name, ResourceLocation wall) { - return singleTexture(name, BLOCK_FOLDER + "/template_wall_side", "wall", wall); - } - - public T wallSideTall(String name, ResourceLocation wall) { - return singleTexture(name, BLOCK_FOLDER + "/template_wall_side_tall", "wall", wall); - } - - public T wallInventory(String name, ResourceLocation wall) { - return singleTexture(name, BLOCK_FOLDER + "/wall_inventory", "wall", wall); - } - - private T pane(String name, String parent, ResourceLocation pane, ResourceLocation edge) { - return withExistingParent(name, BLOCK_FOLDER + "/" + parent) - .texture("pane", pane) - .texture("edge", edge); - } - - public T panePost(String name, ResourceLocation pane, ResourceLocation edge) { - return pane(name, "template_glass_pane_post", pane, edge); - } - - public T paneSide(String name, ResourceLocation pane, ResourceLocation edge) { - return pane(name, "template_glass_pane_side", pane, edge); - } - - public T paneSideAlt(String name, ResourceLocation pane, ResourceLocation edge) { - return pane(name, "template_glass_pane_side_alt", pane, edge); - } - - public T paneNoSide(String name, ResourceLocation pane) { - return singleTexture(name, BLOCK_FOLDER + "/template_glass_pane_noside", "pane", pane); - } - - public T paneNoSideAlt(String name, ResourceLocation pane) { - return singleTexture(name, BLOCK_FOLDER + "/template_glass_pane_noside_alt", "pane", pane); - } - - private T door(String name, String model, ResourceLocation bottom, ResourceLocation top) { - return withExistingParent(name, BLOCK_FOLDER + "/" + model) - .texture("bottom", bottom) - .texture("top", top); - } - - public T doorBottomLeft(String name, ResourceLocation bottom, ResourceLocation top) { - return door(name, "door_bottom", bottom, top); - } - - public T doorBottomRight(String name, ResourceLocation bottom, ResourceLocation top) { - return door(name, "door_bottom_rh", bottom, top); - } - - public T doorTopLeft(String name, ResourceLocation bottom, ResourceLocation top) { - return door(name, "door_top", bottom, top); - } - - public T doorTopRight(String name, ResourceLocation bottom, ResourceLocation top) { - return door(name, "door_top_rh", bottom, top); - } - - public T trapdoorBottom(String name, ResourceLocation texture) { - return singleTexture(name, BLOCK_FOLDER + "/template_trapdoor_bottom", texture); - } - - public T trapdoorTop(String name, ResourceLocation texture) { - return singleTexture(name, BLOCK_FOLDER + "/template_trapdoor_top", texture); - } - - public T trapdoorOpen(String name, ResourceLocation texture) { - return singleTexture(name, BLOCK_FOLDER + "/template_trapdoor_open", texture); - } - - public T trapdoorOrientableBottom(String name, ResourceLocation texture) { - return singleTexture(name, BLOCK_FOLDER + "/template_orientable_trapdoor_bottom", texture); - } - - public T trapdoorOrientableTop(String name, ResourceLocation texture) { - return singleTexture(name, BLOCK_FOLDER + "/template_orientable_trapdoor_top", texture); - } - - public T trapdoorOrientableOpen(String name, ResourceLocation texture) { - return singleTexture(name, BLOCK_FOLDER + "/template_orientable_trapdoor_open", texture); - } - - public T torch(String name, ResourceLocation torch) { - return singleTexture(name, BLOCK_FOLDER + "/template_torch", "torch", torch); - } - - public T torchWall(String name, ResourceLocation torch) { - return singleTexture(name, BLOCK_FOLDER + "/template_torch_wall", "torch", torch); - } - - public T carpet(String name, ResourceLocation wool) { - return singleTexture(name, BLOCK_FOLDER + "/carpet", "wool", wool); - } - - /** - * Gets a model builder that's not directly saved to disk. Meant for use in custom model loaders. - */ - public T nested() - { - return factory.apply(new ResourceLocation("dummy:dummy")); - } - - public ModelFile.ExistingModelFile getExistingFile(ResourceLocation path) { - ModelFile.ExistingModelFile ret = new ModelFile.ExistingModelFile(extendWithFolder(path), existingFileHelper); - ret.assertExistence(); - return ret; - } - - protected void clear() { - generatedModels.clear(); - } - - - @Override - public CompletableFuture run(CachedOutput cache) { - clear(); - registerModels(); - return generateAll(cache); - } - - protected CompletableFuture generateAll(CachedOutput cache) { - CompletableFuture[] futures = new CompletableFuture[this.generatedModels.size()]; - int i = 0; - - for (T model : this.generatedModels.values()) { - Path target = getPath(model); - futures[i++] = DataProvider.saveStable(cache, model.toJson(), target); - } - - return CompletableFuture.allOf(futures); - } - - protected Path getPath(T model) { - ResourceLocation loc = model.getLocation(); - return this.output.getOutputFolder(PackOutput.Target.RESOURCE_PACK).resolve(loc.getNamespace()).resolve("models").resolve(loc.getPath() + ".json"); - } -} diff --git a/src/portaforgy/java/net/minecraftforge/client/model/generators/MultiPartBlockStateBuilder.java b/src/portaforgy/java/net/minecraftforge/client/model/generators/MultiPartBlockStateBuilder.java deleted file mode 100644 index 5c9f958a595..00000000000 --- a/src/portaforgy/java/net/minecraftforge/client/model/generators/MultiPartBlockStateBuilder.java +++ /dev/null @@ -1,289 +0,0 @@ -/* - * Minecraft Forge - * Copyright (c) 2016-2021. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation version 2.1 - * of the License. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -package net.minecraftforge.client.model.generators; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Map.Entry; - -import com.google.common.base.Preconditions; -import com.google.common.collect.Multimap; -import com.google.common.collect.MultimapBuilder; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; - -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.state.properties.Property; - -public final class MultiPartBlockStateBuilder implements IGeneratedBlockstate { - - private final List parts = new ArrayList<>(); - private final Block owner; - - public MultiPartBlockStateBuilder(Block owner) { - this.owner = owner; - } - - /** - * Creates a builder for models to assign to a {@link PartBuilder}, which when - * completed via {@link ConfiguredModel.Builder#addModel()} will assign the - * resultant set of models to the part and return it for further processing. - * - * @return the model builder - * @see ConfiguredModel.Builder - */ - public ConfiguredModel.Builder part() { - return ConfiguredModel.builder(this); - } - - MultiPartBlockStateBuilder addPart(PartBuilder part) { - this.parts.add(part); - return this; - } - - @Override - public JsonObject toJson() { - JsonArray variants = new JsonArray(); - for (PartBuilder part : parts) { - variants.add(part.toJson()); - } - JsonObject main = new JsonObject(); - main.add("multipart", variants); - return main; - } - - public class PartBuilder { - public BlockStateProvider.ConfiguredModelList models; - public boolean useOr; - public final Multimap, Comparable> conditions = MultimapBuilder.linkedHashKeys().arrayListValues().build(); - public final List nestedConditionGroups = new ArrayList<>(); - - PartBuilder(BlockStateProvider.ConfiguredModelList models) { - this.models = models; - } - - /** - * Makes this part get applied if any of the conditions/condition groups are true, instead of all of them needing to be true. - */ - public PartBuilder useOr() { - this.useOr = true; - return this; - } - - /** - * Set a condition for this part, which consists of a property and a set of - * valid values. Can be called multiple times for multiple different properties. - * - * @param the type of the property value - * @param prop the property - * @param values a set of valid values - * @return this builder - * @throws NullPointerException if {@code prop} is {@code null} - * @throws NullPointerException if {@code values} is {@code null} - * @throws IllegalArgumentException if {@code values} is empty - * @throws IllegalArgumentException if {@code prop} has already been configured - * @throws IllegalArgumentException if {@code prop} is not applicable to the - * current block's state - * @throws IllegalStateException if {@code !nestedConditionGroups.isEmpty()} - */ - @SafeVarargs - public final > PartBuilder condition(Property prop, T... values) { - Preconditions.checkNotNull(prop, "Property must not be null"); - Preconditions.checkNotNull(values, "Value list must not be null"); - Preconditions.checkArgument(values.length > 0, "Value list must not be empty"); - Preconditions.checkArgument(!conditions.containsKey(prop), "Cannot set condition for property \"%s\" more than once", prop.getName()); - Preconditions.checkArgument(canApplyTo(owner), "IProperty %s is not valid for the block %s", prop, owner); - Preconditions.checkState(nestedConditionGroups.isEmpty(), "Can't have normal conditions if there are already nested condition groups"); - this.conditions.putAll(prop, Arrays.asList(values)); - return this; - } - - /** - * Allows having nested groups of conditions if there are not any normal conditions. - * @throws IllegalStateException if {@code !conditions.isEmpty()} - */ - public final ConditionGroup nestedGroup() - { - Preconditions.checkState(conditions.isEmpty(), "Can't have nested condition groups if there are already normal conditions"); - ConditionGroup group = new ConditionGroup(); - this.nestedConditionGroups.add(group); - return group; - } - - public MultiPartBlockStateBuilder end() { return MultiPartBlockStateBuilder.this; } - - JsonObject toJson() { - JsonObject out = new JsonObject(); - if (!conditions.isEmpty()) { - out.add("when", MultiPartBlockStateBuilder.toJson(this.conditions, this.useOr)); - } - else if (!nestedConditionGroups.isEmpty()) - { - out.add("when", MultiPartBlockStateBuilder.toJson(this.nestedConditionGroups, this.useOr)); - } - out.add("apply", models.toJSON()); - return out; - } - - public boolean canApplyTo(Block b) { - return b.getStateDefinition().getProperties().containsAll(conditions.keySet()); - } - - public class ConditionGroup - { - public final Multimap, Comparable> conditions = MultimapBuilder.linkedHashKeys().arrayListValues().build(); - public final List nestedConditionGroups = new ArrayList<>(); - private ConditionGroup parent = null; - public boolean useOr; - - /** - * Set a condition for this part, which consists of a property and a set of - * valid values. Can be called multiple times for multiple different properties. - * - * @param the type of the property value - * @param prop the property - * @param values a set of valid values - * @return this builder - * @throws NullPointerException if {@code prop} is {@code null} - * @throws NullPointerException if {@code values} is {@code null} - * @throws IllegalArgumentException if {@code values} is empty - * @throws IllegalArgumentException if {@code prop} has already been configured - * @throws IllegalArgumentException if {@code prop} is not applicable to the - * current block's state - * @throws IllegalStateException if {@code !nestedConditionGroups.isEmpty()} - */ - @SafeVarargs - public final > ConditionGroup condition(Property prop, T... values) - { - Preconditions.checkNotNull(prop, "Property must not be null"); - Preconditions.checkNotNull(values, "Value list must not be null"); - Preconditions.checkArgument(values.length > 0, "Value list must not be empty"); - Preconditions.checkArgument(!conditions.containsKey(prop), "Cannot set condition for property \"%s\" more than once", prop.getName()); - Preconditions.checkArgument(canApplyTo(owner), "IProperty %s is not valid for the block %s", prop, owner); - Preconditions.checkState(nestedConditionGroups.isEmpty(), "Can't have normal conditions if there are already nested condition groups"); - this.conditions.putAll(prop, Arrays.asList(values)); - return this; - } - - /** - * Allows having nested groups of conditions if there are not any normal conditions. - * @throws IllegalStateException if {@code !conditions.isEmpty()} - */ - public ConditionGroup nestedGroup() - { - Preconditions.checkState(conditions.isEmpty(), "Can't have nested condition groups if there are already normal conditions"); - ConditionGroup group = new ConditionGroup(); - group.parent = this; - this.nestedConditionGroups.add(group); - return group; - } - - /** - * Ends this nested condition group and returns the parent condition group - * - * @throws IllegalStateException If this is not a nested condition group - */ - public ConditionGroup endNestedGroup() - { - if (parent == null) - throw new IllegalStateException("This condition group is not nested, use end() instead"); - return parent; - } - - /** - * Ends this condition group and returns the part builder - * - * @throws IllegalStateException If this is a nested condition group - */ - public MultiPartBlockStateBuilder.PartBuilder end() - { - if (this.parent != null) - throw new IllegalStateException("This is a nested condition group, use endNestedGroup() instead"); - return MultiPartBlockStateBuilder.PartBuilder.this; - } - - /** - * Makes this part get applied if any of the conditions/condition groups are true, instead of all of them needing to be true. - */ - public ConditionGroup useOr() - { - this.useOr = true; - return this; - } - - JsonObject toJson() - { - if (!this.conditions.isEmpty()) - { - return MultiPartBlockStateBuilder.toJson(this.conditions, this.useOr); - } - else if (!this.nestedConditionGroups.isEmpty()) - { - return MultiPartBlockStateBuilder.toJson(this.nestedConditionGroups, this.useOr); - } - return new JsonObject(); - } - } - } - - private static JsonObject toJson(List conditions, boolean useOr) - { - JsonObject groupJson = new JsonObject(); - JsonArray innerGroupJson = new JsonArray(); - groupJson.add(useOr ? "OR" : "AND", innerGroupJson); - for (PartBuilder.ConditionGroup group : conditions) - { - innerGroupJson.add(group.toJson()); - } - return groupJson; - } - - private static JsonObject toJson(Multimap, Comparable> conditions, boolean useOr) - { - JsonObject groupJson = new JsonObject(); - for (Entry, Collection>> e : conditions.asMap().entrySet()) - { - StringBuilder activeString = new StringBuilder(); - for (Comparable val : e.getValue()) - { - if (activeString.length() > 0) - activeString.append("|"); - activeString.append(((Property) e.getKey()).getName(val)); - } - groupJson.addProperty(e.getKey().getName(), activeString.toString()); - } - if (useOr) - { - JsonArray innerWhen = new JsonArray(); - for (Entry entry : groupJson.entrySet()) - { - JsonObject obj = new JsonObject(); - obj.add(entry.getKey(), entry.getValue()); - innerWhen.add(obj); - } - groupJson = new JsonObject(); - groupJson.add("OR", innerWhen); - } - return groupJson; - } -} diff --git a/src/portaforgy/java/net/minecraftforge/client/model/generators/VariantBlockStateBuilder.java b/src/portaforgy/java/net/minecraftforge/client/model/generators/VariantBlockStateBuilder.java deleted file mode 100644 index 2c8d3bfdb79..00000000000 --- a/src/portaforgy/java/net/minecraftforge/client/model/generators/VariantBlockStateBuilder.java +++ /dev/null @@ -1,340 +0,0 @@ -/* - * Minecraft Forge - * Copyright (c) 2016-2021. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation version 2.1 - * of the License. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -package net.minecraftforge.client.model.generators; - -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.Set; -import java.util.SortedMap; -import java.util.SortedSet; -import java.util.TreeSet; -import java.util.function.Function; -import java.util.function.Predicate; - -import org.jetbrains.annotations.Nullable; - -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.gson.JsonObject; - -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.properties.Property; -import net.minecraftforge.client.model.generators.BlockStateProvider.ConfiguredModelList; - -/** - * Builder for variant-type blockstates, i.e. non-multipart blockstates. Should - * not be manually instantiated, instead use - * {@link BlockStateProvider#getVariantBuilder(Block)}. - *

- * Variants can either be set via - * {@link #setModels(PartialBlockstate, ConfiguredModel...)} or - * {@link #addModels(PartialBlockstate, ConfiguredModel...)}, where model(s) can - * be assigned directly to {@link PartialBlockstate partial states}, or builder - * style via {@link #partialState()} and its subsequent methods. - *

- * This class also provides the convenience methods - * {@link #forAllStates(Function)} and - * {@link #forAllStatesExcept(Function, Property...)} for cases where the model - * for each variant can be decided dynamically based on the state's property - * values. - * - * @see BlockStateProvider - */ -public class VariantBlockStateBuilder implements IGeneratedBlockstate { - - private final Block owner; - private final Map models = new LinkedHashMap<>(); - private final Set coveredStates = new HashSet<>(); - - VariantBlockStateBuilder(Block owner) { - this.owner = owner; - } - - public Map getModels() { - return models; - } - - public Block getOwner() { - return owner; - } - - @Override - public JsonObject toJson() { - List missingStates = Lists.newArrayList(owner.getStateDefinition().getPossibleStates()); - missingStates.removeAll(coveredStates); - Preconditions.checkState(missingStates.isEmpty(), "Blockstate for block %s does not cover all states. Missing: %s", owner, missingStates); - JsonObject variants = new JsonObject(); - getModels().entrySet().stream() - .sorted(Entry.comparingByKey(PartialBlockstate.comparingByProperties())) - .forEach(entry -> variants.add(entry.getKey().toString(), entry.getValue().toJSON())); - JsonObject main = new JsonObject(); - main.add("variants", variants); - return main; - } - - /** - * Assign some models to a given {@link PartialBlockstate partial state}. - * - * @param state The {@link PartialBlockstate partial state} for which to add - * the models - * @param models A set of models to add to this state - * @return this builder - * @throws NullPointerException if {@code state} is {@code null} - * @throws IllegalArgumentException if {@code models} is empty - * @throws IllegalArgumentException if {@code state}'s owning block differs from - * the builder's - * @throws IllegalArgumentException if {@code state} partially matches another - * state which has already been configured - */ - public VariantBlockStateBuilder addModels(PartialBlockstate state, ConfiguredModel... models) { - Preconditions.checkNotNull(state, "state must not be null"); - Preconditions.checkArgument(models.length > 0, "Cannot set models to empty array"); - Preconditions.checkArgument(state.getOwner() == owner, "Cannot set models for a different block. Found: %s, Current: %s", state.getOwner(), owner); - if (!this.models.containsKey(state)) { - Preconditions.checkArgument(disjointToAll(state), "Cannot set models for a state for which a partial match has already been configured"); - this.models.put(state, new ConfiguredModelList(models)); - for (BlockState fullState : owner.getStateDefinition().getPossibleStates()) { - if (state.test(fullState)) { - coveredStates.add(fullState); - } - } - } else { - this.models.compute(state, ($, cml) -> cml.append(models)); - } - return this; - } - - /** - * Assign some models to a given {@link PartialBlockstate partial state}, - * throwing an exception if the state has already been configured. Otherwise, - * simply calls {@link #addModels(PartialBlockstate, ConfiguredModel...)}. - * - * @param state The {@link PartialBlockstate partial state} for which to set - * the models - * @param models A set of models to assign to this state - * @return this builder - * @throws IllegalArgumentException if {@code state} has already been configured - * @see #addModels(PartialBlockstate, ConfiguredModel...) - */ - public VariantBlockStateBuilder setModels(PartialBlockstate state, ConfiguredModel... model) { - Preconditions.checkArgument(!models.containsKey(state), "Cannot set models for a state that has already been configured: %s", state); - addModels(state, model); - return this; - } - - private boolean disjointToAll(PartialBlockstate newState) { - return coveredStates.stream().noneMatch(newState); - } - - public PartialBlockstate partialState() { - return new PartialBlockstate(owner, this); - } - - public VariantBlockStateBuilder forAllStates(Function mapper) { - return forAllStatesExcept(mapper); - } - - public VariantBlockStateBuilder forAllStatesExcept(Function mapper, Property... ignored) { - Set seen = new HashSet<>(); - for (BlockState fullState : owner.getStateDefinition().getPossibleStates()) { - Map, Comparable> propertyValues = Maps.newLinkedHashMap(fullState.getValues()); - for (Property p : ignored) { - propertyValues.remove(p); - } - PartialBlockstate partialState = new PartialBlockstate(owner, propertyValues, this); - if (seen.add(partialState)) { - setModels(partialState, mapper.apply(fullState)); - } - } - return this; - } - - public static class PartialBlockstate implements Predicate { - private final Block owner; - private final SortedMap, Comparable> setStates; - @Nullable - private final VariantBlockStateBuilder outerBuilder; - - PartialBlockstate(Block owner, @Nullable VariantBlockStateBuilder outerBuilder) { - this(owner, ImmutableMap.of(), outerBuilder); - } - - PartialBlockstate(Block owner, Map, Comparable> setStates, @Nullable VariantBlockStateBuilder outerBuilder) { - this.owner = owner; - this.outerBuilder = outerBuilder; - for (Map.Entry, Comparable> entry : setStates.entrySet()) { - Property prop = entry.getKey(); - Comparable value = entry.getValue(); - Preconditions.checkArgument(owner.getStateDefinition().getProperties().contains(prop), "Property %s not found on block %s", entry, this.owner); - Preconditions.checkArgument(prop.getPossibleValues().contains(value), "%s is not a valid value for %s", value, prop); - } - this.setStates = Maps.newTreeMap(Comparator.comparing(Property::getName)); - this.setStates.putAll(setStates); - } - - public > PartialBlockstate with(Property prop, T value) { - Preconditions.checkArgument(!setStates.containsKey(prop), "Property %s has already been set", prop); - Map, Comparable> newState = new HashMap<>(setStates); - newState.put(prop, value); - return new PartialBlockstate(owner, newState, outerBuilder); - } - - private void checkValidOwner() { - Preconditions.checkNotNull(outerBuilder, "Partial blockstate must have a valid owner to perform this action"); - } - - /** - * Creates a builder for models to assign to this state, which when completed - * via {@link ConfiguredModel.Builder#addModel()} will assign the resultant set - * of models to this state. - * - * @return the model builder - * @see ConfiguredModel.Builder - */ - public ConfiguredModel.Builder modelForState() { - checkValidOwner(); - return ConfiguredModel.builder(outerBuilder, this); - } - - /** - * Add models to the current state's variant. For use when it is more convenient - * to add multiple sets of models, as a replacement for - * {@link #setModels(ConfiguredModel...)}. - * - * @param models The models to add. - * @return {@code this} - * @throws NullPointerException If the parent builder is {@code null} - */ - public PartialBlockstate addModels(ConfiguredModel... models) { - checkValidOwner(); - outerBuilder.addModels(this, models); - return this; - } - - /** - * Set this variant's models, and return the parent builder. - * - * @param models The models to set - * @return The parent builder instance - * @throws NullPointerException If the parent builder is {@code null} - */ - public VariantBlockStateBuilder setModels(ConfiguredModel... models) { - checkValidOwner(); - return outerBuilder.setModels(this, models); - } - - /** - * Complete this state without adding any new models, and return a new partial - * state via the parent builder. For use after calling - * {@link #addModels(ConfiguredModel...)}. - * - * @return A fresh partial state as specified by - * {@link VariantBlockStateBuilder#partialState()}. - * @throws NullPointerException If the parent builder is {@code null} - */ - public PartialBlockstate partialState() { - checkValidOwner(); - return outerBuilder.partialState(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - PartialBlockstate that = (PartialBlockstate) o; - return owner.equals(that.owner) && - setStates.equals(that.setStates); - } - - @Override - public int hashCode() { - return Objects.hash(owner, setStates); - } - - public Block getOwner() { - return owner; - } - - public SortedMap, Comparable> getSetStates() { - return setStates; - } - - @Override - public boolean test(BlockState blockState) { - if (blockState.getBlock() != getOwner()) { - return false; - } - for (Map.Entry, Comparable> entry : setStates.entrySet()) { - if (blockState.getValue(entry.getKey()) != entry.getValue()) { - return false; - } - } - return true; - } - - @Override - public String toString() { - StringBuilder ret = new StringBuilder(); - for (Map.Entry, Comparable> entry : setStates.entrySet()) { - if (ret.length() > 0) { - ret.append(','); - } - ret.append(entry.getKey().getName()) - .append('=') - .append(((Property) entry.getKey()).getName(entry.getValue())); - } - return ret.toString(); - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - public static Comparator comparingByProperties() { - // Sort variants inversely by property values, to approximate vanilla style - return (s1, s2) -> { - SortedSet> propUniverse = new TreeSet<>(s1.getSetStates().comparator().reversed()); - propUniverse.addAll(s1.getSetStates().keySet()); - propUniverse.addAll(s2.getSetStates().keySet()); - for (Property prop : propUniverse) { - Comparable val1 = s1.getSetStates().get(prop); - Comparable val2 = s2.getSetStates().get(prop); - if (val1 == null && val2 != null) { - return -1; - } else if (val2 == null && val1 != null) { - return 1; - } else if (val1 != null && val2 != null){ - int cmp = val1.compareTo(val2); - if (cmp != 0) { - return cmp; - } - } - } - return 0; - }; - } - } -} diff --git a/src/portaforgy/java/net/minecraftforge/common/data/ExistingFileHelper.java b/src/portaforgy/java/net/minecraftforge/common/data/ExistingFileHelper.java deleted file mode 100644 index 7b9b800a373..00000000000 --- a/src/portaforgy/java/net/minecraftforge/common/data/ExistingFileHelper.java +++ /dev/null @@ -1,240 +0,0 @@ -/* - * Minecraft Forge - * Copyright (c) 2016-2021. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation version 2.1 - * of the License. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -package net.minecraftforge.common.data; - -import com.google.common.collect.HashMultimap; -import com.google.common.collect.Multimap; -import net.minecraft.client.resources.ClientPackSource; -import net.minecraft.client.resources.IndexedAssetSource; -import net.minecraft.data.DataProvider; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.packs.BuiltInMetadata; -import net.minecraft.server.packs.FilePackResources; -import net.minecraft.server.packs.PackResources; -import net.minecraft.server.packs.PackType; -import net.minecraft.server.packs.PathPackResources; -import net.minecraft.server.packs.VanillaPackResources; -import net.minecraft.server.packs.VanillaPackResourcesBuilder; -import net.minecraft.server.packs.resources.MultiPackResourceManager; -import net.minecraft.server.packs.resources.ResourceManager; -import net.minecraftforge.client.model.generators.ModelBuilder; - -import java.io.File; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -/** - * Enables data providers to check if other data files currently exist. The - * instance provided in the {@link GatherDataEvent} utilizes the standard - * resources (via {@link VanillaPackResources}), forge's resources, as well as any - * extra resource packs passed in via the {@code --existing} argument, - * or mod resources via the {@code --existing-mod} argument. - */ -public class ExistingFileHelper { - - public interface IResourceType { - - PackType getPackType(); - - String getSuffix(); - - String getPrefix(); - } - - public static class ResourceType implements IResourceType { - - final PackType packType; - final String suffix, prefix; - - public ResourceType(PackType type, String suffix, String prefix) { - this.packType = type; - this.suffix = suffix; - this.prefix = prefix; - } - - @Override - public PackType getPackType() { - return packType; - } - - @Override - public String getSuffix() { - return suffix; - } - - @Override - public String getPrefix() { - return prefix; - } - } - - private final MultiPackResourceManager clientResources, serverData; - private final boolean enable; - private final Multimap generated = HashMultimap.create(); - - /** - * Create a new helper. This should probably NOT be used by mods, as - * the instance provided by forge is designed to be a central instance that - * tracks existence of generated data. - *

- * Only create a new helper if you intentionally want to ignore the existence of - * other generated files. - * - * @param existingPacks - * @param enable - */ - public ExistingFileHelper(Collection existingPacks, boolean enable) { - List candidateClientResources = new ArrayList<>(); - List candidateServerResources = new ArrayList<>(); - - var resources = new VanillaPackResourcesBuilder() - .setMetadata(BuiltInMetadata.of()) - .exposeNamespace("minecraft") - .pushJarResources() - .build(); - candidateClientResources.add(resources); - candidateServerResources.add(resources); - - for (Path existing : existingPacks) { - File file = existing.toFile(); - var packid = file.getName(); - PackResources pack = file.isDirectory() ? new PathPackResources(file.getName(), file.toPath(), false) : new FilePackResources(file.getName(), file, false); - candidateClientResources.add(pack); - candidateServerResources.add(pack); - } - - this.clientResources = new MultiPackResourceManager(PackType.CLIENT_RESOURCES, candidateClientResources); - this.serverData = new MultiPackResourceManager(PackType.SERVER_DATA, candidateServerResources); - - this.enable = enable; - } - - private ResourceManager getManager(PackType packType) { - return packType == PackType.CLIENT_RESOURCES ? clientResources : serverData; - } - - private ResourceLocation getLocation(ResourceLocation base, String suffix, String prefix) { - return new ResourceLocation(base.getNamespace(), prefix + "/" + base.getPath() + suffix); - } - - /** - * Check if a given resource exists in the known resource packs. - * - * @param loc the complete location of the resource, e.g. - * {@code "minecraft:textures/block/stone.png"} - * @param packType the type of resources to check - * @return {@code true} if the resource exists in any pack, {@code false} - * otherwise - */ - public boolean exists(ResourceLocation loc, PackType packType) { - if (!enable) { - return true; - } - return generated.get(packType).contains(loc) || getManager(packType).getResource(loc).isPresent(); - } - - /** - * Check if a given resource exists in the known resource packs. This is a - * convenience method to avoid repeating type/prefix/suffix and instead use the - * common definitions in {@link ResourceType}, or a custom {@link IResourceType} - * definition. - * - * @param loc the base location of the resource, e.g. - * {@code "minecraft:block/stone"} - * @param type a {@link IResourceType} describing how to form the path to the - * resource - * @return {@code true} if the resource exists in any pack, {@code false} - * otherwise - */ - public boolean exists(ResourceLocation loc, IResourceType type) { - return exists(getLocation(loc, type.getSuffix(), type.getPrefix()), type.getPackType()); - } - - /** - * Check if a given resource exists in the known resource packs. - * - * @param loc the base location of the resource, e.g. - * {@code "minecraft:block/stone"} - * @param packType the type of resources to check - * @param pathSuffix a string to append after the path, e.g. {@code ".json"} - * @param pathPrefix a string to append before the path, before a slash, e.g. - * {@code "models"} - * @return {@code true} if the resource exists in any pack, {@code false} - * otherwise - */ - public boolean exists(ResourceLocation loc, PackType packType, String pathSuffix, String pathPrefix) { - return exists(getLocation(loc, pathSuffix, pathPrefix), packType); - } - - /** - * Track the existence of a generated file. This is a convenience method to - * avoid repeating type/prefix/suffix and instead use the common definitions in - * {@link ResourceType}, or a custom {@link IResourceType} definition. - *

- * This should be called by data providers immediately when a new data object is - * created, i.e. not during - * {@link DataProvider#run(net.minecraft.data.CachedOutput) run} but instead - * when the "builder" (or whatever intermediate object) is created, such as a - * {@link ModelBuilder}. - *

- * This represents a promise to generate the file later, since other - * datagen may rely on this file existing. - * - * @param loc the base location of the resource, e.g. - * {@code "minecraft:block/stone"} - * @param type a {@link IResourceType} describing how to form the path to the - * resource - */ - public void trackGenerated(ResourceLocation loc, IResourceType type) { - this.generated.put(type.getPackType(), getLocation(loc, type.getSuffix(), type.getPrefix())); - } - - /** - * Track the existence of a generated file. - *

- * This should be called by data providers immediately when a new data object is - * created, i.e. not during - * {@link DataProvider#run(net.minecraft.data.CachedOutput) run} but instead - * when the "builder" (or whatever intermediate object) is created, such as a - * {@link ModelBuilder}. - *

- * This represents a promise to generate the file later, since other - * datagen may rely on this file existing. - * - * @param loc the base location of the resource, e.g. - * {@code "minecraft:block/stone"} - * @param packType the type of resources to check - * @param pathSuffix a string to append after the path, e.g. {@code ".json"} - * @param pathPrefix a string to append before the path, before a slash, e.g. - * {@code "models"} - */ - public void trackGenerated(ResourceLocation loc, PackType packType, String pathSuffix, String pathPrefix) { - this.generated.put(packType, getLocation(loc, pathSuffix, pathPrefix)); - } - - /** - * @return {@code true} if validation is enabled, {@code false} otherwise - */ - public boolean isEnabled() { - return enable; - } -} diff --git a/src/test/java/appeng/api/inventories/FabricInternalInventoryTest.java b/src/test/java/appeng/api/inventories/FabricInternalInventoryTest.java deleted file mode 100644 index 3043998d81b..00000000000 --- a/src/test/java/appeng/api/inventories/FabricInternalInventoryTest.java +++ /dev/null @@ -1,127 +0,0 @@ -package appeng.api.inventories; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.same; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; - -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; - -import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant; -import net.fabricmc.fabric.api.transfer.v1.storage.Storage; -import net.fabricmc.fabric.api.transfer.v1.storage.StorageUtil; -import net.fabricmc.fabric.api.transfer.v1.storage.base.ResourceAmount; -import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.Items; - -import appeng.core.definitions.AEItems; -import appeng.util.BootstrapMinecraft; -import appeng.util.inv.AppEngInternalInventory; -import appeng.util.inv.InternalInventoryHost; - -@BootstrapMinecraft -class FabricInternalInventoryTest { - InternalInventoryHost host = Mockito.mock(InternalInventoryHost.class); - - AppEngInternalInventory inv = new AppEngInternalInventory(host, 3); - - Storage storage = inv.toStorage(); - - ItemStack unstackable = AEItems.ITEM_CELL_64K.stack(); - ItemStack unstackableCopy = unstackable.copy(); - - ItemStack stackable = new ItemStack(Items.OAK_PLANKS, 64); - ItemStack stackableCopy = stackable.copy(); - - public FabricInternalInventoryTest() { - inv.setItemDirect(0, unstackable); - inv.setItemDirect(2, stackable); - Mockito.clearInvocations(host); - } - - @Test - void testAbortedTransactionWithoutChanges() { - try (var tx = Transaction.openOuter()) { - assertEquals( - new ResourceAmount<>(ItemVariant.of(unstackable), unstackable.getCount()), - StorageUtil.findExtractableContent(storage, tx)); - } - assertUnchanged(); - } - - @Test - void testComittedTransactionWithoutChanges() { - try (var tx = Transaction.openOuter()) { - assertEquals( - new ResourceAmount<>(ItemVariant.of(unstackable), unstackable.getCount()), - StorageUtil.findExtractableContent(storage, tx)); - tx.commit(); - } - assertUnchanged(); - } - - @Test - void testAbortedTransactionWithChangesToStackSize1() { - try (var tx = Transaction.openOuter()) { - assertEquals(1, storage.extract(ItemVariant.of(unstackable), 1, tx)); - } - assertUnchanged(); - } - - @Test - void testAbortedTransactionWithChangesToStackSize64() { - try (var tx = Transaction.openOuter()) { - assertEquals(1, storage.extract(ItemVariant.of(stackable), 1, tx)); - } - assertUnchanged(); - } - - @Test - void testComittedTransactionWithChangesToStackSize1() { - try (var tx = Transaction.openOuter()) { - assertEquals(1, storage.extract(ItemVariant.of(unstackable), 1, tx)); - tx.commit(); - } - - assertSame(ItemStack.EMPTY, inv.getStackInSlot(0)); - verify(host).onChangeInventory(inv, 0); - assertStackableIsUnchanged(); - } - - @Test - void testComittedTransactionWithChangesToStackSize64() { - try (var tx = Transaction.openOuter()) { - assertEquals(1, storage.extract(ItemVariant.of(stackable), 1, tx)); - tx.commit(); - } - - assertSame(stackable.getItem(), inv.getStackInSlot(2).getItem()); - assertEquals(63, inv.getStackInSlot(2).getCount()); - verify(host).onChangeInventory(inv, 2); - assertUnstackableIsUnchanged(); - } - - private void assertUnchanged() { - assertUnstackableIsUnchanged(); - assertSame(ItemStack.EMPTY, inv.getStackInSlot(1)); - assertStackableIsUnchanged(); - Mockito.verifyNoMoreInteractions(host); - } - - private void assertStackableIsUnchanged() { - assertSame(stackable, inv.getStackInSlot(2)); - assertTrue(ItemStack.matches(stackableCopy, stackable), "The item in the third slot was modified in-place"); - verify(host, never()).onChangeInventory(same(inv), eq(2)); - } - - private void assertUnstackableIsUnchanged() { - assertSame(unstackable, inv.getStackInSlot(0)); - assertTrue(ItemStack.matches(unstackableCopy, unstackable), "The item in the first slot was modified in-place"); - verify(host, never()).onChangeInventory(same(inv), eq(0)); - } -} diff --git a/src/test/java/appeng/blockentity/misc/CondenserMEStorageTest.java b/src/test/java/appeng/blockentity/misc/CondenserMEStorageTest.java index 044b6c22121..a373f59da1e 100644 --- a/src/test/java/appeng/blockentity/misc/CondenserMEStorageTest.java +++ b/src/test/java/appeng/blockentity/misc/CondenserMEStorageTest.java @@ -6,11 +6,10 @@ import org.junit.jupiter.api.Test; -import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant; -import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant; -import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction; import net.minecraft.core.BlockPos; import net.minecraft.world.level.material.Fluids; +import net.neoforged.neoforge.fluids.FluidStack; +import net.neoforged.neoforge.fluids.capability.IFluidHandler; import appeng.api.config.Actionable; import appeng.api.config.CondenserOutput; @@ -36,6 +35,8 @@ void testSingularityProductionAndPriority() { assertThat(inv).isInstanceOf(CondenserMEStorage.class); assertThat(inv.getAvailableStacks()).isEmpty(); + CondenserOutput.SINGULARITY.requiredPower = 256000; + be.getConfigManager().putSetting(Settings.CONDENSER_OUTPUT, CondenserOutput.SINGULARITY); be.getInternalInventory().setItemDirect(2, AEItems.CELL_COMPONENT_64K.stack()); be.addPower(99999999999.0); @@ -65,18 +66,12 @@ void testEnergyPerUnit() { assertThat(be.getStoredPower()).isEqualTo(8 + 1); // test Fluid insert via transfer API - try (Transaction transaction = Transaction.openOuter()) { - be.getFluidHandler().insert(FluidVariant.of(Fluids.WATER.getSource()), AEFluidKey.AMOUNT_BUCKET, - transaction); - transaction.commit(); - } + be.getFluidHandler().fill(new FluidStack(Fluids.WATER.getSource(), AEFluidKey.AMOUNT_BUCKET), + IFluidHandler.FluidAction.EXECUTE); assertThat(be.getStoredPower()).isEqualTo(8 + 1 + 8); // test item insert via transfer API - try (Transaction transaction = Transaction.openOuter()) { - be.getExternalInv().toStorage().insert(ItemVariant.of(AEItems.MATTER_BALL.stack()), 1, transaction); - transaction.commit(); - } + be.getExternalInv().insertItem(0, AEItems.MATTER_BALL.stack(), false); assertThat(be.getStoredPower()).isEqualTo(8 + 1 + 8 + 1); } } diff --git a/src/test/java/appeng/client/gui/MockResourceManager.java b/src/test/java/appeng/client/gui/MockResourceManager.java index b68902c5dc8..d9141a26db3 100644 --- a/src/test/java/appeng/client/gui/MockResourceManager.java +++ b/src/test/java/appeng/client/gui/MockResourceManager.java @@ -42,14 +42,14 @@ private MockResourceManager() { public static ReloadableResourceManager create() { - var testResourceBasePath = AppEng.class.getResource("/ae2.mixins.json"); + var testResourceBasePath = AppEng.class.getResource("/META-INF/mods.toml"); if (testResourceBasePath == null) { throw new IllegalStateException("Couldn't find root of assets"); } Path assetRootPath; try { - assetRootPath = Paths.get(testResourceBasePath.toURI()).getParent(); + assetRootPath = Paths.get(testResourceBasePath.toURI()).getParent().getParent(); } catch (Exception e) { throw new IllegalStateException( "Failed to convert asset root to a path on disk. (" + testResourceBasePath + ")"); diff --git a/src/test/java/appeng/client/gui/TooltipTest.java b/src/test/java/appeng/client/gui/TooltipTest.java index 339e95523dc..714ce0f95dd 100644 --- a/src/test/java/appeng/client/gui/TooltipTest.java +++ b/src/test/java/appeng/client/gui/TooltipTest.java @@ -40,7 +40,7 @@ void testLineSplitting() { Component.literal("Fourth Line")); assertThat(tooltip.getContent()) - .extracting(net.minecraft.network.chat.Component::getString) + .extracting(Component::getString) .containsExactly( "BOLDMore Text", "Second LineContinued Second Line", @@ -54,7 +54,7 @@ void testSplitAtNewlineInTranslationText() { Component.translatable("gui.tooltips.ae2.MatterBalls", 256)); assertThat(tooltip.getContent()) - .extracting(net.minecraft.network.chat.Component::getString) + .extracting(Component::getString) .containsExactly( "Condense Into Matter Balls", "256 per item"); @@ -67,7 +67,7 @@ void testNoLineSplitting() { Component.translatable("b")); assertThat(tooltip.getContent()) - .extracting(net.minecraft.network.chat.Component::getString) + .extracting(Component::getString) .containsExactly( "a", "b"); } diff --git a/src/test/java/appeng/client/render/cablebus/BakedQuadBuilder.java b/src/test/java/appeng/client/render/cablebus/BakedQuadBuilder.java deleted file mode 100644 index 6a5ca12be4d..00000000000 --- a/src/test/java/appeng/client/render/cablebus/BakedQuadBuilder.java +++ /dev/null @@ -1,46 +0,0 @@ -package appeng.client.render.cablebus; - -import java.util.List; - -import org.jetbrains.annotations.Nullable; - -import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter; -import net.fabricmc.fabric.api.renderer.v1.model.SpriteFinder; -import net.fabricmc.fabric.impl.client.indigo.renderer.IndigoRenderer; -import net.fabricmc.fabric.impl.client.indigo.renderer.mesh.EncodingFormat; -import net.fabricmc.fabric.impl.client.indigo.renderer.mesh.MutableQuadViewImpl; -import net.minecraft.client.Minecraft; -import net.minecraft.client.renderer.block.model.BakedQuad; -import net.minecraft.client.renderer.texture.TextureAtlas; -import net.minecraft.client.resources.model.ModelManager; - -class BakedQuadBuilder extends MutableQuadViewImpl implements QuadEmitter { - private final List output; - - private final SpriteFinder finder; - - public BakedQuadBuilder(List output) { - this(output, Minecraft.getInstance() != null ? Minecraft.getInstance().getModelManager() : null); - } - - public BakedQuadBuilder(List output, @Nullable ModelManager modelManager) { - this.output = output; - - data = new int[EncodingFormat.TOTAL_STRIDE]; - material(IndigoRenderer.MATERIAL_STANDARD); - - if (modelManager != null) { - this.finder = SpriteFinder.get(modelManager.getAtlas(TextureAtlas.LOCATION_BLOCKS)); - } else { - this.finder = null; - } - } - - @Override - public QuadEmitter emit() { - output.add(toBakedQuad(finder != null ? finder.find(this) : null)); - - clear(); - return this; - } -} diff --git a/src/test/java/appeng/client/render/cablebus/CubeBuilderTest.java b/src/test/java/appeng/client/render/cablebus/CubeBuilderTest.java index 392c90abc60..7ca6ac2e1ab 100644 --- a/src/test/java/appeng/client/render/cablebus/CubeBuilderTest.java +++ b/src/test/java/appeng/client/render/cablebus/CubeBuilderTest.java @@ -7,10 +7,12 @@ import java.util.ArrayList; import java.util.Arrays; +import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.DefaultVertexFormat; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; +import org.mockito.Mockito; import net.minecraft.client.renderer.block.model.BakedQuad; import net.minecraft.client.renderer.block.model.FaceBakery; @@ -33,22 +35,23 @@ class CubeBuilderTest { @ParameterizedTest @EnumSource(Direction.class) void testFaceVertexOrdering(Direction side) { - var output = new ArrayList(); - var emitter = new BakedQuadBuilder(output); - var cb = new CubeBuilder(emitter); - cb.setTexture(mock(TextureAtlasSprite.class)); - cb.addQuad(side, 0, 0, 0, 1, 1, 1); + try (var mockedStatic = Mockito.mockStatic(RenderSystem.class)) { + var output = new ArrayList(); + var cb = new CubeBuilder(output); + cb.setTexture(mock(TextureAtlasSprite.class)); + cb.addQuad(side, 0, 0, 0, 1, 1, 1); - assertEquals(1, output.size()); - var quad = output.get(0); - assertEquals(side, quad.getDirection()); + assertEquals(1, output.size()); + var quad = output.get(0); + assertEquals(side, quad.getDirection()); - var fb = new FaceBakery(); - var rewinded = quad.getVertices().clone(); - fb.recalculateWinding(rewinded, side); + var fb = new FaceBakery(); + var rewinded = quad.getVertices().clone(); + fb.recalculateWinding(rewinded, side); - assertThat(getVertexOrder(rewinded, quad.getVertices())) - .containsExactly(0, 1, 2, 3); + assertThat(getVertexOrder(rewinded, quad.getVertices())) + .containsExactly(0, 1, 2, 3); + } } // Get the order of vertices compared to the original array diff --git a/src/test/java/appeng/crafting/pattern/CraftingPatternItemTest.java b/src/test/java/appeng/crafting/pattern/CraftingPatternItemTest.java index 4e5da309844..8daf153b1f9 100644 --- a/src/test/java/appeng/crafting/pattern/CraftingPatternItemTest.java +++ b/src/test/java/appeng/crafting/pattern/CraftingPatternItemTest.java @@ -28,6 +28,8 @@ import net.minecraft.world.item.crafting.RecipeType; import net.minecraft.world.item.crafting.ShapedRecipe; import net.minecraft.world.level.Level; +import net.neoforged.neoforge.common.crafting.CraftingHelper; +import net.neoforged.neoforge.common.crafting.VanillaIngredientSerializer; import appeng.api.crafting.PatternDetailsHelper; import appeng.api.stacks.AEItemKey; @@ -38,6 +40,10 @@ @BootstrapMinecraft class CraftingPatternItemTest { + static { + CraftingHelper.register(new ResourceLocation("minecraft", "item"), VanillaIngredientSerializer.INSTANCE); + } + private static final ResourceLocation TEST_RECIPE_ID = AppEng.makeId("test_recipe"); private final ShapedRecipe TEST_RECIPE = buildRecipe(ShapedRecipeBuilder.shaped(RecipeCategory.MISC, Items.STICK) diff --git a/src/test/java/appeng/init/client/InitScreensTest.java b/src/test/java/appeng/init/client/InitScreensTest.java index 83a0936bb97..3b5140930fa 100644 --- a/src/test/java/appeng/init/client/InitScreensTest.java +++ b/src/test/java/appeng/init/client/InitScreensTest.java @@ -21,6 +21,7 @@ import static org.assertj.core.api.Assertions.assertThat; import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -100,6 +101,12 @@ private static String getExceptionChain(Throwable e) { */ @Test void testMissingTranslationKeys() throws IOException { + // Load AE2 translation data + Map i18n = new HashMap<>(Language.getInstance().getLanguageData()); + try (InputStream in = getClass().getResourceAsStream("/assets/ae2/lang/en_us.json")) { + Language.loadFromJson(in, i18n::put); + } + StyleManager.initialize(MockResourceManager.create()); Map errors = new HashMap<>(); diff --git a/src/test/java/appeng/me/cells/BasicInventoryTest.java b/src/test/java/appeng/me/cells/BasicInventoryTest.java index 50b49bff33d..08ad3622a8d 100644 --- a/src/test/java/appeng/me/cells/BasicInventoryTest.java +++ b/src/test/java/appeng/me/cells/BasicInventoryTest.java @@ -4,12 +4,14 @@ import java.util.Objects; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import net.minecraft.nbt.CompoundTag; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.level.material.Fluids; +import net.neoforged.neoforge.registries.ForgeRegistries; import appeng.api.config.Actionable; import appeng.api.networking.security.IActionSource; @@ -19,6 +21,9 @@ import appeng.api.storage.StorageCells; import appeng.api.storage.cells.CellState; import appeng.core.definitions.AEItems; +import appeng.init.InitItems; +import appeng.init.internal.InitStorageCells; +import appeng.init.internal.InitUpgrades; import appeng.me.helpers.BaseActionSource; import appeng.util.BootstrapMinecraft; @@ -26,6 +31,13 @@ public class BasicInventoryTest { private static final IActionSource SRC = new BaseActionSource(); + @BeforeAll + static void initCells() { + InitItems.init(ForgeRegistries.ITEMS); + InitStorageCells.init(); + InitUpgrades.init(); + } + /** * Check that we can extract more than MAX_INT fluid at once from a cell. Regression test for * https://github.com/AppliedEnergistics/Applied-Energistics-2/issues/6794 diff --git a/src/test/java/appeng/util/BootstrapMinecraftExtension.java b/src/test/java/appeng/util/BootstrapMinecraftExtension.java index f527d6292f9..f2ce91ecb50 100644 --- a/src/test/java/appeng/util/BootstrapMinecraftExtension.java +++ b/src/test/java/appeng/util/BootstrapMinecraftExtension.java @@ -1,74 +1,90 @@ package appeng.util; +import static org.mockito.ArgumentMatchers.any; + import java.nio.file.Files; -import java.nio.file.Path; -import java.util.function.Consumer; import com.google.common.io.MoreFiles; import com.google.common.io.RecursiveDeleteOption; +import com.google.common.reflect.Reflection; -import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.platform.commons.util.ReflectionUtils; +import org.mockito.Mockito; -import net.fabricmc.api.DedicatedServerModInitializer; -import net.fabricmc.api.ModInitializer; -import net.fabricmc.loader.api.entrypoint.PreLaunchEntrypoint; -import net.fabricmc.loader.impl.FabricLoaderImpl; import net.minecraft.SharedConstants; import net.minecraft.server.Bootstrap; - -import appeng.client.guidebook.Guide; +import net.neoforged.neoforge.common.CommonHooks; +import net.neoforged.neoforge.fluids.FluidType; +import net.neoforged.neoforge.registries.ForgeRegistries; +import net.neoforged.neoforge.registries.NewRegistryEvent; +import net.neoforged.neoforge.registries.RegistryBuilder; + +import appeng.api.stacks.AEKeyType; +import appeng.api.stacks.AEKeyTypes; +import appeng.api.stacks.AEKeyTypesInternal; import appeng.core.AEConfig; +import appeng.core.AppEng; import appeng.core.AppEngBootstrap; +import appeng.core.definitions.AEBlockEntities; +import appeng.core.definitions.AEBlocks; +import appeng.core.definitions.AEItems; +import appeng.init.InitBlocks; +import appeng.init.InitItems; -public class BootstrapMinecraftExtension implements Extension, BeforeAllCallback, AfterAllCallback { +public class BootstrapMinecraftExtension implements Extension, BeforeAllCallback { - private static boolean modInitialized; - - Path configDir; + private static boolean keyTypesInitialized; @Override public void beforeAll(ExtensionContext context) throws Exception { SharedConstants.tryDetectVersion(); Bootstrap.bootStrap(); AppEngBootstrap.runEarlyStartup(); - - configDir = Files.createTempDirectory("ae2config"); - if (AEConfig.instance() == null) { - AEConfig.load(configDir); + Reflection.initialize(AEItems.class); + Reflection.initialize(AEBlocks.class); + Reflection.initialize(AEBlockEntities.class); + + var configDir = Files.createTempDirectory("ae2config"); + try { + if (AEConfig.instance() == null) { + AEConfig.load(configDir); + } + } finally { + MoreFiles.deleteRecursively(configDir, RecursiveDeleteOption.ALLOW_INSECURE); } - if (!modInitialized) { - modInitialized = true; - - invokeEntrypoints("preLaunch", PreLaunchEntrypoint.class, PreLaunchEntrypoint::onPreLaunch); - invokeEntrypoints("main", ModInitializer.class, ModInitializer::onInitialize); - invokeEntrypoints("server", DedicatedServerModInitializer.class, - DedicatedServerModInitializer::onInitializeServer); + if (!keyTypesInitialized) { + try { + InitBlocks.init(ForgeRegistries.BLOCKS); + InitItems.init(ForgeRegistries.ITEMS); + } catch (Throwable e) { + throw new RuntimeException(e); + } - Guide.runDatapackReload(); + mockForgeFluidTypes(); + + var e = new NewRegistryEvent(); + var supplier = e.create(new RegistryBuilder() + .setMaxID(127) + .setName(AppEng.makeId("keytypes"))); + ReflectionUtils.invokeMethod(NewRegistryEvent.class.getDeclaredMethod("fill"), e); + AEKeyTypesInternal.setRegistry(supplier); + AEKeyTypes.register(AEKeyType.items()); + AEKeyTypes.register(AEKeyType.fluids()); + keyTypesInitialized = true; } - } - - private void invokeEntrypoints(String name, Class type, Consumer invoker) { - var entrypoints = FabricLoaderImpl.INSTANCE.getEntrypointContainers(name, type); - for (var container : entrypoints) { - var modId = container.getProvider().getMetadata().getId(); - // Fix WTHIT API runtime protection messing our tests up - if (modId.equals("wthit_api")) { - continue; - } - invoker.accept(container.getEntrypoint()); - } } - @Override - public void afterAll(ExtensionContext context) throws Exception { - if (configDir != null && Files.exists(configDir)) { - MoreFiles.deleteRecursively(configDir, RecursiveDeleteOption.ALLOW_INSECURE); - } + private void mockForgeFluidTypes() { + // Otherwise constructing ANY FluidKey will crash + var mocked = Mockito.mockStatic(CommonHooks.class, Mockito.CALLS_REAL_METHODS); + var props = FluidType.Properties.create(); + props.descriptionId("fluid"); + mocked.when(() -> ForgeHooks.getVanillaFluidType(any())) + .thenReturn(new FluidType(props)); } }