diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6815656ec7..b8b754fa71 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,13 +4,13 @@ jobs: build: strategy: matrix: - java: [17-jdk, 20-jdk] + java: [17-ubuntu, 21-ubuntu] runs-on: ubuntu-22.04 container: - image: eclipse-temurin:${{ matrix.java }} + image: mcr.microsoft.com/openjdk/jdk:${{ matrix.java }} options: --user root steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: gradle/wrapper-validation-action@v1 @@ -28,15 +28,19 @@ jobs: with: name: Artifacts path: ./*/build/libs/ + - uses: actions/upload-artifact@v3 + with: + name: Artifacts + path: build/publishMods/ - uses: actions/upload-artifact@v3 with: name: Maven Local - path: /root/.m2/repository + path: /root/.m2/repository/net/fabricmc/ client_test: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: actions/setup-java@v3 @@ -56,7 +60,7 @@ jobs: server_test: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: actions/setup-java@v3 @@ -69,7 +73,7 @@ jobs: check_resources: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: actions/setup-java@v3 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2f8d9cc2e4..2b660a130b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,12 +8,12 @@ jobs: build: runs-on: ubuntu-22.04 container: - image: eclipse-temurin:20-jdk + image: mcr.microsoft.com/openjdk/jdk:21-ubuntu options: --user root steps: - run: apt update && apt install git -y && git --version - run: git config --global --add safe.directory /__w/fabric/fabric - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: FabricMC/fabric-action-scripts@v2 @@ -22,7 +22,7 @@ jobs: context: changelog workflow_id: release.yml - uses: gradle/wrapper-validation-action@v1 - - run: ./gradlew checkVersion build publish curseforge github modrinth --stacktrace -Porg.gradle.parallel.threads=4 + - run: ./gradlew checkVersion build publish publishMods --stacktrace -Porg.gradle.parallel.threads=4 env: MAVEN_URL: ${{ secrets.MAVEN_URL }} MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} diff --git a/README.md b/README.md index dba0995f55..74bc75391a 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ For support and discussion for both developers and users, visit [the Fabric Disc ## Using Fabric API to play with mods -Make sure you have install fabric loader first. More information about installing Fabric Loader can be found [here](https://fabricmc.net/use/). +Make sure you have installed fabric loader first. More information about installing Fabric Loader can be found [here](https://fabricmc.net/use/). To use Fabric API, download it from [CurseForge](https://www.curseforge.com/minecraft/mc-mods/fabric-api), [GitHub Releases](https://github.com/FabricMC/fabric/releases) or [Modrinth](https://modrinth.com/mod/fabric-api). @@ -23,7 +23,7 @@ The downloaded jar file should be placed in your `mods` folder. ## Using Fabric API to develop mods -To setup a Fabric development environment, check out the [Fabric example mod](https://github.com/FabricMC/fabric-example-mod) and follow the instructions there. The example mod already depends on Fabric API. +To set up a Fabric development environment, check out the [Fabric example mod](https://github.com/FabricMC/fabric-example-mod) and follow the instructions there. The example mod already depends on Fabric API. To include the full Fabric API with all modules in the development environment, add the following to your `dependencies` block in the gradle buildscript: diff --git a/build.gradle b/build.gradle index ca0e80d7ab..73001c5ceb 100644 --- a/build.gradle +++ b/build.gradle @@ -1,25 +1,17 @@ -buildscript { - dependencies { - classpath 'org.kohsuke:github-api:1.135' - } -} - plugins { id "java-library" id "eclipse" id "idea" id "maven-publish" id 'jacoco' - id "fabric-loom" version "1.2.7" apply false - id "com.diffplug.spotless" version "6.18.0" + id "fabric-loom" version "1.4.1" apply false + id "com.diffplug.spotless" version "6.20.0" id "org.ajoberstar.grgit" version "3.1.0" - id "com.matthewprenger.cursegradle" version "1.4.0" - id "com.modrinth.minotaur" version "2.4.3" id "me.modmuss50.remotesign" version "0.4.0" apply false + id "me.modmuss50.mod-publish-plugin" version "0.3.4" } def ENV = System.getenv() -def signingEnabled = ENV.SIGNING_SERVER version = project.version + "+" + (ENV.GITHUB_RUN_NUMBER ? "" : "local-") + getBranch() logger.lifecycle("Building Fabric: " + version) @@ -123,16 +115,16 @@ allprojects { enabled = false } - if (signingEnabled) { - remoteSign { - requestUrl = ENV.SIGNING_SERVER - pgpAuthKey = ENV.SIGNING_PGP_KEY - jarAuthKey = ENV.SIGNING_JAR_KEY + remoteSign { + requestUrl = ENV.SIGNING_SERVER + pgpAuthKey = ENV.SIGNING_PGP_KEY + jarAuthKey = ENV.SIGNING_JAR_KEY - afterEvaluate { - // PGP sign all maven publications. - sign publishing.publications.mavenJava - } + useDummyForTesting = ENV.SIGNING_SERVER == null + + afterEvaluate { + // PGP sign all maven publications. + sign publishing.publications.mavenJava } } @@ -203,6 +195,10 @@ allprojects { } } + loom.runs.configureEach { + vmArg("-enableassertions") + } + allprojects.each { p -> if (project.name == "deprecated") { return @@ -231,6 +227,7 @@ allprojects { testImplementation "net.fabricmc:fabric-loader-junit:${project.loader_version}" testImplementation sourceSets.testmodClient.output + testImplementation 'org.mockito:mockito-core:5.4.0' } test { @@ -266,7 +263,7 @@ allprojects { checkstyle { configFile = rootProject.file("checkstyle.xml") - toolVersion = "10.11.0" + toolVersion = "10.12.1" } tasks.withType(AbstractArchiveTask).configureEach { @@ -274,10 +271,8 @@ allprojects { reproducibleFileOrder = true } - if (signingEnabled) { - remoteSign { - sign remapJar - } + remoteSign { + sign remapJar } // Run this task after updating minecraft to regenerate any required resources @@ -411,7 +406,7 @@ loom { } autoTestServer { inherit testmodServer - name "Auto Test Server" + name "Auto Test Server" vmArg "-Dfabric.autoTest" } autoTestClient { @@ -510,7 +505,8 @@ tasks.register('runProductionAutoTestClient', JavaExec) { jvmArgs( "-Dfabric.addMods=${remapJar.archiveFile.get().asFile.absolutePath}${File.pathSeparator}${remapTestmodJar.archiveFile.get().asFile.absolutePath}", - "-Dfabric.autoTest" + "-Dfabric.autoTest", + "-enableassertions" ) } } @@ -541,7 +537,8 @@ tasks.register('runProductionAutoTestServer', JavaExec) { jvmArgs( "-Dfabric.addMods=${remapJar.archiveFile.get().asFile.absolutePath}${File.pathSeparator}${remapTestmodJar.archiveFile.get().asFile.absolutePath}", - "-Dfabric.autoTest" + "-Dfabric.autoTest", + "-enableassertions" ) args("nogui") @@ -595,6 +592,10 @@ subprojects { return } + base { + archivesName = project.name + } + dependencies { testmodImplementation sourceSets.main.output @@ -619,8 +620,8 @@ subprojects { pom { addPomMetadataInformation(project, pom) } - artifact(signingEnabled ? signRemapJar.output : remapJar) { - builtBy(signingEnabled ? signRemapJar : remapJar) + artifact(signRemapJar.output) { + builtBy(signRemapJar) } artifact(remapSourcesJar) { @@ -639,8 +640,8 @@ subprojects { publishing { publications { mavenJava(MavenPublication) { - artifact(signingEnabled ? signRemapJar.output : remapJar) { - builtBy(signingEnabled ? signRemapJar : remapJar) + artifact(signRemapJar.output) { + builtBy(signRemapJar) } artifact(sourcesJar) { @@ -657,7 +658,7 @@ publishing { pom.withXml { def depsNode = asNode().appendNode("dependencies") subprojects.each { - // Dont depend on the deprecated modules in the main artifact. + // The maven BOM containing all of the deprecated modules is added manually below. if (it.path.startsWith(":deprecated")) { return } @@ -668,6 +669,13 @@ publishing { depNode.appendNode("version", it.version) depNode.appendNode("scope", "compile") } + + // Depend on the deprecated BOM to allow opting out of deprecated modules. + def depNode = depsNode.appendNode("dependency") + depNode.appendNode("groupId", group) + depNode.appendNode("artifactId", "fabric-api-deprecated") + depNode.appendNode("version", version) + depNode.appendNode("scope", "compile") } } } @@ -727,77 +735,39 @@ remapJar { } // Include the signed or none signed jar from the sub project. - nestedJars.from project("${it.path}").tasks.getByName(signingEnabled ? "signRemapJar" : "remapJar") + nestedJars.from project("${it.path}").tasks.getByName("signRemapJar") } } } -curseforge { - if (ENV.CURSEFORGE_API_KEY) { - apiKey = ENV.CURSEFORGE_API_KEY - } - - project { - id = "306612" - changelog = ENV.CHANGELOG ?: "No changelog provided" - releaseType = project.prerelease == "true" ? "beta" : "release" - addGameVersion "1.20.1" - addGameVersion "Fabric" - - mainArtifact(signingEnabled ? signRemapJar.output : remapJar) { - displayName = "[$project.minecraft_version] Fabric API $project.version" - } - - afterEvaluate { - uploadTask.dependsOn("remapJar") - } - } +publishMods { + file = signRemapJar.output + changelog = providers.environmentVariable("CHANGELOG").getOrElse("No changelog provided") + type = project.prerelease == "true" ? BETA : STABLE + displayName = "[${project.minecraft_version}] Fabric API $project.version" + modLoaders.add("fabric") + dryRun = providers.environmentVariable("CURSEFORGE_API_KEY").getOrNull() == null - options { - forgeGradleIntegration = false + curseforge { + accessToken = providers.environmentVariable("CURSEFORGE_API_KEY") + projectId = "306612" + minecraftVersions.add(project.curseforge_minecraft_version) } -} - -if (signingEnabled) { - project.tasks.curseforge.dependsOn signRemapJar - project.tasks.modrinth.dependsOn signRemapJar - build.dependsOn signRemapJar -} - -import org.kohsuke.github.GHReleaseBuilder -import org.kohsuke.github.GitHub - -import java.util.stream.Collectors - -tasks.register('github') { - dependsOn(signingEnabled ? signRemapJar : remapJar) - onlyIf { - ENV.GITHUB_TOKEN + modrinth { + accessToken = providers.environmentVariable("MODRINTH_TOKEN") + projectId = "P7dR8mSH" + minecraftVersions.add(project.minecraft_version) } - - doLast { - def github = GitHub.connectUsingOAuth(ENV.GITHUB_TOKEN as String) - def repository = github.getRepository(ENV.GITHUB_REPOSITORY) - - def releaseBuilder = new GHReleaseBuilder(repository, version as String) - releaseBuilder.name("[$project.minecraft_version] Fabric API $project.version") - releaseBuilder.body(ENV.CHANGELOG ?: "No changelog provided") - releaseBuilder.commitish(getBranch()) - releaseBuilder.prerelease(project.prerelease == "true") - - def ghRelease = releaseBuilder.create() - ghRelease.uploadAsset(signingEnabled ? signRemapJar.output.get().getAsFile() : remapJar.archiveFile.get().getAsFile(), "application/java-archive"); + github { + accessToken = providers.environmentVariable("GITHUB_TOKEN") + repository = providers.environmentVariable("GITHUB_REPOSITORY").getOrElse("FabricMC/dryrun") + commitish = providers.environmentVariable("GITHUB_REF_NAME").getOrElse("dryrun") } } -modrinth { - projectId = "fabric-api" - versionName = "[$project.minecraft_version] Fabric API $project.version" - versionType = project.prerelease == "true" ? "beta" : "release" - changelog = ENV.CHANGELOG ?: "No changelog provided" +assemble.dependsOn signRemapJar - uploadFile = signingEnabled ? signRemapJar.output : remapJar -} +import java.util.stream.Collectors // A task to ensure that the version being released has not already been released. tasks.register('checkVersion') { @@ -811,7 +781,5 @@ tasks.register('checkVersion') { } } -github.mustRunAfter checkVersion -project.tasks.modrinth.mustRunAfter checkVersion +tasks.publishMods.dependsOn checkVersion publish.mustRunAfter checkVersion -project.tasks.curseforge.mustRunAfter checkVersion diff --git a/deprecated/fabric-command-api-v1/build.gradle b/deprecated/fabric-command-api-v1/build.gradle index 6afd12fe2c..1d9fadecdd 100644 --- a/deprecated/fabric-command-api-v1/build.gradle +++ b/deprecated/fabric-command-api-v1/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-command-api-v1" version = getSubprojectVersion(project) moduleDependencies(project, [ diff --git a/deprecated/fabric-commands-v0/build.gradle b/deprecated/fabric-commands-v0/build.gradle index 1decd8dce4..402be7a599 100644 --- a/deprecated/fabric-commands-v0/build.gradle +++ b/deprecated/fabric-commands-v0/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-commands-v0" version = getSubprojectVersion(project) moduleDependencies(project, [ diff --git a/deprecated/fabric-containers-v0/build.gradle b/deprecated/fabric-containers-v0/build.gradle index 3a87ac952d..70b5d0059b 100644 --- a/deprecated/fabric-containers-v0/build.gradle +++ b/deprecated/fabric-containers-v0/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-containers-v0" version = getSubprojectVersion(project) moduleDependencies(project, [ diff --git a/deprecated/fabric-containers-v0/src/main/java/net/fabricmc/fabric/impl/container/ContainerProviderImpl.java b/deprecated/fabric-containers-v0/src/main/java/net/fabricmc/fabric/impl/container/ContainerProviderImpl.java index c297ec9c04..6be92112e3 100644 --- a/deprecated/fabric-containers-v0/src/main/java/net/fabricmc/fabric/impl/container/ContainerProviderImpl.java +++ b/deprecated/fabric-containers-v0/src/main/java/net/fabricmc/fabric/impl/container/ContainerProviderImpl.java @@ -21,18 +21,18 @@ import java.util.function.Consumer; import io.netty.buffer.Unpooled; -import org.slf4j.LoggerFactory; import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import net.minecraft.network.packet.s2c.play.CustomPayloadS2CPacket; -import net.minecraft.screen.ScreenHandler; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.screen.ScreenHandler; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.util.Identifier; -import net.minecraft.network.PacketByteBuf; import net.fabricmc.fabric.api.container.ContainerFactory; import net.fabricmc.fabric.api.container.ContainerProviderRegistry; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; import net.fabricmc.fabric.mixin.container.ServerPlayerEntityAccessor; public class ContainerProviderImpl implements ContainerProviderRegistry { @@ -87,7 +87,7 @@ public void openContainer(Identifier identifier, ServerPlayerEntity player, Cons buf.writeByte(syncId); writer.accept(buf); - player.networkHandler.sendPacket(new CustomPayloadS2CPacket(OPEN_CONTAINER, buf)); + player.networkHandler.sendPacket(ServerPlayNetworking.createS2CPacket(OPEN_CONTAINER, buf)); PacketByteBuf clonedBuf = new PacketByteBuf(buf.duplicate()); clonedBuf.readIdentifier(); diff --git a/deprecated/fabric-events-lifecycle-v0/build.gradle b/deprecated/fabric-events-lifecycle-v0/build.gradle index 4bc6d891a7..20e1a551dd 100644 --- a/deprecated/fabric-events-lifecycle-v0/build.gradle +++ b/deprecated/fabric-events-lifecycle-v0/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-events-lifecycle-v0" version = getSubprojectVersion(project) moduleDependencies(project, [ diff --git a/deprecated/fabric-keybindings-v0/build.gradle b/deprecated/fabric-keybindings-v0/build.gradle index 50f676ea54..2a9a297e15 100644 --- a/deprecated/fabric-keybindings-v0/build.gradle +++ b/deprecated/fabric-keybindings-v0/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-keybindings-v0" version = getSubprojectVersion(project) moduleDependencies(project, [ diff --git a/deprecated/fabric-loot-tables-v1/build.gradle b/deprecated/fabric-loot-tables-v1/build.gradle deleted file mode 100644 index ba46e8b42e..0000000000 --- a/deprecated/fabric-loot-tables-v1/build.gradle +++ /dev/null @@ -1,11 +0,0 @@ -archivesBaseName = "fabric-loot-tables-v1" -version = getSubprojectVersion(project) - -moduleDependencies(project, [ - 'fabric-api-base', - 'fabric-loot-api-v2' -]) - -dependencies { - testmodRuntimeOnly(project(path: ':fabric-resource-loader-v0', configuration: 'namedElements')) -} diff --git a/deprecated/fabric-loot-tables-v1/src/main/java/net/fabricmc/fabric/api/loot/v1/FabricLootPool.java b/deprecated/fabric-loot-tables-v1/src/main/java/net/fabricmc/fabric/api/loot/v1/FabricLootPool.java deleted file mode 100644 index e310bdb03a..0000000000 --- a/deprecated/fabric-loot-tables-v1/src/main/java/net/fabricmc/fabric/api/loot/v1/FabricLootPool.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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 net.fabricmc.fabric.api.loot.v1; - -import java.util.List; - -import net.minecraft.loot.LootPool; -import net.minecraft.loot.condition.LootCondition; -import net.minecraft.loot.entry.LootPoolEntry; -import net.minecraft.loot.function.LootFunction; -import net.minecraft.loot.provider.number.LootNumberProvider; - -/** - * An interface implemented by all {@code net.minecraft.loot.LootPool} instances when - * Fabric API is present. Contains accessors for various fields. - * - * @deprecated Replaced with transitive access wideners in Fabric Transitive Access Wideners (v1). - */ -@Deprecated -public interface FabricLootPool { - default LootPool asVanilla() { - return (LootPool) this; - } - - List getEntries(); - List getConditions(); - List getFunctions(); - LootNumberProvider getRolls(); -} diff --git a/deprecated/fabric-loot-tables-v1/src/main/java/net/fabricmc/fabric/api/loot/v1/FabricLootPoolBuilder.java b/deprecated/fabric-loot-tables-v1/src/main/java/net/fabricmc/fabric/api/loot/v1/FabricLootPoolBuilder.java deleted file mode 100644 index 7f85a0baaf..0000000000 --- a/deprecated/fabric-loot-tables-v1/src/main/java/net/fabricmc/fabric/api/loot/v1/FabricLootPoolBuilder.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * 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 net.fabricmc.fabric.api.loot.v1; - -import net.minecraft.loot.condition.LootCondition; -import net.minecraft.loot.LootPool; -import net.minecraft.loot.provider.number.LootNumberProvider; -import net.minecraft.loot.entry.LootPoolEntry; -import net.minecraft.loot.function.LootFunction; - -/** - * @deprecated Replaced with {@link net.fabricmc.fabric.api.loot.v2.FabricLootPoolBuilder}. - */ -@Deprecated -public class FabricLootPoolBuilder extends LootPool.Builder { - private FabricLootPoolBuilder() { } - - private FabricLootPoolBuilder(LootPool pool) { - copyFrom(pool, true); - } - - private net.fabricmc.fabric.api.loot.v2.FabricLootPoolBuilder asV2() { - return (net.fabricmc.fabric.api.loot.v2.FabricLootPoolBuilder) this; - } - - @Override - public FabricLootPoolBuilder rolls(LootNumberProvider range) { - super.rolls(range); - return this; - } - - @Override - public FabricLootPoolBuilder with(LootPoolEntry.Builder entry) { - super.with(entry); - return this; - } - - @Override - public FabricLootPoolBuilder conditionally(LootCondition.Builder condition) { - super.conditionally(condition); - return this; - } - - @Override - public FabricLootPoolBuilder apply(LootFunction.Builder function) { - super.apply(function); - return this; - } - - public FabricLootPoolBuilder withEntry(LootPoolEntry entry) { - asV2().with(entry); - return this; - } - - public FabricLootPoolBuilder withCondition(LootCondition condition) { - asV2().conditionally(condition); - return this; - } - - public FabricLootPoolBuilder withFunction(LootFunction function) { - asV2().apply(function); - return this; - } - - /** - * Copies the entries, conditions and functions of the {@code pool} to this - * builder. - * - *

This is equal to {@code copyFrom(pool, false)}. - */ - public FabricLootPoolBuilder copyFrom(LootPool pool) { - return copyFrom(pool, false); - } - - /** - * Copies the entries, conditions and functions of the {@code pool} to this - * builder. - * - *

If {@code copyRolls} is true, the {@link FabricLootPool#getRolls rolls} of the pool are also copied. - */ - public FabricLootPoolBuilder copyFrom(LootPool pool, boolean copyRolls) { - FabricLootPool extended = (FabricLootPool) pool; - asV2().with(extended.getEntries()); - asV2().conditionally(extended.getConditions()); - asV2().apply(extended.getFunctions()); - - if (copyRolls) { - rolls(extended.getRolls()); - } - - return this; - } - - public static FabricLootPoolBuilder builder() { - return new FabricLootPoolBuilder(); - } - - public static FabricLootPoolBuilder of(LootPool pool) { - return new FabricLootPoolBuilder(pool); - } -} diff --git a/deprecated/fabric-loot-tables-v1/src/main/java/net/fabricmc/fabric/api/loot/v1/FabricLootSupplier.java b/deprecated/fabric-loot-tables-v1/src/main/java/net/fabricmc/fabric/api/loot/v1/FabricLootSupplier.java deleted file mode 100644 index 4890d1cc0b..0000000000 --- a/deprecated/fabric-loot-tables-v1/src/main/java/net/fabricmc/fabric/api/loot/v1/FabricLootSupplier.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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 net.fabricmc.fabric.api.loot.v1; - -import java.util.List; - -import net.minecraft.loot.LootPool; -import net.minecraft.loot.LootTable; -import net.minecraft.loot.context.LootContextType; -import net.minecraft.loot.function.LootFunction; - -/** - * An interface implemented by all {@link LootTable} instances when - * Fabric API is present. Contains accessors for various fields. - * - * @deprecated Replaced with transitive access wideners in Fabric Transitive Access Wideners (v1). - */ -@Deprecated -public interface FabricLootSupplier { - default LootTable asVanilla() { - return (LootTable) this; - } - - List getPools(); - List getFunctions(); - default LootContextType getType() { - return asVanilla().getType(); // Vanilla has this now - } -} diff --git a/deprecated/fabric-loot-tables-v1/src/main/java/net/fabricmc/fabric/api/loot/v1/FabricLootSupplierBuilder.java b/deprecated/fabric-loot-tables-v1/src/main/java/net/fabricmc/fabric/api/loot/v1/FabricLootSupplierBuilder.java deleted file mode 100644 index 657ce56a23..0000000000 --- a/deprecated/fabric-loot-tables-v1/src/main/java/net/fabricmc/fabric/api/loot/v1/FabricLootSupplierBuilder.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * 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 net.fabricmc.fabric.api.loot.v1; - -import java.util.Collection; - -import net.minecraft.loot.LootPool; -import net.minecraft.loot.LootTable; -import net.minecraft.loot.context.LootContextType; -import net.minecraft.loot.function.LootFunction; - -import net.fabricmc.fabric.api.loot.v2.FabricLootTableBuilder; - -/** - * @deprecated Replaced with {@link FabricLootTableBuilder}. - */ -@Deprecated -public class FabricLootSupplierBuilder extends LootTable.Builder { - protected FabricLootSupplierBuilder() { } - - private FabricLootSupplierBuilder(LootTable supplier) { - copyFrom(supplier, true); - } - - private FabricLootTableBuilder asV2() { - return (FabricLootTableBuilder) this; - } - - @Override - public FabricLootSupplierBuilder pool(LootPool.Builder pool) { - super.pool(pool); - return this; - } - - @Override - public FabricLootSupplierBuilder type(LootContextType type) { - super.type(type); - return this; - } - - @Override - public FabricLootSupplierBuilder apply(LootFunction.Builder function) { - super.apply(function); - return this; - } - - public FabricLootSupplierBuilder withPool(LootPool pool) { - asV2().pool(pool); - return this; - } - - public FabricLootSupplierBuilder withFunction(LootFunction function) { - asV2().apply(function); - return this; - } - - public FabricLootSupplierBuilder withPools(Collection pools) { - asV2().pools(pools); - return this; - } - - public FabricLootSupplierBuilder withFunctions(Collection functions) { - asV2().apply(functions); - return this; - } - - /** - * Copies the pools and functions of the {@code supplier} to this builder. - * This is equal to {@code copyFrom(supplier, false)}. - */ - public FabricLootSupplierBuilder copyFrom(LootTable supplier) { - return copyFrom(supplier, false); - } - - /** - * Copies the pools and functions of the {@code supplier} to this builder. - * If {@code copyType} is true, the {@link FabricLootSupplier#getType type} of the supplier is also copied. - */ - public FabricLootSupplierBuilder copyFrom(LootTable supplier, boolean copyType) { - FabricLootSupplier extendedSupplier = (FabricLootSupplier) supplier; - asV2().pools(extendedSupplier.getPools()); - asV2().apply(extendedSupplier.getFunctions()); - - if (copyType) { - type(extendedSupplier.getType()); - } - - return this; - } - - public static FabricLootSupplierBuilder builder() { - return new FabricLootSupplierBuilder(); - } - - public static FabricLootSupplierBuilder of(LootTable supplier) { - return new FabricLootSupplierBuilder(supplier); - } -} diff --git a/deprecated/fabric-loot-tables-v1/src/main/java/net/fabricmc/fabric/api/loot/v1/LootEntryTypeRegistry.java b/deprecated/fabric-loot-tables-v1/src/main/java/net/fabricmc/fabric/api/loot/v1/LootEntryTypeRegistry.java deleted file mode 100644 index a4b46fc4d2..0000000000 --- a/deprecated/fabric-loot-tables-v1/src/main/java/net/fabricmc/fabric/api/loot/v1/LootEntryTypeRegistry.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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 net.fabricmc.fabric.api.loot.v1; - -import net.minecraft.loot.entry.LootPoolEntry; -import net.minecraft.util.Identifier; -import net.minecraft.util.JsonSerializer; - -import net.fabricmc.fabric.impl.loot.table.LootEntryTypeRegistryImpl; - -/** - * Fabric's extensions to {@link net.minecraft.loot.entry.LootPoolEntryTypes} for registering - * custom loot entry types. - * - * @see #register - * @deprecated Use {@link net.minecraft.registry.Registries#LOOT_POOL_ENTRY_TYPE} from vanilla instead. - */ -@Deprecated -public interface LootEntryTypeRegistry { - LootEntryTypeRegistry INSTANCE = new LootEntryTypeRegistryImpl(); - - /** - * Registers a loot entry type serializer by its ID. - * - * @param id the loot entry's ID - * @param serializer the loot entry serializer - */ - void register(Identifier id, JsonSerializer serializer); -} diff --git a/deprecated/fabric-loot-tables-v1/src/main/java/net/fabricmc/fabric/api/loot/v1/LootJsonParser.java b/deprecated/fabric-loot-tables-v1/src/main/java/net/fabricmc/fabric/api/loot/v1/LootJsonParser.java deleted file mode 100644 index 86246980ea..0000000000 --- a/deprecated/fabric-loot-tables-v1/src/main/java/net/fabricmc/fabric/api/loot/v1/LootJsonParser.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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 net.fabricmc.fabric.api.loot.v1; - -import java.io.Reader; - -import com.google.gson.Gson; - -import net.minecraft.loot.LootGsons; -import net.minecraft.util.JsonHelper; - -/** - * @deprecated Use {@link LootGsons#getTableGsonBuilder()} from vanilla instead. - */ -@Deprecated -public final class LootJsonParser { - private static final Gson GSON = LootGsons.getTableGsonBuilder().create(); - - private LootJsonParser() { } - - public static T read(Reader json, Class c) { - return JsonHelper.deserialize(GSON, json, c); - } - - public static T read(String json, Class c) { - return JsonHelper.deserialize(GSON, json, c); - } -} diff --git a/deprecated/fabric-loot-tables-v1/src/main/java/net/fabricmc/fabric/api/loot/v1/event/LootTableLoadingCallback.java b/deprecated/fabric-loot-tables-v1/src/main/java/net/fabricmc/fabric/api/loot/v1/event/LootTableLoadingCallback.java deleted file mode 100644 index 764c94ada0..0000000000 --- a/deprecated/fabric-loot-tables-v1/src/main/java/net/fabricmc/fabric/api/loot/v1/event/LootTableLoadingCallback.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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 net.fabricmc.fabric.api.loot.v1.event; - -import net.minecraft.resource.ResourceManager; -import net.minecraft.util.Identifier; -import net.minecraft.loot.LootManager; -import net.minecraft.loot.LootTable; - -import net.fabricmc.fabric.api.event.Event; -import net.fabricmc.fabric.api.event.EventFactory; -import net.fabricmc.fabric.api.loot.v1.FabricLootSupplierBuilder; - -/** - * An event handler that is called when loot tables are loaded. - * Use {@link #EVENT} to register instances. - * - * @deprecated Replaced with {@link net.fabricmc.fabric.api.loot.v2.LootTableEvents}. - */ -@Deprecated -@FunctionalInterface -public interface LootTableLoadingCallback { - @Deprecated - @FunctionalInterface - interface LootTableSetter { - void set(LootTable supplier); - } - - Event EVENT = EventFactory.createArrayBacked( - LootTableLoadingCallback.class, - (listeners) -> (resourceManager, manager, id, supplier, setter) -> { - for (LootTableLoadingCallback callback : listeners) { - callback.onLootTableLoading(resourceManager, manager, id, supplier, setter); - } - } - ); - - void onLootTableLoading(ResourceManager resourceManager, LootManager manager, Identifier id, FabricLootSupplierBuilder supplier, LootTableSetter setter); -} diff --git a/deprecated/fabric-loot-tables-v1/src/main/java/net/fabricmc/fabric/impl/loot/table/BufferingLootTableBuilder.java b/deprecated/fabric-loot-tables-v1/src/main/java/net/fabricmc/fabric/impl/loot/table/BufferingLootTableBuilder.java deleted file mode 100644 index 3e6ddd6594..0000000000 --- a/deprecated/fabric-loot-tables-v1/src/main/java/net/fabricmc/fabric/impl/loot/table/BufferingLootTableBuilder.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * 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 net.fabricmc.fabric.impl.loot.table; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.function.Consumer; - -import net.minecraft.loot.LootPool; -import net.minecraft.loot.LootTable; -import net.minecraft.loot.context.LootContextType; -import net.minecraft.loot.function.LootFunction; - -import net.fabricmc.fabric.api.loot.v1.FabricLootSupplier; -import net.fabricmc.fabric.api.loot.v1.FabricLootSupplierBuilder; -import net.fabricmc.fabric.api.loot.v2.FabricLootTableBuilder; - -/** - * A {@link FabricLootSupplierBuilder} that caches all methods so they can be applied to a v2 {@link FabricLootTableBuilder}. - * Used for hooking {@code LootTableLoadingCallback} to the two different {@link net.fabricmc.fabric.api.loot.v2.LootTableEvents}. - */ -public class BufferingLootTableBuilder extends FabricLootSupplierBuilder { - private final List> modifications = new ArrayList<>(); - - private FabricLootSupplierBuilder addAction(Consumer action) { - modifications.add(action); - return this; - } - - private FabricLootSupplierBuilder addV2Action(Consumer action) { - return addAction(builder -> action.accept((FabricLootTableBuilder) builder)); - } - - @Override - public FabricLootSupplierBuilder pool(LootPool.Builder pool) { - super.pool(pool); - return addAction(builder -> builder.pool(pool)); - } - - @Override - public FabricLootSupplierBuilder type(LootContextType type) { - super.type(type); - return addAction(builder -> builder.type(type)); - } - - @Override - public FabricLootSupplierBuilder apply(LootFunction.Builder function) { - super.apply(function); - return addAction(builder -> builder.apply(function)); - } - - @Override - public FabricLootSupplierBuilder withPool(LootPool pool) { - super.withPool(pool); - return addV2Action(builder -> builder.pool(pool)); - } - - @Override - public FabricLootSupplierBuilder withFunction(LootFunction function) { - super.withFunction(function); - return addV2Action(builder -> builder.apply(function)); - } - - @Override - public FabricLootSupplierBuilder withPools(Collection pools) { - super.withPools(pools); - return addV2Action(builder -> builder.pools(pools)); - } - - @Override - public FabricLootSupplierBuilder withFunctions(Collection functions) { - super.withFunctions(functions); - return addV2Action(builder -> builder.apply(functions)); - } - - @Override - public FabricLootSupplierBuilder copyFrom(LootTable supplier, boolean copyType) { - super.copyFrom(supplier, copyType); - return addV2Action(builder -> { - FabricLootSupplier extended = (FabricLootSupplier) supplier; - builder.pools(extended.getPools()); - builder.apply(extended.getFunctions()); - - if (copyType) { - ((LootTable.Builder) builder).type(supplier.getType()); - } - }); - } - - public void init(LootTable original) { - super.type(original.getType()); - super.withPools(((FabricLootSupplier) original).getPools()); - super.withFunctions(((FabricLootSupplier) original).getFunctions()); - } - - public void applyTo(LootTable.Builder builder) { - for (Consumer modification : modifications) { - modification.accept(builder); - } - } -} diff --git a/deprecated/fabric-loot-tables-v1/src/main/java/net/fabricmc/fabric/impl/loot/table/LootEntryTypeRegistryImpl.java b/deprecated/fabric-loot-tables-v1/src/main/java/net/fabricmc/fabric/impl/loot/table/LootEntryTypeRegistryImpl.java deleted file mode 100644 index a27101e23c..0000000000 --- a/deprecated/fabric-loot-tables-v1/src/main/java/net/fabricmc/fabric/impl/loot/table/LootEntryTypeRegistryImpl.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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 net.fabricmc.fabric.impl.loot.table; - -import net.minecraft.loot.entry.LootPoolEntry; -import net.minecraft.loot.entry.LootPoolEntryType; -import net.minecraft.util.Identifier; -import net.minecraft.util.JsonSerializer; -import net.minecraft.registry.Registries; -import net.minecraft.registry.Registry; - -public final class LootEntryTypeRegistryImpl implements net.fabricmc.fabric.api.loot.v1.LootEntryTypeRegistry { - public LootEntryTypeRegistryImpl() { } - - @Override - public void register(Identifier id, JsonSerializer serializer) { - Registry.register(Registries.LOOT_POOL_ENTRY_TYPE, id, new LootPoolEntryType(serializer)); - } -} diff --git a/deprecated/fabric-loot-tables-v1/src/main/java/net/fabricmc/fabric/impl/loot/table/LootTablesV1Init.java b/deprecated/fabric-loot-tables-v1/src/main/java/net/fabricmc/fabric/impl/loot/table/LootTablesV1Init.java deleted file mode 100644 index d8c262e2c7..0000000000 --- a/deprecated/fabric-loot-tables-v1/src/main/java/net/fabricmc/fabric/impl/loot/table/LootTablesV1Init.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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 net.fabricmc.fabric.impl.loot.table; - -import java.util.HashMap; -import java.util.Map; - -import net.minecraft.loot.LootTable; -import net.minecraft.util.Identifier; - -import net.fabricmc.api.ModInitializer; -import net.fabricmc.fabric.api.loot.v1.event.LootTableLoadingCallback; -import net.fabricmc.fabric.api.loot.v2.LootTableEvents; - -public final class LootTablesV1Init implements ModInitializer { - private static final ThreadLocal> BUFFERS = ThreadLocal.withInitial(HashMap::new); - - @Override - public void onInitialize() { - LootTableEvents.REPLACE.register((resourceManager, lootManager, id, original, source) -> { - BufferingLootTableBuilder builder = new BufferingLootTableBuilder(); - builder.init(original); - BUFFERS.get().put(id, builder); - - LootTable[] result = new LootTable[1]; - LootTableLoadingCallback.EVENT.invoker().onLootTableLoading( - resourceManager, - lootManager, - id, - builder, - table -> result[0] = table - ); - - return result[0]; - }); - - LootTableEvents.MODIFY.register((resourceManager, lootManager, id, tableBuilder, source) -> { - Map buffers = BUFFERS.get(); - - if (buffers.containsKey(id)) { - try { - buffers.get(id).applyTo(tableBuilder); - } finally { - buffers.remove(id); - - if (buffers.isEmpty()) { - BUFFERS.remove(); - } - } - } - }); - } -} diff --git a/deprecated/fabric-loot-tables-v1/src/main/java/net/fabricmc/fabric/mixin/loot/table/LootPoolMixin.java b/deprecated/fabric-loot-tables-v1/src/main/java/net/fabricmc/fabric/mixin/loot/table/LootPoolMixin.java deleted file mode 100644 index f7855e7027..0000000000 --- a/deprecated/fabric-loot-tables-v1/src/main/java/net/fabricmc/fabric/mixin/loot/table/LootPoolMixin.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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 net.fabricmc.fabric.mixin.loot.table; - -import java.util.List; - -import com.google.common.collect.ImmutableList; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; - -import net.minecraft.loot.LootPool; -import net.minecraft.loot.condition.LootCondition; -import net.minecraft.loot.entry.LootPoolEntry; -import net.minecraft.loot.function.LootFunction; -import net.minecraft.loot.provider.number.LootNumberProvider; - -import net.fabricmc.fabric.api.loot.v1.FabricLootPool; - -@Mixin(LootPool.class) -public abstract class LootPoolMixin implements FabricLootPool { - @Shadow - @Final - LootPoolEntry[] entries; - - @Shadow - @Final - LootCondition[] conditions; - - @Shadow - @Final - LootFunction[] functions; - - @Shadow - @Final - LootNumberProvider rolls; - - @Override - public List getEntries() { - return ImmutableList.copyOf(entries); - } - - @Override - public List getConditions() { - return ImmutableList.copyOf(conditions); - } - - @Override - public List getFunctions() { - return ImmutableList.copyOf(functions); - } - - @Override - public LootNumberProvider getRolls() { - return rolls; - } -} diff --git a/deprecated/fabric-loot-tables-v1/src/main/java/net/fabricmc/fabric/mixin/loot/table/LootTableMixin.java b/deprecated/fabric-loot-tables-v1/src/main/java/net/fabricmc/fabric/mixin/loot/table/LootTableMixin.java deleted file mode 100644 index 42891bd0a4..0000000000 --- a/deprecated/fabric-loot-tables-v1/src/main/java/net/fabricmc/fabric/mixin/loot/table/LootTableMixin.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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 net.fabricmc.fabric.mixin.loot.table; - -import java.util.List; - -import com.google.common.collect.ImmutableList; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; - -import net.minecraft.loot.LootPool; -import net.minecraft.loot.LootTable; -import net.minecraft.loot.function.LootFunction; - -import net.fabricmc.fabric.api.loot.v1.FabricLootSupplier; - -@Mixin(LootTable.class) -public abstract class LootTableMixin implements FabricLootSupplier { - @Shadow - @Final - LootPool[] pools; - - @Shadow - @Final - LootFunction[] functions; - - @Override - public List getPools() { - return ImmutableList.copyOf(pools); - } - - @Override - public List getFunctions() { - return ImmutableList.copyOf(functions); - } -} diff --git a/deprecated/fabric-loot-tables-v1/src/main/resources/fabric-loot-tables-v1.mixins.json b/deprecated/fabric-loot-tables-v1/src/main/resources/fabric-loot-tables-v1.mixins.json deleted file mode 100644 index 4dd106ca42..0000000000 --- a/deprecated/fabric-loot-tables-v1/src/main/resources/fabric-loot-tables-v1.mixins.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "required": true, - "package": "net.fabricmc.fabric.mixin.loot.table", - "compatibilityLevel": "JAVA_16", - "mixins": [ - "LootPoolMixin", - "LootTableMixin" - ], - "injectors": { - "defaultRequire": 1 - } -} diff --git a/deprecated/fabric-loot-tables-v1/src/main/resources/fabric.mod.json b/deprecated/fabric-loot-tables-v1/src/main/resources/fabric.mod.json deleted file mode 100644 index 453c93cd35..0000000000 --- a/deprecated/fabric-loot-tables-v1/src/main/resources/fabric.mod.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "schemaVersion": 1, - "id": "fabric-loot-tables-v1", - "name": "Fabric Loot Tables (v1)", - "version": "${version}", - "environment": "*", - "license": "Apache-2.0", - "icon": "assets/fabric-loot-tables-v1/icon.png", - "contact": { - "homepage": "https://fabricmc.net", - "irc": "irc://irc.esper.net:6667/fabric", - "issues": "https://github.com/FabricMC/fabric/issues", - "sources": "https://github.com/FabricMC/fabric" - }, - "authors": [ - "FabricMC" - ], - "depends": { - "fabricloader": ">=0.4.0", - "fabric-api-base": "*", - "fabric-loot-api-v2": "*" - }, - "entrypoints": { - "main": ["net.fabricmc.fabric.impl.loot.table.LootTablesV1Init"] - }, - "description": "Hooks for manipulating loot tables.", - "mixins": [ - "fabric-loot-tables-v1.mixins.json" - ], - "custom": { - "fabric-api:module-lifecycle": "deprecated" - } -} diff --git a/deprecated/fabric-loot-tables-v1/src/testmod/java/net/fabricmc/fabric/test/loot/LootV1Test.java b/deprecated/fabric-loot-tables-v1/src/testmod/java/net/fabricmc/fabric/test/loot/LootV1Test.java deleted file mode 100644 index e87b06a37c..0000000000 --- a/deprecated/fabric-loot-tables-v1/src/testmod/java/net/fabricmc/fabric/test/loot/LootV1Test.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * 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 net.fabricmc.fabric.test.loot; - -import com.google.gson.Gson; -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonObject; -import com.google.gson.JsonSerializationContext; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import net.minecraft.block.Blocks; -import net.minecraft.item.Items; -import net.minecraft.loot.LootGsons; -import net.minecraft.loot.LootPool; -import net.minecraft.loot.LootTable; -import net.minecraft.loot.condition.LootCondition; -import net.minecraft.loot.condition.SurvivesExplosionLootCondition; -import net.minecraft.loot.entry.ItemEntry; -import net.minecraft.loot.entry.LootPoolEntry; -import net.minecraft.loot.entry.TagEntry; -import net.minecraft.loot.provider.number.ConstantLootNumberProvider; -import net.minecraft.util.Identifier; -import net.minecraft.util.JsonHelper; - -import net.fabricmc.api.ModInitializer; -import net.fabricmc.fabric.api.loot.v1.FabricLootPoolBuilder; -import net.fabricmc.fabric.api.loot.v1.FabricLootSupplierBuilder; -import net.fabricmc.fabric.api.loot.v1.LootEntryTypeRegistry; -import net.fabricmc.fabric.api.loot.v1.event.LootTableLoadingCallback; - -public class LootV1Test implements ModInitializer { - private static final Logger LOGGER = LoggerFactory.getLogger(LootV1Test.class); - - private static final Gson LOOT_GSON = LootGsons.getTableGsonBuilder().create(); - private static final String LOOT_ENTRY_JSON = "{\"type\":\"minecraft:item\",\"name\":\"minecraft:apple\"}"; - - @Override - public void onInitialize() { - // Test loot entry - LootEntryTypeRegistry.INSTANCE.register(new Identifier("fabric", "extended_tag"), new TestSerializer()); - - // Test loot table load event - LootTableLoadingCallback.EVENT.register((resourceManager, manager, id, supplier, setter) -> { - // Add feathers and apples to dirt (only one drops at the time since they're in the same pool), - // and replace grass block drops with wheat - if (Blocks.DIRT.getLootTableId().equals(id)) { - LootPoolEntry entryFromString = LOOT_GSON.fromJson(LOOT_ENTRY_JSON, LootPoolEntry.class); - - LootPool pool = FabricLootPoolBuilder.builder() - .withEntry(ItemEntry.builder(Items.FEATHER).build()) - .withEntry(entryFromString) - .rolls(ConstantLootNumberProvider.create(1)) - .withCondition(SurvivesExplosionLootCondition.builder().build()) - .build(); - - supplier.withPool(pool); - } else if (Blocks.GRASS_BLOCK.getLootTableId().equals(id)) { - LootTable table = FabricLootSupplierBuilder.builder() - .pool(FabricLootPoolBuilder.builder().with(ItemEntry.builder(Items.WHEAT))) - .build(); - setter.set(table); - } - }); - } - - private static class TestSerializer extends LootPoolEntry.Serializer { - private static final TagEntry.Serializer SERIALIZER = new TagEntry.Serializer(); - - @Override - public void addEntryFields(JsonObject json, TagEntry entry, JsonSerializationContext context) { - SERIALIZER.addEntryFields(json, entry, context); - json.addProperty("fabric", true); - } - - @Override - public TagEntry fromJson(JsonObject var1, JsonDeserializationContext var2, LootCondition[] var3) { - LOGGER.info("Is this a Fabric loot entry? " + JsonHelper.getBoolean(var1, "fabric", true)); - return SERIALIZER.fromJson(var1, var2, var3); - } - } -} diff --git a/deprecated/fabric-loot-tables-v1/src/testmod/resources/data/minecraft/loot_tables/blocks/stone.json b/deprecated/fabric-loot-tables-v1/src/testmod/resources/data/minecraft/loot_tables/blocks/stone.json deleted file mode 100644 index 6b0ef6c00f..0000000000 --- a/deprecated/fabric-loot-tables-v1/src/testmod/resources/data/minecraft/loot_tables/blocks/stone.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "type": "minecraft:block", - "pools": [ - { - "rolls": 1, - "entries": [ - { - "type": "minecraft:alternatives", - "children": [ - { - "type": "minecraft:item", - "conditions": [ - { - "condition": "minecraft:match_tool", - "predicate": { - "enchantments": [ - { - "enchantment": "minecraft:silk_touch", - "levels": { - "min": 1 - } - } - ] - } - } - ], - "name": "minecraft:stone" - }, - { - "type": "fabric:extended_tag", - "conditions": [ - { - "condition": "minecraft:survives_explosion" - } - ], - "name": "minecraft:wool", - "expand": false - } - ] - } - ] - } - ] -} diff --git a/deprecated/fabric-loot-tables-v1/src/testmod/resources/fabric.mod.json b/deprecated/fabric-loot-tables-v1/src/testmod/resources/fabric.mod.json deleted file mode 100644 index 4a2f37cfb2..0000000000 --- a/deprecated/fabric-loot-tables-v1/src/testmod/resources/fabric.mod.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "schemaVersion": 1, - "id": "fabric-loot-tables-v1-testmod", - "name": "Fabric Loot Tables API (v1) Test Mod", - "version": "1.0.0", - "environment": "*", - "license": "Apache-2.0", - "depends": { - "fabric-loot-tables-v1": "*" - }, - "entrypoints": { - "main": [ - "net.fabricmc.fabric.test.loot.LootV1Test" - ] - } -} diff --git a/deprecated/fabric-networking-v0/build.gradle b/deprecated/fabric-models-v0/build.gradle similarity index 57% rename from deprecated/fabric-networking-v0/build.gradle rename to deprecated/fabric-models-v0/build.gradle index f4d366d9aa..cfc8a41fda 100644 --- a/deprecated/fabric-networking-v0/build.gradle +++ b/deprecated/fabric-models-v0/build.gradle @@ -1,7 +1,6 @@ -archivesBaseName = "fabric-networking-v0" version = getSubprojectVersion(project) moduleDependencies(project, [ 'fabric-api-base', - 'fabric-networking-api-v1' + 'fabric-model-loading-api-v1' ]) diff --git a/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/BakedModelManagerHelper.java b/deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/BakedModelManagerHelper.java similarity index 89% rename from fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/BakedModelManagerHelper.java rename to deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/BakedModelManagerHelper.java index 3eea16ad15..d13bec8c80 100644 --- a/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/BakedModelManagerHelper.java +++ b/deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/BakedModelManagerHelper.java @@ -23,8 +23,12 @@ import net.minecraft.client.util.ModelIdentifier; import net.minecraft.util.Identifier; -import net.fabricmc.fabric.impl.client.model.BakedModelManagerHooks; +import net.fabricmc.fabric.api.client.model.loading.v1.FabricBakedModelManager; +/** + * @deprecated Use {@link FabricBakedModelManager#getModel(Identifier)} instead. + */ +@Deprecated public final class BakedModelManagerHelper { /** * An alternative to {@link BakedModelManager#getModel(ModelIdentifier)} that accepts an @@ -42,7 +46,7 @@ public final class BakedModelManagerHelper { */ @Nullable public static BakedModel getModel(BakedModelManager manager, Identifier id) { - return ((BakedModelManagerHooks) manager).fabric_getModel(id); + return manager.getModel(id); } private BakedModelManagerHelper() { } diff --git a/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ExtraModelProvider.java b/deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ExtraModelProvider.java similarity index 90% rename from fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ExtraModelProvider.java rename to deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ExtraModelProvider.java index 8293166531..0a6afcbed8 100644 --- a/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ExtraModelProvider.java +++ b/deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ExtraModelProvider.java @@ -22,6 +22,12 @@ import net.minecraft.resource.ResourceManager; import net.minecraft.util.Identifier; +import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin; + +/** + * @deprecated Use {@link ModelLoadingPlugin} and related classes instead. + */ +@Deprecated @FunctionalInterface public interface ExtraModelProvider { /** diff --git a/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelAppender.java b/deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelAppender.java similarity index 100% rename from fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelAppender.java rename to deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelAppender.java diff --git a/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelLoadingRegistry.java b/deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelLoadingRegistry.java similarity index 92% rename from fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelLoadingRegistry.java rename to deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelLoadingRegistry.java index b0f041da0f..8f72a5d046 100644 --- a/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelLoadingRegistry.java +++ b/deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelLoadingRegistry.java @@ -21,8 +21,13 @@ import net.minecraft.resource.ResourceManager; import net.minecraft.util.Identifier; +import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin; import net.fabricmc.fabric.impl.client.model.ModelLoadingRegistryImpl; +/** + * @deprecated Register a {@link ModelLoadingPlugin} instead. + */ +@Deprecated public interface ModelLoadingRegistry { ModelLoadingRegistry INSTANCE = new ModelLoadingRegistryImpl(); diff --git a/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelProviderContext.java b/deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelProviderContext.java similarity index 88% rename from fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelProviderContext.java rename to deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelProviderContext.java index 3aafc7014a..96b097d285 100644 --- a/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelProviderContext.java +++ b/deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelProviderContext.java @@ -20,9 +20,13 @@ import net.minecraft.client.util.ModelIdentifier; import net.minecraft.util.Identifier; +import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin; + /** * The model loading context used during model providing. + * @deprecated Use {@link ModelLoadingPlugin} and related classes instead. */ +@Deprecated public interface ModelProviderContext { /** * Load a model using a {@link Identifier}, {@link ModelIdentifier}, ... diff --git a/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelProviderException.java b/deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelProviderException.java similarity index 83% rename from fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelProviderException.java rename to deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelProviderException.java index 1d7899927e..41805578cc 100644 --- a/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelProviderException.java +++ b/deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelProviderException.java @@ -16,6 +16,12 @@ package net.fabricmc.fabric.api.client.model; +import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin; + +/** + * @deprecated Use {@link ModelLoadingPlugin} and related classes instead. + */ +@Deprecated public class ModelProviderException extends Exception { public ModelProviderException(String s) { super(s); diff --git a/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelResourceProvider.java b/deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelResourceProvider.java similarity index 92% rename from fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelResourceProvider.java rename to deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelResourceProvider.java index b7e7eb60b0..caf7b1a49a 100644 --- a/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelResourceProvider.java +++ b/deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelResourceProvider.java @@ -21,6 +21,8 @@ import net.minecraft.client.render.model.UnbakedModel; import net.minecraft.util.Identifier; +import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin; + /** * Interface for model resource providers. * @@ -40,7 +42,10 @@ * *

  • Only load files with a mod-suffixed name, such as .architect.obj, *
  • Only load files from an explicit list of namespaces, registered elsewhere.
+ * + * @deprecated Use {@link ModelLoadingPlugin} and related classes instead. */ +@Deprecated @FunctionalInterface public interface ModelResourceProvider { /** diff --git a/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelVariantProvider.java b/deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelVariantProvider.java similarity index 92% rename from fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelVariantProvider.java rename to deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelVariantProvider.java index b2ab8e6c5d..1fdf970b2b 100644 --- a/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelVariantProvider.java +++ b/deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/api/client/model/ModelVariantProvider.java @@ -21,6 +21,8 @@ import net.minecraft.client.render.model.UnbakedModel; import net.minecraft.client.util.ModelIdentifier; +import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin; + /** * Interface for model variant providers. * @@ -37,7 +39,10 @@ * *

Keep in mind that only *one* ModelVariantProvider may respond to a given model * at any time. + * + * @deprecated Use {@link ModelLoadingPlugin} and related classes instead. */ +@Deprecated @FunctionalInterface public interface ModelVariantProvider { /** diff --git a/deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/impl/client/model/ModelLoadingRegistryImpl.java b/deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/impl/client/model/ModelLoadingRegistryImpl.java new file mode 100644 index 0000000000..6ab871a580 --- /dev/null +++ b/deprecated/fabric-models-v0/src/client/java/net/fabricmc/fabric/impl/client/model/ModelLoadingRegistryImpl.java @@ -0,0 +1,111 @@ +/* + * 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 net.fabricmc.fabric.impl.client.model; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; +import java.util.function.Function; + +import net.minecraft.client.util.ModelIdentifier; +import net.minecraft.resource.ResourceManager; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.api.client.model.ExtraModelProvider; +import net.fabricmc.fabric.api.client.model.ModelAppender; +import net.fabricmc.fabric.api.client.model.ModelLoadingRegistry; +import net.fabricmc.fabric.api.client.model.ModelProviderContext; +import net.fabricmc.fabric.api.client.model.ModelProviderException; +import net.fabricmc.fabric.api.client.model.ModelResourceProvider; +import net.fabricmc.fabric.api.client.model.ModelVariantProvider; +import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin; +import net.fabricmc.fabric.api.client.model.loading.v1.PreparableModelLoadingPlugin; +import net.fabricmc.fabric.impl.client.model.loading.ModelLoaderPluginContextImpl; + +public class ModelLoadingRegistryImpl implements ModelLoadingRegistry { + private final List modelProviders = new ArrayList<>(); + private final List modelAppenders = new ArrayList<>(); + private final List> resourceProviderSuppliers = new ArrayList<>(); + private final List> variantProviderSuppliers = new ArrayList<>(); + + { + // Grabs the resource manager to use it in the main model loading code. + // When using the v1 API, data should be loaded in parallel before model loading starts. + PreparableModelLoadingPlugin.register( + (resourceManager, executor) -> CompletableFuture.completedFuture(resourceManager), + this::onInitializeModelLoader); + } + + private void onInitializeModelLoader(ResourceManager resourceManager, ModelLoadingPlugin.Context pluginContext) { + Consumer extraModelConsumer = pluginContext::addModels; + Consumer extraModelConsumer2 = pluginContext::addModels; + // A bit hacky, but avoids the allocation of a new context wrapper every time. + ModelProviderContext resourceProviderContext = ((ModelLoaderPluginContextImpl) pluginContext).modelGetter::apply; + + for (ExtraModelProvider provider : modelProviders) { + provider.provideExtraModels(resourceManager, extraModelConsumer); + } + + for (ModelAppender appender : modelAppenders) { + appender.appendAll(resourceManager, extraModelConsumer2); + } + + for (Function supplier : resourceProviderSuppliers) { + ModelResourceProvider provider = supplier.apply(resourceManager); + + pluginContext.resolveModel().register(resolverContext -> { + try { + return provider.loadModelResource(resolverContext.id(), resourceProviderContext); + } catch (ModelProviderException e) { + throw new RuntimeException(e); + } + }); + } + + for (Function supplier : variantProviderSuppliers) { + ModelVariantProvider provider = supplier.apply(resourceManager); + ((ModelLoaderPluginContextImpl) pluginContext).legacyVariantProviders().register(modelId -> { + try { + return provider.loadModelVariant(modelId, resourceProviderContext); + } catch (ModelProviderException e) { + throw new RuntimeException(e); + } + }); + } + } + + @Override + public void registerModelProvider(ExtraModelProvider provider) { + modelProviders.add(provider); + } + + @Override + public void registerAppender(ModelAppender appender) { + modelAppenders.add(appender); + } + + @Override + public void registerResourceProvider(Function providerSupplier) { + resourceProviderSuppliers.add(providerSupplier); + } + + @Override + public void registerVariantProvider(Function providerSupplier) { + variantProviderSuppliers.add(providerSupplier); + } +} diff --git a/deprecated/fabric-loot-tables-v1/src/main/resources/assets/fabric-loot-tables-v1/icon.png b/deprecated/fabric-models-v0/src/client/resources/assets/fabric-models-v0/icon.png similarity index 100% rename from deprecated/fabric-loot-tables-v1/src/main/resources/assets/fabric-loot-tables-v1/icon.png rename to deprecated/fabric-models-v0/src/client/resources/assets/fabric-models-v0/icon.png diff --git a/fabric-models-v0/src/client/resources/fabric.mod.json b/deprecated/fabric-models-v0/src/client/resources/fabric.mod.json similarity index 82% rename from fabric-models-v0/src/client/resources/fabric.mod.json rename to deprecated/fabric-models-v0/src/client/resources/fabric.mod.json index 93a4b284b5..ced3a503e8 100644 --- a/fabric-models-v0/src/client/resources/fabric.mod.json +++ b/deprecated/fabric-models-v0/src/client/resources/fabric.mod.json @@ -17,13 +17,11 @@ ], "depends": { "fabricloader": ">=0.4.0", - "fabric-api-base": "*" + "fabric-api-base": "*", + "fabric-model-loading-api-v1": "*" }, "description": "Hooks for models and model loading.", - "mixins": [ - "fabric-models-v0.mixins.json" - ], "custom": { - "fabric-api:module-lifecycle": "stable" + "fabric-api:module-lifecycle": "deprecated" } } diff --git a/deprecated/fabric-networking-v0/src/client/java/net/fabricmc/fabric/api/network/ClientSidePacketRegistry.java b/deprecated/fabric-networking-v0/src/client/java/net/fabricmc/fabric/api/network/ClientSidePacketRegistry.java deleted file mode 100644 index ce22ac536b..0000000000 --- a/deprecated/fabric-networking-v0/src/client/java/net/fabricmc/fabric/api/network/ClientSidePacketRegistry.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * 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 net.fabricmc.fabric.api.network; - -import io.netty.util.concurrent.Future; -import io.netty.util.concurrent.GenericFutureListener; - -import net.minecraft.network.packet.Packet; -import net.minecraft.util.Identifier; -import net.minecraft.network.PacketByteBuf; - -import net.fabricmc.fabric.impl.client.networking.v0.ClientSidePacketRegistryImpl; - -/** - * The client-side packet registry. - * - *

It is used for: - * - *

  • registering client-side packet receivers (server -> client packets) - *
  • sending packets to the server (client -> server packets).
- * - * @deprecated Please migrate to {@link net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking}. - */ -@Deprecated -public interface ClientSidePacketRegistry extends PacketRegistry { - ClientSidePacketRegistry INSTANCE = new ClientSidePacketRegistryImpl(); - - /** - * Check if the server declared the ability to receive a given packet ID - * using the vanilla "register/unregister" protocol. - * - * @param id The packet identifier. - * @return True if the server side declared a given packet identifier. - */ - boolean canServerReceive(Identifier id); - - /** - * Send a packet to the server. - * - * @param packet The packet to be sent. - * @param completionListener Completion listener. Can be used to check for - * the success or failure of sending a given packet, among others. - */ - void sendToServer(Packet packet, GenericFutureListener> completionListener); - - /** - * Send an identifier/buffer-based packet to the server. - * - * @param id The packet identifier. - * @param buf The packet byte buffer. - * @param completionListener Completion listener. Can be used to check for - * the success or failure of sending a given packet, among others. - */ - default void sendToServer(Identifier id, PacketByteBuf buf, GenericFutureListener> completionListener) { - sendToServer(toPacket(id, buf), completionListener); - } - - /** - * Send a packet to the server. - * - * @param packet The packet to be sent. - */ - default void sendToServer(Packet packet) { - sendToServer(packet, null); - } - - /** - * Send an identifier/buffer-based packet to the server. - * - * @param id The packet identifier. - * @param buf The packet byte buffer. - */ - default void sendToServer(Identifier id, PacketByteBuf buf) { - sendToServer(id, buf, null); - } -} diff --git a/deprecated/fabric-networking-v0/src/client/java/net/fabricmc/fabric/impl/client/networking/v0/ClientSidePacketRegistryImpl.java b/deprecated/fabric-networking-v0/src/client/java/net/fabricmc/fabric/impl/client/networking/v0/ClientSidePacketRegistryImpl.java deleted file mode 100644 index 9a902344d5..0000000000 --- a/deprecated/fabric-networking-v0/src/client/java/net/fabricmc/fabric/impl/client/networking/v0/ClientSidePacketRegistryImpl.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * 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 net.fabricmc.fabric.impl.client.networking.v0; - -import java.util.Objects; - -import io.netty.util.concurrent.Future; -import io.netty.util.concurrent.GenericFutureListener; - -import net.minecraft.client.MinecraftClient; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.network.packet.Packet; -import net.minecraft.network.PacketByteBuf; -import net.minecraft.util.Identifier; -import net.minecraft.util.thread.ThreadExecutor; - -import net.fabricmc.api.EnvType; -import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; -import net.fabricmc.fabric.api.network.ClientSidePacketRegistry; -import net.fabricmc.fabric.api.network.PacketConsumer; -import net.fabricmc.fabric.api.network.PacketContext; -import net.fabricmc.fabric.api.network.PacketRegistry; -import net.fabricmc.fabric.impl.networking.GenericFutureListenerHolder; - -public class ClientSidePacketRegistryImpl implements ClientSidePacketRegistry, PacketRegistry { - @Override - public boolean canServerReceive(Identifier id) { - return ClientPlayNetworking.getSendable().contains(id); - } - - @Override - public void sendToServer(Packet packet, GenericFutureListener> completionListener) { - if (MinecraftClient.getInstance().getNetworkHandler() != null) { - MinecraftClient.getInstance().getNetworkHandler().getConnection().send(packet, GenericFutureListenerHolder.create(completionListener)); - return; - } - - throw new IllegalStateException("Cannot send packet to server while not in game!"); // TODO: Error message - } - - @Override - public Packet toPacket(Identifier id, PacketByteBuf buf) { - return ClientPlayNetworking.createC2SPacket(id, buf); - } - - @Override - public void register(Identifier id, PacketConsumer consumer) { - // id is checked in client networking - Objects.requireNonNull(consumer, "PacketConsumer cannot be null"); - - ClientPlayNetworking.registerGlobalReceiver(id, (client, handler, buf, sender) -> { - consumer.accept(new PacketContext() { - @Override - public EnvType getPacketEnvironment() { - return EnvType.CLIENT; - } - - @Override - public PlayerEntity getPlayer() { - return client.player; - } - - @Override - public ThreadExecutor getTaskQueue() { - return client; - } - }, buf); - }); - } - - @Override - public void unregister(Identifier id) { - ClientPlayNetworking.unregisterGlobalReceiver(id); - } -} diff --git a/deprecated/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/event/network/C2SPacketTypeCallback.java b/deprecated/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/event/network/C2SPacketTypeCallback.java deleted file mode 100644 index 88459950f0..0000000000 --- a/deprecated/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/event/network/C2SPacketTypeCallback.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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 net.fabricmc.fabric.api.event.network; - -import java.util.Collection; - -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.util.Identifier; - -import net.fabricmc.fabric.api.event.Event; -import net.fabricmc.fabric.api.event.EventFactory; -import net.fabricmc.fabric.api.networking.v1.S2CPlayChannelEvents; - -/** - * Event for listening to packet type registration and unregistration notifications - * (also known as "minecraft:register" and "minecraft:unregister") sent by a client. - * - *

Registrations received will be for server -> client packets - * that the sending client can understand. - * - * @deprecated Please migrate to {@link S2CPlayChannelEvents} since this was incorrectly named. - */ -@Deprecated -public interface C2SPacketTypeCallback { - /** - * @deprecated Please migrate to {@link net.fabricmc.fabric.api.client.networking.v1.C2SPlayChannelEvents#REGISTER}. - */ - @Deprecated - Event REGISTERED = EventFactory.createArrayBacked( - C2SPacketTypeCallback.class, - (callbacks) -> (client, types) -> { - for (C2SPacketTypeCallback callback : callbacks) { - callback.accept(client, types); - } - } - ); - - /** - * @deprecated Please migrate to {@link net.fabricmc.fabric.api.client.networking.v1.C2SPlayChannelEvents#UNREGISTER}. - */ - @Deprecated - Event UNREGISTERED = EventFactory.createArrayBacked( - C2SPacketTypeCallback.class, - (callbacks) -> (client, types) -> { - for (C2SPacketTypeCallback callback : callbacks) { - callback.accept(client, types); - } - } - ); - - /** - * Accept a collection of types. - * - * @param client The player who is the source of the packet. - * @param types The provided collection of types. - */ - void accept(PlayerEntity client, Collection types); -} diff --git a/deprecated/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/event/network/S2CPacketTypeCallback.java b/deprecated/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/event/network/S2CPacketTypeCallback.java deleted file mode 100644 index 5bf7b59207..0000000000 --- a/deprecated/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/event/network/S2CPacketTypeCallback.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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 net.fabricmc.fabric.api.event.network; - -import java.util.Collection; - -import net.minecraft.util.Identifier; - -import net.fabricmc.fabric.api.event.Event; -import net.fabricmc.fabric.api.event.EventFactory; -import net.fabricmc.fabric.api.networking.v1.S2CPlayChannelEvents; - -/** - * Event for listening to packet type registration and unregistration notifications - * (also known as "minecraft:register" and "minecraft:unregister") sent by a server. - * - *

Registrations received will be for client -> server packets - * that the sending server can understand. - * - * @deprecated Please migrate to {@link net.fabricmc.fabric.api.client.networking.v1.C2SPlayChannelEvents} since this was incorrectly named. - */ -@Deprecated -public interface S2CPacketTypeCallback { - /** - * @deprecated Please migrate to {@link S2CPlayChannelEvents#REGISTER}. - */ - @Deprecated - Event REGISTERED = EventFactory.createArrayBacked( - S2CPacketTypeCallback.class, - (callbacks) -> (types) -> { - for (S2CPacketTypeCallback callback : callbacks) { - callback.accept(types); - } - } - ); - - /** - * @deprecated Please migrate to {@link S2CPlayChannelEvents#UNREGISTER}. - */ - @Deprecated - Event UNREGISTERED = EventFactory.createArrayBacked( - S2CPacketTypeCallback.class, - (callbacks) -> (types) -> { - for (S2CPacketTypeCallback callback : callbacks) { - callback.accept(types); - } - } - ); - - /** - * Accept a collection of types. - * - * @param types The provided collection of types. - */ - void accept(Collection types); -} diff --git a/deprecated/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/network/PacketConsumer.java b/deprecated/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/network/PacketConsumer.java deleted file mode 100644 index d390ac03a4..0000000000 --- a/deprecated/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/network/PacketConsumer.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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 net.fabricmc.fabric.api.network; - -import net.minecraft.network.PacketByteBuf; - -import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; - -/** - * Interface for receiving CustomPayload-based packets. - * - * @deprecated See the corresponding play packet handler in {@link net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking} or {@link ServerPlayNetworking} - */ -@Deprecated -@FunctionalInterface -public interface PacketConsumer { - /** - * Receive a CustomPayload-based packet. - * - *

The PacketByteBuf received will be released as soon as the method exits, - * meaning that you have to call .retain()/.release() on it if you want to - * keep it around after that. - * - *

Please keep in mind that this CAN be called OUTSIDE of the main thread! - * Most game operations are not thread-safe, so you should look into using - * the thread task queue ({@link PacketContext#getTaskQueue()}) to split - * the "reading" (which should happen within this method's execution) - * and "applying" (which, unless you know what you're doing, should happen - * on the main thread, after this method exits). - * - * @param context The context (receiving player, side, etc.) - * @param buffer The byte buffer containing the received packet data. - */ - void accept(PacketContext context, PacketByteBuf buffer); -} diff --git a/deprecated/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/network/PacketContext.java b/deprecated/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/network/PacketContext.java deleted file mode 100644 index 962d98cc12..0000000000 --- a/deprecated/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/network/PacketContext.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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 net.fabricmc.fabric.api.network; - -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.util.thread.ThreadExecutor; - -import net.fabricmc.api.EnvType; - -/** - * Interface defining a context used during packet processing. Allows access - * to additional information, such as the source/target of the player, or - * the correct task queue to enqueue synchronization-requiring code on. - */ -@Deprecated -public interface PacketContext { - /** - * Get the environment associated with the packet. - * - * @return EnvType.CLIENT if processing packet on the client side, - * EnvType.SERVER otherwise. - */ - EnvType getPacketEnvironment(); - - /** - * Get the player associated with the packet. - * - *

On the client side, this always returns the client-side player instance. - * On the server side, it returns the player belonging to the client this - * packet was sent by. - * - * @return The player associated with the packet. - */ - PlayerEntity getPlayer(); - - /** - * Get the task queue for a given side. - * - *

As Minecraft networking I/O is asynchronous, but a lot of its logic is - * not thread-safe, it is recommended to do the following: - * - *

  • read and parse the PacketByteBuf, - *
  • run the packet response logic through the main thread task queue via - * ThreadTaskQueue.execute(). The method will check if it's not already - * on the main thread in order to avoid unnecessary delays, so don't - * worry about that!
- * - * @return The thread task queue. - */ - ThreadExecutor getTaskQueue(); -} diff --git a/deprecated/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/network/PacketRegistry.java b/deprecated/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/network/PacketRegistry.java deleted file mode 100644 index b5263731a2..0000000000 --- a/deprecated/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/network/PacketRegistry.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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 net.fabricmc.fabric.api.network; - -import net.minecraft.network.packet.Packet; -import net.minecraft.util.Identifier; -import net.minecraft.network.PacketByteBuf; - -@Deprecated -public interface PacketRegistry { - /** - * Turn a (identifier, byte buffer) pair into a "custom payload" packet - * suitable for sending in the PacketRegistry's sending direction. - * - * @param id The identifier. - * @param buf The byte buffer. - * @return - */ - Packet toPacket(Identifier id, PacketByteBuf buf); - - /** - * Register a packet. - * - * @param id The packet Identifier. - * @param consumer The method used for handling the packet. - */ - void register(Identifier id, PacketConsumer consumer); - - /** - * Unregister a packet. - * - * @param id The packet Identifier. - */ - void unregister(Identifier id); -} diff --git a/deprecated/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/network/ServerSidePacketRegistry.java b/deprecated/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/network/ServerSidePacketRegistry.java deleted file mode 100644 index febc39fbe9..0000000000 --- a/deprecated/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/network/ServerSidePacketRegistry.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * 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 net.fabricmc.fabric.api.network; - -import io.netty.util.concurrent.Future; -import io.netty.util.concurrent.GenericFutureListener; - -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.network.packet.Packet; -import net.minecraft.util.Identifier; -import net.minecraft.network.PacketByteBuf; - -import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; -import net.fabricmc.fabric.api.server.PlayerStream; -import net.fabricmc.fabric.impl.networking.v0.ServerSidePacketRegistryImpl; - -/** - * The server-side packet registry. - * - *

It is used for: - * - *

  • registering server-side packet receivers (client -> server packets) - *
  • sending packets to clients (server -> client packets).
- * - *

For iterating over clients in a server, see {@link PlayerStream}. - * - * @deprecated Please migrate to {@link ServerPlayNetworking}. - */ -@Deprecated -public interface ServerSidePacketRegistry extends PacketRegistry { - ServerSidePacketRegistry INSTANCE = new ServerSidePacketRegistryImpl(); - - /** - * Check if a given client declared the ability to receive a given packet ID - * using the vanilla "register/unregister" protocol. - * - * @param id The packet identifier. - * @return True if the client side declared a given packet identifier. - */ - boolean canPlayerReceive(PlayerEntity player, Identifier id); - - /** - * Send a packet to a given client. - * - * @param player The given client. - * @param packet The packet to be sent. - * @param completionListener Completion listener. Can be used to check for - * the success or failure of sending a given packet, among others. - */ - void sendToPlayer(PlayerEntity player, Packet packet, GenericFutureListener> completionListener); - - /** - * Send an identifier/buffer-based packet to a given client. - * - * @param player The given client. - * @param id The packet identifier. - * @param buf The packet byte buffer. - * @param completionListener Completion listener. Can be used to check for - * the success or failure of sending a given packet, among others. - */ - default void sendToPlayer(PlayerEntity player, Identifier id, PacketByteBuf buf, GenericFutureListener> completionListener) { - sendToPlayer(player, toPacket(id, buf), completionListener); - } - - /** - * Send a packet to a given client. - * - * @param player The given client. - * @param packet The packet to be sent. - */ - default void sendToPlayer(PlayerEntity player, Packet packet) { - sendToPlayer(player, packet, null); - } - - /** - * Send an identifier/buffer-based packet to a given client. - * - * @param player The given client. - * @param id The packet identifier. - * @param buf The packet byte buffer. - */ - default void sendToPlayer(PlayerEntity player, Identifier id, PacketByteBuf buf) { - sendToPlayer(player, id, buf, null); - } -} diff --git a/deprecated/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/server/PlayerStream.java b/deprecated/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/server/PlayerStream.java deleted file mode 100644 index 15b0654edf..0000000000 --- a/deprecated/fabric-networking-v0/src/main/java/net/fabricmc/fabric/api/server/PlayerStream.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * 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 net.fabricmc.fabric.api.server; - -import java.util.stream.Stream; - -import net.minecraft.block.entity.BlockEntity; -import net.minecraft.entity.Entity; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.server.MinecraftServer; -import net.minecraft.server.network.ServerPlayerEntity; -import net.minecraft.server.world.ServerWorld; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.ChunkPos; -import net.minecraft.util.math.Vec3d; -import net.minecraft.world.World; - -import net.fabricmc.fabric.api.networking.v1.PlayerLookup; - -/** - * Helper streams for looking up players on a server. - * - *

In general, most of these methods will only function with a {@link ServerWorld} instance. - * - * @deprecated Please use {@link PlayerLookup} instead. - */ -@Deprecated -public final class PlayerStream { - private PlayerStream() { } - - public static Stream all(MinecraftServer server) { - if (server.getPlayerManager() != null) { - return server.getPlayerManager().getPlayerList().stream(); - } else { - return Stream.empty(); - } - } - - public static Stream world(World world) { - if (world instanceof ServerWorld) { - // noinspection unchecked,rawtypes - return ((Stream) ((ServerWorld) world).getPlayers().stream()); - } else { - throw new RuntimeException("Only supported on ServerWorld!"); - } - } - - public static Stream watching(World world, ChunkPos pos) { - if (world instanceof ServerWorld) { - //noinspection unchecked,rawtypes - return (Stream) PlayerLookup.tracking((ServerWorld) world, pos).stream(); - } - - throw new RuntimeException("Only supported on ServerWorld!"); - } - - /** - * Warning: If the provided entity is a PlayerEntity themselves, it is not - * guaranteed by the contract that said PlayerEntity is included in the - * resulting stream. - */ - @SuppressWarnings("JavaDoc") - public static Stream watching(Entity entity) { - //noinspection unchecked,rawtypes - return (Stream) PlayerLookup.tracking(entity).stream(); - } - - public static Stream watching(BlockEntity entity) { - return watching(entity.getWorld(), entity.getPos()); - } - - public static Stream watching(World world, BlockPos pos) { - return watching(world, new ChunkPos(pos)); - } - - public static Stream around(World world, Vec3d vector, double radius) { - double radiusSq = radius * radius; - return world(world).filter((p) -> p.squaredDistanceTo(vector) <= radiusSq); - } - - public static Stream around(World world, BlockPos pos, double radius) { - double radiusSq = radius * radius; - return world(world).filter((p) -> p.squaredDistanceTo(pos.getX(), pos.getY(), pos.getZ()) <= radiusSq); - } -} diff --git a/deprecated/fabric-networking-v0/src/main/java/net/fabricmc/fabric/impl/networking/v0/OldNetworkingHooks.java b/deprecated/fabric-networking-v0/src/main/java/net/fabricmc/fabric/impl/networking/v0/OldNetworkingHooks.java deleted file mode 100644 index e1cf45e058..0000000000 --- a/deprecated/fabric-networking-v0/src/main/java/net/fabricmc/fabric/impl/networking/v0/OldNetworkingHooks.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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 net.fabricmc.fabric.impl.networking.v0; - -import net.fabricmc.api.ModInitializer; -import net.fabricmc.fabric.api.event.network.C2SPacketTypeCallback; -import net.fabricmc.fabric.api.networking.v1.S2CPlayChannelEvents; - -public final class OldNetworkingHooks implements ModInitializer { - @Override - public void onInitialize() { - // Must be lambdas below - S2CPlayChannelEvents.REGISTER.register((handler, server, sender, channels) -> { - C2SPacketTypeCallback.REGISTERED.invoker().accept(handler.player, channels); - }); - S2CPlayChannelEvents.UNREGISTER.register((handler, server, sender, channels) -> { - C2SPacketTypeCallback.UNREGISTERED.invoker().accept(handler.player, channels); - }); - } -} diff --git a/deprecated/fabric-networking-v0/src/main/java/net/fabricmc/fabric/impl/networking/v0/ServerSidePacketRegistryImpl.java b/deprecated/fabric-networking-v0/src/main/java/net/fabricmc/fabric/impl/networking/v0/ServerSidePacketRegistryImpl.java deleted file mode 100644 index 0265a0d044..0000000000 --- a/deprecated/fabric-networking-v0/src/main/java/net/fabricmc/fabric/impl/networking/v0/ServerSidePacketRegistryImpl.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * 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 net.fabricmc.fabric.impl.networking.v0; - -import java.util.Objects; - -import io.netty.util.concurrent.Future; -import io.netty.util.concurrent.GenericFutureListener; - -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.network.packet.Packet; -import net.minecraft.network.PacketByteBuf; -import net.minecraft.network.packet.s2c.play.CustomPayloadS2CPacket; -import net.minecraft.server.network.ServerPlayerEntity; -import net.minecraft.util.Identifier; -import net.minecraft.util.thread.ThreadExecutor; - -import net.fabricmc.api.EnvType; -import net.fabricmc.fabric.api.network.PacketConsumer; -import net.fabricmc.fabric.api.network.PacketContext; -import net.fabricmc.fabric.api.network.PacketRegistry; -import net.fabricmc.fabric.api.network.ServerSidePacketRegistry; -import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; -import net.fabricmc.fabric.impl.networking.GenericFutureListenerHolder; - -public class ServerSidePacketRegistryImpl implements ServerSidePacketRegistry, PacketRegistry { - @Override - public boolean canPlayerReceive(PlayerEntity player, Identifier id) { - if (player instanceof ServerPlayerEntity) { - return ServerPlayNetworking.canSend((ServerPlayerEntity) player, id); - } - - return false; - } - - @Override - public void sendToPlayer(PlayerEntity player, Packet packet, GenericFutureListener> completionListener) { - if (player instanceof ServerPlayerEntity) { - ((ServerPlayerEntity) player).networkHandler.sendPacket(packet, GenericFutureListenerHolder.create(completionListener)); - return; - } - - throw new RuntimeException("Can only send to ServerPlayerEntities!"); - } - - @Override - public Packet toPacket(Identifier id, PacketByteBuf buf) { - return new CustomPayloadS2CPacket(id, buf); - } - - @Override - public void register(Identifier id, PacketConsumer consumer) { - Objects.requireNonNull(consumer, "PacketConsumer cannot be null"); - - ServerPlayNetworking.registerGlobalReceiver(id, (server, player, handler, buf, sender) -> { - consumer.accept(new PacketContext() { - @Override - public EnvType getPacketEnvironment() { - return EnvType.SERVER; - } - - @Override - public PlayerEntity getPlayer() { - return player; - } - - @Override - public ThreadExecutor getTaskQueue() { - return server; - } - }, buf); - }); - } - - @Override - public void unregister(Identifier id) { - ServerPlayNetworking.unregisterGlobalReceiver(id); - } -} diff --git a/deprecated/fabric-networking-v0/src/main/resources/fabric.mod.json b/deprecated/fabric-networking-v0/src/main/resources/fabric.mod.json deleted file mode 100644 index 65b755fe18..0000000000 --- a/deprecated/fabric-networking-v0/src/main/resources/fabric.mod.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "schemaVersion": 1, - "id": "fabric-networking-v0", - "name": "Fabric Networking (v0)", - "version": "${version}", - "environment": "*", - "license": "Apache-2.0", - "icon": "assets/fabric-networking-v0/icon.png", - "contact": { - "homepage": "https://fabricmc.net", - "irc": "irc://irc.esper.net:6667/fabric", - "issues": "https://github.com/FabricMC/fabric/issues", - "sources": "https://github.com/FabricMC/fabric" - }, - "authors": [ - "FabricMC" - ], - "entrypoints": { - "main": [ - "net.fabricmc.fabric.impl.networking.v0.OldNetworkingHooks" - ], - "client": [ - "net.fabricmc.fabric.impl.client.networking.v0.OldClientNetworkingHooks" - ] - }, - "depends": { - "fabricloader": ">=0.4.0", - "fabric-api-base": "*", - "fabric-networking-api-v1": "*" - }, - "description": "Legacy Networking packet hooks and registries, superseded by fabric-networking-api-v1.", - "custom": { - "fabric-api:module-lifecycle": "deprecated" - } -} diff --git a/deprecated/fabric-renderer-registries-v1/build.gradle b/deprecated/fabric-renderer-registries-v1/build.gradle index 4e44b71157..a4911f2414 100644 --- a/deprecated/fabric-renderer-registries-v1/build.gradle +++ b/deprecated/fabric-renderer-registries-v1/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-renderer-registries-v1" version = getSubprojectVersion(project) moduleDependencies(project, [ diff --git a/deprecated/fabric-rendering-data-attachment-v1/build.gradle b/deprecated/fabric-rendering-data-attachment-v1/build.gradle new file mode 100644 index 0000000000..d13b85ea09 --- /dev/null +++ b/deprecated/fabric-rendering-data-attachment-v1/build.gradle @@ -0,0 +1,3 @@ +version = getSubprojectVersion(project) + +moduleDependencies(project, ['fabric-block-view-api-v2']) diff --git a/fabric-rendering-data-attachment-v1/src/client/java/net/fabricmc/fabric/mixin/rendering/data/attachment/client/ChunkRendererRegionMixin.java b/deprecated/fabric-rendering-data-attachment-v1/src/client/java/net/fabricmc/fabric/mixin/rendering/data/client/ChunkRendererRegionMixin.java similarity index 54% rename from fabric-rendering-data-attachment-v1/src/client/java/net/fabricmc/fabric/mixin/rendering/data/attachment/client/ChunkRendererRegionMixin.java rename to deprecated/fabric-rendering-data-attachment-v1/src/client/java/net/fabricmc/fabric/mixin/rendering/data/client/ChunkRendererRegionMixin.java index 71a1b49864..8a7c5b521d 100644 --- a/fabric-rendering-data-attachment-v1/src/client/java/net/fabricmc/fabric/mixin/rendering/data/attachment/client/ChunkRendererRegionMixin.java +++ b/deprecated/fabric-rendering-data-attachment-v1/src/client/java/net/fabricmc/fabric/mixin/rendering/data/client/ChunkRendererRegionMixin.java @@ -14,29 +14,21 @@ * limitations under the License. */ -package net.fabricmc.fabric.mixin.rendering.data.attachment.client; +package net.fabricmc.fabric.mixin.rendering.data.client; -import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import org.spongepowered.asm.mixin.Mixin; import net.minecraft.client.render.chunk.ChunkRendererRegion; -import net.minecraft.util.math.BlockPos; +import net.minecraft.world.WorldView; import net.fabricmc.fabric.api.rendering.data.v1.RenderAttachedBlockView; -import net.fabricmc.fabric.impl.rendering.data.attachment.RenderDataObjectConsumer; +/** + * Since {@link RenderAttachedBlockView} is only automatically implemented on {@link WorldView} instances and + * {@link ChunkRendererRegion} does not implement {@link WorldView}, this mixin manually implements + * {@link RenderAttachedBlockView} on {@link ChunkRendererRegion}. The BlockView API v2 implementation ensures + * that all default method implementations of {@link RenderAttachedBlockView} work here automatically. + */ @Mixin(ChunkRendererRegion.class) -public abstract class ChunkRendererRegionMixin implements RenderAttachedBlockView, RenderDataObjectConsumer { - private Long2ObjectOpenHashMap fabric_renderDataObjects; - - @Override - public Object getBlockEntityRenderAttachment(BlockPos pos) { - return fabric_renderDataObjects == null ? null : fabric_renderDataObjects.get(pos.asLong()); - } - - // Called in MixinChunkRendererRegionBuilder - @Override - public void fabric_acceptRenderDataObjects(Long2ObjectOpenHashMap renderDataObjects) { - this.fabric_renderDataObjects = renderDataObjects; - } +public abstract class ChunkRendererRegionMixin implements RenderAttachedBlockView { } diff --git a/deprecated/fabric-rendering-data-attachment-v1/src/client/resources/fabric-rendering-data-attachment-v1.client.mixins.json b/deprecated/fabric-rendering-data-attachment-v1/src/client/resources/fabric-rendering-data-attachment-v1.client.mixins.json new file mode 100644 index 0000000000..0eb7357bd1 --- /dev/null +++ b/deprecated/fabric-rendering-data-attachment-v1/src/client/resources/fabric-rendering-data-attachment-v1.client.mixins.json @@ -0,0 +1,11 @@ +{ + "required": true, + "package": "net.fabricmc.fabric.mixin.rendering.data.client", + "compatibilityLevel": "JAVA_17", + "client": [ + "ChunkRendererRegionMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/deprecated/fabric-rendering-data-attachment-v1/src/main/java/net/fabricmc/fabric/api/rendering/data/v1/RenderAttachedBlockView.java b/deprecated/fabric-rendering-data-attachment-v1/src/main/java/net/fabricmc/fabric/api/rendering/data/v1/RenderAttachedBlockView.java new file mode 100644 index 0000000000..a5214e0f56 --- /dev/null +++ b/deprecated/fabric-rendering-data-attachment-v1/src/main/java/net/fabricmc/fabric/api/rendering/data/v1/RenderAttachedBlockView.java @@ -0,0 +1,46 @@ +/* + * 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 net.fabricmc.fabric.api.rendering.data.v1; + +import org.jetbrains.annotations.Nullable; + +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.BlockRenderView; +import net.minecraft.world.WorldView; + +import net.fabricmc.fabric.api.blockview.v2.FabricBlockView; + +/** + * This interface is guaranteed to be implemented on all {@link WorldView} instances. + * It is likely to be implemented on any given {@link BlockRenderView} instance, but + * this is not guaranteed. + * + * @deprecated Use {@link FabricBlockView} instead. + */ +@Deprecated +public interface RenderAttachedBlockView extends BlockRenderView { + /** + * This method will call {@link FabricBlockView#getBlockEntityRenderData(BlockPos)} by default. + * + * @deprecated Use {@link FabricBlockView#getBlockEntityRenderData(BlockPos)} instead. + */ + @Deprecated + @Nullable + default Object getBlockEntityRenderAttachment(BlockPos pos) { + return getBlockEntityRenderData(pos); + } +} diff --git a/deprecated/fabric-rendering-data-attachment-v1/src/main/java/net/fabricmc/fabric/api/rendering/data/v1/RenderAttachmentBlockEntity.java b/deprecated/fabric-rendering-data-attachment-v1/src/main/java/net/fabricmc/fabric/api/rendering/data/v1/RenderAttachmentBlockEntity.java new file mode 100644 index 0000000000..30b2b1c5c2 --- /dev/null +++ b/deprecated/fabric-rendering-data-attachment-v1/src/main/java/net/fabricmc/fabric/api/rendering/data/v1/RenderAttachmentBlockEntity.java @@ -0,0 +1,41 @@ +/* + * 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 net.fabricmc.fabric.api.rendering.data.v1; + +import org.jetbrains.annotations.Nullable; + +import net.minecraft.block.entity.BlockEntity; + +import net.fabricmc.fabric.api.blockview.v2.RenderDataBlockEntity; + +/** + * This interface is guaranteed to be implemented on all {@link BlockEntity} instances. + * + * @deprecated Use {@link RenderDataBlockEntity} instead. + */ +@Deprecated +@FunctionalInterface +public interface RenderAttachmentBlockEntity { + /** + * This method will be automatically called if {@link RenderDataBlockEntity#getRenderData()} is not overridden. + * + * @deprecated Use {@link RenderDataBlockEntity#getRenderData()} instead. + */ + @Deprecated + @Nullable + Object getRenderAttachmentData(); +} diff --git a/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/simple/client/PillarModelVariantProvider.java b/deprecated/fabric-rendering-data-attachment-v1/src/main/java/net/fabricmc/fabric/mixin/rendering/data/BlockEntityMixin.java similarity index 51% rename from fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/simple/client/PillarModelVariantProvider.java rename to deprecated/fabric-rendering-data-attachment-v1/src/main/java/net/fabricmc/fabric/mixin/rendering/data/BlockEntityMixin.java index 8fbd970551..c82fa30422 100644 --- a/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/simple/client/PillarModelVariantProvider.java +++ b/deprecated/fabric-rendering-data-attachment-v1/src/main/java/net/fabricmc/fabric/mixin/rendering/data/BlockEntityMixin.java @@ -14,25 +14,30 @@ * limitations under the License. */ -package net.fabricmc.fabric.test.renderer.simple.client; +package net.fabricmc.fabric.mixin.rendering.data; import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; -import net.minecraft.client.render.model.UnbakedModel; -import net.minecraft.client.util.ModelIdentifier; +import net.minecraft.block.entity.BlockEntity; -import net.fabricmc.fabric.api.client.model.ModelProviderContext; -import net.fabricmc.fabric.api.client.model.ModelVariantProvider; -import net.fabricmc.fabric.test.renderer.simple.RendererTest; +import net.fabricmc.fabric.api.blockview.v2.RenderDataBlockEntity; +import net.fabricmc.fabric.api.rendering.data.v1.RenderAttachmentBlockEntity; -public class PillarModelVariantProvider implements ModelVariantProvider { +@Mixin(BlockEntity.class) +public class BlockEntityMixin implements RenderAttachmentBlockEntity, RenderDataBlockEntity { @Override @Nullable - public UnbakedModel loadModelVariant(ModelIdentifier modelId, ModelProviderContext context) { - if (RendererTest.PILLAR_ID.equals(modelId)) { - return new PillarUnbakedModel(); - } else { - return null; - } + public Object getRenderAttachmentData() { + return null; + } + + /** + * Instead of returning null by default in v2, proxy to v1 method instead. + */ + @Override + @Nullable + public Object getRenderData() { + return getRenderAttachmentData(); } } diff --git a/fabric-rendering-data-attachment-v1/src/main/java/net/fabricmc/fabric/mixin/rendering/data/attachment/WorldViewMixin.java b/deprecated/fabric-rendering-data-attachment-v1/src/main/java/net/fabricmc/fabric/mixin/rendering/data/WorldViewMixin.java similarity index 82% rename from fabric-rendering-data-attachment-v1/src/main/java/net/fabricmc/fabric/mixin/rendering/data/attachment/WorldViewMixin.java rename to deprecated/fabric-rendering-data-attachment-v1/src/main/java/net/fabricmc/fabric/mixin/rendering/data/WorldViewMixin.java index 317cc7df8a..bbd04fcfdc 100644 --- a/fabric-rendering-data-attachment-v1/src/main/java/net/fabricmc/fabric/mixin/rendering/data/attachment/WorldViewMixin.java +++ b/deprecated/fabric-rendering-data-attachment-v1/src/main/java/net/fabricmc/fabric/mixin/rendering/data/WorldViewMixin.java @@ -14,15 +14,14 @@ * limitations under the License. */ -package net.fabricmc.fabric.mixin.rendering.data.attachment; +package net.fabricmc.fabric.mixin.rendering.data; import org.spongepowered.asm.mixin.Mixin; -import net.minecraft.world.BlockRenderView; import net.minecraft.world.WorldView; import net.fabricmc.fabric.api.rendering.data.v1.RenderAttachedBlockView; -/** Make {@link BlockRenderView} implement {@link RenderAttachedBlockView}. */ @Mixin(WorldView.class) -public interface WorldViewMixin extends RenderAttachedBlockView { } +public interface WorldViewMixin extends RenderAttachedBlockView { +} diff --git a/deprecated/fabric-networking-v0/src/main/resources/assets/fabric-networking-v0/icon.png b/deprecated/fabric-rendering-data-attachment-v1/src/main/resources/assets/fabric-rendering-data-attachment-v1/icon.png similarity index 100% rename from deprecated/fabric-networking-v0/src/main/resources/assets/fabric-networking-v0/icon.png rename to deprecated/fabric-rendering-data-attachment-v1/src/main/resources/assets/fabric-rendering-data-attachment-v1/icon.png diff --git a/fabric-rendering-data-attachment-v1/src/main/resources/fabric-rendering-data-attachment-v1.mixins.json b/deprecated/fabric-rendering-data-attachment-v1/src/main/resources/fabric-rendering-data-attachment-v1.mixins.json similarity index 56% rename from fabric-rendering-data-attachment-v1/src/main/resources/fabric-rendering-data-attachment-v1.mixins.json rename to deprecated/fabric-rendering-data-attachment-v1/src/main/resources/fabric-rendering-data-attachment-v1.mixins.json index 3e4c81bc9d..9c09ede3c4 100644 --- a/fabric-rendering-data-attachment-v1/src/main/resources/fabric-rendering-data-attachment-v1.mixins.json +++ b/deprecated/fabric-rendering-data-attachment-v1/src/main/resources/fabric-rendering-data-attachment-v1.mixins.json @@ -1,7 +1,7 @@ { "required": true, - "package": "net.fabricmc.fabric.mixin.rendering.data.attachment", - "compatibilityLevel": "JAVA_16", + "package": "net.fabricmc.fabric.mixin.rendering.data", + "compatibilityLevel": "JAVA_17", "mixins": [ "BlockEntityMixin", "WorldViewMixin" diff --git a/fabric-rendering-data-attachment-v1/src/main/resources/fabric.mod.json b/deprecated/fabric-rendering-data-attachment-v1/src/main/resources/fabric.mod.json similarity index 85% rename from fabric-rendering-data-attachment-v1/src/main/resources/fabric.mod.json rename to deprecated/fabric-rendering-data-attachment-v1/src/main/resources/fabric.mod.json index 03fcd46275..bcd623857b 100644 --- a/fabric-rendering-data-attachment-v1/src/main/resources/fabric.mod.json +++ b/deprecated/fabric-rendering-data-attachment-v1/src/main/resources/fabric.mod.json @@ -17,7 +17,7 @@ ], "depends": { "fabricloader": ">=0.4.0", - "fabric-api-base": "*" + "fabric-block-view-api-v2": "*" }, "description": "Thread-safe hooks for block entity data use during terrain rendering.", "mixins": [ @@ -28,7 +28,6 @@ } ], "custom": { - "fabric-api:module-lifecycle": "stable" - }, - "accessWidener": "fabric-rendering-data-attachment-v1.accesswidener" + "fabric-api:module-lifecycle": "deprecated" + } } diff --git a/deprecated/fabric-rendering-v0/build.gradle b/deprecated/fabric-rendering-v0/build.gradle index a709f8760e..a4911f2414 100644 --- a/deprecated/fabric-rendering-v0/build.gradle +++ b/deprecated/fabric-rendering-v0/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-rendering-v0" version = getSubprojectVersion(project) moduleDependencies(project, [ diff --git a/fabric-api-base/build.gradle b/fabric-api-base/build.gradle index 182a62bb16..fc1eb105c5 100644 --- a/fabric-api-base/build.gradle +++ b/fabric-api-base/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-api-base" version = getSubprojectVersion(project) testDependencies(project, [ diff --git a/fabric-api-base/src/main/java/net/fabricmc/fabric/impl/base/event/ArrayBackedEvent.java b/fabric-api-base/src/main/java/net/fabricmc/fabric/impl/base/event/ArrayBackedEvent.java index 01f1c725ca..d8d8699cda 100644 --- a/fabric-api-base/src/main/java/net/fabricmc/fabric/impl/base/event/ArrayBackedEvent.java +++ b/fabric-api-base/src/main/java/net/fabricmc/fabric/impl/base/event/ArrayBackedEvent.java @@ -18,22 +18,19 @@ import java.lang.reflect.Array; import java.util.ArrayList; +import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.function.Function; -import org.slf4j.LoggerFactory; -import org.slf4j.Logger; - import net.minecraft.util.Identifier; import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.impl.base.toposort.NodeSorting; 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; @@ -82,7 +79,7 @@ private EventPhaseData getOrCreatePhase(Identifier id, boolean sortIfCreate) sortedPhases.add(phase); if (sortIfCreate) { - PhaseSorting.sortPhases(sortedPhases); + NodeSorting.sort(sortedPhases, "event phases", Comparator.comparing(data -> data.id)); } } @@ -121,9 +118,8 @@ public void addPhaseOrdering(Identifier firstPhase, Identifier secondPhase) { synchronized (lock) { EventPhaseData first = getOrCreatePhase(firstPhase, false); EventPhaseData second = getOrCreatePhase(secondPhase, false); - first.subsequentPhases.add(second); - second.previousPhases.add(first); - PhaseSorting.sortPhases(this.sortedPhases); + EventPhaseData.link(first, second); + NodeSorting.sort(this.sortedPhases, "event phases", Comparator.comparing(data -> data.id)); rebuildInvoker(handlers.length); } } diff --git a/fabric-api-base/src/main/java/net/fabricmc/fabric/impl/base/event/EventPhaseData.java b/fabric-api-base/src/main/java/net/fabricmc/fabric/impl/base/event/EventPhaseData.java index f13744a19d..81dbebbef2 100644 --- a/fabric-api-base/src/main/java/net/fabricmc/fabric/impl/base/event/EventPhaseData.java +++ b/fabric-api-base/src/main/java/net/fabricmc/fabric/impl/base/event/EventPhaseData.java @@ -17,21 +17,18 @@ package net.fabricmc.fabric.impl.base.event; import java.lang.reflect.Array; -import java.util.ArrayList; import java.util.Arrays; -import java.util.List; import net.minecraft.util.Identifier; +import net.fabricmc.fabric.impl.base.toposort.SortableNode; + /** * Data of an {@link ArrayBackedEvent} phase. */ -class EventPhaseData { +class EventPhaseData extends SortableNode> { final Identifier 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(Identifier id, Class listenerClass) { @@ -44,4 +41,9 @@ void addListener(T listener) { listeners = Arrays.copyOf(listeners, oldLength + 1); listeners[oldLength] = listener; } + + @Override + protected String getDescription() { + return id.toString(); + } } diff --git a/fabric-api-base/src/main/java/net/fabricmc/fabric/impl/base/event/PhaseSorting.java b/fabric-api-base/src/main/java/net/fabricmc/fabric/impl/base/event/PhaseSorting.java deleted file mode 100644 index c998c44bf9..0000000000 --- a/fabric-api-base/src/main/java/net/fabricmc/fabric/impl/base/event/PhaseSorting.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * 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 net.fabricmc.fabric.impl.base.event; - -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/fabric-api-base/src/main/java/net/fabricmc/fabric/impl/base/toposort/NodeSorting.java b/fabric-api-base/src/main/java/net/fabricmc/fabric/impl/base/toposort/NodeSorting.java new file mode 100644 index 0000000000..8d59594e4a --- /dev/null +++ b/fabric-api-base/src/main/java/net/fabricmc/fabric/impl/base/toposort/NodeSorting.java @@ -0,0 +1,190 @@ +/* + * 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 net.fabricmc.fabric.impl.base.toposort; + +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; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Contains a topological sort implementation, with tie breaking using a {@link Comparator}. + * + *

The final order is always deterministic (i.e. doesn't change with the order of the input elements or the edges), + * assuming that they are all different according to the comparator. This also holds in the presence of cycles. + * + *

The steps are as follows: + *

    + *
  1. Compute node SCCs (Strongly Connected Components, i.e. cycles).
  2. + *
  3. Sort nodes within SCCs using the comparator.
  4. + *
  5. Sort SCCs with respect to each other by respecting constraints, and using the comparator in case of a tie.
  6. + *
+ */ +public class NodeSorting { + private static final Logger LOGGER = LoggerFactory.getLogger("fabric-api-base"); + + @VisibleForTesting + public static boolean ENABLE_CYCLE_WARNING = true; + + /** + * Sort a list of nodes. + * + * @param sortedNodes The list of nodes to sort. Will be modified in-place. + * @param elementDescription A description of the elements, used for logging in the presence of cycles. + * @param comparator The comparator to break ties and to order elements within a cycle. + * @return {@code true} if all the constraints were satisfied, {@code false} if there was at least one cycle. + */ + public static > boolean sort(List sortedNodes, String elementDescription, Comparator comparator) { + // FIRST KOSARAJU SCC VISIT + List toposort = new ArrayList<>(sortedNodes.size()); + + for (N node : sortedNodes) { + forwardVisit(node, null, toposort); + } + + clearStatus(toposort); + Collections.reverse(toposort); + + // SECOND KOSARAJU SCC VISIT + Map> nodeToScc = new IdentityHashMap<>(); + + for (N node : toposort) { + if (!node.visited) { + List sccNodes = new ArrayList<>(); + // Collect nodes in SCC. + backwardVisit(node, sccNodes); + // Sort nodes by id. + sccNodes.sort(comparator); + // Mark nodes as belonging to this SCC. + NodeScc scc = new NodeScc<>(sccNodes); + + for (N nodeInScc : sccNodes) { + nodeToScc.put(nodeInScc, scc); + } + } + } + + clearStatus(toposort); + + // Build SCC graph + for (NodeScc scc : nodeToScc.values()) { + for (N node : scc.nodes) { + for (N subsequentNode : node.subsequentNodes) { + NodeScc subsequentScc = nodeToScc.get(subsequentNode); + + 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.nodes.get(0), comparator)); + sortedNodes.clear(); + + for (NodeScc scc : nodeToScc.values()) { + if (scc.inDegree == 0) { + pq.add(scc); + // Prevent adding the same SCC multiple times, as nodeToScc may contain the same value multiple times. + scc.inDegree = -1; + } + } + + boolean noCycle = true; + + while (!pq.isEmpty()) { + NodeScc scc = pq.poll(); + sortedNodes.addAll(scc.nodes); + + if (scc.nodes.size() > 1) { + noCycle = false; + + if (ENABLE_CYCLE_WARNING) { + // Print cycle warning + StringBuilder builder = new StringBuilder(); + builder.append("Found cycle while sorting ").append(elementDescription).append(":\n"); + + for (N node : scc.nodes) { + builder.append("\t").append(node.getDescription()).append("\n"); + } + + LOGGER.warn(builder.toString()); + } + } + + for (NodeScc subsequentScc : scc.subsequentSccs) { + subsequentScc.inDegree--; + + if (subsequentScc.inDegree == 0) { + pq.add(subsequentScc); + } + } + } + + return noCycle; + } + + private static > void forwardVisit(N node, N parent, List toposort) { + if (!node.visited) { + // Not yet visited. + node.visited = true; + + for (N data : node.subsequentNodes) { + forwardVisit(data, node, toposort); + } + + toposort.add(node); + } + } + + private static > void clearStatus(List nodes) { + for (N node : nodes) { + node.visited = false; + } + } + + private static > void backwardVisit(N node, List sccNodes) { + if (!node.visited) { + node.visited = true; + sccNodes.add(node); + + for (N data : node.previousNodes) { + backwardVisit(data, sccNodes); + } + } + } + + private static class NodeScc> { + final List nodes; + final List> subsequentSccs = new ArrayList<>(); + int inDegree = 0; + + private NodeScc(List nodes) { + this.nodes = nodes; + } + } +} diff --git a/fabric-api-base/src/main/java/net/fabricmc/fabric/impl/base/toposort/SortableNode.java b/fabric-api-base/src/main/java/net/fabricmc/fabric/impl/base/toposort/SortableNode.java new file mode 100644 index 0000000000..a420914be4 --- /dev/null +++ b/fabric-api-base/src/main/java/net/fabricmc/fabric/impl/base/toposort/SortableNode.java @@ -0,0 +1,40 @@ +/* + * 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 net.fabricmc.fabric.impl.base.toposort; + +import java.util.ArrayList; +import java.util.List; + +public abstract class SortableNode> { + final List subsequentNodes = new ArrayList<>(); + final List previousNodes = new ArrayList<>(); + boolean visited = false; + + /** + * @return Description of this node, used to print the cycle warning. + */ + protected abstract String getDescription(); + + public static > void link(N first, N second) { + if (first == second) { + throw new IllegalArgumentException("Cannot link a node to itself!"); + } + + first.subsequentNodes.add(second); + second.previousNodes.add(first); + } +} diff --git a/fabric-api-base/src/testmod/java/net/fabricmc/fabric/test/base/EventTests.java b/fabric-api-base/src/testmod/java/net/fabricmc/fabric/test/base/EventTests.java index 28b016058b..e808cf2015 100644 --- a/fabric-api-base/src/testmod/java/net/fabricmc/fabric/test/base/EventTests.java +++ b/fabric-api-base/src/testmod/java/net/fabricmc/fabric/test/base/EventTests.java @@ -29,7 +29,7 @@ import net.fabricmc.fabric.api.event.Event; import net.fabricmc.fabric.api.event.EventFactory; -import net.fabricmc.fabric.impl.base.event.PhaseSorting; +import net.fabricmc.fabric.impl.base.toposort.NodeSorting; public class EventTests { private static final Logger LOGGER = LoggerFactory.getLogger("fabric-api-base"); @@ -41,10 +41,10 @@ public static void run() { testMultipleDefaultPhases(); testAddedPhases(); testCycle(); - PhaseSorting.ENABLE_CYCLE_WARNING = false; + NodeSorting.ENABLE_CYCLE_WARNING = false; testDeterministicOrdering(); testTwoCycles(); - PhaseSorting.ENABLE_CYCLE_WARNING = true; + NodeSorting.ENABLE_CYCLE_WARNING = true; long time2 = System.currentTimeMillis(); LOGGER.info("Event unit tests succeeded in {} milliseconds.", time2 - time1); diff --git a/fabric-api-base/src/testmodClient/java/net/fabricmc/fabric/test/base/client/FabricClientTestHelper.java b/fabric-api-base/src/testmodClient/java/net/fabricmc/fabric/test/base/client/FabricClientTestHelper.java index 2d944948ac..39485c96c5 100644 --- a/fabric-api-base/src/testmodClient/java/net/fabricmc/fabric/test/base/client/FabricClientTestHelper.java +++ b/fabric-api-base/src/testmodClient/java/net/fabricmc/fabric/test/base/client/FabricClientTestHelper.java @@ -143,7 +143,7 @@ public static void waitForWorldTicks(long ticks) { public static void enableDebugHud() { submitAndWait(client -> { - client.options.debugEnabled = true; + client.inGameHud.getDebugHud().toggleDebugHud(); return null; }); } diff --git a/fabric-api-lookup-api-v1/build.gradle b/fabric-api-lookup-api-v1/build.gradle index e842382f06..10e724d47f 100644 --- a/fabric-api-lookup-api-v1/build.gradle +++ b/fabric-api-lookup-api-v1/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-api-lookup-api-v1" version = getSubprojectVersion(project) moduleDependencies(project, [ diff --git a/fabric-api-lookup-api-v1/src/testmod/java/net/fabricmc/fabric/test/lookup/ChuteBlock.java b/fabric-api-lookup-api-v1/src/testmod/java/net/fabricmc/fabric/test/lookup/ChuteBlock.java index b9fdd3718d..c1c1b5b190 100644 --- a/fabric-api-lookup-api-v1/src/testmod/java/net/fabricmc/fabric/test/lookup/ChuteBlock.java +++ b/fabric-api-lookup-api-v1/src/testmod/java/net/fabricmc/fabric/test/lookup/ChuteBlock.java @@ -40,6 +40,6 @@ public BlockEntity createBlockEntity(BlockPos pos, BlockState state) { @Nullable @Override public BlockEntityTicker getTicker(World world, BlockState state, BlockEntityType type) { - return world.isClient ? null : checkType(type, FabricApiLookupTest.CHUTE_BLOCK_ENTITY_TYPE, ChuteBlockEntity::serverTick); + return world.isClient ? null : validateTicker(type, FabricApiLookupTest.CHUTE_BLOCK_ENTITY_TYPE, ChuteBlockEntity::serverTick); } } diff --git a/fabric-biome-api-v1/build.gradle b/fabric-biome-api-v1/build.gradle index 5012031859..0c086498cb 100644 --- a/fabric-biome-api-v1/build.gradle +++ b/fabric-biome-api-v1/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-biome-api-v1" version = getSubprojectVersion(project) loom { diff --git a/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/api/biome/v1/BiomeModification.java b/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/api/biome/v1/BiomeModification.java index fd9482201f..846f2dbc09 100644 --- a/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/api/biome/v1/BiomeModification.java +++ b/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/api/biome/v1/BiomeModification.java @@ -27,7 +27,8 @@ import net.fabricmc.fabric.impl.biome.modification.BiomeModificationImpl; /** - * Experimental feature, may be removed or changed without further notice. + * Provides methods for modifying biomes. To create an instance, call + * {@link BiomeModifications#create(Identifier)}. * * @see BiomeModifications */ diff --git a/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/api/biome/v1/BiomeModificationContext.java b/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/api/biome/v1/BiomeModificationContext.java index 4df866ef9f..34cdde5c08 100644 --- a/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/api/biome/v1/BiomeModificationContext.java +++ b/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/api/biome/v1/BiomeModificationContext.java @@ -40,8 +40,6 @@ /** * Allows {@link Biome} properties to be modified. - * - *

Experimental feature, may be removed or changed without further notice. */ public interface BiomeModificationContext { /** @@ -66,8 +64,8 @@ public interface BiomeModificationContext { interface WeatherContext { /** - * @see Biome#getPrecipitation() - * @see Biome.Builder#precipitation(Biome.Precipitation) + * @see Biome#hasPrecipitation() + * @see Biome.Builder#precipitation(boolean) */ void setPrecipitation(boolean hasPrecipitation); @@ -83,7 +81,7 @@ interface WeatherContext { void setTemperatureModifier(Biome.TemperatureModifier temperatureModifier); /** - * @see Biome#getDownfall() + * @see Biome.Weather#downfall() * @see Biome.Builder#downfall(float) */ void setDownfall(float downfall); diff --git a/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/api/biome/v1/BiomeModifications.java b/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/api/biome/v1/BiomeModifications.java index d1d2589733..fd0e243d33 100644 --- a/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/api/biome/v1/BiomeModifications.java +++ b/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/api/biome/v1/BiomeModifications.java @@ -33,10 +33,7 @@ /** * Provides an API to modify Biomes after they have been loaded and before they are used in the World. * - *

Any modifications made to biomes will not be available for use in server.properties (as of 1.16.1), - * or the demo level. - * - *

Experimental feature, may be removed or changed without further notice. + *

Any modifications made to biomes will not be available for use in the demo level. */ public final class BiomeModifications { private BiomeModifications() { @@ -87,7 +84,7 @@ public static void addSpawn(Predicate biomeSelector, } /** - * Create a new biome modification which will be applied whenever biomes are loaded from data packs. + * Creates a new biome modification which will be applied whenever biomes are loaded from data packs. * * @param id An identifier for the new set of biome modifications that is returned. Is used for * guaranteeing consistent ordering between the biome modifications added by different mods diff --git a/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/api/biome/v1/BiomeSelectors.java b/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/api/biome/v1/BiomeSelectors.java index b8b92c1aea..6da5235af7 100644 --- a/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/api/biome/v1/BiomeSelectors.java +++ b/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/api/biome/v1/BiomeSelectors.java @@ -34,8 +34,6 @@ /** * Provides several convenient biome selectors that can be used with {@link BiomeModifications}. - * - *

Experimental feature, may be removed or changed without further notice. */ public final class BiomeSelectors { private BiomeSelectors() { diff --git a/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/api/biome/v1/ModificationPhase.java b/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/api/biome/v1/ModificationPhase.java index 1f4d3ae1a7..9e227f1380 100644 --- a/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/api/biome/v1/ModificationPhase.java +++ b/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/api/biome/v1/ModificationPhase.java @@ -27,8 +27,6 @@ *

  • Replacements (removal + add) in biomes
  • *
  • Generic post-processing of biomes
  • * - * - *

    Experimental feature, may be removed or changed without further notice. */ public enum ModificationPhase { /** diff --git a/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/api/biome/v1/NetherBiomes.java b/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/api/biome/v1/NetherBiomes.java index d76c7cf3bb..b5fabc239c 100644 --- a/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/api/biome/v1/NetherBiomes.java +++ b/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/api/biome/v1/NetherBiomes.java @@ -24,8 +24,6 @@ /** * API that exposes the internals of Minecraft's nether biome code. - * - *

    Experimental feature, may be removed or changed without further notice. */ public final class NetherBiomes { private NetherBiomes() { diff --git a/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/api/biome/v1/TheEndBiomes.java b/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/api/biome/v1/TheEndBiomes.java index d020a9a745..8a69dfdf6e 100644 --- a/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/api/biome/v1/TheEndBiomes.java +++ b/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/api/biome/v1/TheEndBiomes.java @@ -25,9 +25,6 @@ /** * API that exposes some internals of the minecraft default biome source for The End. * - *

    Experimental feature, may be removed or changed without further notice. - * Because of the volatility of world generation in Minecraft 1.16, this API is marked experimental - * since it is likely to change in future Minecraft versions. */ public final class TheEndBiomes { private TheEndBiomes() { diff --git a/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/mixin/biome/BiomeSourceMixin.java b/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/mixin/biome/BiomeSourceMixin.java index 546d5b2cf6..117d2dca5e 100644 --- a/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/mixin/biome/BiomeSourceMixin.java +++ b/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/mixin/biome/BiomeSourceMixin.java @@ -16,8 +16,6 @@ package net.fabricmc.fabric.mixin.biome; -import java.util.Collections; -import java.util.HashSet; import java.util.Set; import java.util.function.Supplier; @@ -33,11 +31,10 @@ public class BiomeSourceMixin { @Redirect(method = "getBiomes", at = @At(value = "INVOKE", target = "Ljava/util/function/Supplier;get()Ljava/lang/Object;")) private Object getBiomes(Supplier>> instance) { - var biomes = new HashSet<>(instance.get()); - fabric_modifyBiomeSet(biomes); - return Collections.unmodifiableSet(biomes); + return fabric_modifyBiomeSet(instance.get()); } - protected void fabric_modifyBiomeSet(Set> biomes) { + protected Set> fabric_modifyBiomeSet(Set> biomes) { + return biomes; } } diff --git a/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/mixin/biome/TheEndBiomeSourceMixin.java b/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/mixin/biome/TheEndBiomeSourceMixin.java index 3d156f4c6e..09ee624240 100644 --- a/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/mixin/biome/TheEndBiomeSourceMixin.java +++ b/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/mixin/biome/TheEndBiomeSourceMixin.java @@ -16,6 +16,8 @@ package net.fabricmc.fabric.mixin.biome; +import java.util.Collections; +import java.util.LinkedHashSet; import java.util.Set; import java.util.function.Supplier; @@ -108,14 +110,18 @@ private void getWeightedEndBiome(int biomeX, int biomeY, int biomeZ, MultiNoiseU } @Override - protected void fabric_modifyBiomeSet(Set> biomes) { + protected Set> fabric_modifyBiomeSet(Set> biomes) { if (!hasCheckedForModifiedSet) { hasCheckedForModifiedSet = true; biomeSetModified = !overrides.get().customBiomes.isEmpty(); } if (biomeSetModified) { - biomes.addAll(overrides.get().customBiomes); + var modifiedBiomes = new LinkedHashSet<>(biomes); + modifiedBiomes.addAll(overrides.get().customBiomes); + return Collections.unmodifiableSet(modifiedBiomes); } + + return biomes; } } diff --git a/fabric-biome-api-v1/src/main/resources/fabric.mod.json b/fabric-biome-api-v1/src/main/resources/fabric.mod.json index 21c70d68f8..d73bb37296 100644 --- a/fabric-biome-api-v1/src/main/resources/fabric.mod.json +++ b/fabric-biome-api-v1/src/main/resources/fabric.mod.json @@ -25,6 +25,6 @@ ], "accessWidener" : "fabric-biome-api-v1.accesswidener", "custom": { - "fabric-api:module-lifecycle": "experimental" + "fabric-api:module-lifecycle": "stable" } } diff --git a/fabric-biome-api-v1/src/testmod/generated/data/fabric-biome-api-v1-testmod/worldgen/biome/custom_plains.json b/fabric-biome-api-v1/src/testmod/generated/data/fabric-biome-api-v1-testmod/worldgen/biome/custom_plains.json index 4c41954577..a223a71660 100644 --- a/fabric-biome-api-v1/src/testmod/generated/data/fabric-biome-api-v1-testmod/worldgen/biome/custom_plains.json +++ b/fabric-biome-api-v1/src/testmod/generated/data/fabric-biome-api-v1-testmod/worldgen/biome/custom_plains.json @@ -54,6 +54,7 @@ "minecraft:ore_redstone", "minecraft:ore_redstone_lower", "minecraft:ore_diamond", + "minecraft:ore_diamond_medium", "minecraft:ore_diamond_large", "minecraft:ore_diamond_buried", "minecraft:ore_lapis", diff --git a/fabric-block-api-v1/build.gradle b/fabric-block-api-v1/build.gradle index 0299872be2..85b5e378d4 100644 --- a/fabric-block-api-v1/build.gradle +++ b/fabric-block-api-v1/build.gradle @@ -1,2 +1 @@ -archivesBaseName = "fabric-block-api-v1" version = getSubprojectVersion(project) diff --git a/fabric-block-api-v1/src/main/java/net/fabricmc/fabric/api/block/v1/FabricBlock.java b/fabric-block-api-v1/src/main/java/net/fabricmc/fabric/api/block/v1/FabricBlock.java index 5240b08f44..6304c434a5 100644 --- a/fabric-block-api-v1/src/main/java/net/fabricmc/fabric/api/block/v1/FabricBlock.java +++ b/fabric-block-api-v1/src/main/java/net/fabricmc/fabric/api/block/v1/FabricBlock.java @@ -56,11 +56,12 @@ public interface FabricBlock { *

    This can be called on the server, where block entity data can be safely accessed, * and on the client, possibly in a meshing thread, where block entity data is not safe to access! * Here is an example of how data from a block entity can be handled safely. - * The block entity needs to implement {@code RenderAttachmentBlockEntity} for this to work. + * The block entity should override {@code RenderDataBlockEntity#getBlockEntityRenderData} to return + * the necessary data. Refer to the documentation of {@code RenderDataBlockEntity} for more information. *

    {@code @Override
     	 * public BlockState getAppearance(BlockState state, BlockRenderView renderView, BlockPos pos, Direction side, @Nullable BlockState sourceState, @Nullable BlockPos sourcePos) {
     	 *     if (renderView instanceof ServerWorld serverWorld) {
    -	 *         // Server side, ok to use block entity directly!
    +	 *         // Server side; ok to use block entity directly!
     	 *         BlockEntity blockEntity = serverWorld.getBlockEntity(pos);
     	 *
     	 *         if (blockEntity instanceof ...) {
    @@ -68,9 +69,8 @@ public interface FabricBlock {
     	 *             return ...;
     	 *         }
     	 *     } else {
    -	 *         // Client side, need to use the render attachment!
    -	 *         RenderAttachedBlockView attachmentView = (RenderAttachedBlockView) renderView;
    -	 *         Object data = attachmentView.getBlockEntityRenderAttachment(pos);
    +	 *         // Client side; need to use the block entity render data!
    +	 *         Object data = renderView.getBlockEntityRenderData(pos);
     	 *
     	 *         // Check if data is not null and of the correct type, and use that to determine the appearance
     	 *         if (data instanceof ...) {
    diff --git a/fabric-block-view-api-v2/build.gradle b/fabric-block-view-api-v2/build.gradle
    new file mode 100644
    index 0000000000..84bd602fab
    --- /dev/null
    +++ b/fabric-block-view-api-v2/build.gradle
    @@ -0,0 +1,5 @@
    +version = getSubprojectVersion(project)
    +
    +loom {
    +	accessWidenerPath = file("src/main/resources/fabric-block-view-api-v2.accesswidener")
    +}
    diff --git a/fabric-models-v0/src/client/java/net/fabricmc/fabric/impl/client/model/BakedModelManagerHooks.java b/fabric-block-view-api-v2/src/client/java/net/fabricmc/fabric/impl/blockview/client/RenderDataMapConsumer.java
    similarity index 73%
    rename from fabric-models-v0/src/client/java/net/fabricmc/fabric/impl/client/model/BakedModelManagerHooks.java
    rename to fabric-block-view-api-v2/src/client/java/net/fabricmc/fabric/impl/blockview/client/RenderDataMapConsumer.java
    index b4748ab6f2..b525be8cef 100644
    --- a/fabric-models-v0/src/client/java/net/fabricmc/fabric/impl/client/model/BakedModelManagerHooks.java
    +++ b/fabric-block-view-api-v2/src/client/java/net/fabricmc/fabric/impl/blockview/client/RenderDataMapConsumer.java
    @@ -14,11 +14,10 @@
      * limitations under the License.
      */
     
    -package net.fabricmc.fabric.impl.client.model;
    +package net.fabricmc.fabric.impl.blockview.client;
     
    -import net.minecraft.client.render.model.BakedModel;
    -import net.minecraft.util.Identifier;
    +import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
     
    -public interface BakedModelManagerHooks {
    -	BakedModel fabric_getModel(Identifier id);
    +public interface RenderDataMapConsumer {
    +	void fabric_acceptRenderDataMap(Long2ObjectMap renderDataMap);
     }
    diff --git a/fabric-rendering-data-attachment-v1/src/client/java/net/fabricmc/fabric/mixin/rendering/data/attachment/client/ChunkRendererRegionBuilderMixin.java b/fabric-block-view-api-v2/src/client/java/net/fabricmc/fabric/mixin/blockview/client/ChunkRendererRegionBuilderMixin.java
    similarity index 62%
    rename from fabric-rendering-data-attachment-v1/src/client/java/net/fabricmc/fabric/mixin/rendering/data/attachment/client/ChunkRendererRegionBuilderMixin.java
    rename to fabric-block-view-api-v2/src/client/java/net/fabricmc/fabric/mixin/blockview/client/ChunkRendererRegionBuilderMixin.java
    index eb759c7898..039dfec9ad 100644
    --- a/fabric-rendering-data-attachment-v1/src/client/java/net/fabricmc/fabric/mixin/rendering/data/attachment/client/ChunkRendererRegionBuilderMixin.java
    +++ b/fabric-block-view-api-v2/src/client/java/net/fabricmc/fabric/mixin/blockview/client/ChunkRendererRegionBuilderMixin.java
    @@ -14,61 +14,67 @@
      * limitations under the License.
      */
     
    -package net.fabricmc.fabric.mixin.rendering.data.attachment.client;
    +package net.fabricmc.fabric.mixin.blockview.client;
     
     import java.util.ConcurrentModificationException;
     import java.util.Map;
     import java.util.concurrent.atomic.AtomicInteger;
     
     import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
    -import org.slf4j.LoggerFactory;
     import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
     import org.spongepowered.asm.mixin.Mixin;
    +import org.spongepowered.asm.mixin.Unique;
     import org.spongepowered.asm.mixin.injection.At;
     import org.spongepowered.asm.mixin.injection.Inject;
     import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
     import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
     
     import net.minecraft.block.entity.BlockEntity;
    -import net.minecraft.client.render.chunk.ChunkRendererRegionBuilder;
     import net.minecraft.client.render.chunk.ChunkRendererRegion;
    +import net.minecraft.client.render.chunk.ChunkRendererRegionBuilder;
     import net.minecraft.util.math.BlockPos;
     import net.minecraft.world.World;
     import net.minecraft.world.chunk.WorldChunk;
     
    -import net.fabricmc.fabric.api.rendering.data.v1.RenderAttachmentBlockEntity;
    -import net.fabricmc.fabric.impl.rendering.data.attachment.RenderDataObjectConsumer;
    +import net.fabricmc.fabric.impl.blockview.client.RenderDataMapConsumer;
     
     @Mixin(ChunkRendererRegionBuilder.class)
     public abstract class ChunkRendererRegionBuilderMixin {
     	private static final AtomicInteger ERROR_COUNTER = new AtomicInteger();
     	private static final Logger LOGGER = LoggerFactory.getLogger(ChunkRendererRegionBuilderMixin.class);
     
    -	@Inject(at = @At("RETURN"), method = "build", locals = LocalCapture.CAPTURE_FAILHARD)
    -	private void create(World world, BlockPos startPos, BlockPos endPos, int chunkRadius, CallbackInfoReturnable info, int i, int j, int k, int l, ChunkRendererRegionBuilder.ClientChunk[][] chunkData) {
    +	@Inject(method = "build", at = @At("RETURN"), locals = LocalCapture.CAPTURE_FAILHARD)
    +	private void createDataMap(World world, BlockPos startPos, BlockPos endPos, int offset, CallbackInfoReturnable cir, int startX, int startZ, int endX, int endZ, ChunkRendererRegionBuilder.ClientChunk[][] chunksXZ) {
    +		ChunkRendererRegion rendererRegion = cir.getReturnValue();
    +
    +		if (rendererRegion == null) {
    +			return;
    +		}
    +
     		// instantiated lazily - avoids allocation for chunks without any data objects - which is most of them!
     		Long2ObjectOpenHashMap map = null;
     
    -		for (ChunkRendererRegionBuilder.ClientChunk[] chunkDataOuter : chunkData) {
    -			for (ChunkRendererRegionBuilder.ClientChunk data : chunkDataOuter) {
    +		for (ChunkRendererRegionBuilder.ClientChunk[] chunksZ : chunksXZ) {
    +			for (ChunkRendererRegionBuilder.ClientChunk chunk : chunksZ) {
     				// Hash maps in chunks should generally not be modified outside of client thread
     				// but does happen in practice, due to mods or inconsistent vanilla behaviors, causing
    -				// CMEs when we iterate the map.  (Vanilla does not iterate these maps when it builds
    +				// CMEs when we iterate the map. (Vanilla does not iterate these maps when it builds
     				// the chunk cache and does not suffer from this problem.)
     				//
    -				// We handle this simply by retrying until it works.  Ugly but effective.
    -				for (;;) {
    +				// We handle this simply by retrying until it works. Ugly but effective.
    +				while (true) {
     					try {
    -						map = mapChunk(data.getChunk(), startPos, endPos, map);
    +						map = mapChunk(chunk.getChunk(), startPos, endPos, map);
     						break;
     					} catch (ConcurrentModificationException e) {
     						final int count = ERROR_COUNTER.incrementAndGet();
     
     						if (count <= 5) {
    -							LOGGER.warn("[Render Data Attachment] Encountered CME during render region build. A mod is accessing or changing chunk data outside the main thread. Retrying.", e);
    +							LOGGER.warn("[Block Entity Render Data] Encountered CME during render region build. A mod is accessing or changing chunk data outside the main thread. Retrying.", e);
     
     							if (count == 5) {
    -								LOGGER.info("[Render Data Attachment] Subsequent exceptions will be suppressed.");
    +								LOGGER.info("[Block Entity Render Data] Subsequent exceptions will be suppressed.");
     							}
     						}
     					}
    @@ -76,35 +82,34 @@ private void create(World world, BlockPos startPos, BlockPos endPos, int chunkRa
     			}
     		}
     
    -		ChunkRendererRegion rendererRegion = info.getReturnValue();
    -
    -		if (map != null && rendererRegion != null) {
    -			((RenderDataObjectConsumer) rendererRegion).fabric_acceptRenderDataObjects(map);
    +		if (map != null) {
    +			((RenderDataMapConsumer) rendererRegion).fabric_acceptRenderDataMap(map);
     		}
     	}
     
    +	@Unique
     	private static Long2ObjectOpenHashMap mapChunk(WorldChunk chunk, BlockPos posFrom, BlockPos posTo, Long2ObjectOpenHashMap map) {
     		final int xMin = posFrom.getX();
     		final int xMax = posTo.getX();
    -		final int zMin = posFrom.getZ();
    -		final int zMax = posTo.getZ();
     		final int yMin = posFrom.getY();
     		final int yMax = posTo.getY();
    +		final int zMin = posFrom.getZ();
    +		final int zMax = posTo.getZ();
     
     		for (Map.Entry entry : chunk.getBlockEntities().entrySet()) {
    -			final BlockPos entPos = entry.getKey();
    +			final BlockPos pos = entry.getKey();
     
    -			if (entPos.getX() >= xMin && entPos.getX() <= xMax
    -					&& entPos.getY() >= yMin && entPos.getY() <= yMax
    -					&& entPos.getZ() >= zMin && entPos.getZ() <= zMax) {
    -				final Object o = ((RenderAttachmentBlockEntity) entry.getValue()).getRenderAttachmentData();
    +			if (pos.getX() >= xMin && pos.getX() <= xMax
    +					&& pos.getY() >= yMin && pos.getY() <= yMax
    +					&& pos.getZ() >= zMin && pos.getZ() <= zMax) {
    +				final Object data = entry.getValue().getRenderData();
     
    -				if (o != null) {
    +				if (data != null) {
     					if (map == null) {
     						map = new Long2ObjectOpenHashMap<>();
     					}
     
    -					map.put(entPos.asLong(), o);
    +					map.put(pos.asLong(), data);
     				}
     			}
     		}
    diff --git a/fabric-block-view-api-v2/src/client/java/net/fabricmc/fabric/mixin/blockview/client/ChunkRendererRegionMixin.java b/fabric-block-view-api-v2/src/client/java/net/fabricmc/fabric/mixin/blockview/client/ChunkRendererRegionMixin.java
    new file mode 100644
    index 0000000000..71e771936c
    --- /dev/null
    +++ b/fabric-block-view-api-v2/src/client/java/net/fabricmc/fabric/mixin/blockview/client/ChunkRendererRegionMixin.java
    @@ -0,0 +1,67 @@
    +/*
    + * 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 net.fabricmc.fabric.mixin.blockview.client;
    +
    +import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
    +import org.jetbrains.annotations.Nullable;
    +import org.spongepowered.asm.mixin.Final;
    +import org.spongepowered.asm.mixin.Mixin;
    +import org.spongepowered.asm.mixin.Shadow;
    +import org.spongepowered.asm.mixin.Unique;
    +
    +import net.minecraft.client.render.chunk.ChunkRendererRegion;
    +import net.minecraft.registry.entry.RegistryEntry;
    +import net.minecraft.util.math.BlockPos;
    +import net.minecraft.world.BlockRenderView;
    +import net.minecraft.world.World;
    +import net.minecraft.world.biome.Biome;
    +
    +import net.fabricmc.fabric.impl.blockview.client.RenderDataMapConsumer;
    +
    +@Mixin(ChunkRendererRegion.class)
    +public abstract class ChunkRendererRegionMixin implements BlockRenderView, RenderDataMapConsumer {
    +	@Shadow
    +	@Final
    +	protected World world;
    +
    +	@Unique
    +	@Nullable
    +	private Long2ObjectMap fabric_renderDataMap;
    +
    +	@Override
    +	public Object getBlockEntityRenderData(BlockPos pos) {
    +		return fabric_renderDataMap == null ? null : fabric_renderDataMap.get(pos.asLong());
    +	}
    +
    +	/**
    +	 * Called in {@link ChunkRendererRegionBuilderMixin}.
    +	 */
    +	@Override
    +	public void fabric_acceptRenderDataMap(Long2ObjectMap renderDataMap) {
    +		this.fabric_renderDataMap = renderDataMap;
    +	}
    +
    +	@Override
    +	public boolean hasBiomes() {
    +		return true;
    +	}
    +
    +	@Override
    +	public RegistryEntry getBiomeFabric(BlockPos pos) {
    +		return world.getBiome(pos);
    +	}
    +}
    diff --git a/fabric-rendering-data-attachment-v1/src/client/resources/fabric-rendering-data-attachment-v1.client.mixins.json b/fabric-block-view-api-v2/src/client/resources/fabric-block-view-api-v2.client.mixins.json
    similarity index 58%
    rename from fabric-rendering-data-attachment-v1/src/client/resources/fabric-rendering-data-attachment-v1.client.mixins.json
    rename to fabric-block-view-api-v2/src/client/resources/fabric-block-view-api-v2.client.mixins.json
    index 1acdfb76b2..2aae976266 100644
    --- a/fabric-rendering-data-attachment-v1/src/client/resources/fabric-rendering-data-attachment-v1.client.mixins.json
    +++ b/fabric-block-view-api-v2/src/client/resources/fabric-block-view-api-v2.client.mixins.json
    @@ -1,7 +1,7 @@
     {
       "required": true,
    -  "package": "net.fabricmc.fabric.mixin.rendering.data.attachment.client",
    -  "compatibilityLevel": "JAVA_16",
    +  "package": "net.fabricmc.fabric.mixin.blockview.client",
    +  "compatibilityLevel": "JAVA_17",
       "client": [
         "ChunkRendererRegionMixin",
         "ChunkRendererRegionBuilderMixin"
    diff --git a/fabric-block-view-api-v2/src/main/java/net/fabricmc/fabric/api/blockview/v2/FabricBlockView.java b/fabric-block-view-api-v2/src/main/java/net/fabricmc/fabric/api/blockview/v2/FabricBlockView.java
    new file mode 100644
    index 0000000000..e310ca4ef3
    --- /dev/null
    +++ b/fabric-block-view-api-v2/src/main/java/net/fabricmc/fabric/api/blockview/v2/FabricBlockView.java
    @@ -0,0 +1,104 @@
    +/*
    + * 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 net.fabricmc.fabric.api.blockview.v2;
    +
    +import org.jetbrains.annotations.Nullable;
    +import org.jetbrains.annotations.UnknownNullability;
    +
    +import net.minecraft.block.entity.BlockEntity;
    +import net.minecraft.registry.entry.RegistryEntry;
    +import net.minecraft.util.math.BlockPos;
    +import net.minecraft.world.BlockView;
    +import net.minecraft.world.WorldView;
    +import net.minecraft.world.biome.Biome;
    +
    +/**
    + * General-purpose Fabric-provided extensions for {@link BlockView} subclasses.
    + *
    + * 

    These extensions were designed primarily for use by methods invoked during chunk building, but + * they can also be used in other contexts. + * + *

    Note: This interface is automatically implemented on all {@link BlockView} instances via Mixin and interface injection. + */ +public interface FabricBlockView { + /** + * Retrieves block entity render data for a given block position. + * + *

    This method must be used instead of {@link BlockView#getBlockEntity(BlockPos)} in cases + * where the user knows that the current context may be multithreaded, such as chunk building, to + * ensure thread safety and data consistency. Using a {@link BlockEntity} directly may not be + * thread-safe since it may lead to non-atomic modification of the internal state of the + * {@link BlockEntity} (such as through lazy computation). Using a {@link BlockEntity} directly + * may not be consistent since the internal state of the {@link BlockEntity} may change on a + * different thread. + * + *

    As previously stated, a common environment to use this method in is chunk building. Methods + * that are invoked during chunk building and that thus should use this method include, but are + * not limited to, {@code FabricBakedModel#emitBlockQuads} (block models), + * {@code BlockColorProvider#getColor} (block color providers), and + * {@code FabricBlock#getAppearance} (block appearance computation). + * + *

    Users of this method are required to check the returned object before using it. Users must + * check if it is null and if it is of the correct type to avoid null pointer and class cast + * exceptions, as the returned data is not guaranteed to be what the user expects. A simple way + * to implement these checks is to use {@code instanceof}, since it always returns {@code false} + * if the object is null. If the {@code instanceof} returns {@code false}, a fallback path should + * be used. + * + * @param pos the position of the block entity + * @return the render data provided by the block entity, or null if there is no block entity at this position + * + * @see RenderDataBlockEntity + */ + @Nullable + default Object getBlockEntityRenderData(BlockPos pos) { + BlockEntity blockEntity = ((BlockView) this).getBlockEntity(pos); + return blockEntity == null ? null : blockEntity.getRenderData(); + } + + /** + * Checks whether biome retrieval is supported. The returned value will not change between + * multiple calls of this method. See {@link #getBiomeFabric(BlockPos)} for more information. + * + * @return whether biome retrieval is supported + * @see #getBiomeFabric(BlockPos) + */ + default boolean hasBiomes() { + return false; + } + + /** + * Retrieves the biome at the given position if biome retrieval is supported. If + * {@link #hasBiomes()} returns {@code true}, this method will always return a non-null + * {@link RegistryEntry} whose {@link RegistryEntry#value() value} is non-null. If + * {@link #hasBiomes()} returns {@code false}, this method will always return {@code null}. + * + *

    Prefer using {@link WorldView#getBiome(BlockPos)} instead of this method if this instance + * is known to implement {@link WorldView}. + * + * @implNote Implementations which do not return null are encouraged to use the plains biome as + * the default value, for example when the biome at the given position is unknown. + * + * @param pos the position for which to retrieve the biome + * @return the biome, or null if biome retrieval is not supported + * @see #hasBiomes() + */ + @UnknownNullability + default RegistryEntry getBiomeFabric(BlockPos pos) { + return null; + } +} diff --git a/fabric-block-view-api-v2/src/main/java/net/fabricmc/fabric/api/blockview/v2/RenderDataBlockEntity.java b/fabric-block-view-api-v2/src/main/java/net/fabricmc/fabric/api/blockview/v2/RenderDataBlockEntity.java new file mode 100644 index 0000000000..487b6a017a --- /dev/null +++ b/fabric-block-view-api-v2/src/main/java/net/fabricmc/fabric/api/blockview/v2/RenderDataBlockEntity.java @@ -0,0 +1,63 @@ +/* + * 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 net.fabricmc.fabric.api.blockview.v2; + +import org.jetbrains.annotations.Nullable; + +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.BlockView; + +/** + * Extensions that allow {@link BlockEntity} subclasses to provide render data. + * + *

    Block entity render data is arbitrary data that captures some useful state of the + * {@link BlockEntity} and is safe to use in a multithreaded environment. In these environments, + * accessing and using a {@link BlockEntity} directly via {@link BlockView#getBlockEntity(BlockPos)} + * may not be thread-safe since the {@link BlockEntity} may be modified on a different thread, and it + * may not be consistent since accessing the internal state of the {@link BlockEntity} could modify it + * in a non-atomic way (such as through lazy computation). Using render data avoids these issues. + * + *

    Implementation Tips

    + * + *

    The simplest form of render data is a value or object that is immutable. If only one such value + * must serve as render data, then it can be returned directly. An example of this would be returning + * an {@code Integer} that represents some internal state of a block entity. If more than one value + * must be used as render data, it can be packaged into an object that cannot be modified externally, + * such as a record. It is also possible to make render data a mutable object, but it must be ensured + * that changes to the internal state of this object are atomic and safe. + * + *

    Note: This interface is automatically implemented on all {@link BlockEntity} instances via Mixin and interface injection. + */ +public interface RenderDataBlockEntity { + /** + * Gets the render data provided by this block entity. The returned object must be safe to + * use in a multithreaded environment. + * + *

    Note: This method should not be called directly; use + * {@link FabricBlockView#getBlockEntityRenderData(BlockPos)} instead. Only call this + * method when the result is used to implement + * {@link FabricBlockView#getBlockEntityRenderData(BlockPos)}. + * + * @return the render data + * @see FabricBlockView#getBlockEntityRenderData(BlockPos) + */ + @Nullable + default Object getRenderData() { + return null; + } +} diff --git a/fabric-rendering-data-attachment-v1/src/main/java/net/fabricmc/fabric/mixin/rendering/data/attachment/BlockEntityMixin.java b/fabric-block-view-api-v2/src/main/java/net/fabricmc/fabric/mixin/blockview/BlockEntityMixin.java similarity index 72% rename from fabric-rendering-data-attachment-v1/src/main/java/net/fabricmc/fabric/mixin/rendering/data/attachment/BlockEntityMixin.java rename to fabric-block-view-api-v2/src/main/java/net/fabricmc/fabric/mixin/blockview/BlockEntityMixin.java index e1c8f7395e..d55f592055 100644 --- a/fabric-rendering-data-attachment-v1/src/main/java/net/fabricmc/fabric/mixin/rendering/data/attachment/BlockEntityMixin.java +++ b/fabric-block-view-api-v2/src/main/java/net/fabricmc/fabric/mixin/blockview/BlockEntityMixin.java @@ -14,18 +14,14 @@ * limitations under the License. */ -package net.fabricmc.fabric.mixin.rendering.data.attachment; +package net.fabricmc.fabric.mixin.blockview; import org.spongepowered.asm.mixin.Mixin; import net.minecraft.block.entity.BlockEntity; -import net.fabricmc.fabric.api.rendering.data.v1.RenderAttachmentBlockEntity; +import net.fabricmc.fabric.api.blockview.v2.RenderDataBlockEntity; @Mixin(BlockEntity.class) -public class BlockEntityMixin implements RenderAttachmentBlockEntity { - @Override - public Object getRenderAttachmentData() { - return null; - } +public abstract class BlockEntityMixin implements RenderDataBlockEntity { } diff --git a/fabric-block-view-api-v2/src/main/java/net/fabricmc/fabric/mixin/blockview/BlockViewMixin.java b/fabric-block-view-api-v2/src/main/java/net/fabricmc/fabric/mixin/blockview/BlockViewMixin.java new file mode 100644 index 0000000000..e9e5730ce2 --- /dev/null +++ b/fabric-block-view-api-v2/src/main/java/net/fabricmc/fabric/mixin/blockview/BlockViewMixin.java @@ -0,0 +1,27 @@ +/* + * 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 net.fabricmc.fabric.mixin.blockview; + +import org.spongepowered.asm.mixin.Mixin; + +import net.minecraft.world.BlockView; + +import net.fabricmc.fabric.api.blockview.v2.FabricBlockView; + +@Mixin(BlockView.class) +public interface BlockViewMixin extends FabricBlockView { +} diff --git a/fabric-models-v0/src/client/java/net/fabricmc/fabric/mixin/client/model/BakedModelManagerMixin.java b/fabric-block-view-api-v2/src/main/java/net/fabricmc/fabric/mixin/blockview/WorldViewMixin.java similarity index 57% rename from fabric-models-v0/src/client/java/net/fabricmc/fabric/mixin/client/model/BakedModelManagerMixin.java rename to fabric-block-view-api-v2/src/main/java/net/fabricmc/fabric/mixin/blockview/WorldViewMixin.java index 8117abbfe7..ea5852ab78 100644 --- a/fabric-models-v0/src/client/java/net/fabricmc/fabric/mixin/client/model/BakedModelManagerMixin.java +++ b/fabric-block-view-api-v2/src/main/java/net/fabricmc/fabric/mixin/blockview/WorldViewMixin.java @@ -14,26 +14,29 @@ * limitations under the License. */ -package net.fabricmc.fabric.mixin.client.model; - -import java.util.Map; +package net.fabricmc.fabric.mixin.blockview; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; -import net.minecraft.client.render.model.BakedModel; -import net.minecraft.client.render.model.BakedModelManager; -import net.minecraft.util.Identifier; - -import net.fabricmc.fabric.impl.client.model.BakedModelManagerHooks; +import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.BlockRenderView; +import net.minecraft.world.WorldView; +import net.minecraft.world.biome.Biome; -@Mixin(BakedModelManager.class) -public class BakedModelManagerMixin implements BakedModelManagerHooks { +@Mixin(WorldView.class) +public interface WorldViewMixin extends BlockRenderView { @Shadow - private Map models; + RegistryEntry getBiome(BlockPos pos); + + @Override + default boolean hasBiomes() { + return true; + } @Override - public BakedModel fabric_getModel(Identifier id) { - return models.get(id); + default RegistryEntry getBiomeFabric(BlockPos pos) { + return getBiome(pos); } } diff --git a/fabric-models-v0/src/client/resources/assets/fabric-models-v0/icon.png b/fabric-block-view-api-v2/src/main/resources/assets/fabric-block-view-api-v2/icon.png similarity index 100% rename from fabric-models-v0/src/client/resources/assets/fabric-models-v0/icon.png rename to fabric-block-view-api-v2/src/main/resources/assets/fabric-block-view-api-v2/icon.png diff --git a/fabric-rendering-data-attachment-v1/src/main/resources/fabric-rendering-data-attachment-v1.accesswidener b/fabric-block-view-api-v2/src/main/resources/fabric-block-view-api-v2.accesswidener similarity index 100% rename from fabric-rendering-data-attachment-v1/src/main/resources/fabric-rendering-data-attachment-v1.accesswidener rename to fabric-block-view-api-v2/src/main/resources/fabric-block-view-api-v2.accesswidener diff --git a/fabric-block-view-api-v2/src/main/resources/fabric-block-view-api-v2.mixins.json b/fabric-block-view-api-v2/src/main/resources/fabric-block-view-api-v2.mixins.json new file mode 100644 index 0000000000..4fdfe605a3 --- /dev/null +++ b/fabric-block-view-api-v2/src/main/resources/fabric-block-view-api-v2.mixins.json @@ -0,0 +1,12 @@ +{ + "required": true, + "package": "net.fabricmc.fabric.mixin.blockview", + "compatibilityLevel": "JAVA_17", + "mixins": [ + "BlockEntityMixin", + "BlockViewMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/fabric-block-view-api-v2/src/main/resources/fabric.mod.json b/fabric-block-view-api-v2/src/main/resources/fabric.mod.json new file mode 100644 index 0000000000..63d06cb7c5 --- /dev/null +++ b/fabric-block-view-api-v2/src/main/resources/fabric.mod.json @@ -0,0 +1,37 @@ +{ + "schemaVersion": 1, + "id": "fabric-block-view-api-v2", + "name": "Fabric BlockView API (v2)", + "version": "${version}", + "environment": "*", + "license": "Apache-2.0", + "icon": "assets/fabric-block-view-api-v2/icon.png", + "contact": { + "homepage": "https://fabricmc.net", + "irc": "irc://irc.esper.net:6667/fabric", + "issues": "https://github.com/FabricMC/fabric/issues", + "sources": "https://github.com/FabricMC/fabric" + }, + "authors": [ + "FabricMC" + ], + "depends": { + "fabricloader": ">=0.14.21" + }, + "description": "Hooks for block views", + "mixins": [ + "fabric-block-view-api-v2.mixins.json", + { + "config": "fabric-block-view-api-v2.client.mixins.json", + "environment": "client" + } + ], + "custom": { + "fabric-api:module-lifecycle": "stable", + "loom:injected_interfaces": { + "net/minecraft/class_1922": ["net/fabricmc/fabric/api/blockview/v2/FabricBlockView"], + "net/minecraft/class_2586": ["net/fabricmc/fabric/api/blockview/v2/RenderDataBlockEntity"] + } + }, + "accessWidener": "fabric-block-view-api-v2.accesswidener" +} diff --git a/fabric-blockrenderlayer-v1/build.gradle b/fabric-blockrenderlayer-v1/build.gradle index 6abe270a2f..9ced6ef8b4 100644 --- a/fabric-blockrenderlayer-v1/build.gradle +++ b/fabric-blockrenderlayer-v1/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-blockrenderlayer-v1" version = getSubprojectVersion(project) moduleDependencies(project, ['fabric-api-base']) diff --git a/fabric-client-tags-api-v1/build.gradle b/fabric-client-tags-api-v1/build.gradle index 664ec5a4ec..406c6a3cbb 100644 --- a/fabric-client-tags-api-v1/build.gradle +++ b/fabric-client-tags-api-v1/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-client-tags-api-v1" version = getSubprojectVersion(project) moduleDependencies(project, ['fabric-api-base']) @@ -6,4 +5,5 @@ moduleDependencies(project, ['fabric-api-base']) testDependencies(project, [ ':fabric-convention-tags-v1', ':fabric-lifecycle-events-v1', + ':fabric-resource-loader-v0', ]) diff --git a/fabric-client-tags-api-v1/src/client/java/net/fabricmc/fabric/api/tag/client/v1/ClientTags.java b/fabric-client-tags-api-v1/src/client/java/net/fabricmc/fabric/api/tag/client/v1/ClientTags.java index 78ace907e2..21d1e1226e 100644 --- a/fabric-client-tags-api-v1/src/client/java/net/fabricmc/fabric/api/tag/client/v1/ClientTags.java +++ b/fabric-client-tags-api-v1/src/client/java/net/fabricmc/fabric/api/tag/client/v1/ClientTags.java @@ -16,21 +16,15 @@ package net.fabricmc.fabric.api.tag.client.v1; -import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import net.minecraft.client.MinecraftClient; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.entry.RegistryEntry; import net.minecraft.registry.tag.TagKey; import net.minecraft.util.Identifier; -import net.minecraft.registry.Registries; -import net.minecraft.registry.Registry; -import net.minecraft.registry.entry.RegistryEntry; -import net.minecraft.registry.RegistryKey; -import net.fabricmc.fabric.impl.tag.client.ClientTagsLoader; +import net.fabricmc.fabric.impl.tag.client.ClientTagsImpl; /** * Allows the use of tags by directly loading them from the installed mods. @@ -45,8 +39,6 @@ * even when connected to a vanilla server. */ public final class ClientTags { - private static final Map, Set> LOCAL_TAG_CACHE = new ConcurrentHashMap<>(); - private ClientTags() { } @@ -57,54 +49,25 @@ private ClientTags() { * @return a set of {@code Identifier}s this tag contains */ public static Set getOrCreateLocalTag(TagKey tagKey) { - Set ids = LOCAL_TAG_CACHE.get(tagKey); - - if (ids == null) { - ids = ClientTagsLoader.loadTag(tagKey); - LOCAL_TAG_CACHE.put(tagKey, ids); - } - - return ids; + return ClientTagsImpl.getOrCreatePartiallySyncedTag(tagKey).completeIds(); } /** * Checks if an entry is in a tag. * *

    If the synced tag does exist, it is queried. If it does not exist, - * the tag populated from the available mods is checked. + * the tag populated from the available mods is checked, recursively checking the + * synced tags and entries contained within. * * @param tagKey the {@code TagKey} to being checked * @param entry the entry to check * @return if the entry is in the given tag */ - @SuppressWarnings("unchecked") public static boolean isInWithLocalFallback(TagKey tagKey, T entry) { Objects.requireNonNull(tagKey); Objects.requireNonNull(entry); - Optional> maybeRegistry = getRegistry(tagKey); - - if (maybeRegistry.isEmpty()) { - return false; - } - - if (!tagKey.isOf(maybeRegistry.get().getKey())) { - return false; - } - - Registry registry = (Registry) maybeRegistry.get(); - - Optional> maybeKey = registry.getKey(entry); - - // Check synced tag - if (registry.getEntryList(tagKey).isPresent()) { - return maybeKey.filter(registryKey -> registry.entryOf(registryKey).isIn(tagKey)) - .isPresent(); - } - - // Check local tags - Set ids = getOrCreateLocalTag(tagKey); - return maybeKey.filter(registryKey -> ids.contains(registryKey.getValue())).isPresent(); + return ClientTagsImpl.getRegistryEntry(tagKey, entry).map(re -> isInWithLocalFallback(tagKey, re)).orElse(false); } /** @@ -112,7 +75,8 @@ public static boolean isInWithLocalFallback(TagKey tagKey, T entry) { * such as {@link net.minecraft.world.biome.Biome}s. * *

    If the synced tag does exist, it is queried. If it does not exist, - * the tag populated from the available mods is checked. + * the tag populated from the available mods is checked, recursively checking the + * synced tags and entries contained within. * * @param tagKey the {@code TagKey} to be checked * @param registryEntry the entry to check @@ -121,21 +85,7 @@ public static boolean isInWithLocalFallback(TagKey tagKey, T entry) { public static boolean isInWithLocalFallback(TagKey tagKey, RegistryEntry registryEntry) { Objects.requireNonNull(tagKey); Objects.requireNonNull(registryEntry); - - // Check if the tag exists in the dynamic registry first - Optional> maybeRegistry = getRegistry(tagKey); - - if (maybeRegistry.isPresent()) { - if (maybeRegistry.get().getEntryList(tagKey).isPresent()) { - return registryEntry.isIn(tagKey); - } - } - - if (registryEntry.getKey().isPresent()) { - return isInLocal(tagKey, registryEntry.getKey().get()); - } - - return false; + return ClientTagsImpl.isInWithLocalFallback(tagKey, registryEntry); } /** @@ -157,22 +107,4 @@ public static boolean isInLocal(TagKey tagKey, RegistryKey registryKey return false; } - - @SuppressWarnings("unchecked") - private static Optional> getRegistry(TagKey tagKey) { - Objects.requireNonNull(tagKey); - - // Check if the tag represents a dynamic registry - if (MinecraftClient.getInstance() != null) { - if (MinecraftClient.getInstance().world != null) { - if (MinecraftClient.getInstance().world.getRegistryManager() != null) { - Optional> maybeRegistry = MinecraftClient.getInstance().world - .getRegistryManager().getOptional(tagKey.registry()); - if (maybeRegistry.isPresent()) return maybeRegistry; - } - } - } - - return (Optional>) Registries.REGISTRIES.getOrEmpty(tagKey.registry().getValue()); - } } diff --git a/fabric-client-tags-api-v1/src/client/java/net/fabricmc/fabric/impl/tag/client/ClientTagsImpl.java b/fabric-client-tags-api-v1/src/client/java/net/fabricmc/fabric/impl/tag/client/ClientTagsImpl.java new file mode 100644 index 0000000000..1700242200 --- /dev/null +++ b/fabric-client-tags-api-v1/src/client/java/net/fabricmc/fabric/impl/tag/client/ClientTagsImpl.java @@ -0,0 +1,124 @@ +/* + * 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 net.fabricmc.fabric.impl.tag.client; + +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.registry.Registries; +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.registry.tag.TagKey; + +public class ClientTagsImpl { + private static final Map, ClientTagsLoader.LoadedTag> LOCAL_TAG_HIERARCHY = new ConcurrentHashMap<>(); + + public static boolean isInWithLocalFallback(TagKey tagKey, RegistryEntry registryEntry) { + return isInWithLocalFallback(tagKey, registryEntry, new HashSet<>()); + } + + @SuppressWarnings("unchecked") + private static boolean isInWithLocalFallback(TagKey tagKey, RegistryEntry registryEntry, Set> checked) { + if (checked.contains(tagKey)) { + return false; + } + + checked.add(tagKey); + + // Check if the tag exists in the dynamic registry first + Optional> maybeRegistry = ClientTagsImpl.getRegistry(tagKey); + + if (maybeRegistry.isPresent()) { + // Check the synced tag exists and use that + if (maybeRegistry.get().getEntryList(tagKey).isPresent()) { + return registryEntry.isIn(tagKey); + } + } + + if (registryEntry.getKey().isEmpty()) { + // No key? + return false; + } + + // Recursively search the entries contained with the tag + ClientTagsLoader.LoadedTag wt = ClientTagsImpl.getOrCreatePartiallySyncedTag(tagKey); + + if (wt.immediateChildIds().contains(registryEntry.getKey().get().getValue())) { + return true; + } + + for (TagKey key : wt.immediateChildTags()) { + if (isInWithLocalFallback((TagKey) key, registryEntry, checked)) { + return true; + } + + checked.add((TagKey) key); + } + + return false; + } + + @SuppressWarnings("unchecked") + public static Optional> getRegistry(TagKey tagKey) { + Objects.requireNonNull(tagKey); + + // Check if the tag represents a dynamic registry + if (MinecraftClient.getInstance() != null) { + if (MinecraftClient.getInstance().world != null) { + if (MinecraftClient.getInstance().world.getRegistryManager() != null) { + Optional> maybeRegistry = MinecraftClient.getInstance().world + .getRegistryManager().getOptional(tagKey.registry()); + if (maybeRegistry.isPresent()) return maybeRegistry; + } + } + } + + return (Optional>) Registries.REGISTRIES.getOrEmpty(tagKey.registry().getValue()); + } + + @SuppressWarnings("unchecked") + public static Optional> getRegistryEntry(TagKey tagKey, T entry) { + Optional> maybeRegistry = getRegistry(tagKey); + + if (maybeRegistry.isEmpty() || !tagKey.isOf(maybeRegistry.get().getKey())) { + return Optional.empty(); + } + + Registry registry = (Registry) maybeRegistry.get(); + + Optional> maybeKey = registry.getKey(entry); + + return maybeKey.map(registry::entryOf); + } + + public static ClientTagsLoader.LoadedTag getOrCreatePartiallySyncedTag(TagKey tagKey) { + ClientTagsLoader.LoadedTag loadedTag = LOCAL_TAG_HIERARCHY.get(tagKey); + + if (loadedTag == null) { + loadedTag = ClientTagsLoader.loadTag(tagKey); + LOCAL_TAG_HIERARCHY.put(tagKey, loadedTag); + } + + return loadedTag; + } +} diff --git a/fabric-client-tags-api-v1/src/client/java/net/fabricmc/fabric/impl/tag/client/ClientTagsLoader.java b/fabric-client-tags-api-v1/src/client/java/net/fabricmc/fabric/impl/tag/client/ClientTagsLoader.java index e5a2d0f22b..5e77f9398c 100644 --- a/fabric-client-tags-api-v1/src/client/java/net/fabricmc/fabric/impl/tag/client/ClientTagsLoader.java +++ b/fabric-client-tags-api-v1/src/client/java/net/fabricmc/fabric/impl/tag/client/ClientTagsLoader.java @@ -33,15 +33,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKey; import net.minecraft.registry.tag.TagEntry; import net.minecraft.registry.tag.TagFile; import net.minecraft.registry.tag.TagKey; import net.minecraft.registry.tag.TagManagerLoader; import net.minecraft.util.Identifier; -import net.minecraft.registry.Registry; -import net.minecraft.registry.RegistryKey; -import net.fabricmc.fabric.api.tag.client.v1.ClientTags; import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.ModContainer; @@ -51,7 +50,7 @@ public class ClientTagsLoader { * Load a given tag from the available mods into a set of {@code Identifier}s. * Parsing based on {@link net.minecraft.registry.tag.TagGroupLoader#loadTags(net.minecraft.resource.ResourceManager)} */ - public static Set loadTag(TagKey tagKey) { + public static LoadedTag loadTag(TagKey tagKey) { var tags = new HashSet(); HashSet tagFiles = getTagFiles(tagKey.registry(), tagKey.id()); @@ -73,13 +72,16 @@ public static Set loadTag(TagKey tagKey) { } } - HashSet ids = new HashSet<>(); + HashSet completeIds = new HashSet<>(); + HashSet immediateChildIds = new HashSet<>(); + HashSet> immediateChildTags = new HashSet<>(); for (TagEntry tagEntry : tags) { tagEntry.resolve(new TagEntry.ValueGetter<>() { @Nullable @Override public Identifier direct(Identifier id) { + immediateChildIds.add(id); return id; } @@ -87,12 +89,20 @@ public Identifier direct(Identifier id) { @Override public Collection tag(Identifier id) { TagKey tag = TagKey.of(tagKey.registry(), id); - return ClientTags.getOrCreateLocalTag(tag); + immediateChildTags.add(tag); + return ClientTagsImpl.getOrCreatePartiallySyncedTag(tag).completeIds; } - }, ids::add); + }, completeIds::add); } - return Collections.unmodifiableSet(ids); + // Ensure that the tag does not refer to itself + immediateChildTags.remove(tagKey); + + return new LoadedTag(Collections.unmodifiableSet(completeIds), Collections.unmodifiableSet(immediateChildTags), + Collections.unmodifiableSet(immediateChildIds)); + } + + public record LoadedTag(Set completeIds, Set> immediateChildTags, Set immediateChildIds) { } /** diff --git a/fabric-client-tags-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/tag/client/v1/ClientTagTest.java b/fabric-client-tags-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/tag/client/v1/ClientTagTest.java index eb2d95c5f4..ed2b70a8ad 100644 --- a/fabric-client-tags-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/tag/client/v1/ClientTagTest.java +++ b/fabric-client-tags-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/tag/client/v1/ClientTagTest.java @@ -20,20 +20,36 @@ import org.slf4j.LoggerFactory; import net.minecraft.block.Blocks; +import net.minecraft.registry.Registries; +import net.minecraft.registry.tag.TagKey; +import net.minecraft.util.Identifier; import net.minecraft.world.biome.BiomeKeys; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.fabricmc.fabric.api.resource.ResourceManagerHelper; +import net.fabricmc.fabric.api.resource.ResourcePackActivationType; import net.fabricmc.fabric.api.tag.client.v1.ClientTags; import net.fabricmc.fabric.api.tag.convention.v1.ConventionalBiomeTags; import net.fabricmc.fabric.api.tag.convention.v1.ConventionalBlockTags; import net.fabricmc.fabric.api.tag.convention.v1.ConventionalEnchantmentTags; +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.ModContainer; public class ClientTagTest implements ClientModInitializer { private static final Logger LOGGER = LoggerFactory.getLogger(ClientTagTest.class); + private static final String MODID = "fabric-clients-tags-api-v1-testmod"; @Override public void onInitializeClient() { + final ModContainer container = FabricLoader.getInstance().getModContainer(MODID).get(); + + if (!ResourceManagerHelper.registerBuiltinResourcePack(new Identifier(MODID, "test2"), + container, ResourcePackActivationType.ALWAYS_ENABLED)) { + throw new IllegalStateException("Could not register built-in resource pack."); + } + ClientLifecycleEvents.CLIENT_STARTED.register(client -> { if (ClientTags.getOrCreateLocalTag(ConventionalEnchantmentTags.INCREASES_BLOCK_DROPS) == null) { throw new AssertionError("Expected to load c:fortune, but it was not found!"); @@ -51,8 +67,25 @@ public void onInitializeClient() { throw new AssertionError("Expected to find forest in c:forest, but it was not found!"); } + if (ClientTags.isInWithLocalFallback(TagKey.of(Registries.BLOCK.getKey(), + new Identifier("fabric", "sword_efficient")), Blocks.DIRT)) { + throw new AssertionError("Expected not to find dirt in fabric:sword_efficient, but it was found!"); + } + // Success! LOGGER.info("The tests for client tags passed!"); }); + + if (true) return; + + // This should be tested on a server with the datapack from the builtin resourcepack. + // That is, fabric:sword_efficient should NOT exist on the server (can be confirmed with F3 on a dirt block), + // but the this test should pass as minecraft:sword_efficient will contain dirt on the server + ClientTickEvents.END_WORLD_TICK.register(client -> { + if (!ClientTags.isInWithLocalFallback(TagKey.of(Registries.BLOCK.getKey(), + new Identifier("fabric", "sword_efficient")), Blocks.DIRT)) { + throw new AssertionError("Expected to find dirt in fabric:sword_efficient, but it was not found!"); + } + }); } } diff --git a/fabric-client-tags-api-v1/src/testmodClient/resources/data/fabric/tags/blocks/sword_efficient.json b/fabric-client-tags-api-v1/src/testmodClient/resources/data/fabric/tags/blocks/sword_efficient.json new file mode 100644 index 0000000000..2974a7894a --- /dev/null +++ b/fabric-client-tags-api-v1/src/testmodClient/resources/data/fabric/tags/blocks/sword_efficient.json @@ -0,0 +1,25 @@ +{ + "replace": false, + "values": [ + { + "id": "#fabric:mineable/sword", + "required": false + }, + { + "id": "#minecraft:sword_efficient", + "required": false + }, + { + "id": "minecraft:bamboo", + "required": false + }, + { + "id": "minecraft:cobweb", + "required": false + }, + { + "id": "minecraft:bamboo_sapling", + "required": false + } + ] +} \ No newline at end of file diff --git a/fabric-client-tags-api-v1/src/testmodClient/resources/resourcepacks/test2/data/minecraft/tags/blocks/sword_efficient.json b/fabric-client-tags-api-v1/src/testmodClient/resources/resourcepacks/test2/data/minecraft/tags/blocks/sword_efficient.json new file mode 100644 index 0000000000..bc9980aeca --- /dev/null +++ b/fabric-client-tags-api-v1/src/testmodClient/resources/resourcepacks/test2/data/minecraft/tags/blocks/sword_efficient.json @@ -0,0 +1,10 @@ +{ + "replace": false, + "values": [ + "minecraft:dirt", + { + "id": "", + "required": false + } + ] +} \ No newline at end of file diff --git a/fabric-client-tags-api-v1/src/testmodClient/resources/resourcepacks/test2/pack.mcmeta b/fabric-client-tags-api-v1/src/testmodClient/resources/resourcepacks/test2/pack.mcmeta new file mode 100644 index 0000000000..bd0d5ce811 --- /dev/null +++ b/fabric-client-tags-api-v1/src/testmodClient/resources/resourcepacks/test2/pack.mcmeta @@ -0,0 +1,6 @@ +{ + "pack": { + "pack_format": 9, + "description": "Test Dirt in SwordEfficient" + } +} \ No newline at end of file diff --git a/fabric-command-api-v2/build.gradle b/fabric-command-api-v2/build.gradle index 8c17b0efe8..6cdfdfe104 100644 --- a/fabric-command-api-v2/build.gradle +++ b/fabric-command-api-v2/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-command-api-v2" version = getSubprojectVersion(project) moduleDependencies(project, ['fabric-api-base']) diff --git a/fabric-command-api-v2/src/client/java/net/fabricmc/fabric/mixin/command/client/ClientPlayNetworkHandlerMixin.java b/fabric-command-api-v2/src/client/java/net/fabricmc/fabric/mixin/command/client/ClientPlayNetworkHandlerMixin.java index c7177974c3..02471be870 100644 --- a/fabric-command-api-v2/src/client/java/net/fabricmc/fabric/mixin/command/client/ClientPlayNetworkHandlerMixin.java +++ b/fabric-command-api-v2/src/client/java/net/fabricmc/fabric/mixin/command/client/ClientPlayNetworkHandlerMixin.java @@ -26,14 +26,13 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import net.minecraft.client.network.ClientCommandSource; -import net.minecraft.client.network.ClientDynamicRegistryType; import net.minecraft.client.network.ClientPlayNetworkHandler; import net.minecraft.command.CommandRegistryAccess; import net.minecraft.command.CommandSource; import net.minecraft.network.packet.s2c.play.CommandTreeS2CPacket; import net.minecraft.network.packet.s2c.play.GameJoinS2CPacket; +import net.minecraft.registry.DynamicRegistryManager; import net.minecraft.resource.featuretoggle.FeatureSet; -import net.minecraft.registry.CombinedDynamicRegistries; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; @@ -48,17 +47,19 @@ abstract class ClientPlayNetworkHandlerMixin { @Final private ClientCommandSource commandSource; + @Final @Shadow private FeatureSet enabledFeatures; + @Final @Shadow - private CombinedDynamicRegistries combinedDynamicRegistries; + private DynamicRegistryManager.Immutable combinedDynamicRegistries; @Inject(method = "onGameJoin", at = @At("RETURN")) private void onGameJoin(GameJoinS2CPacket packet, CallbackInfo info) { final CommandDispatcher dispatcher = new CommandDispatcher<>(); ClientCommandInternals.setActiveDispatcher(dispatcher); - ClientCommandRegistrationCallback.EVENT.invoker().register(dispatcher, CommandRegistryAccess.of(this.combinedDynamicRegistries.getCombinedRegistryManager(), this.enabledFeatures)); + ClientCommandRegistrationCallback.EVENT.invoker().register(dispatcher, CommandRegistryAccess.of(this.combinedDynamicRegistries, this.enabledFeatures)); ClientCommandInternals.finalizeInit(); } diff --git a/fabric-content-registries-v0/build.gradle b/fabric-content-registries-v0/build.gradle index a86225b9b9..1ca0b2511d 100644 --- a/fabric-content-registries-v0/build.gradle +++ b/fabric-content-registries-v0/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-content-registries-v0" version = getSubprojectVersion(project) loom { diff --git a/fabric-content-registries-v0/src/main/java/net/fabricmc/fabric/api/registry/LandPathNodeTypesRegistry.java b/fabric-content-registries-v0/src/main/java/net/fabricmc/fabric/api/registry/LandPathNodeTypesRegistry.java index e74eff6711..c7407355b5 100644 --- a/fabric-content-registries-v0/src/main/java/net/fabricmc/fabric/api/registry/LandPathNodeTypesRegistry.java +++ b/fabric-content-registries-v0/src/main/java/net/fabricmc/fabric/api/registry/LandPathNodeTypesRegistry.java @@ -185,9 +185,9 @@ public non-sealed interface StaticPathNodeTypeProvider extends PathNodeTypeProvi *

    You can specify what to return if the block state is a direct target of an entity path, * or a neighbor block of the entity path. * - *

    For example, for a cactus-like block you should use {@link PathNodeType#DAMAGE_CACTUS} if the block + *

    For example, for a cactus-like block you should use {@link PathNodeType#DAMAGE_OTHER} if the block * is a direct target in the entity path ({@code neighbor == false}) to specify that an entity should not pass - * through or above the block because it will cause damage, and you should use{@link PathNodeType#DANGER_CACTUS} + * through or above the block because it will cause damage, and you should use {@link PathNodeType#DANGER_OTHER} * if the block is a neighbor block in the entity path ({@code neighbor == true}) to specify that the entity * should not get close to the block because it is dangerous. * @@ -211,9 +211,9 @@ public non-sealed interface DynamicPathNodeTypeProvider extends PathNodeTypeProv *

    You can specify what to return if the block state is a direct target of an entity path, * or a neighbor block of the entity path. * - *

    For example, for a cactus-like block you should specify {@link PathNodeType#DAMAGE_CACTUS} if the block + *

    For example, for a cactus-like block you should specify {@link PathNodeType#DAMAGE_OTHER} if the block * is a direct target ({@code neighbor == false}) to specify that an entity should not pass through or above - * the block because it will cause damage, and {@link PathNodeType#DANGER_CACTUS} if the cactus will be found + * the block because it will cause damage, and {@link PathNodeType#DANGER_OTHER} if the cactus will be found * as a neighbor block in the entity path ({@code neighbor == true}) to specify that the entity should not get * close to the block because is dangerous. * diff --git a/fabric-content-registries-v0/src/main/java/net/fabricmc/fabric/api/registry/SculkSensorFrequencyRegistry.java b/fabric-content-registries-v0/src/main/java/net/fabricmc/fabric/api/registry/SculkSensorFrequencyRegistry.java index a809cd90b8..4a8b832f56 100644 --- a/fabric-content-registries-v0/src/main/java/net/fabricmc/fabric/api/registry/SculkSensorFrequencyRegistry.java +++ b/fabric-content-registries-v0/src/main/java/net/fabricmc/fabric/api/registry/SculkSensorFrequencyRegistry.java @@ -20,6 +20,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import net.minecraft.registry.Registries; import net.minecraft.registry.tag.GameEventTags; import net.minecraft.world.event.GameEvent; import net.minecraft.world.event.Vibrations; @@ -49,14 +50,14 @@ private SculkSensorFrequencyRegistry() { */ public static void register(GameEvent event, int frequency) { if (frequency <= 0 || frequency >= 16) { - throw new IllegalArgumentException("Attempted to register Sculk Sensor frequency for event "+event.getId()+" with frequency "+frequency+". Sculk Sensor frequencies must be between 1 and 15 inclusive."); + throw new IllegalArgumentException("Attempted to register Sculk Sensor frequency for event "+ Registries.GAME_EVENT.getId(event) +" with frequency "+frequency+". Sculk Sensor frequencies must be between 1 and 15 inclusive."); } final Object2IntOpenHashMap map = (Object2IntOpenHashMap) Vibrations.FREQUENCIES; int replaced = map.put(event, frequency); if (replaced != 0) { - LOGGER.debug("Replaced old frequency mapping for {} - was {}, now {}", event.getId(), replaced, frequency); + LOGGER.debug("Replaced old frequency mapping for {} - was {}, now {}", Registries.GAME_EVENT.getId(event), replaced, frequency); } } } diff --git a/fabric-content-registries-v0/src/main/java/net/fabricmc/fabric/api/registry/VillagerInteractionRegistries.java b/fabric-content-registries-v0/src/main/java/net/fabricmc/fabric/api/registry/VillagerInteractionRegistries.java index cfde391765..bc8691d791 100644 --- a/fabric-content-registries-v0/src/main/java/net/fabricmc/fabric/api/registry/VillagerInteractionRegistries.java +++ b/fabric-content-registries-v0/src/main/java/net/fabricmc/fabric/api/registry/VillagerInteractionRegistries.java @@ -38,7 +38,6 @@ /** * Registries for modifying villager interactions that * villagers have with the world. - * @see VillagerPlantableRegistry for registering plants that farmers can plant */ public final class VillagerInteractionRegistries { private static final Logger LOGGER = LoggerFactory.getLogger(VillagerInteractionRegistries.class); @@ -58,7 +57,7 @@ public static void registerCollectable(ItemConvertible item) { } /** - * Registers an item to be use in a composter by farmer villagers. + * Registers an item to be used in a composter by farmer villagers. * @param item the item to register */ public static void registerCompostable(ItemConvertible item) { @@ -73,7 +72,6 @@ public static void registerCompostable(ItemConvertible item) { */ public static void registerFood(ItemConvertible item, int foodValue) { Objects.requireNonNull(item.asItem(), "Item cannot be null!"); - Objects.requireNonNull(foodValue, "Food value cannot be null!"); Integer oldValue = getFoodRegistry().put(item.asItem(), foodValue); if (oldValue != null) { diff --git a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/IndigoConfig.java b/fabric-content-registries-v0/src/main/java/net/fabricmc/fabric/api/registry/package-info.java similarity index 85% rename from fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/IndigoConfig.java rename to fabric-content-registries-v0/src/main/java/net/fabricmc/fabric/api/registry/package-info.java index 5d8f2465d2..0697c3f03e 100644 --- a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/IndigoConfig.java +++ b/fabric-content-registries-v0/src/main/java/net/fabricmc/fabric/api/registry/package-info.java @@ -14,6 +14,7 @@ * limitations under the License. */ -package net.fabricmc.fabric.impl.client.indigo; - -public class IndigoConfig { } +/** + * Includes methods for registering in-game logics. + */ +package net.fabricmc.fabric.api.registry; diff --git a/fabric-content-registries-v0/src/main/java/net/fabricmc/fabric/impl/content/registry/FlammableBlockRegistryImpl.java b/fabric-content-registries-v0/src/main/java/net/fabricmc/fabric/impl/content/registry/FlammableBlockRegistryImpl.java index eea4fe822d..09f0bc7d68 100644 --- a/fabric-content-registries-v0/src/main/java/net/fabricmc/fabric/impl/content/registry/FlammableBlockRegistryImpl.java +++ b/fabric-content-registries-v0/src/main/java/net/fabricmc/fabric/impl/content/registry/FlammableBlockRegistryImpl.java @@ -40,7 +40,7 @@ public class FlammableBlockRegistryImpl implements FlammableBlockRegistry { private FlammableBlockRegistryImpl(Block key) { this.key = key; - // Reset computed values after tags change since they depends on tags. + // Reset computed values after tags change since they depend on tags. CommonLifecycleEvents.TAGS_LOADED.register((registries, client) -> { computedEntries = null; }); diff --git a/fabric-content-registries-v0/src/main/java/net/fabricmc/fabric/impl/content/registry/LootEntryTypeRegistryImpl.java b/fabric-content-registries-v0/src/main/java/net/fabricmc/fabric/impl/content/registry/LootEntryTypeRegistryImpl.java deleted file mode 100644 index a8bf6fcd8f..0000000000 --- a/fabric-content-registries-v0/src/main/java/net/fabricmc/fabric/impl/content/registry/LootEntryTypeRegistryImpl.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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 net.fabricmc.fabric.impl.content.registry; - -import java.lang.reflect.Method; - -import net.minecraft.loot.entry.LootPoolEntryTypes; -import net.minecraft.loot.entry.LootPoolEntry; - -import net.fabricmc.fabric.api.registry.LootEntryTypeRegistry; - -@Deprecated -public final class LootEntryTypeRegistryImpl implements LootEntryTypeRegistry { - private static final Method REGISTER_METHOD; - - static { - Method target = null; - - for (Method m : LootPoolEntryTypes.class.getDeclaredMethods()) { - if (m.getParameterCount() == 1 && m.getParameterTypes()[0] == LootPoolEntry.Serializer.class) { - if (target != null) { - throw new RuntimeException("More than one register-like method found in LootEntries!"); - } else { - target = m; - } - } - } - - if (target == null) { - throw new RuntimeException("Could not find register-like method in LootEntries!"); - } else { - REGISTER_METHOD = target; - REGISTER_METHOD.setAccessible(true); - } - } - - public LootEntryTypeRegistryImpl() { } - - @Override - public void register(LootPoolEntry.Serializer serializer) { - try { - REGISTER_METHOD.invoke(null, serializer); - } catch (Throwable t) { - throw new RuntimeException(t); - } - } -} diff --git a/fabric-content-registries-v0/src/testmod/java/net/fabricmc/fabric/test/content/registry/ContentRegistryTest.java b/fabric-content-registries-v0/src/testmod/java/net/fabricmc/fabric/test/content/registry/ContentRegistryTest.java index b4864b3e0f..3395994a0a 100644 --- a/fabric-content-registries-v0/src/testmod/java/net/fabricmc/fabric/test/content/registry/ContentRegistryTest.java +++ b/fabric-content-registries-v0/src/testmod/java/net/fabricmc/fabric/test/content/registry/ContentRegistryTest.java @@ -64,7 +64,7 @@ public final class ContentRegistryTest implements ModInitializer { public static final Logger LOGGER = LoggerFactory.getLogger(ContentRegistryTest.class); public static final Identifier TEST_EVENT_ID = new Identifier("fabric-content-registries-v0-testmod", "test_event"); - public static final GameEvent TEST_EVENT = new GameEvent(TEST_EVENT_ID.toString(), GameEvent.DEFAULT_RANGE); + public static final GameEvent TEST_EVENT = new GameEvent(GameEvent.DEFAULT_RANGE); @Override public void onInitialize() { @@ -81,7 +81,7 @@ public void onInitialize() { // - copper ore, iron ore, gold ore, and diamond ore can be waxed into their deepslate variants and scraped back again // - aforementioned ores can be scraped from diamond -> gold -> iron -> copper // - villagers can now collect, consume (at the same level of bread) and compost apples - // - villagers can now collect and plant oak saplings + // - villagers can now collect oak saplings // - assign a loot table to the nitwit villager type // - right-clicking a 'test_event' block will emit a 'test_event' game event, which will have a sculk sensor frequency of 2 // - instant health potions can be brewed from awkward potions with any item in the 'minecraft:small_flowers' tag diff --git a/fabric-convention-tags-v1/build.gradle b/fabric-convention-tags-v1/build.gradle index 9c30736c4d..ab2cb0ef62 100644 --- a/fabric-convention-tags-v1/build.gradle +++ b/fabric-convention-tags-v1/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-convention-tags-v1" version = getSubprojectVersion(project) moduleDependencies(project, ['fabric-api-base']) diff --git a/fabric-convention-tags-v1/src/datagen/java/net/fabricmc/fabric/impl/tag/convention/datagen/generators/BiomeTagGenerator.java b/fabric-convention-tags-v1/src/datagen/java/net/fabricmc/fabric/impl/tag/convention/datagen/generators/BiomeTagGenerator.java index 5d308d5ef3..fad79f66fd 100644 --- a/fabric-convention-tags-v1/src/datagen/java/net/fabricmc/fabric/impl/tag/convention/datagen/generators/BiomeTagGenerator.java +++ b/fabric-convention-tags-v1/src/datagen/java/net/fabricmc/fabric/impl/tag/convention/datagen/generators/BiomeTagGenerator.java @@ -82,7 +82,7 @@ private void generateDimensionTags() { .add(BiomeKeys.STONY_PEAKS).add(BiomeKeys.MUSHROOM_FIELDS).add(BiomeKeys.DRIPSTONE_CAVES) .add(BiomeKeys.LUSH_CAVES).add(BiomeKeys.SNOWY_BEACH).add(BiomeKeys.SWAMP).add(BiomeKeys.STONY_SHORE) .add(BiomeKeys.DEEP_DARK).add(BiomeKeys.MANGROVE_SWAMP) - .addOptional(BiomeKeys.CHERRY_GROVE); + .add(BiomeKeys.CHERRY_GROVE); } private void generateCategoryTags() { @@ -235,6 +235,7 @@ private void generateClimateAndVegetationTags() { getOrCreateTagBuilder(ConventionalBiomeTags.FLORAL) .add(BiomeKeys.SUNFLOWER_PLAINS) .add(BiomeKeys.MEADOW) + .add(BiomeKeys.CHERRY_GROVE) .addOptionalTag(ConventionalBiomeTags.FLOWER_FORESTS); } diff --git a/fabric-convention-tags-v1/src/generated/resources/data/c/tags/worldgen/biome/floral.json b/fabric-convention-tags-v1/src/generated/resources/data/c/tags/worldgen/biome/floral.json index 5ceea1f7b7..393f0d0626 100644 --- a/fabric-convention-tags-v1/src/generated/resources/data/c/tags/worldgen/biome/floral.json +++ b/fabric-convention-tags-v1/src/generated/resources/data/c/tags/worldgen/biome/floral.json @@ -3,6 +3,7 @@ "values": [ "minecraft:sunflower_plains", "minecraft:meadow", + "minecraft:cherry_grove", { "id": "#c:flower_forests", "required": false diff --git a/fabric-convention-tags-v1/src/generated/resources/data/c/tags/worldgen/biome/in_overworld.json b/fabric-convention-tags-v1/src/generated/resources/data/c/tags/worldgen/biome/in_overworld.json index 5d6cb44cf1..b807c3a6a1 100644 --- a/fabric-convention-tags-v1/src/generated/resources/data/c/tags/worldgen/biome/in_overworld.json +++ b/fabric-convention-tags-v1/src/generated/resources/data/c/tags/worldgen/biome/in_overworld.json @@ -57,9 +57,6 @@ "minecraft:stony_shore", "minecraft:deep_dark", "minecraft:mangrove_swamp", - { - "id": "minecraft:cherry_grove", - "required": false - } + "minecraft:cherry_grove" ] } \ No newline at end of file diff --git a/fabric-crash-report-info-v1/build.gradle b/fabric-crash-report-info-v1/build.gradle index 1909659a61..85b5e378d4 100644 --- a/fabric-crash-report-info-v1/build.gradle +++ b/fabric-crash-report-info-v1/build.gradle @@ -1,2 +1 @@ -archivesBaseName = "fabric-crash-report-info-v1" version = getSubprojectVersion(project) diff --git a/fabric-data-generation-api-v1/build.gradle b/fabric-data-generation-api-v1/build.gradle index 9333f27e53..bbe27e46d7 100644 --- a/fabric-data-generation-api-v1/build.gradle +++ b/fabric-data-generation-api-v1/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-data-generation-api-v1" version = getSubprojectVersion(project) moduleDependencies(project, [ @@ -54,7 +53,7 @@ loom { test.dependsOn runDatagen task datapackZip(type: Zip, dependsOn: runDatagen) { - archiveFileName = "${archivesBaseName}-${project.version}-test-datapack.zip" + archiveFileName = "${base.archivesName.get()}-${project.version}-test-datapack.zip" destinationDirectory = layout.buildDirectory.dir('libs') from file("src/testmod/generated") diff --git a/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/DataGeneratorEntrypoint.java b/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/DataGeneratorEntrypoint.java index a0290152d0..48b739e151 100644 --- a/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/DataGeneratorEntrypoint.java +++ b/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/DataGeneratorEntrypoint.java @@ -60,4 +60,11 @@ default String getEffectiveModId() { */ default void buildRegistry(RegistryBuilder registryBuilder) { } + + /** + * Provides a callback for setting the sort priority of object keys in generated JSON files. + * @param callback a callback for setting the sort priority for a given key + */ + default void addJsonKeySortOrders(JsonKeySortOrderCallback callback) { + } } diff --git a/fabric-content-registries-v0/src/main/java/net/fabricmc/fabric/api/registry/LootEntryTypeRegistry.java b/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/JsonKeySortOrderCallback.java similarity index 51% rename from fabric-content-registries-v0/src/main/java/net/fabricmc/fabric/api/registry/LootEntryTypeRegistry.java rename to fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/JsonKeySortOrderCallback.java index 2e86a43a24..a65425581b 100644 --- a/fabric-content-registries-v0/src/main/java/net/fabricmc/fabric/api/registry/LootEntryTypeRegistry.java +++ b/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/JsonKeySortOrderCallback.java @@ -14,20 +14,19 @@ * limitations under the License. */ -package net.fabricmc.fabric.api.registry; - -import net.minecraft.loot.entry.LootPoolEntry; - -import net.fabricmc.fabric.impl.content.registry.LootEntryTypeRegistryImpl; +package net.fabricmc.fabric.api.datagen.v1; /** - * @deprecated Use {@link net.fabricmc.fabric.api.loot.v1.LootEntryTypeRegistry} + * Provides a callback for setting the sort priority of object keys in generated JSON files. */ -@Deprecated -public interface LootEntryTypeRegistry { - @Deprecated - LootEntryTypeRegistry INSTANCE = new LootEntryTypeRegistryImpl(); - - @Deprecated - void register(LootPoolEntry.Serializer serializer); +@FunctionalInterface +public interface JsonKeySortOrderCallback { + /** + * Sets the sort priority for a given object key within generated JSON files. + * @param key the key to set priority for + * @param priority the priority for the key, where keys with lower priority are sorted before keys with higher priority + * @implNote The default priority is 2. + * @see net.minecraft.data.DataProvider#JSON_KEY_SORT_ORDER + */ + void add(String key, int priority); } diff --git a/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/FabricAdvancementProvider.java b/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/FabricAdvancementProvider.java index 52030b8045..76cc3a67f5 100644 --- a/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/FabricAdvancementProvider.java +++ b/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/FabricAdvancementProvider.java @@ -28,6 +28,7 @@ import com.google.gson.JsonObject; import net.minecraft.advancement.Advancement; +import net.minecraft.advancement.AdvancementEntry; import net.minecraft.data.DataOutput; import net.minecraft.data.DataProvider; import net.minecraft.data.DataWriter; @@ -57,12 +58,12 @@ protected FabricAdvancementProvider(FabricDataOutput output) { * *

    Use {@link Advancement.Builder#build(Consumer, String)} to help build advancements. */ - public abstract void generateAdvancement(Consumer consumer); + public abstract void generateAdvancement(Consumer consumer); /** * Return a new exporter that applies the specified conditions to any advancement it receives. */ - protected Consumer withConditions(Consumer exporter, ConditionJsonProvider... conditions) { + protected Consumer withConditions(Consumer exporter, ConditionJsonProvider... conditions) { Preconditions.checkArgument(conditions.length > 0, "Must add at least one condition."); return advancement -> { FabricDataGenHelper.addConditions(advancement, conditions); @@ -73,18 +74,18 @@ protected Consumer withConditions(Consumer exporter, C @Override public CompletableFuture run(DataWriter writer) { final Set identifiers = Sets.newHashSet(); - final Set advancements = Sets.newHashSet(); + final Set advancements = Sets.newHashSet(); generateAdvancement(advancements::add); final List> futures = new ArrayList<>(); - for (Advancement advancement : advancements) { - if (!identifiers.add(advancement.getId())) { - throw new IllegalStateException("Duplicate advancement " + advancement.getId()); + for (AdvancementEntry advancement : advancements) { + if (!identifiers.add(advancement.id())) { + throw new IllegalStateException("Duplicate advancement " + advancement.id()); } - JsonObject advancementJson = advancement.createTask().toJson(); + JsonObject advancementJson = advancement.value().toJson(); ConditionJsonProvider.write(advancementJson, FabricDataGenHelper.consumeConditions(advancement)); futures.add(DataProvider.writeToPath(writer, advancementJson, getOutputPath(advancement))); @@ -93,8 +94,8 @@ public CompletableFuture run(DataWriter writer) { return CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new)); } - private Path getOutputPath(Advancement advancement) { - return pathResolver.resolveJson(advancement.getId()); + private Path getOutputPath(AdvancementEntry advancement) { + return pathResolver.resolveJson(advancement.id()); } @Override diff --git a/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/FabricDynamicRegistryProvider.java b/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/FabricDynamicRegistryProvider.java index 71588502d8..0be6cc8287 100644 --- a/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/FabricDynamicRegistryProvider.java +++ b/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/FabricDynamicRegistryProvider.java @@ -51,12 +51,13 @@ import net.minecraft.world.gen.feature.PlacedFeature; import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; +import net.fabricmc.fabric.api.event.registry.DynamicRegistries; +import net.fabricmc.fabric.impl.registry.sync.DynamicRegistriesImpl; /** * A provider to help with data-generation of dynamic registry objects, * such as biomes, features, or message types. */ -@ApiStatus.Experimental public abstract class FabricDynamicRegistryProvider implements DataProvider { private static final Logger LOGGER = LoggerFactory.getLogger(FabricDynamicRegistryProvider.class); @@ -79,7 +80,9 @@ public static final class Entries { @ApiStatus.Internal Entries(RegistryWrapper.WrapperLookup registries, String modId) { this.registries = registries; - this.queuedEntries = RegistryLoader.DYNAMIC_REGISTRIES.stream() + this.queuedEntries = DynamicRegistries.getDynamicRegistries().stream() + // Some modded dynamic registries might not be in the wrapper lookup, filter them out + .filter(e -> registries.getOptionalWrapper(e.key()).isPresent()) .collect(Collectors.toMap( e -> e.key().getValue(), e -> RegistryEntries.create(registries, e) @@ -219,7 +222,9 @@ public CompletableFuture run(DataWriter writer) { private CompletableFuture writeRegistryEntries(DataWriter writer, RegistryOps ops, RegistryEntries entries) { final RegistryKey> registry = entries.registry; - final DataOutput.PathResolver pathResolver = output.getResolver(DataOutput.OutputType.DATA_PACK, registry.getValue().getPath()); + final boolean shouldOmitNamespace = registry.getValue().getNamespace().equals(Identifier.DEFAULT_NAMESPACE) || !DynamicRegistriesImpl.FABRIC_DYNAMIC_REGISTRY_KEYS.contains(registry); + final String directoryName = shouldOmitNamespace ? registry.getValue().getPath() : registry.getValue().getNamespace() + "/" + registry.getValue().getPath(); + final DataOutput.PathResolver pathResolver = output.getResolver(DataOutput.OutputType.DATA_PACK, directoryName); final List> futures = new ArrayList<>(); for (Map.Entry, T> entry : entries.entries.entrySet()) { diff --git a/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/FabricLanguageProvider.java b/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/FabricLanguageProvider.java index 5454389a43..849922531f 100644 --- a/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/FabricLanguageProvider.java +++ b/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/FabricLanguageProvider.java @@ -199,7 +199,7 @@ default void add(EntityAttribute entityAttribute, String value) { * @param value The value of the entry. */ default void add(StatType statType, String value) { - add(statType.getTranslationKey(), value); + add("stat_type." + Registries.STAT_TYPE.getId(statType).toString().replace(':', '.'), value); } /** diff --git a/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/FabricRecipeProvider.java b/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/FabricRecipeProvider.java index 4a1dd58812..a555864355 100644 --- a/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/FabricRecipeProvider.java +++ b/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/FabricRecipeProvider.java @@ -20,14 +20,17 @@ import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; -import java.util.function.Consumer; import com.google.common.base.Preconditions; import com.google.common.collect.Sets; import com.google.gson.JsonObject; +import net.minecraft.advancement.Advancement; +import net.minecraft.advancement.AdvancementEntry; import net.minecraft.data.DataProvider; import net.minecraft.data.DataWriter; +import net.minecraft.data.server.recipe.CraftingRecipeJsonBuilder; +import net.minecraft.data.server.recipe.RecipeExporter; import net.minecraft.data.server.recipe.RecipeJsonProvider; import net.minecraft.data.server.recipe.RecipeProvider; import net.minecraft.data.server.recipe.ShapedRecipeJsonBuilder; @@ -56,16 +59,24 @@ public FabricRecipeProvider(FabricDataOutput output) { * Implement this method and then use the range of methods in {@link RecipeProvider} or from one of the recipe json factories such as {@link ShapedRecipeJsonBuilder} or {@link ShapelessRecipeJsonBuilder}. */ @Override - public abstract void generate(Consumer exporter); + public abstract void generate(RecipeExporter exporter); /** * Return a new exporter that applies the specified conditions to any recipe json provider it receives. */ - protected Consumer withConditions(Consumer exporter, ConditionJsonProvider... conditions) { + protected RecipeExporter withConditions(RecipeExporter exporter, ConditionJsonProvider... conditions) { Preconditions.checkArgument(conditions.length > 0, "Must add at least one condition."); - return json -> { - FabricDataGenHelper.addConditions(json, conditions); - exporter.accept(json); + return new RecipeExporter() { + @Override + public void accept(RecipeJsonProvider provider) { + FabricDataGenHelper.addConditions(provider, conditions); + exporter.accept(provider); + } + + @Override + public Advancement.Builder getAdvancementBuilder() { + return exporter.getAdvancementBuilder(); + } }; } @@ -73,23 +84,33 @@ protected Consumer withConditions(Consumer run(DataWriter writer) { Set generatedRecipes = Sets.newHashSet(); List> list = new ArrayList<>(); - generate(provider -> { - Identifier identifier = getRecipeIdentifier(provider.getRecipeId()); + generate(new RecipeExporter() { + @Override + public void accept(RecipeJsonProvider provider) { + Identifier identifier = getRecipeIdentifier(provider.id()); - if (!generatedRecipes.add(identifier)) { - throw new IllegalStateException("Duplicate recipe " + identifier); - } + if (!generatedRecipes.add(identifier)) { + throw new IllegalStateException("Duplicate recipe " + identifier); + } - JsonObject recipeJson = provider.toJson(); - ConditionJsonProvider[] conditions = FabricDataGenHelper.consumeConditions(provider); - ConditionJsonProvider.write(recipeJson, conditions); + JsonObject recipeJson = provider.toJson(); + ConditionJsonProvider[] conditions = FabricDataGenHelper.consumeConditions(provider); + ConditionJsonProvider.write(recipeJson, conditions); - list.add(DataProvider.writeToPath(writer, recipeJson, this.recipesPathResolver.resolveJson(identifier))); - JsonObject advancementJson = provider.toAdvancementJson(); + list.add(DataProvider.writeToPath(writer, recipeJson, recipesPathResolver.resolveJson(identifier))); + + AdvancementEntry advancement = provider.advancement(); + + if (advancement != null) { + JsonObject advancementJson = advancement.value().toJson(); + ConditionJsonProvider.write(advancementJson, conditions); + list.add(DataProvider.writeToPath(writer, advancementJson, advancementsPathResolver.resolveJson(getRecipeIdentifier(advancement.id())))); + } + } - if (advancementJson != null) { - ConditionJsonProvider.write(advancementJson, conditions); - list.add(DataProvider.writeToPath(writer, advancementJson, this.advancementsPathResolver.resolveJson(getRecipeIdentifier(provider.getAdvancementId())))); + @Override + public Advancement.Builder getAdvancementBuilder() { + return Advancement.Builder.createUntelemetered().parent(CraftingRecipeJsonBuilder.ROOT); } }); return CompletableFuture.allOf(list.toArray(CompletableFuture[]::new)); diff --git a/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/SimpleFabricLootTableProvider.java b/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/SimpleFabricLootTableProvider.java index 047ae60035..48606a6642 100644 --- a/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/SimpleFabricLootTableProvider.java +++ b/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/SimpleFabricLootTableProvider.java @@ -46,6 +46,6 @@ public CompletableFuture run(DataWriter writer) { @Override public String getName() { - return Objects.requireNonNull(LootContextTypes.getId(lootContextType), "Could not get id for loot context type") + " Loot Table"; + return Objects.requireNonNull(LootContextTypes.MAP.inverse().get(lootContextType), "Could not get id for loot context type") + " Loot Table"; } } diff --git a/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/impl/datagen/FabricDataGenHelper.java b/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/impl/datagen/FabricDataGenHelper.java index 6cbf74ca33..e6cba0e02d 100644 --- a/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/impl/datagen/FabricDataGenHelper.java +++ b/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/impl/datagen/FabricDataGenHelper.java @@ -20,6 +20,7 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; @@ -28,11 +29,13 @@ import com.mojang.logging.LogUtils; import com.mojang.serialization.Lifecycle; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import org.apache.commons.lang3.ArrayUtils; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import net.minecraft.data.DataProvider; import net.minecraft.registry.BuiltinRegistries; import net.minecraft.registry.DynamicRegistryManager; import net.minecraft.registry.Registerable; @@ -107,6 +110,9 @@ private static void runInternal() { final List entrypoints = dataGeneratorInitializers.stream().map(EntrypointContainer::getEntrypoint).toList(); CompletableFuture registriesFuture = CompletableFuture.supplyAsync(() -> createRegistryWrapper(entrypoints), Util.getMainWorkerExecutor()); + Object2IntOpenHashMap jsonKeySortOrders = (Object2IntOpenHashMap) DataProvider.JSON_KEY_SORT_ORDER; + Object2IntOpenHashMap defaultJsonKeySortOrders = new Object2IntOpenHashMap<>(jsonKeySortOrders); + for (EntrypointContainer entrypointContainer : dataGeneratorInitializers) { final String id = entrypointContainer.getProvider().getMetadata().getId(); @@ -123,6 +129,13 @@ private static void runInternal() { final String effectiveModId = entrypoint.getEffectiveModId(); ModContainer modContainer = entrypointContainer.getProvider(); + HashSet keys = new HashSet<>(); + entrypoint.addJsonKeySortOrders((key, value) -> { + Objects.requireNonNull(key, "Tried to register a priority for a null key"); + jsonKeySortOrders.put(key, value); + keys.add(key); + }); + if (effectiveModId != null) { modContainer = FabricLoader.getInstance().getModContainer(effectiveModId).orElseThrow(() -> new RuntimeException("Failed to find effective mod container for mod id (%s)".formatted(effectiveModId))); } @@ -130,6 +143,9 @@ private static void runInternal() { FabricDataGenerator dataGenerator = new FabricDataGenerator(outputDir, modContainer, STRICT_VALIDATION, registriesFuture); entrypoint.onInitializeDataGenerator(dataGenerator); dataGenerator.run(); + + jsonKeySortOrders.keySet().removeAll(keys); + jsonKeySortOrders.putAll(defaultJsonKeySortOrders); } catch (Throwable t) { throw new RuntimeException("Failed to run data generator from mod (%s)".formatted(id), t); } diff --git a/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/impl/datagen/loot/FabricLootTableProviderImpl.java b/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/impl/datagen/loot/FabricLootTableProviderImpl.java index ea5e08e420..6368c485b3 100644 --- a/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/impl/datagen/loot/FabricLootTableProviderImpl.java +++ b/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/impl/datagen/loot/FabricLootTableProviderImpl.java @@ -25,14 +25,15 @@ import com.google.common.collect.Maps; import com.google.gson.JsonObject; +import com.mojang.serialization.JsonOps; import net.minecraft.data.DataOutput; import net.minecraft.data.DataProvider; import net.minecraft.data.DataWriter; -import net.minecraft.loot.LootDataType; import net.minecraft.loot.LootTable; import net.minecraft.loot.context.LootContextType; import net.minecraft.util.Identifier; +import net.minecraft.util.Util; import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; import net.fabricmc.fabric.api.datagen.v1.provider.FabricBlockLootTableProvider; @@ -65,7 +66,7 @@ public static CompletableFuture run( final List> futures = new ArrayList<>(); for (Map.Entry entry : builders.entrySet()) { - JsonObject tableJson = (JsonObject) LootDataType.LOOT_TABLES.getGson().toJsonTree(entry.getValue()); + JsonObject tableJson = (JsonObject) Util.getResult(LootTable.CODEC.encodeStart(JsonOps.INSTANCE, entry.getValue()), IllegalStateException::new); ConditionJsonProvider.write(tableJson, conditionMap.remove(entry.getKey())); futures.add(DataProvider.writeToPath(writer, tableJson, getOutputPath(fabricDataOutput, entry.getKey()))); diff --git a/fabric-data-generation-api-v1/src/main/resources/fabric-data-generation-api-v1.accesswidener b/fabric-data-generation-api-v1/src/main/resources/fabric-data-generation-api-v1.accesswidener index 96ceeb0bdd..8ac8f29b3b 100644 --- a/fabric-data-generation-api-v1/src/main/resources/fabric-data-generation-api-v1.accesswidener +++ b/fabric-data-generation-api-v1/src/main/resources/fabric-data-generation-api-v1.accesswidener @@ -6,120 +6,133 @@ accessWidener v2 named accessible field net/minecraft/data/DataGenerator output Lnet/minecraft/data/DataOutput; mutable field net/minecraft/data/DataGenerator output Lnet/minecraft/data/DataOutput; -accessible field net/minecraft/data/server/recipe/RecipeProvider recipesPathResolver Lnet/minecraft/data/DataOutput$PathResolver; -accessible field net/minecraft/data/server/recipe/RecipeProvider advancementsPathResolver Lnet/minecraft/data/DataOutput$PathResolver; +accessible field net/minecraft/data/server/recipe/RecipeProvider recipesPathResolver Lnet/minecraft/data/DataOutput$PathResolver; +accessible field net/minecraft/data/server/recipe/RecipeProvider advancementsPathResolver Lnet/minecraft/data/DataOutput$PathResolver; -accessible field net/minecraft/data/server/tag/TagProvider$ProvidedTagBuilder builder Lnet/minecraft/registry/tag/TagBuilder; -extendable method net/minecraft/data/server/tag/TagProvider$ProvidedTagBuilder add (Lnet/minecraft/registry/RegistryKey;)Lnet/minecraft/data/server/tag/TagProvider$ProvidedTagBuilder; -extendable method net/minecraft/data/server/tag/TagProvider$ProvidedTagBuilder add ([Lnet/minecraft/registry/RegistryKey;)Lnet/minecraft/data/server/tag/TagProvider$ProvidedTagBuilder; +accessible field net/minecraft/data/server/tag/TagProvider$ProvidedTagBuilder builder Lnet/minecraft/registry/tag/TagBuilder; +extendable method net/minecraft/data/server/tag/TagProvider$ProvidedTagBuilder add (Lnet/minecraft/registry/RegistryKey;)Lnet/minecraft/data/server/tag/TagProvider$ProvidedTagBuilder; +extendable method net/minecraft/data/server/tag/TagProvider$ProvidedTagBuilder add ([Lnet/minecraft/registry/RegistryKey;)Lnet/minecraft/data/server/tag/TagProvider$ProvidedTagBuilder; -accessible field net/minecraft/data/server/tag/TagProvider tagBuilders Ljava/util/Map; +accessible field net/minecraft/data/server/tag/TagProvider tagBuilders Ljava/util/Map; -accessible field net/minecraft/data/server/loottable/BlockLootTableGenerator lootTables Ljava/util/Map; +accessible field net/minecraft/data/server/loottable/BlockLootTableGenerator lootTables Ljava/util/Map; -extendable method net/minecraft/registry/tag/TagEntry (Lnet/minecraft/util/Identifier;ZZ)V -accessible field net/minecraft/registry/tag/TagEntry id Lnet/minecraft/util/Identifier; -accessible field net/minecraft/registry/tag/TagEntry tag Z -accessible field net/minecraft/registry/tag/TagEntry required Z +extendable method net/minecraft/registry/tag/TagEntry (Lnet/minecraft/util/Identifier;ZZ)V +accessible field net/minecraft/registry/tag/TagEntry id Lnet/minecraft/util/Identifier; +accessible field net/minecraft/registry/tag/TagEntry tag Z +accessible field net/minecraft/registry/tag/TagEntry required Z -extendable method net/minecraft/data/DataOutput$PathResolver (Lnet/minecraft/data/DataOutput;Lnet/minecraft/data/DataOutput$OutputType;Ljava/lang/String;)V -accessible field net/minecraft/data/DataOutput$PathResolver rootPath Ljava/nio/file/Path; -accessible field net/minecraft/data/DataOutput$PathResolver directoryName Ljava/lang/String; +extendable method net/minecraft/data/DataOutput$PathResolver (Lnet/minecraft/data/DataOutput;Lnet/minecraft/data/DataOutput$OutputType;Ljava/lang/String;)V +accessible field net/minecraft/data/DataOutput$PathResolver rootPath Ljava/nio/file/Path; +accessible field net/minecraft/data/DataOutput$PathResolver directoryName Ljava/lang/String; -extendable method net/minecraft/data/DataGenerator$Pack (Lnet/minecraft/data/DataGenerator;ZLjava/lang/String;Lnet/minecraft/data/DataOutput;)V +extendable method net/minecraft/data/DataGenerator$Pack (Lnet/minecraft/data/DataGenerator;ZLjava/lang/String;Lnet/minecraft/data/DataOutput;)V -accessible field net/minecraft/registry/BuiltinRegistries REGISTRY_BUILDER Lnet/minecraft/registry/RegistryBuilder; -accessible method net/minecraft/registry/BuiltinRegistries validate (Lnet/minecraft/registry/RegistryWrapper$WrapperLookup;)V -accessible field net/minecraft/registry/RegistryBuilder registries Ljava/util/List; -accessible class net/minecraft/registry/RegistryBuilder$RegistryInfo +accessible field net/minecraft/registry/BuiltinRegistries REGISTRY_BUILDER Lnet/minecraft/registry/RegistryBuilder; +accessible method net/minecraft/registry/BuiltinRegistries validate (Lnet/minecraft/registry/RegistryWrapper$WrapperLookup;)V +accessible field net/minecraft/registry/RegistryBuilder registries Ljava/util/List; +accessible class net/minecraft/registry/RegistryBuilder$RegistryInfo -transitive-accessible method net/minecraft/data/family/BlockFamilies register (Lnet/minecraft/block/Block;)Lnet/minecraft/data/family/BlockFamily$Builder; +accessible field net/minecraft/loot/context/LootContextTypes MAP Lcom/google/common/collect/BiMap; -transitive-accessible field net/minecraft/data/client/BlockStateModelGenerator blockStateCollector Ljava/util/function/Consumer; -transitive-accessible field net/minecraft/data/client/BlockStateModelGenerator modelCollector Ljava/util/function/BiConsumer; +transitive-accessible method net/minecraft/data/family/BlockFamilies register (Lnet/minecraft/block/Block;)Lnet/minecraft/data/family/BlockFamily$Builder; -transitive-accessible field net/minecraft/data/client/ItemModelGenerator writer Ljava/util/function/BiConsumer; +transitive-accessible field net/minecraft/data/client/BlockStateModelGenerator blockStateCollector Ljava/util/function/Consumer; +transitive-accessible field net/minecraft/data/client/BlockStateModelGenerator modelCollector Ljava/util/function/BiConsumer; -transitive-accessible method net/minecraft/data/client/TextureKey of (Ljava/lang/String;)Lnet/minecraft/data/client/TextureKey; -transitive-accessible method net/minecraft/data/client/TextureKey of (Ljava/lang/String;Lnet/minecraft/data/client/TextureKey;)Lnet/minecraft/data/client/TextureKey; +transitive-accessible field net/minecraft/data/client/ItemModelGenerator writer Ljava/util/function/BiConsumer; -transitive-extendable method net/minecraft/data/server/tag/TagProvider$ProvidedTagBuilder add ([Lnet/minecraft/registry/RegistryKey;)Lnet/minecraft/data/server/tag/TagProvider$ProvidedTagBuilder; +transitive-accessible method net/minecraft/data/client/TextureKey of (Ljava/lang/String;)Lnet/minecraft/data/client/TextureKey; +transitive-accessible method net/minecraft/data/client/TextureKey of (Ljava/lang/String;Lnet/minecraft/data/client/TextureKey;)Lnet/minecraft/data/client/TextureKey; -transitive-accessible method net/minecraft/data/client/TexturedModel makeFactory (Ljava/util/function/Function;Lnet/minecraft/data/client/Model;)Lnet/minecraft/data/client/TexturedModel$Factory; +transitive-extendable method net/minecraft/data/server/tag/TagProvider$ProvidedTagBuilder add ([Lnet/minecraft/registry/RegistryKey;)Lnet/minecraft/data/server/tag/TagProvider$ProvidedTagBuilder; -transitive-accessible class net/minecraft/data/client/BlockStateModelGenerator$TintType -transitive-accessible class net/minecraft/data/client/BlockStateModelGenerator$BlockTexturePool -transitive-accessible class net/minecraft/data/client/BlockStateModelGenerator$LogTexturePool -transitive-accessible class net/minecraft/data/client/BlockStateModelGenerator$BuiltinModelPool +transitive-accessible method net/minecraft/data/client/TexturedModel makeFactory (Ljava/util/function/Function;Lnet/minecraft/data/client/Model;)Lnet/minecraft/data/client/TexturedModel$Factory; -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider saveRecipeAdvancement (Lnet/minecraft/data/DataWriter;Lnet/minecraft/util/Identifier;Lnet/minecraft/advancement/Advancement$Builder;)Ljava/util/concurrent/CompletableFuture; -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider generate (Ljava/util/function/Consumer;)V -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider generateFamilies (Ljava/util/function/Consumer;Lnet/minecraft/resource/featuretoggle/FeatureSet;)V -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerSingleOutputShapelessRecipe (Ljava/util/function/Consumer;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;Ljava/lang/String;)V -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerShapelessRecipe (Ljava/util/function/Consumer;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;Ljava/lang/String;I)V -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerSmelting (Ljava/util/function/Consumer;Ljava/util/List;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;FILjava/lang/String;)V -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerBlasting (Ljava/util/function/Consumer;Ljava/util/List;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;FILjava/lang/String;)V -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerMultipleOptions (Ljava/util/function/Consumer;Lnet/minecraft/recipe/RecipeSerializer;Ljava/util/List;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;FILjava/lang/String;Ljava/lang/String;)V -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerNetheriteUpgradeRecipe (Ljava/util/function/Consumer;Lnet/minecraft/item/Item;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/Item;)V -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerSmithingTrimRecipe (Ljava/util/function/Consumer;Lnet/minecraft/item/Item;Lnet/minecraft/util/Identifier;)V -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offer2x2CompactingRecipe (Ljava/util/function/Consumer;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerCompactingRecipe (Ljava/util/function/Consumer;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;Ljava/lang/String;)V -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerCompactingRecipe (Ljava/util/function/Consumer;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerPlanksRecipe2 (Ljava/util/function/Consumer;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/registry/tag/TagKey;I)V -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerPlanksRecipe (Ljava/util/function/Consumer;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/registry/tag/TagKey;I)V -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerBarkBlockRecipe (Ljava/util/function/Consumer;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerBoatRecipe (Ljava/util/function/Consumer;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerChestBoatRecipe (Ljava/util/function/Consumer;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V +transitive-accessible class net/minecraft/data/client/BlockStateModelGenerator$TintType +transitive-accessible class net/minecraft/data/client/BlockStateModelGenerator$BlockTexturePool +transitive-accessible class net/minecraft/data/client/BlockStateModelGenerator$LogTexturePool +transitive-accessible class net/minecraft/data/client/BlockStateModelGenerator$BuiltinModelPool + +transitive-accessible field net/minecraft/data/server/loottable/BlockLootTableGenerator WITH_SILK_TOUCH Lnet/minecraft/loot/condition/LootCondition$Builder; +transitive-accessible field net/minecraft/data/server/loottable/BlockLootTableGenerator WITHOUT_SILK_TOUCH Lnet/minecraft/loot/condition/LootCondition$Builder; +transitive-accessible field net/minecraft/data/server/loottable/BlockLootTableGenerator WITH_SHEARS Lnet/minecraft/loot/condition/LootCondition$Builder; +transitive-accessible field net/minecraft/data/server/loottable/BlockLootTableGenerator WITH_SILK_TOUCH_OR_SHEARS Lnet/minecraft/loot/condition/LootCondition$Builder; +transitive-accessible field net/minecraft/data/server/loottable/BlockLootTableGenerator WITHOUT_SILK_TOUCH_NOR_SHEARS Lnet/minecraft/loot/condition/LootCondition$Builder; +transitive-accessible field net/minecraft/data/server/loottable/BlockLootTableGenerator SAPLING_DROP_CHANCE [F +transitive-accessible field net/minecraft/data/server/loottable/BlockLootTableGenerator LEAVES_STICK_DROP_CHANCE [F + +### Generated access wideners below + +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider saveRecipeAdvancement (Lnet/minecraft/data/DataWriter;Lnet/minecraft/advancement/AdvancementEntry;)Ljava/util/concurrent/CompletableFuture; +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider generate (Lnet/minecraft/data/server/recipe/RecipeExporter;)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider generateFamilies (Lnet/minecraft/data/server/recipe/RecipeExporter;Lnet/minecraft/resource/featuretoggle/FeatureSet;)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerSingleOutputShapelessRecipe (Lnet/minecraft/data/server/recipe/RecipeExporter;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;Ljava/lang/String;)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerShapelessRecipe (Lnet/minecraft/data/server/recipe/RecipeExporter;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;Ljava/lang/String;I)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerSmelting (Lnet/minecraft/data/server/recipe/RecipeExporter;Ljava/util/List;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;FILjava/lang/String;)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerBlasting (Lnet/minecraft/data/server/recipe/RecipeExporter;Ljava/util/List;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;FILjava/lang/String;)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerMultipleOptions (Lnet/minecraft/data/server/recipe/RecipeExporter;Lnet/minecraft/recipe/RecipeSerializer;Ljava/util/List;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;FILjava/lang/String;Ljava/lang/String;)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerNetheriteUpgradeRecipe (Lnet/minecraft/data/server/recipe/RecipeExporter;Lnet/minecraft/item/Item;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/Item;)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerSmithingTrimRecipe (Lnet/minecraft/data/server/recipe/RecipeExporter;Lnet/minecraft/item/Item;Lnet/minecraft/util/Identifier;)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offer2x2CompactingRecipe (Lnet/minecraft/data/server/recipe/RecipeExporter;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerCompactingRecipe (Lnet/minecraft/data/server/recipe/RecipeExporter;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;Ljava/lang/String;)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerCompactingRecipe (Lnet/minecraft/data/server/recipe/RecipeExporter;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerPlanksRecipe2 (Lnet/minecraft/data/server/recipe/RecipeExporter;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/registry/tag/TagKey;I)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerPlanksRecipe (Lnet/minecraft/data/server/recipe/RecipeExporter;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/registry/tag/TagKey;I)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerBarkBlockRecipe (Lnet/minecraft/data/server/recipe/RecipeExporter;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerBoatRecipe (Lnet/minecraft/data/server/recipe/RecipeExporter;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerChestBoatRecipe (Lnet/minecraft/data/server/recipe/RecipeExporter;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider createTransmutationRecipe (Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/recipe/Ingredient;)Lnet/minecraft/data/server/recipe/CraftingRecipeJsonBuilder; transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider createDoorRecipe (Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/recipe/Ingredient;)Lnet/minecraft/data/server/recipe/CraftingRecipeJsonBuilder; transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider createFenceRecipe (Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/recipe/Ingredient;)Lnet/minecraft/data/server/recipe/CraftingRecipeJsonBuilder; transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider createFenceGateRecipe (Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/recipe/Ingredient;)Lnet/minecraft/data/server/recipe/CraftingRecipeJsonBuilder; -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerPressurePlateRecipe (Ljava/util/function/Consumer;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerPressurePlateRecipe (Lnet/minecraft/data/server/recipe/RecipeExporter;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider createPressurePlateRecipe (Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/recipe/Ingredient;)Lnet/minecraft/data/server/recipe/CraftingRecipeJsonBuilder; -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerSlabRecipe (Ljava/util/function/Consumer;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerSlabRecipe (Lnet/minecraft/data/server/recipe/RecipeExporter;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider createSlabRecipe (Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/recipe/Ingredient;)Lnet/minecraft/data/server/recipe/CraftingRecipeJsonBuilder; transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider createStairsRecipe (Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/recipe/Ingredient;)Lnet/minecraft/data/server/recipe/CraftingRecipeJsonBuilder; transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider createTrapdoorRecipe (Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/recipe/Ingredient;)Lnet/minecraft/data/server/recipe/CraftingRecipeJsonBuilder; transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider createSignRecipe (Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/recipe/Ingredient;)Lnet/minecraft/data/server/recipe/CraftingRecipeJsonBuilder; -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerHangingSignRecipe (Ljava/util/function/Consumer;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerDyeableRecipes (Ljava/util/function/Consumer;Ljava/util/List;Ljava/util/List;Ljava/lang/String;)V -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerCarpetRecipe (Ljava/util/function/Consumer;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerBedRecipe (Ljava/util/function/Consumer;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerBannerRecipe (Ljava/util/function/Consumer;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerStainedGlassDyeingRecipe (Ljava/util/function/Consumer;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerStainedGlassPaneRecipe (Ljava/util/function/Consumer;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerStainedGlassPaneDyeingRecipe (Ljava/util/function/Consumer;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerTerracottaDyeingRecipe (Ljava/util/function/Consumer;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerConcretePowderDyeingRecipe (Ljava/util/function/Consumer;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerCandleDyeingRecipe (Ljava/util/function/Consumer;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerWallRecipe (Ljava/util/function/Consumer;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerHangingSignRecipe (Lnet/minecraft/data/server/recipe/RecipeExporter;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerDyeableRecipes (Lnet/minecraft/data/server/recipe/RecipeExporter;Ljava/util/List;Ljava/util/List;Ljava/lang/String;)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerCarpetRecipe (Lnet/minecraft/data/server/recipe/RecipeExporter;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerBedRecipe (Lnet/minecraft/data/server/recipe/RecipeExporter;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerBannerRecipe (Lnet/minecraft/data/server/recipe/RecipeExporter;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerStainedGlassDyeingRecipe (Lnet/minecraft/data/server/recipe/RecipeExporter;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerStainedGlassPaneRecipe (Lnet/minecraft/data/server/recipe/RecipeExporter;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerStainedGlassPaneDyeingRecipe (Lnet/minecraft/data/server/recipe/RecipeExporter;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerTerracottaDyeingRecipe (Lnet/minecraft/data/server/recipe/RecipeExporter;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerConcretePowderDyeingRecipe (Lnet/minecraft/data/server/recipe/RecipeExporter;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerCandleDyeingRecipe (Lnet/minecraft/data/server/recipe/RecipeExporter;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerWallRecipe (Lnet/minecraft/data/server/recipe/RecipeExporter;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider getWallRecipe (Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/recipe/Ingredient;)Lnet/minecraft/data/server/recipe/CraftingRecipeJsonBuilder; -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerPolishedStoneRecipe (Ljava/util/function/Consumer;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerPolishedStoneRecipe (Lnet/minecraft/data/server/recipe/RecipeExporter;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider createCondensingRecipe (Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/recipe/Ingredient;)Lnet/minecraft/data/server/recipe/CraftingRecipeJsonBuilder; -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerCutCopperRecipe (Ljava/util/function/Consumer;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerCutCopperRecipe (Lnet/minecraft/data/server/recipe/RecipeExporter;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider createCutCopperRecipe (Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/recipe/Ingredient;)Lnet/minecraft/data/server/recipe/ShapedRecipeJsonBuilder; -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerChiseledBlockRecipe (Ljava/util/function/Consumer;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerMosaicRecipe (Ljava/util/function/Consumer;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerChiseledBlockRecipe (Lnet/minecraft/data/server/recipe/RecipeExporter;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerMosaicRecipe (Lnet/minecraft/data/server/recipe/RecipeExporter;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider createChiseledBlockRecipe (Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/recipe/Ingredient;)Lnet/minecraft/data/server/recipe/ShapedRecipeJsonBuilder; -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerStonecuttingRecipe (Ljava/util/function/Consumer;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerStonecuttingRecipe (Ljava/util/function/Consumer;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;I)V -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerCrackingRecipe (Ljava/util/function/Consumer;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerReversibleCompactingRecipes (Ljava/util/function/Consumer;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;)V -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerReversibleCompactingRecipesWithCompactingRecipeGroup (Ljava/util/function/Consumer;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;Ljava/lang/String;Ljava/lang/String;)V -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerReversibleCompactingRecipesWithReverseRecipeGroup (Ljava/util/function/Consumer;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;Ljava/lang/String;Ljava/lang/String;)V -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerReversibleCompactingRecipes (Ljava/util/function/Consumer;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerSmithingTemplateCopyingRecipe (Ljava/util/function/Consumer;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/registry/tag/TagKey;)V -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerSmithingTemplateCopyingRecipe (Ljava/util/function/Consumer;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider generateCookingRecipes (Ljava/util/function/Consumer;Ljava/lang/String;Lnet/minecraft/recipe/RecipeSerializer;I)V -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerFoodCookingRecipe (Ljava/util/function/Consumer;Ljava/lang/String;Lnet/minecraft/recipe/RecipeSerializer;ILnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;F)V -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerWaxingRecipes (Ljava/util/function/Consumer;)V -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider generateFamily (Ljava/util/function/Consumer;Lnet/minecraft/data/family/BlockFamily;)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerStonecuttingRecipe (Lnet/minecraft/data/server/recipe/RecipeExporter;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerStonecuttingRecipe (Lnet/minecraft/data/server/recipe/RecipeExporter;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;I)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerCrackingRecipe (Lnet/minecraft/data/server/recipe/RecipeExporter;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerReversibleCompactingRecipes (Lnet/minecraft/data/server/recipe/RecipeExporter;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerReversibleCompactingRecipesWithCompactingRecipeGroup (Lnet/minecraft/data/server/recipe/RecipeExporter;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;Ljava/lang/String;Ljava/lang/String;)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerReversibleCompactingRecipesWithReverseRecipeGroup (Lnet/minecraft/data/server/recipe/RecipeExporter;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;Ljava/lang/String;Ljava/lang/String;)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerReversibleCompactingRecipes (Lnet/minecraft/data/server/recipe/RecipeExporter;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/recipe/book/RecipeCategory;Lnet/minecraft/item/ItemConvertible;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerSmithingTemplateCopyingRecipe (Lnet/minecraft/data/server/recipe/RecipeExporter;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/registry/tag/TagKey;)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerSmithingTemplateCopyingRecipe (Lnet/minecraft/data/server/recipe/RecipeExporter;Lnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider generateCookingRecipes (Lnet/minecraft/data/server/recipe/RecipeExporter;Ljava/lang/String;Lnet/minecraft/recipe/RecipeSerializer;I)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerFoodCookingRecipe (Lnet/minecraft/data/server/recipe/RecipeExporter;Ljava/lang/String;Lnet/minecraft/recipe/RecipeSerializer;ILnet/minecraft/item/ItemConvertible;Lnet/minecraft/item/ItemConvertible;F)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider offerWaxingRecipes (Lnet/minecraft/data/server/recipe/RecipeExporter;)V +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider generateFamily (Lnet/minecraft/data/server/recipe/RecipeExporter;Lnet/minecraft/data/family/BlockFamily;)V transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider getVariantRecipeInput (Lnet/minecraft/data/family/BlockFamily;Lnet/minecraft/data/family/BlockFamily$Variant;)Lnet/minecraft/block/Block; -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider requireEnteringFluid (Lnet/minecraft/block/Block;)Lnet/minecraft/advancement/criterion/EnterBlockCriterion$Conditions; -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider conditionsFromItem (Lnet/minecraft/predicate/NumberRange$IntRange;Lnet/minecraft/item/ItemConvertible;)Lnet/minecraft/advancement/criterion/InventoryChangedCriterion$Conditions; -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider conditionsFromItem (Lnet/minecraft/item/ItemConvertible;)Lnet/minecraft/advancement/criterion/InventoryChangedCriterion$Conditions; -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider conditionsFromTag (Lnet/minecraft/registry/tag/TagKey;)Lnet/minecraft/advancement/criterion/InventoryChangedCriterion$Conditions; -transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider conditionsFromItemPredicates ([Lnet/minecraft/predicate/item/ItemPredicate;)Lnet/minecraft/advancement/criterion/InventoryChangedCriterion$Conditions; +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider requireEnteringFluid (Lnet/minecraft/block/Block;)Lnet/minecraft/advancement/AdvancementCriterion; +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider conditionsFromItem (Lnet/minecraft/predicate/NumberRange$IntRange;Lnet/minecraft/item/ItemConvertible;)Lnet/minecraft/advancement/AdvancementCriterion; +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider conditionsFromItem (Lnet/minecraft/item/ItemConvertible;)Lnet/minecraft/advancement/AdvancementCriterion; +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider conditionsFromTag (Lnet/minecraft/registry/tag/TagKey;)Lnet/minecraft/advancement/AdvancementCriterion; +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider conditionsFromPredicates ([Lnet/minecraft/predicate/item/ItemPredicate$Builder;)Lnet/minecraft/advancement/AdvancementCriterion; +transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider conditionsFromItemPredicates ([Lnet/minecraft/predicate/item/ItemPredicate;)Lnet/minecraft/advancement/AdvancementCriterion; transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider hasItem (Lnet/minecraft/item/ItemConvertible;)Ljava/lang/String; transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider getItemPath (Lnet/minecraft/item/ItemConvertible;)Ljava/lang/String; transitive-accessible method net/minecraft/data/server/recipe/RecipeProvider getRecipeName (Lnet/minecraft/item/ItemConvertible;)Ljava/lang/String; @@ -222,7 +235,7 @@ transitive-accessible method net/minecraft/data/client/BlockStateModelGenerator transitive-accessible method net/minecraft/data/client/BlockStateModelGenerator buildBlockStateVariants (Ljava/util/List;Ljava/util/function/UnaryOperator;)Ljava/util/List; transitive-accessible method net/minecraft/data/client/BlockStateModelGenerator registerLantern (Lnet/minecraft/block/Block;)V transitive-accessible method net/minecraft/data/client/BlockStateModelGenerator registerTopSoil (Lnet/minecraft/block/Block;Lnet/minecraft/util/Identifier;Lnet/minecraft/data/client/BlockStateVariant;)V -transitive-accessible method net/minecraft/data/client/BlockStateModelGenerator registerPressurePlate (Lnet/minecraft/block/Block;Lnet/minecraft/block/Block;)V +transitive-accessible method net/minecraft/data/client/BlockStateModelGenerator registerWeightedPressurePlate (Lnet/minecraft/block/Block;Lnet/minecraft/block/Block;)V transitive-accessible method net/minecraft/data/client/BlockStateModelGenerator registerParented (Lnet/minecraft/block/Block;Lnet/minecraft/block/Block;)V transitive-accessible method net/minecraft/data/client/BlockStateModelGenerator registerNorthDefaultHorizontalRotation (Lnet/minecraft/block/Block;)V transitive-accessible method net/minecraft/data/client/BlockStateModelGenerator registerPiston (Lnet/minecraft/block/Block;Lnet/minecraft/util/Identifier;Lnet/minecraft/data/client/TextureMap;)V diff --git a/fabric-data-generation-api-v1/src/testmod/java/net/fabricmc/fabric/test/datagen/DataGeneratorTestContent.java b/fabric-data-generation-api-v1/src/testmod/java/net/fabricmc/fabric/test/datagen/DataGeneratorTestContent.java index 0edda44319..b367cc0578 100644 --- a/fabric-data-generation-api-v1/src/testmod/java/net/fabricmc/fabric/test/datagen/DataGeneratorTestContent.java +++ b/fabric-data-generation-api-v1/src/testmod/java/net/fabricmc/fabric/test/datagen/DataGeneratorTestContent.java @@ -16,6 +16,9 @@ package net.fabricmc.fabric.test.datagen; +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; + import net.minecraft.block.AbstractBlock; import net.minecraft.block.Block; import net.minecraft.block.Blocks; @@ -32,6 +35,7 @@ import net.minecraft.util.Identifier; import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.event.registry.DynamicRegistries; import net.fabricmc.fabric.api.itemgroup.v1.FabricItemGroup; import net.fabricmc.fabric.api.itemgroup.v1.ItemGroupEvents; @@ -46,6 +50,16 @@ public class DataGeneratorTestContent implements ModInitializer { public static final RegistryKey SIMPLE_ITEM_GROUP = RegistryKey.of(RegistryKeys.ITEM_GROUP, new Identifier(MOD_ID, "simple")); + public static final RegistryKey> TEST_DATAGEN_DYNAMIC_REGISTRY_KEY = + RegistryKey.ofRegistry(new Identifier("fabric", "test_datagen_dynamic")); + public static final RegistryKey TEST_DYNAMIC_REGISTRY_ITEM_KEY = RegistryKey.of( + TEST_DATAGEN_DYNAMIC_REGISTRY_KEY, + new Identifier(MOD_ID, "tiny_potato") + ); + // Empty registry + public static final RegistryKey> TEST_DATAGEN_DYNAMIC_EMPTY_REGISTRY_KEY = + RegistryKey.ofRegistry(new Identifier("fabric", "test_datagen_dynamic_empty")); + @Override public void onInitialize() { SIMPLE_BLOCK = createBlock("simple_block", true, AbstractBlock.Settings.create()); @@ -60,6 +74,9 @@ public void onInitialize() { .icon(() -> new ItemStack(Items.DIAMOND_PICKAXE)) .displayName(Text.translatable("fabric-data-gen-api-v1-testmod.simple_item_group")) .build()); + + DynamicRegistries.register(TEST_DATAGEN_DYNAMIC_REGISTRY_KEY, TestDatagenObject.CODEC); + DynamicRegistries.register(TEST_DATAGEN_DYNAMIC_EMPTY_REGISTRY_KEY, TestDatagenObject.CODEC); } private static Block createBlock(String name, boolean hasItem, AbstractBlock.Settings settings) { @@ -72,4 +89,10 @@ private static Block createBlock(String name, boolean hasItem, AbstractBlock.Set return block; } + + public record TestDatagenObject(String value) { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + Codec.STRING.fieldOf("value").forGetter(TestDatagenObject::value) + ).apply(instance, TestDatagenObject::new)); + } } diff --git a/fabric-data-generation-api-v1/src/testmod/java/net/fabricmc/fabric/test/datagen/DataGeneratorTestEntrypoint.java b/fabric-data-generation-api-v1/src/testmod/java/net/fabricmc/fabric/test/datagen/DataGeneratorTestEntrypoint.java index e990661288..559cb3068c 100644 --- a/fabric-data-generation-api-v1/src/testmod/java/net/fabricmc/fabric/test/datagen/DataGeneratorTestEntrypoint.java +++ b/fabric-data-generation-api-v1/src/testmod/java/net/fabricmc/fabric/test/datagen/DataGeneratorTestEntrypoint.java @@ -23,6 +23,8 @@ import static net.fabricmc.fabric.test.datagen.DataGeneratorTestContent.MOD_ID; import static net.fabricmc.fabric.test.datagen.DataGeneratorTestContent.SIMPLE_BLOCK; import static net.fabricmc.fabric.test.datagen.DataGeneratorTestContent.SIMPLE_ITEM_GROUP; +import static net.fabricmc.fabric.test.datagen.DataGeneratorTestContent.TEST_DATAGEN_DYNAMIC_REGISTRY_KEY; +import static net.fabricmc.fabric.test.datagen.DataGeneratorTestContent.TEST_DYNAMIC_REGISTRY_ITEM_KEY; import java.io.IOException; import java.lang.reflect.InvocationTargetException; @@ -36,12 +38,13 @@ import org.slf4j.LoggerFactory; import net.minecraft.advancement.Advancement; +import net.minecraft.advancement.AdvancementEntry; import net.minecraft.advancement.AdvancementFrame; import net.minecraft.advancement.criterion.OnKilledCriterion; import net.minecraft.block.Blocks; import net.minecraft.data.client.BlockStateModelGenerator; import net.minecraft.data.client.ItemModelGenerator; -import net.minecraft.data.server.recipe.RecipeJsonProvider; +import net.minecraft.data.server.recipe.RecipeExporter; import net.minecraft.data.server.recipe.ShapelessRecipeJsonBuilder; import net.minecraft.entity.EntityType; import net.minecraft.entity.attribute.EntityAttributes; @@ -55,6 +58,8 @@ import net.minecraft.loot.provider.number.ConstantLootNumberProvider; import net.minecraft.recipe.Ingredient; import net.minecraft.recipe.book.RecipeCategory; +import net.minecraft.registry.Registerable; +import net.minecraft.registry.RegistryBuilder; import net.minecraft.registry.RegistryKeys; import net.minecraft.registry.RegistryWrapper; import net.minecraft.registry.tag.BlockTags; @@ -69,8 +74,10 @@ import net.fabricmc.fabric.api.datagen.v1.DataGeneratorEntrypoint; import net.fabricmc.fabric.api.datagen.v1.FabricDataGenerator; import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; +import net.fabricmc.fabric.api.datagen.v1.JsonKeySortOrderCallback; import net.fabricmc.fabric.api.datagen.v1.provider.FabricAdvancementProvider; import net.fabricmc.fabric.api.datagen.v1.provider.FabricBlockLootTableProvider; +import net.fabricmc.fabric.api.datagen.v1.provider.FabricDynamicRegistryProvider; import net.fabricmc.fabric.api.datagen.v1.provider.FabricLanguageProvider; import net.fabricmc.fabric.api.datagen.v1.provider.FabricModelProvider; import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider; @@ -86,6 +93,11 @@ public class DataGeneratorTestEntrypoint implements DataGeneratorEntrypoint { private static final ConditionJsonProvider NEVER_LOADED = DefaultResourceConditions.allModsLoaded("a"); private static final ConditionJsonProvider ALWAYS_LOADED = DefaultResourceConditions.not(NEVER_LOADED); + @Override + public void addJsonKeySortOrders(JsonKeySortOrderCallback callback) { + callback.add("trigger", 0); + } + @Override public void onInitializeDataGenerator(FabricDataGenerator dataGenerator) { final FabricDataGenerator.Pack pack = dataGenerator.createPack(); @@ -97,6 +109,7 @@ public void onInitializeDataGenerator(FabricDataGenerator dataGenerator) { pack.addProvider(TestBarterLootTableProvider::new); pack.addProvider(ExistingEnglishLangProvider::new); pack.addProvider(JapaneseLangProvider::new); + pack.addProvider(TestDynamicRegistryProvider::new); TestBlockTagProvider blockTagProvider = pack.addProvider(TestBlockTagProvider::new); pack.addProvider((output, registries) -> new TestItemTagProvider(output, registries, blockTagProvider)); @@ -114,13 +127,26 @@ public void onInitializeDataGenerator(FabricDataGenerator dataGenerator) { } } + @Override + public void buildRegistry(RegistryBuilder registryBuilder) { + registryBuilder.addRegistry( + TEST_DATAGEN_DYNAMIC_REGISTRY_KEY, + this::bootstrapTestDatagenRegistry + ); + // do NOT add TEST_DATAGEN_DYNAMIC_EMPTY_REGISTRY_KEY, should still work without it + } + + private void bootstrapTestDatagenRegistry(Registerable registerable) { + registerable.register(TEST_DYNAMIC_REGISTRY_ITEM_KEY, new DataGeneratorTestContent.TestDatagenObject(":tiny_potato:")); + } + private static class TestRecipeProvider extends FabricRecipeProvider { private TestRecipeProvider(FabricDataOutput output) { super(output); } @Override - public void generate(Consumer exporter) { + public void generate(RecipeExporter exporter) { offerPlanksRecipe2(exporter, SIMPLE_BLOCK, ItemTags.ACACIA_LOGS, 1); ShapelessRecipeJsonBuilder.create(RecipeCategory.MISC, Items.LEATHER, 4).input(Items.ITEM_FRAME) @@ -138,7 +164,7 @@ public void generate(Consumer exporter) { // - Create a new fabric server with the ingredient API. // - Copy the generated recipes to a datapack, for example to world/datapacks//data/test/recipes/. // - Remember to also include a pack.mcmeta file in world/datapacks/. - // (see https://minecraft.fandom.com/wiki/Tutorials/Creating_a_data_pack) + // (see https://minecraft.wiki/w/Tutorials/Creating_a_data_pack) // - Start the server and connect to it with a vanilla client. // - Test all the following recipes @@ -310,8 +336,8 @@ private TestAdvancementProvider(FabricDataOutput output) { } @Override - public void generateAdvancement(Consumer consumer) { - Advancement root = Advancement.Builder.create() + public void generateAdvancement(Consumer consumer) { + AdvancementEntry root = Advancement.Builder.create() .display( SIMPLE_BLOCK, Text.translatable("advancements.test.root.title"), @@ -321,7 +347,7 @@ public void generateAdvancement(Consumer consumer) { false, false, false) .criterion("killed_something", OnKilledCriterion.Conditions.createPlayerKilledEntity()) .build(consumer, MOD_ID + ":test/root"); - Advancement rootNotLoaded = Advancement.Builder.create() + AdvancementEntry rootNotLoaded = Advancement.Builder.create() .display( SIMPLE_BLOCK, Text.translatable("advancements.test.root_not_loaded.title"), @@ -364,4 +390,24 @@ public void accept(BiConsumer consumer) { ); } } + + /** + * Tests generating files for a custom dynamic registry. + * Note that Biome API testmod provides the test for vanilla dynamic registries. + */ + private static class TestDynamicRegistryProvider extends FabricDynamicRegistryProvider { + TestDynamicRegistryProvider(FabricDataOutput output, CompletableFuture registriesFuture) { + super(output, registriesFuture); + } + + @Override + protected void configure(RegistryWrapper.WrapperLookup registries, Entries entries) { + entries.add(registries.getWrapperOrThrow(TEST_DATAGEN_DYNAMIC_REGISTRY_KEY), TEST_DYNAMIC_REGISTRY_ITEM_KEY); + } + + @Override + public String getName() { + return "Test Dynamic Registry"; + } + } } diff --git a/fabric-data-generation-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/datagen/client/DataGeneratorClientTestEntrypoint.java b/fabric-data-generation-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/datagen/client/DataGeneratorClientTestEntrypoint.java index ee9e178cd0..2d6ce1ce75 100644 --- a/fabric-data-generation-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/datagen/client/DataGeneratorClientTestEntrypoint.java +++ b/fabric-data-generation-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/datagen/client/DataGeneratorClientTestEntrypoint.java @@ -30,10 +30,16 @@ import net.fabricmc.fabric.api.datagen.v1.DataGeneratorEntrypoint; import net.fabricmc.fabric.api.datagen.v1.FabricDataGenerator; import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; +import net.fabricmc.fabric.api.datagen.v1.JsonKeySortOrderCallback; import net.fabricmc.fabric.api.datagen.v1.provider.FabricCodecDataProvider; @SuppressWarnings("unused") public class DataGeneratorClientTestEntrypoint implements DataGeneratorEntrypoint { + @Override + public void addJsonKeySortOrders(JsonKeySortOrderCallback callback) { + callback.add("type", 100); // Force 'type' at the end + } + @Override public void onInitializeDataGenerator(FabricDataGenerator dataGenerator) { final FabricDataGenerator.Pack pack = dataGenerator.createBuiltinResourcePack(new Identifier(MOD_ID, "example_builtin")); diff --git a/fabric-data-generation-api-v1/template.accesswidener b/fabric-data-generation-api-v1/template.accesswidener index 15ec72392a..80d56b0625 100644 --- a/fabric-data-generation-api-v1/template.accesswidener +++ b/fabric-data-generation-api-v1/template.accesswidener @@ -1,48 +1,60 @@ accessible field net/minecraft/data/DataGenerator output Lnet/minecraft/data/DataOutput; mutable field net/minecraft/data/DataGenerator output Lnet/minecraft/data/DataOutput; -accessible field net/minecraft/data/server/recipe/RecipeProvider recipesPathResolver Lnet/minecraft/data/DataOutput$PathResolver; -accessible field net/minecraft/data/server/recipe/RecipeProvider advancementsPathResolver Lnet/minecraft/data/DataOutput$PathResolver; +accessible field net/minecraft/data/server/recipe/RecipeProvider recipesPathResolver Lnet/minecraft/data/DataOutput$PathResolver; +accessible field net/minecraft/data/server/recipe/RecipeProvider advancementsPathResolver Lnet/minecraft/data/DataOutput$PathResolver; -accessible field net/minecraft/data/server/tag/TagProvider$ProvidedTagBuilder builder Lnet/minecraft/registry/tag/TagBuilder; -extendable method net/minecraft/data/server/tag/TagProvider$ProvidedTagBuilder add (Lnet/minecraft/registry/RegistryKey;)Lnet/minecraft/data/server/tag/TagProvider$ProvidedTagBuilder; -extendable method net/minecraft/data/server/tag/TagProvider$ProvidedTagBuilder add ([Lnet/minecraft/registry/RegistryKey;)Lnet/minecraft/data/server/tag/TagProvider$ProvidedTagBuilder; +accessible field net/minecraft/data/server/tag/TagProvider$ProvidedTagBuilder builder Lnet/minecraft/registry/tag/TagBuilder; +extendable method net/minecraft/data/server/tag/TagProvider$ProvidedTagBuilder add (Lnet/minecraft/registry/RegistryKey;)Lnet/minecraft/data/server/tag/TagProvider$ProvidedTagBuilder; +extendable method net/minecraft/data/server/tag/TagProvider$ProvidedTagBuilder add ([Lnet/minecraft/registry/RegistryKey;)Lnet/minecraft/data/server/tag/TagProvider$ProvidedTagBuilder; -accessible field net/minecraft/data/server/tag/TagProvider tagBuilders Ljava/util/Map; +accessible field net/minecraft/data/server/tag/TagProvider tagBuilders Ljava/util/Map; -accessible field net/minecraft/data/server/loottable/BlockLootTableGenerator lootTables Ljava/util/Map; +accessible field net/minecraft/data/server/loottable/BlockLootTableGenerator lootTables Ljava/util/Map; -extendable method net/minecraft/registry/tag/TagEntry (Lnet/minecraft/util/Identifier;ZZ)V -accessible field net/minecraft/registry/tag/TagEntry id Lnet/minecraft/util/Identifier; -accessible field net/minecraft/registry/tag/TagEntry tag Z -accessible field net/minecraft/registry/tag/TagEntry required Z +extendable method net/minecraft/registry/tag/TagEntry (Lnet/minecraft/util/Identifier;ZZ)V +accessible field net/minecraft/registry/tag/TagEntry id Lnet/minecraft/util/Identifier; +accessible field net/minecraft/registry/tag/TagEntry tag Z +accessible field net/minecraft/registry/tag/TagEntry required Z -extendable method net/minecraft/data/DataOutput$PathResolver (Lnet/minecraft/data/DataOutput;Lnet/minecraft/data/DataOutput$OutputType;Ljava/lang/String;)V -accessible field net/minecraft/data/DataOutput$PathResolver rootPath Ljava/nio/file/Path; -accessible field net/minecraft/data/DataOutput$PathResolver directoryName Ljava/lang/String; +extendable method net/minecraft/data/DataOutput$PathResolver (Lnet/minecraft/data/DataOutput;Lnet/minecraft/data/DataOutput$OutputType;Ljava/lang/String;)V +accessible field net/minecraft/data/DataOutput$PathResolver rootPath Ljava/nio/file/Path; +accessible field net/minecraft/data/DataOutput$PathResolver directoryName Ljava/lang/String; -extendable method net/minecraft/data/DataGenerator$Pack (Lnet/minecraft/data/DataGenerator;ZLjava/lang/String;Lnet/minecraft/data/DataOutput;)V +extendable method net/minecraft/data/DataGenerator$Pack (Lnet/minecraft/data/DataGenerator;ZLjava/lang/String;Lnet/minecraft/data/DataOutput;)V -accessible field net/minecraft/registry/BuiltinRegistries REGISTRY_BUILDER Lnet/minecraft/registry/RegistryBuilder; -accessible method net/minecraft/registry/BuiltinRegistries validate (Lnet/minecraft/registry/RegistryWrapper$WrapperLookup;)V -accessible field net/minecraft/registry/RegistryBuilder registries Ljava/util/List; -accessible class net/minecraft/registry/RegistryBuilder$RegistryInfo +accessible field net/minecraft/registry/BuiltinRegistries REGISTRY_BUILDER Lnet/minecraft/registry/RegistryBuilder; +accessible method net/minecraft/registry/BuiltinRegistries validate (Lnet/minecraft/registry/RegistryWrapper$WrapperLookup;)V +accessible field net/minecraft/registry/RegistryBuilder registries Ljava/util/List; +accessible class net/minecraft/registry/RegistryBuilder$RegistryInfo -transitive-accessible method net/minecraft/data/family/BlockFamilies register (Lnet/minecraft/block/Block;)Lnet/minecraft/data/family/BlockFamily$Builder; +accessible field net/minecraft/loot/context/LootContextTypes MAP Lcom/google/common/collect/BiMap; -transitive-accessible field net/minecraft/data/client/BlockStateModelGenerator blockStateCollector Ljava/util/function/Consumer; -transitive-accessible field net/minecraft/data/client/BlockStateModelGenerator modelCollector Ljava/util/function/BiConsumer; +transitive-accessible method net/minecraft/data/family/BlockFamilies register (Lnet/minecraft/block/Block;)Lnet/minecraft/data/family/BlockFamily$Builder; -transitive-accessible field net/minecraft/data/client/ItemModelGenerator writer Ljava/util/function/BiConsumer; +transitive-accessible field net/minecraft/data/client/BlockStateModelGenerator blockStateCollector Ljava/util/function/Consumer; +transitive-accessible field net/minecraft/data/client/BlockStateModelGenerator modelCollector Ljava/util/function/BiConsumer; -transitive-accessible method net/minecraft/data/client/TextureKey of (Ljava/lang/String;)Lnet/minecraft/data/client/TextureKey; -transitive-accessible method net/minecraft/data/client/TextureKey of (Ljava/lang/String;Lnet/minecraft/data/client/TextureKey;)Lnet/minecraft/data/client/TextureKey; +transitive-accessible field net/minecraft/data/client/ItemModelGenerator writer Ljava/util/function/BiConsumer; -transitive-extendable method net/minecraft/data/server/tag/TagProvider$ProvidedTagBuilder add ([Lnet/minecraft/registry/RegistryKey;)Lnet/minecraft/data/server/tag/TagProvider$ProvidedTagBuilder; +transitive-accessible method net/minecraft/data/client/TextureKey of (Ljava/lang/String;)Lnet/minecraft/data/client/TextureKey; +transitive-accessible method net/minecraft/data/client/TextureKey of (Ljava/lang/String;Lnet/minecraft/data/client/TextureKey;)Lnet/minecraft/data/client/TextureKey; -transitive-accessible method net/minecraft/data/client/TexturedModel makeFactory (Ljava/util/function/Function;Lnet/minecraft/data/client/Model;)Lnet/minecraft/data/client/TexturedModel$Factory; +transitive-extendable method net/minecraft/data/server/tag/TagProvider$ProvidedTagBuilder add ([Lnet/minecraft/registry/RegistryKey;)Lnet/minecraft/data/server/tag/TagProvider$ProvidedTagBuilder; -transitive-accessible class net/minecraft/data/client/BlockStateModelGenerator$TintType -transitive-accessible class net/minecraft/data/client/BlockStateModelGenerator$BlockTexturePool -transitive-accessible class net/minecraft/data/client/BlockStateModelGenerator$LogTexturePool -transitive-accessible class net/minecraft/data/client/BlockStateModelGenerator$BuiltinModelPool +transitive-accessible method net/minecraft/data/client/TexturedModel makeFactory (Ljava/util/function/Function;Lnet/minecraft/data/client/Model;)Lnet/minecraft/data/client/TexturedModel$Factory; + +transitive-accessible class net/minecraft/data/client/BlockStateModelGenerator$TintType +transitive-accessible class net/minecraft/data/client/BlockStateModelGenerator$BlockTexturePool +transitive-accessible class net/minecraft/data/client/BlockStateModelGenerator$LogTexturePool +transitive-accessible class net/minecraft/data/client/BlockStateModelGenerator$BuiltinModelPool + +transitive-accessible field net/minecraft/data/server/loottable/BlockLootTableGenerator WITH_SILK_TOUCH Lnet/minecraft/loot/condition/LootCondition$Builder; +transitive-accessible field net/minecraft/data/server/loottable/BlockLootTableGenerator WITHOUT_SILK_TOUCH Lnet/minecraft/loot/condition/LootCondition$Builder; +transitive-accessible field net/minecraft/data/server/loottable/BlockLootTableGenerator WITH_SHEARS Lnet/minecraft/loot/condition/LootCondition$Builder; +transitive-accessible field net/minecraft/data/server/loottable/BlockLootTableGenerator WITH_SILK_TOUCH_OR_SHEARS Lnet/minecraft/loot/condition/LootCondition$Builder; +transitive-accessible field net/minecraft/data/server/loottable/BlockLootTableGenerator WITHOUT_SILK_TOUCH_NOR_SHEARS Lnet/minecraft/loot/condition/LootCondition$Builder; +transitive-accessible field net/minecraft/data/server/loottable/BlockLootTableGenerator SAPLING_DROP_CHANCE [F +transitive-accessible field net/minecraft/data/server/loottable/BlockLootTableGenerator LEAVES_STICK_DROP_CHANCE [F + +### Generated access wideners below diff --git a/fabric-dimensions-v1/build.gradle b/fabric-dimensions-v1/build.gradle index 1b0b8fab78..84dd58e6c5 100644 --- a/fabric-dimensions-v1/build.gradle +++ b/fabric-dimensions-v1/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-dimensions-v1" version = getSubprojectVersion(project) moduleDependencies(project, ['fabric-api-base']) diff --git a/fabric-rendering-data-attachment-v1/src/main/java/net/fabricmc/fabric/impl/rendering/data/attachment/RenderDataObjectConsumer.java b/fabric-dimensions-v1/src/main/java/net/fabricmc/fabric/impl/dimension/TaggedChoiceExtension.java similarity index 71% rename from fabric-rendering-data-attachment-v1/src/main/java/net/fabricmc/fabric/impl/rendering/data/attachment/RenderDataObjectConsumer.java rename to fabric-dimensions-v1/src/main/java/net/fabricmc/fabric/impl/dimension/TaggedChoiceExtension.java index 32c50114a0..2bb7340afb 100644 --- a/fabric-rendering-data-attachment-v1/src/main/java/net/fabricmc/fabric/impl/rendering/data/attachment/RenderDataObjectConsumer.java +++ b/fabric-dimensions-v1/src/main/java/net/fabricmc/fabric/impl/dimension/TaggedChoiceExtension.java @@ -14,10 +14,8 @@ * limitations under the License. */ -package net.fabricmc.fabric.impl.rendering.data.attachment; +package net.fabricmc.fabric.impl.dimension; -import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; - -public interface RenderDataObjectConsumer { - void fabric_acceptRenderDataObjects(Long2ObjectOpenHashMap renderDataObjects); +public interface TaggedChoiceExtension { + void fabric$setFailSoft(boolean cond); } diff --git a/fabric-dimensions-v1/src/main/java/net/fabricmc/fabric/impl/dimension/TaggedChoiceTypeExtension.java b/fabric-dimensions-v1/src/main/java/net/fabricmc/fabric/impl/dimension/TaggedChoiceTypeExtension.java new file mode 100644 index 0000000000..8e041d1b0f --- /dev/null +++ b/fabric-dimensions-v1/src/main/java/net/fabricmc/fabric/impl/dimension/TaggedChoiceTypeExtension.java @@ -0,0 +1,21 @@ +/* + * 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 net.fabricmc.fabric.impl.dimension; + +public interface TaggedChoiceTypeExtension { + void fabric$setFailSoft(boolean cond); +} diff --git a/fabric-dimensions-v1/src/main/java/net/fabricmc/fabric/mixin/dimension/Schema2832Mixin.java b/fabric-dimensions-v1/src/main/java/net/fabricmc/fabric/mixin/dimension/Schema2832Mixin.java new file mode 100644 index 0000000000..c441aee85d --- /dev/null +++ b/fabric-dimensions-v1/src/main/java/net/fabricmc/fabric/mixin/dimension/Schema2832Mixin.java @@ -0,0 +1,56 @@ +/* + * 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 net.fabricmc.fabric.mixin.dimension; + +import java.util.Map; +import java.util.function.Supplier; + +import com.mojang.datafixers.DSL; +import com.mojang.datafixers.types.Type; +import com.mojang.datafixers.types.templates.TaggedChoice; +import com.mojang.datafixers.types.templates.TypeTemplate; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +import net.minecraft.datafixer.schema.Schema2832; + +import net.fabricmc.fabric.impl.dimension.TaggedChoiceExtension; + +@Mixin(Schema2832.class) +public class Schema2832Mixin { + /** + * Make the DSL.taggedChoiceLazy to ignore mod custom generator types and not cause deserialization failure. + */ + @Redirect( + method = { + "method_38837", "method_38838" + }, + at = @At( + value = "INVOKE", + target = "Lcom/mojang/datafixers/DSL;taggedChoiceLazy(Ljava/lang/String;Lcom/mojang/datafixers/types/Type;Ljava/util/Map;)Lcom/mojang/datafixers/types/templates/TaggedChoice;", + remap = false + ) + ) + private static TaggedChoice redirectTaggedChoiceLazy( + String name, Type keyType, Map> templates + ) { + TaggedChoice result = DSL.taggedChoiceLazy(name, keyType, templates); + ((TaggedChoiceExtension) (Object) result).fabric$setFailSoft(true); + return result; + } +} diff --git a/fabric-dimensions-v1/src/main/java/net/fabricmc/fabric/mixin/dimension/TaggedChoiceMixin.java b/fabric-dimensions-v1/src/main/java/net/fabricmc/fabric/mixin/dimension/TaggedChoiceMixin.java new file mode 100644 index 0000000000..e1da627b68 --- /dev/null +++ b/fabric-dimensions-v1/src/main/java/net/fabricmc/fabric/mixin/dimension/TaggedChoiceMixin.java @@ -0,0 +1,57 @@ +/* + * 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 net.fabricmc.fabric.mixin.dimension; + +import com.mojang.datafixers.types.Type; +import com.mojang.datafixers.types.templates.TaggedChoice; +import com.mojang.datafixers.util.Pair; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import net.fabricmc.fabric.impl.dimension.TaggedChoiceExtension; +import net.fabricmc.fabric.impl.dimension.TaggedChoiceTypeExtension; + +@Mixin(value = TaggedChoice.class, remap = false) +public class TaggedChoiceMixin implements TaggedChoiceExtension { + @Unique + boolean failSoft = false; + + @Override + public void fabric$setFailSoft(boolean cond) { + failSoft = cond; + } + + /** + * Pass the failSoft information into TaggedChoice.TaggedChoiceType. + */ + @SuppressWarnings("rawtypes") + @Inject( + method = "lambda$apply$0", at = @At("RETURN"), remap = false + ) + private void onApply(Pair key, CallbackInfoReturnable cir) { + if (failSoft) { + Type returnValue = cir.getReturnValue(); + + if (returnValue instanceof TaggedChoice.TaggedChoiceType taggedChoiceType) { + ((TaggedChoiceTypeExtension) (Object) taggedChoiceType).fabric$setFailSoft(true); + } + } + } +} diff --git a/fabric-dimensions-v1/src/main/java/net/fabricmc/fabric/mixin/dimension/TaggedChoiceTypeMixin.java b/fabric-dimensions-v1/src/main/java/net/fabricmc/fabric/mixin/dimension/TaggedChoiceTypeMixin.java new file mode 100644 index 0000000000..f0339e408e --- /dev/null +++ b/fabric-dimensions-v1/src/main/java/net/fabricmc/fabric/mixin/dimension/TaggedChoiceTypeMixin.java @@ -0,0 +1,69 @@ +/* + * 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 net.fabricmc.fabric.mixin.dimension; + +import com.mojang.datafixers.types.Type; +import com.mojang.datafixers.types.templates.TaggedChoice; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import net.fabricmc.fabric.impl.dimension.TaggedChoiceTypeExtension; + +@Mixin(value = TaggedChoice.TaggedChoiceType.class, remap = false) +public class TaggedChoiceTypeMixin implements TaggedChoiceTypeExtension { + @Unique + private static final Logger LOGGER = LoggerFactory.getLogger("TaggedChoiceType_DimDataFix"); + + @Shadow(remap = false) + @Final + protected Object2ObjectMap> types; + + @Unique + private boolean failSoft; + + /** + * Make the DSL.taggedChoiceLazy to ignore mod custom generator types and not cause deserialization failure. + * The Codec.PASSTHROUGH will not make Dynamic to be deserialized and serialized to Dynamic. + * This will avoid deserialization failure from DFU when upgrading level.dat that contains mod custom generator types. + */ + @Inject( + method = "getCodec", at = @At("HEAD"), cancellable = true, remap = false + ) + private void onGetCodec(K k, CallbackInfoReturnable>> cir) { + if (failSoft) { + if (!types.containsKey(k)) { + LOGGER.warn("Not recognizing key {}. Using pass-through codec. {}", k, this); + cir.setReturnValue(DataResult.success(Codec.PASSTHROUGH)); + } + } + } + + @Override + public void fabric$setFailSoft(boolean cond) { + failSoft = cond; + } +} diff --git a/fabric-dimensions-v1/src/main/resources/fabric-dimensions-v1.mixins.json b/fabric-dimensions-v1/src/main/resources/fabric-dimensions-v1.mixins.json index bc4fcdd35b..74f20a425f 100644 --- a/fabric-dimensions-v1/src/main/resources/fabric-dimensions-v1.mixins.json +++ b/fabric-dimensions-v1/src/main/resources/fabric-dimensions-v1.mixins.json @@ -4,7 +4,10 @@ "compatibilityLevel": "JAVA_16", "mixins": [ "EntityMixin", - "RegistryCodecsMixin" + "RegistryCodecsMixin", + "Schema2832Mixin", + "TaggedChoiceMixin", + "TaggedChoiceTypeMixin" ], "injectors": { "defaultRequire": 1 diff --git a/fabric-dimensions-v1/src/testmod/resources/data/fabric_dimension/dimension/void_earlier_in_hash_map.json b/fabric-dimensions-v1/src/testmod/resources/data/fabric_dimension/dimension/void_earlier_in_hash_map.json new file mode 100644 index 0000000000..c071854bb0 --- /dev/null +++ b/fabric-dimensions-v1/src/testmod/resources/data/fabric_dimension/dimension/void_earlier_in_hash_map.json @@ -0,0 +1,22 @@ +{ + "$comments": [ + "CompoundListCodec is sensitive to the order of elements in hash map in NbtCompound.", + "In CompoundListCodec, when one entry fails deserialization,", + "entries before it will remain but entries after it will be lost.", + "Coincidentally, fabric_dimension:void's position in hash map is after all vanilla dimensions.", + "When fabric_dimension:void fails deserialization in DFU, the vanilla dimensions will still be deserialized.", + "But a mod dimension's id could be before the vanilla dimensions in the hash map,", + "which will make it deserialize before vanilla dimensions and cause vanilla dimension lost.", + "This dimension fabric_dimension:void_earilier_in_hash_map has a different hashcode ", + "and is before minecraft:the_nether in the hash map, so it can reproduce that issue." + ], + "generator": { + "type": "fabric_dimension:void", + "custom_bool": true, + "biome_source": { + "type": "minecraft:fixed", + "biome": "minecraft:plains" + } + }, + "type": "fabric_dimension:void_type" +} diff --git a/fabric-entity-events-v1/build.gradle b/fabric-entity-events-v1/build.gradle index 7dd922250f..fdfef9dd92 100644 --- a/fabric-entity-events-v1/build.gradle +++ b/fabric-entity-events-v1/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-entity-events-v1" version = getSubprojectVersion(project) moduleDependencies(project, ['fabric-api-base']) diff --git a/fabric-events-interaction-v0/build.gradle b/fabric-events-interaction-v0/build.gradle index a2e684128d..9ced6ef8b4 100644 --- a/fabric-events-interaction-v0/build.gradle +++ b/fabric-events-interaction-v0/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-events-interaction-v0" version = getSubprojectVersion(project) moduleDependencies(project, ['fabric-api-base']) diff --git a/fabric-events-interaction-v0/src/main/java/net/fabricmc/fabric/api/entity/FakePlayer.java b/fabric-events-interaction-v0/src/main/java/net/fabricmc/fabric/api/entity/FakePlayer.java index edb6569353..0585f531fd 100644 --- a/fabric-events-interaction-v0/src/main/java/net/fabricmc/fabric/api/entity/FakePlayer.java +++ b/fabric-events-interaction-v0/src/main/java/net/fabricmc/fabric/api/entity/FakePlayer.java @@ -30,7 +30,7 @@ import net.minecraft.entity.damage.DamageSource; import net.minecraft.entity.passive.AbstractHorseEntity; import net.minecraft.inventory.Inventory; -import net.minecraft.network.packet.c2s.play.ClientSettingsC2SPacket; +import net.minecraft.network.packet.c2s.common.SyncedClientOptions; import net.minecraft.scoreboard.AbstractTeam; import net.minecraft.screen.NamedScreenHandlerFactory; import net.minecraft.server.network.ServerPlayerEntity; @@ -102,7 +102,7 @@ private record FakePlayerKey(ServerWorld world, GameProfile profile) { } private static final Map FAKE_PLAYER_MAP = new MapMaker().weakValues().makeMap(); protected FakePlayer(ServerWorld world, GameProfile profile) { - super(world.getServer(), world, profile); + super(world.getServer(), world, profile, SyncedClientOptions.createDefault()); this.networkHandler = new FakePlayerNetworkHandler(this); } @@ -111,7 +111,7 @@ protected FakePlayer(ServerWorld world, GameProfile profile) { public void tick() { } @Override - public void setClientSettings(ClientSettingsC2SPacket packet) { } + public void setClientOptions(SyncedClientOptions settings) { } @Override public void increaseStat(Stat stat, int amount) { } diff --git a/fabric-events-interaction-v0/src/main/java/net/fabricmc/fabric/impl/event/interaction/FakePlayerNetworkHandler.java b/fabric-events-interaction-v0/src/main/java/net/fabricmc/fabric/impl/event/interaction/FakePlayerNetworkHandler.java index ba3fe0fe55..dd4d087a22 100644 --- a/fabric-events-interaction-v0/src/main/java/net/fabricmc/fabric/impl/event/interaction/FakePlayerNetworkHandler.java +++ b/fabric-events-interaction-v0/src/main/java/net/fabricmc/fabric/impl/event/interaction/FakePlayerNetworkHandler.java @@ -21,17 +21,29 @@ import net.minecraft.network.ClientConnection; import net.minecraft.network.NetworkSide; import net.minecraft.network.PacketCallbacks; +import net.minecraft.network.listener.PacketListener; import net.minecraft.network.packet.Packet; +import net.minecraft.server.network.ConnectedClientData; import net.minecraft.server.network.ServerPlayNetworkHandler; import net.minecraft.server.network.ServerPlayerEntity; public class FakePlayerNetworkHandler extends ServerPlayNetworkHandler { - private static final ClientConnection FAKE_CONNECTION = new ClientConnection(NetworkSide.CLIENTBOUND); + private static final ClientConnection FAKE_CONNECTION = new FakeClientConnection(); public FakePlayerNetworkHandler(ServerPlayerEntity player) { - super(player.getServer(), FAKE_CONNECTION, player); + super(player.getServer(), FAKE_CONNECTION, player, ConnectedClientData.createDefault(player.getGameProfile())); } @Override - public void sendPacket(Packet packet, @Nullable PacketCallbacks callbacks) { } + public void send(Packet packet, @Nullable PacketCallbacks callbacks) { } + + private static final class FakeClientConnection extends ClientConnection { + private FakeClientConnection() { + super(NetworkSide.CLIENTBOUND); + } + + @Override + public void setPacketListener(PacketListener packetListener) { + } + } } diff --git a/fabric-events-interaction-v0/src/main/java/net/fabricmc/fabric/mixin/event/interaction/PlayerAdvancementTrackerMixin.java b/fabric-events-interaction-v0/src/main/java/net/fabricmc/fabric/mixin/event/interaction/PlayerAdvancementTrackerMixin.java index ddd507b97c..259ccae0be 100644 --- a/fabric-events-interaction-v0/src/main/java/net/fabricmc/fabric/mixin/event/interaction/PlayerAdvancementTrackerMixin.java +++ b/fabric-events-interaction-v0/src/main/java/net/fabricmc/fabric/mixin/event/interaction/PlayerAdvancementTrackerMixin.java @@ -23,8 +23,8 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; -import net.minecraft.advancement.Advancement; import net.minecraft.advancement.PlayerAdvancementTracker; +import net.minecraft.advancement.AdvancementEntry; import net.minecraft.server.network.ServerPlayerEntity; import net.fabricmc.fabric.api.entity.FakePlayer; @@ -43,7 +43,7 @@ void preventOwnerOverride(ServerPlayerEntity newOwner, CallbackInfo ci) { } @Inject(method = "grantCriterion", at = @At("HEAD"), cancellable = true) - void preventGrantCriterion(Advancement advancement, String criterionName, CallbackInfoReturnable ci) { + void preventGrantCriterion(AdvancementEntry advancement, String criterionName, CallbackInfoReturnable ci) { if (owner instanceof FakePlayer) { // Prevent granting advancements to fake players. ci.setReturnValue(false); diff --git a/fabric-events-interaction-v0/src/testmod/java/net/fabricmc/fabric/test/event/interaction/FakePlayerTests.java b/fabric-events-interaction-v0/src/testmod/java/net/fabricmc/fabric/test/event/interaction/FakePlayerTests.java index 5917e8dca5..2bda646cc1 100644 --- a/fabric-events-interaction-v0/src/testmod/java/net/fabricmc/fabric/test/event/interaction/FakePlayerTests.java +++ b/fabric-events-interaction-v0/src/testmod/java/net/fabricmc/fabric/test/event/interaction/FakePlayerTests.java @@ -17,10 +17,12 @@ package net.fabricmc.fabric.test.event.interaction; import net.minecraft.block.Blocks; +import net.minecraft.entity.EntityType; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.ItemStack; import net.minecraft.item.ItemUsageContext; import net.minecraft.item.Items; +import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.test.GameTest; import net.minecraft.test.TestContext; import net.minecraft.util.Hand; @@ -60,4 +62,23 @@ public void testFakePlayerPlaceSign(TestContext context) { context.assertTrue(signStack.isEmpty(), "Sign stack was not emptied"); context.complete(); } + + /** + * Try breaking a beehive with a fake player (see {@code BeehiveBlockMixin}). + */ + @GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE) + public void testFakePlayerBreakBeehive(TestContext context) { + BlockPos basePos = new BlockPos(0, 1, 0); + context.setBlockState(basePos, Blocks.BEEHIVE); + context.spawnEntity(EntityType.BEE, basePos.up()); + + ServerPlayerEntity fakePlayer = FakePlayer.get(context.getWorld()); + + BlockPos fakePlayerPos = context.getAbsolutePos(basePos.add(2, 0, 2)); + fakePlayer.setPosition(fakePlayerPos.getX(), fakePlayerPos.getY(), fakePlayerPos.getZ()); + + context.assertTrue(fakePlayer.interactionManager.tryBreakBlock(context.getAbsolutePos(basePos)), "Block was not broken"); + context.expectBlock(Blocks.AIR, basePos); + context.complete(); + } } diff --git a/fabric-game-rule-api-v1/build.gradle b/fabric-game-rule-api-v1/build.gradle index 506856782c..f8a14ccf3d 100644 --- a/fabric-game-rule-api-v1/build.gradle +++ b/fabric-game-rule-api-v1/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-game-rule-api-v1" version = getSubprojectVersion(project) loom { diff --git a/fabric-gametest-api-v1/build.gradle b/fabric-gametest-api-v1/build.gradle index 851ec7da8f..6d90f8f8f2 100644 --- a/fabric-gametest-api-v1/build.gradle +++ b/fabric-gametest-api-v1/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-gametest-api-v1" version = getSubprojectVersion(project) loom { diff --git a/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/server/MainMixin.java b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/server/MainMixin.java index d9498d8d49..42282b5166 100644 --- a/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/server/MainMixin.java +++ b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/server/MainMixin.java @@ -47,8 +47,8 @@ private static boolean isEulaAgreedTo(EulaReader reader) { } // Inject after resourcePackManager is stored - @Inject(method = "main", cancellable = true, locals = LocalCapture.CAPTURE_FAILHARD, at = @At(value = "INVOKE", shift = At.Shift.BY, by = 2, target = "Lnet/minecraft/resource/VanillaDataPackProvider;createManager(Ljava/nio/file/Path;)Lnet/minecraft/resource/ResourcePackManager;")) - private static void main(String[] args, CallbackInfo info, OptionParser optionParser, OptionSpec optionSpec, OptionSpec optionSpec2, OptionSpec optionSpec3, OptionSpec optionSpec4, OptionSpec optionSpec5, OptionSpec optionSpec6, OptionSpec optionSpec7, OptionSpec optionSpec8, OptionSpec optionSpec9, OptionSpec optionSpec10, OptionSpec optionSpec11, OptionSpec optionSpec12, OptionSpec optionSpec13, OptionSpec optionSpec14, OptionSpec optionSpec15, OptionSpec optionSpec16, OptionSet optionSet, Path path2, ServerPropertiesLoader serverPropertiesLoader, Path path3, EulaReader eulaReader, File file, ApiServices apiServices, String string, LevelStorage levelStorage, LevelStorage.Session session, LevelSummary levelSummary, boolean bl, ResourcePackManager resourcePackManager) { + @Inject(method = "main", cancellable = true, locals = LocalCapture.CAPTURE_FAILHARD, at = @At(value = "INVOKE", shift = At.Shift.BY, by = 2, target = "Lnet/minecraft/resource/VanillaDataPackProvider;createManager(Lnet/minecraft/world/level/storage/LevelStorage$Session;)Lnet/minecraft/resource/ResourcePackManager;")) + private static void main(String[] args, CallbackInfo info, OptionParser optionParser, OptionSpec optionSpec, OptionSpec optionSpec2, OptionSpec optionSpec3, OptionSpec optionSpec4, OptionSpec optionSpec5, OptionSpec optionSpec6, OptionSpec optionSpec7, OptionSpec optionSpec8, OptionSpec optionSpec9, OptionSpec optionSpec10, OptionSpec optionSpec11, OptionSpec optionSpec12, OptionSpec optionSpec13, OptionSpec optionSpec14, OptionSpec optionSpec15, OptionSet optionSet, Path path2, ServerPropertiesLoader serverPropertiesLoader, Path path3, EulaReader eulaReader, File file, ApiServices apiServices, String string, LevelStorage levelStorage, LevelStorage.Session session, LevelSummary levelSummary, boolean bl, ResourcePackManager resourcePackManager) { if (FabricGameTestHelper.ENABLED) { FabricGameTestHelper.runHeadlessServer(session, resourcePackManager); info.cancel(); // Do not progress in starting the normal dedicated server diff --git a/fabric-item-api-v1/build.gradle b/fabric-item-api-v1/build.gradle index ae9e5b889f..e54321e6e7 100644 --- a/fabric-item-api-v1/build.gradle +++ b/fabric-item-api-v1/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-item-api-v1" version = getSubprojectVersion(project) moduleDependencies(project, ['fabric-api-base']) diff --git a/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/AbstractFurnaceBlockEntityMixin.java b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/AbstractFurnaceBlockEntityMixin.java index b364b8abd3..d1524d1eef 100644 --- a/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/AbstractFurnaceBlockEntityMixin.java +++ b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/AbstractFurnaceBlockEntityMixin.java @@ -26,8 +26,8 @@ import net.minecraft.block.BlockState; import net.minecraft.block.entity.AbstractFurnaceBlockEntity; +import net.minecraft.recipe.RecipeEntry; import net.minecraft.item.ItemStack; -import net.minecraft.recipe.Recipe; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; @@ -37,7 +37,7 @@ public abstract class AbstractFurnaceBlockEntityMixin { private static final ThreadLocal REMAINDER_STACK = new ThreadLocal<>(); @Inject(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/ItemStack;getItem()Lnet/minecraft/item/Item;"), locals = LocalCapture.CAPTURE_FAILHARD, allow = 1) - private static void getStackRemainder(World world, BlockPos pos, BlockState state, AbstractFurnaceBlockEntity blockEntity, CallbackInfo ci, boolean bl, boolean bl2, ItemStack itemStack, boolean bl3, boolean bl4, Recipe recipe, int i) { + private static void getStackRemainder(World world, BlockPos pos, BlockState state, AbstractFurnaceBlockEntity blockEntity, CallbackInfo ci, boolean bl, boolean bl2, ItemStack itemStack, boolean bl3, boolean bl4, RecipeEntry recipe, int i) { REMAINDER_STACK.set(itemStack.getRecipeRemainder()); } diff --git a/fabric-item-api-v1/src/testmod/java/net/fabricmc/fabric/test/item/gametest/RecipeGameTest.java b/fabric-item-api-v1/src/testmod/java/net/fabricmc/fabric/test/item/gametest/RecipeGameTest.java index 799e6b23ef..cb60671042 100644 --- a/fabric-item-api-v1/src/testmod/java/net/fabricmc/fabric/test/item/gametest/RecipeGameTest.java +++ b/fabric-item-api-v1/src/testmod/java/net/fabricmc/fabric/test/item/gametest/RecipeGameTest.java @@ -28,7 +28,6 @@ import net.minecraft.test.GameTest; import net.minecraft.test.GameTestException; import net.minecraft.test.TestContext; -import net.minecraft.util.Identifier; import net.minecraft.util.collection.DefaultedList; import net.minecraft.world.World; @@ -92,12 +91,7 @@ public boolean fits(int width, int height) { } @Override - public ItemStack getOutput(DynamicRegistryManager dynamicRegistryManager) { - return null; - } - - @Override - public Identifier getId() { + public ItemStack getResult(DynamicRegistryManager registryManager) { return null; } diff --git a/fabric-item-group-api-v1/build.gradle b/fabric-item-group-api-v1/build.gradle index 7a8a9237a6..466244f8e2 100644 --- a/fabric-item-group-api-v1/build.gradle +++ b/fabric-item-group-api-v1/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-item-group-api-v1" version = getSubprojectVersion(project) moduleDependencies(project, [ diff --git a/fabric-item-group-api-v1/src/main/java/net/fabricmc/fabric/api/itemgroup/v1/FabricItemGroupEntries.java b/fabric-item-group-api-v1/src/main/java/net/fabricmc/fabric/api/itemgroup/v1/FabricItemGroupEntries.java index 2823fafcfd..f43082aeec 100644 --- a/fabric-item-group-api-v1/src/main/java/net/fabricmc/fabric/api/itemgroup/v1/FabricItemGroupEntries.java +++ b/fabric-item-group-api-v1/src/main/java/net/fabricmc/fabric/api/itemgroup/v1/FabricItemGroupEntries.java @@ -32,7 +32,6 @@ /** * This class allows the entries of {@linkplain ItemGroup item groups} to be modified by the events in {@link ItemGroupEvents}. */ -@ApiStatus.Experimental public class FabricItemGroupEntries implements ItemGroup.Entries { private final ItemGroup.DisplayContext context; private final List displayStacks; diff --git a/fabric-item-group-api-v1/src/main/java/net/fabricmc/fabric/mixin/itemgroup/ItemGroupsMixin.java b/fabric-item-group-api-v1/src/main/java/net/fabricmc/fabric/mixin/itemgroup/ItemGroupsMixin.java index 3caa9a6608..dcbae12944 100644 --- a/fabric-item-group-api-v1/src/main/java/net/fabricmc/fabric/mixin/itemgroup/ItemGroupsMixin.java +++ b/fabric-item-group-api-v1/src/main/java/net/fabricmc/fabric/mixin/itemgroup/ItemGroupsMixin.java @@ -31,6 +31,7 @@ import static net.minecraft.item.ItemGroups.SPAWN_EGGS; import static net.minecraft.item.ItemGroups.TOOLS; +import java.util.Comparator; import java.util.HashMap; import java.util.List; @@ -58,7 +59,12 @@ private static void collect(CallbackInfo ci) { int count = 0; - for (RegistryKey registryKey : Registries.ITEM_GROUP.getKeys()) { + // Sort the item groups to ensure they are in a deterministic order. + final List> sortedItemGroups = Registries.ITEM_GROUP.getKeys().stream() + .sorted(Comparator.comparing(RegistryKey::getValue)) + .toList(); + + for (RegistryKey registryKey : sortedItemGroups) { final ItemGroup itemGroup = Registries.ITEM_GROUP.getOrThrow(registryKey); final FabricItemGroup fabricItemGroup = (FabricItemGroup) itemGroup; diff --git a/fabric-item-group-api-v1/src/main/resources/assets/fabric/lang/uk_ua.json b/fabric-item-group-api-v1/src/main/resources/assets/fabric/lang/uk_ua.json new file mode 100644 index 0000000000..bc86ac26f9 --- /dev/null +++ b/fabric-item-group-api-v1/src/main/resources/assets/fabric/lang/uk_ua.json @@ -0,0 +1,3 @@ +{ + "fabric.gui.creativeTabPage": "Сторінка %d/%d" +} \ No newline at end of file diff --git a/fabric-item-group-api-v1/src/main/resources/assets/fabric/lang/vi_vn.json b/fabric-item-group-api-v1/src/main/resources/assets/fabric/lang/vi_vn.json new file mode 100644 index 0000000000..04346a546f --- /dev/null +++ b/fabric-item-group-api-v1/src/main/resources/assets/fabric/lang/vi_vn.json @@ -0,0 +1,3 @@ +{ + "fabric.gui.creativeTabPage": "Trang %d/%d" +} diff --git a/fabric-key-binding-api-v1/build.gradle b/fabric-key-binding-api-v1/build.gradle index 64dcf3c11f..4bcab6eb68 100644 --- a/fabric-key-binding-api-v1/build.gradle +++ b/fabric-key-binding-api-v1/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-key-binding-api-v1" version = getSubprojectVersion(project) testDependencies(project, [ diff --git a/fabric-lifecycle-events-v1/build.gradle b/fabric-lifecycle-events-v1/build.gradle index 0dd2bac50f..91e81b718b 100644 --- a/fabric-lifecycle-events-v1/build.gradle +++ b/fabric-lifecycle-events-v1/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-lifecycle-events-v1" version = getSubprojectVersion(project) loom { diff --git a/fabric-lifecycle-events-v1/src/client/java/net/fabricmc/fabric/mixin/event/lifecycle/client/ClientChunkManagerMixin.java b/fabric-lifecycle-events-v1/src/client/java/net/fabricmc/fabric/mixin/event/lifecycle/client/ClientChunkManagerMixin.java index d7d8fe5ccf..a477973d33 100644 --- a/fabric-lifecycle-events-v1/src/client/java/net/fabricmc/fabric/mixin/event/lifecycle/client/ClientChunkManagerMixin.java +++ b/fabric-lifecycle-events-v1/src/client/java/net/fabricmc/fabric/mixin/event/lifecycle/client/ClientChunkManagerMixin.java @@ -55,8 +55,8 @@ private void onChunkUnload(int x, int z, PacketByteBuf buf, NbtCompound tag, Con } } - @Inject(method = "unload", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/world/ClientChunkManager$ClientChunkMap;compareAndSet(ILnet/minecraft/world/chunk/WorldChunk;Lnet/minecraft/world/chunk/WorldChunk;)Lnet/minecraft/world/chunk/WorldChunk;"), locals = LocalCapture.CAPTURE_FAILEXCEPTION) - private void onChunkUnload(int chunkX, int chunkZ, CallbackInfo ci, int i, WorldChunk chunk) { + @Inject(method = "unload", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/world/ClientChunkManager$ClientChunkMap;compareAndSet(ILnet/minecraft/world/chunk/WorldChunk;Lnet/minecraft/world/chunk/WorldChunk;)Lnet/minecraft/world/chunk/WorldChunk;"), locals = LocalCapture.CAPTURE_FAILHARD) + private void onChunkUnload(ChunkPos pos, CallbackInfo ci, int i, WorldChunk chunk) { ClientChunkEvents.CHUNK_UNLOAD.invoker().onChunkUnload(this.world, chunk); } diff --git a/fabric-lifecycle-events-v1/src/client/java/net/fabricmc/fabric/mixin/event/lifecycle/client/ClientPlayNetworkHandlerMixin.java b/fabric-lifecycle-events-v1/src/client/java/net/fabricmc/fabric/mixin/event/lifecycle/client/ClientPlayNetworkHandlerMixin.java index 8d5d66fed5..d6ac064379 100644 --- a/fabric-lifecycle-events-v1/src/client/java/net/fabricmc/fabric/mixin/event/lifecycle/client/ClientPlayNetworkHandlerMixin.java +++ b/fabric-lifecycle-events-v1/src/client/java/net/fabricmc/fabric/mixin/event/lifecycle/client/ClientPlayNetworkHandlerMixin.java @@ -26,9 +26,9 @@ import net.minecraft.client.network.ClientPlayNetworkHandler; import net.minecraft.client.world.ClientWorld; import net.minecraft.entity.Entity; +import net.minecraft.network.packet.s2c.common.SynchronizeTagsS2CPacket; import net.minecraft.network.packet.s2c.play.GameJoinS2CPacket; import net.minecraft.network.packet.s2c.play.PlayerRespawnS2CPacket; -import net.minecraft.network.packet.s2c.play.SynchronizeTagsS2CPacket; import net.minecraft.world.chunk.WorldChunk; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientBlockEntityEvents; @@ -101,7 +101,7 @@ private void onClearWorld(CallbackInfo ci) { method = "onSynchronizeTags", at = @At( value = "INVOKE", - target = "java/util/Map.forEach(Ljava/util/function/BiConsumer;)V", + target = "Lnet/minecraft/client/network/ClientCommonNetworkHandler;onSynchronizeTags(Lnet/minecraft/network/packet/s2c/common/SynchronizeTagsS2CPacket;)V", shift = At.Shift.AFTER, by = 1 ) ) diff --git a/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/PlayerManagerMixin.java b/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/PlayerManagerMixin.java index d9bd77f952..8f65afc4cc 100644 --- a/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/PlayerManagerMixin.java +++ b/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/PlayerManagerMixin.java @@ -23,6 +23,7 @@ import net.minecraft.network.ClientConnection; import net.minecraft.server.PlayerManager; +import net.minecraft.server.network.ConnectedClientData; import net.minecraft.server.network.ServerPlayerEntity; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; @@ -33,13 +34,13 @@ public class PlayerManagerMixin { method = "onPlayerConnect", at = @At(value = "INVOKE", target = "net/minecraft/network/packet/s2c/play/SynchronizeRecipesS2CPacket.(Ljava/util/Collection;)V") ) - private void hookOnPlayerConnect(ClientConnection connection, ServerPlayerEntity player, CallbackInfo ci) { + private void hookOnPlayerConnect(ClientConnection connection, ServerPlayerEntity player, ConnectedClientData arg, CallbackInfo ci) { ServerLifecycleEvents.SYNC_DATA_PACK_CONTENTS.invoker().onSyncDataPackContents(player, true); } @Inject( method = "onDataPacksReloaded", - at = @At(value = "INVOKE", target = "net/minecraft/network/packet/s2c/play/SynchronizeTagsS2CPacket.(Ljava/util/Map;)V") + at = @At(value = "INVOKE", target = "Lnet/minecraft/network/packet/s2c/common/SynchronizeTagsS2CPacket;(Ljava/util/Map;)V") ) private void hookOnDataPacksReloaded(CallbackInfo ci) { for (ServerPlayerEntity player : ((PlayerManager) (Object) this).getPlayerList()) { diff --git a/fabric-loot-api-v2/build.gradle b/fabric-loot-api-v2/build.gradle index 557749a7ca..f2db1b51aa 100644 --- a/fabric-loot-api-v2/build.gradle +++ b/fabric-loot-api-v2/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-loot-api-v2" version = getSubprojectVersion(project) moduleDependencies(project, [ diff --git a/fabric-loot-api-v2/src/main/java/net/fabricmc/fabric/api/loot/v2/FabricLootPoolBuilder.java b/fabric-loot-api-v2/src/main/java/net/fabricmc/fabric/api/loot/v2/FabricLootPoolBuilder.java index 90d3db90d7..359d816a24 100644 --- a/fabric-loot-api-v2/src/main/java/net/fabricmc/fabric/api/loot/v2/FabricLootPoolBuilder.java +++ b/fabric-loot-api-v2/src/main/java/net/fabricmc/fabric/api/loot/v2/FabricLootPoolBuilder.java @@ -17,7 +17,6 @@ package net.fabricmc.fabric.api.loot.v2; import java.util.Collection; -import java.util.List; import org.jetbrains.annotations.ApiStatus; @@ -107,8 +106,8 @@ static LootPool.Builder copyOf(LootPool pool) { return LootPool.builder() .rolls(accessor.fabric_getRolls()) .bonusRolls(accessor.fabric_getBonusRolls()) - .with(List.of(accessor.fabric_getEntries())) - .conditionally(List.of(accessor.fabric_getConditions())) - .apply(List.of(accessor.fabric_getFunctions())); + .with(accessor.fabric_getEntries()) + .conditionally(accessor.fabric_getConditions()) + .apply(accessor.fabric_getFunctions()); } } diff --git a/fabric-loot-api-v2/src/main/java/net/fabricmc/fabric/api/loot/v2/FabricLootTableBuilder.java b/fabric-loot-api-v2/src/main/java/net/fabricmc/fabric/api/loot/v2/FabricLootTableBuilder.java index 949d045bce..d966a36426 100644 --- a/fabric-loot-api-v2/src/main/java/net/fabricmc/fabric/api/loot/v2/FabricLootTableBuilder.java +++ b/fabric-loot-api-v2/src/main/java/net/fabricmc/fabric/api/loot/v2/FabricLootTableBuilder.java @@ -17,7 +17,6 @@ package net.fabricmc.fabric.api.loot.v2; import java.util.Collection; -import java.util.List; import java.util.function.Consumer; import org.jetbrains.annotations.ApiStatus; @@ -104,9 +103,9 @@ static LootTable.Builder copyOf(LootTable table) { LootTableAccessor accessor = (LootTableAccessor) table; builder.type(table.getType()); - builder.pools(List.of(accessor.fabric_getPools())); - builder.apply(List.of(accessor.fabric_getFunctions())); - builder.randomSequenceId(accessor.fabric_getRandomSequenceId()); + builder.pools(accessor.fabric_getPools()); + builder.apply(accessor.fabric_getFunctions()); + accessor.fabric_getRandomSequenceId().ifPresent(builder::randomSequenceId); return builder; } diff --git a/fabric-loot-api-v2/src/main/java/net/fabricmc/fabric/api/loot/v2/LootTableEvents.java b/fabric-loot-api-v2/src/main/java/net/fabricmc/fabric/api/loot/v2/LootTableEvents.java index 56776cb121..5e9d67ad7b 100644 --- a/fabric-loot-api-v2/src/main/java/net/fabricmc/fabric/api/loot/v2/LootTableEvents.java +++ b/fabric-loot-api-v2/src/main/java/net/fabricmc/fabric/api/loot/v2/LootTableEvents.java @@ -89,6 +89,15 @@ private LootTableEvents() { } }); + /** + * This event can be used for post-processing after all loot tables have been loaded and modified by Fabric. + */ + public static final Event ALL_LOADED = EventFactory.createArrayBacked(Loaded.class, listeners -> (resourceManager, lootManager) -> { + for (Loaded listener : listeners) { + listener.onLootTablesLoaded(resourceManager, lootManager); + } + }); + public interface Replace { /** * Replaces loot tables. @@ -116,4 +125,14 @@ public interface Modify { */ void modifyLootTable(ResourceManager resourceManager, LootManager lootManager, Identifier id, LootTable.Builder tableBuilder, LootTableSource source); } + + public interface Loaded { + /** + * Called when all loot tables have been loaded and {@link LootTableEvents#REPLACE} and {@link LootTableEvents#MODIFY} have been invoked. + * + * @param resourceManager the server resource manager + * @param lootManager the loot manager + */ + void onLootTablesLoaded(ResourceManager resourceManager, LootManager lootManager); + } } diff --git a/fabric-loot-api-v2/src/main/java/net/fabricmc/fabric/mixin/loot/LootManagerMixin.java b/fabric-loot-api-v2/src/main/java/net/fabricmc/fabric/mixin/loot/LootManagerMixin.java index aa684722b2..9c8625f77b 100644 --- a/fabric-loot-api-v2/src/main/java/net/fabricmc/fabric/mixin/loot/LootManagerMixin.java +++ b/fabric-loot-api-v2/src/main/java/net/fabricmc/fabric/mixin/loot/LootManagerMixin.java @@ -95,5 +95,6 @@ private void applyLootTableEvents(ResourceManager resourceManager, LootManager l }); this.keyToValue = newTables.build(); + LootTableEvents.ALL_LOADED.invoker().onLootTablesLoaded(resourceManager, lootManager); } } diff --git a/fabric-loot-api-v2/src/main/java/net/fabricmc/fabric/mixin/loot/LootPoolAccessor.java b/fabric-loot-api-v2/src/main/java/net/fabricmc/fabric/mixin/loot/LootPoolAccessor.java index dc7841e116..f681f0f754 100644 --- a/fabric-loot-api-v2/src/main/java/net/fabricmc/fabric/mixin/loot/LootPoolAccessor.java +++ b/fabric-loot-api-v2/src/main/java/net/fabricmc/fabric/mixin/loot/LootPoolAccessor.java @@ -16,6 +16,8 @@ package net.fabricmc.fabric.mixin.loot; +import java.util.List; + import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Accessor; @@ -38,11 +40,11 @@ public interface LootPoolAccessor { LootNumberProvider fabric_getBonusRolls(); @Accessor("entries") - LootPoolEntry[] fabric_getEntries(); + List fabric_getEntries(); @Accessor("conditions") - LootCondition[] fabric_getConditions(); + List fabric_getConditions(); @Accessor("functions") - LootFunction[] fabric_getFunctions(); + List fabric_getFunctions(); } diff --git a/fabric-loot-api-v2/src/main/java/net/fabricmc/fabric/mixin/loot/LootPoolBuilderMixin.java b/fabric-loot-api-v2/src/main/java/net/fabricmc/fabric/mixin/loot/LootPoolBuilderMixin.java index d1124fac85..d0326b7de2 100644 --- a/fabric-loot-api-v2/src/main/java/net/fabricmc/fabric/mixin/loot/LootPoolBuilderMixin.java +++ b/fabric-loot-api-v2/src/main/java/net/fabricmc/fabric/mixin/loot/LootPoolBuilderMixin.java @@ -17,8 +17,8 @@ package net.fabricmc.fabric.mixin.loot; import java.util.Collection; -import java.util.List; +import com.google.common.collect.ImmutableList; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -39,15 +39,15 @@ abstract class LootPoolBuilderMixin implements FabricLootPoolBuilder { @Shadow @Final - private List entries; + private ImmutableList.Builder entries; @Shadow @Final - private List conditions; + private ImmutableList.Builder conditions; @Shadow @Final - private List functions; + private ImmutableList.Builder functions; @Unique private LootPool.Builder self() { diff --git a/fabric-loot-api-v2/src/main/java/net/fabricmc/fabric/mixin/loot/LootTableAccessor.java b/fabric-loot-api-v2/src/main/java/net/fabricmc/fabric/mixin/loot/LootTableAccessor.java index 741e6ab1cd..4e8993eed0 100644 --- a/fabric-loot-api-v2/src/main/java/net/fabricmc/fabric/mixin/loot/LootTableAccessor.java +++ b/fabric-loot-api-v2/src/main/java/net/fabricmc/fabric/mixin/loot/LootTableAccessor.java @@ -16,6 +16,9 @@ package net.fabricmc.fabric.mixin.loot; +import java.util.List; +import java.util.Optional; + import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Accessor; @@ -31,11 +34,11 @@ @Mixin(LootTable.class) public interface LootTableAccessor { @Accessor("pools") - LootPool[] fabric_getPools(); + List fabric_getPools(); @Accessor("functions") - LootFunction[] fabric_getFunctions(); + List fabric_getFunctions(); @Accessor("randomSequenceId") - Identifier fabric_getRandomSequenceId(); + Optional fabric_getRandomSequenceId(); } diff --git a/fabric-loot-api-v2/src/main/java/net/fabricmc/fabric/mixin/loot/LootTableBuilderMixin.java b/fabric-loot-api-v2/src/main/java/net/fabricmc/fabric/mixin/loot/LootTableBuilderMixin.java index 8286ac9763..91b5ed703a 100644 --- a/fabric-loot-api-v2/src/main/java/net/fabricmc/fabric/mixin/loot/LootTableBuilderMixin.java +++ b/fabric-loot-api-v2/src/main/java/net/fabricmc/fabric/mixin/loot/LootTableBuilderMixin.java @@ -16,13 +16,15 @@ package net.fabricmc.fabric.mixin.loot; +import java.util.ArrayList; import java.util.Collection; -import java.util.List; import java.util.ListIterator; import java.util.function.Consumer; +import com.google.common.collect.ImmutableList; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Unique; @@ -41,11 +43,12 @@ abstract class LootTableBuilderMixin implements FabricLootTableBuilder { @Shadow @Final - private List pools; + @Mutable + private ImmutableList.Builder pools; @Shadow @Final - private List functions; + private ImmutableList.Builder functions; @Unique private LootTable.Builder self() { @@ -79,7 +82,8 @@ public LootTable.Builder apply(Collection functions) { @Override public LootTable.Builder modifyPools(Consumer modifier) { - ListIterator iterator = pools.listIterator(); + var list = new ArrayList<>(pools.build()); + ListIterator iterator = list.listIterator(); while (iterator.hasNext()) { LootPool.Builder poolBuilder = FabricLootPoolBuilder.copyOf(iterator.next()); @@ -87,6 +91,9 @@ public LootTable.Builder modifyPools(Consumer modifier iterator.set(poolBuilder.build()); } + this.pools = ImmutableList.builder(); + this.pools.addAll(list); + return self(); } } diff --git a/fabric-loot-api-v2/src/testmod/java/net/fabricmc/fabric/test/loot/LootTest.java b/fabric-loot-api-v2/src/testmod/java/net/fabricmc/fabric/test/loot/LootTest.java index 4a7236347d..1f34b42dd9 100644 --- a/fabric-loot-api-v2/src/testmod/java/net/fabricmc/fabric/test/loot/LootTest.java +++ b/fabric-loot-api-v2/src/testmod/java/net/fabricmc/fabric/test/loot/LootTest.java @@ -92,5 +92,13 @@ public void onInitialize() { tableBuilder.modifyPools(poolBuilder -> poolBuilder.with(ItemEntry.builder(Items.EMERALD))); } }); + + LootTableEvents.ALL_LOADED.register((resourceManager, lootManager) -> { + LootTable blackWoolTable = lootManager.getLootTable(Blocks.BLACK_WOOL.getLootTableId()); + + if (blackWoolTable == LootTable.EMPTY) { + throw new AssertionError("black wool loot table should not be empty"); + } + }); } } diff --git a/fabric-message-api-v1/build.gradle b/fabric-message-api-v1/build.gradle index 79491115f9..a733beb92b 100644 --- a/fabric-message-api-v1/build.gradle +++ b/fabric-message-api-v1/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-message-api-v1" version = getSubprojectVersion(project) moduleDependencies(project, ['fabric-api-base']) diff --git a/fabric-message-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/message/ClientPlayNetworkHandlerMixin.java b/fabric-message-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/message/ClientPlayNetworkHandlerMixin.java index 8640750047..86e05c420b 100644 --- a/fabric-message-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/message/ClientPlayNetworkHandlerMixin.java +++ b/fabric-message-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/message/ClientPlayNetworkHandlerMixin.java @@ -40,7 +40,7 @@ private void fabric_allowSendChatMessage(String content, CallbackInfo ci) { } } - @ModifyVariable(method = "sendChatMessage", at = @At(value = "LOAD", ordinal = 0), ordinal = 0, argsOnly = true) + @ModifyVariable(method = "sendChatMessage", at = @At("HEAD"), ordinal = 0, argsOnly = true) private String fabric_modifySendChatMessage(String content) { content = ClientSendMessageEvents.MODIFY_CHAT.invoker().modifySendChatMessage(content); ClientSendMessageEvents.CHAT.invoker().onSendChatMessage(content); @@ -55,7 +55,7 @@ private void fabric_allowSendCommandMessage(String command, CallbackInfo ci) { } } - @ModifyVariable(method = "sendChatCommand", at = @At(value = "LOAD", ordinal = 0), ordinal = 0, argsOnly = true) + @ModifyVariable(method = "sendChatCommand", at = @At("HEAD"), ordinal = 0, argsOnly = true) private String fabric_modifySendCommandMessage(String command) { command = ClientSendMessageEvents.MODIFY_COMMAND.invoker().modifySendCommandMessage(command); ClientSendMessageEvents.COMMAND.invoker().onSendCommandMessage(command); diff --git a/fabric-message-api-v1/src/main/java/net/fabricmc/fabric/api/message/v1/ServerMessageDecoratorEvent.java b/fabric-message-api-v1/src/main/java/net/fabricmc/fabric/api/message/v1/ServerMessageDecoratorEvent.java index 0b0d9856d4..93c0853f34 100644 --- a/fabric-message-api-v1/src/main/java/net/fabricmc/fabric/api/message/v1/ServerMessageDecoratorEvent.java +++ b/fabric-message-api-v1/src/main/java/net/fabricmc/fabric/api/message/v1/ServerMessageDecoratorEvent.java @@ -17,10 +17,6 @@ package net.fabricmc.fabric.api.message.v1; import java.util.Objects; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; - -import org.jetbrains.annotations.Nullable; import net.minecraft.network.message.MessageDecorator; import net.minecraft.text.Text; @@ -56,10 +52,9 @@ * ServerMessageDecoratorEvent.EVENT.register(ServerMessageDecoratorEvent.STYLING_PHASE, (sender, message) -> { * // Apply orange color to messages sent by server operators * if (sender != null && sender.server.getPlayerManager().isOperator(sender.getGameProfile())) { - * return CompletableFuture.completedFuture( - * message.copy().styled(style -> style.withColor(0xFFA500))); + * return message.copy().styled(style -> style.withColor(0xFFA500)); * } - * return CompletableFuture.completedFuture(message); + * return message; * }); * } */ @@ -79,27 +74,17 @@ private ServerMessageDecoratorEvent() { public static final Identifier STYLING_PHASE = new Identifier("fabric", "styling"); public static final Event EVENT = EventFactory.createWithPhases(MessageDecorator.class, decorators -> (sender, message) -> { - CompletableFuture future = null; + Text decorated = message; for (MessageDecorator decorator : decorators) { - if (future == null) { - future = decorator.decorate(sender, message).handle((decorated, throwable) -> handle(decorated, throwable, decorator)); - } else { - future = future.thenCompose((decorated) -> decorator.decorate(sender, decorated).handle((newlyDecorated, throwable) -> handle(newlyDecorated, throwable, decorator))); - } + decorated = handle(decorator.decorate(sender, decorated), decorator); } - return future == null ? CompletableFuture.completedFuture(message) : future; + return decorated; }, CONTENT_PHASE, Event.DEFAULT_PHASE, STYLING_PHASE); - private static T handle(T decorated, @Nullable Throwable throwable, MessageDecorator decorator) { + private static T handle(T decorated, MessageDecorator decorator) { String decoratorName = decorator.getClass().getName(); - - if (throwable != null) { - if (throwable instanceof CompletionException) throwable = throwable.getCause(); - throw new CompletionException("message decorator %s failed".formatted(decoratorName), throwable); - } - return Objects.requireNonNull(decorated, "message decorator %s returned null".formatted(decoratorName)); } } diff --git a/fabric-message-api-v1/src/main/java/net/fabricmc/fabric/mixin/message/MinecraftServerMixin.java b/fabric-message-api-v1/src/main/java/net/fabricmc/fabric/mixin/message/MinecraftServerMixin.java index 9a7b415b36..a43ef47594 100644 --- a/fabric-message-api-v1/src/main/java/net/fabricmc/fabric/mixin/message/MinecraftServerMixin.java +++ b/fabric-message-api-v1/src/main/java/net/fabricmc/fabric/mixin/message/MinecraftServerMixin.java @@ -30,7 +30,6 @@ public class MinecraftServerMixin { @Inject(method = "getMessageDecorator", at = @At("RETURN"), cancellable = true) private void onGetChatDecorator(CallbackInfoReturnable cir) { - MessageDecorator originalDecorator = cir.getReturnValue(); - cir.setReturnValue((sender, message) -> originalDecorator.decorate(sender, message).thenCompose((decorated) -> ServerMessageDecoratorEvent.EVENT.invoker().decorate(sender, decorated))); + cir.setReturnValue((sender, message) -> ServerMessageDecoratorEvent.EVENT.invoker().decorate(sender, message)); } } diff --git a/fabric-message-api-v1/src/main/resources/fabric.mod.json b/fabric-message-api-v1/src/main/resources/fabric.mod.json index 7a0e078cb9..546a20b66e 100644 --- a/fabric-message-api-v1/src/main/resources/fabric.mod.json +++ b/fabric-message-api-v1/src/main/resources/fabric.mod.json @@ -28,6 +28,6 @@ } ], "custom": { - "fabric-api:module-lifecycle": "experimental" + "fabric-api:module-lifecycle": "stable" } } diff --git a/fabric-message-api-v1/src/testmod/java/net/fabricmc/fabric/test/message/ChatTest.java b/fabric-message-api-v1/src/testmod/java/net/fabricmc/fabric/test/message/ChatTest.java index 993028dd4b..82044e29b2 100644 --- a/fabric-message-api-v1/src/testmod/java/net/fabricmc/fabric/test/message/ChatTest.java +++ b/fabric-message-api-v1/src/testmod/java/net/fabricmc/fabric/test/message/ChatTest.java @@ -16,7 +16,6 @@ package net.fabricmc.fabric.test.message; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import org.slf4j.Logger; @@ -41,45 +40,28 @@ public void onInitialize() { // Basic content phase testing ServerMessageDecoratorEvent.EVENT.register(ServerMessageDecoratorEvent.CONTENT_PHASE, (sender, message) -> { if (message.getString().contains("tater")) { - return CompletableFuture.completedFuture(message.copy().append(" :tiny_potato:")); + return message.copy().append(" :tiny_potato:"); } - return CompletableFuture.completedFuture(message); + return message; }); // Content phase testing, with variable info ServerMessageDecoratorEvent.EVENT.register(ServerMessageDecoratorEvent.CONTENT_PHASE, (sender, message) -> { if (message.getString().contains("random")) { - return CompletableFuture.completedFuture(Text.of(String.valueOf(Random.create().nextBetween(0, 100)))); + return Text.of(String.valueOf(Random.create().nextBetween(0, 100))); } - return CompletableFuture.completedFuture(message); + return message; }); // Basic styling phase testing ServerMessageDecoratorEvent.EVENT.register(ServerMessageDecoratorEvent.STYLING_PHASE, (sender, message) -> { if (sender != null && sender.getAbilities().creativeMode) { - return CompletableFuture.completedFuture(message.copy().styled(style -> style.withColor(0xFFA500))); + return message.copy().styled(style -> style.withColor(0xFFA500)); } - return CompletableFuture.completedFuture(message); - }); - - // Async testing - ServerMessageDecoratorEvent.EVENT.register(ServerMessageDecoratorEvent.CONTENT_PHASE, (sender, message) -> { - if (message.getString().contains("wait")) { - return CompletableFuture.supplyAsync(() -> { - try { - Thread.sleep(Random.create().nextBetween(500, 2000)); - } catch (InterruptedException ignored) { - // Ignore interruption - } - - return message; - }, ioWorkerExecutor); - } - - return CompletableFuture.completedFuture(message); + return message; }); // ServerMessageEvents diff --git a/fabric-message-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/message/client/ChatTestClient.java b/fabric-message-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/message/client/ChatTestClient.java index 73f1a7f3d1..e659df0a6a 100644 --- a/fabric-message-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/message/client/ChatTestClient.java +++ b/fabric-message-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/message/client/ChatTestClient.java @@ -16,6 +16,7 @@ package net.fabricmc.fabric.test.message.client; +import com.mojang.brigadier.Command; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -36,6 +37,11 @@ public void onInitializeClient() { ClientCommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> dispatcher.register(ClientCommandManager.literal("block").then(ClientCommandManager.literal("send").executes(context -> { throw new AssertionError("This client command should be blocked!"); })))); + //Register the modified result command from ClientSendMessageEvents#MODIFY_COMMAND to ensure that MODIFY_COMMAND executes before the client command api + ClientCommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> dispatcher.register(ClientCommandManager.literal("sending").then(ClientCommandManager.literal("modified").then(ClientCommandManager.literal("command").then(ClientCommandManager.literal("message").executes(context -> { + LOGGER.info("Command modified by ClientSendMessageEvents#MODIFY_COMMAND successfully processed by fabric client command api"); + return Command.SINGLE_SUCCESS; + })))))); //Test client send message events ClientSendMessageEvents.ALLOW_CHAT.register((message) -> { if (message.contains("block send")) { diff --git a/fabric-mining-level-api-v1/build.gradle b/fabric-mining-level-api-v1/build.gradle index 750e26698a..d3b50580ee 100644 --- a/fabric-mining-level-api-v1/build.gradle +++ b/fabric-mining-level-api-v1/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-mining-level-api-v1" version = getSubprojectVersion(project) moduleDependencies(project, [ diff --git a/fabric-models-v0/build.gradle b/fabric-model-loading-api-v1/build.gradle similarity index 82% rename from fabric-models-v0/build.gradle rename to fabric-model-loading-api-v1/build.gradle index 58fefe77b6..ca3eb5eb15 100644 --- a/fabric-models-v0/build.gradle +++ b/fabric-model-loading-api-v1/build.gradle @@ -1,9 +1,9 @@ -archivesBaseName = "fabric-models-v0" version = getSubprojectVersion(project) moduleDependencies(project, ['fabric-api-base']) testDependencies(project, [ + ':fabric-renderer-api-v1', ':fabric-rendering-v1', ':fabric-resource-loader-v0' ]) diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/BlockStateResolver.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/BlockStateResolver.java new file mode 100644 index 0000000000..dacd2f1a4f --- /dev/null +++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/BlockStateResolver.java @@ -0,0 +1,90 @@ +/* + * 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 net.fabricmc.fabric.api.client.model.loading.v1; + +import org.jetbrains.annotations.ApiStatus; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.client.render.model.ModelLoader; +import net.minecraft.client.render.model.UnbakedModel; +import net.minecraft.client.util.ModelIdentifier; +import net.minecraft.util.Identifier; + +/** + * Block state resolvers are responsible for mapping each {@link BlockState} of a block to an {@link UnbakedModel}. + * They replace the {@code blockstates/} JSON files. One block can be mapped to only one block state resolver; multiple + * resolvers will not receive the same block. + * + *

    Block state resolvers can be used to create custom block state formats or dynamically resolve block state models. + * + *

    Use {@link ModelResolver} instead of this interface if interacting with the block and block states directly is not + * necessary. This includes custom model deserializers and loaders. + * + * @see ModelResolver + * @see ModelModifier.OnLoad + */ +@FunctionalInterface +public interface BlockStateResolver { + /** + * Resolves the models for all block states of the block. + * + *

    For each block state, call {@link Context#setModel} to set its unbaked model. + * This method must be called exactly once for each block state. + * + *

    Note that if multiple block states share the same unbaked model instance, it will be baked multiple times + * (once per block state that has the model set), which is not efficient. To improve efficiency in this case, the + * model should be delegated to using {@link DelegatingUnbakedModel} to ensure that it is only baked once. The inner + * model can be loaded using {@link ModelResolver} if custom loading logic is necessary. + */ + void resolveBlockStates(Context context); + + /** + * The context for block state resolution. + */ + @ApiStatus.NonExtendable + interface Context { + /** + * The block for which block state models are being resolved. + */ + Block block(); + + /** + * Sets the model for a block state. + * + * @param state the block state for which this model should be used + * @param model the unbaked model for this block state + */ + void setModel(BlockState state, UnbakedModel model); + + /** + * Loads a model using an {@link Identifier} or {@link ModelIdentifier}, or gets it if it was already loaded. + * + * @param id the model identifier + * @return the unbaked model, or a missing model if it is not present + */ + UnbakedModel getOrLoadModel(Identifier id); + + /** + * The current model loader instance, which changes between resource reloads. + * + *

    Do not call {@link ModelLoader#getOrLoadModel} as it does not supported nested model resolution; + * use {@link #getOrLoadModel} from the context instead. + */ + ModelLoader loader(); + } +} diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/DelegatingUnbakedModel.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/DelegatingUnbakedModel.java new file mode 100644 index 0000000000..ee37263fd2 --- /dev/null +++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/DelegatingUnbakedModel.java @@ -0,0 +1,67 @@ +/* + * 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 net.fabricmc.fabric.api.client.model.loading.v1; + +import java.util.Collection; +import java.util.List; +import java.util.function.Function; + +import org.jetbrains.annotations.Nullable; + +import net.minecraft.client.render.model.BakedModel; +import net.minecraft.client.render.model.Baker; +import net.minecraft.client.render.model.ModelBakeSettings; +import net.minecraft.client.render.model.UnbakedModel; +import net.minecraft.client.texture.Sprite; +import net.minecraft.client.util.ModelIdentifier; +import net.minecraft.client.util.SpriteIdentifier; +import net.minecraft.util.Identifier; + +/** + * An unbaked model that returns another {@link BakedModel} at {@linkplain #bake bake time}. + * This allows multiple {@link UnbakedModel}s to share the same {@link BakedModel} instance + * and prevents baking the same model multiple times. + */ +public final class DelegatingUnbakedModel implements UnbakedModel { + private final Identifier delegate; + private final List dependencies; + + /** + * Constructs a new delegating model. + * + * @param delegate The identifier (can be a {@link ModelIdentifier}) of the underlying baked model. + */ + public DelegatingUnbakedModel(Identifier delegate) { + this.delegate = delegate; + this.dependencies = List.of(delegate); + } + + @Override + public Collection getModelDependencies() { + return dependencies; + } + + @Override + public void setParents(Function modelLoader) { + } + + @Nullable + @Override + public BakedModel bake(Baker baker, Function textureGetter, ModelBakeSettings rotationContainer, Identifier modelId) { + return baker.bake(delegate, rotationContainer); + } +} diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/FabricBakedModelManager.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/FabricBakedModelManager.java new file mode 100644 index 0000000000..dbc5e0c4c7 --- /dev/null +++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/FabricBakedModelManager.java @@ -0,0 +1,50 @@ +/* + * 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 net.fabricmc.fabric.api.client.model.loading.v1; + +import org.jetbrains.annotations.Nullable; + +import net.minecraft.client.render.model.BakedModel; +import net.minecraft.client.render.model.BakedModelManager; +import net.minecraft.client.util.ModelIdentifier; +import net.minecraft.util.Identifier; + +/** + * Fabric-provided helper methods for {@link BakedModelManager}. + * + *

    Note: This interface is automatically implemented on the {@link BakedModelManager} via Mixin and interface injection. + */ +public interface FabricBakedModelManager { + /** + * An alternative to {@link BakedModelManager#getModel(ModelIdentifier)} that accepts an + * {@link Identifier} instead. Models loaded using {@link ModelLoadingPlugin.Context#addModels} + * do not have a corresponding {@link ModelIdentifier}, so the vanilla method cannot be used to + * retrieve them. The {@link Identifier} that was used to load them can be used in this method + * to retrieve them. + * + *

    This method, as well as its vanilla counterpart, should only be used after the + * {@link BakedModelManager} has completed reloading. Otherwise, the result will be + * outdated or null. + * + * @param id the id of the model + * @return the model + */ + @Nullable + default BakedModel getModel(Identifier id) { + throw new UnsupportedOperationException("Implemented via mixin."); + } +} diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/ModelLoadingPlugin.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/ModelLoadingPlugin.java new file mode 100644 index 0000000000..002deb2fae --- /dev/null +++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/ModelLoadingPlugin.java @@ -0,0 +1,98 @@ +/* + * 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 net.fabricmc.fabric.api.client.model.loading.v1; + +import java.util.Collection; + +import org.jetbrains.annotations.ApiStatus; + +import net.minecraft.block.Block; +import net.minecraft.client.render.model.json.JsonUnbakedModel; +import net.minecraft.client.util.ModelIdentifier; +import net.minecraft.resource.ResourceManager; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.impl.client.model.loading.ModelLoadingPluginManager; + +/** + * A model loading plugin is used to extend the model loading process through the passed {@link Context} object. + * + *

    {@link PreparableModelLoadingPlugin} can be used if some resources need to be loaded from the + * {@link ResourceManager}. + */ +@FunctionalInterface +public interface ModelLoadingPlugin { + /** + * Registers a model loading plugin. + */ + static void register(ModelLoadingPlugin plugin) { + ModelLoadingPluginManager.registerPlugin(plugin); + } + + /** + * Called towards the beginning of the model loading process, every time resource are (re)loaded. + * Use the context object to extend model loading as desired. + */ + void onInitializeModelLoader(Context pluginContext); + + @ApiStatus.NonExtendable + interface Context { + /** + * Adds one or more models (can be {@link ModelIdentifier}s) to the list of models that will be loaded and + * baked. + */ + void addModels(Identifier... ids); + + /** + * Adds multiple models (can be {@link ModelIdentifier}s) to the list of models that will be loaded and baked. + */ + void addModels(Collection ids); + + /** + * Registers a block state resolver for a block. + * + *

    The block must be registered and a block state resolver must not have been previously registered for the + * block. + */ + void registerBlockStateResolver(Block block, BlockStateResolver resolver); + + /** + * Event access to register model resolvers. + */ + Event resolveModel(); + + /** + * Event access to monitor unbaked model loads and replace the loaded model. + */ + Event modifyModelOnLoad(); + + /** + * Event access to replace the unbaked model used for baking without replacing the cached model. + * + *

    This is useful for mods which wish to wrap a model without affecting other models that use it as a parent + * (e.g. wrap a block's model into a non-{@link JsonUnbakedModel} class but still allow the item model to be + * loaded and baked without exceptions). + */ + Event modifyModelBeforeBake(); + + /** + * Event access to monitor baked model loads and replace the loaded model. + */ + Event modifyModelAfterBake(); + } +} diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/ModelModifier.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/ModelModifier.java new file mode 100644 index 0000000000..58786621f2 --- /dev/null +++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/ModelModifier.java @@ -0,0 +1,226 @@ +/* + * 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 net.fabricmc.fabric.api.client.model.loading.v1; + +import java.util.function.Function; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +import net.minecraft.client.render.model.BakedModel; +import net.minecraft.client.render.model.Baker; +import net.minecraft.client.render.model.ModelBakeSettings; +import net.minecraft.client.render.model.ModelLoader; +import net.minecraft.client.render.model.UnbakedModel; +import net.minecraft.client.texture.Sprite; +import net.minecraft.client.util.ModelIdentifier; +import net.minecraft.client.util.SpriteIdentifier; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.api.event.Event; + +/** + * Contains interfaces for the events that can be used to modify models at different points in the loading and baking + * process. + * + *

    Example use cases: + *

      + *
    • Overriding a model for a particular block state - check if the given identifier is a {@link ModelIdentifier}, + * and then check if it has the appropriate variant for that block state. If so, return your desired model, + * otherwise return the given model.
    • + *
    • Wrapping a model to override certain behaviors - simply return a new model instance and delegate calls + * to the original model as needed.
    • + *
    + * + *

    Phases are used to ensure that modifications occur in a reasonable order, e.g. wrapping occurs after overrides, + * and separate phases are provided for mods that wrap their own models and mods that need to wrap models of other mods + * or wrap models arbitrarily. + * + *

    These callbacks are invoked for every single model that is loaded or baked, so implementations should be + * as efficient as possible. + */ +public final class ModelModifier { + /** + * Recommended phase to use when overriding models, e.g. replacing a model with another model. + */ + public static final Identifier OVERRIDE_PHASE = new Identifier("fabric", "override"); + /** + * Recommended phase to use for transformations that need to happen before wrapping, but after model overrides. + */ + public static final Identifier DEFAULT_PHASE = Event.DEFAULT_PHASE; + /** + * Recommended phase to use when wrapping models. + */ + public static final Identifier WRAP_PHASE = new Identifier("fabric", "wrap"); + /** + * Recommended phase to use when wrapping models with transformations that want to happen last, + * e.g. for connected textures or other similar visual effects that should be the final processing step. + */ + public static final Identifier WRAP_LAST_PHASE = new Identifier("fabric", "wrap_last"); + + @FunctionalInterface + public interface OnLoad { + /** + * This handler is invoked to allow modification of an unbaked model right after it is first loaded and before + * it is cached. + * + * @param model the current unbaked model instance + * @param context context with additional information about the model/loader + * @return the model that should be used in this scenario. If no changes are needed, just return {@code model} as-is. + * @see ModelLoadingPlugin.Context#modifyModelOnLoad + */ + UnbakedModel modifyModelOnLoad(UnbakedModel model, Context context); + + /** + * The context for an on load model modification event. + */ + @ApiStatus.NonExtendable + interface Context { + /** + * The identifier of this model (may be a {@link ModelIdentifier}). + * + *

    For item models, only the {@link ModelIdentifier} with the {@code inventory} variant is passed, and + * not the corresponding plain identifier. + */ + Identifier id(); + + /** + * Loads a model using an {@link Identifier} or {@link ModelIdentifier}, or gets it if it was already + * loaded. + * + * @param id the model identifier + * @return the unbaked model, or a missing model if it is not present + */ + UnbakedModel getOrLoadModel(Identifier id); + + /** + * The current model loader instance, which changes between resource reloads. + * + *

    Do not call {@link ModelLoader#getOrLoadModel} as it does not supported nested model + * resolution; use {@link #getOrLoadModel} from the context instead. + */ + ModelLoader loader(); + } + } + + @FunctionalInterface + public interface BeforeBake { + /** + * This handler is invoked to allow modification of the unbaked model instance right before it is baked. + * + * @param model the current unbaked model instance + * @param context context with additional information about the model/loader + * @return the model that should be used in this scenario. If no changes are needed, just return {@code model} as-is. + * @see ModelLoadingPlugin.Context#modifyModelBeforeBake + */ + UnbakedModel modifyModelBeforeBake(UnbakedModel model, Context context); + + /** + * The context for a before bake model modification event. + */ + @ApiStatus.NonExtendable + interface Context { + /** + * The identifier of this model (may be a {@link ModelIdentifier}). + */ + Identifier id(); + + /** + * The function that can be used to retrieve sprites. + */ + Function textureGetter(); + + /** + * The settings this model is being baked with. + */ + ModelBakeSettings settings(); + + /** + * The baker being used to bake this model. + * It can be used to {@linkplain Baker#getOrLoadModel load unbaked models} and + * {@linkplain Baker#bake load baked models}. + */ + Baker baker(); + + /** + * The current model loader instance, which changes between resource reloads. + */ + ModelLoader loader(); + } + } + + @FunctionalInterface + public interface AfterBake { + /** + * This handler is invoked to allow modification of the baked model instance right after it is baked and before + * it is cached. + * + *

    Note that the passed baked model may be null and that this handler may return a null baked model, since + * {@link UnbakedModel#bake} and {@link Baker#bake} may also return null baked models. Null baked models are + * automatically mapped to the missing model during model retrieval. + * + *

    For further information, see the docs of {@link ModelLoadingPlugin.Context#modifyModelAfterBake()}. + * + * @param model the current baked model instance + * @param context context with additional information about the model/loader + * @return the model that should be used in this scenario. If no changes are needed, just return {@code model} as-is. + * @see ModelLoadingPlugin.Context#modifyModelAfterBake + */ + @Nullable + BakedModel modifyModelAfterBake(@Nullable BakedModel model, Context context); + + /** + * The context for an after bake model modification event. + */ + @ApiStatus.NonExtendable + interface Context { + /** + * The identifier of this model (may be a {@link ModelIdentifier}). + */ + Identifier id(); + + /** + * The unbaked model that is being baked. + */ + UnbakedModel sourceModel(); + + /** + * The function that can be used to retrieve sprites. + */ + Function textureGetter(); + + /** + * The settings this model is being baked with. + */ + ModelBakeSettings settings(); + + /** + * The baker being used to bake this model. + * It can be used to {@linkplain Baker#getOrLoadModel load unbaked models} and + * {@linkplain Baker#bake load baked models}. + */ + Baker baker(); + + /** + * The current model loader instance, which changes between resource reloads. + */ + ModelLoader loader(); + } + } + + private ModelModifier() { } +} diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/ModelResolver.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/ModelResolver.java new file mode 100644 index 0000000000..8236af0319 --- /dev/null +++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/ModelResolver.java @@ -0,0 +1,82 @@ +/* + * 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 net.fabricmc.fabric.api.client.model.loading.v1; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +import net.minecraft.client.render.model.ModelLoader; +import net.minecraft.client.render.model.UnbakedModel; +import net.minecraft.client.util.ModelIdentifier; +import net.minecraft.util.Identifier; + +/** + * Model resolvers are able to provide a custom model for specific {@link Identifier}s. + * In vanilla, these {@link Identifier}s are converted to file paths and used to load + * a model from JSON. Since model resolvers override this process, they can be used to + * create custom model formats. + * + *

    Only one resolver may provide a custom model for a certain {@link Identifier}. + * Thus, resolvers that load models using a custom format could conflict. To avoid + * conflicts, such resolvers may want to only load files with a mod-suffixed name + * or only load files that have been explicitly defined elsewhere. + * + *

    If it is necessary to load and bake an arbitrary model that is not referenced + * normally, a model resolver can be used in conjunction with + * {@link ModelLoadingPlugin.Context#addModels} to directly load and bake custom model + * instances. + * + *

    Model resolvers are invoked for every single model that will be loaded, + * so implementations should be as efficient as possible. + * + * @see ModelLoadingPlugin.Context#addModels + */ +@FunctionalInterface +public interface ModelResolver { + /** + * @return the resolved {@link UnbakedModel}, or {@code null} if this resolver does not handle the current {@link Identifier} + */ + @Nullable + UnbakedModel resolveModel(Context context); + + /** + * The context for model resolution. + */ + @ApiStatus.NonExtendable + interface Context { + /** + * The identifier of the model to be loaded. + */ + Identifier id(); + + /** + * Loads a model using an {@link Identifier} or {@link ModelIdentifier}, or gets it if it was already loaded. + * + * @param id the model identifier + * @return the unbaked model, or a missing model if it is not present + */ + UnbakedModel getOrLoadModel(Identifier id); + + /** + * The current model loader instance, which changes between resource reloads. + * + *

    Do not call {@link ModelLoader#getOrLoadModel} as it does not supported nested model resolution; + * use {@link #getOrLoadModel} from the context instead. + */ + ModelLoader loader(); + } +} diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/PreparableModelLoadingPlugin.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/PreparableModelLoadingPlugin.java new file mode 100644 index 0000000000..b900cffc51 --- /dev/null +++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/PreparableModelLoadingPlugin.java @@ -0,0 +1,66 @@ +/* + * 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 net.fabricmc.fabric.api.client.model.loading.v1; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.function.Supplier; + +import net.minecraft.resource.ResourceManager; + +import net.fabricmc.fabric.impl.client.model.loading.ModelLoadingPluginManager; + +/** + * A model loading plugin is used to extend the model loading process through the passed + * {@link ModelLoadingPlugin.Context} object. + * + *

    This version of {@link ModelLoadingPlugin} allows loading ("preparing") some data off-thread in parallel before + * the model loading process starts. Usually, this means loading some resources from the provided + * {@link ResourceManager}. + */ +@FunctionalInterface +public interface PreparableModelLoadingPlugin { + /** + * Registers a preparable model loading plugin. + */ + static void register(DataLoader loader, PreparableModelLoadingPlugin plugin) { + ModelLoadingPluginManager.registerPlugin(loader, plugin); + } + + /** + * Called towards the beginning of the model loading process, every time resource are (re)loaded. + * Use the context object to extend model loading as desired. + * + * @param data The data loaded by the {@link DataLoader}. + * @param pluginContext The context that can be used to extend model loading. + */ + void onInitializeModelLoader(T data, ModelLoadingPlugin.Context pluginContext); + + @FunctionalInterface + interface DataLoader { + /** + * Returns a {@link CompletableFuture} that will load the data. + * Do not block the thread when this function is called, rather use + * {@link CompletableFuture#supplyAsync(Supplier, Executor)} to compute the data. + * The completable future should be scheduled to run using the passed executor. + * + * @param resourceManager The resource manager that can be used to retrieve resources. + * @param executor The executor that must be used to schedule any completable future. + */ + CompletableFuture load(ResourceManager resourceManager, Executor executor); + } +} diff --git a/fabric-models-v0/src/client/java/net/fabricmc/fabric/impl/client/model/ModelLoaderHooks.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/BlockStateResolverHolder.java similarity index 71% rename from fabric-models-v0/src/client/java/net/fabricmc/fabric/impl/client/model/ModelLoaderHooks.java rename to fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/BlockStateResolverHolder.java index e93a75b232..3b70bc224c 100644 --- a/fabric-models-v0/src/client/java/net/fabricmc/fabric/impl/client/model/ModelLoaderHooks.java +++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/BlockStateResolverHolder.java @@ -14,13 +14,12 @@ * limitations under the License. */ -package net.fabricmc.fabric.impl.client.model; +package net.fabricmc.fabric.impl.client.model.loading; -import net.minecraft.client.render.model.UnbakedModel; +import net.minecraft.block.Block; import net.minecraft.util.Identifier; -public interface ModelLoaderHooks { - void fabric_addModel(Identifier id); +import net.fabricmc.fabric.api.client.model.loading.v1.BlockStateResolver; - UnbakedModel fabric_loadModel(Identifier id); +record BlockStateResolverHolder(BlockStateResolver resolver, Block block, Identifier blockId) { } diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/LegacyModelVariantProvider.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/LegacyModelVariantProvider.java new file mode 100644 index 0000000000..ecc5ddc0ba --- /dev/null +++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/LegacyModelVariantProvider.java @@ -0,0 +1,30 @@ +/* + * 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 net.fabricmc.fabric.impl.client.model.loading; + +import org.jetbrains.annotations.Nullable; + +import net.minecraft.client.render.model.UnbakedModel; +import net.minecraft.client.util.ModelIdentifier; + +/** + * Legacy v0 bridge - remove if the legacy v0 module is removed. + */ +public interface LegacyModelVariantProvider { + @Nullable + UnbakedModel loadModelVariant(ModelIdentifier modelId); +} diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/ModelLoaderHooks.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/ModelLoaderHooks.java new file mode 100644 index 0000000000..67e1dfd264 --- /dev/null +++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/ModelLoaderHooks.java @@ -0,0 +1,37 @@ +/* + * 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 net.fabricmc.fabric.impl.client.model.loading; + +import net.minecraft.client.render.model.UnbakedModel; +import net.minecraft.client.render.model.json.JsonUnbakedModel; +import net.minecraft.util.Identifier; + +public interface ModelLoaderHooks { + ModelLoadingEventDispatcher fabric_getDispatcher(); + + UnbakedModel fabric_getMissingModel(); + + UnbakedModel fabric_getOrLoadModel(Identifier id); + + void fabric_putModel(Identifier id, UnbakedModel model); + + void fabric_putModelDirectly(Identifier id, UnbakedModel model); + + void fabric_queueModelDependencies(UnbakedModel model); + + JsonUnbakedModel fabric_loadModelFromJson(Identifier id); +} diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/ModelLoaderPluginContextImpl.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/ModelLoaderPluginContextImpl.java new file mode 100644 index 0000000000..ba4ceec640 --- /dev/null +++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/ModelLoaderPluginContextImpl.java @@ -0,0 +1,223 @@ +/* + * 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 net.fabricmc.fabric.impl.client.model.loading; + +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; + +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.minecraft.block.Block; +import net.minecraft.client.render.model.UnbakedModel; +import net.minecraft.client.util.ModelIdentifier; +import net.minecraft.registry.Registries; +import net.minecraft.registry.RegistryKey; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.api.client.model.loading.v1.BlockStateResolver; +import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin; +import net.fabricmc.fabric.api.client.model.loading.v1.ModelModifier; +import net.fabricmc.fabric.api.client.model.loading.v1.ModelResolver; +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; + +public class ModelLoaderPluginContextImpl implements ModelLoadingPlugin.Context { + private static final Logger LOGGER = LoggerFactory.getLogger(ModelLoaderPluginContextImpl.class); + + final Set extraModels = new LinkedHashSet<>(); + + private final Map blockStateResolvers = new HashMap<>(); + private final BlockKey lookupKey = new BlockKey(); + + private final Event modelResolvers = EventFactory.createArrayBacked(ModelResolver.class, resolvers -> context -> { + for (ModelResolver resolver : resolvers) { + try { + UnbakedModel model = resolver.resolveModel(context); + + if (model != null) { + return model; + } + } catch (Exception exception) { + LOGGER.error("Failed to resolve model", exception); + } + } + + return null; + }); + + private static final Identifier[] MODEL_MODIFIER_PHASES = new Identifier[] { ModelModifier.OVERRIDE_PHASE, ModelModifier.DEFAULT_PHASE, ModelModifier.WRAP_PHASE, ModelModifier.WRAP_LAST_PHASE }; + + private final Event onLoadModifiers = EventFactory.createWithPhases(ModelModifier.OnLoad.class, modifiers -> (model, context) -> { + for (ModelModifier.OnLoad modifier : modifiers) { + try { + model = modifier.modifyModelOnLoad(model, context); + } catch (Exception exception) { + LOGGER.error("Failed to modify unbaked model on load", exception); + } + } + + return model; + }, MODEL_MODIFIER_PHASES); + private final Event beforeBakeModifiers = EventFactory.createWithPhases(ModelModifier.BeforeBake.class, modifiers -> (model, context) -> { + for (ModelModifier.BeforeBake modifier : modifiers) { + try { + model = modifier.modifyModelBeforeBake(model, context); + } catch (Exception exception) { + LOGGER.error("Failed to modify unbaked model before bake", exception); + } + } + + return model; + }, MODEL_MODIFIER_PHASES); + private final Event afterBakeModifiers = EventFactory.createWithPhases(ModelModifier.AfterBake.class, modifiers -> (model, context) -> { + for (ModelModifier.AfterBake modifier : modifiers) { + try { + model = modifier.modifyModelAfterBake(model, context); + } catch (Exception exception) { + LOGGER.error("Failed to modify baked model after bake", exception); + } + } + + return model; + }, MODEL_MODIFIER_PHASES); + + /** + * This field is used by the v0 wrapper to avoid constantly wrapping the context in hot code. + */ + public final Function modelGetter; + + public ModelLoaderPluginContextImpl(Function modelGetter) { + this.modelGetter = modelGetter; + } + + @Override + public void addModels(Identifier... ids) { + for (Identifier id : ids) { + extraModels.add(id); + } + } + + @Override + public void addModels(Collection ids) { + extraModels.addAll(ids); + } + + @Override + public void registerBlockStateResolver(Block block, BlockStateResolver resolver) { + Objects.requireNonNull(block, "block cannot be null"); + Objects.requireNonNull(resolver, "resolver cannot be null"); + + Optional> optionalKey = Registries.BLOCK.getKey(block); + + if (optionalKey.isEmpty()) { + throw new IllegalArgumentException("Received unregistered block"); + } + + Identifier blockId = optionalKey.get().getValue(); + BlockKey key = new BlockKey(blockId.getNamespace(), blockId.getPath()); + BlockStateResolverHolder holder = new BlockStateResolverHolder(resolver, block, blockId); + + if (blockStateResolvers.put(key, holder) != null) { + throw new IllegalArgumentException("Duplicate block state resolver for block " + blockId); + } + } + + @Nullable + BlockStateResolverHolder getBlockStateResolver(ModelIdentifier modelId) { + BlockKey key = lookupKey; + key.namespace = modelId.getNamespace(); + key.path = modelId.getPath(); + + return blockStateResolvers.get(key); + } + + @Override + public Event resolveModel() { + return modelResolvers; + } + + @Override + public Event modifyModelOnLoad() { + return onLoadModifiers; + } + + @Override + public Event modifyModelBeforeBake() { + return beforeBakeModifiers; + } + + @Override + public Event modifyModelAfterBake() { + return afterBakeModifiers; + } + + private static class BlockKey { + private String namespace; + private String path; + + private BlockKey() { + } + + private BlockKey(String namespace, String path) { + this.namespace = namespace; + this.path = path; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BlockKey blockKey = (BlockKey) o; + return namespace.equals(blockKey.namespace) && path.equals(blockKey.path); + } + + @Override + public int hashCode() { + return 31 * namespace.hashCode() + path.hashCode(); + } + } + + // Legacy v0 bridge - remove if the legacy v0 module is removed. + + private final Event legacyVariantProviders = EventFactory.createArrayBacked(LegacyModelVariantProvider.class, providers -> modelId -> { + for (LegacyModelVariantProvider provider : providers) { + try { + UnbakedModel model = provider.loadModelVariant(modelId); + + if (model != null) { + return model; + } + } catch (Exception exception) { + LOGGER.error("Failed to run legacy model variant provider", exception); + } + } + + return null; + }); + + public Event legacyVariantProviders() { + return legacyVariantProviders; + } +} diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/ModelLoadingEventDispatcher.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/ModelLoadingEventDispatcher.java new file mode 100644 index 0000000000..c0a62ce673 --- /dev/null +++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/ModelLoadingEventDispatcher.java @@ -0,0 +1,466 @@ +/* + * 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 net.fabricmc.fabric.impl.client.model.loading; + +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Function; + +import com.google.common.collect.ImmutableList; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.Reference2ReferenceMap; +import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; +import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; +import it.unimi.dsi.fastutil.objects.ReferenceSet; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.client.render.block.BlockModels; +import net.minecraft.client.render.model.BakedModel; +import net.minecraft.client.render.model.Baker; +import net.minecraft.client.render.model.ModelBakeSettings; +import net.minecraft.client.render.model.ModelLoader; +import net.minecraft.client.render.model.UnbakedModel; +import net.minecraft.client.texture.Sprite; +import net.minecraft.client.util.ModelIdentifier; +import net.minecraft.client.util.SpriteIdentifier; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.api.client.model.loading.v1.BlockStateResolver; +import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin; +import net.fabricmc.fabric.api.client.model.loading.v1.ModelModifier; +import net.fabricmc.fabric.api.client.model.loading.v1.ModelResolver; + +public class ModelLoadingEventDispatcher { + private static final Logger LOGGER = LoggerFactory.getLogger(ModelLoadingEventDispatcher.class); + + private final ModelLoader loader; + private final ModelLoaderPluginContextImpl pluginContext; + + private final ObjectArrayList modelResolverContextStack = new ObjectArrayList<>(); + + private final ObjectArrayList blockStateResolverContextStack = new ObjectArrayList<>(); + private final ReferenceSet resolvingBlocks = new ReferenceOpenHashSet<>(); + + private final ObjectArrayList onLoadModifierContextStack = new ObjectArrayList<>(); + private final ObjectArrayList beforeBakeModifierContextStack = new ObjectArrayList<>(); + private final ObjectArrayList afterBakeModifierContextStack = new ObjectArrayList<>(); + + public ModelLoadingEventDispatcher(ModelLoader loader, List plugins) { + this.loader = loader; + this.pluginContext = new ModelLoaderPluginContextImpl(((ModelLoaderHooks) loader)::fabric_getOrLoadModel); + + for (ModelLoadingPlugin plugin : plugins) { + try { + plugin.onInitializeModelLoader(pluginContext); + } catch (Exception exception) { + LOGGER.error("Failed to initialize model loading plugin", exception); + } + } + } + + public void addExtraModels(Consumer extraModelConsumer) { + for (Identifier id : pluginContext.extraModels) { + extraModelConsumer.accept(id); + } + } + + /** + * @return {@code true} to cancel the vanilla method + */ + public boolean loadModel(Identifier id) { + if (id instanceof ModelIdentifier modelId) { + if ("inventory".equals(modelId.getVariant())) { + // We ALWAYS override the vanilla inventory model code path entirely, even for vanilla item models. + // See loadItemModel for an explanation. + loadItemModel(modelId); + return true; + } else { + // Prioritize block state resolver over legacy variant provider + BlockStateResolverHolder resolver = pluginContext.getBlockStateResolver(modelId); + + if (resolver != null) { + loadBlockStateModels(resolver.resolver(), resolver.block(), resolver.blockId()); + return true; + } + + UnbakedModel legacyModel = legacyLoadModelVariant(modelId); + + if (legacyModel != null) { + ((ModelLoaderHooks) loader).fabric_putModel(id, legacyModel); + return true; + } + + return false; + } + } else { + UnbakedModel model = resolveModel(id); + + if (model != null) { + ((ModelLoaderHooks) loader).fabric_putModel(id, model); + return true; + } + + return false; + } + } + + @Nullable + private UnbakedModel legacyLoadModelVariant(ModelIdentifier modelId) { + return pluginContext.legacyVariantProviders().invoker().loadModelVariant(modelId); + } + + /** + * This function handles both modded item models and vanilla item models. + * The vanilla code path for item models is never used. + * See the long comment in the function for an explanation. + */ + private void loadItemModel(ModelIdentifier modelId) { + ModelLoaderHooks loaderHooks = (ModelLoaderHooks) loader; + + Identifier id = modelId.withPrefixedPath("item/"); + + // Legacy variant provider + UnbakedModel model = legacyLoadModelVariant(modelId); + + // Model resolver + if (model == null) { + model = resolveModel(id); + } + + // Load from the vanilla code path otherwise. + if (model == null) { + model = loaderHooks.fabric_loadModelFromJson(id); + } + + // This is a bit tricky: + // We have a single UnbakedModel now, but there are two identifiers: + // the ModelIdentifier (...#inventory) and the Identifier (...:item/...). + // So we call the on load modifier now and then directly add the model to the ModelLoader, + // reimplementing the behavior of ModelLoader#put. + // Calling ModelLoader#put is not an option as the model for the Identifier would not be replaced by an on load modifier. + // This is why we override the vanilla code path entirely. + model = modifyModelOnLoad(modelId, model); + + loaderHooks.fabric_putModelDirectly(modelId, model); + loaderHooks.fabric_putModelDirectly(id, model); + loaderHooks.fabric_queueModelDependencies(model); + } + + private void loadBlockStateModels(BlockStateResolver resolver, Block block, Identifier blockId) { + if (!resolvingBlocks.add(block)) { + throw new IllegalStateException("Circular reference while resolving models for block " + block); + } + + try { + resolveBlockStates(resolver, block, blockId); + } finally { + resolvingBlocks.remove(block); + } + } + + private void resolveBlockStates(BlockStateResolver resolver, Block block, Identifier blockId) { + // Get and prepare context + if (blockStateResolverContextStack.isEmpty()) { + blockStateResolverContextStack.add(new BlockStateResolverContext()); + } + + BlockStateResolverContext context = blockStateResolverContextStack.pop(); + context.prepare(block); + + Reference2ReferenceMap resolvedModels = context.models; + ImmutableList allStates = block.getStateManager().getStates(); + boolean thrown = false; + + // Call resolver + try { + resolver.resolveBlockStates(context); + } catch (Exception e) { + LOGGER.error("Failed to resolve block state models for block {}. Using missing model for all states.", block, e); + thrown = true; + } + + // Copy models over to the loader + if (thrown) { + UnbakedModel missingModel = ((ModelLoaderHooks) loader).fabric_getMissingModel(); + + for (BlockState state : allStates) { + ModelIdentifier modelId = BlockModels.getModelId(blockId, state); + ((ModelLoaderHooks) loader).fabric_putModelDirectly(modelId, missingModel); + } + } else if (resolvedModels.size() == allStates.size()) { + // If there are as many resolved models as total states, all states have + // been resolved and models do not need to be null-checked. + resolvedModels.forEach((state, model) -> { + ModelIdentifier modelId = BlockModels.getModelId(blockId, state); + ((ModelLoaderHooks) loader).fabric_putModel(modelId, model); + }); + } else { + UnbakedModel missingModel = ((ModelLoaderHooks) loader).fabric_getMissingModel(); + + for (BlockState state : allStates) { + ModelIdentifier modelId = BlockModels.getModelId(blockId, state); + @Nullable + UnbakedModel model = resolvedModels.get(state); + + if (model == null) { + LOGGER.error("Block state resolver did not provide a model for state {} in block {}. Using missing model.", state, block); + ((ModelLoaderHooks) loader).fabric_putModelDirectly(modelId, missingModel); + } else { + ((ModelLoaderHooks) loader).fabric_putModel(modelId, model); + } + } + } + + resolvedModels.clear(); + + // Store context for reuse + blockStateResolverContextStack.add(context); + } + + @Nullable + private UnbakedModel resolveModel(Identifier id) { + if (modelResolverContextStack.isEmpty()) { + modelResolverContextStack.add(new ModelResolverContext()); + } + + ModelResolverContext context = modelResolverContextStack.pop(); + context.prepare(id); + + UnbakedModel model = pluginContext.resolveModel().invoker().resolveModel(context); + + modelResolverContextStack.push(context); + return model; + } + + public UnbakedModel modifyModelOnLoad(Identifier id, UnbakedModel model) { + if (onLoadModifierContextStack.isEmpty()) { + onLoadModifierContextStack.add(new OnLoadModifierContext()); + } + + OnLoadModifierContext context = onLoadModifierContextStack.pop(); + context.prepare(id); + + model = pluginContext.modifyModelOnLoad().invoker().modifyModelOnLoad(model, context); + + onLoadModifierContextStack.push(context); + return model; + } + + public UnbakedModel modifyModelBeforeBake(UnbakedModel model, Identifier id, Function textureGetter, ModelBakeSettings settings, Baker baker) { + if (beforeBakeModifierContextStack.isEmpty()) { + beforeBakeModifierContextStack.add(new BeforeBakeModifierContext()); + } + + BeforeBakeModifierContext context = beforeBakeModifierContextStack.pop(); + context.prepare(id, textureGetter, settings, baker); + + model = pluginContext.modifyModelBeforeBake().invoker().modifyModelBeforeBake(model, context); + + beforeBakeModifierContextStack.push(context); + return model; + } + + @Nullable + public BakedModel modifyModelAfterBake(@Nullable BakedModel model, Identifier id, UnbakedModel sourceModel, Function textureGetter, ModelBakeSettings settings, Baker baker) { + if (afterBakeModifierContextStack.isEmpty()) { + afterBakeModifierContextStack.add(new AfterBakeModifierContext()); + } + + AfterBakeModifierContext context = afterBakeModifierContextStack.pop(); + context.prepare(id, sourceModel, textureGetter, settings, baker); + + model = pluginContext.modifyModelAfterBake().invoker().modifyModelAfterBake(model, context); + + afterBakeModifierContextStack.push(context); + return model; + } + + private class ModelResolverContext implements ModelResolver.Context { + private Identifier id; + + private void prepare(Identifier id) { + this.id = id; + } + + @Override + public Identifier id() { + return id; + } + + @Override + public UnbakedModel getOrLoadModel(Identifier id) { + return ((ModelLoaderHooks) loader).fabric_getOrLoadModel(id); + } + + @Override + public ModelLoader loader() { + return loader; + } + } + + private class BlockStateResolverContext implements BlockStateResolver.Context { + private Block block; + private final Reference2ReferenceMap models = new Reference2ReferenceOpenHashMap<>(); + + private void prepare(Block block) { + this.block = block; + models.clear(); + } + + @Override + public Block block() { + return block; + } + + @Override + public void setModel(BlockState state, UnbakedModel model) { + Objects.requireNonNull(model, "state cannot be null"); + Objects.requireNonNull(model, "model cannot be null"); + + if (!state.isOf(block)) { + throw new IllegalArgumentException("Attempted to set model for state " + state + " on block " + block); + } + + if (models.putIfAbsent(state, model) != null) { + throw new IllegalStateException("Duplicate model for state " + state + " on block " + block); + } + } + + @Override + public UnbakedModel getOrLoadModel(Identifier id) { + return ((ModelLoaderHooks) loader).fabric_getOrLoadModel(id); + } + + @Override + public ModelLoader loader() { + return loader; + } + } + + private class OnLoadModifierContext implements ModelModifier.OnLoad.Context { + private Identifier id; + + private void prepare(Identifier id) { + this.id = id; + } + + @Override + public Identifier id() { + return id; + } + + @Override + public UnbakedModel getOrLoadModel(Identifier id) { + return ((ModelLoaderHooks) loader).fabric_getOrLoadModel(id); + } + + @Override + public ModelLoader loader() { + return loader; + } + } + + private class BeforeBakeModifierContext implements ModelModifier.BeforeBake.Context { + private Identifier id; + private Function textureGetter; + private ModelBakeSettings settings; + private Baker baker; + + private void prepare(Identifier id, Function textureGetter, ModelBakeSettings settings, Baker baker) { + this.id = id; + this.textureGetter = textureGetter; + this.settings = settings; + this.baker = baker; + } + + @Override + public Identifier id() { + return id; + } + + @Override + public Function textureGetter() { + return textureGetter; + } + + @Override + public ModelBakeSettings settings() { + return settings; + } + + @Override + public Baker baker() { + return baker; + } + + @Override + public ModelLoader loader() { + return loader; + } + } + + private class AfterBakeModifierContext implements ModelModifier.AfterBake.Context { + private Identifier id; + private UnbakedModel sourceModel; + private Function textureGetter; + private ModelBakeSettings settings; + private Baker baker; + + private void prepare(Identifier id, UnbakedModel sourceModel, Function textureGetter, ModelBakeSettings settings, Baker baker) { + this.id = id; + this.sourceModel = sourceModel; + this.textureGetter = textureGetter; + this.settings = settings; + this.baker = baker; + } + + @Override + public Identifier id() { + return id; + } + + @Override + public UnbakedModel sourceModel() { + return sourceModel; + } + + @Override + public Function textureGetter() { + return textureGetter; + } + + @Override + public ModelBakeSettings settings() { + return settings; + } + + @Override + public Baker baker() { + return baker; + } + + @Override + public ModelLoader loader() { + return loader; + } + } +} diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/ModelLoadingPluginManager.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/ModelLoadingPluginManager.java new file mode 100644 index 0000000000..8ccbee0264 --- /dev/null +++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/ModelLoadingPluginManager.java @@ -0,0 +1,77 @@ +/* + * 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 net.fabricmc.fabric.impl.client.model.loading; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; + +import net.minecraft.resource.ResourceManager; +import net.minecraft.util.Util; + +import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin; +import net.fabricmc.fabric.api.client.model.loading.v1.PreparableModelLoadingPlugin; + +public final class ModelLoadingPluginManager { + private static final List PLUGINS = new ArrayList<>(); + private static final List> PREPARABLE_PLUGINS = new ArrayList<>(); + + public static final ThreadLocal> CURRENT_PLUGINS = new ThreadLocal<>(); + + public static void registerPlugin(ModelLoadingPlugin plugin) { + Objects.requireNonNull(plugin, "plugin must not be null"); + + PLUGINS.add(plugin); + } + + public static void registerPlugin(PreparableModelLoadingPlugin.DataLoader loader, PreparableModelLoadingPlugin plugin) { + Objects.requireNonNull(loader, "data loader must not be null"); + Objects.requireNonNull(plugin, "plugin must not be null"); + + PREPARABLE_PLUGINS.add(new PreparablePluginHolder<>(loader, plugin)); + } + + /** + * The current exception behavior as of 1.20 is as follows. + * If getting a {@link CompletableFuture}s throws then the whole client will crash. + * If a {@link CompletableFuture} completes exceptionally then the resource reload will fail. + */ + public static CompletableFuture> preparePlugins(ResourceManager resourceManager, Executor executor) { + List> futures = new ArrayList<>(); + + for (ModelLoadingPlugin plugin : PLUGINS) { + futures.add(CompletableFuture.completedFuture(plugin)); + } + + for (PreparablePluginHolder holder : PREPARABLE_PLUGINS) { + futures.add(preparePlugin(holder, resourceManager, executor)); + } + + return Util.combine(futures); + } + + private static CompletableFuture preparePlugin(PreparablePluginHolder holder, ResourceManager resourceManager, Executor executor) { + CompletableFuture dataFuture = holder.loader.load(resourceManager, executor); + return dataFuture.thenApply(data -> pluginContext -> holder.plugin.onInitializeModelLoader(data, pluginContext)); + } + + private ModelLoadingPluginManager() { } + + private record PreparablePluginHolder(PreparableModelLoadingPlugin.DataLoader loader, PreparableModelLoadingPlugin plugin) { } +} diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/BakedModelManagerMixin.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/BakedModelManagerMixin.java new file mode 100644 index 0000000000..d627eb951f --- /dev/null +++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/BakedModelManagerMixin.java @@ -0,0 +1,82 @@ +/* + * 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 net.fabricmc.fabric.mixin.client.model.loading; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Executor; +import java.util.function.BiFunction; + +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.Redirect; + +import net.minecraft.client.render.model.BakedModel; +import net.minecraft.client.render.model.BakedModelManager; +import net.minecraft.client.render.model.ModelLoader; +import net.minecraft.client.render.model.json.JsonUnbakedModel; +import net.minecraft.resource.ResourceManager; +import net.minecraft.resource.ResourceReloader; +import net.minecraft.util.Identifier; +import net.minecraft.util.Pair; +import net.minecraft.util.profiler.Profiler; + +import net.fabricmc.fabric.api.client.model.loading.v1.FabricBakedModelManager; +import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin; +import net.fabricmc.fabric.impl.client.model.loading.ModelLoadingPluginManager; + +@Mixin(BakedModelManager.class) +public class BakedModelManagerMixin implements FabricBakedModelManager { + @Shadow + private Map models; + + @Override + public BakedModel getModel(Identifier id) { + return models.get(id); + } + + @Redirect( + method = "reload", + at = @At( + value = "INVOKE", + target = "java/util/concurrent/CompletableFuture.thenCombineAsync(Ljava/util/concurrent/CompletionStage;Ljava/util/function/BiFunction;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;", + remap = false + ), + allow = 1) + private CompletableFuture loadModelPluginData( + CompletableFuture> self, + CompletionStage>> otherFuture, + BiFunction, Map>, ModelLoader> modelLoaderConstructor, + Executor executor, + // reload args + ResourceReloader.Synchronizer synchronizer, + ResourceManager manager, + Profiler prepareProfiler, + Profiler applyProfiler, + Executor prepareExecutor, + Executor applyExecutor) { + CompletableFuture> pluginsFuture = ModelLoadingPluginManager.preparePlugins(manager, prepareExecutor); + CompletableFuture, Map>>> pairFuture = self.thenCombine(otherFuture, Pair::new); + return pairFuture.thenCombineAsync(pluginsFuture, (pair, plugins) -> { + ModelLoadingPluginManager.CURRENT_PLUGINS.set(plugins); + return modelLoaderConstructor.apply(pair.getLeft(), pair.getRight()); + }, executor); + } +} diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/ModelLoaderBakerImplMixin.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/ModelLoaderBakerImplMixin.java new file mode 100644 index 0000000000..ef55ae9000 --- /dev/null +++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/ModelLoaderBakerImplMixin.java @@ -0,0 +1,69 @@ +/* + * 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 net.fabricmc.fabric.mixin.client.model.loading; + +import java.util.function.Function; + +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.ModifyVariable; +import org.spongepowered.asm.mixin.injection.Redirect; + +import net.minecraft.client.render.model.BakedModel; +import net.minecraft.client.render.model.Baker; +import net.minecraft.client.render.model.ModelBakeSettings; +import net.minecraft.client.render.model.ModelLoader; +import net.minecraft.client.render.model.UnbakedModel; +import net.minecraft.client.render.model.json.JsonUnbakedModel; +import net.minecraft.client.texture.Sprite; +import net.minecraft.client.util.SpriteIdentifier; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.impl.client.model.loading.ModelLoaderHooks; +import net.fabricmc.fabric.impl.client.model.loading.ModelLoadingEventDispatcher; + +@Mixin(targets = "net/minecraft/client/render/model/ModelLoader$BakerImpl") +public class ModelLoaderBakerImplMixin { + @Shadow + @Final + private ModelLoader field_40571; + @Shadow + @Final + private Function textureGetter; + + @ModifyVariable(method = "bake", at = @At(value = "INVOKE_ASSIGN", target = "Lnet/minecraft/client/render/model/ModelLoader$BakerImpl;getOrLoadModel(Lnet/minecraft/util/Identifier;)Lnet/minecraft/client/render/model/UnbakedModel;")) + private UnbakedModel invokeModifyBeforeBake(UnbakedModel model, Identifier id, ModelBakeSettings settings) { + ModelLoadingEventDispatcher dispatcher = ((ModelLoaderHooks) this.field_40571).fabric_getDispatcher(); + return dispatcher.modifyModelBeforeBake(model, id, textureGetter, settings, (Baker) this); + } + + @Redirect(method = "bake", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/model/UnbakedModel;bake(Lnet/minecraft/client/render/model/Baker;Ljava/util/function/Function;Lnet/minecraft/client/render/model/ModelBakeSettings;Lnet/minecraft/util/Identifier;)Lnet/minecraft/client/render/model/BakedModel;")) + private BakedModel invokeModifyAfterBake(UnbakedModel unbakedModel, Baker baker, Function textureGetter, ModelBakeSettings settings, Identifier id) { + BakedModel model = unbakedModel.bake(baker, textureGetter, settings, id); + ModelLoadingEventDispatcher dispatcher = ((ModelLoaderHooks) this.field_40571).fabric_getDispatcher(); + return dispatcher.modifyModelAfterBake(model, id, unbakedModel, textureGetter, settings, baker); + } + + @Redirect(method = "bake", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/model/json/JsonUnbakedModel;bake(Lnet/minecraft/client/render/model/Baker;Lnet/minecraft/client/render/model/json/JsonUnbakedModel;Ljava/util/function/Function;Lnet/minecraft/client/render/model/ModelBakeSettings;Lnet/minecraft/util/Identifier;Z)Lnet/minecraft/client/render/model/BakedModel;")) + private BakedModel invokeModifyAfterBake(JsonUnbakedModel unbakedModel, Baker baker, JsonUnbakedModel parent, Function textureGetter, ModelBakeSettings settings, Identifier id, boolean hasDepth) { + BakedModel model = unbakedModel.bake(baker, parent, textureGetter, settings, id, hasDepth); + ModelLoadingEventDispatcher dispatcher = ((ModelLoaderHooks) this.field_40571).fabric_getDispatcher(); + return dispatcher.modifyModelAfterBake(model, id, unbakedModel, textureGetter, settings, baker); + } +} diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/ModelLoaderMixin.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/ModelLoaderMixin.java new file mode 100644 index 0000000000..33cf18a349 --- /dev/null +++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/ModelLoaderMixin.java @@ -0,0 +1,201 @@ +/* + * 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 net.fabricmc.fabric.mixin.client.model.loading; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.ModifyVariable; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import net.minecraft.client.color.block.BlockColors; +import net.minecraft.client.render.model.ModelLoader; +import net.minecraft.client.render.model.UnbakedModel; +import net.minecraft.client.render.model.json.JsonUnbakedModel; +import net.minecraft.client.util.ModelIdentifier; +import net.minecraft.util.Identifier; +import net.minecraft.util.profiler.Profiler; + +import net.fabricmc.fabric.impl.client.model.loading.ModelLoaderHooks; +import net.fabricmc.fabric.impl.client.model.loading.ModelLoadingEventDispatcher; +import net.fabricmc.fabric.impl.client.model.loading.ModelLoadingPluginManager; + +@Mixin(ModelLoader.class) +public abstract class ModelLoaderMixin implements ModelLoaderHooks { + // The missing model is always loaded and added first. + @Final + @Shadow + public static ModelIdentifier MISSING_ID; + @Final + @Shadow + private Set modelsToLoad; + @Final + @Shadow + private Map unbakedModels; + @Shadow + @Final + private Map modelsToBake; + + @Unique + private ModelLoadingEventDispatcher fabric_eventDispatcher; + // Explicitly not @Unique to allow mods that heavily rework model loading to reimplement the guard. + // Note that this is an implementation detail; it can change at any time. + private int fabric_guardGetOrLoadModel = 0; + private boolean fabric_enableGetOrLoadModelGuard = true; + + @Shadow + private void addModel(ModelIdentifier id) { + } + + @Shadow + public abstract UnbakedModel getOrLoadModel(Identifier id); + + @Shadow + private void loadModel(Identifier id) { + } + + @Shadow + private void putModel(Identifier id, UnbakedModel unbakedModel) { + } + + @Shadow + public abstract JsonUnbakedModel loadModelFromJson(Identifier id); + + @Inject(method = "", at = @At(value = "INVOKE", target = "net/minecraft/util/profiler/Profiler.swap(Ljava/lang/String;)V", ordinal = 0)) + private void afterMissingModelInit(BlockColors blockColors, Profiler profiler, Map jsonUnbakedModels, Map> blockStates, CallbackInfo info) { + // Sanity check + if (!unbakedModels.containsKey(MISSING_ID)) { + throw new AssertionError("Missing model not initialized. This is likely a Fabric API porting bug."); + } + + profiler.swap("fabric_plugins_init"); + + fabric_eventDispatcher = new ModelLoadingEventDispatcher((ModelLoader) (Object) this, ModelLoadingPluginManager.CURRENT_PLUGINS.get()); + ModelLoadingPluginManager.CURRENT_PLUGINS.remove(); + fabric_eventDispatcher.addExtraModels(this::addModel); + } + + @Unique + private void addModel(Identifier id) { + if (id instanceof ModelIdentifier) { + addModel((ModelIdentifier) id); + } else { + // The vanilla addModel method is arbitrarily limited to ModelIdentifiers, + // but it's useful to tell the game to just load and bake a direct model path as well. + // Replicate the vanilla logic of addModel here. + UnbakedModel unbakedModel = getOrLoadModel(id); + this.unbakedModels.put(id, unbakedModel); + this.modelsToBake.put(id, unbakedModel); + } + } + + @Inject(method = "getOrLoadModel", at = @At("HEAD")) + private void fabric_preventNestedGetOrLoadModel(Identifier id, CallbackInfoReturnable cir) { + if (fabric_enableGetOrLoadModelGuard && fabric_guardGetOrLoadModel > 0) { + throw new IllegalStateException("ModelLoader#getOrLoadModel called from a ModelResolver or ModelModifier.OnBake instance. This is not allowed to prevent errors during model loading. Use getOrLoadModel from the context instead."); + } + } + + @Inject(method = "loadModel", at = @At("HEAD"), cancellable = true) + private void onLoadModel(Identifier id, CallbackInfo ci) { + // Prevent calls to getOrLoadModel from loadModel as it will cause problems. + // Mods should call getOrLoadModel on the ModelResolver.Context instead. + fabric_guardGetOrLoadModel++; + + try { + if (fabric_eventDispatcher.loadModel(id)) { + ci.cancel(); + } + } finally { + fabric_guardGetOrLoadModel--; + } + } + + @ModifyVariable(method = "putModel", at = @At("HEAD"), argsOnly = true) + private UnbakedModel onPutModel(UnbakedModel model, Identifier id) { + fabric_guardGetOrLoadModel++; + + try { + return fabric_eventDispatcher.modifyModelOnLoad(id, model); + } finally { + fabric_guardGetOrLoadModel--; + } + } + + @Override + public ModelLoadingEventDispatcher fabric_getDispatcher() { + return fabric_eventDispatcher; + } + + @Override + public UnbakedModel fabric_getMissingModel() { + return unbakedModels.get(MISSING_ID); + } + + /** + * Unlike getOrLoadModel, this method supports nested model loading. + * + *

    Vanilla does not due to the iteration over modelsToLoad which causes models to be resolved multiple times, + * possibly leading to crashes. + */ + @Override + public UnbakedModel fabric_getOrLoadModel(Identifier id) { + if (this.unbakedModels.containsKey(id)) { + return this.unbakedModels.get(id); + } + + if (!modelsToLoad.add(id)) { + throw new IllegalStateException("Circular reference while loading " + id); + } + + try { + loadModel(id); + } finally { + modelsToLoad.remove(id); + } + + return unbakedModels.get(id); + } + + @Override + public void fabric_putModel(Identifier id, UnbakedModel model) { + putModel(id, model); + } + + @Override + public void fabric_putModelDirectly(Identifier id, UnbakedModel model) { + unbakedModels.put(id, model); + } + + @Override + public void fabric_queueModelDependencies(UnbakedModel model) { + modelsToLoad.addAll(model.getModelDependencies()); + } + + @Override + public JsonUnbakedModel fabric_loadModelFromJson(Identifier id) { + return loadModelFromJson(id); + } +} diff --git a/fabric-rendering-data-attachment-v1/src/main/resources/assets/fabric-rendering-data-attachment-v1/icon.png b/fabric-model-loading-api-v1/src/client/resources/assets/fabric-model-loading-api-v1/icon.png similarity index 100% rename from fabric-rendering-data-attachment-v1/src/main/resources/assets/fabric-rendering-data-attachment-v1/icon.png rename to fabric-model-loading-api-v1/src/client/resources/assets/fabric-model-loading-api-v1/icon.png diff --git a/fabric-model-loading-api-v1/src/client/resources/fabric-model-loading-api-v1.mixins.json b/fabric-model-loading-api-v1/src/client/resources/fabric-model-loading-api-v1.mixins.json new file mode 100644 index 0000000000..133a6b4428 --- /dev/null +++ b/fabric-model-loading-api-v1/src/client/resources/fabric-model-loading-api-v1.mixins.json @@ -0,0 +1,13 @@ +{ + "required": true, + "package": "net.fabricmc.fabric.mixin.client.model.loading", + "compatibilityLevel": "JAVA_17", + "client": [ + "BakedModelManagerMixin", + "ModelLoaderMixin", + "ModelLoaderBakerImplMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/fabric-model-loading-api-v1/src/client/resources/fabric.mod.json b/fabric-model-loading-api-v1/src/client/resources/fabric.mod.json new file mode 100644 index 0000000000..be0a98beb5 --- /dev/null +++ b/fabric-model-loading-api-v1/src/client/resources/fabric.mod.json @@ -0,0 +1,38 @@ +{ + "schemaVersion": 1, + "id": "fabric-model-loading-api-v1", + "name": "Fabric Model Loading API (v1)", + "version": "${version}", + "environment": "client", + "license": "Apache-2.0", + "icon": "assets/fabric-model-loading-api-v1/icon.png", + "contact": { + "homepage": "https://fabricmc.net", + "irc": "irc://irc.esper.net:6667/fabric", + "issues": "https://github.com/FabricMC/fabric/issues", + "sources": "https://github.com/FabricMC/fabric" + }, + "authors": [ + "FabricMC" + ], + "depends": { + "fabricloader": ">=0.14.21", + "fabric-api-base": "*" + }, + "breaks": { + "fabric-models-v0": "<0.4.0" + }, + "description": "Provides hooks for model loading.", + "mixins": [ + { + "environment": "client", + "config": "fabric-model-loading-api-v1.mixins.json" + } + ], + "custom": { + "fabric-api:module-lifecycle": "stable", + "loom:injected_interfaces": { + "net/minecraft/class_1092": [ "net/fabricmc/fabric/api/client/model/loading/v1/FabricBakedModelManager" ] + } + } +} diff --git a/fabric-models-v0/src/testmodClient/java/net/fabricmc/fabric/test/model/BakedModelFeatureRenderer.java b/fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/BakedModelFeatureRenderer.java similarity index 86% rename from fabric-models-v0/src/testmodClient/java/net/fabricmc/fabric/test/model/BakedModelFeatureRenderer.java rename to fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/BakedModelFeatureRenderer.java index 9577ce3cfa..056ac08fe0 100644 --- a/fabric-models-v0/src/testmodClient/java/net/fabricmc/fabric/test/model/BakedModelFeatureRenderer.java +++ b/fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/BakedModelFeatureRenderer.java @@ -14,13 +14,15 @@ * limitations under the License. */ -package net.fabricmc.fabric.test.model; +package net.fabricmc.fabric.test.model.loading; import java.util.function.Supplier; import org.joml.AxisAngle4f; import org.joml.Quaternionf; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.render.OverlayTexture; import net.minecraft.client.render.TexturedRenderLayers; import net.minecraft.client.render.VertexConsumer; import net.minecraft.client.render.VertexConsumerProvider; @@ -32,7 +34,7 @@ import net.minecraft.entity.LivingEntity; public class BakedModelFeatureRenderer> extends FeatureRenderer { - private Supplier modelSupplier; + private final Supplier modelSupplier; public BakedModelFeatureRenderer(FeatureRendererContext context, Supplier modelSupplier) { super(context); @@ -50,7 +52,7 @@ public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, matrices.scale(-0.75F, -0.75F, 0.75F); float aboveHead = (float) (Math.sin(animationProgress * 0.08F)) * 0.5F + 0.5F; matrices.translate(-0.5F, 0.75F + aboveHead, -0.5F); - BakedModelRenderer.renderBakedModel(model, vertices, matrices.peek(), light); + MinecraftClient.getInstance().getBlockRenderManager().getModelRenderer().render(matrices.peek(), vertices, null, model, 1, 1, 1, light, OverlayTexture.DEFAULT_UV); matrices.pop(); } } diff --git a/fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/ModelTestModClient.java b/fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/ModelTestModClient.java new file mode 100644 index 0000000000..2f0ba65ab9 --- /dev/null +++ b/fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/ModelTestModClient.java @@ -0,0 +1,122 @@ +/* + * 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 net.fabricmc.fabric.test.model.loading; + +import java.util.function.Supplier; + +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.block.CropBlock; +import net.minecraft.block.HorizontalConnectingBlock; +import net.minecraft.client.render.block.BlockModels; +import net.minecraft.client.render.entity.PlayerEntityRenderer; +import net.minecraft.client.render.model.BakedModel; +import net.minecraft.client.render.model.ModelLoader; +import net.minecraft.client.render.model.UnbakedModel; +import net.minecraft.client.util.ModelIdentifier; +import net.minecraft.resource.ResourceType; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.random.Random; +import net.minecraft.world.BlockRenderView; + +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin; +import net.fabricmc.fabric.api.client.model.loading.v1.ModelModifier; +import net.fabricmc.fabric.api.client.model.loading.v1.DelegatingUnbakedModel; +import net.fabricmc.fabric.api.client.rendering.v1.LivingEntityFeatureRendererRegistrationCallback; +import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel; +import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; +import net.fabricmc.fabric.api.resource.ResourceManagerHelper; + +public class ModelTestModClient implements ClientModInitializer { + public static final String ID = "fabric-model-loading-api-v1-testmod"; + + public static final Identifier MODEL_ID = new Identifier(ID, "half_red_sand"); + + static class DownQuadRemovingModel extends ForwardingBakedModel { + DownQuadRemovingModel(BakedModel model) { + wrapped = model; + } + + @Override + public void emitBlockQuads(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier randomSupplier, RenderContext context) { + context.pushTransform(q -> q.cullFace() != Direction.DOWN); + super.emitBlockQuads(blockView, state, pos, randomSupplier, context); + context.popTransform(); + } + } + + @Override + public void onInitializeClient() { + ModelLoadingPlugin.register(pluginContext -> { + pluginContext.addModels(MODEL_ID); + // remove bottom face of gold blocks + pluginContext.modifyModelAfterBake().register(ModelModifier.WRAP_PHASE, (model, context) -> { + if (context.id().getPath().equals("block/gold_block")) { + return new DownQuadRemovingModel(model); + } else { + return model; + } + }); + // make fences with west: true and everything else false appear to be a missing model visually + ModelIdentifier fenceId = BlockModels.getModelId(Blocks.OAK_FENCE.getDefaultState().with(HorizontalConnectingBlock.WEST, true)); + pluginContext.modifyModelOnLoad().register(ModelModifier.OVERRIDE_PHASE, (model, context) -> { + if (fenceId.equals(context.id())) { + return context.getOrLoadModel(ModelLoader.MISSING_ID); + } + + return model; + }); + // make brown glazed terracotta appear to be a missing model visually, but without affecting the item, by using pre-bake + // using load here would make the item also appear missing + pluginContext.modifyModelBeforeBake().register(ModelModifier.OVERRIDE_PHASE, (model, context) -> { + if (context.id().getPath().equals("block/brown_glazed_terracotta")) { + return context.loader().getOrLoadModel(ModelLoader.MISSING_ID); + } + + return model; + }); + + // Make wheat stages 1->6 use the same model as stage 0. This can be done with resource packs, this is just a test. + pluginContext.registerBlockStateResolver(Blocks.WHEAT, context -> { + BlockState state = context.block().getDefaultState(); + + // All the block state models are top-level... + // Use a delegating unbaked model to make sure the identical models only get baked a single time. + Identifier wheatStage0Id = new Identifier("block/wheat_stage0"); + + UnbakedModel stage0Model = new DelegatingUnbakedModel(wheatStage0Id); + + for (int age = 0; age <= 6; age++) { + context.setModel(state.with(CropBlock.AGE, age), stage0Model); + } + + context.setModel(state.with(CropBlock.AGE, 7), context.getOrLoadModel(new Identifier("block/wheat_stage7"))); + }); + }); + + ResourceManagerHelper.get(ResourceType.CLIENT_RESOURCES).registerReloadListener(SpecificModelReloadListener.INSTANCE); + + LivingEntityFeatureRendererRegistrationCallback.EVENT.register((entityType, entityRenderer, registrationHelper, context) -> { + if (entityRenderer instanceof PlayerEntityRenderer playerRenderer) { + registrationHelper.register(new BakedModelFeatureRenderer<>(playerRenderer, SpecificModelReloadListener.INSTANCE::getSpecificModel)); + } + }); + } +} diff --git a/fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/NestedModelLoadingTest.java b/fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/NestedModelLoadingTest.java new file mode 100644 index 0000000000..7183940e40 --- /dev/null +++ b/fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/NestedModelLoadingTest.java @@ -0,0 +1,108 @@ +/* + * 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 net.fabricmc.fabric.test.model.loading; + +import com.mojang.logging.LogUtils; +import org.slf4j.Logger; + +import net.minecraft.client.render.model.ModelLoader; +import net.minecraft.client.render.model.UnbakedModel; +import net.minecraft.util.Identifier; + +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin; + +/** + * Tests that deep model resolution resolve each model a single time, depth-first. + */ +public class NestedModelLoadingTest implements ClientModInitializer { + private static final Logger LOGGER = LogUtils.getLogger(); + + private static Identifier id(String path) { + return new Identifier("fabric-model-loading-api-v1-testmod", path); + } + + private static final Identifier BASE_MODEL = id("nested_base"); + private static final Identifier NESTED_MODEL_1 = id("nested_1"); + private static final Identifier NESTED_MODEL_2 = id("nested_2"); + private static final Identifier NESTED_MODEL_3 = id("nested_3"); + private static final Identifier NESTED_MODEL_4 = id("nested_4"); + private static final Identifier NESTED_MODEL_5 = id("nested_5"); + private static final Identifier TARGET_MODEL = new Identifier("minecraft", "block/stone"); + + @Override + public void onInitializeClient() { + ModelLoadingPlugin.register(pluginContext -> { + pluginContext.addModels(BASE_MODEL); + + pluginContext.resolveModel().register(context -> { + Identifier id = context.id(); + UnbakedModel ret = null; + + if (id.equals(BASE_MODEL)) { + LOGGER.info("Nested model 1 started loading"); + ret = context.getOrLoadModel(NESTED_MODEL_1); + LOGGER.info("Nested model 1 finished loading"); + } else if (id.equals(NESTED_MODEL_1)) { + LOGGER.info(" Nested model 2 started loading"); + ret = context.getOrLoadModel(NESTED_MODEL_2); + LOGGER.info(" Nested model 2 finished loading"); + } else if (id.equals(NESTED_MODEL_2)) { + LOGGER.info(" Nested model 3 started loading"); + ret = context.getOrLoadModel(NESTED_MODEL_3); + LOGGER.info(" Nested model 3 finished loading"); + } else if (id.equals(NESTED_MODEL_3)) { + // Will be overridden by the model modifier below anyway. + LOGGER.info(" Returning dummy model for nested model 3"); + ret = context.getOrLoadModel(ModelLoader.MISSING_ID); + } else if (id.equals(NESTED_MODEL_4)) { + // Will be overridden by the model modifier below anyway. + LOGGER.info(" Returning dummy model for nested model 4"); + ret = context.getOrLoadModel(ModelLoader.MISSING_ID); + } else if (id.equals(NESTED_MODEL_5)) { + LOGGER.info(" Target model started loading"); + ret = context.getOrLoadModel(TARGET_MODEL); + LOGGER.info(" Target model finished loading"); + } + + return ret; + }); + + pluginContext.modifyModelOnLoad().register((model, context) -> { + UnbakedModel ret = model; + + if (context.id().equals(NESTED_MODEL_3)) { + Identifier id = context.id(); + + LOGGER.info(" Nested model 4 started loading"); + ret = context.getOrLoadModel(NESTED_MODEL_4); + LOGGER.info(" Nested model 4 finished loading"); + + if (!id.equals(context.id())) { + throw new AssertionError("Context object should not have changed."); + } + } else if (context.id().equals(NESTED_MODEL_4)) { + LOGGER.info(" Nested model 5 started loading"); + ret = context.getOrLoadModel(NESTED_MODEL_5); + LOGGER.info(" Nested model 5 finished loading"); + } + + return ret; + }); + }); + } +} diff --git a/fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/PreparablePluginTest.java b/fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/PreparablePluginTest.java new file mode 100644 index 0000000000..eb4ec3331f --- /dev/null +++ b/fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/PreparablePluginTest.java @@ -0,0 +1,89 @@ +/* + * 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 net.fabricmc.fabric.test.model.loading; + +import java.io.BufferedReader; +import java.util.ArrayList; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.stream.Collectors; + +import com.mojang.datafixers.util.Pair; +import com.mojang.logging.LogUtils; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; + +import net.minecraft.client.render.model.BakedModelManager; +import net.minecraft.client.render.model.UnbakedModel; +import net.minecraft.client.render.model.json.JsonUnbakedModel; +import net.minecraft.resource.Resource; +import net.minecraft.resource.ResourceFinder; +import net.minecraft.resource.ResourceManager; +import net.minecraft.util.Identifier; +import net.minecraft.util.Util; + +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.model.loading.v1.PreparableModelLoadingPlugin; + +/** + * Allows putting model files in {@code /model_replacements} instead of {@code /models} to override models. + * This is just a test for off-thread data loading. + * + *

    The visible effect in game is that gold blocks use the diamond texture instead... + */ +public class PreparablePluginTest implements ClientModInitializer { + private static final Logger LOGGER = LogUtils.getLogger(); + private static final ResourceFinder MODEL_REPLACEMENTS_FINDER = ResourceFinder.json("model_replacements"); + + @Override + public void onInitializeClient() { + PreparableModelLoadingPlugin.register(PreparablePluginTest::loadModelReplacements, (replacementModels, pluginContext) -> { + pluginContext.modifyModelOnLoad().register((model, ctx) -> { + @Nullable + UnbakedModel replacementModel = replacementModels.get(ctx.id()); + return replacementModel == null ? model : replacementModel; + }); + }); + } + + /** + * Adaptation of the {@link BakedModelManager} method. + */ + private static CompletableFuture> loadModelReplacements(ResourceManager resourceManager, Executor executor) { + return CompletableFuture.supplyAsync(() -> MODEL_REPLACEMENTS_FINDER.findResources(resourceManager), executor).thenCompose(models2 -> { + ArrayList>> list = new ArrayList<>(models2.size()); + + for (Map.Entry entry : models2.entrySet()) { + list.add(CompletableFuture.supplyAsync(() -> { + try (BufferedReader reader = entry.getValue().getReader()) { + // Remove model_replacements/ prefix from the identifier + Identifier modelId = MODEL_REPLACEMENTS_FINDER.toResourceId(entry.getKey()); + + return Pair.of(modelId, JsonUnbakedModel.deserialize(reader)); + } catch (Exception exception) { + LOGGER.error("Failed to load model {}", entry.getKey(), exception); + return null; + } + }, executor)); + } + + return Util.combineSafe(list).thenApply(models -> models.stream().filter(Objects::nonNull).collect(Collectors.toUnmodifiableMap(Pair::getFirst, Pair::getSecond))); + }); + } +} diff --git a/fabric-models-v0/src/testmodClient/java/net/fabricmc/fabric/test/model/SpecificModelReloadListener.java b/fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/SpecificModelReloadListener.java similarity index 88% rename from fabric-models-v0/src/testmodClient/java/net/fabricmc/fabric/test/model/SpecificModelReloadListener.java rename to fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/SpecificModelReloadListener.java index 948b419b31..7a9c9f90fd 100644 --- a/fabric-models-v0/src/testmodClient/java/net/fabricmc/fabric/test/model/SpecificModelReloadListener.java +++ b/fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/SpecificModelReloadListener.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package net.fabricmc.fabric.test.model; +package net.fabricmc.fabric.test.model.loading; import java.util.Arrays; import java.util.Collection; @@ -27,7 +27,6 @@ import net.minecraft.util.Unit; import net.minecraft.util.profiler.Profiler; -import net.fabricmc.fabric.api.client.model.BakedModelManagerHelper; import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener; import net.fabricmc.fabric.api.resource.ResourceReloadListenerKeys; @@ -48,7 +47,7 @@ protected Unit prepare(ResourceManager manager, Profiler profiler) { @Override protected void apply(Unit loader, ResourceManager manager, Profiler profiler) { - specificModel = BakedModelManagerHelper.getModel(MinecraftClient.getInstance().getBakedModelManager(), ModelTestModClient.MODEL_ID); + specificModel = MinecraftClient.getInstance().getBakedModelManager().getModel(ModelTestModClient.MODEL_ID); } @Override diff --git a/fabric-models-v0/src/testmodClient/resources/assets/fabric-models-v0-testmod/models/half_red_sand.json b/fabric-model-loading-api-v1/src/testmodClient/resources/assets/fabric-model-loading-api-v1-testmod/models/half_red_sand.json similarity index 100% rename from fabric-models-v0/src/testmodClient/resources/assets/fabric-models-v0-testmod/models/half_red_sand.json rename to fabric-model-loading-api-v1/src/testmodClient/resources/assets/fabric-model-loading-api-v1-testmod/models/half_red_sand.json diff --git a/fabric-model-loading-api-v1/src/testmodClient/resources/assets/minecraft/model_replacements/block/gold_block.json b/fabric-model-loading-api-v1/src/testmodClient/resources/assets/minecraft/model_replacements/block/gold_block.json new file mode 100644 index 0000000000..f03eb10fd5 --- /dev/null +++ b/fabric-model-loading-api-v1/src/testmodClient/resources/assets/minecraft/model_replacements/block/gold_block.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:block/cube_all", + "textures": { + "all": "minecraft:block/diamond_block" + } +} diff --git a/fabric-model-loading-api-v1/src/testmodClient/resources/fabric.mod.json b/fabric-model-loading-api-v1/src/testmodClient/resources/fabric.mod.json new file mode 100644 index 0000000000..97eea29e4d --- /dev/null +++ b/fabric-model-loading-api-v1/src/testmodClient/resources/fabric.mod.json @@ -0,0 +1,19 @@ +{ + "schemaVersion": 1, + "id": "fabric-model-loading-api-v1-testmod", + "name": "Fabric Model Loading API (v1) Test Mod", + "version": "1.0.0", + "environment": "client", + "license": "Apache-2.0", + "depends": { + "fabric-model-loading-api-v1": "*", + "fabric-resource-loader-v0": "*" + }, + "entrypoints": { + "client": [ + "net.fabricmc.fabric.test.model.loading.ModelTestModClient", + "net.fabricmc.fabric.test.model.loading.NestedModelLoadingTest", + "net.fabricmc.fabric.test.model.loading.PreparablePluginTest" + ] + } +} diff --git a/fabric-models-v0/src/client/java/net/fabricmc/fabric/impl/client/model/ModelLoadingRegistryImpl.java b/fabric-models-v0/src/client/java/net/fabricmc/fabric/impl/client/model/ModelLoadingRegistryImpl.java deleted file mode 100644 index 4c58d8da9e..0000000000 --- a/fabric-models-v0/src/client/java/net/fabricmc/fabric/impl/client/model/ModelLoadingRegistryImpl.java +++ /dev/null @@ -1,209 +0,0 @@ -/* - * 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 net.fabricmc.fabric.impl.client.model; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Objects; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.stream.Collectors; - -import com.google.common.collect.Lists; -import org.slf4j.LoggerFactory; -import org.slf4j.Logger; -import org.jetbrains.annotations.Nullable; - -import net.minecraft.client.render.model.ModelLoader; -import net.minecraft.client.render.model.UnbakedModel; -import net.minecraft.client.util.ModelIdentifier; -import net.minecraft.resource.ResourceManager; -import net.minecraft.util.Identifier; - -import net.fabricmc.fabric.api.client.model.ExtraModelProvider; -import net.fabricmc.fabric.api.client.model.ModelAppender; -import net.fabricmc.fabric.api.client.model.ModelLoadingRegistry; -import net.fabricmc.fabric.api.client.model.ModelProviderContext; -import net.fabricmc.fabric.api.client.model.ModelProviderException; -import net.fabricmc.fabric.api.client.model.ModelResourceProvider; -import net.fabricmc.fabric.api.client.model.ModelVariantProvider; -import net.fabricmc.loader.api.FabricLoader; - -public class ModelLoadingRegistryImpl implements ModelLoadingRegistry { - private static final boolean DEBUG_MODEL_LOADING = FabricLoader.getInstance().isDevelopmentEnvironment() - || Boolean.valueOf(System.getProperty("fabric.debugModelLoading", "false")); - - @FunctionalInterface - private interface CustomModelItf { - UnbakedModel load(T obj) throws ModelProviderException; - } - - public static class LoaderInstance implements ModelProviderContext { - private final Logger logger; - private final ResourceManager manager; - private final List modelVariantProviders; - private final List modelResourceProviders; - private final List modelAppenders; - private ModelLoader loader; - - private LoaderInstance(ModelLoadingRegistryImpl i, ModelLoader loader, ResourceManager manager) { - this.logger = ModelLoadingRegistryImpl.LOGGER; - this.loader = loader; - this.manager = manager; - this.modelVariantProviders = i.variantProviderSuppliers.stream().map((s) -> s.apply(manager)).collect(Collectors.toList()); - this.modelResourceProviders = i.resourceProviderSuppliers.stream().map((s) -> s.apply(manager)).collect(Collectors.toList()); - this.modelAppenders = i.appenders; - } - - @Override - public UnbakedModel loadModel(Identifier id) { - if (loader == null) { - throw new RuntimeException("Called loadModel too late!"); - } - - return ((ModelLoaderHooks) loader).fabric_loadModel(id); - } - - public void onModelPopulation(Consumer addModel) { - for (ExtraModelProvider appender : modelAppenders) { - appender.provideExtraModels(manager, addModel); - } - } - - private UnbakedModel loadCustomModel(CustomModelItf function, Collection loaders, String debugName) { - if (!DEBUG_MODEL_LOADING) { - for (T provider : loaders) { - try { - UnbakedModel model = function.load(provider); - - if (model != null) { - return model; - } - } catch (ModelProviderException e) { - logger.error("Failed to load custom model", e); - return null; - } - } - - return null; - } - - UnbakedModel modelLoaded = null; - T providerUsed = null; - List providersApplied = null; - - for (T provider : loaders) { - try { - UnbakedModel model = function.load(provider); - - if (model != null) { - if (providersApplied != null) { - providersApplied.add(provider); - } else if (providerUsed != null) { - providersApplied = Lists.newArrayList(providerUsed, provider); - } else { - modelLoaded = model; - providerUsed = provider; - } - } - } catch (ModelProviderException e) { - logger.error("Failed to load custom model", e); - return null; - } - } - - if (providersApplied != null) { - StringBuilder builder = new StringBuilder("Conflict - multiple " + debugName + "s claimed the same unbaked model:"); - - for (T loader : providersApplied) { - builder.append("\n\t - ").append(loader.getClass().getName()); - } - - logger.error(builder.toString()); - return null; - } else { - return modelLoaded; - } - } - - @Nullable - public UnbakedModel loadModelFromResource(Identifier resourceId) { - return loadCustomModel((r) -> r.loadModelResource(resourceId, this), modelResourceProviders, "resource provider"); - } - - @Nullable - public UnbakedModel loadModelFromVariant(Identifier variantId) { - if (!(variantId instanceof ModelIdentifier)) { - return loadModelFromResource(variantId); - } else { - ModelIdentifier modelId = (ModelIdentifier) variantId; - UnbakedModel model = loadCustomModel((r) -> r.loadModelVariant((ModelIdentifier) variantId, this), modelVariantProviders, "resource provider"); - - if (model != null) { - return model; - } - - // Replicating the special-case from ModelLoader as loadModelFromJson is insufficiently patchable - if (Objects.equals(modelId.getVariant(), "inventory")) { - Identifier resourceId = new Identifier(modelId.getNamespace(), "item/" + modelId.getPath()); - model = loadModelFromResource(resourceId); - - if (model != null) { - return model; - } - } - - return null; - } - } - - public void finish() { - loader = null; - } - } - - private static final Logger LOGGER = LoggerFactory.getLogger(ModelLoadingRegistryImpl.class); - - private final List> variantProviderSuppliers = new ArrayList<>(); - private final List> resourceProviderSuppliers = new ArrayList<>(); - private final List appenders = new ArrayList<>(); - - @Override - public void registerModelProvider(ExtraModelProvider appender) { - appenders.add(appender); - } - - @Override - public void registerAppender(ModelAppender appender) { - registerModelProvider((manager, consumer) -> appender.appendAll(manager, consumer::accept)); - } - - @Override - public void registerResourceProvider(Function providerSupplier) { - resourceProviderSuppliers.add(providerSupplier); - } - - @Override - public void registerVariantProvider(Function providerSupplier) { - variantProviderSuppliers.add(providerSupplier); - } - - public static LoaderInstance begin(ModelLoader loader, ResourceManager manager) { - return new LoaderInstance((ModelLoadingRegistryImpl) INSTANCE, loader, manager); - } -} diff --git a/fabric-models-v0/src/client/java/net/fabricmc/fabric/mixin/client/model/ModelLoaderMixin.java b/fabric-models-v0/src/client/java/net/fabricmc/fabric/mixin/client/model/ModelLoaderMixin.java deleted file mode 100644 index b16480a415..0000000000 --- a/fabric-models-v0/src/client/java/net/fabricmc/fabric/mixin/client/model/ModelLoaderMixin.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * 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 net.fabricmc.fabric.mixin.client.model; - -import java.util.Map; -import java.util.Set; - -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.client.MinecraftClient; -import net.minecraft.client.render.model.ModelLoader; -import net.minecraft.client.render.model.UnbakedModel; -import net.minecraft.client.util.ModelIdentifier; -import net.minecraft.resource.ResourceManager; -import net.minecraft.util.Identifier; - -import net.fabricmc.fabric.impl.client.model.ModelLoaderHooks; -import net.fabricmc.fabric.impl.client.model.ModelLoadingRegistryImpl; - -@Mixin(ModelLoader.class) -public abstract class ModelLoaderMixin implements ModelLoaderHooks { - // this is the first one - @Final - @Shadow - public static ModelIdentifier MISSING_ID; - @Final - @Shadow - private Set modelsToLoad; - @Final - @Shadow - private Map unbakedModels; - @Shadow - @Final - private Map modelsToBake; - - private ModelLoadingRegistryImpl.LoaderInstance fabric_mlrLoaderInstance; - - @Shadow - private void addModel(ModelIdentifier id) { - } - - @Shadow - private void putModel(Identifier id, UnbakedModel unbakedModel) { - } - - @Shadow - private void loadModel(Identifier id) { - } - - @Shadow - public abstract UnbakedModel getOrLoadModel(Identifier id); - - @Inject(at = @At("HEAD"), method = "loadModel", cancellable = true) - private void loadModelHook(Identifier id, CallbackInfo ci) { - UnbakedModel customModel = fabric_mlrLoaderInstance.loadModelFromVariant(id); - - if (customModel != null) { - putModel(id, customModel); - ci.cancel(); - } - } - - @Inject(at = @At("HEAD"), method = "addModel") - private void addModelHook(ModelIdentifier id, CallbackInfo info) { - if (id == MISSING_ID) { - //noinspection RedundantCast - ModelLoaderHooks hooks = this; - - ResourceManager resourceManager = MinecraftClient.getInstance().getResourceManager(); - fabric_mlrLoaderInstance = ModelLoadingRegistryImpl.begin((ModelLoader) (Object) this, resourceManager); - fabric_mlrLoaderInstance.onModelPopulation(hooks::fabric_addModel); - } - } - - @Inject(at = @At("RETURN"), method = "") - private void initFinishedHook(CallbackInfo info) { - //noinspection ConstantConditions - fabric_mlrLoaderInstance.finish(); - } - - @Override - public void fabric_addModel(Identifier id) { - if (id instanceof ModelIdentifier) { - addModel((ModelIdentifier) id); - } else { - // The vanilla addModel method is arbitrarily limited to ModelIdentifiers, - // but it's useful to tell the game to just load and bake a direct model path as well. - // Replicate the vanilla logic of addModel here. - UnbakedModel unbakedModel = getOrLoadModel(id); - this.unbakedModels.put(id, unbakedModel); - this.modelsToBake.put(id, unbakedModel); - } - } - - @Override - public UnbakedModel fabric_loadModel(Identifier id) { - if (!modelsToLoad.add(id)) { - throw new IllegalStateException("Circular reference while loading " + id); - } - - loadModel(id); - modelsToLoad.remove(id); - return unbakedModels.get(id); - } -} diff --git a/fabric-models-v0/src/client/resources/fabric-models-v0.mixins.json b/fabric-models-v0/src/client/resources/fabric-models-v0.mixins.json deleted file mode 100644 index 4da50ed3d0..0000000000 --- a/fabric-models-v0/src/client/resources/fabric-models-v0.mixins.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "required": true, - "package": "net.fabricmc.fabric.mixin.client.model", - "compatibilityLevel": "JAVA_16", - "client": [ - "BakedModelManagerMixin", - "ModelLoaderMixin" - ], - "injectors": { - "defaultRequire": 1 - } -} diff --git a/fabric-models-v0/src/testmodClient/java/net/fabricmc/fabric/test/model/BakedModelRenderer.java b/fabric-models-v0/src/testmodClient/java/net/fabricmc/fabric/test/model/BakedModelRenderer.java deleted file mode 100644 index bf3328591b..0000000000 --- a/fabric-models-v0/src/testmodClient/java/net/fabricmc/fabric/test/model/BakedModelRenderer.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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 net.fabricmc.fabric.test.model; - -import org.apache.commons.lang3.ArrayUtils; - -import net.minecraft.client.render.OverlayTexture; -import net.minecraft.client.render.VertexConsumer; -import net.minecraft.client.render.model.BakedModel; -import net.minecraft.client.render.model.BakedQuad; -import net.minecraft.client.util.math.MatrixStack; -import net.minecraft.util.math.Direction; -import net.minecraft.util.math.random.Random; - -public class BakedModelRenderer { - private static final Direction[] CULL_FACES = ArrayUtils.add(Direction.values(), null); - private static final Random RANDOM = Random.create(); - - public static void renderBakedModel(BakedModel model, VertexConsumer vertices, MatrixStack.Entry entry, int light) { - for (Direction cullFace : CULL_FACES) { - RANDOM.setSeed(42L); - - for (BakedQuad quad : model.getQuads(null, cullFace, RANDOM)) { - vertices.quad(entry, quad, 1.0F, 1.0F, 1.0F, light, OverlayTexture.DEFAULT_UV); - } - } - } -} diff --git a/fabric-models-v0/src/testmodClient/java/net/fabricmc/fabric/test/model/ModelTestModClient.java b/fabric-models-v0/src/testmodClient/java/net/fabricmc/fabric/test/model/ModelTestModClient.java deleted file mode 100644 index 2f54acddf6..0000000000 --- a/fabric-models-v0/src/testmodClient/java/net/fabricmc/fabric/test/model/ModelTestModClient.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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 net.fabricmc.fabric.test.model; - -import net.minecraft.client.render.entity.PlayerEntityRenderer; -import net.minecraft.resource.ResourceType; -import net.minecraft.util.Identifier; - -import net.fabricmc.api.ClientModInitializer; -import net.fabricmc.fabric.api.client.model.ModelLoadingRegistry; -import net.fabricmc.fabric.api.client.rendering.v1.LivingEntityFeatureRendererRegistrationCallback; -import net.fabricmc.fabric.api.resource.ResourceManagerHelper; - -public class ModelTestModClient implements ClientModInitializer { - public static final String ID = "fabric-models-v0-testmod"; - - public static final Identifier MODEL_ID = new Identifier(ID, "half_red_sand"); - - @Override - public void onInitializeClient() { - ModelLoadingRegistry.INSTANCE.registerModelProvider((manager, out) -> { - out.accept(MODEL_ID); - }); - - ResourceManagerHelper.get(ResourceType.CLIENT_RESOURCES).registerReloadListener(SpecificModelReloadListener.INSTANCE); - - LivingEntityFeatureRendererRegistrationCallback.EVENT.register((entityType, entityRenderer, registrationHelper, context) -> { - if (entityRenderer instanceof PlayerEntityRenderer) { - registrationHelper.register(new BakedModelFeatureRenderer<>((PlayerEntityRenderer) entityRenderer, SpecificModelReloadListener.INSTANCE::getSpecificModel)); - } - }); - } -} diff --git a/fabric-models-v0/src/testmodClient/resources/fabric.mod.json b/fabric-models-v0/src/testmodClient/resources/fabric.mod.json deleted file mode 100644 index 83599a1f77..0000000000 --- a/fabric-models-v0/src/testmodClient/resources/fabric.mod.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "schemaVersion": 1, - "id": "fabric-models-v0-testmod", - "name": "Fabric Models (v0) Test Mod", - "version": "1.0.0", - "environment": "client", - "license": "Apache-2.0", - "depends": { - "fabric-models-v0": "*", - "fabric-resource-loader-v0": "*" - }, - "entrypoints": { - "client": [ - "net.fabricmc.fabric.test.model.ModelTestModClient" - ] - } -} diff --git a/fabric-networking-api-v1/build.gradle b/fabric-networking-api-v1/build.gradle index d622d32e46..a66181f704 100644 --- a/fabric-networking-api-v1/build.gradle +++ b/fabric-networking-api-v1/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-networking-api-v1" version = getSubprojectVersion(project) moduleDependencies(project, ['fabric-api-base']) @@ -8,3 +7,7 @@ testDependencies(project, [ ':fabric-lifecycle-events-v1', ':fabric-key-binding-api-v1' ]) + +loom { + accessWidenerPath = file('src/main/resources/fabric-networking-api-v1.accesswidener') +} diff --git a/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/api/client/networking/v1/C2SConfigurationChannelEvents.java b/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/api/client/networking/v1/C2SConfigurationChannelEvents.java new file mode 100644 index 0000000000..0c56453e0c --- /dev/null +++ b/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/api/client/networking/v1/C2SConfigurationChannelEvents.java @@ -0,0 +1,71 @@ +/* + * 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 net.fabricmc.fabric.api.client.networking.v1; + +import java.util.List; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientConfigurationNetworkHandler; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; +import net.fabricmc.fabric.api.networking.v1.PacketSender; + +/** + * Offers access to events related to the indication of a connected server's ability to receive packets in certain channels. + */ +public final class C2SConfigurationChannelEvents { + /** + * An event for the client configuration network handler receiving an update indicating the connected server's ability to receive packets in certain channels. + * This event may be invoked at any time after login and up to disconnection. + */ + public static final Event REGISTER = EventFactory.createArrayBacked(Register.class, callbacks -> (handler, sender, client, channels) -> { + for (Register callback : callbacks) { + callback.onChannelRegister(handler, sender, client, channels); + } + }); + + /** + * An event for the client configuration network handler receiving an update indicating the connected server's lack of ability to receive packets in certain channels. + * This event may be invoked at any time after login and up to disconnection. + */ + public static final Event UNREGISTER = EventFactory.createArrayBacked(Unregister.class, callbacks -> (handler, sender, client, channels) -> { + for (Unregister callback : callbacks) { + callback.onChannelUnregister(handler, sender, client, channels); + } + }); + + private C2SConfigurationChannelEvents() { + } + + /** + * @see C2SConfigurationChannelEvents#REGISTER + */ + @FunctionalInterface + public interface Register { + void onChannelRegister(ClientConfigurationNetworkHandler handler, PacketSender sender, MinecraftClient client, List channels); + } + + /** + * @see C2SConfigurationChannelEvents#UNREGISTER + */ + @FunctionalInterface + public interface Unregister { + void onChannelUnregister(ClientConfigurationNetworkHandler handler, PacketSender sender, MinecraftClient client, List channels); + } +} diff --git a/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/api/client/networking/v1/ClientConfigurationConnectionEvents.java b/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/api/client/networking/v1/ClientConfigurationConnectionEvents.java new file mode 100644 index 0000000000..42eb7abc10 --- /dev/null +++ b/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/api/client/networking/v1/ClientConfigurationConnectionEvents.java @@ -0,0 +1,80 @@ +/* + * 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 net.fabricmc.fabric.api.client.networking.v1; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientConfigurationNetworkHandler; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; + +/** + * Offers access to events related to the configuration connection to a server on a logical client. + */ +public final class ClientConfigurationConnectionEvents { + /** + * Event indicating a connection entering the CONFIGURATION state, ready for registering channel handlers. + * + * @see ClientConfigurationNetworking#registerReceiver(Identifier, ClientConfigurationNetworking.ConfigurationChannelHandler) + */ + public static final Event INIT = EventFactory.createArrayBacked(ClientConfigurationConnectionEvents.Init.class, callbacks -> (handler, client) -> { + for (ClientConfigurationConnectionEvents.Init callback : callbacks) { + callback.onConfigurationInit(handler, client); + } + }); + + /** + * An event called after the ReadyS2CPacket has been received, just before switching to the PLAY state. + * + *

    No packets should be sent when this event is invoked. + */ + public static final Event READY = EventFactory.createArrayBacked(ClientConfigurationConnectionEvents.Ready.class, callbacks -> (handler, client) -> { + for (ClientConfigurationConnectionEvents.Ready callback : callbacks) { + callback.onConfigurationReady(handler, client); + } + }); + + /** + * An event for the disconnection of the client configuration network handler. + * + *

    No packets should be sent when this event is invoked. + */ + public static final Event DISCONNECT = EventFactory.createArrayBacked(ClientConfigurationConnectionEvents.Disconnect.class, callbacks -> (handler, client) -> { + for (ClientConfigurationConnectionEvents.Disconnect callback : callbacks) { + callback.onConfigurationDisconnect(handler, client); + } + }); + + private ClientConfigurationConnectionEvents() { + } + + @FunctionalInterface + public interface Init { + void onConfigurationInit(ClientConfigurationNetworkHandler handler, MinecraftClient client); + } + + @FunctionalInterface + public interface Ready { + void onConfigurationReady(ClientConfigurationNetworkHandler handler, MinecraftClient client); + } + + @FunctionalInterface + public interface Disconnect { + void onConfigurationDisconnect(ClientConfigurationNetworkHandler handler, MinecraftClient client); + } +} diff --git a/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/api/client/networking/v1/ClientConfigurationNetworking.java b/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/api/client/networking/v1/ClientConfigurationNetworking.java new file mode 100644 index 0000000000..455771367e --- /dev/null +++ b/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/api/client/networking/v1/ClientConfigurationNetworking.java @@ -0,0 +1,457 @@ +/* + * 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 net.fabricmc.fabric.api.client.networking.v1; + +import java.util.Objects; +import java.util.Set; + +import org.jetbrains.annotations.Nullable; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientConfigurationNetworkHandler; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.network.listener.ServerCommonPacketListener; +import net.minecraft.network.packet.Packet; +import net.minecraft.util.Identifier; +import net.minecraft.util.thread.ThreadExecutor; + +import net.fabricmc.fabric.api.networking.v1.FabricPacket; +import net.fabricmc.fabric.api.networking.v1.PacketSender; +import net.fabricmc.fabric.api.networking.v1.PacketType; +import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking; +import net.fabricmc.fabric.impl.networking.client.ClientConfigurationNetworkAddon; +import net.fabricmc.fabric.impl.networking.client.ClientNetworkingImpl; +import net.fabricmc.fabric.mixin.networking.client.accessor.ClientCommonNetworkHandlerAccessor; + +/** + * Offers access to configuration stage client-side networking functionalities. + * + *

    Client-side networking functionalities include receiving clientbound packets, + * sending serverbound packets, and events related to client-side network handlers. + * + *

    This class should be only used on the physical client and for the logical client. + * + *

    See {@link net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking} for information on how to use the packet + * object-based API. + * + * @see ServerConfigurationNetworking + */ +public final class ClientConfigurationNetworking { + /** + * Registers a handler to a channel. + * A global receiver is registered to all connections, in the present and future. + * + *

    The handler runs on the network thread. After reading the buffer there, access to game state + * must be performed in the render thread by calling {@link ThreadExecutor#execute(Runnable)}. + * + *

    If a handler is already registered to the {@code channel}, this method will return {@code false}, and no change will be made. + * Use {@link #unregisterGlobalReceiver(Identifier)} to unregister the existing handler. + * + *

    For new code, {@link #registerGlobalReceiver(PacketType, ConfigurationPacketHandler)} + * is preferred, as it is designed in a way that prevents thread safety issues. + * + * @param channelName the id of the channel + * @param channelHandler the handler + * @return false if a handler is already registered to the channel + * @see ClientConfigurationNetworking#unregisterGlobalReceiver(Identifier) + * @see ClientConfigurationNetworking#registerReceiver(Identifier, ConfigurationChannelHandler) + */ + public static boolean registerGlobalReceiver(Identifier channelName, ConfigurationChannelHandler channelHandler) { + return ClientNetworkingImpl.CONFIGURATION.registerGlobalReceiver(channelName, channelHandler); + } + + /** + * Registers a handler for a packet type. + * A global receiver is registered to all connections, in the present and future. + * + *

    If a handler is already registered for the {@code type}, this method will return {@code false}, and no change will be made. + * Use {@link #unregisterGlobalReceiver(PacketType)} to unregister the existing handler. + * + * @param type the packet type + * @param handler the handler + * @return false if a handler is already registered to the channel + * @see ClientConfigurationNetworking#unregisterGlobalReceiver(PacketType) + * @see ClientConfigurationNetworking#registerReceiver(PacketType, ConfigurationPacketHandler) + */ + public static boolean registerGlobalReceiver(PacketType type, ConfigurationPacketHandler handler) { + return registerGlobalReceiver(type.getId(), new ConfigurationChannelHandlerProxy() { + @Override + public ConfigurationPacketHandler getOriginalHandler() { + return handler; + } + + @Override + public void receive(MinecraftClient client, ClientConfigurationNetworkHandler networkHandler, PacketByteBuf buf, PacketSender sender) { + T packet = type.read(buf); + + if (client.isOnThread()) { + // Do not submit to the render thread if we're already running there. + // Normally, packets are handled on the network IO thread - though it is + // not guaranteed (for example, with 1.19.4 S2C packet bundling) + // Since we're handling it right now, connection check is redundant. + handler.receive(packet, sender); + } else { + client.execute(() -> { + if (((ClientCommonNetworkHandlerAccessor) networkHandler).getConnection().isOpen()) handler.receive(packet, sender); + }); + } + } + }); + } + + /** + * Removes the handler of a channel. + * A global receiver is registered to all connections, in the present and future. + * + *

    The {@code channel} is guaranteed not to have a handler after this call. + * + * @param channelName the id of the channel + * @return the previous handler, or {@code null} if no handler was bound to the channel + * @see ClientConfigurationNetworking#registerGlobalReceiver(Identifier, ConfigurationChannelHandler) + * @see ClientConfigurationNetworking#unregisterReceiver(Identifier) + */ + @Nullable + public static ClientConfigurationNetworking.ConfigurationChannelHandler unregisterGlobalReceiver(Identifier channelName) { + return ClientNetworkingImpl.CONFIGURATION.unregisterGlobalReceiver(channelName); + } + + /** + * Removes the handler for a packet type. + * A global receiver is registered to all connections, in the present and future. + * + *

    The {@code type} is guaranteed not to have an associated handler after this call. + * + * @param type the packet type + * @return the previous handler, or {@code null} if no handler was bound to the channel, + * or it was not registered using {@link #registerGlobalReceiver(PacketType, ConfigurationPacketHandler)} + * @see ClientConfigurationNetworking#registerGlobalReceiver(PacketType, ConfigurationPacketHandler) + * @see ClientConfigurationNetworking#unregisterReceiver(PacketType) + */ + @Nullable + @SuppressWarnings("unchecked") + public static ClientConfigurationNetworking.ConfigurationPacketHandler unregisterGlobalReceiver(PacketType type) { + ConfigurationChannelHandler handler = ClientNetworkingImpl.CONFIGURATION.unregisterGlobalReceiver(type.getId()); + return handler instanceof ConfigurationChannelHandlerProxy proxy ? (ConfigurationPacketHandler) proxy.getOriginalHandler() : null; + } + + /** + * Gets all channel names which global receivers are registered for. + * A global receiver is registered to all connections, in the present and future. + * + * @return all channel names which global receivers are registered for. + */ + public static Set getGlobalReceivers() { + return ClientNetworkingImpl.CONFIGURATION.getChannels(); + } + + /** + * Registers a handler to a channel. + * + *

    If a handler is already registered to the {@code channel}, this method will return {@code false}, and no change will be made. + * Use {@link #unregisterReceiver(Identifier)} to unregister the existing handler. + * + *

    For example, if you only register a receiver using this method when a {@linkplain ClientLoginNetworking#registerGlobalReceiver(Identifier, ClientLoginNetworking.LoginQueryRequestHandler)} + * login query has been received, you should use {@link ClientPlayConnectionEvents#INIT} to register the channel handler. + * + *

    For new code, {@link #registerReceiver(PacketType, ConfigurationPacketHandler)} + * is preferred, as it is designed in a way that prevents thread safety issues. + * + * @param channelName the id of the channel + * @return false if a handler is already registered to the channel + * @throws IllegalStateException if the client is not connected to a server + * @see ClientPlayConnectionEvents#INIT + */ + public static boolean registerReceiver(Identifier channelName, ConfigurationChannelHandler channelHandler) { + final ClientConfigurationNetworkAddon addon = ClientNetworkingImpl.getClientConfigurationAddon(); + + if (addon != null) { + return addon.registerChannel(channelName, channelHandler); + } + + throw new IllegalStateException("Cannot register receiver while not configuring!"); + } + + /** + * Registers a handler for a packet type. + * + *

    If a handler is already registered for the {@code type}, this method will return {@code false}, and no change will be made. + * Use {@link #unregisterReceiver(PacketType)} to unregister the existing handler. + * + *

    For example, if you only register a receiver using this method when a {@linkplain ClientLoginNetworking#registerGlobalReceiver(Identifier, ClientLoginNetworking.LoginQueryRequestHandler)} + * login query has been received, you should use {@link ClientPlayConnectionEvents#INIT} to register the channel handler. + * + * @param type the packet type + * @param handler the handler + * @return {@code false} if a handler is already registered for the type + * @throws IllegalStateException if the client is not connected to a server + * @see ClientPlayConnectionEvents#INIT + */ + public static boolean registerReceiver(PacketType type, ConfigurationPacketHandler handler) { + return registerReceiver(type.getId(), new ConfigurationChannelHandlerProxy() { + @Override + public ConfigurationPacketHandler getOriginalHandler() { + return handler; + } + + @Override + public void receive(MinecraftClient client, ClientConfigurationNetworkHandler networkHandler, PacketByteBuf buf, PacketSender sender) { + T packet = type.read(buf); + + if (client.isOnThread()) { + // Do not submit to the render thread if we're already running there. + // Normally, packets are handled on the network IO thread - though it is + // not guaranteed (for example, with 1.19.4 S2C packet bundling) + // Since we're handling it right now, connection check is redundant. + handler.receive(packet, sender); + } else { + client.execute(() -> { + if (((ClientCommonNetworkHandlerAccessor) networkHandler).getConnection().isOpen()) handler.receive(packet, sender); + }); + } + } + }); + } + + /** + * Removes the handler of a channel. + * + *

    The {@code channelName} is guaranteed not to have a handler after this call. + * + * @param channelName the id of the channel + * @return the previous handler, or {@code null} if no handler was bound to the channel + * @throws IllegalStateException if the client is not connected to a server + */ + @Nullable + public static ClientConfigurationNetworking.ConfigurationChannelHandler unregisterReceiver(Identifier channelName) throws IllegalStateException { + final ClientConfigurationNetworkAddon addon = ClientNetworkingImpl.getClientConfigurationAddon(); + + if (addon != null) { + return addon.unregisterChannel(channelName); + } + + throw new IllegalStateException("Cannot unregister receiver while not configuring!"); + } + + /** + * Removes the handler for a packet type. + * + *

    The {@code type} is guaranteed not to have an associated handler after this call. + * + * @param type the packet type + * @return the previous handler, or {@code null} if no handler was bound to the channel, + * or it was not registered using {@link #registerReceiver(PacketType, ConfigurationPacketHandler)} + * @throws IllegalStateException if the client is not connected to a server + */ + @Nullable + @SuppressWarnings("unchecked") + public static ClientConfigurationNetworking.ConfigurationPacketHandler unregisterReceiver(PacketType type) { + ConfigurationChannelHandler handler = unregisterReceiver(type.getId()); + return handler instanceof ConfigurationChannelHandlerProxy proxy ? (ConfigurationPacketHandler) proxy.getOriginalHandler() : null; + } + + /** + * Gets all the channel names that the client can receive packets on. + * + * @return All the channel names that the client can receive packets on + * @throws IllegalStateException if the client is not connected to a server + */ + public static Set getReceived() throws IllegalStateException { + final ClientConfigurationNetworkAddon addon = ClientNetworkingImpl.getClientConfigurationAddon(); + + if (addon != null) { + return addon.getReceivableChannels(); + } + + throw new IllegalStateException("Cannot get a list of channels the client can receive packets on while not configuring!"); + } + + /** + * Gets all channel names that the connected server declared the ability to receive a packets on. + * + * @return All the channel names the connected server declared the ability to receive a packets on + * @throws IllegalStateException if the client is not connected to a server + */ + public static Set getSendable() throws IllegalStateException { + final ClientConfigurationNetworkAddon addon = ClientNetworkingImpl.getClientConfigurationAddon(); + + if (addon != null) { + return addon.getSendableChannels(); + } + + throw new IllegalStateException("Cannot get a list of channels the server can receive packets on while not configuring!"); + } + + /** + * Checks if the connected server declared the ability to receive a packet on a specified channel name. + * + * @param channelName the channel name + * @return {@code true} if the connected server has declared the ability to receive a packet on the specified channel. + * False if the client is not in game. + */ + public static boolean canSend(Identifier channelName) throws IllegalArgumentException { + final ClientConfigurationNetworkAddon addon = ClientNetworkingImpl.getClientConfigurationAddon(); + + if (addon != null) { + return addon.getSendableChannels().contains(channelName); + } + + throw new IllegalStateException("Cannot get a list of channels the server can receive packets on while not configuring!"); + } + + /** + * Checks if the connected server declared the ability to receive a packet on a specified channel name. + * This returns {@code false} if the client is not in game. + * + * @param type the packet type + * @return {@code true} if the connected server has declared the ability to receive a packet on the specified channel + */ + public static boolean canSend(PacketType type) { + return canSend(type.getId()); + } + + /** + * Creates a packet which may be sent to the connected server. + * + * @param channelName the channel name + * @param buf the packet byte buf which represents the payload of the packet + * @return a new packet + */ + public static Packet createC2SPacket(Identifier channelName, PacketByteBuf buf) { + Objects.requireNonNull(channelName, "Channel name cannot be null"); + Objects.requireNonNull(buf, "Buf cannot be null"); + + return ClientNetworkingImpl.createC2SPacket(channelName, buf); + } + + /** + * Gets the packet sender which sends packets to the connected server. + * + * @return the client's packet sender + * @throws IllegalStateException if the client is not connected to a server + */ + public static PacketSender getSender() throws IllegalStateException { + final ClientConfigurationNetworkAddon addon = ClientNetworkingImpl.getClientConfigurationAddon(); + + if (addon != null) { + return addon; + } + + throw new IllegalStateException("Cannot get PacketSender while not configuring!"); + } + + /** + * Sends a packet to the connected server. + * + * @param channelName the channel of the packet + * @param buf the payload of the packet + * @throws IllegalStateException if the client is not connected to a server + */ + public static void send(Identifier channelName, PacketByteBuf buf) throws IllegalStateException { + final ClientConfigurationNetworkAddon addon = ClientNetworkingImpl.getClientConfigurationAddon(); + + if (addon != null) { + addon.sendPacket(createC2SPacket(channelName, buf)); + return; + } + + throw new IllegalStateException("Cannot send packet while not configuring!"); + } + + /** + * Sends a packet to the connected server. + * + * @param packet the packet + * @throws IllegalStateException if the client is not connected to a server + */ + public static void send(T packet) { + Objects.requireNonNull(packet, "Packet cannot be null"); + Objects.requireNonNull(packet.getType(), "Packet#getType cannot return null"); + + final ClientConfigurationNetworkAddon addon = ClientNetworkingImpl.getClientConfigurationAddon(); + + if (addon != null) { + addon.sendPacket(packet); + return; + } + + throw new IllegalStateException("Cannot send packet while not configuring!"); + } + + private ClientConfigurationNetworking() { + } + + @FunctionalInterface + public interface ConfigurationChannelHandler { + /** + * Handles an incoming packet. + * + *

    This method is executed on {@linkplain io.netty.channel.EventLoop netty's event loops}. + * Modification to the game should be {@linkplain net.minecraft.util.thread.ThreadExecutor#submit(Runnable) scheduled} using the provided Minecraft client instance. + * + *

    An example usage of this is to display an overlay message: + *

    {@code
    +		 * ClientConfigurationNetworking.registerReceiver(new Identifier("mymod", "overlay"), (client, handler, buf, responseSender) -> {
    +		 * 	String message = buf.readString(32767);
    +		 *
    +		 * 	// All operations on the server or world must be executed on the server thread
    +		 * 	client.execute(() -> {
    +		 * 		client.inGameHud.setOverlayMessage(message, true);
    +		 * 	});
    +		 * });
    +		 * }
    + * @param client the client + * @param handler the network handler that received this packet + * @param buf the payload of the packet + * @param responseSender the packet sender + */ + void receive(MinecraftClient client, ClientConfigurationNetworkHandler handler, PacketByteBuf buf, PacketSender responseSender); + } + + /** + * An internal packet handler that works as a proxy between old and new API. + * @param the type of the packet + */ + private interface ConfigurationChannelHandlerProxy extends ConfigurationChannelHandler { + ConfigurationPacketHandler getOriginalHandler(); + } + + /** + * A thread-safe packet handler utilizing {@link FabricPacket}. + * @param the type of the packet + */ + @FunctionalInterface + public interface ConfigurationPacketHandler { + /** + * Handles the incoming packet. This is called on the render thread, and can safely + * call client methods. + * + *

    An example usage of this is to display an overlay message: + *

    {@code
    +		 * // See FabricPacket for creating the packet
    +		 * ClientConfigurationNetworking.registerReceiver(OVERLAY_PACKET_TYPE, (player, packet, responseSender) -> {
    +		 * 	MinecraftClient.getInstance().inGameHud.setOverlayMessage(packet.message(), true);
    +		 * });
    +		 * }
    + * + * + * @param packet the packet + * @param responseSender the packet sender + * @see FabricPacket + */ + void receive(T packet, PacketSender responseSender); + } +} diff --git a/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/api/client/networking/v1/ClientPlayNetworking.java b/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/api/client/networking/v1/ClientPlayNetworking.java index c95700577a..e0493d1255 100644 --- a/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/api/client/networking/v1/ClientPlayNetworking.java +++ b/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/api/client/networking/v1/ClientPlayNetworking.java @@ -25,13 +25,12 @@ import net.minecraft.client.network.ClientPlayNetworkHandler; import net.minecraft.client.network.ClientPlayerEntity; import net.minecraft.network.PacketByteBuf; -import net.minecraft.network.listener.ServerPlayPacketListener; +import net.minecraft.network.listener.ServerCommonPacketListener; import net.minecraft.network.packet.Packet; import net.minecraft.util.Identifier; import net.minecraft.util.thread.ThreadExecutor; import net.fabricmc.fabric.api.networking.v1.FabricPacket; -import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; import net.fabricmc.fabric.api.networking.v1.PacketSender; import net.fabricmc.fabric.api.networking.v1.PacketType; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; @@ -50,6 +49,7 @@ * object-based API. * * @see ClientLoginNetworking + * @see ClientConfigurationNetworking * @see ServerPlayNetworking */ public final class ClientPlayNetworking { @@ -331,11 +331,21 @@ public static boolean canSend(PacketType type) { * @param buf the packet byte buf which represents the payload of the packet * @return a new packet */ - public static Packet createC2SPacket(Identifier channelName, PacketByteBuf buf) { + public static Packet createC2SPacket(Identifier channelName, PacketByteBuf buf) { Objects.requireNonNull(channelName, "Channel name cannot be null"); Objects.requireNonNull(buf, "Buf cannot be null"); - return ClientNetworkingImpl.createPlayC2SPacket(channelName, buf); + return ClientNetworkingImpl.createC2SPacket(channelName, buf); + } + + /** + * Creates a packet which may be sent to the connected server. + * + * @param packet the fabric packet + * @return a new packet + */ + public static Packet createC2SPacket(T packet) { + return ClientNetworkingImpl.createC2SPacket(packet); } /** @@ -380,9 +390,13 @@ public static void send(T packet) { Objects.requireNonNull(packet, "Packet cannot be null"); Objects.requireNonNull(packet.getType(), "Packet#getType cannot return null"); - PacketByteBuf buf = PacketByteBufs.create(); - packet.write(buf); - send(packet.getType().getId(), buf); + // You cant send without a client player, so this is fine + if (MinecraftClient.getInstance().getNetworkHandler() != null) { + MinecraftClient.getInstance().getNetworkHandler().sendPacket(createC2SPacket(packet)); + return; + } + + throw new IllegalStateException("Cannot send packets when not in game!"); } private ClientPlayNetworking() { diff --git a/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/api/client/networking/v1/package-info.java b/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/api/client/networking/v1/package-info.java index 0c4b8cf4e7..db0f27caf4 100644 --- a/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/api/client/networking/v1/package-info.java +++ b/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/api/client/networking/v1/package-info.java @@ -17,13 +17,31 @@ /** * The Networking API (client side), version 1. * - *

    For login stage networking see {@link net.fabricmc.fabric.api.client.networking.v1.ClientLoginNetworking}. - * For play stage networking see {@link net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking}. - * - *

    For events related to connection to a server see {@link net.fabricmc.fabric.api.client.networking.v1.ClientLoginConnectionEvents} for login stage - * or {@link net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents} for play stage. - * - *

    For events related to the ability of a server to receive packets on a channel of a specific name see {@link net.fabricmc.fabric.api.client.networking.v1.C2SPlayChannelEvents}. + *

    There are three stages of Minecraft networking, all of which are supported in this API: + *

    + *
    LOGIN
    + *
    This is the initial stage, before the player logs into the world. If using a proxy server, + * the packets in this stage may be intercepted and discarded by the proxy. Most of the pre-1.20.2 + * uses of this event should be replaced with the CONFIGURATION stage. + * Related events are found at {@link net.fabricmc.fabric.api.client.networking.v1.ClientLoginConnectionEvents}, + * and related methods are found at {@link net.fabricmc.fabric.api.client.networking.v1.ClientLoginNetworking}. + *
    + *
    CONFIGURATION
    + *
    This is the stage after LOGIN. The player is authenticated, but still hasn't joined the + * world at this point. Clients can use this phase to send configurations or verify their mod + * versions. Note that some server mods allow players in the PLAY stage to re-enter this stage, + * for example when a player chooses a minigame server in a lobby. + * Related events are found at {@link net.fabricmc.fabric.api.client.networking.v1.C2SConfigurationChannelEvents} + * and {@link net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationConnectionEvents}, and related methods are found at + * {@link net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationNetworking}. + *
    + *
    PLAY
    + *
    This is the stage after CONFIGURATION, where gameplay-related packets are sent and received. + * The player has joined the world and is playing the game. Related events are found at + * {@link net.fabricmc.fabric.api.client.networking.v1.C2SPlayChannelEvents} + * and {@link net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents}, and related methods are found at + * {@link net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking}.
    + *
    */ package net.fabricmc.fabric.api.client.networking.v1; diff --git a/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/impl/networking/client/ClientConfigurationNetworkAddon.java b/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/impl/networking/client/ClientConfigurationNetworkAddon.java new file mode 100644 index 0000000000..9499e22e73 --- /dev/null +++ b/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/impl/networking/client/ClientConfigurationNetworkAddon.java @@ -0,0 +1,167 @@ +/* + * 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 net.fabricmc.fabric.impl.networking.client; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientConfigurationNetworkHandler; +import net.minecraft.network.NetworkState; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.network.packet.Packet; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.api.client.networking.v1.C2SConfigurationChannelEvents; +import net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationConnectionEvents; +import net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationNetworking; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.fabricmc.fabric.api.networking.v1.FabricPacket; +import net.fabricmc.fabric.impl.networking.AbstractChanneledNetworkAddon; +import net.fabricmc.fabric.impl.networking.ChannelInfoHolder; +import net.fabricmc.fabric.impl.networking.NetworkingImpl; +import net.fabricmc.fabric.impl.networking.payload.PacketByteBufPayload; +import net.fabricmc.fabric.mixin.networking.client.accessor.ClientCommonNetworkHandlerAccessor; +import net.fabricmc.fabric.mixin.networking.client.accessor.ClientConfigurationNetworkHandlerAccessor; + +public final class ClientConfigurationNetworkAddon extends AbstractChanneledNetworkAddon { + private final ClientConfigurationNetworkHandler handler; + private final MinecraftClient client; + private boolean sentInitialRegisterPacket; + + public ClientConfigurationNetworkAddon(ClientConfigurationNetworkHandler handler, MinecraftClient client) { + super(ClientNetworkingImpl.CONFIGURATION, ((ClientCommonNetworkHandlerAccessor) handler).getConnection(), "ClientPlayNetworkAddon for " + ((ClientConfigurationNetworkHandlerAccessor) handler).getProfile().getName()); + this.handler = handler; + this.client = client; + + // Must register pending channels via lateinit + this.registerPendingChannels((ChannelInfoHolder) this.connection, NetworkState.CONFIGURATION); + + // Register global receivers and attach to session + this.receiver.startSession(this); + } + + @Override + public void lateInit() { + for (Map.Entry entry : this.receiver.getHandlers().entrySet()) { + this.registerChannel(entry.getKey(), entry.getValue()); + } + + ClientConfigurationConnectionEvents.INIT.invoker().onConfigurationInit(this.handler, this.client); + } + + public void onServerReady() { + // Do nothing for now + } + + @Override + protected void receiveRegistration(boolean register, PacketByteBuf buf) { + super.receiveRegistration(register, buf); + + if (register && !this.sentInitialRegisterPacket) { + this.sendInitialChannelRegistrationPacket(); + this.sentInitialRegisterPacket = true; + } + } + + /** + * Handles an incoming packet. + * + * @param payload the payload to handle + * @return true if the packet has been handled + */ + public boolean handle(PacketByteBufPayload payload) { + return this.handle(payload.id(), payload.data()); + } + + @Override + protected void receive(ClientConfigurationNetworking.ConfigurationChannelHandler handler, PacketByteBuf buf) { + handler.receive(this.client, this.handler, buf, this); + } + + // impl details + + @Override + protected void schedule(Runnable task) { + MinecraftClient.getInstance().execute(task); + } + + @Override + public Packet createPacket(Identifier channelName, PacketByteBuf buf) { + return ClientPlayNetworking.createC2SPacket(channelName, buf); + } + + @Override + public Packet createPacket(FabricPacket packet) { + return ClientPlayNetworking.createC2SPacket(packet); + } + + @Override + protected void invokeRegisterEvent(List ids) { + C2SConfigurationChannelEvents.REGISTER.invoker().onChannelRegister(this.handler, this, this.client, ids); + } + + @Override + protected void invokeUnregisterEvent(List ids) { + C2SConfigurationChannelEvents.UNREGISTER.invoker().onChannelUnregister(this.handler, this, this.client, ids); + } + + @Override + protected void handleRegistration(Identifier channelName) { + // If we can already send packets, immediately send the register packet for this channel + if (this.sentInitialRegisterPacket) { + final PacketByteBuf buf = this.createRegistrationPacket(Collections.singleton(channelName)); + + if (buf != null) { + this.sendPacket(NetworkingImpl.REGISTER_CHANNEL, buf); + } + } + } + + @Override + protected void handleUnregistration(Identifier channelName) { + // If we can already send packets, immediately send the unregister packet for this channel + if (this.sentInitialRegisterPacket) { + final PacketByteBuf buf = this.createRegistrationPacket(Collections.singleton(channelName)); + + if (buf != null) { + this.sendPacket(NetworkingImpl.UNREGISTER_CHANNEL, buf); + } + } + } + + public void handleReady() { + ClientConfigurationConnectionEvents.READY.invoker().onConfigurationReady(this.handler, this.client); + ClientNetworkingImpl.setClientConfigurationAddon(null); + } + + @Override + protected void invokeDisconnectEvent() { + ClientConfigurationConnectionEvents.DISCONNECT.invoker().onConfigurationDisconnect(this.handler, this.client); + this.receiver.endSession(this); + } + + @Override + protected boolean isReservedChannel(Identifier channelName) { + return NetworkingImpl.isReservedCommonChannel(channelName); + } + + public ChannelInfoHolder getChannelInfoHolder() { + return (ChannelInfoHolder) ((ClientCommonNetworkHandlerAccessor) handler).getConnection(); + } +} diff --git a/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/impl/networking/client/ClientLoginNetworkAddon.java b/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/impl/networking/client/ClientLoginNetworkAddon.java index bcf64337ca..a8706d5899 100644 --- a/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/impl/networking/client/ClientLoginNetworkAddon.java +++ b/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/impl/networking/client/ClientLoginNetworkAddon.java @@ -38,6 +38,8 @@ import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; import net.fabricmc.fabric.impl.networking.AbstractNetworkAddon; import net.fabricmc.fabric.impl.networking.GenericFutureListenerHolder; +import net.fabricmc.fabric.impl.networking.payload.PacketByteBufLoginQueryRequestPayload; +import net.fabricmc.fabric.impl.networking.payload.PacketByteBufLoginQueryResponse; import net.fabricmc.fabric.mixin.networking.client.accessor.ClientLoginNetworkHandlerAccessor; public final class ClientLoginNetworkAddon extends AbstractNetworkAddon { @@ -55,7 +57,8 @@ public ClientLoginNetworkAddon(ClientLoginNetworkHandler handler, MinecraftClien } public boolean handlePacket(LoginQueryRequestS2CPacket packet) { - return handlePacket(packet.getQueryId(), packet.getChannel(), packet.getPayload()); + PacketByteBufLoginQueryRequestPayload payload = (PacketByteBufLoginQueryRequestPayload) packet.payload(); + return handlePacket(packet.queryId(), packet.payload().id(), payload.data()); } private boolean handlePacket(int queryId, Identifier channelName, PacketByteBuf originalBuf) { @@ -83,7 +86,7 @@ private boolean handlePacket(int queryId, Identifier channelName, PacketByteBuf try { CompletableFuture<@Nullable PacketByteBuf> future = handler.receive(this.client, this.handler, buf, futureListeners::add); future.thenAccept(result -> { - LoginQueryResponseC2SPacket packet = new LoginQueryResponseC2SPacket(queryId, result); + LoginQueryResponseC2SPacket packet = new LoginQueryResponseC2SPacket(queryId, new PacketByteBufLoginQueryResponse(result)); GenericFutureListener> listener = null; for (GenericFutureListener> each : futureListeners) { @@ -114,7 +117,7 @@ protected void invokeDisconnectEvent() { this.receiver.endSession(this); } - public void handlePlayTransition() { + public void handleConfigurationTransition() { this.receiver.endSession(this); } diff --git a/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/impl/networking/client/ClientNetworkingImpl.java b/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/impl/networking/client/ClientNetworkingImpl.java index 50e6f83930..61c5a2accd 100644 --- a/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/impl/networking/client/ClientNetworkingImpl.java +++ b/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/impl/networking/client/ClientNetworkingImpl.java @@ -16,51 +16,71 @@ package net.fabricmc.fabric.impl.networking.client; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.concurrent.CompletableFuture; +import java.util.Objects; import org.jetbrains.annotations.Nullable; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.screen.ConnectScreen; +import net.minecraft.client.network.ClientConfigurationNetworkHandler; import net.minecraft.client.network.ClientLoginNetworkHandler; import net.minecraft.client.network.ClientPlayNetworkHandler; import net.minecraft.network.ClientConnection; -import net.minecraft.network.packet.Packet; +import net.minecraft.network.NetworkState; import net.minecraft.network.PacketByteBuf; -import net.minecraft.network.listener.ServerPlayPacketListener; -import net.minecraft.network.packet.c2s.play.CustomPayloadC2SPacket; +import net.minecraft.network.listener.ServerCommonPacketListener; +import net.minecraft.network.packet.Packet; +import net.minecraft.network.packet.c2s.common.CustomPayloadC2SPacket; import net.minecraft.util.Identifier; +import net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationConnectionEvents; +import net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationNetworking; import net.fabricmc.fabric.api.client.networking.v1.ClientLoginNetworking; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.fabricmc.fabric.api.networking.v1.FabricPacket; import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; -import net.fabricmc.fabric.impl.networking.ChannelInfoHolder; +import net.fabricmc.fabric.api.networking.v1.PacketSender; +import net.fabricmc.fabric.impl.networking.CommonPacketsImpl; +import net.fabricmc.fabric.impl.networking.CommonRegisterPayload; +import net.fabricmc.fabric.impl.networking.CommonVersionPayload; import net.fabricmc.fabric.impl.networking.GlobalReceiverRegistry; import net.fabricmc.fabric.impl.networking.NetworkHandlerExtensions; import net.fabricmc.fabric.impl.networking.NetworkingImpl; -import net.fabricmc.fabric.mixin.networking.client.accessor.ClientLoginNetworkHandlerAccessor; +import net.fabricmc.fabric.impl.networking.payload.PacketByteBufPayload; import net.fabricmc.fabric.mixin.networking.client.accessor.ConnectScreenAccessor; import net.fabricmc.fabric.mixin.networking.client.accessor.MinecraftClientAccessor; public final class ClientNetworkingImpl { - public static final GlobalReceiverRegistry LOGIN = new GlobalReceiverRegistry<>(); - public static final GlobalReceiverRegistry PLAY = new GlobalReceiverRegistry<>(); + public static final GlobalReceiverRegistry LOGIN = new GlobalReceiverRegistry<>(NetworkState.LOGIN); + public static final GlobalReceiverRegistry CONFIGURATION = new GlobalReceiverRegistry<>(NetworkState.CONFIGURATION); + public static final GlobalReceiverRegistry PLAY = new GlobalReceiverRegistry<>(NetworkState.PLAY); private static ClientPlayNetworkAddon currentPlayAddon; + private static ClientConfigurationNetworkAddon currentConfigurationAddon; public static ClientPlayNetworkAddon getAddon(ClientPlayNetworkHandler handler) { return (ClientPlayNetworkAddon) ((NetworkHandlerExtensions) handler).getAddon(); } + public static ClientConfigurationNetworkAddon getAddon(ClientConfigurationNetworkHandler handler) { + return (ClientConfigurationNetworkAddon) ((NetworkHandlerExtensions) handler).getAddon(); + } + public static ClientLoginNetworkAddon getAddon(ClientLoginNetworkHandler handler) { return (ClientLoginNetworkAddon) ((NetworkHandlerExtensions) handler).getAddon(); } - public static Packet createPlayC2SPacket(Identifier channelName, PacketByteBuf buf) { - return new CustomPayloadC2SPacket(channelName, buf); + public static Packet createC2SPacket(Identifier channelName, PacketByteBuf buf) { + return new CustomPayloadC2SPacket(new PacketByteBufPayload(channelName, buf)); + } + + public static Packet createC2SPacket(FabricPacket packet) { + Objects.requireNonNull(packet, "Packet cannot be null"); + Objects.requireNonNull(packet.getType(), "Packet#getType cannot return null"); + + PacketByteBuf buf = PacketByteBufs.create(); + packet.write(buf); + return createC2SPacket(packet.getType().getId(), buf); } /** @@ -85,6 +105,11 @@ public static ClientConnection getLoginConnection() { return null; } + @Nullable + public static ClientConfigurationNetworkAddon getClientConfigurationAddon() { + return currentConfigurationAddon; + } + @Nullable public static ClientPlayNetworkAddon getClientPlayAddon() { // Since Minecraft can be a bit weird, we need to check for the play addon in a few ways: @@ -104,38 +129,63 @@ public static ClientPlayNetworkAddon getClientPlayAddon() { } public static void setClientPlayAddon(ClientPlayNetworkAddon addon) { + assert addon == null || currentConfigurationAddon == null; currentPlayAddon = addon; } + public static void setClientConfigurationAddon(ClientConfigurationNetworkAddon addon) { + assert addon == null || currentPlayAddon == null; + currentConfigurationAddon = addon; + } + public static void clientInit() { // Reference cleanup for the locally stored addon if we are disconnected ClientPlayConnectionEvents.DISCONNECT.register((handler, client) -> { currentPlayAddon = null; }); - // Register a login query handler for early channel registration. - ClientLoginNetworking.registerGlobalReceiver(NetworkingImpl.EARLY_REGISTRATION_CHANNEL, (client, handler, buf, listenerAdder) -> { - int n = buf.readVarInt(); - List ids = new ArrayList<>(n); + ClientConfigurationConnectionEvents.DISCONNECT.register((handler, client) -> { + currentConfigurationAddon = null; + }); - for (int i = 0; i < n; i++) { - ids.add(buf.readIdentifier()); - } + // Version packet + ClientConfigurationNetworking.registerGlobalReceiver(CommonVersionPayload.PACKET_ID, (client, handler, buf, responseSender) -> { + var payload = new CommonVersionPayload(buf); + int negotiatedVersion = handleVersionPacket(payload, responseSender); + ClientNetworkingImpl.getAddon(handler).onCommonVersionPacket(negotiatedVersion); + }); - ClientConnection connection = ((ClientLoginNetworkHandlerAccessor) handler).getConnection(); - ((ChannelInfoHolder) connection).getPendingChannelsNames().addAll(ids); - NetworkingImpl.LOGGER.debug("Received accepted channels from the server"); + // Register packet + ClientConfigurationNetworking.registerGlobalReceiver(CommonRegisterPayload.PACKET_ID, (client, handler, buf, responseSender) -> { + var payload = new CommonRegisterPayload(buf); + ClientConfigurationNetworkAddon addon = ClientNetworkingImpl.getAddon(handler); + + if (CommonRegisterPayload.PLAY_PHASE.equals(payload.phase())) { + if (payload.version() != addon.getNegotiatedVersion()) { + throw new IllegalStateException("Negotiated common packet version: %d but received packet with version: %d".formatted(addon.getNegotiatedVersion(), payload.version())); + } + + addon.getChannelInfoHolder().getPendingChannelsNames(NetworkState.PLAY).addAll(payload.channels()); + NetworkingImpl.LOGGER.debug("Received accepted channels from the server"); + responseSender.sendPacket(new CommonRegisterPayload(addon.getNegotiatedVersion(), CommonRegisterPayload.PLAY_PHASE, ClientPlayNetworking.getGlobalReceivers())); + } else { + addon.onCommonRegisterPacket(payload); + responseSender.sendPacket(addon.createRegisterPayload()); + } + }); + } - PacketByteBuf response = PacketByteBufs.create(); - Collection channels = ClientPlayNetworking.getGlobalReceivers(); - response.writeVarInt(channels.size()); + // Disconnect if there are no commonly supported versions. + // Client responds with the intersection of supported versions. + // Return the highest supported version + private static int handleVersionPacket(CommonVersionPayload payload, PacketSender packetSender) { + int version = CommonPacketsImpl.getHighestCommonVersion(payload.versions(), CommonPacketsImpl.SUPPORTED_COMMON_PACKET_VERSIONS); - for (Identifier id : channels) { - response.writeIdentifier(id); - } + if (version <= 0) { + throw new UnsupportedOperationException("Client does not support any requested versions from server"); + } - NetworkingImpl.LOGGER.debug("Sent accepted channels to the server"); - return CompletableFuture.completedFuture(response); - }); + packetSender.sendPacket(new CommonVersionPayload(new int[]{ version })); + return version; } } diff --git a/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/impl/networking/client/ClientPlayNetworkAddon.java b/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/impl/networking/client/ClientPlayNetworkAddon.java index 26e3d0211a..d10f39925a 100644 --- a/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/impl/networking/client/ClientPlayNetworkAddon.java +++ b/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/impl/networking/client/ClientPlayNetworkAddon.java @@ -25,17 +25,19 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.ClientPlayNetworkHandler; -import net.minecraft.network.packet.Packet; +import net.minecraft.network.NetworkState; import net.minecraft.network.PacketByteBuf; -import net.minecraft.network.packet.s2c.play.CustomPayloadS2CPacket; +import net.minecraft.network.packet.Packet; import net.minecraft.util.Identifier; import net.fabricmc.fabric.api.client.networking.v1.C2SPlayChannelEvents; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.fabricmc.fabric.api.networking.v1.FabricPacket; import net.fabricmc.fabric.impl.networking.AbstractChanneledNetworkAddon; import net.fabricmc.fabric.impl.networking.ChannelInfoHolder; import net.fabricmc.fabric.impl.networking.NetworkingImpl; +import net.fabricmc.fabric.impl.networking.payload.PacketByteBufPayload; public final class ClientPlayNetworkAddon extends AbstractChanneledNetworkAddon { private final ClientPlayNetworkHandler handler; @@ -50,7 +52,7 @@ public ClientPlayNetworkAddon(ClientPlayNetworkHandler handler, MinecraftClient this.client = client; // Must register pending channels via lateinit - this.registerPendingChannels((ChannelInfoHolder) this.connection); + this.registerPendingChannels((ChannelInfoHolder) this.connection, NetworkState.PLAY); // Register global receivers and attach to session this.receiver.startSession(this); @@ -80,17 +82,11 @@ public void onServerReady() { /** * Handles an incoming packet. * - * @param packet the packet to handle + * @param payload the payload to handle * @return true if the packet has been handled */ - public boolean handle(CustomPayloadS2CPacket packet) { - PacketByteBuf buf = packet.getData(); - - try { - return this.handle(packet.getChannel(), buf); - } finally { - buf.release(); - } + public boolean handle(PacketByteBufPayload payload) { + return this.handle(payload.id(), payload.data()); } @Override @@ -110,6 +106,11 @@ public Packet createPacket(Identifier channelName, PacketByteBuf buf) { return ClientPlayNetworking.createC2SPacket(channelName, buf); } + @Override + public Packet createPacket(FabricPacket packet) { + return ClientPlayNetworking.createC2SPacket(packet); + } + @Override protected void invokeRegisterEvent(List ids) { C2SPlayChannelEvents.REGISTER.invoker().onChannelRegister(this.handler, this, this.client, ids); @@ -152,6 +153,6 @@ protected void invokeDisconnectEvent() { @Override protected boolean isReservedChannel(Identifier channelName) { - return NetworkingImpl.isReservedPlayChannel(channelName); + return NetworkingImpl.isReservedCommonChannel(channelName); } } diff --git a/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/mixin/networking/client/ClientCommonNetworkHandlerMixin.java b/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/mixin/networking/client/ClientCommonNetworkHandlerMixin.java new file mode 100644 index 0000000000..5fc2c7d150 --- /dev/null +++ b/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/mixin/networking/client/ClientCommonNetworkHandlerMixin.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 net.fabricmc.fabric.mixin.networking.client; + +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 net.minecraft.client.network.ClientCommonNetworkHandler; +import net.minecraft.network.packet.s2c.common.CustomPayloadS2CPacket; +import net.minecraft.text.Text; + +import net.fabricmc.fabric.impl.networking.NetworkHandlerExtensions; +import net.fabricmc.fabric.impl.networking.client.ClientConfigurationNetworkAddon; +import net.fabricmc.fabric.impl.networking.client.ClientPlayNetworkAddon; +import net.fabricmc.fabric.impl.networking.payload.PacketByteBufPayload; + +@Mixin(ClientCommonNetworkHandler.class) +public abstract class ClientCommonNetworkHandlerMixin implements NetworkHandlerExtensions { + @Inject(method = "onDisconnected", at = @At("HEAD")) + private void handleDisconnection(Text reason, CallbackInfo ci) { + this.getAddon().handleDisconnect(); + } + + @Inject(method = "onCustomPayload(Lnet/minecraft/network/packet/s2c/common/CustomPayloadS2CPacket;)V", at = @At("HEAD"), cancellable = true) + public void onCustomPayload(CustomPayloadS2CPacket packet, CallbackInfo ci) { + if (packet.payload() instanceof PacketByteBufPayload payload) { + boolean handled; + + if (this.getAddon() instanceof ClientPlayNetworkAddon addon) { + handled = addon.handle(payload); + } else if (this.getAddon() instanceof ClientConfigurationNetworkAddon addon) { + handled = addon.handle(payload); + } else { + throw new IllegalStateException("Unknown network addon"); + } + + if (handled) { + ci.cancel(); + } else { + payload.data().skipBytes(payload.data().readableBytes()); + } + } + } +} diff --git a/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/mixin/networking/client/ClientConfigurationNetworkHandlerMixin.java b/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/mixin/networking/client/ClientConfigurationNetworkHandlerMixin.java new file mode 100644 index 0000000000..fc43545098 --- /dev/null +++ b/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/mixin/networking/client/ClientConfigurationNetworkHandlerMixin.java @@ -0,0 +1,63 @@ +/* + * 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 net.fabricmc.fabric.mixin.networking.client; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientCommonNetworkHandler; +import net.minecraft.client.network.ClientConfigurationNetworkHandler; +import net.minecraft.client.network.ClientConnectionState; +import net.minecraft.network.ClientConnection; +import net.minecraft.network.packet.s2c.config.ReadyS2CPacket; + +import net.fabricmc.fabric.impl.networking.NetworkHandlerExtensions; +import net.fabricmc.fabric.impl.networking.client.ClientConfigurationNetworkAddon; +import net.fabricmc.fabric.impl.networking.client.ClientNetworkingImpl; + +// We want to apply a bit earlier than other mods which may not use us in order to prevent refCount issues +@Mixin(value = ClientConfigurationNetworkHandler.class, priority = 999) +public abstract class ClientConfigurationNetworkHandlerMixin extends ClientCommonNetworkHandler implements NetworkHandlerExtensions { + @Unique + private ClientConfigurationNetworkAddon addon; + + protected ClientConfigurationNetworkHandlerMixin(MinecraftClient client, ClientConnection connection, ClientConnectionState connectionState) { + super(client, connection, connectionState); + } + + @Inject(method = "", at = @At("RETURN")) + private void initAddon(CallbackInfo ci) { + this.addon = new ClientConfigurationNetworkAddon((ClientConfigurationNetworkHandler) (Object) this, this.client); + // A bit of a hack but it allows the field above to be set in case someone registers handlers during INIT event which refers to said field + ClientNetworkingImpl.setClientConfigurationAddon(this.addon); + this.addon.lateInit(); + } + + @Inject(method = "onReady", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/ClientConnection;setPacketListener(Lnet/minecraft/network/listener/PacketListener;)V", shift = At.Shift.BEFORE)) + public void onReady(ReadyS2CPacket packet, CallbackInfo ci) { + this.addon.handleReady(); + } + + @Override + public ClientConfigurationNetworkAddon getAddon() { + return addon; + } +} diff --git a/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/mixin/networking/client/ClientLoginNetworkHandlerMixin.java b/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/mixin/networking/client/ClientLoginNetworkHandlerMixin.java index fed93e0a79..bc447008e4 100644 --- a/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/mixin/networking/client/ClientLoginNetworkHandlerMixin.java +++ b/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/mixin/networking/client/ClientLoginNetworkHandlerMixin.java @@ -26,11 +26,14 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.ClientLoginNetworkHandler; +import net.minecraft.network.ClientConnection; import net.minecraft.network.packet.s2c.login.LoginQueryRequestS2CPacket; import net.minecraft.text.Text; import net.fabricmc.fabric.impl.networking.NetworkHandlerExtensions; +import net.fabricmc.fabric.impl.networking.client.ClientConfigurationNetworkAddon; import net.fabricmc.fabric.impl.networking.client.ClientLoginNetworkAddon; +import net.fabricmc.fabric.impl.networking.payload.PacketByteBufLoginQueryRequestPayload; @Mixin(ClientLoginNetworkHandler.class) abstract class ClientLoginNetworkHandlerMixin implements NetworkHandlerExtensions { @@ -38,6 +41,10 @@ abstract class ClientLoginNetworkHandlerMixin implements NetworkHandlerExtension @Final private MinecraftClient client; + @Shadow + @Final + private ClientConnection connection; + @Unique private ClientLoginNetworkAddon addon; @@ -48,8 +55,12 @@ private void initAddon(CallbackInfo ci) { @Inject(method = "onQueryRequest", at = @At(value = "INVOKE", target = "Ljava/util/function/Consumer;accept(Ljava/lang/Object;)V", remap = false, shift = At.Shift.AFTER), cancellable = true) private void handleQueryRequest(LoginQueryRequestS2CPacket packet, CallbackInfo ci) { - if (this.addon.handlePacket(packet)) { - ci.cancel(); + if (packet.payload() instanceof PacketByteBufLoginQueryRequestPayload payload) { + if (this.addon.handlePacket(packet)) { + ci.cancel(); + } else { + payload.data().skipBytes(payload.data().readableBytes()); + } } } @@ -59,8 +70,14 @@ private void invokeLoginDisconnectEvent(Text reason, CallbackInfo ci) { } @Inject(method = "onSuccess", at = @At("HEAD")) - private void handlePlayTransition(CallbackInfo ci) { - addon.handlePlayTransition(); + private void handleConfigurationTransition(CallbackInfo ci) { + addon.handleConfigurationTransition(); + } + + @Inject(method = "onSuccess", at = @At("TAIL")) + private void handleConfigurationReady(CallbackInfo ci) { + NetworkHandlerExtensions networkHandlerExtensions = (NetworkHandlerExtensions) connection.getPacketListener(); + ((ClientConfigurationNetworkAddon) networkHandlerExtensions.getAddon()).onServerReady(); } @Override diff --git a/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/mixin/networking/client/ClientPlayNetworkHandlerMixin.java b/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/mixin/networking/client/ClientPlayNetworkHandlerMixin.java index 418b25acea..0fb8f69763 100644 --- a/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/mixin/networking/client/ClientPlayNetworkHandlerMixin.java +++ b/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/mixin/networking/client/ClientPlayNetworkHandlerMixin.java @@ -16,19 +16,18 @@ package net.fabricmc.fabric.mixin.networking.client; -import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientCommonNetworkHandler; +import net.minecraft.client.network.ClientConnectionState; import net.minecraft.client.network.ClientPlayNetworkHandler; -import net.minecraft.network.packet.s2c.play.CustomPayloadS2CPacket; +import net.minecraft.network.ClientConnection; import net.minecraft.network.packet.s2c.play.GameJoinS2CPacket; -import net.minecraft.text.Text; import net.fabricmc.fabric.impl.networking.NetworkHandlerExtensions; import net.fabricmc.fabric.impl.networking.client.ClientNetworkingImpl; @@ -36,14 +35,14 @@ // We want to apply a bit earlier than other mods which may not use us in order to prevent refCount issues @Mixin(value = ClientPlayNetworkHandler.class, priority = 999) -abstract class ClientPlayNetworkHandlerMixin implements NetworkHandlerExtensions { - @Final - @Shadow - private MinecraftClient client; - +abstract class ClientPlayNetworkHandlerMixin extends ClientCommonNetworkHandler implements NetworkHandlerExtensions { @Unique private ClientPlayNetworkAddon addon; + protected ClientPlayNetworkHandlerMixin(MinecraftClient client, ClientConnection connection, ClientConnectionState connectionState) { + super(client, connection, connectionState); + } + @Inject(method = "", at = @At("RETURN")) private void initAddon(CallbackInfo ci) { this.addon = new ClientPlayNetworkAddon((ClientPlayNetworkHandler) (Object) this, this.client); @@ -57,18 +56,6 @@ private void handleServerPlayReady(GameJoinS2CPacket packet, CallbackInfo ci) { this.addon.onServerReady(); } - @Inject(method = "onCustomPayload", at = @At("HEAD"), cancellable = true) - private void handleCustomPayload(CustomPayloadS2CPacket packet, CallbackInfo ci) { - if (this.addon.handle(packet)) { - ci.cancel(); - } - } - - @Inject(method = "onDisconnected", at = @At("HEAD")) - private void handleDisconnection(Text reason, CallbackInfo ci) { - this.addon.handleDisconnect(); - } - @Override public ClientPlayNetworkAddon getAddon() { return this.addon; diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/accessor/ServerPlayNetworkHandlerAccessor.java b/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/mixin/networking/client/accessor/ClientCommonNetworkHandlerAccessor.java similarity index 78% rename from fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/accessor/ServerPlayNetworkHandlerAccessor.java rename to fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/mixin/networking/client/accessor/ClientCommonNetworkHandlerAccessor.java index 9ff3de7a8e..7400e6bb99 100644 --- a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/accessor/ServerPlayNetworkHandlerAccessor.java +++ b/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/mixin/networking/client/accessor/ClientCommonNetworkHandlerAccessor.java @@ -14,16 +14,16 @@ * limitations under the License. */ -package net.fabricmc.fabric.mixin.networking.accessor; +package net.fabricmc.fabric.mixin.networking.client.accessor; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Accessor; +import net.minecraft.client.network.ClientCommonNetworkHandler; import net.minecraft.network.ClientConnection; -import net.minecraft.server.network.ServerPlayNetworkHandler; -@Mixin(ServerPlayNetworkHandler.class) -public interface ServerPlayNetworkHandlerAccessor { +@Mixin(ClientCommonNetworkHandler.class) +public interface ClientCommonNetworkHandlerAccessor { @Accessor ClientConnection getConnection(); } diff --git a/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/mixin/networking/client/accessor/ClientConfigurationNetworkHandlerAccessor.java b/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/mixin/networking/client/accessor/ClientConfigurationNetworkHandlerAccessor.java new file mode 100644 index 0000000000..23bb8539db --- /dev/null +++ b/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/mixin/networking/client/accessor/ClientConfigurationNetworkHandlerAccessor.java @@ -0,0 +1,29 @@ +/* + * 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 net.fabricmc.fabric.mixin.networking.client.accessor; + +import com.mojang.authlib.GameProfile; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import net.minecraft.client.network.ClientConfigurationNetworkHandler; + +@Mixin(ClientConfigurationNetworkHandler.class) +public interface ClientConfigurationNetworkHandlerAccessor { + @Accessor + GameProfile getProfile(); +} diff --git a/fabric-networking-api-v1/src/client/resources/fabric-networking-api-v1.client.mixins.json b/fabric-networking-api-v1/src/client/resources/fabric-networking-api-v1.client.mixins.json index 2d4b321abc..1eaecde87b 100644 --- a/fabric-networking-api-v1/src/client/resources/fabric-networking-api-v1.client.mixins.json +++ b/fabric-networking-api-v1/src/client/resources/fabric-networking-api-v1.client.mixins.json @@ -3,9 +3,13 @@ "package": "net.fabricmc.fabric.mixin.networking.client", "compatibilityLevel": "JAVA_16", "client": [ + "accessor.ClientCommonNetworkHandlerAccessor", + "accessor.ClientConfigurationNetworkHandlerAccessor", "accessor.ClientLoginNetworkHandlerAccessor", "accessor.ConnectScreenAccessor", "accessor.MinecraftClientAccessor", + "ClientCommonNetworkHandlerMixin", + "ClientConfigurationNetworkHandlerMixin", "ClientLoginNetworkHandlerMixin", "ClientPlayNetworkHandlerMixin" ], diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/FabricPacket.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/FabricPacket.java index 7d3ec0d7c7..a0387fcd96 100644 --- a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/FabricPacket.java +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/FabricPacket.java @@ -57,7 +57,7 @@ * * @see ServerPlayNetworking#registerGlobalReceiver(PacketType, ServerPlayNetworking.PlayPacketHandler) * @see ServerPlayNetworking#send(ServerPlayerEntity, PacketType, FabricPacket) - * @see PacketSender#sendPacket(PacketType, FabricPacket) + * @see PacketSender#sendPacket(FabricPacket) */ public interface FabricPacket { /** diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/FabricServerConfigurationNetworkHandler.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/FabricServerConfigurationNetworkHandler.java new file mode 100644 index 0000000000..69ee4ee6ca --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/FabricServerConfigurationNetworkHandler.java @@ -0,0 +1,53 @@ +/* + * 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 net.fabricmc.fabric.api.networking.v1; + +import net.minecraft.server.network.ServerConfigurationNetworkHandler; +import net.minecraft.server.network.ServerPlayerConfigurationTask; +import net.minecraft.util.Identifier; + +/** + * Fabric-provided extensions for {@link ServerConfigurationNetworkHandler}. + * This interface is automatically implemented via Mixin and interface injection. + */ +public interface FabricServerConfigurationNetworkHandler { + /** + * Enqueues a {@link ServerPlayerConfigurationTask} task to be processed. + * + *

    Before adding a task use {@link ServerConfigurationNetworking#canSend(ServerConfigurationNetworkHandler, Identifier)} + * to ensure that the client can process this task. + * + *

    Once the client has handled the task a packet should be sent to the server. + * Upon receiving this packet the server should call {@link FabricServerConfigurationNetworkHandler#completeTask(ServerPlayerConfigurationTask.Key)}, + * otherwise the client cannot join the world. + * + * @param task the task + */ + default void addTask(ServerPlayerConfigurationTask task) { + throw new UnsupportedOperationException("Implemented via mixin"); + } + + /** + * Completes the task identified by {@code key}. + * + * @param key the task key + * @throws IllegalStateException if the current task is not {@code key} + */ + default void completeTask(ServerPlayerConfigurationTask.Key key) { + throw new UnsupportedOperationException("Implemented via mixin"); + } +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/PacketSender.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/PacketSender.java index 32564afef2..29f22c9f93 100644 --- a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/PacketSender.java +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/PacketSender.java @@ -21,11 +21,13 @@ import io.netty.channel.ChannelFutureListener; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; -import net.minecraft.network.packet.Packet; import net.minecraft.network.PacketByteBuf; import net.minecraft.network.PacketCallbacks; +import net.minecraft.network.packet.CustomPayload; +import net.minecraft.network.packet.Packet; import net.minecraft.util.Identifier; import net.fabricmc.fabric.impl.networking.GenericFutureListenerHolder; @@ -34,6 +36,7 @@ * Represents something that supports sending packets to channels. * @see PacketByteBufs */ +@ApiStatus.NonExtendable public interface PacketSender { /** * Makes a packet for a channel. @@ -43,21 +46,38 @@ public interface PacketSender { */ Packet createPacket(Identifier channelName, PacketByteBuf buf); + /** + * Makes a packet for a fabric packet. + * + * @param packet the fabric packet + */ + Packet createPacket(FabricPacket packet); + /** * Sends a packet. * * @param packet the packet */ - void sendPacket(Packet packet); + default void sendPacket(Packet packet) { + sendPacket(packet, (PacketCallbacks) null); + } /** * Sends a packet. * @param packet the packet */ default void sendPacket(T packet) { + sendPacket(createPacket(packet)); + } + + /** + * Sends a packet. + * @param payload the payload + */ + default void sendPacket(CustomPayload payload) { PacketByteBuf buf = PacketByteBufs.create(); - packet.write(buf); - sendPacket(packet.getType().getId(), buf); + payload.write(buf); + sendPacket(payload.id(), buf); } /** @@ -75,9 +95,19 @@ default void sendPacket(T packet) { * @param callback an optional callback to execute after the packet is sent, may be {@code null}. The callback may also accept a {@link ChannelFutureListener}. */ default void sendPacket(T packet, @Nullable GenericFutureListener> callback) { + sendPacket(createPacket(packet), callback); + } + + /** + * Sends a packet. + * + * @param payload the payload + * @param callback an optional callback to execute after the packet is sent, may be {@code null}. The callback may also accept a {@link ChannelFutureListener}. + */ + default void sendPacket(CustomPayload payload, @Nullable GenericFutureListener> callback) { PacketByteBuf buf = PacketByteBufs.create(); - packet.write(buf); - sendPacket(packet.getType().getId(), buf, callback); + payload.write(buf); + sendPacket(payload.id(), buf, callback); } /** @@ -95,9 +125,19 @@ default void sendPacket(T packet, @Nullable GenericFutu * @param callback an optional callback to execute after the packet is sent, may be {@code null}. The callback may also accept a {@link ChannelFutureListener}. */ default void sendPacket(T packet, @Nullable PacketCallbacks callback) { + sendPacket(createPacket(packet), callback); + } + + /** + * Sends a packet. + * + * @param payload the payload + * @param callback an optional callback to execute after the packet is sent, may be {@code null}. The callback may also accept a {@link ChannelFutureListener}. + */ + default void sendPacket(CustomPayload payload, @Nullable PacketCallbacks callback) { PacketByteBuf buf = PacketByteBufs.create(); - packet.write(buf); - sendPacket(packet.getType().getId(), buf, callback); + payload.write(buf); + sendPacket(payload.id(), buf, callback); } /** diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/PlayerLookup.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/PlayerLookup.java index 54667dca81..5001033643 100644 --- a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/PlayerLookup.java +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/PlayerLookup.java @@ -25,7 +25,7 @@ import net.minecraft.entity.Entity; import net.minecraft.server.MinecraftServer; import net.minecraft.server.network.ServerPlayerEntity; -import net.minecraft.server.world.EntityTrackingListener; +import net.minecraft.server.network.PlayerAssociatedNetworkHandler; import net.minecraft.server.world.ServerChunkManager; import net.minecraft.server.world.ServerWorld; import net.minecraft.server.world.ThreadedAnvilChunkStorage; @@ -39,7 +39,7 @@ import net.fabricmc.fabric.mixin.networking.accessor.ThreadedAnvilChunkStorageAccessor; /** - * For example, a block entity may use the methods in this class to send a packet to all clients which can see the block entity in order notify clients about a change. + * Helper methods to lookup players in a server. * *

    The word "tracking" means that an entity/chunk on the server is known to a player's client (within in view distance) and the (block) entity should notify tracking clients of changes. * @@ -117,8 +117,8 @@ public static Collection tracking(Entity entity) { // return an immutable collection to guard against accidental removals. if (tracker != null) { - return Collections.unmodifiableCollection(tracker.getPlayersTracking() - .stream().map(EntityTrackingListener::getPlayer).collect(Collectors.toSet())); + return tracker.getPlayersTracking() + .stream().map(PlayerAssociatedNetworkHandler::getPlayer).collect(Collectors.toUnmodifiableSet()); } return Collections.emptySet(); diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/S2CConfigurationChannelEvents.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/S2CConfigurationChannelEvents.java new file mode 100644 index 0000000000..ba05d648ff --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/S2CConfigurationChannelEvents.java @@ -0,0 +1,70 @@ +/* + * 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 net.fabricmc.fabric.api.networking.v1; + +import java.util.List; + +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerConfigurationNetworkHandler; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; + +/** + * Offers access to events related to the indication of a connected client's ability to receive packets in certain channels. + */ +public final class S2CConfigurationChannelEvents { + /** + * An event for the server configuration network handler receiving an update indicating the connected client's ability to receive packets in certain channels. + * This event may be invoked at any time after login and up to disconnection. + */ + public static final Event REGISTER = EventFactory.createArrayBacked(Register.class, callbacks -> (handler, sender, server, channels) -> { + for (Register callback : callbacks) { + callback.onChannelRegister(handler, sender, server, channels); + } + }); + + /** + * An event for the server configuration network handler receiving an update indicating the connected client's lack of ability to receive packets in certain channels. + * This event may be invoked at any time after login and up to disconnection. + */ + public static final Event UNREGISTER = EventFactory.createArrayBacked(Unregister.class, callbacks -> (handler, sender, server, channels) -> { + for (Unregister callback : callbacks) { + callback.onChannelUnregister(handler, sender, server, channels); + } + }); + + private S2CConfigurationChannelEvents() { + } + + /** + * @see S2CConfigurationChannelEvents#REGISTER + */ + @FunctionalInterface + public interface Register { + void onChannelRegister(ServerConfigurationNetworkHandler handler, PacketSender sender, MinecraftServer server, List channels); + } + + /** + * @see S2CConfigurationChannelEvents#UNREGISTER + */ + @FunctionalInterface + public interface Unregister { + void onChannelUnregister(ServerConfigurationNetworkHandler handler, PacketSender sender, MinecraftServer server, List channels); + } +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/ServerConfigurationConnectionEvents.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/ServerConfigurationConnectionEvents.java new file mode 100644 index 0000000000..3a1452f99d --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/ServerConfigurationConnectionEvents.java @@ -0,0 +1,88 @@ +/* + * 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 net.fabricmc.fabric.api.networking.v1; + +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerConfigurationNetworkHandler; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; + +/** + * Offers access to events related to the connection to a client on a logical server while a client is configuring. + */ +public final class ServerConfigurationConnectionEvents { + /** + * Event fired before any vanilla configuration has taken place. + * + *

    This event is executed on {@linkplain io.netty.channel.EventLoop netty's event loops}. + * + *

    Task queued during this event will complete before vanilla configuration starts. + */ + public static final Event BEFORE_CONFIGURE = EventFactory.createArrayBacked(Configure.class, callbacks -> (handler, server) -> { + for (Configure callback : callbacks) { + callback.onSendConfiguration(handler, server); + } + }); + + /** + * Event fired during vanilla configuration. + * + *

    This event is executed on {@linkplain io.netty.channel.EventLoop netty's event loops}. + * + *

    An example usage of this: + *

    {@code
    +	 * ServerConfigurationConnectionEvents.CONFIGURE.register((handler, server) -> {
    +	 * 	if (ServerConfigurationNetworking.canSend(handler, ConfigurationPacket.PACKET_TYPE)) {
    +	 *  handler.addTask(new TestConfigurationTask("Example data"));
    +	 * 	} else {
    +	 * 	  // You can opt to disconnect the client if it cannot handle the configuration task
    +	 * 	  handler.disconnect(Text.literal("Network test configuration not supported by client"));
    +	 * 	  }
    +	 * });
    +	 * }
    + */ + public static final Event CONFIGURE = EventFactory.createArrayBacked(Configure.class, callbacks -> (handler, server) -> { + for (Configure callback : callbacks) { + callback.onSendConfiguration(handler, server); + } + }); + + /** + * An event for the disconnection of the server configuration network handler. + * + *

    No packets should be sent when this event is invoked. + */ + public static final Event DISCONNECT = EventFactory.createArrayBacked(ServerConfigurationConnectionEvents.Disconnect.class, callbacks -> (handler, server) -> { + for (ServerConfigurationConnectionEvents.Disconnect callback : callbacks) { + callback.onConfigureDisconnect(handler, server); + } + }); + + private ServerConfigurationConnectionEvents() { + } + + @FunctionalInterface + public interface Configure { + void onSendConfiguration(ServerConfigurationNetworkHandler handler, MinecraftServer server); + } + + @FunctionalInterface + public interface Disconnect { + void onConfigureDisconnect(ServerConfigurationNetworkHandler handler, MinecraftServer server); + } +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/ServerConfigurationNetworking.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/ServerConfigurationNetworking.java new file mode 100644 index 0000000000..2261d5817c --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/ServerConfigurationNetworking.java @@ -0,0 +1,438 @@ +/* + * 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 net.fabricmc.fabric.api.networking.v1; + +import java.util.Objects; +import java.util.Set; + +import org.jetbrains.annotations.Nullable; + +import net.minecraft.network.PacketByteBuf; +import net.minecraft.network.listener.ClientCommonPacketListener; +import net.minecraft.network.packet.Packet; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerConfigurationNetworkHandler; +import net.minecraft.util.Identifier; +import net.minecraft.util.thread.ThreadExecutor; + +import net.fabricmc.fabric.impl.networking.server.ServerNetworkingImpl; +import net.fabricmc.fabric.mixin.networking.accessor.ServerCommonNetworkHandlerAccessor; + +/** + * Offers access to configuration stage server-side networking functionalities. + * + *

    Server-side networking functionalities include receiving serverbound packets, sending clientbound packets, and events related to server-side network handlers. + * + *

    This class should be only used for the logical server. + * + *

    See {@link ServerPlayNetworking} for information on how to use the packet + * object-based API. + * + *

    See the documentation on each class for more information. + * + * @see ServerLoginNetworking + * @see ServerConfigurationNetworking + */ +public final class ServerConfigurationNetworking { + /** + * Registers a handler to a channel. + * A global receiver is registered to all connections, in the present and future. + * + *

    The handler runs on the network thread. After reading the buffer there, the server + * must be modified in the server thread by calling {@link ThreadExecutor#execute(Runnable)}. + * + *

    If a handler is already registered to the {@code channel}, this method will return {@code false}, and no change will be made. + * Use {@link #unregisterReceiver(ServerConfigurationNetworkHandler, Identifier)} to unregister the existing handler. + * + *

    For new code, {@link #registerGlobalReceiver(PacketType, ConfigurationPacketHandler)} + * is preferred, as it is designed in a way that prevents thread safety issues. + * + * @param channelName the id of the channel + * @param channelHandler the handler + * @return false if a handler is already registered to the channel + * @see ServerConfigurationNetworking#unregisterGlobalReceiver(Identifier) + * @see ServerConfigurationNetworking#registerReceiver(ServerConfigurationNetworkHandler, Identifier, ConfigurationChannelHandler) + */ + public static boolean registerGlobalReceiver(Identifier channelName, ConfigurationChannelHandler channelHandler) { + return ServerNetworkingImpl.CONFIGURATION.registerGlobalReceiver(channelName, channelHandler); + } + + /** + * Registers a handler for a packet type. + * A global receiver is registered to all connections, in the present and future. + * + *

    If a handler is already registered for the {@code type}, this method will return {@code false}, and no change will be made. + * Use {@link #unregisterReceiver(ServerConfigurationNetworkHandler, PacketType)} to unregister the existing handler. + * + * @param type the packet type + * @param handler the handler + * @return {@code false} if a handler is already registered to the channel + * @see ServerConfigurationNetworking#unregisterGlobalReceiver(PacketType) + * @see ServerConfigurationNetworking#registerReceiver(ServerConfigurationNetworkHandler, PacketType, ConfigurationPacketHandler) + */ + public static boolean registerGlobalReceiver(PacketType type, ConfigurationPacketHandler handler) { + return registerGlobalReceiver(type.getId(), new ConfigurationChannelHandlerProxy() { + @Override + public ConfigurationPacketHandler getOriginalHandler() { + return handler; + } + + @Override + public void receive(MinecraftServer server, ServerConfigurationNetworkHandler networkHandler, PacketByteBuf buf, PacketSender sender) { + T packet = type.read(buf); + handler.receive(packet, networkHandler, sender); + } + }); + } + + /** + * Removes the handler of a channel. + * A global receiver is registered to all connections, in the present and future. + * + *

    The {@code channel} is guaranteed not to have a handler after this call. + * + * @param channelName the id of the channel + * @return the previous handler, or {@code null} if no handler was bound to the channel + * @see ServerConfigurationNetworking#registerGlobalReceiver(Identifier, ConfigurationChannelHandler) + * @see ServerConfigurationNetworking#unregisterReceiver(ServerConfigurationNetworkHandler, Identifier) + */ + @Nullable + public static ServerConfigurationNetworking.ConfigurationChannelHandler unregisterGlobalReceiver(Identifier channelName) { + return ServerNetworkingImpl.CONFIGURATION.unregisterGlobalReceiver(channelName); + } + + /** + * Removes the handler for a packet type. + * A global receiver is registered to all connections, in the present and future. + * + *

    The {@code type} is guaranteed not to have an associated handler after this call. + * + * @param type the packet type + * @return the previous handler, or {@code null} if no handler was bound to the channel, + * or it was not registered using {@link #registerGlobalReceiver(PacketType, ConfigurationPacketHandler)} + * @see ServerConfigurationNetworking#registerGlobalReceiver(PacketType, ConfigurationPacketHandler) + * @see ServerConfigurationNetworking#unregisterReceiver(ServerConfigurationNetworkHandler, PacketType) + */ + @Nullable + @SuppressWarnings("unchecked") + public static ServerConfigurationNetworking.ConfigurationPacketHandler unregisterGlobalReceiver(PacketType type) { + ConfigurationChannelHandler handler = ServerNetworkingImpl.CONFIGURATION.unregisterGlobalReceiver(type.getId()); + return handler instanceof ConfigurationChannelHandlerProxy proxy ? (ConfigurationPacketHandler) proxy.getOriginalHandler() : null; + } + + /** + * Gets all channel names which global receivers are registered for. + * A global receiver is registered to all connections, in the present and future. + * + * @return all channel names which global receivers are registered for. + */ + public static Set getGlobalReceivers() { + return ServerNetworkingImpl.CONFIGURATION.getChannels(); + } + + /** + * Registers a handler to a channel. + * This method differs from {@link ServerConfigurationNetworking#registerGlobalReceiver(Identifier, ConfigurationChannelHandler)} since + * the channel handler will only be applied to the client represented by the {@link ServerConfigurationNetworkHandler}. + * + *

    The handler runs on the network thread. After reading the buffer there, the world + * must be modified in the server thread by calling {@link ThreadExecutor#execute(Runnable)}. + * + *

    For example, if you only register a receiver using this method when a {@linkplain ServerLoginNetworking#registerGlobalReceiver(Identifier, ServerLoginNetworking.LoginQueryResponseHandler)} + * login response has been received, you should use {@link ServerPlayConnectionEvents#INIT} to register the channel handler. + * + *

    If a handler is already registered to the {@code channelName}, this method will return {@code false}, and no change will be made. + * Use {@link #unregisterReceiver(ServerConfigurationNetworkHandler, Identifier)} to unregister the existing handler. + * + *

    For new code, {@link #registerReceiver(ServerConfigurationNetworkHandler, PacketType, ConfigurationPacketHandler)} + * is preferred, as it is designed in a way that prevents thread safety issues. + * + * @param networkHandler the handler + * @param channelName the id of the channel + * @param channelHandler the handler + * @return false if a handler is already registered to the channel name + * @see ServerPlayConnectionEvents#INIT + */ + public static boolean registerReceiver(ServerConfigurationNetworkHandler networkHandler, Identifier channelName, ConfigurationChannelHandler channelHandler) { + Objects.requireNonNull(networkHandler, "Network handler cannot be null"); + + return ServerNetworkingImpl.getAddon(networkHandler).registerChannel(channelName, channelHandler); + } + + /** + * Registers a handler for a packet type. + * This method differs from {@link ServerConfigurationNetworking#registerGlobalReceiver(PacketType, ConfigurationPacketHandler)} since + * the channel handler will only be applied to the client represented by the {@link ServerConfigurationNetworkHandler}. + * + *

    For example, if you only register a receiver using this method when a {@linkplain ServerLoginNetworking#registerGlobalReceiver(Identifier, ServerLoginNetworking.LoginQueryResponseHandler)} + * login response has been received, you should use {@link ServerPlayConnectionEvents#INIT} to register the channel handler. + * + *

    If a handler is already registered for the {@code type}, this method will return {@code false}, and no change will be made. + * Use {@link #unregisterReceiver(ServerConfigurationNetworkHandler, PacketType)} to unregister the existing handler. + * + * @param networkHandler the network handler + * @param type the packet type + * @param handler the handler + * @return {@code false} if a handler is already registered to the channel name + * @see ServerPlayConnectionEvents#INIT + */ + public static boolean registerReceiver(ServerConfigurationNetworkHandler networkHandler, PacketType type, ConfigurationPacketHandler handler) { + return registerReceiver(networkHandler, type.getId(), new ConfigurationChannelHandlerProxy() { + @Override + public ConfigurationPacketHandler getOriginalHandler() { + return handler; + } + + @Override + public void receive(MinecraftServer server, ServerConfigurationNetworkHandler networkHandler2, PacketByteBuf buf, PacketSender sender) { + T packet = type.read(buf); + handler.receive(packet, networkHandler2, sender); + } + }); + } + + /** + * Removes the handler of a channel. + * + *

    The {@code channelName} is guaranteed not to have a handler after this call. + * + * @param channelName the id of the channel + * @return the previous handler, or {@code null} if no handler was bound to the channel name + */ + @Nullable + public static ServerConfigurationNetworking.ConfigurationChannelHandler unregisterReceiver(ServerConfigurationNetworkHandler networkHandler, Identifier channelName) { + Objects.requireNonNull(networkHandler, "Network handler cannot be null"); + + return ServerNetworkingImpl.getAddon(networkHandler).unregisterChannel(channelName); + } + + /** + * Removes the handler for a packet type. + * + *

    The {@code type} is guaranteed not to have an associated handler after this call. + * + * @param type the type of the packet + * @return the previous handler, or {@code null} if no handler was bound to the channel, + * or it was not registered using {@link #registerReceiver(ServerConfigurationNetworkHandler, PacketType, ConfigurationPacketHandler)} + */ + @Nullable + @SuppressWarnings("unchecked") + public static ServerConfigurationNetworking.ConfigurationPacketHandler unregisterReceiver(ServerConfigurationNetworkHandler networkHandler, PacketType type) { + ConfigurationChannelHandler handler = unregisterReceiver(networkHandler, type.getId()); + return handler instanceof ConfigurationChannelHandlerProxy proxy ? (ConfigurationPacketHandler) proxy.getOriginalHandler() : null; + } + + /** + * Gets all the channel names that the server can receive packets on. + * + * @param handler the network handler + * @return All the channel names that the server can receive packets on + */ + public static Set getReceived(ServerConfigurationNetworkHandler handler) { + Objects.requireNonNull(handler, "Server configuration network handler cannot be null"); + + return ServerNetworkingImpl.getAddon(handler).getReceivableChannels(); + } + + /** + * Gets all channel names that a connected client declared the ability to receive a packets on. + * + * @param handler the network handler + * @return {@code true} if the connected client has declared the ability to receive a packet on the specified channel + */ + public static Set getSendable(ServerConfigurationNetworkHandler handler) { + Objects.requireNonNull(handler, "Server configuration network handler cannot be null"); + + return ServerNetworkingImpl.getAddon(handler).getSendableChannels(); + } + + /** + * Checks if the connected client declared the ability to receive a packet on a specified channel name. + * + * @param handler the network handler + * @param channelName the channel name + * @return {@code true} if the connected client has declared the ability to receive a packet on the specified channel + */ + public static boolean canSend(ServerConfigurationNetworkHandler handler, Identifier channelName) { + Objects.requireNonNull(handler, "Server configuration network handler cannot be null"); + Objects.requireNonNull(channelName, "Channel name cannot be null"); + + return ServerNetworkingImpl.getAddon(handler).getSendableChannels().contains(channelName); + } + + /** + * Checks if the connected client declared the ability to receive a specific type of packet. + * + * @param handler the network handler + * @param type the packet type + * @return {@code true} if the connected client has declared the ability to receive a specific type of packet + */ + public static boolean canSend(ServerConfigurationNetworkHandler handler, PacketType type) { + Objects.requireNonNull(handler, "Server configuration network handler cannot be null"); + Objects.requireNonNull(type, "Packet type cannot be null"); + + return ServerNetworkingImpl.getAddon(handler).getSendableChannels().contains(type.getId()); + } + + /** + * Creates a packet which may be sent to a connected client. + * + * @param channelName the channel name + * @param buf the packet byte buf which represents the payload of the packet + * @return a new packet + */ + public static Packet createS2CPacket(Identifier channelName, PacketByteBuf buf) { + Objects.requireNonNull(channelName, "Channel cannot be null"); + Objects.requireNonNull(buf, "Buf cannot be null"); + + return ServerNetworkingImpl.createS2CPacket(channelName, buf); + } + + /** + * Creates a packet which may be sent to a connected client. + * + * @param packet the fabric packet + * @return a new packet + */ + public static Packet createS2CPacket(T packet) { + Objects.requireNonNull(packet, "Packet cannot be null"); + Objects.requireNonNull(packet.getType(), "Packet#getType cannot return null"); + + return ServerNetworkingImpl.createS2CPacket(packet); + } + + /** + * Gets the packet sender which sends packets to the connected client. + * + * @param handler the network handler, representing the connection to the player/client + * @return the packet sender + */ + public static PacketSender getSender(ServerConfigurationNetworkHandler handler) { + Objects.requireNonNull(handler, "Server configuration network handler cannot be null"); + + return ServerNetworkingImpl.getAddon(handler); + } + + /** + * Sends a packet to a configuring player. + * + * @param handler the handler to send the packet to + * @param channelName the channel of the packet + * @param buf the payload of the packet. + */ + public static void send(ServerConfigurationNetworkHandler handler, Identifier channelName, PacketByteBuf buf) { + Objects.requireNonNull(handler, "Server configuration entity cannot be null"); + Objects.requireNonNull(channelName, "Channel name cannot be null"); + Objects.requireNonNull(buf, "Packet byte buf cannot be null"); + + handler.sendPacket(createS2CPacket(channelName, buf)); + } + + /** + * Sends a packet to a configuring player. + * + * @param handler the network handler to send the packet to + * @param packet the packet + */ + public static void send(ServerConfigurationNetworkHandler handler, T packet) { + Objects.requireNonNull(handler, "Server configuration handler cannot be null"); + Objects.requireNonNull(packet, "Packet cannot be null"); + Objects.requireNonNull(packet.getType(), "Packet#getType cannot return null"); + + handler.sendPacket(createS2CPacket(packet)); + } + + // Helper methods + + /** + * Returns the Minecraft Server of a server configuration network handler. + * + * @param handler the server configuration network handler + */ + public static MinecraftServer getServer(ServerConfigurationNetworkHandler handler) { + Objects.requireNonNull(handler, "Network handler cannot be null"); + + return ((ServerCommonNetworkHandlerAccessor) handler).getServer(); + } + + private ServerConfigurationNetworking() { + } + + @FunctionalInterface + public interface ConfigurationChannelHandler { + /** + * Handles an incoming packet. + * + *

    This method is executed on {@linkplain io.netty.channel.EventLoop netty's event loops}. + * Modification to the game should be {@linkplain ThreadExecutor#submit(Runnable) scheduled} using the server instance from {@link ServerConfigurationNetworking#getServer(ServerConfigurationNetworkHandler)}. + * + *

    An example usage of this is: + *

    {@code
    +		 * ServerConfigurationNetworking.registerReceiver(new Identifier("mymod", "boom"), (server, handler, buf, responseSender) -> {
    +		 * 	boolean fire = buf.readBoolean();
    +		 *
    +		 * 	// All operations on the server must be executed on the server thread
    +		 * 	server.execute(() -> {
    +		 *
    +		 * 	});
    +		 * });
    +		 * }
    + * @param server the server + * @param handler the network handler that received this packet, representing the client who sent the packet + * @param buf the payload of the packet + * @param responseSender the packet sender + */ + void receive(MinecraftServer server, ServerConfigurationNetworkHandler handler, PacketByteBuf buf, PacketSender responseSender); + } + + /** + * An internal packet handler that works as a proxy between old and new API. + * @param the type of the packet + */ + private interface ConfigurationChannelHandlerProxy extends ConfigurationChannelHandler { + ConfigurationPacketHandler getOriginalHandler(); + } + + /** + * A thread-safe packet handler utilizing {@link FabricPacket}. + * @param the type of the packet + */ + @FunctionalInterface + public interface ConfigurationPacketHandler { + /** + * Handles an incoming packet. + * + *

    Unlike {@link ServerPlayNetworking.PlayPacketHandler} this method is executed on {@linkplain io.netty.channel.EventLoop netty's event loops}. + * Modification to the game should be {@linkplain ThreadExecutor#submit(Runnable) scheduled} using the Minecraft server instance from {@link ServerConfigurationNetworking#getServer(ServerConfigurationNetworkHandler)}. + * + *

    An example usage of this: + *

    {@code
    +		 * // See FabricPacket for creating the packet
    +		 * ServerConfigurationNetworking.registerReceiver(BOOM_PACKET_TYPE, (packet, responseSender) -> {
    +		 *
    +		 * });
    +		 * }
    + * + * + * @param packet the packet + * @param networkHandler the network handler + * @param responseSender the packet sender + * @see FabricPacket + */ + void receive(T packet, ServerConfigurationNetworkHandler networkHandler, PacketSender responseSender); + } +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/ServerLoginNetworking.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/ServerLoginNetworking.java index 6c6c169dc2..4723f83c1a 100644 --- a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/ServerLoginNetworking.java +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/ServerLoginNetworking.java @@ -20,6 +20,7 @@ import java.util.Set; import java.util.concurrent.Future; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; import net.minecraft.network.PacketByteBuf; @@ -36,6 +37,7 @@ *

    Server-side networking functionalities include receiving serverbound query responses and sending clientbound query requests. * * @see ServerPlayNetworking + * @see ServerConfigurationNetworking */ public final class ServerLoginNetworking { /** @@ -150,10 +152,9 @@ public interface LoginQueryResponseHandler { /** * Allows blocking client log-in until all futures passed into {@link LoginSynchronizer#waitFor(Future)} are completed. - * - * @apiNote this interface is not intended to be implemented by users of api. */ @FunctionalInterface + @ApiStatus.NonExtendable public interface LoginSynchronizer { /** * Allows blocking client log-in until the {@code future} is {@link Future#isDone() done}. diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/ServerPlayNetworking.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/ServerPlayNetworking.java index 9d3c082986..9ac7ce58da 100644 --- a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/ServerPlayNetworking.java +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/ServerPlayNetworking.java @@ -21,9 +21,9 @@ import org.jetbrains.annotations.Nullable; -import net.minecraft.network.packet.Packet; import net.minecraft.network.PacketByteBuf; -import net.minecraft.network.listener.ClientPlayPacketListener; +import net.minecraft.network.listener.ClientCommonPacketListener; +import net.minecraft.network.packet.Packet; import net.minecraft.server.MinecraftServer; import net.minecraft.server.network.ServerPlayNetworkHandler; import net.minecraft.server.network.ServerPlayerEntity; @@ -60,6 +60,7 @@ *

    See the documentation on each class for more information. * * @see ServerLoginNetworking + * @see ServerConfigurationNetworking */ public final class ServerPlayNetworking { /** @@ -381,11 +382,21 @@ public static boolean canSend(ServerPlayNetworkHandler handler, PacketType ty * @param buf the packet byte buf which represents the payload of the packet * @return a new packet */ - public static Packet createS2CPacket(Identifier channelName, PacketByteBuf buf) { + public static Packet createS2CPacket(Identifier channelName, PacketByteBuf buf) { Objects.requireNonNull(channelName, "Channel cannot be null"); Objects.requireNonNull(buf, "Buf cannot be null"); - return ServerNetworkingImpl.createPlayC2SPacket(channelName, buf); + return ServerNetworkingImpl.createS2CPacket(channelName, buf); + } + + /** + * Creates a packet which may be sent to a connected client. + * + * @param packet the fabric packet + * @return a new packet + */ + public static Packet createS2CPacket(T packet) { + return ServerNetworkingImpl.createS2CPacket(packet); } /** @@ -438,9 +449,7 @@ public static void send(ServerPlayerEntity player, T pa Objects.requireNonNull(packet, "Packet cannot be null"); Objects.requireNonNull(packet.getType(), "Packet#getType cannot return null"); - PacketByteBuf buf = PacketByteBufs.create(); - packet.write(buf); - player.networkHandler.sendPacket(createS2CPacket(packet.getType().getId(), buf)); + player.networkHandler.sendPacket(createS2CPacket(packet)); } // Helper methods diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/package-info.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/package-info.java index 142811b1b3..5516404337 100644 --- a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/package-info.java +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/api/networking/v1/package-info.java @@ -17,13 +17,35 @@ /** * The Networking API, version 1. * - *

    For login stage networking see {@link net.fabricmc.fabric.api.networking.v1.ServerLoginNetworking}. - * For play stage networking see {@link net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking}. + *

    There are three stages of Minecraft networking, all of which are supported in this API: + *

    + *
    LOGIN
    + *
    This is the initial stage, before the player logs into the world. If using a proxy server, + * the packets in this stage may be intercepted and discarded by the proxy. Most of the pre-1.20.2 + * uses of this event should be replaced with the CONFIGURATION stage. + * Related events are found at {@link net.fabricmc.fabric.api.networking.v1.ServerLoginConnectionEvents}, + * and related methods are found at {@link net.fabricmc.fabric.api.networking.v1.ServerLoginNetworking}. + *
    + *
    CONFIGURATION
    + *
    This is the stage after LOGIN. The player is authenticated, but still hasn't joined the + * world at this point. Servers can use this phase to send configurations or verify client's mod + * versions. Note that some server mods allow players in the PLAY stage to re-enter this stage, + * for example when a player chooses a minigame server in a lobby. + * Related events are found at {@link net.fabricmc.fabric.api.networking.v1.S2CConfigurationChannelEvents} + * {@link net.fabricmc.fabric.api.networking.v1.ServerConfigurationConnectionEvents}, and related methods are found at + * {@link net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking}. + *
    + *
    PLAY
    + *
    This is the stage after CONFIGURATION, where gameplay-related packets are sent and received. + * The player has joined the world and is playing the game. Related events are found at + * {@link net.fabricmc.fabric.api.networking.v1.S2CPlayChannelEvents} + * and {@link net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents}, and related methods are found at + * {@link net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking}.
    + *
    * - *

    For events related to the connection to a client see {@link net.fabricmc.fabric.api.networking.v1.ServerLoginConnectionEvents} for login stage - * or {@link net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents} for play stage. - * - *

    For events related to the ability of a client to receive packets on a channel of a specific name see {@link net.fabricmc.fabric.api.networking.v1.S2CPlayChannelEvents}. + *

    In addition, this API includes helpers for {@linkplain + * net.fabricmc.fabric.api.networking.v1.PacketByteBufs buffer creations} and {@linkplain + * net.fabricmc.fabric.api.networking.v1.PlayerLookup player lookups}. */ package net.fabricmc.fabric.api.networking.v1; diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/AbstractChanneledNetworkAddon.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/AbstractChanneledNetworkAddon.java index bec3065aed..8818ebc933 100644 --- a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/AbstractChanneledNetworkAddon.java +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/AbstractChanneledNetworkAddon.java @@ -31,9 +31,10 @@ import org.jetbrains.annotations.Nullable; import net.minecraft.network.ClientConnection; -import net.minecraft.network.packet.Packet; +import net.minecraft.network.NetworkState; import net.minecraft.network.PacketByteBuf; import net.minecraft.network.PacketCallbacks; +import net.minecraft.network.packet.Packet; import net.minecraft.util.Identifier; import net.minecraft.util.InvalidIdentifierException; @@ -45,28 +46,24 @@ * * @param the channel handler type */ -public abstract class AbstractChanneledNetworkAddon extends AbstractNetworkAddon implements PacketSender { +public abstract class AbstractChanneledNetworkAddon extends AbstractNetworkAddon implements PacketSender, CommonPacketHandler { protected final ClientConnection connection; protected final GlobalReceiverRegistry receiver; protected final Set sendableChannels; - protected final Set sendableChannelsView; - protected AbstractChanneledNetworkAddon(GlobalReceiverRegistry receiver, ClientConnection connection, String description) { - this(receiver, connection, new HashSet<>(), description); - } + protected int commonVersion = -1; - protected AbstractChanneledNetworkAddon(GlobalReceiverRegistry receiver, ClientConnection connection, Set sendableChannels, String description) { + protected AbstractChanneledNetworkAddon(GlobalReceiverRegistry receiver, ClientConnection connection, String description) { super(receiver, description); this.connection = connection; this.receiver = receiver; - this.sendableChannels = sendableChannels; - this.sendableChannelsView = Collections.unmodifiableSet(sendableChannels); + this.sendableChannels = Collections.synchronizedSet(new HashSet<>()); } public abstract void lateInit(); - protected void registerPendingChannels(ChannelInfoHolder holder) { - final Collection pending = holder.getPendingChannelsNames(); + protected void registerPendingChannels(ChannelInfoHolder holder, NetworkState state) { + final Collection pending = holder.getPendingChannelsNames(state); if (!pending.isEmpty()) { register(new ArrayList<>(pending)); @@ -75,17 +72,17 @@ protected void registerPendingChannels(ChannelInfoHolder holder) { } // always supposed to handle async! - protected boolean handle(Identifier channelName, PacketByteBuf originalBuf) { + protected boolean handle(Identifier channelName, PacketByteBuf buf) { this.logger.debug("Handling inbound packet from channel with name \"{}\"", channelName); // Handle reserved packets if (NetworkingImpl.REGISTER_CHANNEL.equals(channelName)) { - this.receiveRegistration(true, PacketByteBufs.slice(originalBuf)); + this.receiveRegistration(true, buf); return true; } if (NetworkingImpl.UNREGISTER_CHANNEL.equals(channelName)) { - this.receiveRegistration(false, PacketByteBufs.slice(originalBuf)); + this.receiveRegistration(false, buf); return true; } @@ -95,8 +92,6 @@ protected boolean handle(Identifier channelName, PacketByteBuf originalBuf) { return false; } - PacketByteBuf buf = PacketByteBufs.slice(originalBuf); - try { this.receive(handler, buf); } catch (Throwable ex) { @@ -156,24 +151,22 @@ protected void receiveRegistration(boolean register, PacketByteBuf buf) { } this.addId(ids, active); - this.schedule(register ? () -> register(ids) : () -> unregister(ids)); + + if (register) { + register(ids); + } else { + unregister(ids); + } } void register(List ids) { this.sendableChannels.addAll(ids); - this.invokeRegisterEvent(ids); + schedule(() -> this.invokeRegisterEvent(ids)); } void unregister(List ids) { this.sendableChannels.removeAll(ids); - this.invokeUnregisterEvent(ids); - } - - @Override - public void sendPacket(Packet packet) { - Objects.requireNonNull(packet, "Packet cannot be null"); - - this.connection.send(packet); + schedule(() -> this.invokeUnregisterEvent(ids)); } @Override @@ -208,6 +201,62 @@ private void addId(List ids, StringBuilder sb) { } public Set getSendableChannels() { - return this.sendableChannelsView; + return Collections.unmodifiableSet(this.sendableChannels); + } + + // Common packet handlers + + @Override + public void onCommonVersionPacket(int negotiatedVersion) { + assert negotiatedVersion == 1; // We only support version 1 for now + + commonVersion = negotiatedVersion; + this.logger.info("Negotiated common packet version {}", commonVersion); + } + + @Override + public void onCommonRegisterPacket(CommonRegisterPayload payload) { + if (payload.version() != getNegotiatedVersion()) { + throw new IllegalStateException("Negotiated common packet version: %d but received packet with version: %d".formatted(commonVersion, payload.version())); + } + + final String currentPhase = getPhase(); + + if (currentPhase == null) { + // We don't support receiving the register packet during this phase. See getPhase() for supported phases. + // The normal case where the play channels are sent during configuration is handled in the client/common configuration packet handlers. + logger.warn("Received common register packet for phase {} in network state: {}", payload.phase(), receiver.getState()); + return; + } + + if (!payload.phase().equals(currentPhase)) { + // We need to handle receiving the play phase during configuration! + throw new IllegalStateException("Register packet received for phase (%s) on handler for phase(%s)".formatted(payload.phase(), currentPhase)); + } + + register(new ArrayList<>(payload.channels())); + } + + @Override + public CommonRegisterPayload createRegisterPayload() { + return new CommonRegisterPayload(getNegotiatedVersion(), getPhase(), this.getReceivableChannels()); + } + + @Override + public int getNegotiatedVersion() { + if (commonVersion == -1) { + throw new IllegalStateException("Not yet negotiated common packet version"); + } + + return commonVersion; + } + + @Nullable + private String getPhase() { + return switch (receiver.getState()) { + case PLAY -> CommonRegisterPayload.PLAY_PHASE; + case CONFIGURATION -> CommonRegisterPayload.CONFIGURATION_PHASE; + default -> null; // We don't support receiving this packet on any other phase + }; } } diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/ChannelInfoHolder.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/ChannelInfoHolder.java index bb4cad63e3..e86717d11a 100644 --- a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/ChannelInfoHolder.java +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/ChannelInfoHolder.java @@ -18,11 +18,12 @@ import java.util.Collection; +import net.minecraft.network.NetworkState; import net.minecraft.util.Identifier; public interface ChannelInfoHolder { /** * @return Channels which are declared as receivable by the other side but have not been declared yet. */ - Collection getPendingChannelsNames(); + Collection getPendingChannelsNames(NetworkState state); } diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/CommonPacketHandler.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/CommonPacketHandler.java new file mode 100644 index 0000000000..96b90c9593 --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/CommonPacketHandler.java @@ -0,0 +1,27 @@ +/* + * 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 net.fabricmc.fabric.impl.networking; + +public interface CommonPacketHandler { + void onCommonVersionPacket(int negotiatedVersion); + + void onCommonRegisterPacket(CommonRegisterPayload payload); + + CommonRegisterPayload createRegisterPayload(); + + int getNegotiatedVersion(); +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/CommonPacketsImpl.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/CommonPacketsImpl.java new file mode 100644 index 0000000000..66a0052ef2 --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/CommonPacketsImpl.java @@ -0,0 +1,141 @@ +/* + * 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 net.fabricmc.fabric.impl.networking; + +import java.util.Arrays; +import java.util.function.Consumer; + +import net.minecraft.network.NetworkState; +import net.minecraft.network.packet.Packet; +import net.minecraft.server.network.ServerPlayerConfigurationTask; + +import net.fabricmc.fabric.api.networking.v1.ServerConfigurationConnectionEvents; +import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking; +import net.fabricmc.fabric.impl.networking.server.ServerConfigurationNetworkAddon; +import net.fabricmc.fabric.impl.networking.server.ServerNetworkingImpl; + +public class CommonPacketsImpl { + public static final int PACKET_VERSION_1 = 1; + public static final int[] SUPPORTED_COMMON_PACKET_VERSIONS = new int[]{ PACKET_VERSION_1 }; + + public static void init() { + ServerConfigurationNetworking.registerGlobalReceiver(CommonVersionPayload.PACKET_ID, (server, handler, buf, responseSender) -> { + var payload = new CommonVersionPayload(buf); + ServerConfigurationNetworkAddon addon = ServerNetworkingImpl.getAddon(handler); + addon.onCommonVersionPacket(getNegotiatedVersion(payload)); + handler.completeTask(CommonVersionConfigurationTask.KEY); + }); + + ServerConfigurationNetworking.registerGlobalReceiver(CommonRegisterPayload.PACKET_ID, (server, handler, buf, responseSender) -> { + var payload = new CommonRegisterPayload(buf); + ServerConfigurationNetworkAddon addon = ServerNetworkingImpl.getAddon(handler); + + if (CommonRegisterPayload.PLAY_PHASE.equals(payload.phase())) { + if (payload.version() != addon.getNegotiatedVersion()) { + throw new IllegalStateException("Negotiated common packet version: %d but received packet with version: %d".formatted(addon.getNegotiatedVersion(), payload.version())); + } + + // Play phase hasnt started yet, add them to the pending names. + addon.getChannelInfoHolder().getPendingChannelsNames(NetworkState.PLAY).addAll(payload.channels()); + NetworkingImpl.LOGGER.debug("Received accepted channels from the client for play phase"); + } else { + addon.onCommonRegisterPacket(payload); + } + + handler.completeTask(CommonRegisterConfigurationTask.KEY); + }); + + // Create a configuration task to send and receive the common packets + ServerConfigurationConnectionEvents.CONFIGURE.register((handler, server) -> { + final ServerConfigurationNetworkAddon addon = ServerNetworkingImpl.getAddon(handler); + + if (ServerConfigurationNetworking.canSend(handler, CommonVersionPayload.PACKET_ID)) { + // Tasks are processed in order. + handler.addTask(new CommonVersionConfigurationTask(addon)); + + if (ServerConfigurationNetworking.canSend(handler, CommonRegisterPayload.PACKET_ID)) { + handler.addTask(new CommonRegisterConfigurationTask(addon)); + } + } + }); + } + + // A configuration phase task to send and receive the version packets. + private record CommonVersionConfigurationTask(ServerConfigurationNetworkAddon addon) implements ServerPlayerConfigurationTask { + public static final Key KEY = new Key(CommonVersionPayload.PACKET_ID.toString()); + + @Override + public void sendPacket(Consumer> sender) { + addon.sendPacket(new CommonVersionPayload(SUPPORTED_COMMON_PACKET_VERSIONS)); + } + + @Override + public Key getKey() { + return KEY; + } + } + + // A configuration phase task to send and receive the registration packets. + private record CommonRegisterConfigurationTask(ServerConfigurationNetworkAddon addon) implements ServerPlayerConfigurationTask { + public static final Key KEY = new Key(CommonRegisterPayload.PACKET_ID.toString()); + + @Override + public void sendPacket(Consumer> sender) { + addon.sendPacket(addon.createRegisterPayload()); + } + + @Override + public Key getKey() { + return KEY; + } + } + + private static int getNegotiatedVersion(CommonVersionPayload payload) { + int version = getHighestCommonVersion(payload.versions(), SUPPORTED_COMMON_PACKET_VERSIONS); + + if (version <= 0) { + throw new UnsupportedOperationException("server does not support any requested versions from client"); + } + + return version; + } + + public static int getHighestCommonVersion(int[] a, int[] b) { + int[] as = a.clone(); + int[] bs = b.clone(); + + Arrays.sort(as); + Arrays.sort(bs); + + int ap = as.length - 1; + int bp = bs.length - 1; + + while (ap >= 0 && bp >= 0) { + if (as[ap] == bs[bp]) { + return as[ap]; + } + + if (as[ap] > bs[bp]) { + ap--; + } else { + bp--; + } + } + + return -1; + } +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/CommonRegisterPayload.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/CommonRegisterPayload.java new file mode 100644 index 0000000000..071f5568e4 --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/CommonRegisterPayload.java @@ -0,0 +1,51 @@ +/* + * 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 net.fabricmc.fabric.impl.networking; + +import java.util.HashSet; +import java.util.Set; + +import net.minecraft.network.PacketByteBuf; +import net.minecraft.network.packet.CustomPayload; +import net.minecraft.util.Identifier; + +public record CommonRegisterPayload(int version, String phase, Set channels) implements CustomPayload { + public static final Identifier PACKET_ID = new Identifier("c", "register"); + + public static final String PLAY_PHASE = "play"; + public static final String CONFIGURATION_PHASE = "configuration"; + + public CommonRegisterPayload(PacketByteBuf buf) { + this( + buf.readVarInt(), + buf.readString(), + buf.readCollection(HashSet::new, PacketByteBuf::readIdentifier) + ); + } + + @Override + public void write(PacketByteBuf buf) { + buf.writeVarInt(version); + buf.writeString(phase); + buf.writeCollection(channels, PacketByteBuf::writeIdentifier); + } + + @Override + public Identifier id() { + return PACKET_ID; + } +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/CommonVersionPayload.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/CommonVersionPayload.java new file mode 100644 index 0000000000..a319b487eb --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/CommonVersionPayload.java @@ -0,0 +1,39 @@ +/* + * 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 net.fabricmc.fabric.impl.networking; + +import net.minecraft.network.PacketByteBuf; +import net.minecraft.network.packet.CustomPayload; +import net.minecraft.util.Identifier; + +public record CommonVersionPayload(int[] versions) implements CustomPayload { + public static final Identifier PACKET_ID = new Identifier("c", "version"); + + public CommonVersionPayload(PacketByteBuf buf) { + this(buf.readIntArray()); + } + + @Override + public void write(PacketByteBuf buf) { + buf.writeIntArray(versions); + } + + @Override + public Identifier id() { + return PACKET_ID; + } +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/GlobalReceiverRegistry.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/GlobalReceiverRegistry.java index c2518cc039..13b2e87b02 100644 --- a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/GlobalReceiverRegistry.java +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/GlobalReceiverRegistry.java @@ -27,18 +27,22 @@ import org.jetbrains.annotations.Nullable; +import net.minecraft.network.NetworkState; import net.minecraft.util.Identifier; public final class GlobalReceiverRegistry { + private final NetworkState state; + private final ReadWriteLock lock = new ReentrantReadWriteLock(); private final Map handlers; private final Set> trackedAddons = new HashSet<>(); - public GlobalReceiverRegistry() { - this(new HashMap<>()); // sync map should be fine as there is little read write competitions + public GlobalReceiverRegistry(NetworkState state) { + this(state, new HashMap<>()); // sync map should be fine as there is little read write competitions } - public GlobalReceiverRegistry(Map map) { + public GlobalReceiverRegistry(NetworkState state, Map map) { + this.state = state; this.handlers = map; } @@ -58,7 +62,7 @@ public boolean registerGlobalReceiver(Identifier channelName, H handler) { Objects.requireNonNull(channelName, "Channel name cannot be null"); Objects.requireNonNull(handler, "Channel handler cannot be null"); - if (NetworkingImpl.isReservedPlayChannel(channelName)) { + if (NetworkingImpl.isReservedCommonChannel(channelName)) { throw new IllegalArgumentException(String.format("Cannot register handler for reserved channel with name \"%s\"", channelName)); } @@ -81,7 +85,7 @@ public boolean registerGlobalReceiver(Identifier channelName, H handler) { public H unregisterGlobalReceiver(Identifier channelName) { Objects.requireNonNull(channelName, "Channel name cannot be null"); - if (NetworkingImpl.isReservedPlayChannel(channelName)) { + if (NetworkingImpl.isReservedCommonChannel(channelName)) { throw new IllegalArgumentException(String.format("Cannot unregister packet handler for reserved channel with name \"%s\"", channelName)); } @@ -172,4 +176,8 @@ private void handleUnregistration(Identifier channelName) { lock.unlock(); } } + + public NetworkState getState() { + return state; + } } diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/NetworkingImpl.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/NetworkingImpl.java index b9f582dc67..d9c9d97600 100644 --- a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/NetworkingImpl.java +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/NetworkingImpl.java @@ -16,76 +16,26 @@ package net.fabricmc.fabric.impl.networking; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import net.minecraft.network.ClientConnection; -import net.minecraft.network.PacketByteBuf; import net.minecraft.util.Identifier; -import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; -import net.fabricmc.fabric.api.networking.v1.ServerLoginConnectionEvents; -import net.fabricmc.fabric.api.networking.v1.ServerLoginNetworking; -import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; -import net.fabricmc.fabric.mixin.networking.accessor.ServerLoginNetworkHandlerAccessor; - public final class NetworkingImpl { public static final String MOD_ID = "fabric-networking-api-v1"; public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); + /** * Id of packet used to register supported channels. */ public static final Identifier REGISTER_CHANNEL = new Identifier("minecraft", "register"); + /** * Id of packet used to unregister supported channels. */ public static final Identifier UNREGISTER_CHANNEL = new Identifier("minecraft", "unregister"); - /** - * Id of the packet used to declare all currently supported channels. - * Dynamic registration of supported channels is still allowed using {@link NetworkingImpl#REGISTER_CHANNEL} and {@link NetworkingImpl#UNREGISTER_CHANNEL}. - */ - public static final Identifier EARLY_REGISTRATION_CHANNEL = new Identifier(MOD_ID, "early_registration"); - - public static void init() { - // Login setup - ServerLoginConnectionEvents.QUERY_START.register((handler, server, sender, synchronizer) -> { - // Send early registration packet - PacketByteBuf buf = PacketByteBufs.create(); - Collection channelsNames = ServerPlayNetworking.getGlobalReceivers(); - buf.writeVarInt(channelsNames.size()); - - for (Identifier id : channelsNames) { - buf.writeIdentifier(id); - } - - sender.sendPacket(EARLY_REGISTRATION_CHANNEL, buf); - NetworkingImpl.LOGGER.debug("Sent accepted channels to the client for \"{}\"", handler.getConnectionInfo()); - }); - - ServerLoginNetworking.registerGlobalReceiver(EARLY_REGISTRATION_CHANNEL, (server, handler, understood, buf, synchronizer, sender) -> { - if (!understood) { - // The client is likely a vanilla client. - return; - } - - int n = buf.readVarInt(); - List ids = new ArrayList<>(n); - - for (int i = 0; i < n; i++) { - ids.add(buf.readIdentifier()); - } - - ClientConnection connection = ((ServerLoginNetworkHandlerAccessor) handler).getConnection(); - ((ChannelInfoHolder) connection).getPendingChannelsNames().addAll(ids); - NetworkingImpl.LOGGER.debug("Received accepted channels from the client for \"{}\"", handler.getConnectionInfo()); - }); - } - public static boolean isReservedPlayChannel(Identifier channelName) { + public static boolean isReservedCommonChannel(Identifier channelName) { return channelName.equals(REGISTER_CHANNEL) || channelName.equals(UNREGISTER_CHANNEL); } } diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/payload/FabricPacketLoginQueryRequestPayload.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/payload/FabricPacketLoginQueryRequestPayload.java new file mode 100644 index 0000000000..0d3c4dceed --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/payload/FabricPacketLoginQueryRequestPayload.java @@ -0,0 +1,35 @@ +/* + * 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 net.fabricmc.fabric.impl.networking.payload; + +import net.minecraft.network.PacketByteBuf; +import net.minecraft.network.packet.s2c.login.LoginQueryRequestPayload; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.api.networking.v1.FabricPacket; + +public record FabricPacketLoginQueryRequestPayload(FabricPacket fabricPacket) implements LoginQueryRequestPayload { + @Override + public void write(PacketByteBuf buf) { + fabricPacket.write(buf); + } + + @Override + public Identifier id() { + return fabricPacket.getType().getId(); + } +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/payload/PacketByteBufLoginQueryRequestPayload.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/payload/PacketByteBufLoginQueryRequestPayload.java new file mode 100644 index 0000000000..2e50bc9e3c --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/payload/PacketByteBufLoginQueryRequestPayload.java @@ -0,0 +1,28 @@ +/* + * 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 net.fabricmc.fabric.impl.networking.payload; + +import net.minecraft.network.PacketByteBuf; +import net.minecraft.network.packet.s2c.login.LoginQueryRequestPayload; +import net.minecraft.util.Identifier; + +public record PacketByteBufLoginQueryRequestPayload(Identifier id, PacketByteBuf data) implements LoginQueryRequestPayload { + @Override + public void write(PacketByteBuf buf) { + PayloadHelper.write(buf, data()); + } +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/accessor/LoginQueryResponseC2SPacketAccessor.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/payload/PacketByteBufLoginQueryResponse.java similarity index 65% rename from fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/accessor/LoginQueryResponseC2SPacketAccessor.java rename to fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/payload/PacketByteBufLoginQueryResponse.java index d518193d75..08d79fb58e 100644 --- a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/accessor/LoginQueryResponseC2SPacketAccessor.java +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/payload/PacketByteBufLoginQueryResponse.java @@ -14,21 +14,14 @@ * limitations under the License. */ -package net.fabricmc.fabric.mixin.networking.accessor; - -import org.jetbrains.annotations.Nullable; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Accessor; +package net.fabricmc.fabric.impl.networking.payload; import net.minecraft.network.PacketByteBuf; -import net.minecraft.network.packet.c2s.login.LoginQueryResponseC2SPacket; - -@Mixin(LoginQueryResponseC2SPacket.class) -public interface LoginQueryResponseC2SPacketAccessor { - @Accessor - int getQueryId(); +import net.minecraft.network.packet.c2s.login.LoginQueryResponsePayload; - @Nullable - @Accessor - PacketByteBuf getResponse(); +public record PacketByteBufLoginQueryResponse(PacketByteBuf data) implements LoginQueryResponsePayload { + @Override + public void write(PacketByteBuf buf) { + PayloadHelper.write(buf, data()); + } } diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/payload/PacketByteBufPayload.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/payload/PacketByteBufPayload.java new file mode 100644 index 0000000000..78ac1cc39f --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/payload/PacketByteBufPayload.java @@ -0,0 +1,28 @@ +/* + * 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 net.fabricmc.fabric.impl.networking.payload; + +import net.minecraft.network.PacketByteBuf; +import net.minecraft.network.packet.CustomPayload; +import net.minecraft.util.Identifier; + +public record PacketByteBufPayload(Identifier id, PacketByteBuf data) implements CustomPayload { + @Override + public void write(PacketByteBuf buf) { + PayloadHelper.write(buf, data()); + } +} diff --git a/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/mixin/object/builder/CriteriaAccessor.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/payload/PayloadHelper.java similarity index 55% rename from fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/mixin/object/builder/CriteriaAccessor.java rename to fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/payload/PayloadHelper.java index 1684caf235..efef0e8cea 100644 --- a/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/mixin/object/builder/CriteriaAccessor.java +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/payload/PayloadHelper.java @@ -14,18 +14,21 @@ * limitations under the License. */ -package net.fabricmc.fabric.mixin.object.builder; +package net.fabricmc.fabric.impl.networking.payload; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Invoker; +import net.minecraft.network.PacketByteBuf; -import net.minecraft.advancement.criterion.Criteria; -import net.minecraft.advancement.criterion.Criterion; +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; -@Mixin(Criteria.class) -public interface CriteriaAccessor { - @Invoker - static > T callRegister(T object) { - throw new AssertionError("Mixin dummy"); +public class PayloadHelper { + public static void write(PacketByteBuf byteBuf, PacketByteBuf data) { + byteBuf.writeBytes(data.copy()); + } + + public static PacketByteBuf read(PacketByteBuf byteBuf) { + PacketByteBuf newBuf = PacketByteBufs.create(); + newBuf.writeBytes(byteBuf.copy()); + byteBuf.skipBytes(byteBuf.readableBytes()); + return newBuf; } } diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/server/ServerConfigurationNetworkAddon.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/server/ServerConfigurationNetworkAddon.java new file mode 100644 index 0000000000..4ac8ee5dab --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/server/ServerConfigurationNetworkAddon.java @@ -0,0 +1,203 @@ +/* + * 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 net.fabricmc.fabric.impl.networking.server; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import net.minecraft.network.NetworkState; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.network.PacketCallbacks; +import net.minecraft.network.packet.Packet; +import net.minecraft.network.packet.s2c.common.CommonPingS2CPacket; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerConfigurationNetworkHandler; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.api.networking.v1.FabricPacket; +import net.fabricmc.fabric.api.networking.v1.S2CConfigurationChannelEvents; +import net.fabricmc.fabric.api.networking.v1.ServerConfigurationConnectionEvents; +import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.fabricmc.fabric.impl.networking.AbstractChanneledNetworkAddon; +import net.fabricmc.fabric.impl.networking.ChannelInfoHolder; +import net.fabricmc.fabric.impl.networking.NetworkingImpl; +import net.fabricmc.fabric.impl.networking.payload.PacketByteBufPayload; +import net.fabricmc.fabric.mixin.networking.accessor.ServerCommonNetworkHandlerAccessor; + +public final class ServerConfigurationNetworkAddon extends AbstractChanneledNetworkAddon { + private final ServerConfigurationNetworkHandler handler; + private final MinecraftServer server; + private RegisterState registerState = RegisterState.NOT_SENT; + + public ServerConfigurationNetworkAddon(ServerConfigurationNetworkHandler handler, MinecraftServer server) { + super(ServerNetworkingImpl.CONFIGURATION, ((ServerCommonNetworkHandlerAccessor) handler).getConnection(), "ServerConfigurationNetworkAddon for " + handler.getDebugProfile().getName()); + this.handler = handler; + this.server = server; + + // Must register pending channels via lateinit + this.registerPendingChannels((ChannelInfoHolder) this.connection, NetworkState.CONFIGURATION); + + // Register global receivers and attach to session + this.receiver.startSession(this); + } + + @Override + public void lateInit() { + for (Map.Entry entry : this.receiver.getHandlers().entrySet()) { + this.registerChannel(entry.getKey(), entry.getValue()); + } + } + + public void preConfiguration() { + ServerConfigurationConnectionEvents.BEFORE_CONFIGURE.invoker().onSendConfiguration(handler, server); + } + + public void configuration() { + ServerConfigurationConnectionEvents.CONFIGURE.invoker().onSendConfiguration(handler, server); + } + + public boolean startConfiguration() { + if (this.registerState == RegisterState.NOT_SENT) { + // Send the registration packet, followed by a ping + this.sendInitialChannelRegistrationPacket(); + this.sendPacket(new CommonPingS2CPacket(0xFAB71C)); + + this.registerState = RegisterState.SENT; + + // Cancel the configuration for now, the response from the ping or registration packet will continue. + return true; + } + + // We should have received a response + assert registerState == RegisterState.RECEIVED || registerState == RegisterState.NOT_RECEIVED; + return false; + } + + @Override + protected void receiveRegistration(boolean register, PacketByteBuf buf) { + super.receiveRegistration(register, buf); + + if (register && registerState == RegisterState.SENT) { + // We received the registration packet, thus we know this is a modded client, continue with configuration. + registerState = RegisterState.RECEIVED; + handler.sendConfigurations(); + } + } + + public void onPong(int parameter) { + if (registerState == RegisterState.SENT) { + // We did not receive the registration packet, thus we think this is a vanilla client, continue with configuration. + registerState = RegisterState.NOT_RECEIVED; + handler.sendConfigurations(); + } + } + + /** + * Handles an incoming packet. + * + * @param payload the payload to handle + * @return true if the packet has been handled + */ + public boolean handle(PacketByteBufPayload payload) { + return this.handle(payload.id(), payload.data()); + } + + @Override + protected void receive(ServerConfigurationNetworking.ConfigurationChannelHandler handler, PacketByteBuf buf) { + handler.receive(this.server, this.handler, buf, this); + } + + // impl details + + @Override + protected void schedule(Runnable task) { + this.server.execute(task); + } + + @Override + public Packet createPacket(Identifier channelName, PacketByteBuf buf) { + return ServerPlayNetworking.createS2CPacket(channelName, buf); + } + + @Override + public Packet createPacket(FabricPacket packet) { + return ServerPlayNetworking.createS2CPacket(packet); + } + + @Override + protected void invokeRegisterEvent(List ids) { + S2CConfigurationChannelEvents.REGISTER.invoker().onChannelRegister(this.handler, this, this.server, ids); + } + + @Override + protected void invokeUnregisterEvent(List ids) { + S2CConfigurationChannelEvents.UNREGISTER.invoker().onChannelUnregister(this.handler, this, this.server, ids); + } + + @Override + protected void handleRegistration(Identifier channelName) { + // If we can already send packets, immediately send the register packet for this channel + if (this.registerState != RegisterState.NOT_SENT) { + final PacketByteBuf buf = this.createRegistrationPacket(Collections.singleton(channelName)); + + if (buf != null) { + this.sendPacket(NetworkingImpl.REGISTER_CHANNEL, buf); + } + } + } + + @Override + protected void handleUnregistration(Identifier channelName) { + // If we can already send packets, immediately send the unregister packet for this channel + if (this.registerState != RegisterState.NOT_SENT) { + final PacketByteBuf buf = this.createRegistrationPacket(Collections.singleton(channelName)); + + if (buf != null) { + this.sendPacket(NetworkingImpl.UNREGISTER_CHANNEL, buf); + } + } + } + + @Override + protected void invokeDisconnectEvent() { + ServerConfigurationConnectionEvents.DISCONNECT.invoker().onConfigureDisconnect(handler, server); + this.receiver.endSession(this); + } + + @Override + protected boolean isReservedChannel(Identifier channelName) { + return NetworkingImpl.isReservedCommonChannel(channelName); + } + + @Override + public void sendPacket(Packet packet, PacketCallbacks callback) { + handler.send(packet, callback); + } + + private enum RegisterState { + NOT_SENT, + SENT, + RECEIVED, + NOT_RECEIVED + } + + public ChannelInfoHolder getChannelInfoHolder() { + return (ChannelInfoHolder) ((ServerCommonNetworkHandlerAccessor) handler).getConnection(); + } +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/server/ServerLoginNetworkAddon.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/server/ServerLoginNetworkAddon.java index e601c1e697..8c6b22200f 100644 --- a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/server/ServerLoginNetworkAddon.java +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/server/ServerLoginNetworkAddon.java @@ -30,9 +30,9 @@ import org.jetbrains.annotations.Nullable; import net.minecraft.network.ClientConnection; -import net.minecraft.network.packet.Packet; import net.minecraft.network.PacketByteBuf; import net.minecraft.network.PacketCallbacks; +import net.minecraft.network.packet.Packet; import net.minecraft.network.packet.c2s.login.LoginQueryResponseC2SPacket; import net.minecraft.network.packet.s2c.login.LoginCompressionS2CPacket; import net.minecraft.network.packet.s2c.login.LoginQueryRequestS2CPacket; @@ -40,13 +40,16 @@ import net.minecraft.server.network.ServerLoginNetworkHandler; import net.minecraft.util.Identifier; +import net.fabricmc.fabric.api.networking.v1.FabricPacket; import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; import net.fabricmc.fabric.api.networking.v1.PacketSender; import net.fabricmc.fabric.api.networking.v1.ServerLoginConnectionEvents; import net.fabricmc.fabric.api.networking.v1.ServerLoginNetworking; import net.fabricmc.fabric.impl.networking.AbstractNetworkAddon; import net.fabricmc.fabric.impl.networking.GenericFutureListenerHolder; -import net.fabricmc.fabric.mixin.networking.accessor.LoginQueryResponseC2SPacketAccessor; +import net.fabricmc.fabric.impl.networking.payload.FabricPacketLoginQueryRequestPayload; +import net.fabricmc.fabric.impl.networking.payload.PacketByteBufLoginQueryRequestPayload; +import net.fabricmc.fabric.impl.networking.payload.PacketByteBufLoginQueryResponse; import net.fabricmc.fabric.mixin.networking.accessor.ServerLoginNetworkHandlerAccessor; public final class ServerLoginNetworkAddon extends AbstractNetworkAddon implements PacketSender { @@ -128,8 +131,8 @@ private void sendCompressionPacket() { * @return true if the packet was handled */ public boolean handle(LoginQueryResponseC2SPacket packet) { - LoginQueryResponseC2SPacketAccessor access = (LoginQueryResponseC2SPacketAccessor) packet; - return handle(access.getQueryId(), access.getResponse()); + PacketByteBufLoginQueryResponse response = (PacketByteBufLoginQueryResponse) packet.response(); + return handle(packet.queryId(), response == null ? null : response.data()); } private boolean handle(int queryId, @Nullable PacketByteBuf originalBuf) { @@ -163,16 +166,13 @@ private boolean handle(int queryId, @Nullable PacketByteBuf originalBuf) { @Override public Packet createPacket(Identifier channelName, PacketByteBuf buf) { int queryId = this.queryIdFactory.nextId(); - - LoginQueryRequestS2CPacket ret = new LoginQueryRequestS2CPacket(queryId, channelName, buf); - return ret; + return new LoginQueryRequestS2CPacket(queryId, new PacketByteBufLoginQueryRequestPayload(channelName, buf)); } @Override - public void sendPacket(Packet packet) { - Objects.requireNonNull(packet, "Packet cannot be null"); - - this.connection.send(packet); + public Packet createPacket(FabricPacket packet) { + int queryId = this.queryIdFactory.nextId(); + return new LoginQueryRequestS2CPacket(queryId, new FabricPacketLoginQueryRequestPayload(packet)); } @Override @@ -188,7 +188,7 @@ public void sendPacket(Packet packet, PacketCallbacks callback) { } public void registerOutgoingPacket(LoginQueryRequestS2CPacket packet) { - this.channels.put(packet.getQueryId(), packet.getChannel()); + this.channels.put(packet.queryId(), packet.payload().id()); } @Override @@ -205,7 +205,7 @@ protected void invokeDisconnectEvent() { this.receiver.endSession(this); } - public void handlePlayTransition() { + public void handleConfigurationTransition() { this.receiver.endSession(this); } diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/server/ServerNetworkingImpl.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/server/ServerNetworkingImpl.java index 6414ac59a9..2a3c88e596 100644 --- a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/server/ServerNetworkingImpl.java +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/server/ServerNetworkingImpl.java @@ -16,22 +16,31 @@ package net.fabricmc.fabric.impl.networking.server; -import net.minecraft.network.packet.Packet; +import java.util.Objects; + +import net.minecraft.network.NetworkState; import net.minecraft.network.PacketByteBuf; -import net.minecraft.network.listener.ClientPlayPacketListener; -import net.minecraft.network.packet.s2c.play.CustomPayloadS2CPacket; +import net.minecraft.network.listener.ClientCommonPacketListener; +import net.minecraft.network.packet.Packet; +import net.minecraft.network.packet.s2c.common.CustomPayloadS2CPacket; +import net.minecraft.server.network.ServerConfigurationNetworkHandler; import net.minecraft.server.network.ServerLoginNetworkHandler; import net.minecraft.server.network.ServerPlayNetworkHandler; import net.minecraft.util.Identifier; +import net.fabricmc.fabric.api.networking.v1.FabricPacket; +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; +import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking; import net.fabricmc.fabric.api.networking.v1.ServerLoginNetworking; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; import net.fabricmc.fabric.impl.networking.GlobalReceiverRegistry; import net.fabricmc.fabric.impl.networking.NetworkHandlerExtensions; +import net.fabricmc.fabric.impl.networking.payload.PacketByteBufPayload; public final class ServerNetworkingImpl { - public static final GlobalReceiverRegistry LOGIN = new GlobalReceiverRegistry<>(); - public static final GlobalReceiverRegistry PLAY = new GlobalReceiverRegistry<>(); + public static final GlobalReceiverRegistry LOGIN = new GlobalReceiverRegistry<>(NetworkState.LOGIN); + public static final GlobalReceiverRegistry CONFIGURATION = new GlobalReceiverRegistry<>(NetworkState.CONFIGURATION); + public static final GlobalReceiverRegistry PLAY = new GlobalReceiverRegistry<>(NetworkState.PLAY); public static ServerPlayNetworkAddon getAddon(ServerPlayNetworkHandler handler) { return (ServerPlayNetworkAddon) ((NetworkHandlerExtensions) handler).getAddon(); @@ -41,7 +50,20 @@ public static ServerLoginNetworkAddon getAddon(ServerLoginNetworkHandler handler return (ServerLoginNetworkAddon) ((NetworkHandlerExtensions) handler).getAddon(); } - public static Packet createPlayC2SPacket(Identifier channel, PacketByteBuf buf) { - return new CustomPayloadS2CPacket(channel, buf); + public static ServerConfigurationNetworkAddon getAddon(ServerConfigurationNetworkHandler handler) { + return (ServerConfigurationNetworkAddon) ((NetworkHandlerExtensions) handler).getAddon(); + } + + public static Packet createS2CPacket(Identifier channel, PacketByteBuf buf) { + return new CustomPayloadS2CPacket(new PacketByteBufPayload(channel, buf)); + } + + public static Packet createS2CPacket(FabricPacket packet) { + Objects.requireNonNull(packet, "Packet cannot be null"); + Objects.requireNonNull(packet.getType(), "Packet#getType cannot return null"); + + PacketByteBuf buf = PacketByteBufs.create(); + packet.write(buf); + return createS2CPacket(packet.getType().getId(), buf); } } diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/server/ServerPlayNetworkAddon.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/server/ServerPlayNetworkAddon.java index 395fa1b328..3516d1cf1e 100644 --- a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/server/ServerPlayNetworkAddon.java +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/server/ServerPlayNetworkAddon.java @@ -20,21 +20,22 @@ import java.util.List; import java.util.Map; -import net.minecraft.network.packet.Packet; +import net.minecraft.network.NetworkState; import net.minecraft.network.PacketByteBuf; -import net.minecraft.network.packet.c2s.play.CustomPayloadC2SPacket; +import net.minecraft.network.packet.Packet; import net.minecraft.server.MinecraftServer; import net.minecraft.server.network.ServerPlayNetworkHandler; import net.minecraft.util.Identifier; +import net.fabricmc.fabric.api.networking.v1.FabricPacket; import net.fabricmc.fabric.api.networking.v1.S2CPlayChannelEvents; import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; import net.fabricmc.fabric.impl.networking.AbstractChanneledNetworkAddon; import net.fabricmc.fabric.impl.networking.ChannelInfoHolder; import net.fabricmc.fabric.impl.networking.NetworkingImpl; -import net.fabricmc.fabric.mixin.networking.accessor.CustomPayloadC2SPacketAccessor; -import net.fabricmc.fabric.mixin.networking.accessor.ServerPlayNetworkHandlerAccessor; +import net.fabricmc.fabric.impl.networking.payload.PacketByteBufPayload; +import net.fabricmc.fabric.mixin.networking.accessor.ServerCommonNetworkHandlerAccessor; public final class ServerPlayNetworkAddon extends AbstractChanneledNetworkAddon { private final ServerPlayNetworkHandler handler; @@ -42,12 +43,12 @@ public final class ServerPlayNetworkAddon extends AbstractChanneledNetworkAddon< private boolean sentInitialRegisterPacket; public ServerPlayNetworkAddon(ServerPlayNetworkHandler handler, MinecraftServer server) { - super(ServerNetworkingImpl.PLAY, ((ServerPlayNetworkHandlerAccessor) handler).getConnection(), "ServerPlayNetworkAddon for " + handler.player.getEntityName()); + super(ServerNetworkingImpl.PLAY, ((ServerCommonNetworkHandlerAccessor) handler).getConnection(), "ServerPlayNetworkAddon for " + handler.player.getEntityName()); this.handler = handler; this.server = server; // Must register pending channels via lateinit - this.registerPendingChannels((ChannelInfoHolder) this.connection); + this.registerPendingChannels((ChannelInfoHolder) this.connection, NetworkState.PLAY); // Register global receivers and attach to session this.receiver.startSession(this); @@ -72,12 +73,11 @@ public void onClientReady() { /** * Handles an incoming packet. * - * @param packet the packet to handle + * @param payload the payload to handle * @return true if the packet has been handled */ - public boolean handle(CustomPayloadC2SPacket packet) { - CustomPayloadC2SPacketAccessor access = (CustomPayloadC2SPacketAccessor) packet; - return this.handle(access.getChannel(), access.getData()); + public boolean handle(PacketByteBufPayload payload) { + return this.handle(payload.id(), payload.data()); } @Override @@ -97,6 +97,11 @@ public Packet createPacket(Identifier channelName, PacketByteBuf buf) { return ServerPlayNetworking.createS2CPacket(channelName, buf); } + @Override + public Packet createPacket(FabricPacket packet) { + return ServerPlayNetworking.createS2CPacket(packet); + } + @Override protected void invokeRegisterEvent(List ids) { S2CPlayChannelEvents.REGISTER.invoker().onChannelRegister(this.handler, this, this.server, ids); @@ -139,6 +144,6 @@ protected void invokeDisconnectEvent() { @Override protected boolean isReservedChannel(Identifier channelName) { - return NetworkingImpl.isReservedPlayChannel(channelName); + return NetworkingImpl.isReservedCommonChannel(channelName); } } diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/ClientConnectionMixin.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/ClientConnectionMixin.java index 99b61ad630..191368af76 100644 --- a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/ClientConnectionMixin.java +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/ClientConnectionMixin.java @@ -18,6 +18,7 @@ import java.util.Collection; import java.util.Collections; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import io.netty.channel.ChannelFuture; @@ -36,9 +37,9 @@ import net.minecraft.network.ClientConnection; import net.minecraft.network.NetworkSide; import net.minecraft.network.NetworkState; -import net.minecraft.network.packet.Packet; import net.minecraft.network.PacketCallbacks; import net.minecraft.network.listener.PacketListener; +import net.minecraft.network.packet.Packet; import net.minecraft.text.Text; import net.minecraft.util.Identifier; @@ -60,11 +61,11 @@ abstract class ClientConnectionMixin implements ChannelInfoHolder { public abstract void send(Packet packet, @Nullable PacketCallbacks arg); @Unique - private Collection playChannels; + private Map> playChannels; @Inject(method = "", at = @At("RETURN")) private void initAddedFields(NetworkSide side, CallbackInfo ci) { - this.playChannels = Collections.newSetFromMap(new ConcurrentHashMap<>()); + this.playChannels = new ConcurrentHashMap<>(); } // Must be fully qualified due to mixin not working in production without it @@ -81,7 +82,7 @@ private void resendOnExceptionCaught(ClientConnection self, Packet packet, Pa } @Inject(method = "sendImmediately", at = @At(value = "FIELD", target = "Lnet/minecraft/network/ClientConnection;packetsSentCounter:I")) - private void checkPacket(Packet packet, PacketCallbacks callback, CallbackInfo ci) { + private void checkPacket(Packet packet, PacketCallbacks callback, boolean flush, CallbackInfo ci) { if (this.packetListener instanceof PacketCallbackListener) { ((PacketCallbackListener) this.packetListener).sent(packet); } @@ -94,9 +95,9 @@ private void handleDisconnect(ChannelHandlerContext channelHandlerContext, Callb } } - @Inject(method = "sendInternal", at = @At(value = "INVOKE_ASSIGN", target = "Lio/netty/channel/Channel;writeAndFlush(Ljava/lang/Object;)Lio/netty/channel/ChannelFuture;", remap = false), locals = LocalCapture.CAPTURE_FAILHARD) - private void sendInternal(Packet packet, @Nullable PacketCallbacks listener, NetworkState packetState, NetworkState currentState, CallbackInfo ci, ChannelFuture channelFuture) { - if (listener instanceof GenericFutureListenerHolder holder) { + @Inject(method = "sendInternal", at = @At(value = "INVOKE", target = "Lio/netty/channel/ChannelFuture;addListener(Lio/netty/util/concurrent/GenericFutureListener;)Lio/netty/channel/ChannelFuture;", remap = false), locals = LocalCapture.CAPTURE_FAILHARD, cancellable = true) + private void sendInternal(Packet packet, @Nullable PacketCallbacks callbacks, boolean flush, CallbackInfo ci, ChannelFuture channelFuture) { + if (callbacks instanceof GenericFutureListenerHolder holder) { channelFuture.addListener(holder.getDelegate()); channelFuture.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); ci.cancel(); @@ -104,7 +105,7 @@ private void sendInternal(Packet packet, @Nullable PacketCallbacks listener, } @Override - public Collection getPendingChannelsNames() { - return this.playChannels; + public Collection getPendingChannelsNames(NetworkState state) { + return this.playChannels.computeIfAbsent(state, (key) -> Collections.newSetFromMap(new ConcurrentHashMap<>())); } } diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/CommandManagerMixin.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/CommandManagerMixin.java new file mode 100644 index 0000000000..dfdc4e0048 --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/CommandManagerMixin.java @@ -0,0 +1,55 @@ +/* + * 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 net.fabricmc.fabric.mixin.networking; + +import com.mojang.brigadier.CommandDispatcher; +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.SharedConstants; +import net.minecraft.command.CommandRegistryAccess; +import net.minecraft.server.command.CommandManager; +import net.minecraft.server.command.DebugConfigCommand; +import net.minecraft.server.command.ServerCommandSource; + +import net.fabricmc.loader.api.FabricLoader; + +@Mixin(CommandManager.class) +public class CommandManagerMixin { + @Shadow + @Final + private CommandDispatcher dispatcher; + + @Inject(method = "", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/dedicated/command/BanIpCommand;register(Lcom/mojang/brigadier/CommandDispatcher;)V")) + private void init(CommandManager.RegistrationEnvironment environment, CommandRegistryAccess commandRegistryAccess, CallbackInfo ci) { + if (SharedConstants.isDevelopment) { + // Command is registered when isDevelopment is set. + return; + } + + if (!FabricLoader.getInstance().isDevelopmentEnvironment()) { + // Only register this command in a dev env + return; + } + + DebugConfigCommand.register(this.dispatcher); + } +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/CustomPayloadC2SPacketMixin.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/CustomPayloadC2SPacketMixin.java new file mode 100644 index 0000000000..17bd8b1155 --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/CustomPayloadC2SPacketMixin.java @@ -0,0 +1,42 @@ +/* + * 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 net.fabricmc.fabric.mixin.networking; + +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.network.PacketByteBuf; +import net.minecraft.network.packet.CustomPayload; +import net.minecraft.network.packet.c2s.common.CustomPayloadC2SPacket; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.impl.networking.payload.PacketByteBufPayload; +import net.fabricmc.fabric.impl.networking.payload.PayloadHelper; + +@Mixin(CustomPayloadC2SPacket.class) +public class CustomPayloadC2SPacketMixin { + @Inject( + method = "readPayload", + at = @At(value = "INVOKE", target = "Lnet/minecraft/network/packet/c2s/common/CustomPayloadC2SPacket;readUnknownPayload(Lnet/minecraft/util/Identifier;Lnet/minecraft/network/PacketByteBuf;)Lnet/minecraft/network/packet/UnknownCustomPayload;"), + cancellable = true + ) + private static void readPayload(Identifier id, PacketByteBuf buf, CallbackInfoReturnable cir) { + cir.setReturnValue(new PacketByteBufPayload(id, PayloadHelper.read(buf))); + } +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/CustomPayloadS2CPacketMixin.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/CustomPayloadS2CPacketMixin.java new file mode 100644 index 0000000000..ed4599ed1c --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/CustomPayloadS2CPacketMixin.java @@ -0,0 +1,42 @@ +/* + * 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 net.fabricmc.fabric.mixin.networking; + +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.network.PacketByteBuf; +import net.minecraft.network.packet.CustomPayload; +import net.minecraft.network.packet.s2c.common.CustomPayloadS2CPacket; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.impl.networking.payload.PacketByteBufPayload; +import net.fabricmc.fabric.impl.networking.payload.PayloadHelper; + +@Mixin(CustomPayloadS2CPacket.class) +public class CustomPayloadS2CPacketMixin { + @Inject( + method = "readPayload", + at = @At(value = "INVOKE", target = "Lnet/minecraft/network/packet/s2c/common/CustomPayloadS2CPacket;readUnknownPayload(Lnet/minecraft/util/Identifier;Lnet/minecraft/network/PacketByteBuf;)Lnet/minecraft/network/packet/UnknownCustomPayload;"), + cancellable = true + ) + private static void readPayload(Identifier id, PacketByteBuf buf, CallbackInfoReturnable cir) { + cir.setReturnValue(new PacketByteBufPayload(id, PayloadHelper.read(buf))); + } +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/LoginQueryRequestS2CPacketMixin.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/LoginQueryRequestS2CPacketMixin.java new file mode 100644 index 0000000000..432a491628 --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/LoginQueryRequestS2CPacketMixin.java @@ -0,0 +1,38 @@ +/* + * 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 net.fabricmc.fabric.mixin.networking; + +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.network.PacketByteBuf; +import net.minecraft.network.packet.s2c.login.LoginQueryRequestPayload; +import net.minecraft.network.packet.s2c.login.LoginQueryRequestS2CPacket; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.impl.networking.payload.PacketByteBufLoginQueryRequestPayload; +import net.fabricmc.fabric.impl.networking.payload.PayloadHelper; + +@Mixin(LoginQueryRequestS2CPacket.class) +public class LoginQueryRequestS2CPacketMixin { + @Inject(method = "readPayload", at = @At("HEAD"), cancellable = true) + private static void readPayload(Identifier id, PacketByteBuf buf, CallbackInfoReturnable cir) { + cir.setReturnValue(new PacketByteBufLoginQueryRequestPayload(id, PayloadHelper.read(buf))); + } +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/LoginQueryResponseC2SPacketMixin.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/LoginQueryResponseC2SPacketMixin.java new file mode 100644 index 0000000000..dfcbf3b802 --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/LoginQueryResponseC2SPacketMixin.java @@ -0,0 +1,44 @@ +/* + * 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 net.fabricmc.fabric.mixin.networking; + +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.network.PacketByteBuf; +import net.minecraft.network.packet.c2s.login.LoginQueryResponsePayload; +import net.minecraft.network.packet.c2s.login.LoginQueryResponseC2SPacket; + +import net.fabricmc.fabric.impl.networking.payload.PacketByteBufLoginQueryResponse; +import net.fabricmc.fabric.impl.networking.payload.PayloadHelper; + +@Mixin(LoginQueryResponseC2SPacket.class) +public class LoginQueryResponseC2SPacketMixin { + @Inject(method = "readPayload", at = @At("HEAD"), cancellable = true) + private static void readResponse(int queryId, PacketByteBuf buf, CallbackInfoReturnable cir) { + boolean hasPayload = buf.readBoolean(); + + if (!hasPayload) { + cir.setReturnValue(null); + return; + } + + cir.setReturnValue(new PacketByteBufLoginQueryResponse(PayloadHelper.read(buf))); + } +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/PlayerManagerMixin.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/PlayerManagerMixin.java index ec22a832ae..beacc1f51f 100644 --- a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/PlayerManagerMixin.java +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/PlayerManagerMixin.java @@ -23,14 +23,15 @@ import net.minecraft.network.ClientConnection; import net.minecraft.server.PlayerManager; +import net.minecraft.server.network.ConnectedClientData; import net.minecraft.server.network.ServerPlayerEntity; import net.fabricmc.fabric.impl.networking.server.ServerNetworkingImpl; @Mixin(PlayerManager.class) abstract class PlayerManagerMixin { - @Inject(method = "onPlayerConnect", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/packet/s2c/play/CustomPayloadS2CPacket;(Lnet/minecraft/util/Identifier;Lnet/minecraft/network/PacketByteBuf;)V")) - private void handlePlayerConnection(ClientConnection connection, ServerPlayerEntity player, CallbackInfo ci) { + @Inject(method = "onPlayerConnect", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/packet/s2c/play/PlayerAbilitiesS2CPacket;(Lnet/minecraft/entity/player/PlayerAbilities;)V")) + private void handlePlayerConnection(ClientConnection connection, ServerPlayerEntity player, ConnectedClientData arg, CallbackInfo ci) { ServerNetworkingImpl.getAddon(player.networkHandler).onClientReady(); } } diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/ServerCommonNetworkHandlerMixin.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/ServerCommonNetworkHandlerMixin.java new file mode 100644 index 0000000000..58734f1bf0 --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/ServerCommonNetworkHandlerMixin.java @@ -0,0 +1,62 @@ +/* + * 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 net.fabricmc.fabric.mixin.networking; + +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 net.minecraft.network.packet.c2s.common.CustomPayloadC2SPacket; +import net.minecraft.network.packet.c2s.common.CommonPongC2SPacket; +import net.minecraft.server.network.ServerCommonNetworkHandler; + +import net.fabricmc.fabric.impl.networking.NetworkHandlerExtensions; +import net.fabricmc.fabric.impl.networking.payload.PacketByteBufPayload; +import net.fabricmc.fabric.impl.networking.server.ServerConfigurationNetworkAddon; +import net.fabricmc.fabric.impl.networking.server.ServerPlayNetworkAddon; + +@Mixin(ServerCommonNetworkHandler.class) +public abstract class ServerCommonNetworkHandlerMixin implements NetworkHandlerExtensions { + @Inject(method = "onCustomPayload", at = @At("HEAD"), cancellable = true) + private void handleCustomPayloadReceivedAsync(CustomPayloadC2SPacket packet, CallbackInfo ci) { + if (packet.payload() instanceof PacketByteBufPayload payload) { + boolean handled; + + if (getAddon() instanceof ServerPlayNetworkAddon addon) { + handled = addon.handle(payload); + } else if (getAddon() instanceof ServerConfigurationNetworkAddon addon) { + handled = addon.handle(payload); + } else { + throw new IllegalStateException("Unknown addon"); + } + + if (handled) { + ci.cancel(); + } else { + payload.data().skipBytes(payload.data().readableBytes()); + } + } + } + + @Inject(method = "onPong", at = @At("HEAD")) + private void onPlayPong(CommonPongC2SPacket packet, CallbackInfo ci) { + if (getAddon() instanceof ServerConfigurationNetworkAddon addon) { + addon.onPong(packet.getParameter()); + } + } +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/ServerConfigurationNetworkHandlerMixin.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/ServerConfigurationNetworkHandlerMixin.java new file mode 100644 index 0000000000..c68f317e26 --- /dev/null +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/ServerConfigurationNetworkHandlerMixin.java @@ -0,0 +1,180 @@ +/* + * 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 net.fabricmc.fabric.mixin.networking; + +import java.util.Queue; + +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import net.minecraft.network.ClientConnection; +import net.minecraft.network.packet.Packet; +import net.minecraft.network.packet.s2c.common.DisconnectS2CPacket; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ConnectedClientData; +import net.minecraft.server.network.ServerCommonNetworkHandler; +import net.minecraft.server.network.ServerConfigurationNetworkHandler; +import net.minecraft.server.network.ServerPlayerConfigurationTask; +import net.minecraft.text.Text; + +import net.fabricmc.fabric.api.networking.v1.FabricServerConfigurationNetworkHandler; +import net.fabricmc.fabric.impl.networking.DisconnectPacketSource; +import net.fabricmc.fabric.impl.networking.NetworkHandlerExtensions; +import net.fabricmc.fabric.impl.networking.server.ServerConfigurationNetworkAddon; + +// We want to apply a bit earlier than other mods which may not use us in order to prevent refCount issues +@Mixin(value = ServerConfigurationNetworkHandler.class, priority = 900) +public abstract class ServerConfigurationNetworkHandlerMixin extends ServerCommonNetworkHandler implements NetworkHandlerExtensions, DisconnectPacketSource, FabricServerConfigurationNetworkHandler { + @Shadow + @Nullable + private ServerPlayerConfigurationTask currentTask; + + @Shadow + protected abstract void onTaskFinished(ServerPlayerConfigurationTask.Key key); + + @Shadow + @Final + private Queue tasks; + + @Shadow + public abstract boolean isConnectionOpen(); + + @Shadow + public abstract void sendConfigurations(); + + @Unique + private ServerConfigurationNetworkAddon addon; + + @Unique + private boolean sentConfiguration; + + @Unique + private boolean earlyTaskExecution; + + public ServerConfigurationNetworkHandlerMixin(MinecraftServer server, ClientConnection connection, ConnectedClientData arg) { + super(server, connection, arg); + } + + @Inject(method = "", at = @At("RETURN")) + private void initAddon(CallbackInfo ci) { + this.addon = new ServerConfigurationNetworkAddon((ServerConfigurationNetworkHandler) (Object) this, this.server); + // A bit of a hack but it allows the field above to be set in case someone registers handlers during INIT event which refers to said field + this.addon.lateInit(); + } + + @Inject(method = "sendConfigurations", at = @At("HEAD"), cancellable = true) + private void onClientReady(CallbackInfo ci) { + // Send the initial channel registration packet + if (this.addon.startConfiguration()) { + assert currentTask == null; + ci.cancel(); + return; + } + + // Ready to start sending packets + if (!sentConfiguration) { + this.addon.preConfiguration(); + sentConfiguration = true; + earlyTaskExecution = true; + } + + // Run the early tasks + if (earlyTaskExecution) { + if (pollEarlyTasks()) { + ci.cancel(); + return; + } else { + earlyTaskExecution = false; + } + } + + // All early tasks should have been completed + assert currentTask == null; + assert tasks.isEmpty(); + + // Run the vanilla tasks. + this.addon.configuration(); + } + + @Unique + private boolean pollEarlyTasks() { + if (!earlyTaskExecution) { + throw new IllegalStateException("Early task execution has finished"); + } + + if (this.currentTask != null) { + throw new IllegalStateException("Task " + this.currentTask.getKey().id() + " has not finished yet"); + } + + if (!this.isConnectionOpen()) { + return false; + } + + final ServerPlayerConfigurationTask task = this.tasks.poll(); + + if (task != null) { + this.currentTask = task; + task.sendPacket(this::sendPacket); + return true; + } + + return false; + } + + @Inject(method = "onDisconnected", at = @At("HEAD")) + private void handleDisconnection(Text reason, CallbackInfo ci) { + this.addon.handleDisconnect(); + } + + @Override + public ServerConfigurationNetworkAddon getAddon() { + return addon; + } + + @Override + public Packet createDisconnectPacket(Text message) { + return new DisconnectS2CPacket(message); + } + + @Override + public void addTask(ServerPlayerConfigurationTask task) { + tasks.add(task); + } + + @Override + public void completeTask(ServerPlayerConfigurationTask.Key key) { + if (!earlyTaskExecution) { + onTaskFinished(key); + return; + } + + final ServerPlayerConfigurationTask.Key currentKey = this.currentTask != null ? this.currentTask.getKey() : null; + + if (!key.equals(currentKey)) { + throw new IllegalStateException("Unexpected request for task finish, current task: " + currentKey + ", requested: " + key); + } + + this.currentTask = null; + sendConfigurations(); + } +} diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/ServerLoginNetworkHandlerMixin.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/ServerLoginNetworkHandlerMixin.java index 2d141bcca3..6a7e7a2ad9 100644 --- a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/ServerLoginNetworkHandlerMixin.java +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/ServerLoginNetworkHandlerMixin.java @@ -16,6 +16,7 @@ package net.fabricmc.fabric.mixin.networking; +import com.mojang.authlib.GameProfile; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -31,12 +32,12 @@ import net.minecraft.network.packet.s2c.login.LoginQueryRequestS2CPacket; import net.minecraft.server.MinecraftServer; import net.minecraft.server.network.ServerLoginNetworkHandler; -import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.text.Text; import net.fabricmc.fabric.impl.networking.DisconnectPacketSource; import net.fabricmc.fabric.impl.networking.NetworkHandlerExtensions; import net.fabricmc.fabric.impl.networking.PacketCallbackListener; +import net.fabricmc.fabric.impl.networking.payload.PacketByteBufLoginQueryResponse; import net.fabricmc.fabric.impl.networking.server.ServerLoginNetworkAddon; @Mixin(ServerLoginNetworkHandler.class) @@ -46,7 +47,7 @@ abstract class ServerLoginNetworkHandlerMixin implements NetworkHandlerExtension private MinecraftServer server; @Shadow - public abstract void acceptPlayer(); + protected abstract void tickVerify(GameProfile profile); @Unique private ServerLoginNetworkAddon addon; @@ -56,11 +57,11 @@ private void initAddon(CallbackInfo ci) { this.addon = new ServerLoginNetworkAddon((ServerLoginNetworkHandler) (Object) this); } - @Redirect(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/ServerLoginNetworkHandler;acceptPlayer()V")) - private void handlePlayerJoin(ServerLoginNetworkHandler handler) { + @Redirect(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/ServerLoginNetworkHandler;tickVerify(Lcom/mojang/authlib/GameProfile;)V")) + private void handlePlayerJoin(ServerLoginNetworkHandler instance, GameProfile profile) { // Do not accept the player, thereby moving into play stage until all login futures being waited on are completed if (this.addon.queryTick()) { - this.acceptPlayer(); + this.tickVerify(profile); } } @@ -69,10 +70,14 @@ private void handleCustomPayloadReceivedAsync(LoginQueryResponseC2SPacket packet // Handle queries if (this.addon.handle(packet)) { ci.cancel(); + } else { + if (packet.response() instanceof PacketByteBufLoginQueryResponse response) { + response.data().skipBytes(response.data().readableBytes()); + } } } - @Redirect(method = "acceptPlayer", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;getNetworkCompressionThreshold()I", ordinal = 0)) + @Redirect(method = "tickVerify", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;getNetworkCompressionThreshold()I", ordinal = 0)) private int removeLateCompressionPacketSending(MinecraftServer server) { return -1; } @@ -82,9 +87,9 @@ private void handleDisconnection(Text reason, CallbackInfo ci) { this.addon.handleDisconnect(); } - @Inject(method = "addToServer", at = @At("HEAD")) - private void handlePlayTransitionNormal(ServerPlayerEntity player, CallbackInfo ci) { - this.addon.handlePlayTransition(); + @Inject(method = "sendSuccessPacket", at = @At("HEAD")) + private void handlePlayTransitionNormal(GameProfile profile, CallbackInfo ci) { + this.addon.handleConfigurationTransition(); } @Override diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/ServerPlayNetworkHandlerMixin.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/ServerPlayNetworkHandlerMixin.java index 675ede32b7..a24ae9064f 100644 --- a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/ServerPlayNetworkHandlerMixin.java +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/ServerPlayNetworkHandlerMixin.java @@ -16,9 +16,7 @@ package net.fabricmc.fabric.mixin.networking; -import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -26,9 +24,10 @@ import net.minecraft.network.ClientConnection; import net.minecraft.network.packet.Packet; -import net.minecraft.network.packet.c2s.play.CustomPayloadC2SPacket; -import net.minecraft.network.packet.s2c.play.DisconnectS2CPacket; +import net.minecraft.network.packet.s2c.common.DisconnectS2CPacket; import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ConnectedClientData; +import net.minecraft.server.network.ServerCommonNetworkHandler; import net.minecraft.server.network.ServerPlayNetworkHandler; import net.minecraft.text.Text; @@ -38,17 +37,14 @@ // We want to apply a bit earlier than other mods which may not use us in order to prevent refCount issues @Mixin(value = ServerPlayNetworkHandler.class, priority = 999) -abstract class ServerPlayNetworkHandlerMixin implements NetworkHandlerExtensions, DisconnectPacketSource { - @Shadow - @Final - private MinecraftServer server; - @Shadow - @Final - public ClientConnection connection; - +abstract class ServerPlayNetworkHandlerMixin extends ServerCommonNetworkHandler implements NetworkHandlerExtensions, DisconnectPacketSource { @Unique private ServerPlayNetworkAddon addon; + ServerPlayNetworkHandlerMixin(MinecraftServer server, ClientConnection connection, ConnectedClientData arg) { + super(server, connection, arg); + } + @Inject(method = "", at = @At("RETURN")) private void initAddon(CallbackInfo ci) { this.addon = new ServerPlayNetworkAddon((ServerPlayNetworkHandler) (Object) this, this.server); @@ -56,13 +52,6 @@ private void initAddon(CallbackInfo ci) { this.addon.lateInit(); } - @Inject(method = "onCustomPayload", at = @At("HEAD"), cancellable = true) - private void handleCustomPayloadReceivedAsync(CustomPayloadC2SPacket packet, CallbackInfo ci) { - if (this.addon.handle(packet)) { - ci.cancel(); - } - } - @Inject(method = "onDisconnected", at = @At("HEAD")) private void handleDisconnection(Text reason, CallbackInfo ci) { this.addon.handleDisconnect(); diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/accessor/EntityTrackerAccessor.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/accessor/EntityTrackerAccessor.java index f27705c528..d3c472897a 100644 --- a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/accessor/EntityTrackerAccessor.java +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/accessor/EntityTrackerAccessor.java @@ -21,10 +21,10 @@ import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Accessor; -import net.minecraft.server.world.EntityTrackingListener; +import net.minecraft.server.network.PlayerAssociatedNetworkHandler; @Mixin(targets = "net/minecraft/server/world/ThreadedAnvilChunkStorage$EntityTracker") public interface EntityTrackerAccessor { @Accessor("listeners") - Set getPlayersTracking(); + Set getPlayersTracking(); } diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/accessor/CustomPayloadC2SPacketAccessor.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/accessor/ServerCommonNetworkHandlerAccessor.java similarity index 71% rename from fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/accessor/CustomPayloadC2SPacketAccessor.java rename to fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/accessor/ServerCommonNetworkHandlerAccessor.java index 219d77d5e2..23012e3c84 100644 --- a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/accessor/CustomPayloadC2SPacketAccessor.java +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/mixin/networking/accessor/ServerCommonNetworkHandlerAccessor.java @@ -19,15 +19,15 @@ import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Accessor; -import net.minecraft.network.PacketByteBuf; -import net.minecraft.network.packet.c2s.play.CustomPayloadC2SPacket; -import net.minecraft.util.Identifier; +import net.minecraft.network.ClientConnection; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerCommonNetworkHandler; -@Mixin(CustomPayloadC2SPacket.class) -public interface CustomPayloadC2SPacketAccessor { +@Mixin(ServerCommonNetworkHandler.class) +public interface ServerCommonNetworkHandlerAccessor { @Accessor - Identifier getChannel(); + ClientConnection getConnection(); @Accessor - PacketByteBuf getData(); + MinecraftServer getServer(); } diff --git a/fabric-networking-api-v1/src/main/resources/fabric-networking-api-v1.accesswidener b/fabric-networking-api-v1/src/main/resources/fabric-networking-api-v1.accesswidener new file mode 100644 index 0000000000..0c67f5c9a1 --- /dev/null +++ b/fabric-networking-api-v1/src/main/resources/fabric-networking-api-v1.accesswidener @@ -0,0 +1,3 @@ +accessWidener v2 named + +accessible class net/minecraft/network/NetworkState$InternalPacketHandler diff --git a/fabric-networking-api-v1/src/main/resources/fabric-networking-api-v1.mixins.json b/fabric-networking-api-v1/src/main/resources/fabric-networking-api-v1.mixins.json index 8f71d23272..b8c4ff7cbb 100644 --- a/fabric-networking-api-v1/src/main/resources/fabric-networking-api-v1.mixins.json +++ b/fabric-networking-api-v1/src/main/resources/fabric-networking-api-v1.mixins.json @@ -4,14 +4,19 @@ "compatibilityLevel": "JAVA_16", "mixins": [ "ClientConnectionMixin", + "CommandManagerMixin", + "CustomPayloadC2SPacketMixin", + "CustomPayloadS2CPacketMixin", "EntityTrackerEntryMixin", + "LoginQueryRequestS2CPacketMixin", + "LoginQueryResponseC2SPacketMixin", "PlayerManagerMixin", + "ServerCommonNetworkHandlerMixin", + "ServerConfigurationNetworkHandlerMixin", "ServerLoginNetworkHandlerMixin", "ServerPlayNetworkHandlerMixin", - "accessor.CustomPayloadC2SPacketAccessor", "accessor.EntityTrackerAccessor", - "accessor.LoginQueryResponseC2SPacketAccessor", - "accessor.ServerPlayNetworkHandlerAccessor", + "accessor.ServerCommonNetworkHandlerAccessor", "accessor.ServerLoginNetworkHandlerAccessor", "accessor.ThreadedAnvilChunkStorageAccessor" ], diff --git a/fabric-networking-api-v1/src/main/resources/fabric.mod.json b/fabric-networking-api-v1/src/main/resources/fabric.mod.json index cd672bf788..abebc330c2 100644 --- a/fabric-networking-api-v1/src/main/resources/fabric.mod.json +++ b/fabric-networking-api-v1/src/main/resources/fabric.mod.json @@ -17,12 +17,13 @@ ], "entrypoints": { "main": [ - "net.fabricmc.fabric.impl.networking.NetworkingImpl::init" + "net.fabricmc.fabric.impl.networking.CommonPacketsImpl::init" ], "client": [ "net.fabricmc.fabric.impl.networking.client.ClientNetworkingImpl::clientInit" ] }, + "accessWidener": "fabric-networking-api-v1.accesswidener", "depends": { "fabricloader": ">=0.4.0", "fabric-api-base": "*" @@ -36,6 +37,9 @@ } ], "custom": { - "fabric-api:module-lifecycle": "stable" + "fabric-api:module-lifecycle": "stable", + "loom:injected_interfaces": { + "net/minecraft/class_8610": [ "net/fabricmc/fabric/api/networking/v1/FabricServerConfigurationNetworkHandler" ] + } } } diff --git a/fabric-networking-api-v1/src/test/java/net/fabricmc/fabric/test/networking/unit/CommonPacketTests.java b/fabric-networking-api-v1/src/test/java/net/fabricmc/fabric/test/networking/unit/CommonPacketTests.java new file mode 100644 index 0000000000..d3868a642b --- /dev/null +++ b/fabric-networking-api-v1/src/test/java/net/fabricmc/fabric/test/networking/unit/CommonPacketTests.java @@ -0,0 +1,335 @@ +/* + * 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 net.fabricmc.fabric.test.networking.unit; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertIterableEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import net.minecraft.client.network.ClientConfigurationNetworkHandler; +import net.minecraft.network.NetworkState; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.network.packet.CustomPayload; +import net.minecraft.server.network.ServerConfigurationNetworkHandler; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationNetworking; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; +import net.fabricmc.fabric.api.networking.v1.PacketSender; +import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking; +import net.fabricmc.fabric.impl.networking.ChannelInfoHolder; +import net.fabricmc.fabric.impl.networking.CommonPacketHandler; +import net.fabricmc.fabric.impl.networking.CommonPacketsImpl; +import net.fabricmc.fabric.impl.networking.CommonRegisterPayload; +import net.fabricmc.fabric.impl.networking.CommonVersionPayload; +import net.fabricmc.fabric.impl.networking.client.ClientConfigurationNetworkAddon; +import net.fabricmc.fabric.impl.networking.client.ClientNetworkingImpl; +import net.fabricmc.fabric.impl.networking.server.ServerConfigurationNetworkAddon; +import net.fabricmc.fabric.impl.networking.server.ServerNetworkingImpl; + +public class CommonPacketTests { + private PacketSender packetSender; + private ChannelInfoHolder channelInfoHolder; + + private ClientConfigurationNetworkHandler clientNetworkHandler; + private ClientConfigurationNetworkAddon clientAddon; + + private ServerConfigurationNetworkHandler serverNetworkHandler; + private ServerConfigurationNetworkAddon serverAddon; + + @BeforeAll + static void beforeAll() { + CommonPacketsImpl.init(); + ClientNetworkingImpl.clientInit(); + + // Register a receiver to send in the play registry response + ClientPlayNetworking.registerGlobalReceiver(new Identifier("fabric", "global_client"), (client, handler, buf, responseSender) -> { + }); + } + + @BeforeEach + void setUp() { + packetSender = mock(PacketSender.class); + channelInfoHolder = new MockChannelInfoHolder(); + + clientNetworkHandler = mock(ClientConfigurationNetworkHandler.class); + clientAddon = mock(ClientConfigurationNetworkAddon.class); + when(ClientNetworkingImpl.getAddon(clientNetworkHandler)).thenReturn(clientAddon); + when(clientAddon.getChannelInfoHolder()).thenReturn(channelInfoHolder); + + serverNetworkHandler = mock(ServerConfigurationNetworkHandler.class); + serverAddon = mock(ServerConfigurationNetworkAddon.class); + when(ServerNetworkingImpl.getAddon(serverNetworkHandler)).thenReturn(serverAddon); + when(serverAddon.getChannelInfoHolder()).thenReturn(channelInfoHolder); + } + + // Test handling the version packet on the client + @Test + void handleVersionPacketClient() { + ClientConfigurationNetworking.ConfigurationChannelHandler packetHandler = ClientNetworkingImpl.CONFIGURATION.getHandler(CommonVersionPayload.PACKET_ID); + assertNotNull(packetHandler); + + // Receive a packet from the server + PacketByteBuf buf = PacketByteBufs.create(); + buf.writeIntArray(new int[]{1, 2, 3}); + + packetHandler.receive(null, clientNetworkHandler, buf, packetSender); + + // Assert the entire packet was read + assertEquals(0, buf.readableBytes()); + + // Check the response we are sending back to the server + PacketByteBuf response = readResponse(packetSender); + assertArrayEquals(new int[]{1}, response.readIntArray()); + assertEquals(0, response.readableBytes()); + + assertEquals(1, getNegotiatedVersion(clientAddon)); + } + + // Test handling the version packet on the client, when the server sends unsupported versions + @Test + void handleVersionPacketClientUnsupported() { + ClientConfigurationNetworking.ConfigurationChannelHandler packetHandler = ClientNetworkingImpl.CONFIGURATION.getHandler(CommonVersionPayload.PACKET_ID); + assertNotNull(packetHandler); + + // Receive a packet from the server + PacketByteBuf buf = PacketByteBufs.create(); + buf.writeIntArray(new int[]{2, 3}); // We only support version 1 + + assertThrows(UnsupportedOperationException.class, () -> { + packetHandler.receive(null, clientNetworkHandler, buf, packetSender); + }); + + // Assert the entire packet was read + assertEquals(0, buf.readableBytes()); + } + + // Test handling the version packet on the server + @Test + void handleVersionPacketServer() { + ServerConfigurationNetworking.ConfigurationChannelHandler packetHandler = ServerNetworkingImpl.CONFIGURATION.getHandler(CommonVersionPayload.PACKET_ID); + assertNotNull(packetHandler); + + // Receive a packet from the client + PacketByteBuf buf = PacketByteBufs.create(); + buf.writeIntArray(new int[]{1, 2, 3}); + + packetHandler.receive(null, serverNetworkHandler, buf, null); + + // Assert the entire packet was read + assertEquals(0, buf.readableBytes()); + assertEquals(1, getNegotiatedVersion(serverAddon)); + } + + // Test handling the version packet on the server unsupported version + @Test + void handleVersionPacketServerUnsupported() { + ServerConfigurationNetworking.ConfigurationChannelHandler packetHandler = ServerNetworkingImpl.CONFIGURATION.getHandler(CommonVersionPayload.PACKET_ID); + assertNotNull(packetHandler); + + // Receive a packet from the client + PacketByteBuf buf = PacketByteBufs.create(); + buf.writeIntArray(new int[]{3}); // Server only supports version 1 + + assertThrows(UnsupportedOperationException.class, () -> { + packetHandler.receive(null, serverNetworkHandler, buf, packetSender); + }); + + // Assert the entire packet was read + assertEquals(0, buf.readableBytes()); + } + + // Test handing the play registry packet on the client configuration handler + @Test + void handlePlayRegistryClient() { + ClientConfigurationNetworking.ConfigurationChannelHandler packetHandler = ClientNetworkingImpl.CONFIGURATION.getHandler(CommonRegisterPayload.PACKET_ID); + assertNotNull(packetHandler); + + when(clientAddon.getNegotiatedVersion()).thenReturn(1); + + // Receive a packet from the server + PacketByteBuf buf = PacketByteBufs.create(); + buf.writeVarInt(1); // Version + buf.writeString("play"); // Target phase + buf.writeCollection(List.of(new Identifier("fabric", "test")), PacketByteBuf::writeIdentifier); + + packetHandler.receive(null, clientNetworkHandler, buf, packetSender); + + // Assert the entire packet was read + assertEquals(0, buf.readableBytes()); + assertIterableEquals(List.of(new Identifier("fabric", "test")), channelInfoHolder.getPendingChannelsNames(NetworkState.PLAY)); + + // Check the response we are sending back to the server + PacketByteBuf response = readResponse(packetSender); + assertEquals(1, response.readVarInt()); + assertEquals("play", response.readString()); + assertIterableEquals(List.of(new Identifier("fabric", "global_client")), response.readCollection(HashSet::new, PacketByteBuf::readIdentifier)); + assertEquals(0, response.readableBytes()); + } + + // Test handling the configuration registry packet on the client configuration handler + @Test + void handleConfigurationRegistryClient() { + ClientConfigurationNetworking.ConfigurationChannelHandler packetHandler = ClientNetworkingImpl.CONFIGURATION.getHandler(CommonRegisterPayload.PACKET_ID); + assertNotNull(packetHandler); + + when(clientAddon.getNegotiatedVersion()).thenReturn(1); + when(clientAddon.createRegisterPayload()).thenAnswer(i -> new CommonRegisterPayload(1, "configuration", Set.of(new Identifier("fabric", "global_configuration_client")))); + + // Receive a packet from the server + PacketByteBuf buf = PacketByteBufs.create(); + buf.writeVarInt(1); // Version + buf.writeString("configuration"); // Target phase + buf.writeCollection(List.of(new Identifier("fabric", "test")), PacketByteBuf::writeIdentifier); + + packetHandler.receive(null, clientNetworkHandler, buf, packetSender); + + // Assert the entire packet was read + assertEquals(0, buf.readableBytes()); + verify(clientAddon, times(1)).onCommonRegisterPacket(any()); + + // Check the response we are sending back to the server + PacketByteBuf response = readResponse(packetSender); + assertEquals(1, response.readVarInt()); + assertEquals("configuration", response.readString()); + assertIterableEquals(List.of(new Identifier("fabric", "global_configuration_client")), response.readCollection(HashSet::new, PacketByteBuf::readIdentifier)); + assertEquals(0, response.readableBytes()); + } + + // Test handing the play registry packet on the server configuration handler + @Test + void handlePlayRegistryServer() { + ServerConfigurationNetworking.ConfigurationChannelHandler packetHandler = ServerNetworkingImpl.CONFIGURATION.getHandler(CommonRegisterPayload.PACKET_ID); + assertNotNull(packetHandler); + + when(serverAddon.getNegotiatedVersion()).thenReturn(1); + + // Receive a packet from the client + PacketByteBuf buf = PacketByteBufs.create(); + buf.writeVarInt(1); // Version + buf.writeString("play"); // Target phase + buf.writeCollection(List.of(new Identifier("fabric", "test")), PacketByteBuf::writeIdentifier); + + packetHandler.receive(null, serverNetworkHandler, buf, packetSender); + + // Assert the entire packet was read + assertEquals(0, buf.readableBytes()); + assertIterableEquals(List.of(new Identifier("fabric", "test")), channelInfoHolder.getPendingChannelsNames(NetworkState.PLAY)); + } + + // Test handing the configuration registry packet on the server configuration handler + @Test + void handleConfigurationRegistryServer() { + ServerConfigurationNetworking.ConfigurationChannelHandler packetHandler = ServerNetworkingImpl.CONFIGURATION.getHandler(CommonRegisterPayload.PACKET_ID); + assertNotNull(packetHandler); + + when(serverAddon.getNegotiatedVersion()).thenReturn(1); + + // Receive a packet from the client + PacketByteBuf buf = PacketByteBufs.create(); + buf.writeVarInt(1); // Version + buf.writeString("configuration"); // Target phase + buf.writeCollection(List.of(new Identifier("fabric", "test")), PacketByteBuf::writeIdentifier); + + packetHandler.receive(null, serverNetworkHandler, buf, packetSender); + + // Assert the entire packet was read + assertEquals(0, buf.readableBytes()); + verify(serverAddon, times(1)).onCommonRegisterPacket(any()); + } + + @Test + public void testHighestCommonVersionWithCommonElement() { + int[] a = {1, 2, 3}; + int[] b = {1, 2}; + assertEquals(2, CommonPacketsImpl.getHighestCommonVersion(a, b)); + } + + @Test + public void testHighestCommonVersionWithoutCommonElement() { + int[] a = {1, 3, 5}; + int[] b = {2, 4, 6}; + assertEquals(-1, CommonPacketsImpl.getHighestCommonVersion(a, b)); + } + + @Test + public void testHighestCommonVersionWithOneEmptyArray() { + int[] a = {1, 3, 5}; + int[] b = {}; + assertEquals(-1, CommonPacketsImpl.getHighestCommonVersion(a, b)); + } + + @Test + public void testHighestCommonVersionWithBothEmptyArrays() { + int[] a = {}; + int[] b = {}; + assertEquals(-1, CommonPacketsImpl.getHighestCommonVersion(a, b)); + } + + @Test + public void testHighestCommonVersionWithIdenticalArrays() { + int[] a = {1, 2, 3}; + int[] b = {1, 2, 3}; + assertEquals(3, CommonPacketsImpl.getHighestCommonVersion(a, b)); + } + + private static PacketByteBuf readResponse(PacketSender packetSender) { + ArgumentCaptor responseCaptor = ArgumentCaptor.forClass(CustomPayload.class); + verify(packetSender, times(1)).sendPacket(responseCaptor.capture()); + + PacketByteBuf buf = PacketByteBufs.create(); + responseCaptor.getValue().write(buf); + + return buf; + } + + private static int getNegotiatedVersion(CommonPacketHandler packetHandler) { + ArgumentCaptor responseCaptor = ArgumentCaptor.forClass(Integer.class); + verify(packetHandler, times(1)).onCommonVersionPacket(responseCaptor.capture()); + return responseCaptor.getValue(); + } + + private static class MockChannelInfoHolder implements ChannelInfoHolder { + private final Map> playChannels = new ConcurrentHashMap<>(); + + @Override + public Collection getPendingChannelsNames(NetworkState state) { + return this.playChannels.computeIfAbsent(state, (key) -> Collections.newSetFromMap(new ConcurrentHashMap<>())); + } + } +} diff --git a/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/configuration/NetworkingConfigurationTest.java b/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/configuration/NetworkingConfigurationTest.java new file mode 100644 index 0000000000..01313569c4 --- /dev/null +++ b/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/configuration/NetworkingConfigurationTest.java @@ -0,0 +1,104 @@ +/* + * 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 net.fabricmc.fabric.test.networking.configuration; + +import java.util.function.Consumer; + +import net.minecraft.network.PacketByteBuf; +import net.minecraft.network.packet.Packet; +import net.minecraft.server.network.ServerPlayerConfigurationTask; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; + +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.networking.v1.FabricPacket; +import net.fabricmc.fabric.api.networking.v1.PacketType; +import net.fabricmc.fabric.api.networking.v1.ServerConfigurationConnectionEvents; +import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking; +import net.fabricmc.fabric.test.networking.NetworkingTestmods; + +/** + * Also see NetworkingConfigurationClientTest. + */ +public class NetworkingConfigurationTest implements ModInitializer { + @Override + public void onInitialize() { + ServerConfigurationConnectionEvents.CONFIGURE.register((handler, server) -> { + // You must check to see if the client can handle your config task + if (ServerConfigurationNetworking.canSend(handler, ConfigurationPacket.PACKET_TYPE)) { + handler.addTask(new TestConfigurationTask("Example data")); + } else { + // You can opt to disconnect the client if it cannot handle the configuration task + handler.disconnect(Text.literal("Network test configuration not supported by client")); + } + }); + + ServerConfigurationNetworking.registerGlobalReceiver(ConfigurationCompletePacket.PACKET_TYPE, (packet, networkHandler, responseSender) -> { + networkHandler.completeTask(TestConfigurationTask.KEY); + }); + } + + public record TestConfigurationTask(String data) implements ServerPlayerConfigurationTask { + public static final Key KEY = new Key(new Identifier(NetworkingTestmods.ID, "configure").toString()); + + @Override + public void sendPacket(Consumer> sender) { + var packet = new ConfigurationPacket(data); + sender.accept(ServerConfigurationNetworking.createS2CPacket(packet)); + } + + @Override + public Key getKey() { + return KEY; + } + } + + public record ConfigurationPacket(String data) implements FabricPacket { + public static final PacketType PACKET_TYPE = PacketType.create(new Identifier(NetworkingTestmods.ID, "configure"), ConfigurationPacket::new); + + public ConfigurationPacket(PacketByteBuf buf) { + this(buf.readString()); + } + + @Override + public void write(PacketByteBuf buf) { + buf.writeString(data); + } + + @Override + public PacketType getType() { + return PACKET_TYPE; + } + } + + public record ConfigurationCompletePacket() implements FabricPacket { + public static final PacketType PACKET_TYPE = PacketType.create(new Identifier(NetworkingTestmods.ID, "configure_complete"), ConfigurationCompletePacket::new); + + public ConfigurationCompletePacket(PacketByteBuf buf) { + this(); + } + + @Override + public void write(PacketByteBuf buf) { + } + + @Override + public PacketType getType() { + return PACKET_TYPE; + } + } +} diff --git a/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/play/NetworkingPlayPacketTest.java b/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/play/NetworkingPlayPacketTest.java index 52da7a1db6..87ff660824 100644 --- a/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/play/NetworkingPlayPacketTest.java +++ b/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/play/NetworkingPlayPacketTest.java @@ -27,6 +27,8 @@ import com.mojang.brigadier.arguments.StringArgumentType; import net.minecraft.network.PacketByteBuf; +import net.minecraft.network.listener.ClientPlayPacketListener; +import net.minecraft.network.packet.Packet; import net.minecraft.network.packet.s2c.play.BundleS2CPacket; import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.network.ServerPlayerEntity; @@ -43,10 +45,16 @@ public final class NetworkingPlayPacketTest implements ModInitializer { public static final Identifier TEST_CHANNEL = NetworkingTestmods.id("test_channel"); + private static final Identifier UNKNOWN_TEST_CHANNEL = NetworkingTestmods.id("unknown_test_channel"); public static void sendToTestChannel(ServerPlayerEntity player, String stuff) { - ServerPlayNetworking.send(player, new OverlayPacket(Text.literal(stuff))); - NetworkingTestmods.LOGGER.info("Sent custom payload packet in {}", TEST_CHANNEL); + ServerPlayNetworking.getSender(player).sendPacket(new OverlayPacket(Text.literal(stuff)), future -> { + NetworkingTestmods.LOGGER.info("Sent custom payload packet in {}", TEST_CHANNEL); + }); + } + + private static void sendToUnknownChannel(ServerPlayerEntity player) { + ServerPlayNetworking.getSender(player).sendPacket(UNKNOWN_TEST_CHANNEL, PacketByteBufs.create()); } public static void registerCommand(CommandDispatcher dispatcher) { @@ -58,13 +66,17 @@ public static void registerCommand(CommandDispatcher dispat sendToTestChannel(ctx.getSource().getPlayer(), stuff); return Command.SINGLE_SUCCESS; })) + .then(literal("unknown").executes(ctx -> { + sendToUnknownChannel(ctx.getSource().getPlayer()); + return Command.SINGLE_SUCCESS; + })) .then(literal("bundled").executes(ctx -> { PacketByteBuf buf1 = PacketByteBufs.create(); buf1.writeText(Text.literal("bundled #1")); PacketByteBuf buf2 = PacketByteBufs.create(); buf2.writeText(Text.literal("bundled #2")); - BundleS2CPacket packet = new BundleS2CPacket(List.of( + BundleS2CPacket packet = new BundleS2CPacket((List>) (Object) List.of( ServerPlayNetworking.createS2CPacket(TEST_CHANNEL, buf1), ServerPlayNetworking.createS2CPacket(TEST_CHANNEL, buf2))); ctx.getSource().getPlayer().networkHandler.sendPacket(packet); diff --git a/fabric-networking-api-v1/src/testmod/resources/fabric.mod.json b/fabric-networking-api-v1/src/testmod/resources/fabric.mod.json index 403abb9571..e5c57f24ad 100644 --- a/fabric-networking-api-v1/src/testmod/resources/fabric.mod.json +++ b/fabric-networking-api-v1/src/testmod/resources/fabric.mod.json @@ -11,12 +11,14 @@ "entrypoints": { "main": [ "net.fabricmc.fabric.test.networking.channeltest.NetworkingChannelTest", + "net.fabricmc.fabric.test.networking.configuration.NetworkingConfigurationTest", "net.fabricmc.fabric.test.networking.keybindreciever.NetworkingKeybindPacketTest", "net.fabricmc.fabric.test.networking.login.NetworkingLoginQueryTest", "net.fabricmc.fabric.test.networking.play.NetworkingPlayPacketTest" ], "client": [ "net.fabricmc.fabric.test.networking.client.channeltest.NetworkingChannelClientTest", + "net.fabricmc.fabric.test.networking.client.configuration.NetworkingConfigurationClientTest", "net.fabricmc.fabric.test.networking.client.DisconnectScreenTest", "net.fabricmc.fabric.test.networking.client.keybindreciever.NetworkingKeybindClientPacketTest", "net.fabricmc.fabric.test.networking.client.login.NetworkingLoginQueryClientTest", diff --git a/deprecated/fabric-networking-v0/src/client/java/net/fabricmc/fabric/impl/client/networking/v0/OldClientNetworkingHooks.java b/fabric-networking-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/networking/client/configuration/NetworkingConfigurationClientTest.java similarity index 52% rename from deprecated/fabric-networking-v0/src/client/java/net/fabricmc/fabric/impl/client/networking/v0/OldClientNetworkingHooks.java rename to fabric-networking-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/networking/client/configuration/NetworkingConfigurationClientTest.java index 38259016e5..3a1d4acb13 100644 --- a/deprecated/fabric-networking-v0/src/client/java/net/fabricmc/fabric/impl/client/networking/v0/OldClientNetworkingHooks.java +++ b/fabric-networking-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/networking/client/configuration/NetworkingConfigurationClientTest.java @@ -14,17 +14,20 @@ * limitations under the License. */ -package net.fabricmc.fabric.impl.client.networking.v0; +package net.fabricmc.fabric.test.networking.client.configuration; import net.fabricmc.api.ClientModInitializer; -import net.fabricmc.fabric.api.event.network.S2CPacketTypeCallback; -import net.fabricmc.fabric.api.client.networking.v1.C2SPlayChannelEvents; +import net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationNetworking; +import net.fabricmc.fabric.test.networking.configuration.NetworkingConfigurationTest; -public final class OldClientNetworkingHooks implements ClientModInitializer { +public class NetworkingConfigurationClientTest implements ClientModInitializer { @Override public void onInitializeClient() { - // Must be lambdas below - C2SPlayChannelEvents.REGISTER.register((handler, client, sender, channels) -> S2CPacketTypeCallback.REGISTERED.invoker().accept(channels)); - C2SPlayChannelEvents.UNREGISTER.register((handler, client, sender, channels) -> S2CPacketTypeCallback.UNREGISTERED.invoker().accept(channels)); + ClientConfigurationNetworking.registerGlobalReceiver(NetworkingConfigurationTest.ConfigurationPacket.PACKET_TYPE, (packet, responseSender) -> { + // Handle stuff here + + // Respond back to the server that the task is complete + responseSender.sendPacket(new NetworkingConfigurationTest.ConfigurationCompletePacket()); + }); } } diff --git a/fabric-networking-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/networking/client/play/NetworkingPlayPacketClientTest.java b/fabric-networking-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/networking/client/play/NetworkingPlayPacketClientTest.java index 0c0fb240ba..e87babc834 100644 --- a/fabric-networking-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/networking/client/play/NetworkingPlayPacketClientTest.java +++ b/fabric-networking-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/networking/client/play/NetworkingPlayPacketClientTest.java @@ -16,19 +16,36 @@ package net.fabricmc.fabric.test.networking.client.play; +import com.mojang.brigadier.Command; + import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.util.Identifier; import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager; +import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; import net.fabricmc.fabric.api.networking.v1.PacketSender; +import net.fabricmc.fabric.test.networking.NetworkingTestmods; import net.fabricmc.fabric.test.networking.play.NetworkingPlayPacketTest; public final class NetworkingPlayPacketClientTest implements ClientModInitializer, ClientPlayNetworking.PlayPacketHandler { + private static final Identifier UNKNOWN_TEST_CHANNEL = NetworkingTestmods.id("unknown_test_channel"); + @Override public void onInitializeClient() { ClientPlayConnectionEvents.INIT.register((handler, client) -> ClientPlayNetworking.registerReceiver(NetworkingPlayPacketTest.OverlayPacket.PACKET_TYPE, this)); + + ClientCommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> dispatcher.register( + ClientCommandManager.literal("clientnetworktestcommand") + .then(ClientCommandManager.literal("unknown").executes(context -> { + ClientPlayNetworking.send(UNKNOWN_TEST_CHANNEL, PacketByteBufs.create()); + return Command.SINGLE_SUCCESS; + } + )))); } @Override diff --git a/fabric-object-builder-api-v1/build.gradle b/fabric-object-builder-api-v1/build.gradle index 9d9976e472..ec00cd62de 100644 --- a/fabric-object-builder-api-v1/build.gradle +++ b/fabric-object-builder-api-v1/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-object-builder-api-v1" version = getSubprojectVersion(project) moduleDependencies(project, [ @@ -6,7 +5,10 @@ moduleDependencies(project, [ 'fabric-resource-loader-v0' ]) -testDependencies(project, [':fabric-command-api-v2']) +testDependencies(project, [ + ':fabric-command-api-v2', + ':fabric-lifecycle-events-v1' +]) loom { accessWidenerPath = file("src/main/resources/fabric-object-builder-api-v1.accesswidener") diff --git a/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/api/object/builder/v1/advancement/CriterionRegistry.java b/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/api/object/builder/v1/advancement/CriterionRegistry.java deleted file mode 100644 index 5116adb4d4..0000000000 --- a/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/api/object/builder/v1/advancement/CriterionRegistry.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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 net.fabricmc.fabric.api.object.builder.v1.advancement; - -import net.minecraft.advancement.criterion.Criterion; -import net.minecraft.util.Identifier; - -import net.fabricmc.fabric.mixin.object.builder.CriteriaAccessor; - -/** - * Allows registering advancement criteria for triggers. - * - *

    A registered criterion (trigger) can be retrieved through - * {@link net.minecraft.advancement.criterion.Criteria#getById(Identifier)}.

    - * - * @see net.minecraft.advancement.criterion.Criteria - * @deprecated Replaced by access widener for {@link net.minecraft.advancement.criterion.Criteria#register(Criterion)} - * in Fabric Transitive Access Wideners (v1). - */ -@Deprecated -public final class CriterionRegistry { - /** - * Registers a criterion for a trigger for advancements. - * - * @param the criterion's type - * @param criterion the criterion registered - * @return the criterion registered, for chaining - * @throws IllegalArgumentException if a criterion with the same {@link - * Criterion#getId() id} exists - */ - public static > T register(T criterion) { - CriteriaAccessor.callRegister(criterion); - return criterion; - } -} diff --git a/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/api/object/builder/v1/block/type/BlockSetTypeBuilder.java b/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/api/object/builder/v1/block/type/BlockSetTypeBuilder.java new file mode 100644 index 0000000000..04a60465b0 --- /dev/null +++ b/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/api/object/builder/v1/block/type/BlockSetTypeBuilder.java @@ -0,0 +1,235 @@ +/* + * 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 net.fabricmc.fabric.api.object.builder.v1.block.type; + +import net.minecraft.block.BlockSetType; +import net.minecraft.sound.BlockSoundGroup; +import net.minecraft.sound.SoundEvent; +import net.minecraft.sound.SoundEvents; +import net.minecraft.util.Identifier; + +/** + * This class allows easy creation of {@link BlockSetType}s. + * + *

    A {@link BlockSetType} is used to tell the game various properties of related blocks, such as what sounds they should use. + * + * @see WoodTypeBuilder + */ +public final class BlockSetTypeBuilder { + private boolean openableByHand = true; + private BlockSoundGroup soundGroup = BlockSoundGroup.WOOD; + private SoundEvent doorCloseSound = SoundEvents.BLOCK_WOODEN_DOOR_CLOSE; + private SoundEvent doorOpenSound = SoundEvents.BLOCK_WOODEN_DOOR_OPEN; + private SoundEvent trapdoorCloseSound = SoundEvents.BLOCK_WOODEN_TRAPDOOR_CLOSE; + private SoundEvent trapdoorOpenSound = SoundEvents.BLOCK_WOODEN_TRAPDOOR_OPEN; + private SoundEvent pressurePlateClickOffSound = SoundEvents.BLOCK_WOODEN_PRESSURE_PLATE_CLICK_OFF; + private SoundEvent pressurePlateClickOnSound = SoundEvents.BLOCK_WOODEN_PRESSURE_PLATE_CLICK_ON; + private SoundEvent buttonClickOffSound = SoundEvents.BLOCK_WOODEN_BUTTON_CLICK_OFF; + private SoundEvent buttonClickOnSound = SoundEvents.BLOCK_WOODEN_BUTTON_CLICK_ON; + + /** + * Sets whether this block set type's door and trapdoor can be opened by hand. + * + *

    Defaults to {@code true}. + * + * @return this builder for chaining + */ + public BlockSetTypeBuilder openableByHand(boolean openableByHand) { + this.openableByHand = openableByHand; + return this; + } + + /** + * Sets this block set type's sound group. + * + *

    Defaults to {@link BlockSoundGroup#WOOD}. + * + * @return this builder for chaining + */ + public BlockSetTypeBuilder soundGroup(BlockSoundGroup soundGroup) { + this.soundGroup = soundGroup; + return this; + } + + /** + * Sets this block set type's door close sound. + * + *

    Defaults to {@link SoundEvents#BLOCK_WOODEN_DOOR_CLOSE}. + * + * @return this builder for chaining + */ + public BlockSetTypeBuilder doorCloseSound(SoundEvent doorCloseSound) { + this.doorCloseSound = doorCloseSound; + return this; + } + + /** + * Sets this block set type's door open sound. + * + *

    Defaults to {@link SoundEvents#BLOCK_WOODEN_DOOR_OPEN}. + * + * @return this builder for chaining + */ + public BlockSetTypeBuilder doorOpenSound(SoundEvent doorOpenSound) { + this.doorOpenSound = doorOpenSound; + return this; + } + + /** + * Sets this block set type's trapdoor close sound. + * + *

    Defaults to {@link SoundEvents#BLOCK_WOODEN_TRAPDOOR_CLOSE}. + * + * @return this builder for chaining + */ + public BlockSetTypeBuilder trapdoorCloseSound(SoundEvent trapdoorCloseSound) { + this.trapdoorCloseSound = trapdoorCloseSound; + return this; + } + + /** + * Sets this block set type's trapdoor open sound. + * + *

    Defaults to {@link SoundEvents#BLOCK_WOODEN_TRAPDOOR_OPEN}. + * + * @return this builder for chaining + */ + public BlockSetTypeBuilder trapdoorOpenSound(SoundEvent trapdoorOpenSound) { + this.trapdoorOpenSound = trapdoorOpenSound; + return this; + } + + /** + * Sets this block set type's pressure plate click off sound. + * + *

    Defaults to {@link SoundEvents#BLOCK_WOODEN_PRESSURE_PLATE_CLICK_OFF}. + * + * @return this builder for chaining + */ + public BlockSetTypeBuilder pressurePlateClickOffSound(SoundEvent pressurePlateClickOffSound) { + this.pressurePlateClickOffSound = pressurePlateClickOffSound; + return this; + } + + /** + * Sets this block set type's pressure plate click on sound. + * + *

    Defaults to {@link SoundEvents#BLOCK_WOODEN_PRESSURE_PLATE_CLICK_ON}. + * + * @return this builder for chaining + */ + public BlockSetTypeBuilder pressurePlateClickOnSound(SoundEvent pressurePlateClickOnSound) { + this.pressurePlateClickOnSound = pressurePlateClickOnSound; + return this; + } + + /** + * Sets this block set type's button click off sound. + * + *

    Defaults to {@link SoundEvents#BLOCK_WOODEN_BUTTON_CLICK_OFF}. + * + * @return this builder for chaining + */ + public BlockSetTypeBuilder buttonClickOffSound(SoundEvent buttonClickOffSound) { + this.buttonClickOffSound = buttonClickOffSound; + return this; + } + + /** + * Sets this block set type's button click on sound. + * + *

    Defaults to {@link SoundEvents#BLOCK_WOODEN_BUTTON_CLICK_ON}. + * + * @return this builder for chaining + */ + public BlockSetTypeBuilder buttonClickOnSound(SoundEvent buttonClickOnSound) { + this.buttonClickOnSound = buttonClickOnSound; + return this; + } + + /** + * Creates a new {@link BlockSetTypeBuilder} that copies all of another builder's values. + * + * @param builder the {@link BlockSetTypeBuilder} whose values are to be copied + * + * @return the created copy + */ + public static BlockSetTypeBuilder copyOf(BlockSetTypeBuilder builder) { + BlockSetTypeBuilder copy = new BlockSetTypeBuilder(); + copy.openableByHand(builder.openableByHand); + copy.soundGroup(builder.soundGroup); + copy.doorCloseSound(builder.doorCloseSound); + copy.doorOpenSound(builder.doorOpenSound); + copy.trapdoorCloseSound(builder.trapdoorCloseSound); + copy.trapdoorOpenSound(builder.trapdoorOpenSound); + copy.pressurePlateClickOffSound(builder.pressurePlateClickOffSound); + copy.pressurePlateClickOnSound(builder.pressurePlateClickOnSound); + copy.buttonClickOffSound(builder.buttonClickOffSound); + copy.buttonClickOnSound(builder.buttonClickOnSound); + return copy; + } + + /** + * Creates a new {@link BlockSetTypeBuilder} that copies all of another block set type's values. + * + * @param setType the {@link BlockSetType} whose values are to be copied + * + * @return the created copy + */ + public static BlockSetTypeBuilder copyOf(BlockSetType setType) { + BlockSetTypeBuilder copy = new BlockSetTypeBuilder(); + copy.openableByHand(setType.canOpenByHand()); + copy.soundGroup(setType.soundType()); + copy.doorCloseSound(setType.doorClose()); + copy.doorOpenSound(setType.doorOpen()); + copy.trapdoorCloseSound(setType.trapdoorClose()); + copy.trapdoorOpenSound(setType.trapdoorOpen()); + copy.pressurePlateClickOffSound(setType.pressurePlateClickOff()); + copy.pressurePlateClickOnSound(setType.pressurePlateClickOn()); + copy.buttonClickOffSound(setType.buttonClickOff()); + copy.buttonClickOnSound(setType.buttonClickOn()); + return copy; + } + + /** + * Builds and registers a {@link BlockSetType} from this builder's values. + * + *

    Alternatively, you can use {@link #build(Identifier)} to build without registering. + *
    Then {@link BlockSetType#register(BlockSetType)} can be used to register it later. + * + * @param id the id for the built {@link BlockSetType} + * + * @return the built and registered {@link BlockSetType} + */ + public BlockSetType register(Identifier id) { + return BlockSetType.register(this.build(id)); + } + + /** + * Builds a {@link BlockSetType} from this builder's values without registering it. + * + *

    Use {@link BlockSetType#register(BlockSetType)} to register it later. + *
    Alternatively, you can use {@link #register(Identifier)} to build and register it now. + * + * @param id the id for the built {@link BlockSetType} + * + * @return the built {@link BlockSetType} + */ + public BlockSetType build(Identifier id) { + return new BlockSetType(id.toString(), openableByHand, soundGroup, doorCloseSound, doorOpenSound, trapdoorCloseSound, trapdoorOpenSound, pressurePlateClickOffSound, pressurePlateClickOnSound, buttonClickOffSound, buttonClickOnSound); + } +} diff --git a/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/api/object/builder/v1/block/type/BlockSetTypeRegistry.java b/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/api/object/builder/v1/block/type/BlockSetTypeRegistry.java index b92b04e963..8b7febedec 100644 --- a/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/api/object/builder/v1/block/type/BlockSetTypeRegistry.java +++ b/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/api/object/builder/v1/block/type/BlockSetTypeRegistry.java @@ -27,7 +27,9 @@ *

    A {@link BlockSetType} is used to tell the game what sounds various related blocks should use. * * @see WoodTypeRegistry + * @deprecated use {@link BlockSetTypeBuilder} */ +@Deprecated public final class BlockSetTypeRegistry { private BlockSetTypeRegistry() { } diff --git a/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/api/object/builder/v1/block/type/WoodTypeBuilder.java b/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/api/object/builder/v1/block/type/WoodTypeBuilder.java new file mode 100644 index 0000000000..550e388e30 --- /dev/null +++ b/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/api/object/builder/v1/block/type/WoodTypeBuilder.java @@ -0,0 +1,151 @@ +/* + * 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 net.fabricmc.fabric.api.object.builder.v1.block.type; + +import net.minecraft.block.BlockSetType; +import net.minecraft.block.WoodType; +import net.minecraft.sound.BlockSoundGroup; +import net.minecraft.sound.SoundEvent; +import net.minecraft.sound.SoundEvents; +import net.minecraft.util.Identifier; + +/** + * This class allows easy creation of {@link WoodType}s. + * + *

    A {@link WoodType} is used to tell the game what textures signs should use, as well as sounds for both signs and fence gates. + * + *

    Regular sign textures are stored at {@code [namespace]/textures/entity/signs/[path].png}. + *
    Hanging sign textures are stored at {@code [namespace]/textures/entity/signs/hanging/[path].png}. + * + * @see BlockSetTypeBuilder + */ +public final class WoodTypeBuilder { + private BlockSoundGroup soundGroup = BlockSoundGroup.WOOD; + private BlockSoundGroup hangingSignSoundGroup = BlockSoundGroup.HANGING_SIGN; + private SoundEvent fenceGateCloseSound = SoundEvents.BLOCK_FENCE_GATE_CLOSE; + private SoundEvent fenceGateOpenSound = SoundEvents.BLOCK_FENCE_GATE_OPEN; + + /** + * Sets this wood type's sound group. + * + *

    Defaults to {@link BlockSoundGroup#WOOD}. + * + * @return this builder for chaining + */ + public WoodTypeBuilder soundGroup(BlockSoundGroup soundGroup) { + this.soundGroup = soundGroup; + return this; + } + + /** + * Sets this wood type's hanging sign sound group. + * + *

    Defaults to {@link BlockSoundGroup#HANGING_SIGN}. + * + * @return this builder for chaining + */ + public WoodTypeBuilder hangingSignSoundGroup(BlockSoundGroup hangingSignSoundGroup) { + this.hangingSignSoundGroup = hangingSignSoundGroup; + return this; + } + + /** + * Sets this wood type's fence gate close sound. + * + *

    Defaults to {@link SoundEvents#BLOCK_FENCE_GATE_CLOSE}. + * + * @return this builder for chaining + */ + public WoodTypeBuilder fenceGateCloseSound(SoundEvent fenceGateCloseSound) { + this.fenceGateCloseSound = fenceGateCloseSound; + return this; + } + + /** + * Sets this wood type's fence gate open sound. + * + *

    Defaults to {@link SoundEvents#BLOCK_FENCE_GATE_OPEN}. + * + * @return this builder for chaining + */ + public WoodTypeBuilder fenceGateOpenSound(SoundEvent fenceGateOpenSound) { + this.fenceGateOpenSound = fenceGateOpenSound; + return this; + } + + /** + * Creates a new {@link WoodTypeBuilder} that copies all of another builder's values. + * + * @param builder the {@link WoodTypeBuilder} whose values are to be copied + * + * @return the created copy + */ + public static WoodTypeBuilder copyOf(WoodTypeBuilder builder) { + WoodTypeBuilder copy = new WoodTypeBuilder(); + copy.soundGroup(builder.soundGroup); + copy.hangingSignSoundGroup(builder.hangingSignSoundGroup); + copy.fenceGateCloseSound(builder.fenceGateCloseSound); + copy.fenceGateOpenSound(builder.fenceGateOpenSound); + return copy; + } + + /** + * Creates a new {@link WoodTypeBuilder} that copies all of another wood type's values. + * + * @param woodType the {@link WoodType} whose values are to be copied + * + * @return the created copy + */ + public static WoodTypeBuilder copyOf(WoodType woodType) { + WoodTypeBuilder copy = new WoodTypeBuilder(); + copy.soundGroup(woodType.soundType()); + copy.hangingSignSoundGroup(woodType.hangingSignSoundType()); + copy.fenceGateCloseSound(woodType.fenceGateClose()); + copy.fenceGateOpenSound(woodType.fenceGateOpen()); + return copy; + } + + /** + * Builds and registers a {@link WoodType} from this builder's values. + * + *

    Alternatively, you can use {@link #build(Identifier, BlockSetType)} to build without registering. + *
    Then {@link WoodType#register(WoodType)} can be used to register it later. + * + * @param id the id for the built {@link WoodType} + * @param setType the {@link BlockSetType} for the built {@link WoodType} + * + * @return the built and registered {@link WoodType} + */ + public WoodType register(Identifier id, BlockSetType setType) { + return WoodType.register(this.build(id, setType)); + } + + /** + * Builds a {@link WoodType} from this builder's values without registering it. + * + *

    Use {@link WoodType#register(WoodType)} to register it later. + *
    Alternatively, you can use {@link #register(Identifier, BlockSetType)} to build and register it now. + * + * @param id the id for the built {@link WoodType} + * @param setType the {@link BlockSetType} for the built {@link WoodType} + * + * @return the built {@link WoodType} + */ + public WoodType build(Identifier id, BlockSetType setType) { + return new WoodType(id.toString(), setType, soundGroup, hangingSignSoundGroup, fenceGateCloseSound, fenceGateOpenSound); + } +} diff --git a/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/api/object/builder/v1/block/type/WoodTypeRegistry.java b/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/api/object/builder/v1/block/type/WoodTypeRegistry.java index 4b88706dff..9a3a3a646e 100644 --- a/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/api/object/builder/v1/block/type/WoodTypeRegistry.java +++ b/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/api/object/builder/v1/block/type/WoodTypeRegistry.java @@ -31,7 +31,9 @@ *
    Hanging sign textures are stored at {@code [namespace]/textures/entity/signs/hanging/[path].png}. * * @see BlockSetTypeRegistry + * @deprecated use {@link WoodTypeBuilder} */ +@Deprecated public final class WoodTypeRegistry { private WoodTypeRegistry() { } diff --git a/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/api/object/builder/v1/trade/TradeOfferHelper.java b/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/api/object/builder/v1/trade/TradeOfferHelper.java index 71093f70a5..e630abc8cc 100644 --- a/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/api/object/builder/v1/trade/TradeOfferHelper.java +++ b/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/api/object/builder/v1/trade/TradeOfferHelper.java @@ -16,9 +16,13 @@ package net.fabricmc.fabric.api.object.builder.v1.trade; +import java.util.Collection; import java.util.List; import java.util.function.Consumer; +import org.jetbrains.annotations.ApiStatus; + +import net.minecraft.util.Identifier; import net.minecraft.village.TradeOffers; import net.minecraft.village.VillagerProfession; @@ -30,6 +34,9 @@ public final class TradeOfferHelper { /** * Registers trade offer factories for use by villagers. + * This adds the same trade offers to current and rebalanced trades. + * To add separate offers for the rebalanced trade experiment, use + * {@link #registerVillagerOffers(VillagerProfession, int, VillagerOffersAdder)}. * *

    Below is an example, of registering a trade offer factory to be added a blacksmith with a profession level of 3: *

    @@ -43,11 +50,38 @@ public final class TradeOfferHelper {
     	 * @param factories a consumer to provide the factories
     	 */
     	public static void registerVillagerOffers(VillagerProfession profession, int level, Consumer> factories) {
    +		TradeOfferInternals.registerVillagerOffers(profession, level, (trades, rebalanced) -> factories.accept(trades));
    +	}
    +
    +	/**
    +	 * Registers trade offer factories for use by villagers.
    +	 * This method allows separate offers to be added depending on whether the rebalanced
    +	 * trade experiment is enabled.
    +	 * If a particular profession's rebalanced trade offers are not added at all, it falls back
    +	 * to the regular trade offers.
    +	 *
    +	 * 

    Below is an example, of registering a trade offer factory to be added a blacksmith with a profession level of 3: + *

    +	 * TradeOfferHelper.registerVillagerOffers(VillagerProfession.BLACKSMITH, 3, (factories, rebalanced) -> {
    +	 * 	factories.add(new CustomTradeFactory(...);
    +	 * });
    +	 * 
    + * + *

    Experimental feature. This API may receive changes as necessary to adapt to further experiment changes. + * + * @param profession the villager profession to assign the trades to + * @param level the profession level the villager must be to offer the trades + * @param factories a consumer to provide the factories + */ + @ApiStatus.Experimental + public static void registerVillagerOffers(VillagerProfession profession, int level, VillagerOffersAdder factories) { TradeOfferInternals.registerVillagerOffers(profession, level, factories); } /** * Registers trade offer factories for use by wandering trades. + * This does not add offers for the rebalanced trade experiment. + * To add rebalanced trades, use {@link #registerRebalancedWanderingTraderOffers}. * * @param level the level the trades * @param factory a consumer to provide the factories @@ -56,6 +90,20 @@ public static void registerWanderingTraderOffers(int level, ConsumerExperimental feature. This API may receive changes as necessary to adapt to further experiment changes. + * + * @param factory a consumer to add trade offers + */ + @ApiStatus.Experimental + public static synchronized void registerRebalancedWanderingTraderOffers(Consumer factory) { + factory.accept(new TradeOfferInternals.WanderingTraderOffersBuilderImpl()); + } + /** * @deprecated This never did anything useful. */ @@ -66,4 +114,115 @@ public static void refreshOffers() { private TradeOfferHelper() { } + + @FunctionalInterface + public interface VillagerOffersAdder { + void onRegister(List factories, boolean rebalanced); + } + + /** + * A builder for rebalanced wandering trader offers. + * + *

    Experimental feature. This API may receive changes as necessary to adapt to further experiment changes. + * + * @see #registerRebalancedWanderingTraderOffers(Consumer) + */ + @ApiStatus.NonExtendable + @ApiStatus.Experimental + public interface WanderingTraderOffersBuilder { + /** + * The pool ID for the "buy items" pool. + * Two trade offers are picked from this pool. + * + *

    In vanilla, this pool contains offers to buy water buckets, baked potatoes, etc. + * for emeralds. + */ + Identifier BUY_ITEMS_POOL = new Identifier("minecraft", "buy_items"); + /** + * The pool ID for the "sell special items" pool. + * Two trade offers are picked from this pool. + * + *

    In vanilla, this pool contains offers to sell logs, enchanted iron pickaxes, etc. + */ + Identifier SELL_SPECIAL_ITEMS_POOL = new Identifier("minecraft", "sell_special_items"); + /** + * The pool ID for the "sell common items" pool. + * Five trade offers are picked from this pool. + * + *

    In vanilla, this pool contains offers to sell flowers, saplings, etc. + */ + Identifier SELL_COMMON_ITEMS_POOL = new Identifier("minecraft", "sell_common_items"); + + /** + * Adds a new pool to the offer list. Exactly {@code count} offers are picked from + * {@code factories} and offered to customers. + * @param id the ID to be assigned to this pool, to allow further modification + * @param count the number of offers to be picked from {@code factories} + * @param factories the trade offer factories + * @return this builder, for chaining + * @throws IllegalArgumentException if {@code count} is not positive or if {@code factories} is empty + */ + WanderingTraderOffersBuilder pool(Identifier id, int count, TradeOffers.Factory... factories); + + /** + * Adds a new pool to the offer list. Exactly {@code count} offers are picked from + * {@code factories} and offered to customers. + * @param id the ID to be assigned to this pool, to allow further modification + * @param count the number of offers to be picked from {@code factories} + * @param factories the trade offer factories + * @return this builder, for chaining + * @throws IllegalArgumentException if {@code count} is not positive or if {@code factories} is empty + */ + default WanderingTraderOffersBuilder pool(Identifier id, int count, Collection factories) { + return pool(id, count, factories.toArray(TradeOffers.Factory[]::new)); + } + + /** + * Adds trade offers to the offer list. All offers from {@code factories} are + * offered to each customer. + * @param id the ID to be assigned to this pool, to allow further modification + * @param factories the trade offer factories + * @return this builder, for chaining + * @throws IllegalArgumentException if {@code factories} is empty + */ + default WanderingTraderOffersBuilder addAll(Identifier id, Collection factories) { + return pool(id, factories.size(), factories); + } + + /** + * Adds trade offers to the offer list. All offers from {@code factories} are + * offered to each customer. + * @param id the ID to be assigned to this pool, to allow further modification + * @param factories the trade offer factories + * @return this builder, for chaining + * @throws IllegalArgumentException if {@code factories} is empty + */ + default WanderingTraderOffersBuilder addAll(Identifier id, TradeOffers.Factory... factories) { + return pool(id, factories.length, factories); + } + + /** + * Adds trade offers to an existing pool identified by an ID. + * + *

    See the constants for vanilla trade offer pool IDs that are always available. + * @param pool the pool ID + * @param factories the trade offer factories + * @return this builder, for chaining + * @throws IndexOutOfBoundsException if {@code pool} is out of bounds + */ + WanderingTraderOffersBuilder addOffersToPool(Identifier pool, TradeOffers.Factory... factories); + + /** + * Adds trade offers to an existing pool identified by an ID. + * + *

    See the constants for vanilla trade offer pool IDs that are always available. + * @param pool the pool ID + * @param factories the trade offer factories + * @return this builder, for chaining + * @throws IndexOutOfBoundsException if {@code pool} is out of bounds + */ + default WanderingTraderOffersBuilder addOffersToPool(Identifier pool, Collection factories) { + return addOffersToPool(pool, factories.toArray(TradeOffers.Factory[]::new)); + } + } } diff --git a/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/impl/object/builder/TradeOfferInternals.java b/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/impl/object/builder/TradeOfferInternals.java index abfc401953..f735309271 100644 --- a/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/impl/object/builder/TradeOfferInternals.java +++ b/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/impl/object/builder/TradeOfferInternals.java @@ -17,30 +17,57 @@ package net.fabricmc.fabric.impl.object.builder; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.function.Consumer; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import org.apache.commons.lang3.ArrayUtils; -import org.slf4j.LoggerFactory; +import org.apache.commons.lang3.tuple.Pair; import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import net.minecraft.util.Identifier; +import net.minecraft.util.Util; import net.minecraft.village.TradeOffers; import net.minecraft.village.VillagerProfession; +import net.fabricmc.fabric.api.object.builder.v1.trade.TradeOfferHelper; + public final class TradeOfferInternals { private static final Logger LOGGER = LoggerFactory.getLogger("fabric-object-builder-api-v1"); private TradeOfferInternals() { } + /** + * Make the rebalanced profession map modifiable, then copy all vanilla + * professions' trades to prevent modifications from propagating to the rebalanced one. + */ + private static void initVillagerTrades() { + if (!(TradeOffers.REBALANCED_PROFESSION_TO_LEVELED_TRADE instanceof HashMap)) { + Map> map = new HashMap<>(TradeOffers.REBALANCED_PROFESSION_TO_LEVELED_TRADE); + + for (Map.Entry> trade : TradeOffers.PROFESSION_TO_LEVELED_TRADE.entrySet()) { + if (!map.containsKey(trade.getKey())) map.put(trade.getKey(), trade.getValue()); + } + + TradeOffers.REBALANCED_PROFESSION_TO_LEVELED_TRADE = map; + } + } + // synchronized guards against concurrent modifications - Vanilla does not mutate the underlying arrays (as of 1.16), // so reads will be fine without locking. - public static synchronized void registerVillagerOffers(VillagerProfession profession, int level, Consumer> factory) { + public static synchronized void registerVillagerOffers(VillagerProfession profession, int level, TradeOfferHelper.VillagerOffersAdder factory) { Objects.requireNonNull(profession, "VillagerProfession may not be null."); - registerOffers(TradeOffers.PROFESSION_TO_LEVELED_TRADE.computeIfAbsent(profession, key -> new Int2ObjectOpenHashMap<>()), level, factory); + initVillagerTrades(); + registerOffers(TradeOffers.PROFESSION_TO_LEVELED_TRADE.computeIfAbsent(profession, key -> new Int2ObjectOpenHashMap<>()), level, trades -> factory.onRegister(trades, false)); + registerOffers(TradeOffers.REBALANCED_PROFESSION_TO_LEVELED_TRADE.computeIfAbsent(profession, key -> new Int2ObjectOpenHashMap<>()), level, trades -> factory.onRegister(trades, true)); } public static synchronized void registerWanderingTraderOffers(int level, Consumer> factory) { @@ -63,4 +90,62 @@ public static void printRefreshOffersWarning() { Throwable loggingThrowable = new Throwable(); LOGGER.warn("TradeOfferHelper#refreshOffers does not do anything, yet it was called! Stack trace:", loggingThrowable); } + + public static class WanderingTraderOffersBuilderImpl implements TradeOfferHelper.WanderingTraderOffersBuilder { + private static final Object2IntMap ID_TO_INDEX = Util.make(new Object2IntOpenHashMap<>(), idToIndex -> { + idToIndex.put(BUY_ITEMS_POOL, 0); + idToIndex.put(SELL_SPECIAL_ITEMS_POOL, 1); + idToIndex.put(SELL_COMMON_ITEMS_POOL, 2); + }); + + private static final Map DELAYED_MODIFICATIONS = new HashMap<>(); + + /** + * Make the trade list modifiable. + */ + static void initWanderingTraderTrades() { + if (!(TradeOffers.REBALANCED_WANDERING_TRADER_TRADES instanceof ArrayList)) { + TradeOffers.REBALANCED_WANDERING_TRADER_TRADES = new ArrayList<>(TradeOffers.REBALANCED_WANDERING_TRADER_TRADES); + } + } + + @Override + public TradeOfferHelper.WanderingTraderOffersBuilder pool(Identifier id, int count, TradeOffers.Factory... factories) { + if (factories.length == 0) throw new IllegalArgumentException("cannot add empty pool"); + if (count <= 0) throw new IllegalArgumentException("count must be positive"); + + Objects.requireNonNull(id, "id cannot be null"); + + if (ID_TO_INDEX.containsKey(id)) throw new IllegalArgumentException("pool id %s is already registered".formatted(id)); + + Pair pool = Pair.of(factories, count); + initWanderingTraderTrades(); + ID_TO_INDEX.put(id, TradeOffers.REBALANCED_WANDERING_TRADER_TRADES.size()); + TradeOffers.REBALANCED_WANDERING_TRADER_TRADES.add(pool); + TradeOffers.Factory[] delayedModifications = DELAYED_MODIFICATIONS.remove(id); + + if (delayedModifications != null) addOffersToPool(id, delayedModifications); + + return this; + } + + @Override + public TradeOfferHelper.WanderingTraderOffersBuilder addOffersToPool(Identifier pool, TradeOffers.Factory... factories) { + if (!ID_TO_INDEX.containsKey(pool)) { + DELAYED_MODIFICATIONS.compute(pool, (id, current) -> { + if (current == null) return factories; + + return ArrayUtils.addAll(current, factories); + }); + return this; + } + + int poolIndex = ID_TO_INDEX.getInt(pool); + initWanderingTraderTrades(); + Pair poolPair = TradeOffers.REBALANCED_WANDERING_TRADER_TRADES.get(poolIndex); + TradeOffers.Factory[] modified = ArrayUtils.addAll(poolPair.getLeft(), factories); + TradeOffers.REBALANCED_WANDERING_TRADER_TRADES.set(poolIndex, Pair.of(modified, poolPair.getRight())); + return this; + } + } } diff --git a/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/mixin/object/builder/PersistentStateManagerMixin.java b/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/mixin/object/builder/PersistentStateManagerMixin.java new file mode 100644 index 0000000000..e264ec35ed --- /dev/null +++ b/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/mixin/object/builder/PersistentStateManagerMixin.java @@ -0,0 +1,44 @@ +/* + * 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 net.fabricmc.fabric.mixin.object.builder; + +import java.io.File; +import java.io.FileInputStream; +import java.io.PushbackInputStream; + +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 org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +import net.minecraft.datafixer.DataFixTypes; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.world.PersistentStateManager; + +@Mixin(PersistentStateManager.class) +class PersistentStateManagerMixin { + /** + * Handle mods passing a null DataFixTypes to a PersistentState.Type. + */ + @Inject(method = "readNbt", at = @At(value = "INVOKE", target = "Lnet/minecraft/nbt/NbtHelper;getDataVersion(Lnet/minecraft/nbt/NbtCompound;I)I"), cancellable = true, locals = LocalCapture.CAPTURE_FAILHARD) + private void handleNullDataFixType(String id, DataFixTypes dataFixTypes, int currentSaveVersion, CallbackInfoReturnable cir, File file, FileInputStream fileInputStream, PushbackInputStream pushbackInputStream, NbtCompound nbtCompound) { + if (dataFixTypes == null) { + cir.setReturnValue(nbtCompound); + } + } +} diff --git a/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/mixin/object/builder/TradeOffersTypeAwareBuyForOneEmeraldFactoryMixin.java b/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/mixin/object/builder/TradeOffersTypeAwareBuyForOneEmeraldFactoryMixin.java index 3baca35b2f..7fb62d719b 100644 --- a/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/mixin/object/builder/TradeOffersTypeAwareBuyForOneEmeraldFactoryMixin.java +++ b/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/mixin/object/builder/TradeOffersTypeAwareBuyForOneEmeraldFactoryMixin.java @@ -27,10 +27,11 @@ import net.minecraft.entity.Entity; import net.minecraft.item.ItemStack; -import net.minecraft.util.math.random.Random; import net.minecraft.registry.DefaultedRegistry; +import net.minecraft.util.math.random.Random; import net.minecraft.village.TradeOffer; import net.minecraft.village.TradeOffers; +import net.minecraft.village.VillagerDataContainer; import net.minecraft.village.VillagerType; @Mixin(TradeOffers.TypeAwareBuyForOneEmeraldFactory.class) @@ -50,7 +51,7 @@ private Stream disableVanillaCheck(DefaultedRegistry instan * To prevent "item" -> "air" trades, if the result of a type aware trade is air, make sure no offer is created. */ @Inject(method = "create", at = @At(value = "NEW", target = "net/minecraft/village/TradeOffer"), locals = LocalCapture.CAPTURE_FAILEXCEPTION, cancellable = true) - private void failOnNullItem(Entity entity, Random random, CallbackInfoReturnable cir, ItemStack buyingItem) { + private void failOnNullItem(Entity entity, Random random, CallbackInfoReturnable cir, VillagerDataContainer villagerDataContainer, ItemStack buyingItem) { if (buyingItem.isEmpty()) { // Will return true for an "empty" item stack that had null passed in the ctor cir.setReturnValue(null); // Return null to prevent creation of empty trades } diff --git a/fabric-object-builder-api-v1/src/main/resources/fabric-object-builder-api-v1.accesswidener b/fabric-object-builder-api-v1/src/main/resources/fabric-object-builder-api-v1.accesswidener index 0985130c45..d2a7e09bdd 100644 --- a/fabric-object-builder-api-v1/src/main/resources/fabric-object-builder-api-v1.accesswidener +++ b/fabric-object-builder-api-v1/src/main/resources/fabric-object-builder-api-v1.accesswidener @@ -5,6 +5,8 @@ accessible method net/minecraft/world/poi/PointOfInterestTypes register extendable class net/minecraft/block/entity/BlockEntityType$BlockEntityFactory accessible class net/minecraft/village/TradeOffers$TypeAwareBuyForOneEmeraldFactory +mutable field net/minecraft/village/TradeOffers REBALANCED_PROFESSION_TO_LEVELED_TRADE Ljava/util/Map; +mutable field net/minecraft/village/TradeOffers REBALANCED_WANDERING_TRADER_TRADES Ljava/util/List; accessible method net/minecraft/entity/SpawnRestriction register (Lnet/minecraft/entity/EntityType;Lnet/minecraft/entity/SpawnRestriction$Location;Lnet/minecraft/world/Heightmap$Type;Lnet/minecraft/entity/SpawnRestriction$SpawnPredicate;)V diff --git a/fabric-object-builder-api-v1/src/main/resources/fabric-object-builder-v1.mixins.json b/fabric-object-builder-api-v1/src/main/resources/fabric-object-builder-v1.mixins.json index 1f92b1196c..08896b7bfa 100644 --- a/fabric-object-builder-api-v1/src/main/resources/fabric-object-builder-v1.mixins.json +++ b/fabric-object-builder-api-v1/src/main/resources/fabric-object-builder-v1.mixins.json @@ -5,10 +5,10 @@ "mixins": [ "AbstractBlockAccessor", "AbstractBlockSettingsAccessor", - "CriteriaAccessor", "DefaultAttributeRegistryAccessor", "DefaultAttributeRegistryMixin", "DetectorRailBlockMixin", + "PersistentStateManagerMixin", "TradeOffersTypeAwareBuyForOneEmeraldFactoryMixin" ], "injectors": { diff --git a/fabric-object-builder-api-v1/src/testmod/java/net/fabricmc/fabric/test/object/builder/CriterionRegistryTest.java b/fabric-object-builder-api-v1/src/testmod/java/net/fabricmc/fabric/test/object/builder/CriterionRegistryTest.java deleted file mode 100644 index ef1517e403..0000000000 --- a/fabric-object-builder-api-v1/src/testmod/java/net/fabricmc/fabric/test/object/builder/CriterionRegistryTest.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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 net.fabricmc.fabric.test.object.builder; - -import com.google.gson.JsonObject; - -import net.minecraft.advancement.criterion.ImpossibleCriterion; -import net.minecraft.predicate.entity.AdvancementEntityPredicateDeserializer; -import net.minecraft.util.Identifier; - -import net.fabricmc.fabric.api.object.builder.v1.advancement.CriterionRegistry; - -public final class CriterionRegistryTest { - public static void init() { - CriterionRegistry.register(new CustomCriterion()); - } - - static class CustomCriterion extends ImpossibleCriterion { - static final Identifier ID = ObjectBuilderTestConstants.id("custom"); - - @Override - public Identifier getId() { - return ID; - } - - @Override - public Conditions conditionsFromJson(JsonObject jsonObject, AdvancementEntityPredicateDeserializer advancementEntityPredicateDeserializer) { - ObjectBuilderTestConstants.LOGGER.info("Loading custom criterion in advancement!"); - return super.conditionsFromJson(jsonObject, advancementEntityPredicateDeserializer); - } - } -} diff --git a/fabric-object-builder-api-v1/src/testmod/java/net/fabricmc/fabric/test/object/builder/PersistentStateManagerTest.java b/fabric-object-builder-api-v1/src/testmod/java/net/fabricmc/fabric/test/object/builder/PersistentStateManagerTest.java new file mode 100644 index 0000000000..9d219a99d0 --- /dev/null +++ b/fabric-object-builder-api-v1/src/testmod/java/net/fabricmc/fabric/test/object/builder/PersistentStateManagerTest.java @@ -0,0 +1,80 @@ +/* + * 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 net.fabricmc.fabric.test.object.builder; + +import java.util.Objects; + +import net.minecraft.nbt.NbtCompound; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.world.PersistentState; + +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; + +public class PersistentStateManagerTest implements ModInitializer { + private boolean ranTests = false; + + @Override + public void onInitialize() { + ServerTickEvents.END_WORLD_TICK.register(world -> { + if (ranTests) return; + ranTests = true; + + TestState.getOrCreate(world).setValue("Hello!"); + assert Objects.equals(TestState.getOrCreate(world).getValue(), "Hello!"); + }); + } + + private static class TestState extends PersistentState { + /** + * We are testing that null can be passed as the dataFixType. + */ + private static final PersistentState.Type TYPE = new Type<>(TestState::new, TestState::fromTag, null); + + public static TestState getOrCreate(ServerWorld world) { + return world.getPersistentStateManager().getOrCreate(TestState.TYPE, ObjectBuilderTestConstants.id("test_state").toString()); + } + + private String value = ""; + + private TestState() { + } + + private TestState(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + markDirty(); + } + + @Override + public NbtCompound writeNbt(NbtCompound nbt) { + nbt.putString("value", value); + return nbt; + } + + private static TestState fromTag(NbtCompound tag) { + return new TestState(tag.getString("value")); + } + } +} diff --git a/fabric-object-builder-api-v1/src/testmod/java/net/fabricmc/fabric/test/object/builder/TealSignTest.java b/fabric-object-builder-api-v1/src/testmod/java/net/fabricmc/fabric/test/object/builder/TealSignTest.java index 3f13937f52..066eee224d 100644 --- a/fabric-object-builder-api-v1/src/testmod/java/net/fabricmc/fabric/test/object/builder/TealSignTest.java +++ b/fabric-object-builder-api-v1/src/testmod/java/net/fabricmc/fabric/test/object/builder/TealSignTest.java @@ -32,17 +32,19 @@ import net.minecraft.item.SignItem; import net.minecraft.registry.Registries; import net.minecraft.registry.Registry; +import net.minecraft.util.Identifier; import net.minecraft.util.math.BlockPos; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; import net.fabricmc.fabric.api.object.builder.v1.block.entity.FabricBlockEntityTypeBuilder; -import net.fabricmc.fabric.api.object.builder.v1.block.type.BlockSetTypeRegistry; -import net.fabricmc.fabric.api.object.builder.v1.block.type.WoodTypeRegistry; +import net.fabricmc.fabric.api.object.builder.v1.block.type.BlockSetTypeBuilder; +import net.fabricmc.fabric.api.object.builder.v1.block.type.WoodTypeBuilder; public class TealSignTest implements ModInitializer { - public static final BlockSetType TEAL_BLOCK_SET_TYPE = BlockSetTypeRegistry.registerWood(ObjectBuilderTestConstants.id("teal")); - public static final WoodType TEAL_WOOD_TYPE = WoodTypeRegistry.register(ObjectBuilderTestConstants.id("teal"), TEAL_BLOCK_SET_TYPE); + public static final Identifier TEAL_TYPE_ID = ObjectBuilderTestConstants.id("teal"); + public static final BlockSetType TEAL_BLOCK_SET_TYPE = BlockSetTypeBuilder.copyOf(BlockSetType.OAK).build(TEAL_TYPE_ID); + public static final WoodType TEAL_WOOD_TYPE = WoodTypeBuilder.copyOf(WoodType.OAK).build(TEAL_TYPE_ID, TEAL_BLOCK_SET_TYPE); public static final SignBlock TEAL_SIGN = new SignBlock(FabricBlockSettings.copy(Blocks.OAK_SIGN), TEAL_WOOD_TYPE) { @Override public TealSign createBlockEntity(BlockPos pos, BlockState state) { diff --git a/fabric-object-builder-api-v1/src/testmod/java/net/fabricmc/fabric/test/object/builder/VillagerTypeTest1.java b/fabric-object-builder-api-v1/src/testmod/java/net/fabricmc/fabric/test/object/builder/VillagerTypeTest1.java index 6826d8c47a..d66b3489a3 100644 --- a/fabric-object-builder-api-v1/src/testmod/java/net/fabricmc/fabric/test/object/builder/VillagerTypeTest1.java +++ b/fabric-object-builder-api-v1/src/testmod/java/net/fabricmc/fabric/test/object/builder/VillagerTypeTest1.java @@ -25,9 +25,12 @@ import net.minecraft.entity.Entity; import net.minecraft.entity.passive.WanderingTraderEntity; +import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.item.Items; +import net.minecraft.registry.Registries; import net.minecraft.text.Text; +import net.minecraft.util.Identifier; import net.minecraft.util.math.random.Random; import net.minecraft.village.TradeOffer; import net.minecraft.village.TradeOffers; @@ -38,16 +41,55 @@ import net.fabricmc.fabric.api.object.builder.v1.trade.TradeOfferHelper; public class VillagerTypeTest1 implements ModInitializer { + private static final Identifier FOOD_POOL_ID = ObjectBuilderTestConstants.id("food"); + private static final Identifier THING_POOL_ID = ObjectBuilderTestConstants.id("thing"); + @Override public void onInitialize() { - TradeOfferHelper.registerVillagerOffers(VillagerProfession.ARMORER, 1, factories -> { - factories.add(new SimpleTradeFactory(new TradeOffer(new ItemStack(Items.GOLD_INGOT, 3), new ItemStack(Items.NETHERITE_SCRAP, 4), new ItemStack(Items.NETHERITE_INGOT), 2, 6, 0.15F))); + TradeOfferHelper.registerVillagerOffers(VillagerProfession.ARMORER, 1, (factories, rebalanced) -> { + Item scrap = rebalanced ? Items.NETHER_BRICK : Items.NETHERITE_SCRAP; + factories.add(new SimpleTradeFactory(new TradeOffer(new ItemStack(Items.GOLD_INGOT, 3), new ItemStack(scrap, 4), new ItemStack(Items.NETHERITE_INGOT), 2, 6, 0.15F))); + }); + // Toolsmith is not rebalanced yet + TradeOfferHelper.registerVillagerOffers(VillagerProfession.TOOLSMITH, 1, (factories, rebalanced) -> { + Item scrap = rebalanced ? Items.NETHER_BRICK : Items.NETHERITE_SCRAP; + factories.add(new SimpleTradeFactory(new TradeOffer(new ItemStack(Items.GOLD_INGOT, 3), new ItemStack(scrap, 4), new ItemStack(Items.NETHERITE_INGOT), 2, 6, 0.15F))); }); TradeOfferHelper.registerWanderingTraderOffers(1, factories -> { factories.add(new SimpleTradeFactory(new TradeOffer(new ItemStack(Items.GOLD_INGOT, 3), new ItemStack(Items.NETHERITE_SCRAP, 4), new ItemStack(Items.NETHERITE_INGOT), 2, 6, 0.35F))); }); + TradeOfferHelper.registerRebalancedWanderingTraderOffers(builder -> { + builder.pool( + FOOD_POOL_ID, + 5, + Registries.ITEM.stream().filter(item -> item.getFoodComponent() != null).map( + item -> new SimpleTradeFactory(new TradeOffer(new ItemStack(Items.NETHERITE_INGOT), new ItemStack(item), 3, 4, 0.15F)) + ).toList() + ); + builder.addAll( + THING_POOL_ID, + new SimpleTradeFactory(new TradeOffer(new ItemStack(Items.NETHERITE_INGOT), new ItemStack(Items.MOJANG_BANNER_PATTERN), 1, 4, 0.15F)) + ); + builder.addOffersToPool( + TradeOfferHelper.WanderingTraderOffersBuilder.BUY_ITEMS_POOL, + new SimpleTradeFactory(new TradeOffer(new ItemStack(Items.BLAZE_POWDER, 1), new ItemStack(Items.EMERALD, 4), 3, 4, 0.15F)), + new SimpleTradeFactory(new TradeOffer(new ItemStack(Items.NETHER_WART, 5), new ItemStack(Items.EMERALD, 1), 3, 4, 0.15F)), + new SimpleTradeFactory(new TradeOffer(new ItemStack(Items.GOLDEN_CARROT, 4), new ItemStack(Items.EMERALD, 1), 3, 4, 0.15F)) + ); + builder.addOffersToPool( + TradeOfferHelper.WanderingTraderOffersBuilder.SELL_SPECIAL_ITEMS_POOL, + new SimpleTradeFactory(new TradeOffer(new ItemStack(Items.EMERALD, 6), new ItemStack(Items.BRUSH, 1), 1, 4, 0.15F)), + new SimpleTradeFactory(new TradeOffer(new ItemStack(Items.DIAMOND, 16), new ItemStack(Items.ELYTRA, 1), 1, 4, 0.15F)), + new SimpleTradeFactory(new TradeOffer(new ItemStack(Items.EMERALD, 3), new ItemStack(Items.LEAD, 2), 3, 4, 0.15F)) + ); + builder.addOffersToPool( + FOOD_POOL_ID, + new SimpleTradeFactory(new TradeOffer(new ItemStack(Items.NETHERITE_INGOT), new ItemStack(Items.EGG), 3, 4, 0.15F)) + ); + }); + CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> { dispatcher.register(literal("fabric_applywandering_trades") .then(argument("entity", entity()).executes(context -> { diff --git a/fabric-object-builder-api-v1/src/testmod/java/net/fabricmc/fabric/test/object/builder/VillagerTypeTest2.java b/fabric-object-builder-api-v1/src/testmod/java/net/fabricmc/fabric/test/object/builder/VillagerTypeTest2.java index 58d04975ed..b12ac6f443 100644 --- a/fabric-object-builder-api-v1/src/testmod/java/net/fabricmc/fabric/test/object/builder/VillagerTypeTest2.java +++ b/fabric-object-builder-api-v1/src/testmod/java/net/fabricmc/fabric/test/object/builder/VillagerTypeTest2.java @@ -30,7 +30,7 @@ public class VillagerTypeTest2 implements ModInitializer { @Override public void onInitialize() { - TradeOfferHelper.registerVillagerOffers(VillagerProfession.ARMORER, 1, factories -> { + TradeOfferHelper.registerVillagerOffers(VillagerProfession.WEAPONSMITH, 1, factories -> { factories.add(new SimpleTradeFactory(new TradeOffer(new ItemStack(Items.DIAMOND, 5), new ItemStack(Items.NETHERITE_INGOT), 3, 4, 0.15F))); }); TradeOfferHelper.registerVillagerOffers(VillagerProfession.ARMORER, 1, factories -> { diff --git a/fabric-object-builder-api-v1/src/testmod/resources/data/fabric-object-builder-api-v1-testmod/advancements/criterion_registry_test.json b/fabric-object-builder-api-v1/src/testmod/resources/data/fabric-object-builder-api-v1-testmod/advancements/criterion_registry_test.json deleted file mode 100644 index d96be7670f..0000000000 --- a/fabric-object-builder-api-v1/src/testmod/resources/data/fabric-object-builder-api-v1-testmod/advancements/criterion_registry_test.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "display": { - "icon": { - "item": "minecraft:command_block_minecart" - }, - "title": { - "text": "Criterion registry test advancement" - }, - "description": { - "text": "Criterion registry test advancement description" - }, - "frame": "task", - "show_toast": false, - "announce_to_chat": false, - "hidden": false, - "background": "minecraft:textures/gui/advancements/backgrounds/stone.png" - }, - "criteria": { - "custom": { - "trigger": "fabric-object-builder-api-v1-testmod:custom" - } - }, - "requirements": [ - [ - "custom" - ] - ] -} diff --git a/fabric-object-builder-api-v1/src/testmod/resources/fabric.mod.json b/fabric-object-builder-api-v1/src/testmod/resources/fabric.mod.json index 4c1c66d851..ba9b1e122f 100644 --- a/fabric-object-builder-api-v1/src/testmod/resources/fabric.mod.json +++ b/fabric-object-builder-api-v1/src/testmod/resources/fabric.mod.json @@ -29,11 +29,11 @@ "entrypoints": { "main": [ "net.fabricmc.fabric.test.object.builder.BlockEntityTypeBuilderTest", - "net.fabricmc.fabric.test.object.builder.CriterionRegistryTest::init", "net.fabricmc.fabric.test.object.builder.FabricBlockSettingsTest", "net.fabricmc.fabric.test.object.builder.VillagerTypeTest1", "net.fabricmc.fabric.test.object.builder.VillagerTypeTest2", - "net.fabricmc.fabric.test.object.builder.TealSignTest" + "net.fabricmc.fabric.test.object.builder.TealSignTest", + "net.fabricmc.fabric.test.object.builder.PersistentStateManagerTest" ], "client": [ "net.fabricmc.fabric.test.object.builder.client.TealSignClientTest" diff --git a/fabric-particles-v1/build.gradle b/fabric-particles-v1/build.gradle index 7ba90d1f5d..3c2205f361 100644 --- a/fabric-particles-v1/build.gradle +++ b/fabric-particles-v1/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-particles-v1" version = getSubprojectVersion(project) loom { @@ -7,6 +6,12 @@ loom { moduleDependencies(project, ['fabric-api-base']) +testDependencies(project, [ + ':fabric-command-api-v2', + ':fabric-rendering-v1', + ':fabric-resource-loader-v0' +]) + validateMixinNames { // Loom needs to handle inner mixins better exclude "**/ParticleManagerAccessor\$SimpleSpriteProviderAccessor.class" diff --git a/fabric-particles-v1/src/client/java/net/fabricmc/fabric/api/client/particle/v1/ParticleRenderEvents.java b/fabric-particles-v1/src/client/java/net/fabricmc/fabric/api/client/particle/v1/ParticleRenderEvents.java new file mode 100644 index 0000000000..2ffbaab17e --- /dev/null +++ b/fabric-particles-v1/src/client/java/net/fabricmc/fabric/api/client/particle/v1/ParticleRenderEvents.java @@ -0,0 +1,63 @@ +/* + * 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 net.fabricmc.fabric.api.client.particle.v1; + +import net.minecraft.block.BlockState; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.util.math.BlockPos; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; + +/** + * Events related to particle rendering. + */ +public final class ParticleRenderEvents { + private ParticleRenderEvents() { + } + + /** + * An event that checks if a {@linkplain net.minecraft.client.particle.BlockDustParticle block dust particle} + * can be tinted using the corresponding block's {@linkplain net.minecraft.client.color.block.BlockColorProvider color provider}. + * + *

    The default return value of this event is {@code true}. If any callback returns {@code false} for a given call, + * further iteration will be canceled and the event invoker will return {@code false}. + */ + public static final Event ALLOW_BLOCK_DUST_TINT = EventFactory.createArrayBacked(AllowBlockDustTint.class, callbacks -> (state, world, pos) -> { + for (AllowBlockDustTint callback : callbacks) { + if (!callback.allowBlockDustTint(state, world, pos)) { + return false; + } + } + + return true; + }); + + @FunctionalInterface + public interface AllowBlockDustTint { + /** + * Checks whether a {@linkplain net.minecraft.client.particle.BlockDustParticle block dust particle} can be + * tinted using the corresponding block's {@linkplain net.minecraft.client.color.block.BlockColorProvider color provider}. + * + * @param state the block state that the particle represents + * @param world the world the particle is created in + * @param pos the position of the particle + * @return {@code true} if color provider tinting should be allowed, {@code false} otherwise + */ + boolean allowBlockDustTint(BlockState state, ClientWorld world, BlockPos pos); + } +} diff --git a/fabric-particles-v1/src/client/java/net/fabricmc/fabric/mixin/client/particle/BlockDustParticleMixin.java b/fabric-particles-v1/src/client/java/net/fabricmc/fabric/mixin/client/particle/BlockDustParticleMixin.java new file mode 100644 index 0000000000..fe39b2e598 --- /dev/null +++ b/fabric-particles-v1/src/client/java/net/fabricmc/fabric/mixin/client/particle/BlockDustParticleMixin.java @@ -0,0 +1,63 @@ +/* + * 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 net.fabricmc.fabric.mixin.client.particle; + +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.ModifyVariable; +import org.spongepowered.asm.mixin.injection.Slice; + +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.client.particle.BlockDustParticle; +import net.minecraft.client.particle.SpriteBillboardParticle; +import net.minecraft.util.math.BlockPos; + +import net.fabricmc.fabric.api.client.particle.v1.ParticleRenderEvents; + +// Implements ParticleRenderEvents.ALLOW_BLOCK_DUST_TINT +@Mixin(BlockDustParticle.class) +abstract class BlockDustParticleMixin extends SpriteBillboardParticle { + @Shadow + @Final + private BlockPos blockPos; + + private BlockDustParticleMixin() { + super(null, 0, 0, 0); + } + + @ModifyVariable( + method = "(Lnet/minecraft/client/world/ClientWorld;DDDDDDLnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;)V", + at = @At("LOAD"), + argsOnly = true, + slice = @Slice( + from = @At(value = "FIELD", target = "Lnet/minecraft/client/particle/BlockDustParticle;blue:F", ordinal = 0), + to = @At(value = "INVOKE", target = "Lnet/minecraft/block/BlockState;isOf(Lnet/minecraft/block/Block;)Z") + ), + allow = 1 + ) + private BlockState removeUntintableParticles(BlockState state) { + if (!ParticleRenderEvents.ALLOW_BLOCK_DUST_TINT.invoker().allowBlockDustTint(state, world, blockPos)) { + // As of 1.20.1, vanilla hardcodes grass block particles to not get tinted. + return Blocks.GRASS_BLOCK.getDefaultState(); + } + + return state; + } +} diff --git a/fabric-particles-v1/src/client/resources/fabric-particles-v1.client.mixins.json b/fabric-particles-v1/src/client/resources/fabric-particles-v1.client.mixins.json index d871af6887..deeb077a59 100644 --- a/fabric-particles-v1/src/client/resources/fabric-particles-v1.client.mixins.json +++ b/fabric-particles-v1/src/client/resources/fabric-particles-v1.client.mixins.json @@ -3,6 +3,7 @@ "package": "net.fabricmc.fabric.mixin.client.particle", "compatibilityLevel": "JAVA_16", "client": [ + "BlockDustParticleMixin", "ParticleManagerMixin", "ParticleManagerAccessor", "ParticleManagerAccessor$SimpleSpriteProviderAccessor" diff --git a/fabric-particles-v1/src/main/resources/fabric.mod.json b/fabric-particles-v1/src/main/resources/fabric.mod.json index d737d5489d..9a9aeb7b4d 100644 --- a/fabric-particles-v1/src/main/resources/fabric.mod.json +++ b/fabric-particles-v1/src/main/resources/fabric.mod.json @@ -16,7 +16,7 @@ "FabricMC" ], "depends": { - "fabricloader": ">=0.4.0" + "fabricloader": ">=0.14.21" }, "description": "Hooks for registering custom particles.", "mixins": [ diff --git a/fabric-particles-v1/src/testmod/java/net/fabricmc/fabric/test/particle/ParticleTestSetup.java b/fabric-particles-v1/src/testmod/java/net/fabricmc/fabric/test/particle/ParticleTestSetup.java new file mode 100644 index 0000000000..a6869df036 --- /dev/null +++ b/fabric-particles-v1/src/testmod/java/net/fabricmc/fabric/test/particle/ParticleTestSetup.java @@ -0,0 +1,65 @@ +/* + * 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 net.fabricmc.fabric.test.particle; + +import com.mojang.brigadier.Command; + +import net.minecraft.block.AbstractBlock; +import net.minecraft.block.Block; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.item.BlockItem; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.registry.Registries; +import net.minecraft.registry.Registry; +import net.minecraft.server.command.CommandManager; +import net.minecraft.util.Identifier; + +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; + +public final class ParticleTestSetup implements ModInitializer { + // The dust particles of this block are always tinted (default). + public static final Block ALWAYS_TINTED = new ParticleTintTestBlock(AbstractBlock.Settings.create().breakInstantly(), 0xFF00FF); + // The dust particles of this block are only tinted when the block is broken over water. + public static final Block TINTED_OVER_WATER = new ParticleTintTestBlock(AbstractBlock.Settings.create().breakInstantly(), 0xFFFF00); + // The dust particles of this block are never tinted. + public static final Block NEVER_TINTED = new ParticleTintTestBlock(AbstractBlock.Settings.create().breakInstantly(), 0x00FFFF); + + @Override + public void onInitialize() { + registerBlock("always_tinted", ALWAYS_TINTED); + registerBlock("tinted_over_water", TINTED_OVER_WATER); + registerBlock("never_tinted", NEVER_TINTED); + + CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> { + dispatcher.register(CommandManager.literal("addparticletestblocks").executes(context -> { + PlayerInventory inventory = context.getSource().getPlayer().getInventory(); + inventory.offerOrDrop(new ItemStack(ALWAYS_TINTED)); + inventory.offerOrDrop(new ItemStack(TINTED_OVER_WATER)); + inventory.offerOrDrop(new ItemStack(NEVER_TINTED)); + return Command.SINGLE_SUCCESS; + })); + }); + } + + private static void registerBlock(String path, Block block) { + Identifier id = new Identifier("fabric-particles-v1-testmod", path); + Registry.register(Registries.BLOCK, id, block); + Registry.register(Registries.ITEM, id, new BlockItem(block, new Item.Settings())); + } +} diff --git a/fabric-particles-v1/src/testmod/java/net/fabricmc/fabric/test/particle/ParticleTintTestBlock.java b/fabric-particles-v1/src/testmod/java/net/fabricmc/fabric/test/particle/ParticleTintTestBlock.java new file mode 100644 index 0000000000..12b9dbc492 --- /dev/null +++ b/fabric-particles-v1/src/testmod/java/net/fabricmc/fabric/test/particle/ParticleTintTestBlock.java @@ -0,0 +1,28 @@ +/* + * 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 net.fabricmc.fabric.test.particle; + +import net.minecraft.block.Block; + +public class ParticleTintTestBlock extends Block { + public final int color; + + public ParticleTintTestBlock(Settings settings, int color) { + super(settings); + this.color = color; + } +} diff --git a/fabric-particles-v1/src/testmod/resources/assets/fabric-particles-v1-testmod/blockstates/always_tinted.json b/fabric-particles-v1/src/testmod/resources/assets/fabric-particles-v1-testmod/blockstates/always_tinted.json new file mode 100644 index 0000000000..dc9e2814d2 --- /dev/null +++ b/fabric-particles-v1/src/testmod/resources/assets/fabric-particles-v1-testmod/blockstates/always_tinted.json @@ -0,0 +1,5 @@ +{ + "variants": { + "": { "model": "fabric-particles-v1-testmod:block/tint_test" } + } +} diff --git a/fabric-particles-v1/src/testmod/resources/assets/fabric-particles-v1-testmod/blockstates/never_tinted.json b/fabric-particles-v1/src/testmod/resources/assets/fabric-particles-v1-testmod/blockstates/never_tinted.json new file mode 100644 index 0000000000..dc9e2814d2 --- /dev/null +++ b/fabric-particles-v1/src/testmod/resources/assets/fabric-particles-v1-testmod/blockstates/never_tinted.json @@ -0,0 +1,5 @@ +{ + "variants": { + "": { "model": "fabric-particles-v1-testmod:block/tint_test" } + } +} diff --git a/fabric-particles-v1/src/testmod/resources/assets/fabric-particles-v1-testmod/blockstates/tinted_over_water.json b/fabric-particles-v1/src/testmod/resources/assets/fabric-particles-v1-testmod/blockstates/tinted_over_water.json new file mode 100644 index 0000000000..dc9e2814d2 --- /dev/null +++ b/fabric-particles-v1/src/testmod/resources/assets/fabric-particles-v1-testmod/blockstates/tinted_over_water.json @@ -0,0 +1,5 @@ +{ + "variants": { + "": { "model": "fabric-particles-v1-testmod:block/tint_test" } + } +} diff --git a/fabric-particles-v1/src/testmod/resources/assets/fabric-particles-v1-testmod/lang/en_us.json b/fabric-particles-v1/src/testmod/resources/assets/fabric-particles-v1-testmod/lang/en_us.json new file mode 100644 index 0000000000..a0c6be15d1 --- /dev/null +++ b/fabric-particles-v1/src/testmod/resources/assets/fabric-particles-v1-testmod/lang/en_us.json @@ -0,0 +1,5 @@ +{ + "block.fabric-particles-v1-testmod.always_tinted": "Dust Particles Always Tinted", + "block.fabric-particles-v1-testmod.never_tinted": "Dust Particles Never Tinted", + "block.fabric-particles-v1-testmod.tinted_over_water": "Dust Particles Only Tinted Over Water" +} diff --git a/fabric-particles-v1/src/testmod/resources/assets/fabric-particles-v1-testmod/models/block/tint_test.json b/fabric-particles-v1/src/testmod/resources/assets/fabric-particles-v1-testmod/models/block/tint_test.json new file mode 100644 index 0000000000..4a7f3b06f3 --- /dev/null +++ b/fabric-particles-v1/src/testmod/resources/assets/fabric-particles-v1-testmod/models/block/tint_test.json @@ -0,0 +1,21 @@ +{ + "parent": "block/block", + "textures": { + "all": "fabric-particles-v1-testmod:block/tint_test", + "particle": "fabric-particles-v1-testmod:block/tint_test" + }, + "elements": [ + { + "from": [0, 0, 0], + "to": [16, 16, 16], + "faces": { + "north": { "texture": "#all", "cullface": "north", "tintindex": 0 }, + "east": { "texture": "#all", "cullface": "east", "tintindex": 0 }, + "south": { "texture": "#all", "cullface": "south", "tintindex": 0 }, + "west": { "texture": "#all", "cullface": "west", "tintindex": 0 }, + "up": { "texture": "#all", "cullface": "up", "tintindex": 0 }, + "down": { "texture": "#all", "cullface": "down", "tintindex": 0 } + } + } + ] +} diff --git a/fabric-particles-v1/src/testmod/resources/assets/fabric-particles-v1-testmod/models/item/always_tinted.json b/fabric-particles-v1/src/testmod/resources/assets/fabric-particles-v1-testmod/models/item/always_tinted.json new file mode 100644 index 0000000000..98879aec56 --- /dev/null +++ b/fabric-particles-v1/src/testmod/resources/assets/fabric-particles-v1-testmod/models/item/always_tinted.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "fabric-particles-v1-testmod:block/tint_test" + } +} diff --git a/fabric-particles-v1/src/testmod/resources/assets/fabric-particles-v1-testmod/models/item/never_tinted.json b/fabric-particles-v1/src/testmod/resources/assets/fabric-particles-v1-testmod/models/item/never_tinted.json new file mode 100644 index 0000000000..98879aec56 --- /dev/null +++ b/fabric-particles-v1/src/testmod/resources/assets/fabric-particles-v1-testmod/models/item/never_tinted.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "fabric-particles-v1-testmod:block/tint_test" + } +} diff --git a/fabric-particles-v1/src/testmod/resources/assets/fabric-particles-v1-testmod/models/item/tinted_over_water.json b/fabric-particles-v1/src/testmod/resources/assets/fabric-particles-v1-testmod/models/item/tinted_over_water.json new file mode 100644 index 0000000000..98879aec56 --- /dev/null +++ b/fabric-particles-v1/src/testmod/resources/assets/fabric-particles-v1-testmod/models/item/tinted_over_water.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "fabric-particles-v1-testmod:block/tint_test" + } +} diff --git a/fabric-particles-v1/src/testmod/resources/assets/fabric-particles-v1-testmod/textures/block/tint_test.png b/fabric-particles-v1/src/testmod/resources/assets/fabric-particles-v1-testmod/textures/block/tint_test.png new file mode 100644 index 0000000000..fe2ffd626f Binary files /dev/null and b/fabric-particles-v1/src/testmod/resources/assets/fabric-particles-v1-testmod/textures/block/tint_test.png differ diff --git a/fabric-particles-v1/src/testmod/resources/fabric.mod.json b/fabric-particles-v1/src/testmod/resources/fabric.mod.json new file mode 100644 index 0000000000..4404e30b17 --- /dev/null +++ b/fabric-particles-v1/src/testmod/resources/fabric.mod.json @@ -0,0 +1,19 @@ +{ + "schemaVersion": 1, + "id": "fabric-particles-v1-testmod", + "name": "Fabric Particles (v1) Test Mod", + "version": "1.0.0", + "environment": "*", + "license": "Apache-2.0", + "depends": { + "fabric-particles-v1": "*" + }, + "entrypoints": { + "main": [ + "net.fabricmc.fabric.test.particle.ParticleTestSetup" + ], + "client": [ + "net.fabricmc.fabric.test.particle.client.ParticleRenderEventTests" + ] + } +} diff --git a/fabric-particles-v1/src/testmodClient/java/net/fabricmc/fabric/test/particle/client/ParticleRenderEventTests.java b/fabric-particles-v1/src/testmodClient/java/net/fabricmc/fabric/test/particle/client/ParticleRenderEventTests.java new file mode 100644 index 0000000000..f157e9f140 --- /dev/null +++ b/fabric-particles-v1/src/testmodClient/java/net/fabricmc/fabric/test/particle/client/ParticleRenderEventTests.java @@ -0,0 +1,48 @@ +/* + * 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 net.fabricmc.fabric.test.particle.client; + +import net.minecraft.registry.tag.FluidTags; + +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.particle.v1.ParticleRenderEvents; +import net.fabricmc.fabric.api.client.rendering.v1.ColorProviderRegistry; +import net.fabricmc.fabric.test.particle.ParticleTestSetup; +import net.fabricmc.fabric.test.particle.ParticleTintTestBlock; + +public final class ParticleRenderEventTests implements ClientModInitializer { + @Override + public void onInitializeClient() { + ColorProviderRegistry.BLOCK.register((state, world, pos, tintIndex) -> { + if (tintIndex == 0) { + return ((ParticleTintTestBlock) state.getBlock()).color; + } + + return -1; + }, ParticleTestSetup.ALWAYS_TINTED, ParticleTestSetup.TINTED_OVER_WATER, ParticleTestSetup.NEVER_TINTED); + + ParticleRenderEvents.ALLOW_BLOCK_DUST_TINT.register((state, world, pos) -> { + if (state.isOf(ParticleTestSetup.NEVER_TINTED)) { + return false; + } else if (state.isOf(ParticleTestSetup.TINTED_OVER_WATER)) { + return world.getFluidState(pos.down()).isIn(FluidTags.WATER); + } + + return true; + }); + } +} diff --git a/fabric-recipe-api-v1/build.gradle b/fabric-recipe-api-v1/build.gradle index 2d85d16a43..db1d1901ac 100644 --- a/fabric-recipe-api-v1/build.gradle +++ b/fabric-recipe-api-v1/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-recipe-api-v1" version = getSubprojectVersion(project) loom { diff --git a/fabric-recipe-api-v1/src/client/java/net/fabricmc/fabric/impl/recipe/ingredient/client/CustomIngredientSyncClient.java b/fabric-recipe-api-v1/src/client/java/net/fabricmc/fabric/impl/recipe/ingredient/client/CustomIngredientSyncClient.java index 7a3bb82e3d..ecbbf35dda 100644 --- a/fabric-recipe-api-v1/src/client/java/net/fabricmc/fabric/impl/recipe/ingredient/client/CustomIngredientSyncClient.java +++ b/fabric-recipe-api-v1/src/client/java/net/fabricmc/fabric/impl/recipe/ingredient/client/CustomIngredientSyncClient.java @@ -16,10 +16,8 @@ package net.fabricmc.fabric.impl.recipe.ingredient.client; -import java.util.concurrent.CompletableFuture; - import net.fabricmc.api.ClientModInitializer; -import net.fabricmc.fabric.api.client.networking.v1.ClientLoginNetworking; +import net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationNetworking; import net.fabricmc.fabric.impl.recipe.ingredient.CustomIngredientSync; /** @@ -28,10 +26,9 @@ public class CustomIngredientSyncClient implements ClientModInitializer { @Override public void onInitializeClient() { - ClientLoginNetworking.registerGlobalReceiver(CustomIngredientSync.PACKET_ID, (client, handler, buf, listenerAdder) -> { + ClientConfigurationNetworking.registerGlobalReceiver(CustomIngredientSync.PACKET_ID, (client, handler, buf, responseSender) -> { int protocolVersion = buf.readVarInt(); - - return CompletableFuture.completedFuture(CustomIngredientSync.createResponsePacket(protocolVersion)); + handler.sendPacket(ClientConfigurationNetworking.createC2SPacket(CustomIngredientSync.PACKET_ID, CustomIngredientSync.createResponsePacket(protocolVersion))); }); } } diff --git a/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/api/recipe/v1/ingredient/CustomIngredientSerializer.java b/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/api/recipe/v1/ingredient/CustomIngredientSerializer.java index 7f419bb7de..0cf05d2899 100644 --- a/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/api/recipe/v1/ingredient/CustomIngredientSerializer.java +++ b/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/api/recipe/v1/ingredient/CustomIngredientSerializer.java @@ -16,11 +16,11 @@ package net.fabricmc.fabric.api.recipe.v1.ingredient; -import com.google.gson.JsonObject; -import com.google.gson.JsonSyntaxException; +import com.mojang.serialization.Codec; import org.jetbrains.annotations.Nullable; import net.minecraft.network.PacketByteBuf; +import net.minecraft.recipe.Ingredient; import net.minecraft.util.Identifier; import net.fabricmc.fabric.impl.recipe.ingredient.CustomIngredientImpl; @@ -56,17 +56,14 @@ static CustomIngredientSerializer get(Identifier identifier) { Identifier getIdentifier(); /** - * Deserializes the custom ingredient from a JSON object. + * {@return the codec}. * - * @throws JsonSyntaxException if the JSON object does not match the format expected by the serializer - * @throws IllegalArgumentException if the JSON object is invalid for some other reason - */ - T read(JsonObject json); - - /** - * Serializes the custom ingredient to a JSON object. + *

    Codecs are used to read the ingredient from the recipe JSON files. + * + * @see Ingredient#ALLOW_EMPTY_CODEC + * @see Ingredient#DISALLOW_EMPTY_CODEC */ - void write(JsonObject json, T ingredient); + Codec getCodec(boolean allowEmpty); /** * Deserializes the custom ingredient from a packet buffer. diff --git a/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/api/recipe/v1/ingredient/DefaultCustomIngredients.java b/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/api/recipe/v1/ingredient/DefaultCustomIngredients.java index a2051824b6..6d9380e466 100644 --- a/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/api/recipe/v1/ingredient/DefaultCustomIngredients.java +++ b/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/api/recipe/v1/ingredient/DefaultCustomIngredients.java @@ -16,6 +16,7 @@ package net.fabricmc.fabric.api.recipe.v1.ingredient; +import java.util.List; import java.util.Objects; import org.jetbrains.annotations.Nullable; @@ -54,7 +55,7 @@ public final class DefaultCustomIngredients { public static Ingredient all(Ingredient... ingredients) { for (Ingredient ing : ingredients) Objects.requireNonNull(ing, "Ingredient cannot be null"); - return new AllIngredient(ingredients).toVanilla(); + return new AllIngredient(List.of(ingredients)).toVanilla(); } /** @@ -77,7 +78,7 @@ public static Ingredient all(Ingredient... ingredients) { public static Ingredient any(Ingredient... ingredients) { for (Ingredient ing : ingredients) Objects.requireNonNull(ing, "Ingredient cannot be null"); - return new AnyIngredient(ingredients).toVanilla(); + return new AnyIngredient(List.of(ingredients)).toVanilla(); } /** diff --git a/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/impl/recipe/ingredient/CustomIngredientImpl.java b/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/impl/recipe/ingredient/CustomIngredientImpl.java index 7c0f86654e..9e5e3e7744 100644 --- a/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/impl/recipe/ingredient/CustomIngredientImpl.java +++ b/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/impl/recipe/ingredient/CustomIngredientImpl.java @@ -18,12 +18,15 @@ import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Stream; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.DynamicOps; import org.jetbrains.annotations.Nullable; import net.minecraft.item.ItemStack; @@ -46,6 +49,16 @@ public class CustomIngredientImpl extends Ingredient { static final Map> REGISTERED_SERIALIZERS = new ConcurrentHashMap<>(); + public static final Codec> CODEC = Identifier.CODEC.flatXmap(identifier -> + Optional.ofNullable(REGISTERED_SERIALIZERS.get(identifier)) + .map(DataResult::success) + .orElseGet(() -> DataResult.error(() -> "Unknown custom ingredient serializer: " + identifier)), + serializer -> DataResult.success(serializer.getIdentifier()) + ); + + public static final Codec ALLOW_EMPTY_INGREDIENT_CODECS = CODEC.dispatch(TYPE_KEY, CustomIngredient::getSerializer, serializer -> serializer.getCodec(true)); + public static final Codec DISALLOW_EMPTY_INGREDIENT_CODECS = CODEC.dispatch(TYPE_KEY, CustomIngredient::getSerializer, serializer -> serializer.getCodec(false)); + public static void registerSerializer(CustomIngredientSerializer serializer) { Objects.requireNonNull(serializer.getIdentifier(), "CustomIngredientSerializer identifier may not be null."); @@ -113,14 +126,6 @@ public void write(PacketByteBuf buf) { } } - @Override - public JsonElement toJson() { - JsonObject json = new JsonObject(); - json.addProperty(TYPE_KEY, customIngredient.getSerializer().getIdentifier().toString()); - customIngredient.getSerializer().write(json, coerceIngredient()); - return json; - } - @Override public boolean isEmpty() { // We don't want to resolve the matching stacks, @@ -132,4 +137,33 @@ public boolean isEmpty() { private T coerceIngredient() { return (T) customIngredient; } + + public static Codec first(Codec first, Codec second) { + return new First<>(first, second); + } + + // Decode/encode the first codec, if that fails return the result of the second. + record First(Codec first, Codec second) implements Codec { + @Override + public DataResult> decode(DynamicOps ops, T1 input) { + DataResult> firstResult = first.decode(ops, input); + + if (firstResult.result().isPresent()) { + return firstResult; + } + + return second.decode(ops, input); + } + + @Override + public DataResult encode(T input, DynamicOps ops, T1 prefix) { + DataResult firstResult = first.encode(input, ops, prefix); + + if (firstResult.result().isPresent()) { + return firstResult; + } + + return second.encode(input, ops, prefix); + } + } } diff --git a/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/impl/recipe/ingredient/CustomIngredientSync.java b/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/impl/recipe/ingredient/CustomIngredientSync.java index 9a51ddc8a8..f7be0e0004 100644 --- a/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/impl/recipe/ingredient/CustomIngredientSync.java +++ b/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/impl/recipe/ingredient/CustomIngredientSync.java @@ -18,18 +18,22 @@ import java.util.HashSet; import java.util.Set; +import java.util.function.Consumer; import io.netty.channel.ChannelHandler; import org.jetbrains.annotations.Nullable; import net.minecraft.network.PacketByteBuf; -import net.minecraft.network.PacketEncoder; +import net.minecraft.network.handler.PacketEncoder; +import net.minecraft.network.packet.Packet; +import net.minecraft.server.network.ServerPlayerConfigurationTask; import net.minecraft.util.Identifier; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; -import net.fabricmc.fabric.api.networking.v1.ServerLoginConnectionEvents; -import net.fabricmc.fabric.api.networking.v1.ServerLoginNetworking; +import net.fabricmc.fabric.api.networking.v1.ServerConfigurationConnectionEvents; +import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking; +import net.fabricmc.fabric.mixin.networking.accessor.ServerCommonNetworkHandlerAccessor; import net.fabricmc.fabric.mixin.recipe.ingredient.PacketEncoderMixin; /** @@ -81,25 +85,39 @@ public static Set decodeResponsePacket(PacketByteBuf buf) { @Override public void onInitialize() { - ServerLoginConnectionEvents.QUERY_START.register((handler, server, sender, synchronizer) -> { - // Send packet with 1 so the client can send us back the list of supported tags. - // 1 is sent in case we need a different protocol later for some reason. - PacketByteBuf buf = PacketByteBufs.create(); - buf.writeVarInt(PROTOCOL_VERSION_1); // max supported server protocol version - sender.sendPacket(PACKET_ID, buf); - }); - ServerLoginNetworking.registerGlobalReceiver(PACKET_ID, (server, handler, understood, buf, synchronizer, responseSender) -> { - if (!understood) { - // Skip if the client didn't understand the query. - return; + ServerConfigurationConnectionEvents.CONFIGURE.register((handler, server) -> { + if (ServerConfigurationNetworking.canSend(handler, PACKET_ID)) { + handler.addTask(new IngredientSyncTask()); } + }); + ServerConfigurationNetworking.registerGlobalReceiver(PACKET_ID, (server, handler, buf, responseSender) -> { Set supportedCustomIngredients = decodeResponsePacket(buf); - ChannelHandler packetEncoder = handler.connection.channel.pipeline().get("encoder"); + ChannelHandler packetEncoder = ((ServerCommonNetworkHandlerAccessor) handler).getConnection().channel.pipeline().get("encoder"); if (packetEncoder != null) { // Null in singleplayer ((SupportedIngredientsPacketEncoder) packetEncoder).fabric_setSupportedCustomIngredients(supportedCustomIngredients); } + + handler.completeTask(IngredientSyncTask.KEY); }); } + + private record IngredientSyncTask() implements ServerPlayerConfigurationTask { + public static final Key KEY = new Key(PACKET_ID.toString()); + + @Override + public void sendPacket(Consumer> sender) { + // Send packet with 1 so the client can send us back the list of supported tags. + // 1 is sent in case we need a different protocol later for some reason. + PacketByteBuf buf = PacketByteBufs.create(); + buf.writeVarInt(PROTOCOL_VERSION_1); // max supported server protocol version + sender.accept(ServerConfigurationNetworking.createS2CPacket(PACKET_ID, buf)); + } + + @Override + public Key getKey() { + return KEY; + } + } } diff --git a/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/impl/recipe/ingredient/SupportedIngredientsPacketEncoder.java b/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/impl/recipe/ingredient/SupportedIngredientsPacketEncoder.java index c5de3e8607..ac75f6dbea 100644 --- a/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/impl/recipe/ingredient/SupportedIngredientsPacketEncoder.java +++ b/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/impl/recipe/ingredient/SupportedIngredientsPacketEncoder.java @@ -18,7 +18,7 @@ import java.util.Set; -import net.minecraft.network.PacketEncoder; +import net.minecraft.network.handler.PacketEncoder; import net.minecraft.util.Identifier; /** diff --git a/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/impl/recipe/ingredient/builtin/AllIngredient.java b/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/impl/recipe/ingredient/builtin/AllIngredient.java index 387009f428..e1145d864b 100644 --- a/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/impl/recipe/ingredient/builtin/AllIngredient.java +++ b/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/impl/recipe/ingredient/builtin/AllIngredient.java @@ -20,6 +20,8 @@ import java.util.Arrays; import java.util.List; +import com.mojang.serialization.Codec; + import net.minecraft.item.ItemStack; import net.minecraft.recipe.Ingredient; import net.minecraft.util.Identifier; @@ -27,10 +29,21 @@ import net.fabricmc.fabric.api.recipe.v1.ingredient.CustomIngredientSerializer; public class AllIngredient extends CombinedIngredient { + private static final Codec ALLOW_EMPTY_CODEC = createCodec(Ingredient.ALLOW_EMPTY_CODEC); + private static final Codec DISALLOW_EMPTY_CODEC = createCodec(Ingredient.DISALLOW_EMPTY_CODEC); + + private static Codec createCodec(Codec ingredientCodec) { + return ingredientCodec + .listOf() + .fieldOf("ingredients") + .xmap(AllIngredient::new, AllIngredient::getIngredients) + .codec(); + } + public static final CustomIngredientSerializer SERIALIZER = - new Serializer<>(new Identifier("fabric", "all"), AllIngredient::new); + new Serializer<>(new Identifier("fabric", "all"), AllIngredient::new, ALLOW_EMPTY_CODEC, DISALLOW_EMPTY_CODEC); - public AllIngredient(Ingredient[] ingredients) { + public AllIngredient(List ingredients) { super(ingredients); } @@ -48,10 +61,10 @@ public boolean test(ItemStack stack) { @Override public List getMatchingStacks() { // There's always at least one sub ingredient, so accessing ingredients[0] is safe. - List previewStacks = new ArrayList<>(Arrays.asList(ingredients[0].getMatchingStacks())); + List previewStacks = new ArrayList<>(Arrays.asList(ingredients.get(0).getMatchingStacks())); - for (int i = 1; i < ingredients.length; ++i) { - Ingredient ing = ingredients[i]; + for (int i = 1; i < ingredients.size(); ++i) { + Ingredient ing = ingredients.get(i); previewStacks.removeIf(stack -> !ing.test(stack)); } diff --git a/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/impl/recipe/ingredient/builtin/AnyIngredient.java b/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/impl/recipe/ingredient/builtin/AnyIngredient.java index 93b2b40456..b4c6cf6c4c 100644 --- a/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/impl/recipe/ingredient/builtin/AnyIngredient.java +++ b/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/impl/recipe/ingredient/builtin/AnyIngredient.java @@ -20,6 +20,8 @@ import java.util.Arrays; import java.util.List; +import com.mojang.serialization.Codec; + import net.minecraft.item.ItemStack; import net.minecraft.recipe.Ingredient; import net.minecraft.util.Identifier; @@ -27,10 +29,21 @@ import net.fabricmc.fabric.api.recipe.v1.ingredient.CustomIngredientSerializer; public class AnyIngredient extends CombinedIngredient { + private static final Codec ALLOW_EMPTY_CODEC = createCodec(Ingredient.ALLOW_EMPTY_CODEC); + private static final Codec DISALLOW_EMPTY_CODEC = createCodec(Ingredient.DISALLOW_EMPTY_CODEC); + + private static Codec createCodec(Codec ingredientCodec) { + return ingredientCodec + .listOf() + .fieldOf("ingredients") + .xmap(AnyIngredient::new, AnyIngredient::getIngredients) + .codec(); + } + public static final CustomIngredientSerializer SERIALIZER = - new CombinedIngredient.Serializer<>(new Identifier("fabric", "any"), AnyIngredient::new); + new CombinedIngredient.Serializer<>(new Identifier("fabric", "any"), AnyIngredient::new, ALLOW_EMPTY_CODEC, DISALLOW_EMPTY_CODEC); - public AnyIngredient(Ingredient[] ingredients) { + public AnyIngredient(List ingredients) { super(ingredients); } diff --git a/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/impl/recipe/ingredient/builtin/CombinedIngredient.java b/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/impl/recipe/ingredient/builtin/CombinedIngredient.java index 375d805034..6f768d427a 100644 --- a/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/impl/recipe/ingredient/builtin/CombinedIngredient.java +++ b/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/impl/recipe/ingredient/builtin/CombinedIngredient.java @@ -16,15 +16,16 @@ package net.fabricmc.fabric.impl.recipe.ingredient.builtin; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.function.Function; -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; +import com.mojang.serialization.Codec; import net.minecraft.network.PacketByteBuf; import net.minecraft.recipe.Ingredient; import net.minecraft.util.Identifier; -import net.minecraft.util.JsonHelper; import net.fabricmc.fabric.api.recipe.v1.ingredient.CustomIngredient; import net.fabricmc.fabric.api.recipe.v1.ingredient.CustomIngredientSerializer; @@ -33,10 +34,10 @@ * Base class for ALL and ANY ingredients. */ abstract class CombinedIngredient implements CustomIngredient { - protected final Ingredient[] ingredients; + protected final List ingredients; - protected CombinedIngredient(Ingredient[] ingredients) { - if (ingredients.length == 0) { + protected CombinedIngredient(List ingredients) { + if (ingredients.isEmpty()) { throw new IllegalArgumentException("ALL or ANY ingredient must have at least one sub-ingredient"); } @@ -54,13 +55,21 @@ public boolean requiresTesting() { return false; } + List getIngredients() { + return ingredients; + } + static class Serializer implements CustomIngredientSerializer { private final Identifier identifier; - private final Function factory; + private final Function, I> factory; + private final Codec allowEmptyCodec; + private final Codec disallowEmptyCodec; - Serializer(Identifier identifier, Function factory) { + Serializer(Identifier identifier, Function, I> factory, Codec allowEmptyCodec, Codec disallowEmptyCodec) { this.identifier = identifier; this.factory = factory; + this.allowEmptyCodec = allowEmptyCodec; + this.disallowEmptyCodec = disallowEmptyCodec; } @Override @@ -69,43 +78,25 @@ public Identifier getIdentifier() { } @Override - public I read(JsonObject json) { - JsonArray values = JsonHelper.getArray(json, "ingredients"); - Ingredient[] ingredients = new Ingredient[values.size()]; - - for (int i = 0; i < values.size(); i++) { - ingredients[i] = Ingredient.fromJson(values.get(i)); - } - - return factory.apply(ingredients); - } - - @Override - public void write(JsonObject json, I ingredient) { - JsonArray values = new JsonArray(); - - for (Ingredient value : ingredient.ingredients) { - values.add(value.toJson()); - } - - json.add("ingredients", values); + public Codec getCodec(boolean allowEmpty) { + return allowEmpty ? allowEmptyCodec : disallowEmptyCodec; } @Override public I read(PacketByteBuf buf) { int size = buf.readVarInt(); - Ingredient[] ingredients = new Ingredient[size]; + List ingredients = new ArrayList<>(size); for (int i = 0; i < size; i++) { - ingredients[i] = Ingredient.fromPacket(buf); + ingredients.add(Ingredient.fromPacket(buf)); } - return factory.apply(ingredients); + return factory.apply(Collections.unmodifiableList(ingredients)); } @Override public void write(PacketByteBuf buf, I ingredient) { - buf.writeVarInt(ingredient.ingredients.length); + buf.writeVarInt(ingredient.ingredients.size()); for (Ingredient value : ingredient.ingredients) { value.write(buf); diff --git a/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/impl/recipe/ingredient/builtin/DifferenceIngredient.java b/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/impl/recipe/ingredient/builtin/DifferenceIngredient.java index ec07b4f61a..0b0be2a514 100644 --- a/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/impl/recipe/ingredient/builtin/DifferenceIngredient.java +++ b/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/impl/recipe/ingredient/builtin/DifferenceIngredient.java @@ -19,7 +19,8 @@ import java.util.ArrayList; import java.util.List; -import com.google.gson.JsonObject; +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; import net.minecraft.item.ItemStack; import net.minecraft.network.PacketByteBuf; @@ -62,25 +63,36 @@ public CustomIngredientSerializer getSerializer() { return SERIALIZER; } - private static class Serializer implements CustomIngredientSerializer { - private final Identifier id = new Identifier("fabric", "difference"); + private Ingredient getBase() { + return base; + } - @Override - public Identifier getIdentifier() { - return id; + private Ingredient getSubtracted() { + return subtracted; + } + + private static class Serializer implements CustomIngredientSerializer { + private static final Identifier ID = new Identifier("fabric", "difference"); + private static final Codec ALLOW_EMPTY_CODEC = createCodec(Ingredient.ALLOW_EMPTY_CODEC); + private static final Codec DISALLOW_EMPTY_CODEC = createCodec(Ingredient.DISALLOW_EMPTY_CODEC); + + private static Codec createCodec(Codec ingredientCodec) { + return RecordCodecBuilder.create(instance -> + instance.group( + ingredientCodec.fieldOf("base").forGetter(DifferenceIngredient::getBase), + ingredientCodec.fieldOf("subtracted").forGetter(DifferenceIngredient::getSubtracted) + ).apply(instance, DifferenceIngredient::new) + ); } @Override - public DifferenceIngredient read(JsonObject json) { - Ingredient base = Ingredient.fromJson(json.get("base")); - Ingredient subtracted = Ingredient.fromJson(json.get("subtracted")); - return new DifferenceIngredient(base, subtracted); + public Identifier getIdentifier() { + return ID; } @Override - public void write(JsonObject json, DifferenceIngredient ingredient) { - json.add("base", ingredient.base.toJson()); - json.add("subtracted", ingredient.subtracted.toJson()); + public Codec getCodec(boolean allowEmpty) { + return allowEmpty ? ALLOW_EMPTY_CODEC : DISALLOW_EMPTY_CODEC; } @Override diff --git a/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/impl/recipe/ingredient/builtin/NbtIngredient.java b/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/impl/recipe/ingredient/builtin/NbtIngredient.java index 99e5841b24..cf2d0a5394 100644 --- a/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/impl/recipe/ingredient/builtin/NbtIngredient.java +++ b/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/impl/recipe/ingredient/builtin/NbtIngredient.java @@ -21,24 +21,20 @@ import java.util.Objects; import com.mojang.brigadier.exceptions.CommandSyntaxException; -import com.mojang.serialization.JsonOps; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonSyntaxException; +import com.mojang.datafixers.util.Either; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.codecs.RecordCodecBuilder; import org.jetbrains.annotations.Nullable; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtHelper; -import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.StringNbtReader; import net.minecraft.network.PacketByteBuf; -import net.minecraft.predicate.NbtPredicate; import net.minecraft.recipe.Ingredient; import net.minecraft.util.Identifier; -import net.minecraft.util.JsonHelper; +import net.minecraft.util.dynamic.Codecs; import net.fabricmc.fabric.api.recipe.v1.ingredient.CustomIngredient; import net.fabricmc.fabric.api.recipe.v1.ingredient.CustomIngredientSerializer; @@ -98,55 +94,53 @@ public CustomIngredientSerializer getSerializer() { return SERIALIZER; } - private static class Serializer implements CustomIngredientSerializer { - private final Gson gson = new GsonBuilder().disableHtmlEscaping().create(); - private final Identifier id = new Identifier("fabric", "nbt"); + private Ingredient getBase() { + return base; + } - @Override - public Identifier getIdentifier() { - return id; - } + private NbtCompound getNbt() { + return nbt; + } - @Override - public NbtIngredient read(JsonObject json) { - Ingredient base = Ingredient.fromJson(json.get("base")); - NbtCompound nbt = readNbt(json.get("nbt")); - boolean strict = JsonHelper.getBoolean(json, "strict", false); - return new NbtIngredient(base, nbt, strict); - } + private boolean isStrict() { + return strict; + } - /** - * Inspiration taken from {@link NbtPredicate#fromJson}. - */ - @Nullable - private static NbtCompound readNbt(@Nullable JsonElement json) { - // Process null - if (json == null || json.isJsonNull()) { - return null; - } + private static class Serializer implements CustomIngredientSerializer { + private static final Identifier ID = new Identifier("fabric", "nbt"); + // Supports decoding the NBT as a string as well as the object. + private static final Codec NBT_CODEC = Codecs.xor( + Codec.STRING, NbtCompound.CODEC + ).flatXmap(either -> either.map(s -> { try { - if (json.isJsonObject()) { - // We use a normal .toString() to convert the json to string, and read it as SNBT. - // Using DynamicOps would mess with the type of integers and cause things like damage comparisons to fail... - return StringNbtReader.parse(json.toString()); - } else { - // Assume it's a string representation of the NBT - return StringNbtReader.parse(JsonHelper.asString(json, "nbt")); - } - } catch (CommandSyntaxException commandSyntaxException) { - throw new JsonSyntaxException("Invalid nbt tag: " + commandSyntaxException.getMessage()); + return DataResult.success(StringNbtReader.parse(s)); + } catch (CommandSyntaxException e) { + return DataResult.error(e::getMessage); } + }, DataResult::success), nbtCompound -> DataResult.success(Either.left(nbtCompound.asString()))); + + private static final Codec ALLOW_EMPTY_CODEC = createCodec(Ingredient.ALLOW_EMPTY_CODEC); + private static final Codec DISALLOW_EMPTY_CODEC = createCodec(Ingredient.DISALLOW_EMPTY_CODEC); + + private static Codec createCodec(Codec ingredientCodec) { + return RecordCodecBuilder.create(instance -> + instance.group( + ingredientCodec.fieldOf("base").forGetter(NbtIngredient::getBase), + NBT_CODEC.optionalFieldOf("nbt", null).forGetter(NbtIngredient::getNbt), + Codec.BOOL.optionalFieldOf("strict", false).forGetter(NbtIngredient::isStrict) + ).apply(instance, NbtIngredient::new) + ); } @Override - public void write(JsonObject json, NbtIngredient ingredient) { - json.add("base", ingredient.base.toJson()); - json.addProperty("strict", ingredient.strict); + public Identifier getIdentifier() { + return ID; + } - if (ingredient.nbt != null) { - json.add("nbt", NbtOps.INSTANCE.convertTo(JsonOps.INSTANCE, ingredient.nbt)); - } + @Override + public Codec getCodec(boolean allowEmpty) { + return allowEmpty ? ALLOW_EMPTY_CODEC : DISALLOW_EMPTY_CODEC; } @Override diff --git a/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/mixin/recipe/ingredient/IngredientMixin.java b/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/mixin/recipe/ingredient/IngredientMixin.java index 18cf5f5144..4df754a7c8 100644 --- a/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/mixin/recipe/ingredient/IngredientMixin.java +++ b/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/mixin/recipe/ingredient/IngredientMixin.java @@ -16,8 +16,7 @@ package net.fabricmc.fabric.mixin.recipe.ingredient; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; +import com.mojang.serialization.Codec; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -26,51 +25,19 @@ import net.minecraft.network.PacketByteBuf; import net.minecraft.recipe.Ingredient; import net.minecraft.util.Identifier; -import net.minecraft.util.JsonHelper; +import net.fabricmc.fabric.api.recipe.v1.ingredient.CustomIngredient; import net.fabricmc.fabric.api.recipe.v1.ingredient.CustomIngredientSerializer; import net.fabricmc.fabric.api.recipe.v1.ingredient.FabricIngredient; import net.fabricmc.fabric.impl.recipe.ingredient.CustomIngredientImpl; -import net.fabricmc.fabric.impl.recipe.ingredient.builtin.AnyIngredient; @Mixin(Ingredient.class) public class IngredientMixin implements FabricIngredient { - /** - * Inject right when vanilla detected a json object and check for our custom key. - */ - @Inject( - at = @At( - value = "INVOKE", - target = "net/minecraft/recipe/Ingredient.entryFromJson(Lcom/google/gson/JsonObject;)Lnet/minecraft/recipe/Ingredient$Entry;", - ordinal = 0 - ), - method = "fromJson(Lcom/google/gson/JsonElement;Z)Lnet/minecraft/recipe/Ingredient;", - cancellable = true - ) - private static void injectFromJson(JsonElement json, boolean requireNotEmpty, CallbackInfoReturnable cir) { - JsonObject obj = json.getAsJsonObject(); - - if (obj.has(CustomIngredientImpl.TYPE_KEY)) { - Identifier id = new Identifier(JsonHelper.getString(obj, CustomIngredientImpl.TYPE_KEY)); - CustomIngredientSerializer serializer = CustomIngredientSerializer.get(id); - - if (serializer != null) { - cir.setReturnValue(serializer.read(obj).toVanilla()); - } else { - throw new IllegalArgumentException("Unknown custom ingredient type: " + id); - } - } - } - - /** - * Throw exception when someone attempts to use our custom key inside an array ingredient. - * The {@link AnyIngredient} should be used instead. - */ - @Inject(at = @At("HEAD"), method = "entryFromJson") - private static void injectEntryFromJson(JsonObject obj, CallbackInfoReturnable cir) { - if (obj.has(CustomIngredientImpl.TYPE_KEY)) { - throw new IllegalArgumentException("Custom ingredient cannot be used inside an array ingredient. You can replace the array by a fabric:any ingredient."); - } + @Inject(method = "createCodec", at = @At("RETURN"), cancellable = true) + private static void injectCodec(boolean allowEmpty, CallbackInfoReturnable> cir) { + final Codec customIngredientCodec = allowEmpty ? CustomIngredientImpl.ALLOW_EMPTY_INGREDIENT_CODECS : CustomIngredientImpl.DISALLOW_EMPTY_INGREDIENT_CODECS; + Codec ingredientCodec = customIngredientCodec.xmap(CustomIngredient::toVanilla, FabricIngredient::getCustomIngredient); + cir.setReturnValue(CustomIngredientImpl.first(cir.getReturnValue(), ingredientCodec)); } @Inject( diff --git a/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/mixin/recipe/ingredient/PacketEncoderMixin.java b/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/mixin/recipe/ingredient/PacketEncoderMixin.java index 50d2776072..633cf5af1e 100644 --- a/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/mixin/recipe/ingredient/PacketEncoderMixin.java +++ b/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/mixin/recipe/ingredient/PacketEncoderMixin.java @@ -27,7 +27,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import net.minecraft.network.packet.Packet; -import net.minecraft.network.PacketEncoder; +import net.minecraft.network.handler.PacketEncoder; import net.minecraft.util.Identifier; import net.fabricmc.fabric.impl.recipe.ingredient.CustomIngredientSync; diff --git a/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/mixin/recipe/ingredient/ShapelessRecipeMixin.java b/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/mixin/recipe/ingredient/ShapelessRecipeMixin.java index b7eefab426..72471eee48 100644 --- a/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/mixin/recipe/ingredient/ShapelessRecipeMixin.java +++ b/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/mixin/recipe/ingredient/ShapelessRecipeMixin.java @@ -33,7 +33,6 @@ import net.minecraft.recipe.Ingredient; import net.minecraft.recipe.ShapelessRecipe; import net.minecraft.recipe.book.CraftingRecipeCategory; -import net.minecraft.util.Identifier; import net.minecraft.util.collection.DefaultedList; import net.minecraft.world.World; @@ -43,12 +42,12 @@ public class ShapelessRecipeMixin { @Final @Shadow - DefaultedList input; + DefaultedList ingredients; @Unique private boolean fabric_requiresTesting = false; @Inject(at = @At("RETURN"), method = "") - private void cacheRequiresTesting(Identifier id, String group, CraftingRecipeCategory category, ItemStack output, DefaultedList input, CallbackInfo ci) { + private void cacheRequiresTesting(String group, CraftingRecipeCategory category, ItemStack output, DefaultedList input, CallbackInfo ci) { for (Ingredient ingredient : input) { if (ingredient.requiresTesting()) { fabric_requiresTesting = true; @@ -70,7 +69,7 @@ public void customIngredientMatch(RecipeInputInventory craftingInventory, World } } - cir.setReturnValue(ShapelessMatch.isMatch(nonEmptyStacks, input)); + cir.setReturnValue(ShapelessMatch.isMatch(nonEmptyStacks, ingredients)); } } } diff --git a/fabric-recipe-api-v1/src/testmod/java/net/fabricmc/fabric/test/recipe/ingredient/SerializationTests.java b/fabric-recipe-api-v1/src/testmod/java/net/fabricmc/fabric/test/recipe/ingredient/SerializationTests.java index bdebebf7a9..98050b5064 100644 --- a/fabric-recipe-api-v1/src/testmod/java/net/fabricmc/fabric/test/recipe/ingredient/SerializationTests.java +++ b/fabric-recipe-api-v1/src/testmod/java/net/fabricmc/fabric/test/recipe/ingredient/SerializationTests.java @@ -16,15 +16,22 @@ package net.fabricmc.fabric.test.recipe.ingredient; +import java.util.List; + import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; import com.google.gson.JsonParser; +import com.mojang.serialization.JsonOps; +import net.minecraft.item.Items; import net.minecraft.recipe.Ingredient; import net.minecraft.test.GameTest; import net.minecraft.test.GameTestException; import net.minecraft.test.TestContext; +import net.minecraft.util.Util; import net.fabricmc.fabric.api.gametest.v1.FabricGameTest; +import net.fabricmc.fabric.impl.recipe.ingredient.builtin.AllIngredient; public class SerializationTests { /** @@ -49,10 +56,27 @@ public void testArrayDeserialization(TestContext context) { JsonElement json = JsonParser.parseString(ingredientJson); try { - Ingredient.fromJson(json); + Util.getResult(Ingredient.DISALLOW_EMPTY_CODEC.parse(JsonOps.INSTANCE, json), JsonParseException::new); throw new GameTestException("Using a custom ingredient inside an array ingredient should have failed."); - } catch (IllegalArgumentException e) { + } catch (JsonParseException e) { context.complete(); } } + + /** + * Check that we can serialise a custom ingredient. + */ + @GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE) + public void testCustomIngredientSerialization(TestContext context) { + String ingredientJson = """ + {"ingredients":[{"item":"minecraft:stone"}],"fabric:type":"fabric:all"} + """.trim(); + + var ingredient = new AllIngredient(List.of( + Ingredient.ofItems(Items.STONE) + )); + String json = ingredient.toVanilla().toJson(false).toString(); + context.assertTrue(json.equals(ingredientJson), "Unexpected json: " + json); + context.complete(); + } } diff --git a/fabric-recipe-api-v1/src/testmod/java/net/fabricmc/fabric/test/recipe/ingredient/ShapelessRecipeMatchTests.java b/fabric-recipe-api-v1/src/testmod/java/net/fabricmc/fabric/test/recipe/ingredient/ShapelessRecipeMatchTests.java index 96ac83abda..085502c541 100644 --- a/fabric-recipe-api-v1/src/testmod/java/net/fabricmc/fabric/test/recipe/ingredient/ShapelessRecipeMatchTests.java +++ b/fabric-recipe-api-v1/src/testmod/java/net/fabricmc/fabric/test/recipe/ingredient/ShapelessRecipeMatchTests.java @@ -36,7 +36,7 @@ public class ShapelessRecipeMatchTests { @GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE) public void testShapelessMatch(TestContext context) { Identifier recipeId = new Identifier("fabric-recipe-api-v1-testmod", "test_shapeless_match"); - ShapelessRecipe recipe = (ShapelessRecipe) context.getWorld().getRecipeManager().get(recipeId).get(); + ShapelessRecipe recipe = (ShapelessRecipe) context.getWorld().getRecipeManager().get(recipeId).get().value(); ItemStack undamagedPickaxe = new ItemStack(Items.DIAMOND_PICKAXE); ItemStack damagedPickaxe = new ItemStack(Items.DIAMOND_PICKAXE); diff --git a/fabric-recipe-api-v1/src/testmod/resources/data/fabric-recipe-api-v1-testmod/recipes/test_shapeless_match.json b/fabric-recipe-api-v1/src/testmod/resources/data/fabric-recipe-api-v1-testmod/recipes/test_shapeless_match.json index 754bf94aa6..ba924fb411 100644 --- a/fabric-recipe-api-v1/src/testmod/resources/data/fabric-recipe-api-v1-testmod/recipes/test_shapeless_match.json +++ b/fabric-recipe-api-v1/src/testmod/resources/data/fabric-recipe-api-v1-testmod/recipes/test_shapeless_match.json @@ -18,9 +18,7 @@ "base": { "item": "minecraft:diamond_pickaxe" }, - "nbt": { - "Damage": 0 - }, + "nbt": "{Damage:0}", "strict": false }, { diff --git a/fabric-registry-sync-v0/build.gradle b/fabric-registry-sync-v0/build.gradle index e161b139c2..2dd05dd71d 100644 --- a/fabric-registry-sync-v0/build.gradle +++ b/fabric-registry-sync-v0/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-registry-sync-v0" version = getSubprojectVersion(project) loom { diff --git a/fabric-registry-sync-v0/src/client/java/net/fabricmc/fabric/impl/client/registry/sync/FabricRegistryClientInit.java b/fabric-registry-sync-v0/src/client/java/net/fabricmc/fabric/impl/client/registry/sync/FabricRegistryClientInit.java index 67300a6d3b..acc73102bd 100644 --- a/fabric-registry-sync-v0/src/client/java/net/fabricmc/fabric/impl/client/registry/sync/FabricRegistryClientInit.java +++ b/fabric-registry-sync-v0/src/client/java/net/fabricmc/fabric/impl/client/registry/sync/FabricRegistryClientInit.java @@ -16,16 +16,21 @@ package net.fabricmc.fabric.impl.client.registry.sync; +import java.util.concurrent.CompletionException; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import net.minecraft.text.Text; import net.fabricmc.api.ClientModInitializer; -import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationNetworking; +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; +import net.fabricmc.fabric.impl.registry.sync.FabricRegistryInit; import net.fabricmc.fabric.impl.registry.sync.RegistrySyncManager; import net.fabricmc.fabric.impl.registry.sync.RemapException; import net.fabricmc.fabric.impl.registry.sync.packet.RegistryPacketHandler; +import net.fabricmc.fabric.mixin.networking.client.accessor.ClientCommonNetworkHandlerAccessor; public class FabricRegistryClientInit implements ClientModInitializer { private static final Logger LOGGER = LoggerFactory.getLogger(FabricRegistryClientInit.class); @@ -36,20 +41,31 @@ public void onInitializeClient() { } private void registerSyncPacketReceiver(RegistryPacketHandler packetHandler) { - ClientPlayNetworking.registerGlobalReceiver(packetHandler.getPacketId(), (client, handler, buf, responseSender) -> - RegistrySyncManager.receivePacket(client, packetHandler, buf, RegistrySyncManager.DEBUG || !client.isInSingleplayer(), (e) -> { - LOGGER.error("Registry remapping failed!", e); - client.execute(() -> handler.getConnection().disconnect(getText(e))); - })); + ClientConfigurationNetworking.registerGlobalReceiver(packetHandler.getPacketId(), (client, handler, buf, responseSender) -> { + RegistrySyncManager.receivePacket(client, packetHandler, buf, RegistrySyncManager.DEBUG || !client.isInSingleplayer()) + .whenComplete((complete, throwable) -> { + if (throwable != null) { + LOGGER.error("Registry remapping failed!", throwable); + client.execute(() -> ((ClientCommonNetworkHandlerAccessor) handler).getConnection().disconnect(getText(throwable))); + return; + } + + if (complete) { + handler.sendPacket(ClientConfigurationNetworking.createC2SPacket(FabricRegistryInit.SYNC_COMPLETE_ID, PacketByteBufs.create())); + } + }); + }); } - private Text getText(Exception e) { + private Text getText(Throwable e) { if (e instanceof RemapException remapException) { final Text text = remapException.getText(); if (text != null) { return text; } + } else if (e instanceof CompletionException completionException) { + return getText(completionException.getCause()); } return Text.literal("Registry remapping failed: " + e.getMessage()); diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/api/event/registry/DynamicRegistries.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/api/event/registry/DynamicRegistries.java new file mode 100644 index 0000000000..628d6adada --- /dev/null +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/api/event/registry/DynamicRegistries.java @@ -0,0 +1,158 @@ +/* + * 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 net.fabricmc.fabric.api.event.registry; + +import java.util.List; + +import com.mojang.serialization.Codec; +import org.jetbrains.annotations.Unmodifiable; + +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.RegistryLoader; + +import net.fabricmc.fabric.impl.registry.sync.DynamicRegistriesImpl; + +/** + * Contains methods for registering and accessing dynamic {@linkplain Registry registries}. + * + *

    Basic usage

    + * Custom dynamic registries can be registered with {@link #register(RegistryKey, Codec)}. These registries will not be + * synced to the client. + * + *

    The list of all dynamic registries, whether from vanilla or mods, can be accessed using + * {@link #getDynamicRegistries()}. + * + *

    Tags for the entries of a custom registry must be placed in + * {@code /tags///}. For example, the tags for the example + * registry below would be placed in {@code /tags/my_mod/my_data/}. + * + *

    Synchronization

    + * Dynamic registries are not synchronized to the client by default. + * To register a synced dynamic registry, you can replace the {@link #register} call + * with a call to {@link #registerSynced(RegistryKey, Codec, SyncOption...)}. + * + *

    If you want to use a different codec for syncing, e.g. to skip unnecessary data, + * you can use the overload with two codecs: {@link #registerSynced(RegistryKey, Codec, Codec, SyncOption...)}. + * + *

    Synced dynamic registries can also be prevented from syncing if they have no entries. + * This is useful for compatibility with clients that might not have your dynamic registry. + * This behavior can be enabled by passing the {@link SyncOption#SKIP_WHEN_EMPTY} flag to {@code registerSynced}. + * + *

    Examples

    + * {@snippet : + * // @link region substring=RegistryKey target=RegistryKey + * // @link region substring=ofRegistry target="RegistryKey#ofRegistry" + * // @link region substring=Identifier target="net.minecraft.util.Identifier#Identifier(String, String)" + * public static final RegistryKey> MY_DATA_KEY = RegistryKey.ofRegistry(new Identifier("my_mod", "my_data")); + * // @end @end @end + * + * // Option 1: Register a non-synced registry + * // @link substring=register target="#register": + * DynamicRegistries.register(MY_DATA_KEY, MyData.CODEC); + * + * // Option 2a: Register a synced registry + * // @link substring=registerSynced target="#registerSynced(RegistryKey, Codec, SyncOption...)": + * DynamicRegistries.registerSynced(MY_DATA_KEY, MyData.CODEC); + * + * // Option 2b: Register a synced registry with a different network codec + * // @link substring=registerSynced target="#registerSynced(RegistryKey, Codec, Codec, SyncOption...)": + * DynamicRegistries.registerSynced(MY_DATA_KEY, MyData.CODEC, MyData.NETWORK_CODEC); + * } + */ +public final class DynamicRegistries { + private DynamicRegistries() { + } + + /** + * Returns an unmodifiable list of all dynamic registries, including modded ones. + * + *

    The list will not reflect any changes caused by later registrations. + * + * @return an unmodifiable list of all dynamic registries + */ + public static @Unmodifiable List> getDynamicRegistries() { + return DynamicRegistriesImpl.getDynamicRegistries(); + } + + /** + * Registers a non-synced dynamic registry. + * + *

    The entries of the registry will be loaded from data packs at the file path + * {@code data////.json}. + * + * @param key the unique key of the registry + * @param codec the codec used to load registry entries from data packs + * @param the entry type of the registry + */ + public static void register(RegistryKey> key, Codec codec) { + DynamicRegistriesImpl.register(key, codec); + } + + /** + * Registers a synced dynamic registry. + * + *

    The entries of the registry will be loaded from data packs at the file path + * {@code data////.json}. + * + *

    The registry will be synced from the server to players' clients using the same codec + * that is used to load the registry. + * + *

    If the object contained in the registry is complex and contains a lot of data + * that is not relevant on the client, another codec for networking can be specified with + * {@link #registerSynced(RegistryKey, Codec, Codec, SyncOption...)}. + * + * @param key the unique key of the registry + * @param codec the codec used to load registry entries from data packs and the network + * @param options options to configure syncing + * @param the entry type of the registry + */ + public static void registerSynced(RegistryKey> key, Codec codec, SyncOption... options) { + registerSynced(key, codec, codec, options); + } + + /** + * Registers a synced dynamic registry. + * + *

    The entries of the registry will be loaded from data packs at the file path + * {@code data////.json} + * + *

    The registry will be synced from the server to players' clients using the given network codec. + * + * @param key the unique key of the registry + * @param dataCodec the codec used to load registry entries from data packs + * @param networkCodec the codec used to load registry entries from the network + * @param options options to configure syncing + * @param the entry type of the registry + */ + public static void registerSynced(RegistryKey> key, Codec dataCodec, Codec networkCodec, SyncOption... options) { + DynamicRegistriesImpl.register(key, dataCodec); + DynamicRegistriesImpl.addSyncedRegistry(key, networkCodec, options); + } + + /** + * Flags for configuring dynamic registry syncing. + */ + public enum SyncOption { + /** + * Only synchronizes the dynamic registry if it's not empty. + * This is useful for compatibility with vanilla clients, + * or other clients that might not have the registry. + */ + SKIP_WHEN_EMPTY + } +} diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/api/event/registry/FabricRegistryBuilder.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/api/event/registry/FabricRegistryBuilder.java index 7bc31c316b..0a35a321a7 100644 --- a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/api/event/registry/FabricRegistryBuilder.java +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/api/event/registry/FabricRegistryBuilder.java @@ -44,6 +44,10 @@ * } *

    * + *

    Tags for the entries of a custom registry must be placed in + * {@code /tags///}. For example, the tags for the example + * registry above would be placed in {@code /tags/modid/registry_name/}. + * * @param The type stored in the Registry * @param The registry type */ diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/api/event/registry/RegistryAttribute.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/api/event/registry/RegistryAttribute.java index 4e9da163af..104e687e26 100644 --- a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/api/event/registry/RegistryAttribute.java +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/api/event/registry/RegistryAttribute.java @@ -17,11 +17,6 @@ package net.fabricmc.fabric.api.event.registry; public enum RegistryAttribute { - /** - * Registry will be saved to disk when modded. - */ - PERSISTED, - /** * Registry will be synced to the client when modded. */ diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/DynamicRegistriesImpl.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/DynamicRegistriesImpl.java new file mode 100644 index 0000000000..83c5f41206 --- /dev/null +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/DynamicRegistriesImpl.java @@ -0,0 +1,85 @@ +/* + * 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 net.fabricmc.fabric.impl.registry.sync; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import com.mojang.serialization.Codec; +import org.jetbrains.annotations.Unmodifiable; + +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.RegistryLoader; +import net.minecraft.registry.SerializableRegistries; + +import net.fabricmc.fabric.api.event.registry.DynamicRegistries; + +public final class DynamicRegistriesImpl { + private static final List> DYNAMIC_REGISTRIES = new ArrayList<>(RegistryLoader.DYNAMIC_REGISTRIES); + public static final Set> FABRIC_DYNAMIC_REGISTRY_KEYS = new HashSet<>(); + public static final Set>> DYNAMIC_REGISTRY_KEYS = new HashSet<>(); + public static final Set>> SKIP_EMPTY_SYNC_REGISTRIES = new HashSet<>(); + + static { + for (RegistryLoader.Entry vanillaEntry : RegistryLoader.DYNAMIC_REGISTRIES) { + DYNAMIC_REGISTRY_KEYS.add(vanillaEntry.key()); + } + } + + private DynamicRegistriesImpl() { + } + + public static @Unmodifiable List> getDynamicRegistries() { + return List.copyOf(DYNAMIC_REGISTRIES); + } + + public static void register(RegistryKey> key, Codec codec) { + Objects.requireNonNull(key, "Registry key cannot be null"); + Objects.requireNonNull(codec, "Codec cannot be null"); + + if (!DYNAMIC_REGISTRY_KEYS.add(key)) { + throw new IllegalArgumentException("Dynamic registry " + key + " has already been registered!"); + } + + var entry = new RegistryLoader.Entry<>(key, codec); + DYNAMIC_REGISTRIES.add(entry); + FABRIC_DYNAMIC_REGISTRY_KEYS.add(key); + } + + public static void addSyncedRegistry(RegistryKey> registryKey, Codec networkCodec, DynamicRegistries.SyncOption... options) { + Objects.requireNonNull(registryKey, "Registry key cannot be null"); + Objects.requireNonNull(networkCodec, "Network codec cannot be null"); + Objects.requireNonNull(options, "Options cannot be null"); + + if (!(SerializableRegistries.REGISTRIES instanceof HashMap)) { + SerializableRegistries.REGISTRIES = new HashMap<>(SerializableRegistries.REGISTRIES); + } + + SerializableRegistries.REGISTRIES.put(registryKey, new SerializableRegistries.Info<>(registryKey, networkCodec)); + + for (DynamicRegistries.SyncOption option : options) { + if (option == DynamicRegistries.SyncOption.SKIP_WHEN_EMPTY) { + SKIP_EMPTY_SYNC_REGISTRIES.add(registryKey); + } + } + } +} diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/FabricRegistryInit.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/FabricRegistryInit.java index 8edf67df41..3a02452481 100644 --- a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/FabricRegistryInit.java +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/FabricRegistryInit.java @@ -17,17 +17,23 @@ package net.fabricmc.fabric.impl.registry.sync; import net.minecraft.registry.Registries; +import net.minecraft.util.Identifier; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.event.registry.RegistryAttribute; import net.fabricmc.fabric.api.event.registry.RegistryAttributeHolder; -import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; +import net.fabricmc.fabric.api.networking.v1.ServerConfigurationConnectionEvents; +import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking; public class FabricRegistryInit implements ModInitializer { + public static final Identifier SYNC_COMPLETE_ID = new Identifier("fabric", "registry/sync/complete"); + @Override public void onInitialize() { - ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> - RegistrySyncManager.sendPacket(server, handler.player)); + ServerConfigurationConnectionEvents.BEFORE_CONFIGURE.register(RegistrySyncManager::configureClient); + ServerConfigurationNetworking.registerGlobalReceiver(SYNC_COMPLETE_ID, (server, handler, buf, responseSender) -> { + handler.completeTask(RegistrySyncManager.SyncConfigurationTask.KEY); + }); // Synced in PlaySoundS2CPacket. RegistryAttributeHolder.get(Registries.SOUND_EVENT) @@ -39,8 +45,7 @@ public void onInitialize() { // StatusEffectInstance serialises with raw id. RegistryAttributeHolder.get(Registries.STATUS_EFFECT) - .addAttribute(RegistryAttribute.SYNCED) - .addAttribute(RegistryAttribute.PERSISTED); + .addAttribute(RegistryAttribute.SYNCED); // Synced in ChunkDeltaUpdateS2CPacket among other places, a pallet is used when saving. RegistryAttributeHolder.get(Registries.BLOCK) diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/RegistrySyncManager.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/RegistrySyncManager.java index d03530bccf..73c3d32908 100644 --- a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/RegistrySyncManager.java +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/RegistrySyncManager.java @@ -26,9 +26,8 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import java.util.function.Consumer; import com.google.common.base.Joiner; @@ -45,11 +44,13 @@ import net.minecraft.nbt.NbtCompound; import net.minecraft.network.PacketByteBuf; +import net.minecraft.network.packet.Packet; import net.minecraft.registry.Registries; import net.minecraft.registry.Registry; import net.minecraft.registry.RegistryKey; import net.minecraft.server.MinecraftServer; -import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.network.ServerConfigurationNetworkHandler; +import net.minecraft.server.network.ServerPlayerConfigurationTask; import net.minecraft.text.MutableText; import net.minecraft.text.Text; import net.minecraft.util.Formatting; @@ -58,6 +59,7 @@ import net.fabricmc.fabric.api.event.registry.RegistryAttribute; import net.fabricmc.fabric.api.event.registry.RegistryAttributeHolder; +import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking; import net.fabricmc.fabric.impl.registry.sync.packet.DirectRegistryPacketHandler; import net.fabricmc.fabric.impl.registry.sync.packet.RegistryPacketHandler; @@ -74,27 +76,49 @@ public final class RegistrySyncManager { private RegistrySyncManager() { } - public static void sendPacket(MinecraftServer server, ServerPlayerEntity player) { - if (!DEBUG && server.isHost(player.getGameProfile())) { + public static void configureClient(ServerConfigurationNetworkHandler handler, MinecraftServer server) { + if (!DEBUG && server.isHost(handler.getDebugProfile())) { + // Dont send in singleplayer return; } - sendPacket(player, DIRECT_PACKET_HANDLER); + if (!ServerConfigurationNetworking.canSend(handler, DIRECT_PACKET_HANDLER.getPacketId())) { + // Don't send if the client cannot receive + return; + } + + final Map> map = RegistrySyncManager.createAndPopulateRegistryMap(); + + if (map == null) { + // Don't send when there is nothing to map + return; + } + + handler.addTask(new SyncConfigurationTask(handler, map)); } - private static void sendPacket(ServerPlayerEntity player, RegistryPacketHandler handler) { - Map> map = RegistrySyncManager.createAndPopulateRegistryMap(true, null); + public record SyncConfigurationTask( + ServerConfigurationNetworkHandler handler, + Map> map + ) implements ServerPlayerConfigurationTask { + public static final Key KEY = new Key("fabric:registry/sync"); + + @Override + public void sendPacket(Consumer> sender) { + DIRECT_PACKET_HANDLER.sendPacket(handler::sendPacket, map); + } - if (map != null) { - handler.sendPacket(player, map); + @Override + public Key getKey() { + return KEY; } } - public static void receivePacket(ThreadExecutor executor, RegistryPacketHandler handler, PacketByteBuf buf, boolean accept, Consumer errorHandler) { + public static CompletableFuture receivePacket(ThreadExecutor executor, RegistryPacketHandler handler, PacketByteBuf buf, boolean accept) { handler.receivePacket(buf); if (!handler.isPacketFinished()) { - return; + return CompletableFuture.completedFuture(false); } if (DEBUG) { @@ -106,37 +130,31 @@ public static void receivePacket(ThreadExecutor executor, RegistryPacketHandl Map> map = handler.getSyncedRegistryMap(); - if (accept) { - try { - executor.submit(() -> { - if (map == null) { - errorHandler.accept(new RemapException("Received null map in sync packet!")); - return null; - } + if (!accept) { + return CompletableFuture.completedFuture(true); + } - try { - apply(map, RemappableRegistry.RemapMode.REMOTE); - } catch (RemapException e) { - errorHandler.accept(e); - } + return executor.submit(() -> { + if (map == null) { + throw new CompletionException(new RemapException("Received null map in sync packet!")); + } - return null; - }).get(30, TimeUnit.SECONDS); - } catch (ExecutionException | InterruptedException | TimeoutException e) { - errorHandler.accept(e); + try { + apply(map, RemappableRegistry.RemapMode.REMOTE); + return true; + } catch (RemapException e) { + throw new CompletionException(e); } - } + }); } /** - * Creates a {@link NbtCompound} used to save or sync the registry ids. + * Creates a {@link NbtCompound} used to sync the registry ids. * - * @param isClientSync true when syncing to the client, false when saving - * @param activeMap contains the registry ids that were previously read and applied, can be null. - * @return a {@link NbtCompound} to save or sync, null when empty + * @return a {@link NbtCompound} to sync, null when empty */ @Nullable - public static Map> createAndPopulateRegistryMap(boolean isClientSync, @Nullable Map> activeMap) { + public static Map> createAndPopulateRegistryMap() { Map> map = new LinkedHashMap<>(); for (Identifier registryId : Registries.REGISTRIES.getIds()) { @@ -178,43 +196,25 @@ public static Map> createAndPopulateRegist } } - /* - * This contains the previous state's registry data, this is used for a few things: - * Such as ensuring that previously modded registries or registry entries are not lost or overwritten. - */ - Object2IntMap previousIdMap = null; - - if (activeMap != null && activeMap.containsKey(registryId)) { - previousIdMap = activeMap.get(registryId); - } - RegistryAttributeHolder attributeHolder = RegistryAttributeHolder.get(registry.getKey()); - if (!attributeHolder.hasAttribute(isClientSync ? RegistryAttribute.SYNCED : RegistryAttribute.PERSISTED)) { - LOGGER.debug("Not {} registry: {}", isClientSync ? "syncing" : "saving", registryId); + if (!attributeHolder.hasAttribute(RegistryAttribute.SYNCED)) { + LOGGER.debug("Not syncing registry: {}", registryId); continue; } /* * Dont do anything with vanilla registries on client sync. - * When saving skip none modded registries that doesnt have previous registry data * * This will not sync IDs if a world has been previously modded, either from removed mods - * or a previous version of fabric registry sync, but will save these ids to disk in case the mod or mods - * are added back. + * or a previous version of fabric registry sync. */ - if ((previousIdMap == null || isClientSync) && !attributeHolder.hasAttribute(RegistryAttribute.MODDED)) { + if (!attributeHolder.hasAttribute(RegistryAttribute.MODDED)) { LOGGER.debug("Skipping un-modded registry: " + registryId); continue; - } else if (previousIdMap != null) { - LOGGER.debug("Preserving previously modded registry: " + registryId); } - if (isClientSync) { - LOGGER.debug("Syncing registry: " + registryId); - } else { - LOGGER.debug("Saving registry: " + registryId); - } + LOGGER.debug("Syncing registry: " + registryId); if (registry instanceof RemappableRegistry) { Object2IntMap idMap = new Object2IntLinkedOpenHashMap<>(); @@ -245,33 +245,10 @@ public static Map> createAndPopulateRegist idMap.put(id, rawId); } - /* - * Look for existing registry key/values that are not in the current registries. - * This can happen when registry entries are removed, preventing that ID from being re-used by something else. - */ - if (!isClientSync && previousIdMap != null) { - for (Identifier key : previousIdMap.keySet()) { - if (!idMap.containsKey(key)) { - LOGGER.debug("Saving orphaned registry entry: " + key); - idMap.put(key, previousIdMap.getInt(key)); - } - } - } - map.put(registryId, idMap); } } - // Ensure any orphaned registry's are kept on disk - if (!isClientSync && activeMap != null) { - for (Identifier registryKey : activeMap.keySet()) { - if (!map.containsKey(registryKey)) { - LOGGER.debug("Saving orphaned registry: " + registryKey); - map.put(registryKey, activeMap.get(registryKey)); - } - } - } - if (map.isEmpty()) { return null; } diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/packet/DirectRegistryPacketHandler.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/packet/DirectRegistryPacketHandler.java index 3386da0ed8..a83692a6de 100644 --- a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/packet/DirectRegistryPacketHandler.java +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/packet/DirectRegistryPacketHandler.java @@ -22,6 +22,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.function.Consumer; import java.util.stream.Collectors; import com.google.common.base.Preconditions; @@ -30,7 +31,7 @@ import org.jetbrains.annotations.Nullable; import net.minecraft.network.PacketByteBuf; -import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.network.packet.Packet; import net.minecraft.util.Identifier; import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; @@ -73,7 +74,7 @@ public Identifier getPacketId() { } @Override - public void sendPacket(ServerPlayerEntity player, Map> registryMap) { + public void sendPacket(Consumer> sender, Map> registryMap) { PacketByteBuf buf = PacketByteBufs.create(); // Group registry ids with same namespace. @@ -152,12 +153,12 @@ public void sendPacket(ServerPlayerEntity player, Map> registryMap); + public abstract void sendPacket(Consumer> sender, Map> registryMap); public abstract void receivePacket(PacketByteBuf buf); @@ -48,8 +49,8 @@ public abstract class RegistryPacketHandler { @Nullable public abstract Map> getSyncedRegistryMap(); - protected final void sendPacket(ServerPlayerEntity player, PacketByteBuf buf) { - ServerPlayNetworking.send(player, getPacketId(), buf); + protected final void sendPacket(Consumer> sender, PacketByteBuf buf) { + sender.accept(ServerConfigurationNetworking.createS2CPacket(getPacketId(), buf)); } protected final void computeBufSize(PacketByteBuf buf) { diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/LevelStorageSessionMixin.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/LevelStorageSessionMixin.java deleted file mode 100644 index f35efdbb0d..0000000000 --- a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/LevelStorageSessionMixin.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * 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 net.fabricmc.fabric.mixin.registry.sync; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.nio.file.Files; -import java.util.Map; - -import it.unimi.dsi.fastutil.objects.Object2IntMap; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.Unique; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -import net.minecraft.nbt.NbtCompound; -import net.minecraft.nbt.NbtIo; -import net.minecraft.util.Identifier; -import net.minecraft.registry.DynamicRegistryManager; -import net.minecraft.world.SaveProperties; -import net.minecraft.world.level.storage.LevelStorage; - -import net.fabricmc.fabric.impl.registry.sync.RegistryMapSerializer; -import net.fabricmc.fabric.impl.registry.sync.RegistrySyncManager; -import net.fabricmc.fabric.impl.registry.sync.RemapException; -import net.fabricmc.fabric.impl.registry.sync.RemappableRegistry; - -@Mixin(LevelStorage.Session.class) -public class LevelStorageSessionMixin { - @Unique - private static final int FABRIC_ID_REGISTRY_BACKUPS = 3; - @Unique - private static final Logger FABRIC_LOGGER = LoggerFactory.getLogger("FabricRegistrySync"); - @Unique - private Map> fabric_lastSavedRegistryMap = null; - @Unique - private Map> fabric_activeRegistryMap = null; - - @Shadow - @Final - private LevelStorage.LevelSave directory; - - @Unique - private boolean fabric_readIdMapFile(File file) throws IOException, RemapException { - FABRIC_LOGGER.debug("Reading registry data from " + file.toString()); - - if (file.exists()) { - FileInputStream fileInputStream = new FileInputStream(file); - NbtCompound tag = NbtIo.readCompressed(fileInputStream); - fileInputStream.close(); - - if (tag != null) { - fabric_activeRegistryMap = RegistryMapSerializer.fromNbt(tag); - RegistrySyncManager.apply(fabric_activeRegistryMap, RemappableRegistry.RemapMode.AUTHORITATIVE); - return true; - } - } - - return false; - } - - @Unique - private File fabric_getWorldIdMapFile(int i) { - return new File(new File(directory.path().toFile(), "data"), "fabricRegistry" + ".dat" + (i == 0 ? "" : ("." + i))); - } - - @Unique - private void fabric_saveRegistryData() { - FABRIC_LOGGER.debug("Starting registry save"); - Map> newMap = RegistrySyncManager.createAndPopulateRegistryMap(false, fabric_activeRegistryMap); - - if (newMap == null) { - FABRIC_LOGGER.debug("Not saving empty registry data"); - return; - } - - if (!newMap.equals(fabric_lastSavedRegistryMap)) { - for (int i = FABRIC_ID_REGISTRY_BACKUPS - 1; i >= 0; i--) { - File file = fabric_getWorldIdMapFile(i); - - if (file.exists()) { - if (i == FABRIC_ID_REGISTRY_BACKUPS - 1) { - file.delete(); - } else { - File target = fabric_getWorldIdMapFile(i + 1); - file.renameTo(target); - } - } - } - - try { - File file = fabric_getWorldIdMapFile(0); - File parentFile = file.getParentFile(); - - if (!parentFile.exists()) { - if (!parentFile.mkdirs()) { - FABRIC_LOGGER.warn("[fabric-registry-sync] Could not create directory " + parentFile + "!"); - } - } - - FABRIC_LOGGER.debug("Saving registry data to " + file); - FileOutputStream fileOutputStream = new FileOutputStream(file); - NbtIo.writeCompressed(RegistryMapSerializer.toNbt(newMap), fileOutputStream); - fileOutputStream.close(); - } catch (IOException e) { - FABRIC_LOGGER.warn("[fabric-registry-sync] Failed to save registry file!", e); - } - - fabric_lastSavedRegistryMap = newMap; - } - } - - @Inject(method = "backupLevelDataFile(Lnet/minecraft/registry/DynamicRegistryManager;Lnet/minecraft/world/SaveProperties;Lnet/minecraft/nbt/NbtCompound;)V", at = @At("HEAD")) - public void saveWorld(DynamicRegistryManager registryTracker, SaveProperties saveProperties, NbtCompound compoundTag, CallbackInfo info) { - if (!Files.exists(directory.path())) { - return; - } - - fabric_saveRegistryData(); - } - - // TODO: stop double save on client? - @Inject(method = "readLevelProperties", at = @At("HEAD")) - public void readWorldProperties(CallbackInfoReturnable callbackInfo) { - // Load - for (int i = 0; i < FABRIC_ID_REGISTRY_BACKUPS; i++) { - FABRIC_LOGGER.trace("[fabric-registry-sync] Loading Fabric registry [file " + (i + 1) + "/" + (FABRIC_ID_REGISTRY_BACKUPS + 1) + "]"); - - try { - if (fabric_readIdMapFile(fabric_getWorldIdMapFile(i))) { - FABRIC_LOGGER.info("[fabric-registry-sync] Loaded registry data [file " + (i + 1) + "/" + (FABRIC_ID_REGISTRY_BACKUPS + 1) + "]"); - return; - } - } catch (FileNotFoundException e) { - // pass - } catch (IOException e) { - if (i >= FABRIC_ID_REGISTRY_BACKUPS - 1) { - throw new RuntimeException(e); - } else { - FABRIC_LOGGER.warn("Reading registry file failed!", e); - } - } catch (RemapException e) { - throw new RuntimeException("Remapping world failed!", e); - } - } - - // If not returned (not present), try saving the registry data - fabric_saveRegistryData(); - } -} diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/RegistryLoaderMixin.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/RegistryLoaderMixin.java index 5486ea43fb..550b923ade 100644 --- a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/RegistryLoaderMixin.java +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/RegistryLoaderMixin.java @@ -34,8 +34,10 @@ import net.minecraft.registry.RegistryLoader; import net.minecraft.registry.RegistryOps; import net.minecraft.resource.ResourceManager; +import net.minecraft.util.Identifier; import net.fabricmc.fabric.api.event.registry.DynamicRegistrySetupCallback; +import net.fabricmc.fabric.impl.registry.sync.DynamicRegistriesImpl; import net.fabricmc.fabric.impl.registry.sync.DynamicRegistryViewImpl; @Mixin(RegistryLoader.class) @@ -58,4 +60,15 @@ private static void beforeLoad(ResourceManager resourceManager, DynamicRegistryM DynamicRegistrySetupCallback.EVENT.invoker().onRegistrySetup(new DynamicRegistryViewImpl(registries)); } + + // Vanilla doesn't mark namespaces in the directories of dynamic registries at all, + // so we prepend the directories with the namespace if it's a modded registry registered using the Fabric API. + @Inject(method = "getPath", at = @At("RETURN"), cancellable = true) + private static void prependDirectoryWithNamespace(Identifier id, CallbackInfoReturnable info) { + if (!id.getNamespace().equals(Identifier.DEFAULT_NAMESPACE) + && DynamicRegistriesImpl.FABRIC_DYNAMIC_REGISTRY_KEYS.contains(RegistryKey.ofRegistry(id))) { + final String newPath = id.getNamespace() + "/" + info.getReturnValue(); + info.setReturnValue(newPath); + } + } } diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/SaveLoadingMixin.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/SaveLoadingMixin.java new file mode 100644 index 0000000000..1366f2ea72 --- /dev/null +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/SaveLoadingMixin.java @@ -0,0 +1,37 @@ +/* + * 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 net.fabricmc.fabric.mixin.registry.sync; + +import java.util.List; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArg; + +import net.minecraft.registry.RegistryLoader; +import net.minecraft.server.SaveLoading; + +import net.fabricmc.fabric.api.event.registry.DynamicRegistries; + +// Implements dynamic registry loading. +@Mixin(SaveLoading.class) +abstract class SaveLoadingMixin { + @ModifyArg(method = "load", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/SaveLoading;withRegistriesLoaded(Lnet/minecraft/resource/ResourceManager;Lnet/minecraft/registry/CombinedDynamicRegistries;Lnet/minecraft/registry/ServerDynamicRegistryType;Ljava/util/List;)Lnet/minecraft/registry/CombinedDynamicRegistries;"), allow = 1) + private static List> modifyLoadedEntries(List> entries) { + return DynamicRegistries.getDynamicRegistries(); + } +} diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/SerializableRegistriesMixin.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/SerializableRegistriesMixin.java new file mode 100644 index 0000000000..3224d62a39 --- /dev/null +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/SerializableRegistriesMixin.java @@ -0,0 +1,48 @@ +/* + * 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 net.fabricmc.fabric.mixin.registry.sync; + +import java.util.stream.Stream; + +import org.spongepowered.asm.mixin.Dynamic; +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.Redirect; + +import net.minecraft.registry.DynamicRegistryManager; +import net.minecraft.registry.SerializableRegistries; + +import net.fabricmc.fabric.impl.registry.sync.DynamicRegistriesImpl; + +// Implements skipping empty dynamic registries with the SKIP_WHEN_EMPTY sync option. +@Mixin(SerializableRegistries.class) +abstract class SerializableRegistriesMixin { + @Shadow + private static Stream> stream(DynamicRegistryManager dynamicRegistryManager) { + return null; + } + + @Dynamic("method_45961: Codec.xmap in createDynamicRegistryManagerCodec") + @Redirect(method = "method_45961", at = @At(value = "INVOKE", target = "Lnet/minecraft/registry/SerializableRegistries;stream(Lnet/minecraft/registry/DynamicRegistryManager;)Ljava/util/stream/Stream;")) + private static Stream> filterNonSyncedEntries(DynamicRegistryManager drm) { + return stream(drm).filter(entry -> { + boolean canSkip = DynamicRegistriesImpl.SKIP_EMPTY_SYNC_REGISTRIES.contains(entry.key()); + return !canSkip || entry.value().size() > 0; + }); + } +} diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/TagManagerLoaderMixin.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/TagManagerLoaderMixin.java new file mode 100644 index 0000000000..312976553e --- /dev/null +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/TagManagerLoaderMixin.java @@ -0,0 +1,43 @@ +/* + * 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 net.fabricmc.fabric.mixin.registry.sync; + +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.registry.Registry; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.tag.TagManagerLoader; +import net.minecraft.util.Identifier; + +// Adds namespaces to tag directories for registries added by mods. +@Mixin(TagManagerLoader.class) +abstract class TagManagerLoaderMixin { + @Inject(method = "getPath", at = @At("HEAD"), cancellable = true) + private static void onGetPath(RegistryKey> registry, CallbackInfoReturnable info) { + Identifier id = registry.getValue(); + + // Vanilla doesn't mark namespaces in the directories of tags at all, + // so we prepend the directories with the namespace if it's a modded registry id. + // No need to check DIRECTORIES, since this is only used by vanilla registries. + if (!id.getNamespace().equals(Identifier.DEFAULT_NAMESPACE)) { + info.setReturnValue("tags/" + id.getNamespace() + "/" + id.getPath()); + } + } +} diff --git a/fabric-registry-sync-v0/src/main/resources/fabric-registry-sync-v0.accesswidener b/fabric-registry-sync-v0/src/main/resources/fabric-registry-sync-v0.accesswidener index 62858b64b5..18f96ed90d 100644 --- a/fabric-registry-sync-v0/src/main/resources/fabric-registry-sync-v0.accesswidener +++ b/fabric-registry-sync-v0/src/main/resources/fabric-registry-sync-v0.accesswidener @@ -3,3 +3,8 @@ accessWidener v2 named accessible field net/minecraft/registry/SimpleRegistry frozen Z accessible method net/minecraft/registry/entry/RegistryEntry$Reference setValue (Ljava/lang/Object;)V accessible method net/minecraft/registry/Registries init ()V + +accessible class net/minecraft/registry/SerializableRegistries$Info +accessible method net/minecraft/registry/SerializableRegistries$Info (Lnet/minecraft/registry/RegistryKey;Lcom/mojang/serialization/Codec;)V +accessible field net/minecraft/registry/SerializableRegistries REGISTRIES Ljava/util/Map; +mutable field net/minecraft/registry/SerializableRegistries REGISTRIES Ljava/util/Map; diff --git a/fabric-registry-sync-v0/src/main/resources/fabric-registry-sync-v0.mixins.json b/fabric-registry-sync-v0/src/main/resources/fabric-registry-sync-v0.mixins.json index 5d4dc01cf2..a8aebd825b 100644 --- a/fabric-registry-sync-v0/src/main/resources/fabric-registry-sync-v0.mixins.json +++ b/fabric-registry-sync-v0/src/main/resources/fabric-registry-sync-v0.mixins.json @@ -7,13 +7,15 @@ "ChunkSerializerMixin", "DebugChunkGeneratorAccessor", "IdListMixin", - "LevelStorageSessionMixin", "MinecraftServerMixin", "RegistriesAccessor", "RegistriesMixin", "RegistryLoaderMixin", + "SaveLoadingMixin", + "SerializableRegistriesMixin", "SimpleRegistryMixin", - "StructuresToConfiguredStructuresFixMixin" + "StructuresToConfiguredStructuresFixMixin", + "TagManagerLoaderMixin" ], "injectors": { "defaultRequire": 1 diff --git a/fabric-registry-sync-v0/src/testmod/java/net/fabricmc/fabric/test/registry/sync/CustomDynamicRegistryTest.java b/fabric-registry-sync-v0/src/testmod/java/net/fabricmc/fabric/test/registry/sync/CustomDynamicRegistryTest.java new file mode 100644 index 0000000000..1971cf7e9e --- /dev/null +++ b/fabric-registry-sync-v0/src/testmod/java/net/fabricmc/fabric/test/registry/sync/CustomDynamicRegistryTest.java @@ -0,0 +1,87 @@ +/* + * 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 net.fabricmc.fabric.test.registry.sync; + +import com.mojang.logging.LogUtils; +import org.slf4j.Logger; + +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.registry.tag.TagKey; +import net.minecraft.util.Identifier; + +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.event.lifecycle.v1.CommonLifecycleEvents; +import net.fabricmc.fabric.api.event.registry.DynamicRegistries; +import net.fabricmc.fabric.api.event.registry.DynamicRegistrySetupCallback; +import net.fabricmc.fabric.api.event.registry.DynamicRegistryView; + +public final class CustomDynamicRegistryTest implements ModInitializer { + private static final Logger LOGGER = LogUtils.getLogger(); + + public static final RegistryKey> TEST_DYNAMIC_REGISTRY_KEY = + RegistryKey.ofRegistry(new Identifier("fabric", "test_dynamic")); + public static final RegistryKey> TEST_NESTED_DYNAMIC_REGISTRY_KEY = + RegistryKey.ofRegistry(new Identifier("fabric", "test_dynamic_nested")); + public static final RegistryKey> TEST_SYNCED_1_DYNAMIC_REGISTRY_KEY = + RegistryKey.ofRegistry(new Identifier("fabric", "test_dynamic_synced_1")); + public static final RegistryKey> TEST_SYNCED_2_DYNAMIC_REGISTRY_KEY = + RegistryKey.ofRegistry(new Identifier("fabric", "test_dynamic_synced_2")); + public static final RegistryKey> TEST_EMPTY_SYNCED_DYNAMIC_REGISTRY_KEY = + RegistryKey.ofRegistry(new Identifier("fabric", "test_dynamic_synced_empty")); + + private static final RegistryKey SYNCED_ENTRY_KEY = + RegistryKey.of(TEST_SYNCED_1_DYNAMIC_REGISTRY_KEY, new Identifier("fabric-registry-sync-v0-testmod", "synced")); + private static final TagKey TEST_DYNAMIC_OBJECT_TAG = + TagKey.of(TEST_SYNCED_1_DYNAMIC_REGISTRY_KEY, new Identifier("fabric-registry-sync-v0-testmod", "test")); + + @Override + public void onInitialize() { + DynamicRegistries.register(TEST_DYNAMIC_REGISTRY_KEY, TestDynamicObject.CODEC); + DynamicRegistries.registerSynced(TEST_SYNCED_1_DYNAMIC_REGISTRY_KEY, TestDynamicObject.CODEC); + DynamicRegistries.registerSynced(TEST_SYNCED_2_DYNAMIC_REGISTRY_KEY, TestDynamicObject.CODEC, TestDynamicObject.NETWORK_CODEC); + DynamicRegistries.registerSynced(TEST_NESTED_DYNAMIC_REGISTRY_KEY, TestNestedDynamicObject.CODEC); + DynamicRegistries.registerSynced(TEST_EMPTY_SYNCED_DYNAMIC_REGISTRY_KEY, TestDynamicObject.CODEC, DynamicRegistries.SyncOption.SKIP_WHEN_EMPTY); + + DynamicRegistrySetupCallback.EVENT.register(registryView -> { + addListenerForDynamic(registryView, TEST_DYNAMIC_REGISTRY_KEY); + addListenerForDynamic(registryView, TEST_SYNCED_1_DYNAMIC_REGISTRY_KEY); + addListenerForDynamic(registryView, TEST_SYNCED_2_DYNAMIC_REGISTRY_KEY); + addListenerForDynamic(registryView, TEST_NESTED_DYNAMIC_REGISTRY_KEY); + }); + + CommonLifecycleEvents.TAGS_LOADED.register((registries, client) -> { + // Check that the tag has applied + RegistryEntry.Reference entry = registries.get(TEST_SYNCED_1_DYNAMIC_REGISTRY_KEY) + .getEntry(SYNCED_ENTRY_KEY) + .orElseThrow(); + + if (!entry.isIn(TEST_DYNAMIC_OBJECT_TAG)) { + throw new AssertionError("Required dynamic registry entry is not in the expected tag! client: " + client); + } + + LOGGER.info("Found {} in tag {} (client: {})", entry, TEST_DYNAMIC_OBJECT_TAG, client); + }); + } + + private static void addListenerForDynamic(DynamicRegistryView registryView, RegistryKey> key) { + registryView.registerEntryAdded(key, (rawId, id, object) -> { + LOGGER.info("Loaded entry of {}: {} = {}", key, id, object); + }); + } +} diff --git a/fabric-registry-sync-v0/src/testmod/java/net/fabricmc/fabric/test/registry/sync/RegistrySyncTest.java b/fabric-registry-sync-v0/src/testmod/java/net/fabricmc/fabric/test/registry/sync/RegistrySyncTest.java index baf86a036c..0b9a7d4957 100644 --- a/fabric-registry-sync-v0/src/testmod/java/net/fabricmc/fabric/test/registry/sync/RegistrySyncTest.java +++ b/fabric-registry-sync-v0/src/testmod/java/net/fabricmc/fabric/test/registry/sync/RegistrySyncTest.java @@ -84,7 +84,6 @@ public void onInitialize() { Validate.isTrue(RegistryAttributeHolder.get(fabricRegistry).hasAttribute(RegistryAttribute.MODDED)); Validate.isTrue(RegistryAttributeHolder.get(fabricRegistry).hasAttribute(RegistryAttribute.SYNCED)); - Validate.isTrue(!RegistryAttributeHolder.get(fabricRegistry).hasAttribute(RegistryAttribute.PERSISTED)); final AtomicBoolean setupCalled = new AtomicBoolean(false); diff --git a/fabric-registry-sync-v0/src/testmod/java/net/fabricmc/fabric/test/registry/sync/TestDynamicObject.java b/fabric-registry-sync-v0/src/testmod/java/net/fabricmc/fabric/test/registry/sync/TestDynamicObject.java new file mode 100644 index 0000000000..403ed57c41 --- /dev/null +++ b/fabric-registry-sync-v0/src/testmod/java/net/fabricmc/fabric/test/registry/sync/TestDynamicObject.java @@ -0,0 +1,31 @@ +/* + * 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 net.fabricmc.fabric.test.registry.sync; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; + +public record TestDynamicObject(String name, boolean usesNetworkCodec) { + public static final Codec CODEC = codec(false); + public static final Codec NETWORK_CODEC = codec(true); + + private static Codec codec(boolean networkCodec) { + return RecordCodecBuilder.create(instance -> instance.group( + Codec.STRING.fieldOf("name").forGetter(TestDynamicObject::name) + ).apply(instance, name -> new TestDynamicObject(name, networkCodec))); + } +} diff --git a/fabric-registry-sync-v0/src/testmod/java/net/fabricmc/fabric/test/registry/sync/TestNestedDynamicObject.java b/fabric-registry-sync-v0/src/testmod/java/net/fabricmc/fabric/test/registry/sync/TestNestedDynamicObject.java new file mode 100644 index 0000000000..572ea4a13e --- /dev/null +++ b/fabric-registry-sync-v0/src/testmod/java/net/fabricmc/fabric/test/registry/sync/TestNestedDynamicObject.java @@ -0,0 +1,31 @@ +/* + * 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 net.fabricmc.fabric.test.registry.sync; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; + +import net.minecraft.registry.entry.RegistryElementCodec; +import net.minecraft.registry.entry.RegistryEntry; + +public record TestNestedDynamicObject(RegistryEntry nested) { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + RegistryElementCodec.of(CustomDynamicRegistryTest.TEST_SYNCED_1_DYNAMIC_REGISTRY_KEY, TestDynamicObject.CODEC) + .fieldOf("nested") + .forGetter(TestNestedDynamicObject::nested) + ).apply(instance, TestNestedDynamicObject::new)); +} diff --git a/fabric-registry-sync-v0/src/testmod/resources/data/fabric-registry-sync-v0-testmod/fabric/test_dynamic/first.json b/fabric-registry-sync-v0/src/testmod/resources/data/fabric-registry-sync-v0-testmod/fabric/test_dynamic/first.json new file mode 100644 index 0000000000..23ada4c50c --- /dev/null +++ b/fabric-registry-sync-v0/src/testmod/resources/data/fabric-registry-sync-v0-testmod/fabric/test_dynamic/first.json @@ -0,0 +1,3 @@ +{ + "name": "First" +} diff --git a/fabric-registry-sync-v0/src/testmod/resources/data/fabric-registry-sync-v0-testmod/fabric/test_dynamic/second.json b/fabric-registry-sync-v0/src/testmod/resources/data/fabric-registry-sync-v0-testmod/fabric/test_dynamic/second.json new file mode 100644 index 0000000000..5e5eddfe39 --- /dev/null +++ b/fabric-registry-sync-v0/src/testmod/resources/data/fabric-registry-sync-v0-testmod/fabric/test_dynamic/second.json @@ -0,0 +1,3 @@ +{ + "name": "Second" +} diff --git a/fabric-registry-sync-v0/src/testmod/resources/data/fabric-registry-sync-v0-testmod/fabric/test_dynamic_nested/synced.json.disabled b/fabric-registry-sync-v0/src/testmod/resources/data/fabric-registry-sync-v0-testmod/fabric/test_dynamic_nested/synced.json.disabled new file mode 100644 index 0000000000..fc51ec72d7 --- /dev/null +++ b/fabric-registry-sync-v0/src/testmod/resources/data/fabric-registry-sync-v0-testmod/fabric/test_dynamic_nested/synced.json.disabled @@ -0,0 +1,3 @@ +{ + "nested": "fabric-registry-sync-v0-testmod:synced" +} diff --git a/fabric-registry-sync-v0/src/testmod/resources/data/fabric-registry-sync-v0-testmod/fabric/test_dynamic_synced_1/synced.json b/fabric-registry-sync-v0/src/testmod/resources/data/fabric-registry-sync-v0-testmod/fabric/test_dynamic_synced_1/synced.json new file mode 100644 index 0000000000..3bddd0ded4 --- /dev/null +++ b/fabric-registry-sync-v0/src/testmod/resources/data/fabric-registry-sync-v0-testmod/fabric/test_dynamic_synced_1/synced.json @@ -0,0 +1,3 @@ +{ + "name": "Synced #1" +} diff --git a/fabric-registry-sync-v0/src/testmod/resources/data/fabric-registry-sync-v0-testmod/fabric/test_dynamic_synced_2/synced.json b/fabric-registry-sync-v0/src/testmod/resources/data/fabric-registry-sync-v0-testmod/fabric/test_dynamic_synced_2/synced.json new file mode 100644 index 0000000000..80e67782fd --- /dev/null +++ b/fabric-registry-sync-v0/src/testmod/resources/data/fabric-registry-sync-v0-testmod/fabric/test_dynamic_synced_2/synced.json @@ -0,0 +1,3 @@ +{ + "name": "Synced #2" +} diff --git a/fabric-registry-sync-v0/src/testmod/resources/data/fabric-registry-sync-v0-testmod/tags/fabric/test_dynamic_synced_1/test.json b/fabric-registry-sync-v0/src/testmod/resources/data/fabric-registry-sync-v0-testmod/tags/fabric/test_dynamic_synced_1/test.json new file mode 100644 index 0000000000..d892584a30 --- /dev/null +++ b/fabric-registry-sync-v0/src/testmod/resources/data/fabric-registry-sync-v0-testmod/tags/fabric/test_dynamic_synced_1/test.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "fabric-registry-sync-v0-testmod:synced" + ] +} diff --git a/fabric-registry-sync-v0/src/testmod/resources/fabric.mod.json b/fabric-registry-sync-v0/src/testmod/resources/fabric.mod.json index 93c6c72291..bd89658c14 100644 --- a/fabric-registry-sync-v0/src/testmod/resources/fabric.mod.json +++ b/fabric-registry-sync-v0/src/testmod/resources/fabric.mod.json @@ -10,7 +10,11 @@ }, "entrypoints": { "main": [ + "net.fabricmc.fabric.test.registry.sync.CustomDynamicRegistryTest", "net.fabricmc.fabric.test.registry.sync.RegistrySyncTest" + ], + "client": [ + "net.fabricmc.fabric.test.registry.sync.client.DynamicRegistryClientTest" ] } } diff --git a/fabric-registry-sync-v0/src/testmodClient/java/net/fabricmc/fabric/test/registry/sync/client/DynamicRegistryClientTest.java b/fabric-registry-sync-v0/src/testmodClient/java/net/fabricmc/fabric/test/registry/sync/client/DynamicRegistryClientTest.java new file mode 100644 index 0000000000..998582ce60 --- /dev/null +++ b/fabric-registry-sync-v0/src/testmodClient/java/net/fabricmc/fabric/test/registry/sync/client/DynamicRegistryClientTest.java @@ -0,0 +1,98 @@ +/* + * 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 net.fabricmc.fabric.test.registry.sync.client; + +import static net.fabricmc.fabric.test.registry.sync.CustomDynamicRegistryTest.TEST_EMPTY_SYNCED_DYNAMIC_REGISTRY_KEY; +import static net.fabricmc.fabric.test.registry.sync.CustomDynamicRegistryTest.TEST_NESTED_DYNAMIC_REGISTRY_KEY; +import static net.fabricmc.fabric.test.registry.sync.CustomDynamicRegistryTest.TEST_SYNCED_1_DYNAMIC_REGISTRY_KEY; +import static net.fabricmc.fabric.test.registry.sync.CustomDynamicRegistryTest.TEST_SYNCED_2_DYNAMIC_REGISTRY_KEY; + +import com.mojang.logging.LogUtils; +import org.slf4j.Logger; + +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKey; +import net.minecraft.util.Identifier; + +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; +import net.fabricmc.fabric.test.registry.sync.TestDynamicObject; +import net.fabricmc.fabric.test.registry.sync.TestNestedDynamicObject; + +public final class DynamicRegistryClientTest implements ClientModInitializer { + private static final Logger LOGGER = LogUtils.getLogger(); + private static final Identifier SYNCED_ID = new Identifier("fabric-registry-sync-v0-testmod", "synced"); + + @Override + public void onInitializeClient() { + ClientPlayConnectionEvents.JOIN.register((handler, sender, client) -> { + LOGGER.info("Starting dynamic registry sync tests..."); + + TestDynamicObject synced1 = handler.getRegistryManager() + .get(TEST_SYNCED_1_DYNAMIC_REGISTRY_KEY) + .get(SYNCED_ID); + TestDynamicObject synced2 = handler.getRegistryManager() + .get(TEST_SYNCED_2_DYNAMIC_REGISTRY_KEY) + .get(SYNCED_ID); + TestNestedDynamicObject simpleNested = handler.getRegistryManager() + .get(TEST_NESTED_DYNAMIC_REGISTRY_KEY) + .get(SYNCED_ID); + + LOGGER.info("Synced - simple: {}", synced1); + LOGGER.info("Synced - custom network codec: {}", synced2); + LOGGER.info("Synced - simple nested: {}", simpleNested); + + if (synced1 == null) { + didNotReceive(TEST_SYNCED_1_DYNAMIC_REGISTRY_KEY, SYNCED_ID); + } + + if (synced1.usesNetworkCodec()) { + throw new AssertionError("Entries in " + TEST_SYNCED_1_DYNAMIC_REGISTRY_KEY + " should not use network codec"); + } + + if (synced2 == null) { + didNotReceive(TEST_SYNCED_2_DYNAMIC_REGISTRY_KEY, SYNCED_ID); + } + + // The client server check is needed since the registries are passed through in singleplayer. + // The network codec flag would always be false in those cases. + if (client.getServer() == null && !synced2.usesNetworkCodec()) { + throw new AssertionError("Entries in " + TEST_SYNCED_2_DYNAMIC_REGISTRY_KEY + " should use network codec"); + } + + // TODO 1.20.2 + //if (simpleNested == null) { + // didNotReceive(TEST_NESTED_DYNAMIC_REGISTRY_KEY, SYNCED_ID); + //} + + //if (simpleNested.nested().value() != synced1) { + // throw new AssertionError("Did not match up synced nested entry to the other synced value"); + //} + + // If the registries weren't passed through in SP, check that the empty registry was skipped. + if (client.getServer() == null && handler.getRegistryManager().getOptional(TEST_EMPTY_SYNCED_DYNAMIC_REGISTRY_KEY).isPresent()) { + throw new AssertionError("Received empty registry that should have been skipped"); + } + + LOGGER.info("Dynamic registry sync tests passed!"); + }); + } + + private static void didNotReceive(RegistryKey> registryKey, Identifier entryId) { + throw new AssertionError("Did not receive " + registryKey + "/" + entryId); + } +} diff --git a/fabric-renderer-api-v1/build.gradle b/fabric-renderer-api-v1/build.gradle index f8b1366eb8..4c5a4747f9 100644 --- a/fabric-renderer-api-v1/build.gradle +++ b/fabric-renderer-api-v1/build.gradle @@ -1,14 +1,13 @@ -archivesBaseName = "fabric-renderer-api-v1" version = getSubprojectVersion(project) moduleDependencies(project, ['fabric-api-base']) testDependencies(project, [ ':fabric-block-api-v1', + ':fabric-block-view-api-v2', ':fabric-blockrenderlayer-v1', - ':fabric-models-v0', + ':fabric-model-loading-api-v1', ':fabric-object-builder-api-v1', ':fabric-renderer-indigo', - ':fabric-rendering-data-attachment-v1', ':fabric-resource-loader-v0' ]) diff --git a/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/api/renderer/v1/mesh/Mesh.java b/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/api/renderer/v1/mesh/Mesh.java index a57cd293f6..c5ce93fbd1 100644 --- a/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/api/renderer/v1/mesh/Mesh.java +++ b/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/api/renderer/v1/mesh/Mesh.java @@ -33,8 +33,20 @@ 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 + * sent to the consumer will likely be thread-local/reused and should never * be retained by the consumer. */ void forEach(Consumer consumer); + + /** + * Outputs all quads in this mesh to the given quad emitter. + * + * @apiNote The default implementation will be removed in the next breaking release. + */ + default void outputTo(QuadEmitter emitter) { + forEach(quad -> { + emitter.copyFrom(quad); + emitter.emit(); + }); + }; } diff --git a/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/api/renderer/v1/mesh/MutableQuadView.java b/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/api/renderer/v1/mesh/MutableQuadView.java index a3770629c1..da5ffbe2d5 100644 --- a/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/api/renderer/v1/mesh/MutableQuadView.java +++ b/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/api/renderer/v1/mesh/MutableQuadView.java @@ -115,7 +115,7 @@ default MutableQuadView pos(int vertexIndex, Vector3f pos) { } /** - * Set vertex color. + * Set vertex color in ARGB format (0xAARRGGBB). */ MutableQuadView color(int vertexIndex, int color); diff --git a/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/api/renderer/v1/mesh/QuadView.java b/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/api/renderer/v1/mesh/QuadView.java index 82c075fc34..28930c2c0c 100644 --- a/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/api/renderer/v1/mesh/QuadView.java +++ b/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/api/renderer/v1/mesh/QuadView.java @@ -69,7 +69,7 @@ public interface QuadView { Vector3f copyPos(int vertexIndex, @Nullable Vector3f target); /** - * Retrieve vertex color. + * Retrieve vertex color in ARGB format (0xAARRGGBB). */ int color(int vertexIndex); @@ -148,7 +148,7 @@ public interface QuadView { /** * 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. + * on demand. * *

    Not typically needed by models. Exposed to enable standard lighting * utility functions for use by renderers. @@ -178,7 +178,7 @@ public interface QuadView { * @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. + * at least {@link #VANILLA_QUAD_STRIDE} elements available at this index. */ void toVanilla(int[] target, int targetIndex); diff --git a/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/api/renderer/v1/model/FabricBakedModel.java b/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/api/renderer/v1/model/FabricBakedModel.java index bb0906b20a..40b497d729 100644 --- a/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/api/renderer/v1/model/FabricBakedModel.java +++ b/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/api/renderer/v1/model/FabricBakedModel.java @@ -25,11 +25,11 @@ import net.minecraft.client.util.math.MatrixStack; import net.minecraft.item.ItemStack; import net.minecraft.util.math.BlockPos; -import net.minecraft.world.BlockRenderView; import net.minecraft.util.math.random.Random; +import net.minecraft.world.BlockRenderView; -import net.fabricmc.fabric.api.renderer.v1.Renderer; import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; +import net.fabricmc.fabric.impl.renderer.VanillaModelEncoder; /** * Interface for baked models that output meshes with enhanced rendering features. @@ -38,9 +38,7 @@ * *

    Implementors should have a look at {@link ModelHelper} as it contains many useful functions. * - *

    Note for {@link Renderer} implementors: Fabric causes BakedModel to extend this - * interface with {@link #isVanillaAdapter()} == true and to produce standard vertex data. - * This means any BakedModel instance can be safely cast to this interface without an instanceof check. + *

    Note: This interface is automatically implemented on all baked models via Mixin and interface injection. */ public interface FabricBakedModel { /** @@ -48,11 +46,13 @@ public interface FabricBakedModel { * Also means the model does not rely on any non-vanilla features. * Allows the renderer to optimize or route vanilla models through the unmodified vanilla pipeline if desired. * - *

    Fabric overrides to true for vanilla baked models. + *

    Vanilla baked models will return true. * Enhanced models that use this API should return false, * otherwise the API will not recognize the model. */ - boolean isVanillaAdapter(); + default boolean isVanillaAdapter() { + return true; + } /** * This method will be called during chunk rebuilds to generate both the static and @@ -83,9 +83,7 @@ public interface FabricBakedModel { *

    Note: with {@link BakedModel#getQuads(BlockState, net.minecraft.util.math.Direction, Random)}, the random * parameter is normally initialized with the same seed prior to each face layer. * Model authors should note this method is called only once per block, and call the provided - * Random supplier multiple times if re-seeding is necessary. For wrapped vanilla baked models, - * it will probably be easier to use {@link RenderContext#bakedModelConsumer()} which handles - * re-seeding per face automatically. + * Random supplier multiple times if re-seeding is necessary. * * @param blockView Access to world state. Cast to {@code RenderAttachedBlockView} to * retrieve block entity data unless thread safety can be guaranteed. @@ -95,7 +93,9 @@ public interface FabricBakedModel { * Will not be thread-safe. Do not cache or retain a reference. * @param context Accepts model output. */ - void emitBlockQuads(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier randomSupplier, RenderContext context); + default void emitBlockQuads(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier randomSupplier, RenderContext context) { + VanillaModelEncoder.emitBlockQuads((BakedModel) this, state, randomSupplier, context, context.getEmitter()); + } /** * This method will be called during item rendering to generate both the static and @@ -124,5 +124,7 @@ public interface FabricBakedModel { * logic here, instead of returning every possible shape from {@link BakedModel#getOverrides} * as vanilla baked models. */ - void emitItemQuads(ItemStack stack, Supplier randomSupplier, RenderContext context); + default void emitItemQuads(ItemStack stack, Supplier randomSupplier, RenderContext context) { + VanillaModelEncoder.emitItemQuads((BakedModel) this, null, randomSupplier, context); + } } diff --git a/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/api/renderer/v1/model/ForwardingBakedModel.java b/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/api/renderer/v1/model/ForwardingBakedModel.java index 7ffe18385b..ee4a824d92 100644 --- a/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/api/renderer/v1/model/ForwardingBakedModel.java +++ b/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/api/renderer/v1/model/ForwardingBakedModel.java @@ -28,8 +28,8 @@ import net.minecraft.item.ItemStack; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Direction; -import net.minecraft.world.BlockRenderView; import net.minecraft.util.math.random.Random; +import net.minecraft.world.BlockRenderView; import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; @@ -37,23 +37,23 @@ * Base class for specialized model implementations that need to wrap other baked models. * Avoids boilerplate code for pass-through methods. */ -public abstract class ForwardingBakedModel implements BakedModel, FabricBakedModel, WrapperBakedModel { +public abstract class ForwardingBakedModel implements BakedModel, WrapperBakedModel { /** implementations must set this somehow. */ protected BakedModel wrapped; @Override - public void emitBlockQuads(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier randomSupplier, RenderContext context) { - ((FabricBakedModel) wrapped).emitBlockQuads(blockView, state, pos, randomSupplier, context); + public boolean isVanillaAdapter() { + return wrapped.isVanillaAdapter(); } @Override - public boolean isVanillaAdapter() { - return ((FabricBakedModel) wrapped).isVanillaAdapter(); + public void emitBlockQuads(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier randomSupplier, RenderContext context) { + wrapped.emitBlockQuads(blockView, state, pos, randomSupplier, context); } @Override public void emitItemQuads(ItemStack stack, Supplier randomSupplier, RenderContext context) { - ((FabricBakedModel) wrapped).emitItemQuads(stack, randomSupplier, context); + wrapped.emitItemQuads(stack, randomSupplier, context); } @Override diff --git a/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/api/renderer/v1/render/RenderContext.java b/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/api/renderer/v1/render/RenderContext.java index c25f564faf..ab5c96b1dc 100644 --- a/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/api/renderer/v1/render/RenderContext.java +++ b/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/api/renderer/v1/render/RenderContext.java @@ -17,16 +17,22 @@ package net.fabricmc.fabric.api.renderer.v1.render; import java.util.function.Consumer; +import java.util.function.Supplier; import org.jetbrains.annotations.Nullable; import net.minecraft.block.BlockState; import net.minecraft.client.render.model.BakedModel; +import net.minecraft.client.render.model.json.ModelTransformationMode; +import net.minecraft.item.ItemStack; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.world.BlockRenderView; 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.mesh.MutableQuadView; import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter; +import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView; import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel; /** @@ -36,33 +42,30 @@ */ public interface RenderContext { /** - * Used by models to send vertex data previously baked via {@link MeshBuilder}. - * The fastest option and preferred whenever feasible. - */ - Consumer meshConsumer(); - - /** - * Fallback consumer that can process a vanilla {@link BakedModel}. - * Fabric causes vanilla baked models to send themselves - * via this interface. Can also be used by compound models that contain a mix - * of vanilla baked models, packaged quads and/or dynamic elements. - */ - BakedModelConsumer bakedModelConsumer(); - - /** - * Returns a {@link QuadEmitter} instance that emits directly to the render buffer. - * It remains necessary to call {@link QuadEmitter#emit()} to output the quad. + * Returns a {@link QuadEmitter} instance that is used to output quads. + * It is necessary to call {@link QuadEmitter#emit()} to output a quad. * - *

    This method will always be less performant than passing pre-baked meshes - * via {@link #meshConsumer()}. It should be used sparingly for model components that - * demand it - text, icons, dynamic indicators, or other elements that vary too - * much for static baking to be feasible. + *

    The renderer may optimize certain operations such as + * {@link Mesh#outputTo(QuadEmitter)} when used with this emitter. Thus, using + * those operations is preferred to using the emitter directly. It should be + * used sparingly for model components that demand it - text, icons, dynamic + * indicators, or other elements that vary too much for static baking to be + * feasible. * *

    Calling this method invalidates any {@link QuadEmitter} returned earlier. - * Will be threadlocal/re-used - do not retain references. + * Will be thread-local/re-used - do not retain references. */ QuadEmitter getEmitter(); + /** + * Returns whether this context currently has at least one transform. + * + * @apiNote The default implementation will be removed in the next breaking release. + */ + default boolean hasTransform() { + return true; + } + /** * Causes all models/quads/meshes sent to this consumer to be transformed by the provided * {@link QuadTransform} that edits each quad before buffering. Quads in the mesh will @@ -76,6 +79,8 @@ public interface RenderContext { * *

    Meshes are never mutated by the transformer - only buffered quads. This ensures thread-safe * use of meshes/models across multiple chunk builders. + * + *

    Using the {@linkplain #getEmitter() quad emitter of this context} from the inside of a quad transform is not supported. */ void pushTransform(QuadTransform transform); @@ -86,17 +91,71 @@ public interface RenderContext { void popTransform(); /** - * Fabric causes vanilla baked models to send themselves - * via this interface. Can also be used by compound models that contain a mix - * of vanilla baked models, packaged quads and/or dynamic elements. + * Returns {@code true} if the given face will be culled away. * - * @deprecated Prefer using the more flexible {@link #bakedModelConsumer}. + *

    This function can be used to skip complex transformations of quads that will be culled anyway. + * The cull face of a quad is determined by {@link QuadView#cullFace()}. + * Note that if {@linkplain #hasTransform() there is a transform}, no computation should be skipped, + * because the cull face might be changed by the transform, + * or the transform might wish to receive culled faces too. + * + *

    This function can only be used on a block render context (i.e. in {@link FabricBakedModel#emitBlockQuads}). + * Calling it on another context (e.g. in {@link FabricBakedModel#emitItemQuads}) will throw an exception. + * + * @apiNote The default implementation will be removed in the next breaking release. + */ + default boolean isFaceCulled(@Nullable Direction face) { + return false; + } + + /** + * Returns the current transformation mode. + * + *

    This function can only be used on an item render context (i.e. in {@link FabricBakedModel#emitItemQuads}). + * Calling it on another context (e.g. in {@link FabricBakedModel#emitBlockQuads}) will throw an exception. + * + * @apiNote The default implementation will be removed in the next breaking release. + */ + default ModelTransformationMode itemTransformationMode() { + return ModelTransformationMode.NONE; + } + + @FunctionalInterface + 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); + } + + /** + * @deprecated Use {@link Mesh#outputTo(QuadEmitter)} instead. */ @Deprecated + default Consumer meshConsumer() { + return mesh -> mesh.outputTo(getEmitter()); + } + + /** + * @deprecated Use {@link FabricBakedModel#emitBlockQuads(BlockRenderView, BlockState, BlockPos, Supplier, RenderContext) emitBlockQuads} + * or {@link FabricBakedModel#emitItemQuads(ItemStack, Supplier, RenderContext) emitItemQuads} on the baked model + * that you want to consume instead. + */ + @Deprecated(forRemoval = true) + BakedModelConsumer bakedModelConsumer(); + + /** + * @deprecated Use {@link FabricBakedModel#emitBlockQuads(BlockRenderView, BlockState, BlockPos, Supplier, RenderContext) emitBlockQuads} + * or {@link FabricBakedModel#emitItemQuads(ItemStack, Supplier, RenderContext) emitItemQuads} on the baked model + * that you want to consume instead. + */ + @Deprecated(forRemoval = true) default Consumer fallbackConsumer() { return bakedModelConsumer(); } + @Deprecated(forRemoval = true) interface BakedModelConsumer extends Consumer { /** * Render a baked model by processing its {@linkplain BakedModel#getQuads} using the rendered block state. @@ -120,13 +179,4 @@ interface BakedModelConsumer extends Consumer { */ void accept(BakedModel model, @Nullable BlockState state); } - - @FunctionalInterface - 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/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/impl/renderer/VanillaModelEncoder.java b/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/impl/renderer/VanillaModelEncoder.java new file mode 100644 index 0000000000..af2a817cac --- /dev/null +++ b/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/impl/renderer/VanillaModelEncoder.java @@ -0,0 +1,85 @@ +/* + * 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 net.fabricmc.fabric.impl.renderer; + +import java.util.List; +import java.util.function.Supplier; + +import org.jetbrains.annotations.Nullable; + +import net.minecraft.block.BlockState; +import net.minecraft.client.render.model.BakedModel; +import net.minecraft.client.render.model.BakedQuad; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.random.Random; + +import net.fabricmc.fabric.api.renderer.v1.Renderer; +import net.fabricmc.fabric.api.renderer.v1.RendererAccess; +import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial; +import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter; +import net.fabricmc.fabric.api.renderer.v1.model.ModelHelper; +import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; +import net.fabricmc.fabric.api.util.TriState; + +/** + * Routines for adaptation of vanilla {@link BakedModel}s to FRAPI pipelines. + * Even though Indigo calls them directly, they are not for use by third party renderers, and might change at any time. + */ +public class VanillaModelEncoder { + private static final Renderer RENDERER = RendererAccess.INSTANCE.getRenderer(); + private static final RenderMaterial MATERIAL_STANDARD = RENDERER.materialFinder().find(); + private static final RenderMaterial MATERIAL_NO_AO = RENDERER.materialFinder().ambientOcclusion(TriState.FALSE).find(); + + // Separate QuadEmitter parameter so that Indigo can pass its own emitter that handles vanilla quads differently. + public static void emitBlockQuads(BakedModel model, @Nullable BlockState state, Supplier randomSupplier, RenderContext context, QuadEmitter emitter) { + final RenderMaterial defaultMaterial = model.useAmbientOcclusion() ? MATERIAL_STANDARD : MATERIAL_NO_AO; + + for (int i = 0; i <= ModelHelper.NULL_FACE_ID; i++) { + final Direction cullFace = ModelHelper.faceFromIndex(i); + + if (!context.hasTransform() && context.isFaceCulled(cullFace)) { + // Skip entire quad list if possible. + continue; + } + + final List quads = model.getQuads(state, cullFace, randomSupplier.get()); + final int count = quads.size(); + + for (int j = 0; j < count; j++) { + final BakedQuad q = quads.get(j); + emitter.fromVanilla(q, defaultMaterial, cullFace); + emitter.emit(); + } + } + } + + public static void emitItemQuads(BakedModel model, @Nullable BlockState state, Supplier randomSupplier, RenderContext context) { + QuadEmitter emitter = context.getEmitter(); + + for (int i = 0; i <= ModelHelper.NULL_FACE_ID; i++) { + final Direction cullFace = ModelHelper.faceFromIndex(i); + final List quads = model.getQuads(state, cullFace, randomSupplier.get()); + final int count = quads.size(); + + for (int j = 0; j < count; j++) { + final BakedQuad q = quads.get(j); + emitter.fromVanilla(q, MATERIAL_STANDARD, cullFace); + emitter.emit(); + } + } + } +} diff --git a/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/mixin/renderer/client/BakedModelMixin.java b/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/mixin/renderer/client/BakedModelMixin.java index e317a96363..12ee4d2f2b 100644 --- a/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/mixin/renderer/client/BakedModelMixin.java +++ b/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/mixin/renderer/client/BakedModelMixin.java @@ -16,38 +16,15 @@ package net.fabricmc.fabric.mixin.renderer.client; -import java.util.function.Supplier; - import org.spongepowered.asm.mixin.Mixin; -import net.minecraft.block.BlockState; import net.minecraft.client.render.model.BakedModel; -import net.minecraft.item.ItemStack; -import net.minecraft.util.math.BlockPos; -import net.minecraft.world.BlockRenderView; -import net.minecraft.util.math.random.Random; import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel; -import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; /** * Avoids instanceof checks and enables consistent code path for all baked models. */ @Mixin(BakedModel.class) public interface BakedModelMixin extends FabricBakedModel { - @Override - default boolean isVanillaAdapter() { - return true; - } - - @Override - default void emitBlockQuads(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier randomSupplier, RenderContext context) { - context.bakedModelConsumer().accept((BakedModel) this, state); - } - - @Override - default void emitItemQuads(ItemStack stack, Supplier randomSupplier, RenderContext context) { - // Pass null state to enforce item quads in block render contexts - context.bakedModelConsumer().accept((BakedModel) this, null); - } } diff --git a/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/mixin/renderer/client/MultipartBakedModelMixin.java b/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/mixin/renderer/client/MultipartBakedModelMixin.java index 86deca793f..ce8aca7350 100644 --- a/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/mixin/renderer/client/MultipartBakedModelMixin.java +++ b/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/mixin/renderer/client/MultipartBakedModelMixin.java @@ -36,8 +36,8 @@ import net.minecraft.client.render.model.MultipartBakedModel; import net.minecraft.item.ItemStack; import net.minecraft.util.math.BlockPos; -import net.minecraft.world.BlockRenderView; import net.minecraft.util.math.random.Random; +import net.minecraft.world.BlockRenderView; import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel; import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; @@ -63,7 +63,7 @@ public boolean isVanillaAdapter() { @Inject(at = @At("RETURN"), method = "") private void onInit(List, BakedModel>> components, CallbackInfo cb) { for (Pair, BakedModel> component : components) { - if (!((FabricBakedModel) component.getRight()).isVanillaAdapter()) { + if (!component.getRight().isVanillaAdapter()) { isVanilla = false; break; } @@ -81,15 +81,24 @@ public void emitBlockQuads(BlockRenderView blockView, BlockState state, BlockPos Pair, BakedModel> pair = components.get(i); if (pair.getLeft().test(state)) { - ((FabricBakedModel) pair.getRight()).emitBlockQuads(blockView, state, pos, randomSupplier, context); bitSet.set(i); } } stateCache.put(state, bitSet); - } else { - for (int i = 0; i < this.components.size(); i++) { - if (bitSet.get(i)) ((FabricBakedModel) components.get(i).getRight()).emitBlockQuads(blockView, state, pos, randomSupplier, context); + } + + Random random = randomSupplier.get(); + // Imitate vanilla passing a new random to the submodels + long randomSeed = random.nextLong(); + Supplier subModelRandomSupplier = () -> { + random.setSeed(randomSeed); + return random; + }; + + for (int i = 0; i < this.components.size(); i++) { + if (bitSet.get(i)) { + components.get(i).getRight().emitBlockQuads(blockView, state, pos, subModelRandomSupplier, context); } } } diff --git a/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/mixin/renderer/client/OverlayVertexConsumerMixin.java b/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/mixin/renderer/client/OverlayVertexConsumerMixin.java new file mode 100644 index 0000000000..2c90cb32f5 --- /dev/null +++ b/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/mixin/renderer/client/OverlayVertexConsumerMixin.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 net.fabricmc.fabric.mixin.renderer.client; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +import net.minecraft.client.render.OverlayVertexConsumer; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.MathConstants; + +@Mixin(OverlayVertexConsumer.class) +public class OverlayVertexConsumerMixin { + @Unique + private static final Direction[] DIRECTIONS = Direction.values(); + + /* + The original method call is used to get the closest axis-aligned direction of the world-space + normal vector for a certain face. The world-space normal vector is computed using matrices + that change when the camera values change. Due to precision errors during matrix + multiplication, the computed world-space normal of a face will not remain constant, so the + closest axis-aligned direction may flicker. This issue only affects faces that are directly + between two axis-aligned directions (45 degree faces) or three axis-aligned directions. + + The fix involves requiring the dot product of each axis-aligned direction to be a small + amount greater than the previous maximum dot product to be set as the new maximum. + */ + @Redirect(method = "next()V", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/math/Direction;getFacing(FFF)Lnet/minecraft/util/math/Direction;")) + private Direction redirectGetFacing(float x, float y, float z) { + Direction closestDir = Direction.NORTH; + float maxDot = 1.4E-45F; + + for (Direction direction : DIRECTIONS) { + float dot = x * direction.getOffsetX() + y * direction.getOffsetY() + z * direction.getOffsetZ(); + + if (dot > maxDot + MathConstants.EPSILON) { + maxDot = dot; + closestDir = direction; + } + } + + return closestDir; + } +} diff --git a/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/mixin/renderer/client/WeightedBakedModelMixin.java b/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/mixin/renderer/client/WeightedBakedModelMixin.java index 483465d256..53c508121a 100644 --- a/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/mixin/renderer/client/WeightedBakedModelMixin.java +++ b/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/mixin/renderer/client/WeightedBakedModelMixin.java @@ -34,8 +34,8 @@ import net.minecraft.util.collection.Weighted; import net.minecraft.util.collection.Weighting; import net.minecraft.util.math.BlockPos; -import net.minecraft.world.BlockRenderView; import net.minecraft.util.math.random.Random; +import net.minecraft.world.BlockRenderView; import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel; import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; @@ -54,7 +54,7 @@ public class WeightedBakedModelMixin implements FabricBakedModel { @Inject(at = @At("RETURN"), method = "") private void onInit(List> models, CallbackInfo cb) { for (int i = 0; i < models.size(); i++) { - if (!((FabricBakedModel) models.get(i).getData()).isVanillaAdapter()) { + if (!models.get(i).getData().isVanillaAdapter()) { isVanilla = false; break; } @@ -71,7 +71,11 @@ public void emitBlockQuads(BlockRenderView blockView, BlockState state, BlockPos Weighted.Present selected = Weighting.getAt(this.models, Math.abs((int) randomSupplier.get().nextLong()) % this.totalWeight).orElse(null); if (selected != null) { - ((FabricBakedModel) selected.getData()).emitBlockQuads(blockView, state, pos, randomSupplier, context); + selected.getData().emitBlockQuads(blockView, state, pos, () -> { + Random random = randomSupplier.get(); + random.nextLong(); // Imitate vanilla modifying the random before passing it to the submodel + return random; + }, context); } } @@ -80,7 +84,11 @@ public void emitItemQuads(ItemStack stack, Supplier randomSupplier, Rend Weighted.Present selected = Weighting.getAt(this.models, Math.abs((int) randomSupplier.get().nextLong()) % this.totalWeight).orElse(null); if (selected != null) { - ((FabricBakedModel) selected.getData()).emitItemQuads(stack, randomSupplier, context); + selected.getData().emitItemQuads(stack, () -> { + Random random = randomSupplier.get(); + random.nextLong(); // Imitate vanilla modifying the random before passing it to the submodel + return random; + }, context); } } } diff --git a/fabric-renderer-api-v1/src/client/resources/fabric-renderer-api-v1.mixins.json b/fabric-renderer-api-v1/src/client/resources/fabric-renderer-api-v1.mixins.json index 075e074683..f12cca5091 100644 --- a/fabric-renderer-api-v1/src/client/resources/fabric-renderer-api-v1.mixins.json +++ b/fabric-renderer-api-v1/src/client/resources/fabric-renderer-api-v1.mixins.json @@ -6,7 +6,8 @@ "client.BakedModelMixin", "client.MultipartBakedModelMixin", "client.WeightedBakedModelMixin", - "client.SpriteAtlasTextureMixin" + "client.SpriteAtlasTextureMixin", + "client.OverlayVertexConsumerMixin" ], "injectors": { "defaultRequire": 1 diff --git a/fabric-renderer-api-v1/src/client/resources/fabric.mod.json b/fabric-renderer-api-v1/src/client/resources/fabric.mod.json index 61e4f85921..6e152cdd35 100644 --- a/fabric-renderer-api-v1/src/client/resources/fabric.mod.json +++ b/fabric-renderer-api-v1/src/client/resources/fabric.mod.json @@ -26,6 +26,9 @@ "fabric-renderer-api-v1.debughud.mixins.json" ], "custom": { - "fabric-api:module-lifecycle": "stable" + "fabric-api:module-lifecycle": "stable", + "loom:injected_interfaces": { + "net/minecraft/class_1087": ["net/fabricmc/fabric/api/renderer/v1/model/FabricBakedModel"] + } } } diff --git a/fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/simple/FrameBlock.java b/fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/FrameBlock.java similarity index 82% rename from fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/simple/FrameBlock.java rename to fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/FrameBlock.java index 74e5dfafe4..080305ebf9 100644 --- a/fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/simple/FrameBlock.java +++ b/fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/FrameBlock.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package net.fabricmc.fabric.test.renderer.simple; +package net.fabricmc.fabric.test.renderer; import org.jetbrains.annotations.Nullable; @@ -27,7 +27,6 @@ import net.minecraft.item.ItemStack; import net.minecraft.util.ActionResult; import net.minecraft.util.Hand; -import net.minecraft.util.Identifier; import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Direction; @@ -35,16 +34,12 @@ import net.minecraft.world.World; import net.fabricmc.fabric.api.block.v1.FabricBlock; -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.fabricmc.fabric.api.rendering.data.v1.RenderAttachedBlockView; +import net.fabricmc.fabric.api.blockview.v2.FabricBlockView; // Need to implement FabricBlock manually because this is a testmod for another Fabric module, otherwise it would be injected. -public final class FrameBlock extends Block implements BlockEntityProvider, FabricBlock { - public final Identifier id; - - public FrameBlock(Identifier id) { - super(FabricBlockSettings.copyOf(Blocks.IRON_BLOCK).nonOpaque()); - this.id = id; +public class FrameBlock extends Block implements BlockEntityProvider, FabricBlock { + public FrameBlock(Settings settings) { + super(settings); } @Override @@ -106,8 +101,8 @@ public BlockEntity createBlockEntity(BlockPos pos, BlockState state) { // but the goal here is just to test the behavior with the pillar's connected textures. ;-) @Override public BlockState getAppearance(BlockState state, BlockRenderView renderView, BlockPos pos, Direction side, @Nullable BlockState sourceState, @Nullable BlockPos sourcePos) { - // For this specific block, the render attachment works on both the client and the server, so let's use that. - if (((RenderAttachedBlockView) renderView).getBlockEntityRenderAttachment(pos) instanceof Block mimickedBlock) { + // For this specific block, the render data works on both the client and the server, so let's use that. + if (((FabricBlockView) renderView).getBlockEntityRenderData(pos) instanceof Block mimickedBlock) { return mimickedBlock.getDefaultState(); } diff --git a/fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/simple/FrameBlockEntity.java b/fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/FrameBlockEntity.java similarity index 88% rename from fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/simple/FrameBlockEntity.java rename to fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/FrameBlockEntity.java index 5ed12957e8..82587d29e4 100644 --- a/fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/simple/FrameBlockEntity.java +++ b/fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/FrameBlockEntity.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package net.fabricmc.fabric.test.renderer.simple; +package net.fabricmc.fabric.test.renderer; import org.jetbrains.annotations.Nullable; @@ -29,14 +29,14 @@ import net.minecraft.util.Identifier; import net.minecraft.util.math.BlockPos; -import net.fabricmc.fabric.api.rendering.data.v1.RenderAttachmentBlockEntity; +import net.fabricmc.fabric.api.blockview.v2.RenderDataBlockEntity; -public final class FrameBlockEntity extends BlockEntity implements RenderAttachmentBlockEntity { +public class FrameBlockEntity extends BlockEntity implements RenderDataBlockEntity { @Nullable private Block block = null; public FrameBlockEntity(BlockPos blockPos, BlockState blockState) { - super(RendererTest.FRAME_BLOCK_ENTITY, blockPos, blockState); + super(Registration.FRAME_BLOCK_ENTITY_TYPE, blockPos, blockState); } @Override @@ -86,7 +86,7 @@ public void setBlock(@Nullable Block block) { @Nullable @Override - public Block getRenderAttachmentData() { + public Block getRenderData() { return this.block; } diff --git a/fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/Registration.java b/fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/Registration.java new file mode 100644 index 0000000000..aca7347d63 --- /dev/null +++ b/fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/Registration.java @@ -0,0 +1,67 @@ +/* + * 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 net.fabricmc.fabric.test.renderer; + +import net.minecraft.block.Block; +import net.minecraft.block.Blocks; +import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.item.BlockItem; +import net.minecraft.item.Item; +import net.minecraft.registry.Registries; +import net.minecraft.registry.Registry; + +import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; +import net.fabricmc.fabric.api.object.builder.v1.block.entity.FabricBlockEntityTypeBuilder; + +public final class Registration { + public static final FrameBlock FRAME_BLOCK = register("frame", new FrameBlock(FabricBlockSettings.copyOf(Blocks.IRON_BLOCK).nonOpaque())); + public static final FrameBlock FRAME_MULTIPART_BLOCK = register("frame_multipart", new FrameBlock(FabricBlockSettings.copyOf(Blocks.IRON_BLOCK).nonOpaque())); + public static final FrameBlock FRAME_VARIANT_BLOCK = register("frame_variant", new FrameBlock(FabricBlockSettings.copyOf(Blocks.IRON_BLOCK).nonOpaque())); + public static final Block PILLAR_BLOCK = register("pillar", new Block(FabricBlockSettings.create())); + public static final Block OCTAGONAL_COLUMN_BLOCK = register("octagonal_column", new Block(FabricBlockSettings.create().nonOpaque().strength(1.8F))); + public static final Block RIVERSTONE_BLOCK = register("riverstone", new Block(FabricBlockSettings.copyOf(Blocks.STONE))); + + public static final FrameBlock[] FRAME_BLOCKS = new FrameBlock[] { + FRAME_BLOCK, + FRAME_MULTIPART_BLOCK, + FRAME_VARIANT_BLOCK, + }; + + public static final Item FRAME_ITEM = register("frame", new BlockItem(FRAME_BLOCK, new Item.Settings())); + public static final Item FRAME_MULTIPART_ITEM = register("frame_multipart", new BlockItem(FRAME_MULTIPART_BLOCK, new Item.Settings())); + public static final Item FRAME_VARIANT_ITEM = register("frame_variant", new BlockItem(FRAME_VARIANT_BLOCK, new Item.Settings())); + public static final Item PILLAR_ITEM = register("pillar", new BlockItem(PILLAR_BLOCK, new Item.Settings())); + public static final Item OCTAGONAL_COLUMN_ITEM = register("octagonal_column", new BlockItem(OCTAGONAL_COLUMN_BLOCK, new Item.Settings())); + public static final Item RIVERSTONE_ITEM = register("riverstone", new BlockItem(RIVERSTONE_BLOCK, new Item.Settings())); + + public static final BlockEntityType FRAME_BLOCK_ENTITY_TYPE = register("frame", FabricBlockEntityTypeBuilder.create(FrameBlockEntity::new, FRAME_BLOCKS).build(null)); + + private static T register(String path, T block) { + return Registry.register(Registries.BLOCK, RendererTest.id(path), block); + } + + private static T register(String path, T item) { + return Registry.register(Registries.ITEM, RendererTest.id(path), item); + } + + private static > T register(String path, T blockEntityType) { + return Registry.register(Registries.BLOCK_ENTITY_TYPE, RendererTest.id(path), blockEntityType); + } + + public static void init() { + } +} diff --git a/fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/RendererTest.java b/fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/RendererTest.java new file mode 100644 index 0000000000..cab675940d --- /dev/null +++ b/fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/RendererTest.java @@ -0,0 +1,54 @@ +/* + * 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 net.fabricmc.fabric.test.renderer; + +import net.minecraft.util.Identifier; + +import net.fabricmc.api.ModInitializer; + +/** + * The testmod for the Fabric Renderer API. These tests are used to validate that + * Indigo's implementation is correct, but they may also be useful for other + * implementations of the Fabric Renderer API. + * + *

    Tests

    + * + *
      + *
    • Frame blocks display another block inside, scaled down and made translucent. + * Blocks that provide a block entity cannot be placed inside frames. + * + *
    • Pillars connect vertically with each other by changing textures. They also + * connect vertically to frame blocks containing a pillar, and vice versa. + * + *
    • Octagonal columns have irregular faces to test enhanced AO and normal shade. The + * octagonal item column has glint force enabled on all faces except the top and bottom + * faces. + * + *
    • Riverstone blocks look like stone normally, but turn to gold in river biomes + * (biomes tagged with #minecraft:is_river). + *
    + */ +public final class RendererTest implements ModInitializer { + @Override + public void onInitialize() { + Registration.init(); + } + + public static Identifier id(String path) { + return new Identifier("fabric-renderer-api-v1-testmod", path); + } +} diff --git a/fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/package-info.java b/fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/package-info.java deleted file mode 100644 index 0af08e4ffb..0000000000 --- a/fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/package-info.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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. - */ - -/** - * The testmod for the Fabric Renderer API. - * Right now there is only one test here, but more tests may come to exist in the future. - * These tests are used to validate Indigo's implementation is correct, but these tests may also be useful for other implementations of the Fabric Renderer API. - * - *

    Right now there is a simple test in the {@code simple} package which validates that simple meshes and quad emitters function. - * Future tests may look into testing things such as render materials or creating more advanced models. - */ - -package net.fabricmc.fabric.test.renderer; diff --git a/fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/simple/RendererTest.java b/fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/simple/RendererTest.java deleted file mode 100644 index e7e9e85845..0000000000 --- a/fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/simple/RendererTest.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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 net.fabricmc.fabric.test.renderer.simple; - -import net.minecraft.block.Block; -import net.minecraft.block.entity.BlockEntityType; -import net.minecraft.item.BlockItem; -import net.minecraft.item.Item; -import net.minecraft.registry.Registries; -import net.minecraft.registry.Registry; -import net.minecraft.util.Identifier; - -import net.fabricmc.api.ModInitializer; -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.fabricmc.fabric.api.object.builder.v1.block.entity.FabricBlockEntityTypeBuilder; - -/** - * A simple testmod that renders a simple block rendered using the fabric renderer api. - * The block that is rendered is a simple frame that another block is rendered in. - * Blocks that provide a block entity cannot be placed inside the frame. - * - *

    There are no fancy shaders or glow that is provided by this renderer test. - */ -public final class RendererTest implements ModInitializer { - public static final FrameBlock[] FRAMES = new FrameBlock[]{ - new FrameBlock(id("frame")), - new FrameBlock(id("frame_multipart")), - new FrameBlock(id("frame_weighted")), - }; - public static final BlockEntityType FRAME_BLOCK_ENTITY = FabricBlockEntityTypeBuilder.create(FrameBlockEntity::new, FRAMES).build(null); - - public static final Identifier PILLAR_ID = id("pillar"); - public static final Block PILLAR = new Block(FabricBlockSettings.create()); - - @Override - public void onInitialize() { - for (FrameBlock frameBlock : FRAMES) { - Registry.register(Registries.BLOCK, frameBlock.id, frameBlock); - Registry.register(Registries.ITEM, frameBlock.id, new BlockItem(frameBlock, new Item.Settings())); - } - - // To anyone testing this: pillars are supposed to connect vertically with each other. - // Additionally, they should also connect vertically to frame blocks containing a pillar. - // (The frame block will not change, but adjacent pillars should adjust their textures). - Registry.register(Registries.BLOCK, PILLAR_ID, PILLAR); - Registry.register(Registries.ITEM, PILLAR_ID, new BlockItem(PILLAR, new Item.Settings())); - - Registry.register(Registries.BLOCK_ENTITY_TYPE, id("frame"), FRAME_BLOCK_ENTITY); - } - - public static Identifier id(String path) { - return new Identifier("fabric-renderer-api-v1-testmod", path); - } -} diff --git a/fabric-renderer-api-v1/src/testmod/resources/fabric.mod.json b/fabric-renderer-api-v1/src/testmod/resources/fabric.mod.json index 1fd965e719..987d605643 100644 --- a/fabric-renderer-api-v1/src/testmod/resources/fabric.mod.json +++ b/fabric-renderer-api-v1/src/testmod/resources/fabric.mod.json @@ -6,15 +6,15 @@ "environment": "*", "license": "Apache-2.0", "depends": { - "fabric-renderer-api-v1":"*", "fabric-resource-loader-v0": "*" }, "entrypoints": { "main": [ - "net.fabricmc.fabric.test.renderer.simple.RendererTest" + "net.fabricmc.fabric.test.renderer.RendererTest" ], "client": [ - "net.fabricmc.fabric.test.renderer.simple.client.RendererClientTest" + "net.fabricmc.fabric.test.renderer.client.RandomSupplierTest", + "net.fabricmc.fabric.test.renderer.client.RendererClientTest" ] } } diff --git a/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/simple/client/FrameBakedModel.java b/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/FrameBakedModel.java similarity index 78% rename from fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/simple/client/FrameBakedModel.java rename to fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/FrameBakedModel.java index dbd7370fd6..5e9423f597 100644 --- a/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/simple/client/FrameBakedModel.java +++ b/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/FrameBakedModel.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package net.fabricmc.fabric.test.renderer.simple.client; +package net.fabricmc.fabric.test.renderer.client; import java.util.Collections; import java.util.List; @@ -24,7 +24,6 @@ import net.minecraft.block.Block; import net.minecraft.block.BlockState; -import net.minecraft.block.Blocks; import net.minecraft.client.MinecraftClient; import net.minecraft.client.render.model.BakedModel; import net.minecraft.client.render.model.BakedQuad; @@ -32,28 +31,28 @@ import net.minecraft.client.render.model.json.ModelTransformation; import net.minecraft.client.texture.Sprite; import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Direction; import net.minecraft.util.math.random.Random; import net.minecraft.world.BlockRenderView; +import net.fabricmc.fabric.api.blockview.v2.FabricBlockView; import net.fabricmc.fabric.api.renderer.v1.RendererAccess; import net.fabricmc.fabric.api.renderer.v1.material.BlendMode; import net.fabricmc.fabric.api.renderer.v1.material.MaterialFinder; import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial; import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh; -import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel; import net.fabricmc.fabric.api.renderer.v1.model.ModelHelper; import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; -import net.fabricmc.fabric.api.rendering.data.v1.RenderAttachedBlockView; -final class FrameBakedModel implements BakedModel, FabricBakedModel { +public class FrameBakedModel implements BakedModel { private final Mesh frameMesh; private final Sprite frameSprite; private final RenderMaterial translucentMaterial; private final RenderMaterial translucentEmissiveMaterial; - FrameBakedModel(Mesh frameMesh, Sprite frameSprite) { + public FrameBakedModel(Mesh frameMesh, Sprite frameSprite) { this.frameMesh = frameMesh; this.frameSprite = frameSprite; @@ -63,46 +62,6 @@ final class FrameBakedModel implements BakedModel, FabricBakedModel { this.translucentEmissiveMaterial = finder.blendMode(BlendMode.TRANSLUCENT).emissive(true).find(); } - @Override - public List getQuads(@Nullable BlockState state, @Nullable Direction face, Random random) { - return Collections.emptyList(); // Renderer API makes this obsolete, so return no quads - } - - @Override - public boolean useAmbientOcclusion() { - return true; // we want the block to have a shadow depending on the adjacent blocks - } - - @Override - public boolean hasDepth() { - return false; - } - - @Override - public boolean isSideLit() { - return true; // we want the block to be lit from the side when rendered as an item - } - - @Override - public boolean isBuiltin() { - return false; - } - - @Override - public Sprite getParticleSprite() { - return this.frameSprite; - } - - @Override - public ModelTransformation getTransformation() { - return ModelHelper.MODEL_TRANSFORM_BLOCK; - } - - @Override - public ModelOverrideList getOverrides() { - return ModelOverrideList.EMPTY; - } - @Override public boolean isVanillaAdapter() { return false; @@ -111,19 +70,14 @@ public boolean isVanillaAdapter() { @Override public void emitBlockQuads(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier randomSupplier, RenderContext context) { // Emit our frame mesh - context.meshConsumer().accept(this.frameMesh); - - RenderAttachedBlockView renderAttachedBlockView = (RenderAttachedBlockView) blockView; + this.frameMesh.outputTo(context.getEmitter()); - // We cannot access the block entity from here. We should instead use the immutable render attachments provided by the block entity. - @Nullable - Block data = (Block) renderAttachedBlockView.getBlockEntityRenderAttachment(pos); - - if (data == null) { - return; // No inner block to render + // We should not access the block entity from here. We should instead use the immutable render data provided by the block entity. + if (!(((FabricBlockView) blockView).getBlockEntityRenderData(pos) instanceof Block mimickedBlock)) { + return; // No inner block to render, or data of wrong type } - BlockState innerState = data.getDefaultState(); + BlockState innerState = mimickedBlock.getDefaultState(); // Now, we emit a transparent scaled-down version of the inner model // Try both emissive and non-emissive versions of the translucent material @@ -131,25 +85,23 @@ public void emitBlockQuads(BlockRenderView blockView, BlockState state, BlockPos emitInnerQuads(context, material, () -> { // Use emitBlockQuads to allow for Renderer API features - ((FabricBakedModel) MinecraftClient.getInstance().getBlockRenderManager().getModel(innerState)).emitBlockQuads(blockView, innerState, pos, randomSupplier, context); + MinecraftClient.getInstance().getBlockRenderManager().getModel(innerState).emitBlockQuads(blockView, innerState, pos, randomSupplier, context); }); } @Override public void emitItemQuads(ItemStack stack, Supplier randomSupplier, RenderContext context) { // Emit our frame mesh - context.meshConsumer().accept(this.frameMesh); + this.frameMesh.outputTo(context.getEmitter()); // Emit a scaled-down fence for testing, trying both materials again. RenderMaterial material = stack.hasCustomName() ? translucentEmissiveMaterial : translucentMaterial; - BlockState innerState = Blocks.OAK_FENCE.getDefaultState(); + ItemStack innerItem = Items.CRAFTING_TABLE.getDefaultStack(); + BakedModel innerModel = MinecraftClient.getInstance().getItemRenderer().getModel(innerItem, null, null, 0); emitInnerQuads(context, material, () -> { - // Need to use the fallback consumer directly: - // - we can't use emitBlockQuads because we don't have a blockView - // - we can't use emitItemQuads because multipart models don't have item quads - context.bakedModelConsumer().accept(MinecraftClient.getInstance().getBlockRenderManager().getModel(innerState), innerState); + innerModel.emitItemQuads(innerItem, randomSupplier, context); }); } @@ -190,4 +142,44 @@ private void emitInnerQuads(RenderContext context, RenderMaterial material, Runn // Let's not forget to pop the transform! context.popTransform(); } + + @Override + public List getQuads(@Nullable BlockState state, @Nullable Direction face, Random random) { + return Collections.emptyList(); // Renderer API makes this obsolete, so return no quads + } + + @Override + public boolean useAmbientOcclusion() { + return true; // we want the block to have a shadow depending on the adjacent blocks + } + + @Override + public boolean hasDepth() { + return false; + } + + @Override + public boolean isSideLit() { + return true; // we want the block to be lit from the side when rendered as an item + } + + @Override + public boolean isBuiltin() { + return false; + } + + @Override + public Sprite getParticleSprite() { + return this.frameSprite; + } + + @Override + public ModelTransformation getTransformation() { + return ModelHelper.MODEL_TRANSFORM_BLOCK; + } + + @Override + public ModelOverrideList getOverrides() { + return ModelOverrideList.EMPTY; + } } diff --git a/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/FrameUnbakedModel.java b/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/FrameUnbakedModel.java new file mode 100644 index 0000000000..e4a52acede --- /dev/null +++ b/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/FrameUnbakedModel.java @@ -0,0 +1,118 @@ +/* + * 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 net.fabricmc.fabric.test.renderer.client; + +import java.util.Collection; +import java.util.Collections; +import java.util.function.Function; + +import org.jetbrains.annotations.Nullable; + +import net.minecraft.client.render.model.BakedModel; +import net.minecraft.client.render.model.Baker; +import net.minecraft.client.render.model.ModelBakeSettings; +import net.minecraft.client.render.model.UnbakedModel; +import net.minecraft.client.texture.Sprite; +import net.minecraft.client.texture.SpriteAtlasTexture; +import net.minecraft.client.util.SpriteIdentifier; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.Direction; + +import net.fabricmc.fabric.api.renderer.v1.Renderer; +import net.fabricmc.fabric.api.renderer.v1.RendererAccess; +import net.fabricmc.fabric.api.renderer.v1.mesh.MeshBuilder; +import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView; +import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter; + +public class FrameUnbakedModel implements UnbakedModel { + private static final SpriteIdentifier OBSIDIAN_SPRITE_ID = new SpriteIdentifier(SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE, new Identifier("block/obsidian")); + + @Override + public Collection getModelDependencies() { + return Collections.emptySet(); + } + + @Override + public void setParents(Function modelLoader) { + } + + /* + * Bake the model. + * In this case we can prebake the frame into a mesh, but will render the contained block when we draw the quads. + */ + @Nullable + @Override + public BakedModel bake(Baker baker, Function textureGetter, ModelBakeSettings rotationContainer, Identifier modelId) { + // The renderer API may not have an implementation, so we should check if it exists. + if (!RendererAccess.INSTANCE.hasRenderer()) { + // No renderer implementation is present. + return null; + } + + Sprite obsidianSprite = textureGetter.apply(OBSIDIAN_SPRITE_ID); + + Renderer renderer = RendererAccess.INSTANCE.getRenderer(); + MeshBuilder builder = renderer.meshBuilder(); + QuadEmitter emitter = builder.getEmitter(); + + for (Direction direction : Direction.values()) { + // Draw outer frame + emitter.square(direction, 0.0F, 0.9F, 0.9F, 1.0F, 0.0F) + .spriteBake(obsidianSprite, MutableQuadView.BAKE_LOCK_UV) + .color(-1, -1, -1, -1) + .emit(); + + emitter.square(direction, 0.0F, 0.0F, 0.1F, 0.9F, 0.0F) + .spriteBake(obsidianSprite, MutableQuadView.BAKE_LOCK_UV) + .color(-1, -1, -1, -1) + .emit(); + + emitter.square(direction, 0.9F, 0.1F, 1.0F, 1.0F, 0.0F) + .spriteBake(obsidianSprite, MutableQuadView.BAKE_LOCK_UV) + .color(-1, -1, -1, -1) + .emit(); + + emitter.square(direction, 0.1F, 0.0F, 1.0F, 0.1F, 0.0F) + .spriteBake(obsidianSprite, MutableQuadView.BAKE_LOCK_UV) + .color(-1, -1, -1, -1) + .emit(); + + // Draw inner frame - inset by 0.9 so the frame looks like an actual mesh + emitter.square(direction, 0.0F, 0.9F, 0.9F, 1.0F, 0.9F) + .spriteBake(obsidianSprite, MutableQuadView.BAKE_LOCK_UV) + .color(-1, -1, -1, -1) + .emit(); + + emitter.square(direction, 0.0F, 0.0F, 0.1F, 0.9F, 0.9F) + .spriteBake(obsidianSprite, MutableQuadView.BAKE_LOCK_UV) + .color(-1, -1, -1, -1) + .emit(); + + emitter.square(direction, 0.9F, 0.1F, 1.0F, 1.0F, 0.9F) + .spriteBake(obsidianSprite, MutableQuadView.BAKE_LOCK_UV) + .color(-1, -1, -1, -1) + .emit(); + + emitter.square(direction, 0.1F, 0.0F, 1.0F, 0.1F, 0.9F) + .spriteBake(obsidianSprite, MutableQuadView.BAKE_LOCK_UV) + .color(-1, -1, -1, -1) + .emit(); + } + + return new FrameBakedModel(builder.build(), obsidianSprite); + } +} diff --git a/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/ModelResolverImpl.java b/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/ModelResolverImpl.java new file mode 100644 index 0000000000..6cb8950f2b --- /dev/null +++ b/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/ModelResolverImpl.java @@ -0,0 +1,75 @@ +/* + * 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 net.fabricmc.fabric.test.renderer.client; + +import java.util.Set; + +import org.jetbrains.annotations.Nullable; + +import net.minecraft.client.render.model.UnbakedModel; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.api.client.model.loading.v1.ModelResolver; +import net.fabricmc.fabric.test.renderer.RendererTest; + +public class ModelResolverImpl implements ModelResolver { + private static final Set FRAME_MODEL_LOCATIONS = Set.of( + RendererTest.id("block/frame"), + RendererTest.id("item/frame"), + RendererTest.id("item/frame_multipart"), + RendererTest.id("item/frame_variant") + ); + + private static final Set PILLAR_MODEL_LOCATIONS = Set.of( + RendererTest.id("block/pillar"), + RendererTest.id("item/pillar") + ); + + private static final Set OCTAGONAL_COLUMN_MODEL_LOCATIONS = Set.of( + RendererTest.id("block/octagonal_column"), + RendererTest.id("item/octagonal_column") + ); + + private static final Set RIVERSTONE_MODEL_LOCATIONS = Set.of( + RendererTest.id("block/riverstone"), + RendererTest.id("item/riverstone") + ); + + @Override + @Nullable + public UnbakedModel resolveModel(Context context) { + Identifier id = context.id(); + + if (FRAME_MODEL_LOCATIONS.contains(id)) { + return new FrameUnbakedModel(); + } + + if (PILLAR_MODEL_LOCATIONS.contains(id)) { + return new PillarUnbakedModel(); + } + + if (OCTAGONAL_COLUMN_MODEL_LOCATIONS.contains(id)) { + return new OctagonalColumnUnbakedModel(); + } + + if (RIVERSTONE_MODEL_LOCATIONS.contains(id)) { + return new RiverstoneUnbakedModel(); + } + + return null; + } +} diff --git a/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/OctagonalColumnUnbakedModel.java b/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/OctagonalColumnUnbakedModel.java new file mode 100644 index 0000000000..6f564a6b75 --- /dev/null +++ b/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/OctagonalColumnUnbakedModel.java @@ -0,0 +1,249 @@ +/* + * 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 net.fabricmc.fabric.test.renderer.client; + +import java.util.Collection; +import java.util.Collections; +import java.util.function.Function; + +import org.jetbrains.annotations.Nullable; + +import net.minecraft.client.render.model.BakedModel; +import net.minecraft.client.render.model.Baker; +import net.minecraft.client.render.model.ModelBakeSettings; +import net.minecraft.client.render.model.UnbakedModel; +import net.minecraft.client.texture.Sprite; +import net.minecraft.client.texture.SpriteAtlasTexture; +import net.minecraft.client.util.SpriteIdentifier; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.Direction; + +import net.fabricmc.fabric.api.renderer.v1.Renderer; +import net.fabricmc.fabric.api.renderer.v1.RendererAccess; +import net.fabricmc.fabric.api.renderer.v1.material.MaterialFinder; +import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial; +import net.fabricmc.fabric.api.renderer.v1.mesh.MeshBuilder; +import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView; +import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter; +import net.fabricmc.fabric.api.util.TriState; + +public class OctagonalColumnUnbakedModel implements UnbakedModel { + private static final SpriteIdentifier WHITE_CONCRETE_SPRITE_ID = new SpriteIdentifier(SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE, new Identifier("block/white_concrete")); + + // (B - A) is the side length of a regular octagon that fits in a unit square. + // The line from A to B is centered on the line from 0 to 1. + private static final float A = (float) (1 - Math.sqrt(2) / 2); + private static final float B = (float) (Math.sqrt(2) / 2); + + @Override + public Collection getModelDependencies() { + return Collections.emptySet(); + } + + @Override + public void setParents(Function modelLoader) { + } + + @Nullable + @Override + public BakedModel bake(Baker baker, Function textureGetter, ModelBakeSettings rotationContainer, Identifier modelId) { + if (!RendererAccess.INSTANCE.hasRenderer()) { + return null; + } + + Sprite whiteConcreteSprite = textureGetter.apply(WHITE_CONCRETE_SPRITE_ID); + + Renderer renderer = RendererAccess.INSTANCE.getRenderer(); + MaterialFinder finder = renderer.materialFinder(); + RenderMaterial glintMaterial = finder.glint(TriState.TRUE).find(); + + MeshBuilder builder = renderer.meshBuilder(); + QuadEmitter emitter = builder.getEmitter(); + + // up + + emitter.pos(0, A, 1, 0); + emitter.pos(1, 0.5f, 1, 0.5f); + emitter.pos(2, 1, 1, A); + emitter.pos(3, B, 1, 0); + emitter.cullFace(Direction.UP); + emitter.spriteBake(whiteConcreteSprite, MutableQuadView.BAKE_LOCK_UV); + emitter.color(-1, -1, -1, -1); + emitter.emit(); + + emitter.pos(0, 0, 1, A); + emitter.pos(1, 0, 1, B); + emitter.pos(2, 0.5f, 1, 0.5f); + emitter.pos(3, A, 1, 0); + emitter.cullFace(Direction.UP); + emitter.spriteBake(whiteConcreteSprite, MutableQuadView.BAKE_LOCK_UV); + emitter.color(-1, -1, -1, -1); + emitter.emit(); + + emitter.pos(0, 0, 1, B); + emitter.pos(1, A, 1, 1); + emitter.pos(2, B, 1, 1); + emitter.pos(3, 0.5f, 1, 0.5f); + emitter.cullFace(Direction.UP); + emitter.spriteBake(whiteConcreteSprite, MutableQuadView.BAKE_LOCK_UV); + emitter.color(-1, -1, -1, -1); + emitter.emit(); + + emitter.pos(0, 0.5f, 1, 0.5f); + emitter.pos(1, B, 1, 1); + emitter.pos(2, 1, 1, B); + emitter.pos(3, 1, 1, A); + emitter.cullFace(Direction.UP); + emitter.spriteBake(whiteConcreteSprite, MutableQuadView.BAKE_LOCK_UV); + emitter.color(-1, -1, -1, -1); + emitter.emit(); + + // down + + emitter.pos(0, A, 0, 1); + emitter.pos(1, 0.5f, 0, 0.5f); + emitter.pos(2, 1, 0, B); + emitter.pos(3, B, 0, 1); + emitter.cullFace(Direction.DOWN); + emitter.spriteBake(whiteConcreteSprite, MutableQuadView.BAKE_LOCK_UV); + emitter.color(-1, -1, -1, -1); + emitter.emit(); + + emitter.pos(0, 0, 0, B); + emitter.pos(1, 0, 0, A); + emitter.pos(2, 0.5f, 0, 0.5f); + emitter.pos(3, A, 0, 1); + emitter.cullFace(Direction.DOWN); + emitter.spriteBake(whiteConcreteSprite, MutableQuadView.BAKE_LOCK_UV); + emitter.color(-1, -1, -1, -1); + emitter.emit(); + + emitter.pos(0, 0, 0, A); + emitter.pos(1, A, 0, 0); + emitter.pos(2, B, 0, 0); + emitter.pos(3, 0.5f, 0, 0.5f); + emitter.cullFace(Direction.DOWN); + emitter.spriteBake(whiteConcreteSprite, MutableQuadView.BAKE_LOCK_UV); + emitter.color(-1, -1, -1, -1); + emitter.emit(); + + emitter.pos(0, 0.5f, 0, 0.5f); + emitter.pos(1, B, 0, 0); + emitter.pos(2, 1, 0, A); + emitter.pos(3, 1, 0, B); + emitter.cullFace(Direction.DOWN); + emitter.spriteBake(whiteConcreteSprite, MutableQuadView.BAKE_LOCK_UV); + emitter.color(-1, -1, -1, -1); + emitter.emit(); + + // north + emitter.pos(0, B, 1, 0); + emitter.pos(1, B, 0, 0); + emitter.pos(2, A, 0, 0); + emitter.pos(3, A, 1, 0); + emitter.cullFace(Direction.NORTH); + emitter.spriteBake(whiteConcreteSprite, MutableQuadView.BAKE_LOCK_UV); + emitter.material(glintMaterial); + emitter.color(-1, -1, -1, -1); + emitter.emit(); + + // northwest + emitter.pos(0, A, 1, 0); + emitter.pos(1, A, 0, 0); + emitter.pos(2, 0, 0, A); + emitter.pos(3, 0, 1, A); + cornerSprite(emitter, whiteConcreteSprite); + emitter.material(glintMaterial); + emitter.color(-1, -1, -1, -1); + emitter.emit(); + + // west + emitter.pos(0, 0, 1, A); + emitter.pos(1, 0, 0, A); + emitter.pos(2, 0, 0, B); + emitter.pos(3, 0, 1, B); + emitter.cullFace(Direction.WEST); + emitter.spriteBake(whiteConcreteSprite, MutableQuadView.BAKE_LOCK_UV); + emitter.material(glintMaterial); + emitter.color(-1, -1, -1, -1); + emitter.emit(); + + // southwest + emitter.pos(0, 0, 1, B); + emitter.pos(1, 0, 0, B); + emitter.pos(2, A, 0, 1); + emitter.pos(3, A, 1, 1); + cornerSprite(emitter, whiteConcreteSprite); + emitter.material(glintMaterial); + emitter.color(-1, -1, -1, -1); + emitter.emit(); + + // south + emitter.pos(0, A, 1, 1); + emitter.pos(1, A, 0, 1); + emitter.pos(2, B, 0, 1); + emitter.pos(3, B, 1, 1); + emitter.cullFace(Direction.SOUTH); + emitter.spriteBake(whiteConcreteSprite, MutableQuadView.BAKE_LOCK_UV); + emitter.material(glintMaterial); + emitter.color(-1, -1, -1, -1); + emitter.emit(); + + // southeast + emitter.pos(0, B, 1, 1); + emitter.pos(1, B, 0, 1); + emitter.pos(2, 1, 0, B); + emitter.pos(3, 1, 1, B); + cornerSprite(emitter, whiteConcreteSprite); + emitter.material(glintMaterial); + emitter.color(-1, -1, -1, -1); + emitter.emit(); + + // east + emitter.pos(0, 1, 1, B); + emitter.pos(1, 1, 0, B); + emitter.pos(2, 1, 0, A); + emitter.pos(3, 1, 1, A); + emitter.cullFace(Direction.EAST); + emitter.spriteBake(whiteConcreteSprite, MutableQuadView.BAKE_LOCK_UV); + emitter.material(glintMaterial); + emitter.color(-1, -1, -1, -1); + emitter.emit(); + + // northeast + emitter.pos(0, 1, 1, A); + emitter.pos(1, 1, 0, A); + emitter.pos(2, B, 0, 0); + emitter.pos(3, B, 1, 0); + cornerSprite(emitter, whiteConcreteSprite); + emitter.material(glintMaterial); + emitter.color(-1, -1, -1, -1); + emitter.emit(); + + return new SingleMeshBakedModel(builder.build(), whiteConcreteSprite); + } + + private static void cornerSprite(QuadEmitter emitter, Sprite sprite) { + // Assign uvs for a corner face in such a way that the texture is not stretched, using coordinates in [0, 1]. + emitter.uv(0, A, 0); + emitter.uv(1, A, 1); + emitter.uv(2, B, 1); + emitter.uv(3, B, 0); + // Map [0, 1] coordinates to sprite atlas coordinates. spriteBake assumes [0, 16] unless we pass the BAKE_NORMALIZED flag. + emitter.spriteBake(sprite, MutableQuadView.BAKE_NORMALIZED); + } +} diff --git a/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/simple/client/PillarBakedModel.java b/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/PillarBakedModel.java similarity index 64% rename from fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/simple/client/PillarBakedModel.java rename to fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/PillarBakedModel.java index ec37d11c9f..6690e3aee5 100644 --- a/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/simple/client/PillarBakedModel.java +++ b/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/PillarBakedModel.java @@ -14,8 +14,9 @@ * limitations under the License. */ -package net.fabricmc.fabric.test.renderer.simple.client; +package net.fabricmc.fabric.test.renderer.client; +import java.util.Collections; import java.util.List; import java.util.function.Supplier; @@ -34,37 +35,25 @@ import net.minecraft.world.BlockRenderView; import net.fabricmc.fabric.api.block.v1.FabricBlockState; -import net.fabricmc.fabric.api.renderer.v1.RendererAccess; -import net.fabricmc.fabric.api.renderer.v1.material.MaterialFinder; -import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial; import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView; import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter; -import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel; import net.fabricmc.fabric.api.renderer.v1.model.ModelHelper; import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; -import net.fabricmc.fabric.api.util.TriState; -import net.fabricmc.fabric.test.renderer.simple.RendererTest; +import net.fabricmc.fabric.test.renderer.Registration; /** * Very crude implementation of a pillar block model that connects with pillars above and below. */ -public class PillarBakedModel implements BakedModel, FabricBakedModel { +public class PillarBakedModel implements BakedModel { private enum ConnectedTexture { ALONE, BOTTOM, MIDDLE, TOP } // alone, bottom, middle, top private final Sprite[] sprites; - private final RenderMaterial defaultMaterial; - private final RenderMaterial glintMaterial; public PillarBakedModel(Sprite[] sprites) { this.sprites = sprites; - - MaterialFinder finder = RendererAccess.INSTANCE.getRenderer().materialFinder(); - defaultMaterial = finder.find(); - finder.clear(); - glintMaterial = finder.glint(TriState.TRUE).find(); } @Override @@ -74,52 +63,67 @@ public boolean isVanillaAdapter() { @Override public void emitBlockQuads(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier randomSupplier, RenderContext context) { - emitQuads(context.getEmitter(), blockView, state, pos); - } - - @Override - public void emitItemQuads(ItemStack stack, Supplier randomSupplier, RenderContext context) { - emitQuads(context.getEmitter(), null, null, null); - } + QuadEmitter emitter = context.getEmitter(); + // Do not use the passed state to ensure that this model connects + // to and from blocks with a custom appearance correctly. + BlockState worldState = blockView.getBlockState(pos); - private void emitQuads(QuadEmitter emitter, @Nullable BlockRenderView blockView, @Nullable BlockState state, @Nullable BlockPos pos) { for (Direction side : Direction.values()) { ConnectedTexture texture = ConnectedTexture.ALONE; - RenderMaterial material = defaultMaterial; if (side.getAxis().isHorizontal()) { - if (blockView != null && state != null && pos != null) { - boolean connectAbove = canConnect(blockView, pos.offset(Direction.UP), side, state, pos); - boolean connectBelow = canConnect(blockView, pos.offset(Direction.DOWN), side, state, pos); - - if (connectAbove && connectBelow) { - texture = ConnectedTexture.MIDDLE; - } else if (connectAbove) { - texture = ConnectedTexture.BOTTOM; - } else if (connectBelow) { - texture = ConnectedTexture.TOP; - } + boolean connectAbove = canConnect(blockView, worldState, pos, pos.offset(Direction.UP), side); + boolean connectBelow = canConnect(blockView, worldState, pos, pos.offset(Direction.DOWN), side); + + if (connectAbove && connectBelow) { + texture = ConnectedTexture.MIDDLE; + } else if (connectAbove) { + texture = ConnectedTexture.BOTTOM; + } else if (connectBelow) { + texture = ConnectedTexture.TOP; } - - material = glintMaterial; } emitter.square(side, 0, 0, 1, 1, 0); emitter.spriteBake(sprites[texture.ordinal()], MutableQuadView.BAKE_LOCK_UV); emitter.color(-1, -1, -1, -1); - emitter.material(material); emitter.emit(); } } - private static boolean canConnect(BlockRenderView blockView, BlockPos pos, Direction side, BlockState sourceState, BlockPos sourcePos) { + @Override + public void emitItemQuads(ItemStack stack, Supplier randomSupplier, RenderContext context) { + QuadEmitter emitter = context.getEmitter(); + + for (Direction side : Direction.values()) { + emitter.square(side, 0, 0, 1, 1, 0); + emitter.spriteBake(sprites[ConnectedTexture.ALONE.ordinal()], MutableQuadView.BAKE_LOCK_UV); + emitter.color(-1, -1, -1, -1); + emitter.emit(); + } + } + + private static boolean canConnect(BlockRenderView blockView, BlockState originState, BlockPos originPos, BlockPos otherPos, Direction side) { + BlockState otherState = blockView.getBlockState(otherPos); // In this testmod we can't rely on injected interfaces - in normal mods the (FabricBlockState) cast will be unnecessary - return ((FabricBlockState) blockView.getBlockState(pos)).getAppearance(blockView, pos, side, sourceState, sourcePos).isOf(RendererTest.PILLAR); + BlockState originAppearance = ((FabricBlockState) originState).getAppearance(blockView, originPos, side, otherState, otherPos); + + if (!originAppearance.isOf(Registration.PILLAR_BLOCK)) { + return false; + } + + BlockState otherAppearance = ((FabricBlockState) otherState).getAppearance(blockView, otherPos, side, originState, originPos); + + if (!otherAppearance.isOf(Registration.PILLAR_BLOCK)) { + return false; + } + + return true; } @Override public List getQuads(@Nullable BlockState state, @Nullable Direction face, Random random) { - return List.of(); + return Collections.emptyList(); } @Override diff --git a/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/simple/client/PillarUnbakedModel.java b/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/PillarUnbakedModel.java similarity index 92% rename from fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/simple/client/PillarUnbakedModel.java rename to fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/PillarUnbakedModel.java index 9a34e180e7..b94fda0c5e 100644 --- a/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/simple/client/PillarUnbakedModel.java +++ b/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/PillarUnbakedModel.java @@ -14,9 +14,10 @@ * limitations under the License. */ -package net.fabricmc.fabric.test.renderer.simple.client; +package net.fabricmc.fabric.test.renderer.client; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.function.Function; import java.util.stream.Stream; @@ -32,7 +33,7 @@ import net.minecraft.screen.PlayerScreenHandler; import net.minecraft.util.Identifier; -import net.fabricmc.fabric.test.renderer.simple.RendererTest; +import net.fabricmc.fabric.test.renderer.RendererTest; public class PillarUnbakedModel implements UnbakedModel { private static final List SPRITES = Stream.of("alone", "bottom", "middle", "top") @@ -41,7 +42,7 @@ public class PillarUnbakedModel implements UnbakedModel { @Override public Collection getModelDependencies() { - return List.of(); + return Collections.emptySet(); } @Override diff --git a/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/RandomSupplierTest.java b/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/RandomSupplierTest.java new file mode 100644 index 0000000000..d79782a621 --- /dev/null +++ b/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/RandomSupplierTest.java @@ -0,0 +1,138 @@ +/* + * 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 net.fabricmc.fabric.test.renderer.client; + +import java.util.List; +import java.util.function.Supplier; + +import org.apache.commons.lang3.tuple.Pair; +import org.jetbrains.annotations.Nullable; + +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.client.render.model.BakedModel; +import net.minecraft.client.render.model.BakedQuad; +import net.minecraft.client.render.model.MultipartBakedModel; +import net.minecraft.client.render.model.WeightedBakedModel; +import net.minecraft.client.render.model.json.ModelOverrideList; +import net.minecraft.client.render.model.json.ModelTransformation; +import net.minecraft.client.texture.Sprite; +import net.minecraft.util.collection.Weighted; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.random.Random; +import net.minecraft.world.BlockRenderView; + +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; + +/** + * Tests that vanilla and Fabric API give the same random results. + * + *

    Never do this in a real mod, this is purely for testing! + */ +public class RandomSupplierTest implements ClientModInitializer { + private static long previousRandom = 0; + private static boolean hasPreviousRandom = false; + + @Override + public void onInitializeClient() { + var checkingModel = new RandomCheckingBakedModel(); + var weighted = new WeightedBakedModel(List.of( + Weighted.of(checkingModel, 1), + Weighted.of(checkingModel, 2))); + var multipart = new MultipartBakedModel(List.of( + Pair.of(state -> true, weighted), + Pair.of(state -> true, weighted))); + var weightedAgain = new WeightedBakedModel(List.of( + Weighted.of(multipart, 1), + Weighted.of(multipart, 2))); + + long startingSeed = 42; + Random random = Random.create(); + + random.setSeed(startingSeed); + weightedAgain.getQuads(Blocks.STONE.getDefaultState(), null, random); + + random.setSeed(startingSeed); + weightedAgain.getQuads(Blocks.STONE.getDefaultState(), null, random); + + Supplier randomSupplier = () -> { + random.setSeed(startingSeed); + return random; + }; + weightedAgain.emitBlockQuads(null, Blocks.STONE.getDefaultState(), BlockPos.ORIGIN, randomSupplier, null); + } + + private static class RandomCheckingBakedModel implements BakedModel { + @Override + public List getQuads(@Nullable BlockState state, @Nullable Direction face, Random random) { + long value = random.nextLong(); + + if (hasPreviousRandom) { + if (value != previousRandom) { + throw new AssertionError("Random value is not the same as the previous one!"); + } + } else { + hasPreviousRandom = true; + previousRandom = value; + } + + return List.of(); + } + + @Override + public void emitBlockQuads(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier randomSupplier, RenderContext context) { + getQuads(state, null, randomSupplier.get()); + } + + @Override + public boolean useAmbientOcclusion() { + return false; + } + + @Override + public boolean hasDepth() { + return false; + } + + @Override + public boolean isSideLit() { + return false; + } + + @Override + public boolean isBuiltin() { + return false; + } + + @Override + public Sprite getParticleSprite() { + return null; + } + + @Override + public ModelTransformation getTransformation() { + return null; + } + + @Override + public ModelOverrideList getOverrides() { + return null; + } + } +} diff --git a/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/simple/client/RendererClientTest.java b/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/RendererClientTest.java similarity index 57% rename from fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/simple/client/RendererClientTest.java rename to fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/RendererClientTest.java index 3fb47ec6fe..79645dffe0 100644 --- a/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/simple/client/RendererClientTest.java +++ b/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/RendererClientTest.java @@ -14,34 +14,27 @@ * limitations under the License. */ -package net.fabricmc.fabric.test.renderer.simple.client; - -import static net.fabricmc.fabric.test.renderer.simple.RendererTest.id; +package net.fabricmc.fabric.test.renderer.client; import net.minecraft.client.render.RenderLayer; -import net.minecraft.registry.Registries; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap; -import net.fabricmc.fabric.api.client.model.ModelLoadingRegistry; -import net.fabricmc.fabric.test.renderer.simple.FrameBlock; -import net.fabricmc.fabric.test.renderer.simple.RendererTest; +import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin; +import net.fabricmc.fabric.test.renderer.FrameBlock; +import net.fabricmc.fabric.test.renderer.Registration; public final class RendererClientTest implements ClientModInitializer { @Override public void onInitializeClient() { - ModelLoadingRegistry.INSTANCE.registerResourceProvider(manager -> new FrameModelResourceProvider()); - ModelLoadingRegistry.INSTANCE.registerVariantProvider(manager -> new PillarModelVariantProvider()); + ModelLoadingPlugin.register(pluginContext -> { + pluginContext.resolveModel().register(new ModelResolverImpl()); + }); - for (FrameBlock frameBlock : RendererTest.FRAMES) { + for (FrameBlock frameBlock : Registration.FRAME_BLOCKS) { // We don't specify a material for the frame mesh, // so it will use the default material, i.e. the one from BlockRenderLayerMap. BlockRenderLayerMap.INSTANCE.putBlock(frameBlock, RenderLayer.getCutoutMipped()); - - String itemPath = Registries.ITEM.getId(frameBlock.asItem()).getPath(); - FrameModelResourceProvider.FRAME_MODELS.add(id("item/" + itemPath)); } - - FrameModelResourceProvider.FRAME_MODELS.add(id("block/frame")); } } diff --git a/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/RiverstoneBakedModel.java b/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/RiverstoneBakedModel.java new file mode 100644 index 0000000000..e414c793d6 --- /dev/null +++ b/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/RiverstoneBakedModel.java @@ -0,0 +1,109 @@ +/* + * 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 net.fabricmc.fabric.test.renderer.client; + +import java.util.Collections; +import java.util.List; +import java.util.function.Supplier; + +import org.jetbrains.annotations.Nullable; + +import net.minecraft.block.BlockState; +import net.minecraft.client.render.model.BakedModel; +import net.minecraft.client.render.model.BakedQuad; +import net.minecraft.client.render.model.json.ModelOverrideList; +import net.minecraft.client.render.model.json.ModelTransformation; +import net.minecraft.client.texture.Sprite; +import net.minecraft.item.ItemStack; +import net.minecraft.registry.tag.BiomeTags; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.random.Random; +import net.minecraft.world.BlockRenderView; + +import net.fabricmc.fabric.api.blockview.v2.FabricBlockView; +import net.fabricmc.fabric.api.renderer.v1.model.ModelHelper; +import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; + +public class RiverstoneBakedModel implements BakedModel { + private final BakedModel regularModel; + private final BakedModel riverModel; + + public RiverstoneBakedModel(BakedModel regularModel, BakedModel riverModel) { + this.regularModel = regularModel; + this.riverModel = riverModel; + } + + @Override + public boolean isVanillaAdapter() { + return false; + } + + @Override + public void emitBlockQuads(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier randomSupplier, RenderContext context) { + if (((FabricBlockView) blockView).hasBiomes() && ((FabricBlockView) blockView).getBiomeFabric(pos).isIn(BiomeTags.IS_RIVER)) { + riverModel.emitBlockQuads(blockView, state, pos, randomSupplier, context); + } else { + regularModel.emitBlockQuads(blockView, state, pos, randomSupplier, context); + } + } + + @Override + public void emitItemQuads(ItemStack stack, Supplier randomSupplier, RenderContext context) { + regularModel.emitItemQuads(stack, randomSupplier, context); + } + + @Override + public List getQuads(@Nullable BlockState state, @Nullable Direction face, Random random) { + return Collections.emptyList(); + } + + @Override + public boolean useAmbientOcclusion() { + return true; + } + + @Override + public boolean hasDepth() { + return false; + } + + @Override + public boolean isSideLit() { + return true; + } + + @Override + public boolean isBuiltin() { + return false; + } + + @Override + public Sprite getParticleSprite() { + return regularModel.getParticleSprite(); + } + + @Override + public ModelTransformation getTransformation() { + return ModelHelper.MODEL_TRANSFORM_BLOCK; + } + + @Override + public ModelOverrideList getOverrides() { + return ModelOverrideList.EMPTY; + } +} diff --git a/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/RiverstoneUnbakedModel.java b/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/RiverstoneUnbakedModel.java new file mode 100644 index 0000000000..523cb2f103 --- /dev/null +++ b/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/RiverstoneUnbakedModel.java @@ -0,0 +1,53 @@ +/* + * 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 net.fabricmc.fabric.test.renderer.client; + +import java.util.Collection; +import java.util.Collections; +import java.util.function.Function; + +import org.jetbrains.annotations.Nullable; + +import net.minecraft.client.render.model.BakedModel; +import net.minecraft.client.render.model.Baker; +import net.minecraft.client.render.model.ModelBakeSettings; +import net.minecraft.client.render.model.UnbakedModel; +import net.minecraft.client.texture.Sprite; +import net.minecraft.client.util.SpriteIdentifier; +import net.minecraft.util.Identifier; + +public class RiverstoneUnbakedModel implements UnbakedModel { + private static final Identifier STONE_MODEL_ID = new Identifier("block/stone"); + private static final Identifier GOLD_BLOCK_MODEL_ID = new Identifier("block/gold_block"); + + @Override + public Collection getModelDependencies() { + return Collections.emptySet(); + } + + @Override + public void setParents(Function modelLoader) { + } + + @Nullable + @Override + public BakedModel bake(Baker baker, Function textureGetter, ModelBakeSettings rotationContainer, Identifier modelId) { + BakedModel stoneModel = baker.bake(STONE_MODEL_ID, rotationContainer); + BakedModel goldBlockModel = baker.bake(GOLD_BLOCK_MODEL_ID, rotationContainer); + return new RiverstoneBakedModel(stoneModel, goldBlockModel); + } +} diff --git a/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/SingleMeshBakedModel.java b/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/SingleMeshBakedModel.java new file mode 100644 index 0000000000..26b1d3b783 --- /dev/null +++ b/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/SingleMeshBakedModel.java @@ -0,0 +1,104 @@ +/* + * 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 net.fabricmc.fabric.test.renderer.client; + +import java.util.Collections; +import java.util.List; +import java.util.function.Supplier; + +import org.jetbrains.annotations.Nullable; + +import net.minecraft.block.BlockState; +import net.minecraft.client.render.model.BakedModel; +import net.minecraft.client.render.model.BakedQuad; +import net.minecraft.client.render.model.json.ModelOverrideList; +import net.minecraft.client.render.model.json.ModelTransformation; +import net.minecraft.client.texture.Sprite; +import net.minecraft.item.ItemStack; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.random.Random; +import net.minecraft.world.BlockRenderView; + +import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh; +import net.fabricmc.fabric.api.renderer.v1.model.ModelHelper; +import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; + +public class SingleMeshBakedModel implements BakedModel { + private final Mesh mesh; + private final Sprite particleSprite; + + public SingleMeshBakedModel(Mesh mesh, Sprite particleSprite) { + this.mesh = mesh; + this.particleSprite = particleSprite; + } + + @Override + public boolean isVanillaAdapter() { + return false; + } + + @Override + public void emitBlockQuads(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier randomSupplier, RenderContext context) { + mesh.outputTo(context.getEmitter()); + } + + @Override + public void emitItemQuads(ItemStack stack, Supplier randomSupplier, RenderContext context) { + mesh.outputTo(context.getEmitter()); + } + + @Override + public List getQuads(@Nullable BlockState state, @Nullable Direction face, Random random) { + return Collections.emptyList(); + } + + @Override + public boolean useAmbientOcclusion() { + return true; + } + + @Override + public boolean hasDepth() { + return false; + } + + @Override + public boolean isSideLit() { + return true; + } + + @Override + public boolean isBuiltin() { + return false; + } + + @Override + public Sprite getParticleSprite() { + return particleSprite; + } + + @Override + public ModelTransformation getTransformation() { + return ModelHelper.MODEL_TRANSFORM_BLOCK; + } + + @Override + public ModelOverrideList getOverrides() { + return ModelOverrideList.EMPTY; + } +} diff --git a/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/simple/client/FrameModelResourceProvider.java b/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/simple/client/FrameModelResourceProvider.java deleted file mode 100644 index 34ff995baa..0000000000 --- a/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/simple/client/FrameModelResourceProvider.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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 net.fabricmc.fabric.test.renderer.simple.client; - -import java.util.HashSet; -import java.util.Set; - -import org.jetbrains.annotations.Nullable; - -import net.minecraft.client.render.model.UnbakedModel; -import net.minecraft.util.Identifier; - -import net.fabricmc.fabric.api.client.model.ModelProviderContext; -import net.fabricmc.fabric.api.client.model.ModelResourceProvider; - -/** - * Provides the unbaked model for use with the frame block. - */ -final class FrameModelResourceProvider implements ModelResourceProvider { - static final Set FRAME_MODELS = new HashSet<>(); - - @Nullable - @Override - public UnbakedModel loadModelResource(Identifier resourceId, ModelProviderContext context) { - if (FRAME_MODELS.contains(resourceId)) { - return new FrameUnbakedModel(); - } - - return null; - } -} diff --git a/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/simple/client/FrameUnbakedModel.java b/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/simple/client/FrameUnbakedModel.java deleted file mode 100644 index 43b880b118..0000000000 --- a/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/simple/client/FrameUnbakedModel.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * 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 net.fabricmc.fabric.test.renderer.simple.client; - -import java.util.Collection; -import java.util.Collections; -import java.util.function.Function; - -import org.jetbrains.annotations.Nullable; - -import net.minecraft.client.render.model.BakedModel; -import net.minecraft.client.render.model.Baker; -import net.minecraft.client.render.model.ModelBakeSettings; -import net.minecraft.client.render.model.UnbakedModel; -import net.minecraft.client.texture.Sprite; -import net.minecraft.client.texture.SpriteAtlasTexture; -import net.minecraft.client.util.SpriteIdentifier; -import net.minecraft.util.Identifier; -import net.minecraft.util.math.Direction; - -import net.fabricmc.fabric.api.renderer.v1.Renderer; -import net.fabricmc.fabric.api.renderer.v1.RendererAccess; -import net.fabricmc.fabric.api.renderer.v1.mesh.MeshBuilder; -import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView; -import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter; - -final class FrameUnbakedModel implements UnbakedModel { - FrameUnbakedModel() { - } - - @Override - public Collection getModelDependencies() { - return Collections.emptySet(); - } - - @Override - public void setParents(Function function) { - } - - /* - * Bake the model. - * In this case we can prebake the frame into a mesh, but will render the contained block when we draw the quads. - */ - @Nullable - @Override - public BakedModel bake(Baker baker, Function textureGetter, ModelBakeSettings rotationContainer, Identifier modelId) { - // The renderer api may not have an implementation. - // For this reason we will just null check the renderer impl - if (RendererAccess.INSTANCE.hasRenderer()) { - Renderer renderer = RendererAccess.INSTANCE.getRenderer(); - MeshBuilder builder = renderer.meshBuilder(); - QuadEmitter emitter = builder.getEmitter(); - // TODO: Just some random texture to get a missing texture, we should get a proper texture soon - Sprite frameSprite = textureGetter.apply(new SpriteIdentifier(SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE, new Identifier("foo:foo"))); - - for (Direction direction : Direction.values()) { - // Draw outer frame - emitter.square(direction, 0.0F, 0.9F, 0.9F, 1.0F, 0.0F) - .spriteBake(frameSprite, MutableQuadView.BAKE_LOCK_UV) - .color(-1, -1, -1, -1) - .emit(); - - emitter.square(direction, 0.0F, 0.0F, 0.1F, 0.9F, 0.0F) - .spriteBake(frameSprite, MutableQuadView.BAKE_LOCK_UV) - .color(-1, -1, -1, -1) - .emit(); - - emitter.square(direction, 0.9F, 0.1F, 1.0F, 1.0F, 0.0F) - .spriteBake(frameSprite, MutableQuadView.BAKE_LOCK_UV) - .color(-1, -1, -1, -1) - .emit(); - - emitter.square(direction, 0.1F, 0.0F, 1.0F, 0.1F, 0.0F) - .spriteBake(frameSprite, MutableQuadView.BAKE_LOCK_UV) - .color(-1, -1, -1, -1) - .emit(); - - // Draw inner frame - inset by 0.9 so the frame looks like an actual mesh - emitter.square(direction, 0.0F, 0.9F, 0.9F, 1.0F, 0.9F) - .spriteBake(frameSprite, MutableQuadView.BAKE_LOCK_UV) - .color(-1, -1, -1, -1) - .emit(); - - emitter.square(direction, 0.0F, 0.0F, 0.1F, 0.9F, 0.9F) - .spriteBake(frameSprite, MutableQuadView.BAKE_LOCK_UV) - .color(-1, -1, -1, -1) - .emit(); - - emitter.square(direction, 0.9F, 0.1F, 1.0F, 1.0F, 0.9F) - .spriteBake(frameSprite, MutableQuadView.BAKE_LOCK_UV) - .color(-1, -1, -1, -1) - .emit(); - - emitter.square(direction, 0.1F, 0.0F, 1.0F, 0.1F, 0.9F) - .spriteBake(frameSprite, MutableQuadView.BAKE_LOCK_UV) - .color(-1, -1, -1, -1) - .emit(); - } - - return new FrameBakedModel(builder.build(), frameSprite); - } - - // No renderer implementation is present. - return null; - } -} diff --git a/fabric-renderer-api-v1/src/testmodClient/resources/assets/fabric-renderer-api-v1-testmod/blockstates/octagonal_column.json b/fabric-renderer-api-v1/src/testmodClient/resources/assets/fabric-renderer-api-v1-testmod/blockstates/octagonal_column.json new file mode 100644 index 0000000000..f62ea8ad6b --- /dev/null +++ b/fabric-renderer-api-v1/src/testmodClient/resources/assets/fabric-renderer-api-v1-testmod/blockstates/octagonal_column.json @@ -0,0 +1,5 @@ +{ + "variants": { + "": { "model": "fabric-renderer-api-v1-testmod:block/octagonal_column" } + } +} diff --git a/fabric-renderer-api-v1/src/testmodClient/resources/assets/fabric-renderer-api-v1-testmod/blockstates/pillar.json b/fabric-renderer-api-v1/src/testmodClient/resources/assets/fabric-renderer-api-v1-testmod/blockstates/pillar.json new file mode 100644 index 0000000000..53bec1ded6 --- /dev/null +++ b/fabric-renderer-api-v1/src/testmodClient/resources/assets/fabric-renderer-api-v1-testmod/blockstates/pillar.json @@ -0,0 +1,5 @@ +{ + "variants": { + "": { "model": "fabric-renderer-api-v1-testmod:block/pillar" } + } +} diff --git a/fabric-renderer-api-v1/src/testmodClient/resources/assets/fabric-renderer-api-v1-testmod/blockstates/riverstone.json b/fabric-renderer-api-v1/src/testmodClient/resources/assets/fabric-renderer-api-v1-testmod/blockstates/riverstone.json new file mode 100644 index 0000000000..68aaeaaf8f --- /dev/null +++ b/fabric-renderer-api-v1/src/testmodClient/resources/assets/fabric-renderer-api-v1-testmod/blockstates/riverstone.json @@ -0,0 +1,5 @@ +{ + "variants": { + "": { "model": "fabric-renderer-api-v1-testmod:block/riverstone" } + } +} diff --git a/fabric-renderer-indigo/build.gradle b/fabric-renderer-indigo/build.gradle index 70bf920fbf..7dd602d266 100644 --- a/fabric-renderer-indigo/build.gradle +++ b/fabric-renderer-indigo/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-renderer-indigo" version = getSubprojectVersion(project) loom { diff --git a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/Indigo.java b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/Indigo.java index a974bc781e..b1a94ce9a1 100644 --- a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/Indigo.java +++ b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/Indigo.java @@ -40,6 +40,12 @@ public class Indigo implements ClientModInitializer { /** Set true in dev env to confirm results match vanilla when they should. */ public static final boolean DEBUG_COMPARE_LIGHTING; public static final boolean FIX_SMOOTH_LIGHTING_OFFSET; + public static final boolean FIX_MEAN_LIGHT_CALCULATION; + /** + * Same value as {@link #FIX_MEAN_LIGHT_CALCULATION} because it is only required when the mean formula is changed, + * but different field to clearly separate code paths where we change emissive handling. + */ + public static final boolean FIX_EMISSIVE_LIGHTING; public static final boolean FIX_EXTERIOR_VERTEX_LIGHTING; public static final boolean FIX_LUMINOUS_AO_SHADE; @@ -115,6 +121,8 @@ private static TriState asTriState(String property) { AMBIENT_OCCLUSION_MODE = asEnum((String) properties.computeIfAbsent("ambient-occlusion-mode", (a) -> "hybrid"), AoConfig.HYBRID); DEBUG_COMPARE_LIGHTING = asBoolean((String) properties.computeIfAbsent("debug-compare-lighting", (a) -> "auto"), false); FIX_SMOOTH_LIGHTING_OFFSET = asBoolean((String) properties.computeIfAbsent("fix-smooth-lighting-offset", (a) -> "auto"), true); + FIX_MEAN_LIGHT_CALCULATION = asBoolean((String) properties.computeIfAbsent("fix-mean-light-calculation", (a) -> "auto"), true); + FIX_EMISSIVE_LIGHTING = FIX_MEAN_LIGHT_CALCULATION; FIX_EXTERIOR_VERTEX_LIGHTING = asBoolean((String) properties.computeIfAbsent("fix-exterior-vertex-lighting", (a) -> "auto"), true); FIX_LUMINOUS_AO_SHADE = asBoolean((String) properties.computeIfAbsent("fix-luminous-block-ambient-occlusion", (a) -> "auto"), false); diff --git a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/aocalc/AoCalculator.java b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/aocalc/AoCalculator.java index 30ff2534b6..2481a3a8ad 100644 --- a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/aocalc/AoCalculator.java +++ b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/aocalc/AoCalculator.java @@ -16,7 +16,6 @@ package net.fabricmc.fabric.impl.client.indigo.renderer.aocalc; -import static java.lang.Math.max; import static net.fabricmc.fabric.impl.client.indigo.renderer.helper.GeometryHelper.AXIS_ALIGNED_FLAG; import static net.fabricmc.fabric.impl.client.indigo.renderer.helper.GeometryHelper.CUBIC_FLAG; import static net.fabricmc.fabric.impl.client.indigo.renderer.helper.GeometryHelper.LIGHT_FACE_FLAG; @@ -34,11 +33,14 @@ import org.slf4j.LoggerFactory; import net.minecraft.block.BlockState; +import net.minecraft.client.render.LightmapTextureManager; +import net.minecraft.client.render.WorldRenderer; import net.minecraft.client.render.block.BlockModelRenderer; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Direction; import net.minecraft.util.math.MathHelper; import net.minecraft.world.BlockRenderView; +import net.minecraft.world.LightType; import net.fabricmc.fabric.impl.client.indigo.Indigo; import net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoFace.WeightFunction; @@ -385,6 +387,7 @@ private void computeFace(AoFaceData result, Direction lightFace, boolean isOnBlo searchState = world.getBlockState(searchPos); final int light0 = light(searchPos, searchState); final float ao0 = ao(searchPos, searchState); + final boolean em0 = hasEmissiveLighting(world, searchPos, searchState); if (!Indigo.FIX_SMOOTH_LIGHTING_OFFSET) { searchPos.move(lightFace); @@ -397,6 +400,7 @@ private void computeFace(AoFaceData result, Direction lightFace, boolean isOnBlo searchState = world.getBlockState(searchPos); final int light1 = light(searchPos, searchState); final float ao1 = ao(searchPos, searchState); + final boolean em1 = hasEmissiveLighting(world, searchPos, searchState); if (!Indigo.FIX_SMOOTH_LIGHTING_OFFSET) { searchPos.move(lightFace); @@ -409,6 +413,7 @@ private void computeFace(AoFaceData result, Direction lightFace, boolean isOnBlo searchState = world.getBlockState(searchPos); final int light2 = light(searchPos, searchState); final float ao2 = ao(searchPos, searchState); + final boolean em2 = hasEmissiveLighting(world, searchPos, searchState); if (!Indigo.FIX_SMOOTH_LIGHTING_OFFSET) { searchPos.move(lightFace); @@ -421,6 +426,7 @@ private void computeFace(AoFaceData result, Direction lightFace, boolean isOnBlo searchState = world.getBlockState(searchPos); final int light3 = light(searchPos, searchState); final float ao3 = ao(searchPos, searchState); + final boolean em3 = hasEmissiveLighting(world, searchPos, searchState); if (!Indigo.FIX_SMOOTH_LIGHTING_OFFSET) { searchPos.move(lightFace); @@ -432,6 +438,7 @@ private void computeFace(AoFaceData result, Direction lightFace, boolean isOnBlo // c = corner - values at corners of face int cLight0, cLight1, cLight2, cLight3; float cAo0, cAo1, cAo2, cAo3; + boolean cEm0, cEm1, cEm2, cEm3; // If neighbors on both sides of the corner are opaque, then apparently we use the light/shade // from one of the sides adjacent to the corner. If either neighbor is clear (no light subtraction) @@ -439,53 +446,64 @@ private void computeFace(AoFaceData result, Direction lightFace, boolean isOnBlo if (!isClear2 && !isClear0) { cAo0 = ao0; cLight0 = light0; + cEm0 = em0; } else { searchPos.set(lightPos).move(aoFace.neighbors[0]).move(aoFace.neighbors[2]); searchState = world.getBlockState(searchPos); cAo0 = ao(searchPos, searchState); cLight0 = light(searchPos, searchState); + cEm0 = hasEmissiveLighting(world, searchPos, searchState); } if (!isClear3 && !isClear0) { cAo1 = ao0; cLight1 = light0; + cEm1 = em0; } else { searchPos.set(lightPos).move(aoFace.neighbors[0]).move(aoFace.neighbors[3]); searchState = world.getBlockState(searchPos); cAo1 = ao(searchPos, searchState); cLight1 = light(searchPos, searchState); + cEm1 = hasEmissiveLighting(world, searchPos, searchState); } if (!isClear2 && !isClear1) { cAo2 = ao1; cLight2 = light1; + cEm2 = em1; } else { searchPos.set(lightPos).move(aoFace.neighbors[1]).move(aoFace.neighbors[2]); searchState = world.getBlockState(searchPos); cAo2 = ao(searchPos, searchState); cLight2 = light(searchPos, searchState); + cEm2 = hasEmissiveLighting(world, searchPos, searchState); } if (!isClear3 && !isClear1) { cAo3 = ao1; cLight3 = light1; + cEm3 = em1; } else { searchPos.set(lightPos).move(aoFace.neighbors[1]).move(aoFace.neighbors[3]); searchState = world.getBlockState(searchPos); cAo3 = ao(searchPos, searchState); cLight3 = light(searchPos, searchState); + cEm3 = hasEmissiveLighting(world, searchPos, searchState); } // If on block face or neighbor isn't occluding, "center" will be neighbor brightness // Doesn't use light pos because logic not based solely on this block's geometry int lightCenter; + boolean emCenter; searchPos.set(pos, lightFace); searchState = world.getBlockState(searchPos); if (isOnBlockFace || !searchState.isOpaqueFullCube(world, searchPos)) { lightCenter = light(searchPos, searchState); + emCenter = hasEmissiveLighting(world, searchPos, searchState); } else { lightCenter = light(pos, blockState); + emCenter = hasEmissiveLighting(world, pos, blockState); } float aoCenter = ao(lightPos, world.getBlockState(lightPos)); @@ -496,10 +514,39 @@ private void computeFace(AoFaceData result, Direction lightFace, boolean isOnBlo result.a2 = ((ao2 + ao1 + cAo2 + aoCenter) * 0.25F) * worldBrightness; result.a3 = ((ao3 + ao1 + cAo3 + aoCenter) * 0.25F) * worldBrightness; - result.l0(meanBrightness(light3, light0, cLight1, lightCenter)); - result.l1(meanBrightness(light2, light0, cLight0, lightCenter)); - result.l2(meanBrightness(light2, light1, cLight2, lightCenter)); - result.l3(meanBrightness(light3, light1, cLight3, lightCenter)); + result.l0(meanBrightness(light3, light0, cLight1, lightCenter, em3, em0, cEm1, emCenter)); + result.l1(meanBrightness(light2, light0, cLight0, lightCenter, em2, em0, cEm0, emCenter)); + result.l2(meanBrightness(light2, light1, cLight2, lightCenter, em2, em1, cEm2, emCenter)); + result.l3(meanBrightness(light3, light1, cLight3, lightCenter, em3, em1, cEm3, emCenter)); + } + + public static int getLightmapCoordinates(BlockRenderView world, BlockState state, BlockPos pos) { + if (Indigo.FIX_EMISSIVE_LIGHTING) { + // Same as WorldRenderer.getLightmapCoordinates but without the hasEmissiveLighting check. + // We don't want emissive lighting to influence the minimum lightmap in a quad, + // so when the fix is enabled we apply emissive lighting after the quad minimum is computed. + // See AoCalculator#meanBrightness. + int i = world.getLightLevel(LightType.SKY, pos); + int j = world.getLightLevel(LightType.BLOCK, pos); + int k = state.getLuminance(); + + if (j < k) { + j = k; + } + + return i << 20 | j << 4; + } else { + return WorldRenderer.getLightmapCoordinates(world, state, pos); + } + } + + private boolean hasEmissiveLighting(BlockRenderView world, BlockPos pos, BlockState state) { + if (Indigo.FIX_EMISSIVE_LIGHTING) { + return state.hasEmissiveLighting(world, pos); + } else { + // When the fix is disabled, emissive lighting was already applied and does not need to be accounted for. + return false; + } } /** @@ -507,11 +554,30 @@ private void computeFace(AoFaceData result, Direction lightFace, boolean isOnBlo * Still need to substitute or edges are too dark but consistently use the min * value from all four samples. */ - private static int meanBrightness(int a, int b, int c, int d) { - if (Indigo.FIX_SMOOTH_LIGHTING_OFFSET) { - return a == 0 || b == 0 || c == 0 || d == 0 ? meanEdgeBrightness(a, b, c, d) : meanInnerBrightness(a, b, c, d); + private static int meanBrightness(int lightA, int lightB, int lightC, int lightD, boolean emA, boolean emB, boolean emC, boolean emD) { + if (Indigo.FIX_MEAN_LIGHT_CALCULATION) { + if (lightA == 0 || lightB == 0 || lightC == 0 || lightD == 0) { + // Normalize values to non-zero minimum + final int min = nonZeroMin(nonZeroMin(lightA, lightB), nonZeroMin(lightC, lightD)); + + lightA = Math.max(lightA, min); + lightB = Math.max(lightB, min); + lightC = Math.max(lightC, min); + lightD = Math.max(lightD, min); + } + + if (Indigo.FIX_EMISSIVE_LIGHTING) { + // Apply the fullbright lightmap from emissive blocks at the very end so it cannot influence + // the minimum lightmap and produce incorrect results (for example, sculk sensors in a dark room) + if (emA) lightA = LightmapTextureManager.MAX_LIGHT_COORDINATE; + if (emB) lightB = LightmapTextureManager.MAX_LIGHT_COORDINATE; + if (emC) lightC = LightmapTextureManager.MAX_LIGHT_COORDINATE; + if (emD) lightD = LightmapTextureManager.MAX_LIGHT_COORDINATE; + } + + return meanInnerBrightness(lightA, lightB, lightC, lightD); } else { - return vanillaMeanBrightness(a, b, c, d); + return vanillaMeanBrightness(lightA, lightB, lightC, lightD); } } @@ -534,9 +600,4 @@ private static int nonZeroMin(int a, int b) { if (b == 0) return a; return Math.min(a, b); } - - private static int meanEdgeBrightness(int a, int b, int c, int d) { - final int min = nonZeroMin(nonZeroMin(a, b), nonZeroMin(c, d)); - return meanInnerBrightness(max(a, min), max(b, min), max(c, min), max(d, min)); - } } diff --git a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/helper/ColorHelper.java b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/helper/ColorHelper.java index 8ad80e2ef3..e5fe8fda52 100644 --- a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/helper/ColorHelper.java +++ b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/helper/ColorHelper.java @@ -18,8 +18,6 @@ import java.nio.ByteOrder; -import it.unimi.dsi.fastutil.ints.Int2IntFunction; - /** * Static routines of general utility for renderer implementations. * Renderers are not required to use these helpers, but they were @@ -28,14 +26,7 @@ public abstract class ColorHelper { private ColorHelper() { } - private static final Int2IntFunction colorSwapper = ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN ? color -> ((color & 0xFF00FF00) | ((color & 0x00FF0000) >> 16) | ((color & 0xFF) << 16)) : color -> color; - - /** - * Swaps red blue order if needed to match GPU expectations for color component order. - */ - public static int swapRedBlueIfNeeded(int color) { - return colorSwapper.applyAsInt(color); - } + private static final boolean BIG_ENDIAN = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN; /** Component-wise multiply. Components need to be in same order in both inputs! */ public static int multiplyColor(int color1, int color2) { @@ -45,9 +36,9 @@ public static int multiplyColor(int color1, int color2) { return color1; } - final int alpha = ((color1 >> 24) & 0xFF) * ((color2 >> 24) & 0xFF) / 0xFF; - final int red = ((color1 >> 16) & 0xFF) * ((color2 >> 16) & 0xFF) / 0xFF; - final int green = ((color1 >> 8) & 0xFF) * ((color2 >> 8) & 0xFF) / 0xFF; + final int alpha = ((color1 >>> 24) & 0xFF) * ((color2 >>> 24) & 0xFF) / 0xFF; + final int red = ((color1 >>> 16) & 0xFF) * ((color2 >>> 16) & 0xFF) / 0xFF; + final int green = ((color1 >>> 8) & 0xFF) * ((color2 >>> 8) & 0xFF) / 0xFF; final int blue = (color1 & 0xFF) * (color2 & 0xFF) / 0xFF; return (alpha << 24) | (red << 16) | (green << 8) | blue; @@ -55,9 +46,9 @@ public static int multiplyColor(int color1, int color2) { /** Multiplies three lowest components by shade. High byte (usually alpha) unchanged. */ public static int multiplyRGB(int color, float shade) { - final int alpha = ((color >> 24) & 0xFF); - final int red = (int) (((color >> 16) & 0xFF) * shade); - final int green = (int) (((color >> 8) & 0xFF) * shade); + final int alpha = ((color >>> 24) & 0xFF); + final int red = (int) (((color >>> 16) & 0xFF) * shade); + final int green = (int) (((color >>> 8) & 0xFF) * shade); final int blue = (int) ((color & 0xFF) * shade); return (alpha << 24) | (red << 16) | (green << 8) | blue; @@ -72,4 +63,54 @@ public static int maxBrightness(int b0, int b1) { return Math.max(b0 & 0xFFFF, b1 & 0xFFFF) | Math.max(b0 & 0xFFFF0000, b1 & 0xFFFF0000); } + + /* + 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/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/helper/GeometryHelper.java b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/helper/GeometryHelper.java index 72ca3b0f39..d814b40923 100644 --- a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/helper/GeometryHelper.java +++ b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/helper/GeometryHelper.java @@ -18,8 +18,6 @@ import static net.minecraft.util.math.MathHelper.approximatelyEquals; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.joml.Vector3f; import net.minecraft.client.render.model.BakedQuad; @@ -82,11 +80,7 @@ public static int computeShapeFlags(QuadView quad) { * Does not validate quad winding order. * Expects convex quads with all points co-planar. */ - public static boolean isQuadParallelToFace(@Nullable Direction face, QuadView quad) { - if (face == null) { - return false; - } - + public static boolean isQuadParallelToFace(Direction face, QuadView quad) { int i = face.getAxis().ordinal(); final float val = quad.posByIndex(0, i); return approximatelyEquals(val, quad.posByIndex(1, i)) && approximatelyEquals(val, quad.posByIndex(2, i)) && approximatelyEquals(val, quad.posByIndex(3, i)); @@ -100,8 +94,6 @@ public static boolean isQuadParallelToFace(@Nullable Direction face, QuadView qu * 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.getDirection() == AxisDirection.POSITIVE ? x >= EPS_MAX : x <= EPS_MIN; } @@ -114,14 +106,8 @@ public static boolean isParallelQuadOnFace(Direction lightFace, QuadView quad) { * 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; - } - + public static boolean isQuadCubic(Direction lightFace, QuadView quad) { int a, b; switch (lightFace) { diff --git a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/helper/NormalHelper.java b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/helper/NormalHelper.java index 04e8d8c08b..2e20765013 100644 --- a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/helper/NormalHelper.java +++ b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/helper/NormalHelper.java @@ -33,9 +33,12 @@ public abstract class NormalHelper { private NormalHelper() { } + private static final float PACK = 127.0f; + private static final float UNPACK = 1.0f / PACK; + /** * Stores a normal plus an extra value as a quartet of signed bytes. - * This is the same normal format that vanilla item rendering expects. + * This is the same normal format that vanilla rendering expects. * The extra value is for use by shaders. */ public static int packNormal(float x, float y, float z, float w) { @@ -44,7 +47,7 @@ public static int packNormal(float x, float y, float z, float w) { z = MathHelper.clamp(z, -1, 1); w = MathHelper.clamp(w, -1, 1); - return ((int) (x * 127) & 255) | (((int) (y * 127) & 255) << 8) | (((int) (z * 127) & 255) << 16) | (((int) (w * 127) & 255) << 24); + return ((int) (x * PACK) & 0xFF) | (((int) (y * PACK) & 0xFF) << 8) | (((int) (z * PACK) & 0xFF) << 16) | (((int) (w * PACK) & 0xFF) << 24); } /** @@ -55,12 +58,41 @@ public static int packNormal(Vector3f normal, float w) { } /** - * Retrieves values packed by {@link #packNormal(float, float, float, float)}. - * - *

    Components are x, y, z, w - zero based. + * Like {@link #packNormal(float, float, float, float)}, but without a {@code w} value. + */ + public static int packNormal(float x, float y, float z) { + x = MathHelper.clamp(x, -1, 1); + y = MathHelper.clamp(y, -1, 1); + z = MathHelper.clamp(z, -1, 1); + + return ((int) (x * PACK) & 0xFF) | (((int) (y * PACK) & 0xFF) << 8) | (((int) (z * PACK) & 0xFF) << 16); + } + + /** + * Like {@link #packNormal(Vector3f, float)}, but without a {@code w} value. */ - public static float getPackedNormalComponent(int packedNormal, int component) { - return ((byte) (packedNormal >> (8 * component))) / 127f; + public static int packNormal(Vector3f normal) { + return packNormal(normal.x(), normal.y(), normal.z()); + } + + public static float unpackNormalX(int packedNormal) { + return ((byte) (packedNormal & 0xFF)) * UNPACK; + } + + public static float unpackNormalY(int packedNormal) { + return ((byte) ((packedNormal >>> 8) & 0xFF)) * UNPACK; + } + + public static float unpackNormalZ(int packedNormal) { + return ((byte) ((packedNormal >>> 16) & 0xFF)) * UNPACK; + } + + public static float unpackNormalW(int packedNormal) { + return ((byte) ((packedNormal >>> 24) & 0xFF)) * UNPACK; + } + + public static void unpackNormal(int packedNormal, Vector3f target) { + target.set(unpackNormalX(packedNormal), unpackNormalY(packedNormal), unpackNormalZ(packedNormal)); } /** @@ -74,7 +106,7 @@ public static float getPackedNormalComponent(int packedNormal, int component) { public static void computeFaceNormal(@NotNull Vector3f saveTo, QuadView q) { final Direction nominalFace = q.nominalFace(); - if (GeometryHelper.isQuadParallelToFace(nominalFace, q)) { + if (nominalFace != null && GeometryHelper.isQuadParallelToFace(nominalFace, q)) { Vec3i vec = nominalFace.getVector(); saveTo.set(vec.getX(), vec.getY(), vec.getZ()); return; diff --git a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/material/MaterialViewImpl.java b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/material/MaterialViewImpl.java index 0cd1ff1cca..bb4f269ca8 100644 --- a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/material/MaterialViewImpl.java +++ b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/material/MaterialViewImpl.java @@ -61,9 +61,9 @@ protected static int bitMask(int bitLength, int bitOffset) { } protected static boolean areBitsValid(int bits) { - int blendMode = (bits & BLEND_MODE_MASK) >> BLEND_MODE_BIT_OFFSET; - int ao = (bits & AO_MASK) >> AO_BIT_OFFSET; - int glint = (bits & GLINT_MASK) >> GLINT_BIT_OFFSET; + int blendMode = (bits & BLEND_MODE_MASK) >>> BLEND_MODE_BIT_OFFSET; + int ao = (bits & AO_MASK) >>> AO_BIT_OFFSET; + int glint = (bits & GLINT_MASK) >>> GLINT_BIT_OFFSET; return blendMode < BLEND_MODE_COUNT && ao < TRI_STATE_COUNT @@ -78,7 +78,7 @@ protected MaterialViewImpl(int bits) { @Override public BlendMode blendMode() { - return BLEND_MODES[(bits & BLEND_MODE_MASK) >> BLEND_MODE_BIT_OFFSET]; + return BLEND_MODES[(bits & BLEND_MODE_MASK) >>> BLEND_MODE_BIT_OFFSET]; } @Override @@ -98,11 +98,11 @@ public boolean disableDiffuse() { @Override public TriState ambientOcclusion() { - return TRI_STATES[(bits & AO_MASK) >> AO_BIT_OFFSET]; + return TRI_STATES[(bits & AO_MASK) >>> AO_BIT_OFFSET]; } @Override public TriState glint() { - return TRI_STATES[(bits & GLINT_MASK) >> GLINT_BIT_OFFSET]; + return TRI_STATES[(bits & GLINT_MASK) >>> GLINT_BIT_OFFSET]; } } diff --git a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/mesh/EncodingFormat.java b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/mesh/EncodingFormat.java index 66c70ac21e..9c5eef1a40 100644 --- a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/mesh/EncodingFormat.java +++ b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/mesh/EncodingFormat.java @@ -37,9 +37,10 @@ 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 HEADER_FACE_NORMAL = 1; + static final int HEADER_COLOR_INDEX = 2; + static final int HEADER_TAG = 3; + public static final int HEADER_STRIDE = 4; static final int VERTEX_X; static final int VERTEX_Y; @@ -100,7 +101,7 @@ private EncodingFormat() { } } static Direction cullFace(int bits) { - return ModelHelper.faceFromIndex((bits >> CULL_SHIFT) & DIRECTION_MASK); + return ModelHelper.faceFromIndex((bits >>> CULL_SHIFT) & DIRECTION_MASK); } static int cullFace(int bits, Direction face) { @@ -108,7 +109,7 @@ static int cullFace(int bits, Direction face) { } static Direction lightFace(int bits) { - return ModelHelper.faceFromIndex((bits >> LIGHT_SHIFT) & DIRECTION_MASK); + return ModelHelper.faceFromIndex((bits >>> LIGHT_SHIFT) & DIRECTION_MASK); } static int lightFace(int bits, Direction face) { @@ -117,7 +118,7 @@ static int lightFace(int bits, Direction face) { /** indicate if vertex normal has been set - bits correspond to vertex ordinals. */ static int normalFlags(int bits) { - return (bits >> NORMALS_SHIFT) & NORMALS_MASK; + return (bits >>> NORMALS_SHIFT) & NORMALS_MASK; } static int normalFlags(int bits, int normalFlags) { @@ -125,7 +126,7 @@ static int normalFlags(int bits, int normalFlags) { } static int geometryFlags(int bits) { - return (bits >> GEOMETRY_SHIFT) & GEOMETRY_MASK; + return (bits >>> GEOMETRY_SHIFT) & GEOMETRY_MASK; } static int geometryFlags(int bits, int geometryFlags) { @@ -133,7 +134,7 @@ static int geometryFlags(int bits, int geometryFlags) { } static RenderMaterialImpl material(int bits) { - return RenderMaterialImpl.byIndex((bits >> MATERIAL_SHIFT) & MATERIAL_MASK); + return RenderMaterialImpl.byIndex((bits >>> MATERIAL_SHIFT) & MATERIAL_MASK); } static int material(int bits, RenderMaterialImpl material) { diff --git a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/mesh/MeshBuilderImpl.java b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/mesh/MeshBuilderImpl.java index 5160dd0421..b0798c2d93 100644 --- a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/mesh/MeshBuilderImpl.java +++ b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/mesh/MeshBuilderImpl.java @@ -25,13 +25,20 @@ * Not much to it - mainly it just needs to grow the int[] array as quads are appended * and maintain/provide a properly-configured {@link net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView} instance. * All the encoding and other work is handled in the quad base classes. - * The one interesting bit is in {@link Maker#emit()}. + * The one interesting bit is in {@link Maker#emitDirectly()}. */ public class MeshBuilderImpl implements MeshBuilder { - int[] data = new int[256]; + private int[] data = new int[256]; + private int index = 0; + private int limit = data.length; private final Maker maker = new Maker(); - int index = 0; - int limit = data.length; + + public MeshBuilderImpl() { + ensureCapacity(EncodingFormat.TOTAL_STRIDE); + maker.data = data; + maker.baseIndex = index; + maker.clear(); + } protected void ensureCapacity(int stride) { if (stride > limit - index) { @@ -39,41 +46,39 @@ protected void ensureCapacity(int stride) { final int[] bigger = new int[limit]; System.arraycopy(data, 0, bigger, 0, index); data = bigger; - maker.data = bigger; + maker.data = data; } } + @Override + public QuadEmitter getEmitter() { + maker.clear(); + return maker; + } + @Override public Mesh build() { final int[] packed = new int[index]; System.arraycopy(data, 0, packed, 0, index); index = 0; - maker.begin(data, index); + maker.baseIndex = index; + maker.clear(); 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 { + private class Maker extends MutableQuadViewImpl { @Override - public Maker emit() { + public void emitDirectly() { computeGeometry(); index += EncodingFormat.TOTAL_STRIDE; ensureCapacity(EncodingFormat.TOTAL_STRIDE); baseIndex = index; - clear(); - return this; } } } diff --git a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/mesh/MeshImpl.java b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/mesh/MeshImpl.java index 0fcc68cb6b..58479ebd4e 100644 --- a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/mesh/MeshImpl.java +++ b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/mesh/MeshImpl.java @@ -19,6 +19,7 @@ import java.util.function.Consumer; import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh; +import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter; import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView; /** @@ -27,7 +28,7 @@ */ public class MeshImpl implements Mesh { /** Used to satisfy external calls to {@link #forEach(Consumer)}. */ - ThreadLocal POOL = ThreadLocal.withInitial(QuadViewImpl::new); + private final ThreadLocal cursorPool = ThreadLocal.withInitial(QuadViewImpl::new); final int[] data; @@ -35,28 +36,43 @@ public class MeshImpl implements Mesh { this.data = data; } - public int[] data() { - return data; - } - @Override public void forEach(Consumer consumer) { - forEach(consumer, POOL.get()); + forEach(consumer, cursorPool.get()); } /** - * The renderer will call this with it's own cursor + * The renderer can call this with its 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; + cursor.data = this.data; while (index < limit) { - cursor.load(data, index); + cursor.baseIndex = index; + cursor.load(); consumer.accept(cursor); index += EncodingFormat.TOTAL_STRIDE; } } + + @Override + public void outputTo(QuadEmitter emitter) { + MutableQuadViewImpl e = (MutableQuadViewImpl) emitter; + final int[] data = this.data; + final int limit = data.length; + int index = 0; + + while (index < limit) { + System.arraycopy(data, index, e.data, e.baseIndex, EncodingFormat.TOTAL_STRIDE); + e.load(); + e.emitDirectly(); + index += EncodingFormat.TOTAL_STRIDE; + } + + e.clear(); + } } diff --git a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/mesh/MutableQuadViewImpl.java b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/mesh/MutableQuadViewImpl.java index a8e04466c4..09cfbd5dd3 100644 --- a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/mesh/MutableQuadViewImpl.java +++ b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/mesh/MutableQuadViewImpl.java @@ -21,7 +21,6 @@ import static net.fabricmc.fabric.impl.client.indigo.renderer.mesh.EncodingFormat.HEADER_COLOR_INDEX; import static net.fabricmc.fabric.impl.client.indigo.renderer.mesh.EncodingFormat.HEADER_STRIDE; import static net.fabricmc.fabric.impl.client.indigo.renderer.mesh.EncodingFormat.HEADER_TAG; -import static net.fabricmc.fabric.impl.client.indigo.renderer.mesh.EncodingFormat.QUAD_STRIDE; import static net.fabricmc.fabric.impl.client.indigo.renderer.mesh.EncodingFormat.VERTEX_COLOR; import static net.fabricmc.fabric.impl.client.indigo.renderer.mesh.EncodingFormat.VERTEX_LIGHTMAP; import static net.fabricmc.fabric.impl.client.indigo.renderer.mesh.EncodingFormat.VERTEX_NORMAL; @@ -39,21 +38,22 @@ import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter; import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView; import net.fabricmc.fabric.impl.client.indigo.renderer.IndigoRenderer; +import net.fabricmc.fabric.impl.client.indigo.renderer.helper.ColorHelper; import net.fabricmc.fabric.impl.client.indigo.renderer.helper.NormalHelper; import net.fabricmc.fabric.impl.client.indigo.renderer.helper.TextureHelper; import net.fabricmc.fabric.impl.client.indigo.renderer.material.RenderMaterialImpl; /** - * Almost-concrete implementation of a mutable quad. The only missing part is {@link #emit()}, + * Almost-concrete implementation of a mutable quad. The only missing part is {@link #emitDirectly()}, * because that depends on where/how it is used. (Mesh encoding vs. render-time transformation). + * + *

    In many cases an instance of this class is used as an "editor quad". The editor quad's + * {@link #emitDirectly()} method calls some other internal method that transforms the quad + * data and then buffers it. Transformations should be the same as they would be in a vanilla + * render - the editor is serving mainly as a way to access vertex data without magical + * numbers. It also allows for a consistent interface for those transformations. */ public abstract class MutableQuadViewImpl extends QuadViewImpl implements QuadEmitter { - 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; @@ -108,7 +108,7 @@ protected void normalFlags(int 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); + data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_NORMAL] = NormalHelper.packNormal(x, y, z); return this; } @@ -120,7 +120,7 @@ public final void populateMissingNormals() { if (normalFlags == 0b1111) return; - final int packedFaceNormal = NormalHelper.packNormal(faceNormal(), 0); + final int packedFaceNormal = packedFaceNormal(); for (int v = 0; v < 4; v++) { if ((normalFlags & (1 << v)) == 0) { @@ -180,8 +180,16 @@ public MutableQuadViewImpl copyFrom(QuadView quad) { @Override public final MutableQuadViewImpl fromVanilla(int[] quadData, int startIndex) { - System.arraycopy(quadData, startIndex, data, baseIndex + HEADER_STRIDE, QUAD_STRIDE); + 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; } @@ -200,4 +208,17 @@ public final MutableQuadViewImpl fromVanilla(BakedQuad quad, RenderMaterial mate tag(0); return this; } + + /** + * Emit the quad without clearing the underlying data. + * Geometry is not guaranteed to be valid when called, but can be computed by calling {@link #computeGeometry()}. + */ + public abstract void emitDirectly(); + + @Override + public final MutableQuadViewImpl emit() { + emitDirectly(); + clear(); + return this; + } } diff --git a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/mesh/QuadViewImpl.java b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/mesh/QuadViewImpl.java index 86d70838c3..84fc20082b 100644 --- a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/mesh/QuadViewImpl.java +++ b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/mesh/QuadViewImpl.java @@ -18,6 +18,8 @@ import static net.fabricmc.fabric.impl.client.indigo.renderer.mesh.EncodingFormat.HEADER_BITS; import static net.fabricmc.fabric.impl.client.indigo.renderer.mesh.EncodingFormat.HEADER_COLOR_INDEX; +import static net.fabricmc.fabric.impl.client.indigo.renderer.mesh.EncodingFormat.HEADER_FACE_NORMAL; +import static net.fabricmc.fabric.impl.client.indigo.renderer.mesh.EncodingFormat.HEADER_STRIDE; import static net.fabricmc.fabric.impl.client.indigo.renderer.mesh.EncodingFormat.HEADER_TAG; import static net.fabricmc.fabric.impl.client.indigo.renderer.mesh.EncodingFormat.QUAD_STRIDE; import static net.fabricmc.fabric.impl.client.indigo.renderer.mesh.EncodingFormat.VERTEX_COLOR; @@ -38,6 +40,7 @@ import net.minecraft.util.math.Direction; import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView; +import net.fabricmc.fabric.impl.client.indigo.renderer.helper.ColorHelper; import net.fabricmc.fabric.impl.client.indigo.renderer.helper.GeometryHelper; import net.fabricmc.fabric.impl.client.indigo.renderer.helper.NormalHelper; import net.fabricmc.fabric.impl.client.indigo.renderer.material.RenderMaterialImpl; @@ -49,7 +52,7 @@ public class QuadViewImpl implements QuadView { @Nullable protected Direction nominalFace; - /** True when geometry flags or light face may not match geometry. */ + /** True when face normal, light face, or geometry flags may not match geometry. */ protected boolean isGeometryInvalid = true; protected final Vector3f faceNormal = new Vector3f(); @@ -60,45 +63,13 @@ public class QuadViewImpl implements QuadView { protected int baseIndex = 0; /** - * Use when subtype is "attached" to a pre-existing array. - * Sets data reference and index and decodes state from array. + * Decodes necessary state from the backing data array. + * The encoded data must contain valid computed geometry. */ - 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() { + public 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]); + NormalHelper.unpackNormal(packedFaceNormal(), faceNormal); } protected void computeGeometry() { @@ -106,6 +77,7 @@ protected void computeGeometry() { isGeometryInvalid = false; NormalHelper.computeFaceNormal(faceNormal, this); + data[baseIndex + HEADER_FACE_NORMAL] = NormalHelper.packNormal(faceNormal); // depends on face normal data[baseIndex + HEADER_BITS] = EncodingFormat.lightFace(data[baseIndex + HEADER_BITS], GeometryHelper.lightFace(this)); @@ -115,6 +87,12 @@ protected void computeGeometry() { } } + /** gets flags used for lighting - lazily computed via {@link GeometryHelper#computeShapeFlags(QuadView)}. */ + public int geometryFlags() { + computeGeometry(); + return EncodingFormat.geometryFlags(data[baseIndex + HEADER_BITS]); + } + public boolean hasShade() { return !material().disableDiffuse(); } @@ -181,28 +159,42 @@ public int lightmap(int vertexIndex) { return data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_LIGHTMAP]; } + public int normalFlags() { + return EncodingFormat.normalFlags(data[baseIndex + HEADER_BITS]); + } + @Override public boolean hasNormal(int vertexIndex) { return (normalFlags() & (1 << vertexIndex)) != 0; } + /** True if any vertex normal has been set. */ + public boolean hasVertexNormals() { + return normalFlags() != 0; + } + + /** True if all vertex normals have been set. */ + public boolean hasAllVertexNormals() { + return (normalFlags() & 0b1111) == 0b1111; + } + protected final int normalIndex(int vertexIndex) { return baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_NORMAL; } @Override public float normalX(int vertexIndex) { - return hasNormal(vertexIndex) ? NormalHelper.getPackedNormalComponent(data[normalIndex(vertexIndex)], 0) : Float.NaN; + return hasNormal(vertexIndex) ? NormalHelper.unpackNormalX(data[normalIndex(vertexIndex)]) : Float.NaN; } @Override public float normalY(int vertexIndex) { - return hasNormal(vertexIndex) ? NormalHelper.getPackedNormalComponent(data[normalIndex(vertexIndex)], 1) : Float.NaN; + return hasNormal(vertexIndex) ? NormalHelper.unpackNormalY(data[normalIndex(vertexIndex)]) : Float.NaN; } @Override public float normalZ(int vertexIndex) { - return hasNormal(vertexIndex) ? NormalHelper.getPackedNormalComponent(data[normalIndex(vertexIndex)], 2) : Float.NaN; + return hasNormal(vertexIndex) ? NormalHelper.unpackNormalZ(data[normalIndex(vertexIndex)]) : Float.NaN; } @Override @@ -214,7 +206,7 @@ public Vector3f copyNormal(int vertexIndex, @Nullable Vector3f target) { } final int normal = data[normalIndex(vertexIndex)]; - target.set(NormalHelper.getPackedNormalComponent(normal, 0), NormalHelper.getPackedNormalComponent(normal, 1), NormalHelper.getPackedNormalComponent(normal, 2)); + NormalHelper.unpackNormal(normal, target); return target; } else { return null; @@ -240,6 +232,11 @@ public final Direction nominalFace() { return nominalFace; } + public final int packedFaceNormal() { + computeGeometry(); + return data[baseIndex + HEADER_FACE_NORMAL]; + } + @Override public final Vector3f faceNormal() { computeGeometry(); @@ -263,6 +260,16 @@ public final int tag() { @Override public final void toVanilla(int[] target, int targetIndex) { - System.arraycopy(data, baseIndex + VERTEX_X, target, targetIndex, QUAD_STRIDE); + 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/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/AbstractBlockRenderContext.java b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/AbstractBlockRenderContext.java new file mode 100644 index 0000000000..38659bb2fd --- /dev/null +++ b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/AbstractBlockRenderContext.java @@ -0,0 +1,311 @@ +/* + * 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 net.fabricmc.fabric.impl.client.indigo.renderer.render; + +import static net.fabricmc.fabric.impl.client.indigo.renderer.helper.GeometryHelper.AXIS_ALIGNED_FLAG; +import static net.fabricmc.fabric.impl.client.indigo.renderer.helper.GeometryHelper.LIGHT_FACE_FLAG; + +import org.jetbrains.annotations.Nullable; +import org.joml.Vector3f; + +import net.minecraft.block.BlockState; +import net.minecraft.client.render.LightmapTextureManager; +import net.minecraft.client.render.RenderLayer; +import net.minecraft.client.render.VertexConsumer; +import net.minecraft.client.render.WorldRenderer; +import net.minecraft.client.render.model.BakedModel; +import net.minecraft.client.render.model.json.ModelTransformationMode; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; + +import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial; +import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter; +import net.fabricmc.fabric.api.util.TriState; +import net.fabricmc.fabric.impl.client.indigo.Indigo; +import net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoCalculator; +import net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoConfig; +import net.fabricmc.fabric.impl.client.indigo.renderer.helper.ColorHelper; +import net.fabricmc.fabric.impl.client.indigo.renderer.mesh.EncodingFormat; +import net.fabricmc.fabric.impl.client.indigo.renderer.mesh.MutableQuadViewImpl; +import net.fabricmc.fabric.impl.renderer.VanillaModelEncoder; + +public abstract class AbstractBlockRenderContext extends AbstractRenderContext { + protected final BlockRenderInfo blockInfo = new BlockRenderInfo(); + protected final AoCalculator aoCalc; + + private final MutableQuadViewImpl editorQuad = new MutableQuadViewImpl() { + { + data = new int[EncodingFormat.TOTAL_STRIDE]; + clear(); + } + + @Override + public void emitDirectly() { + renderQuad(this, false); + } + }; + private final MutableQuadViewImpl vanillaModelEditorQuad = new MutableQuadViewImpl() { + { + data = new int[EncodingFormat.TOTAL_STRIDE]; + clear(); + } + + @Override + public void emitDirectly() { + renderQuad(this, true); + } + }; + + private final BakedModelConsumerImpl vanillaModelConsumer = new BakedModelConsumerImpl(); + + private final BlockPos.Mutable lightPos = new BlockPos.Mutable(); + + protected AbstractBlockRenderContext() { + aoCalc = createAoCalc(blockInfo); + } + + protected abstract AoCalculator createAoCalc(BlockRenderInfo blockInfo); + + protected abstract VertexConsumer getVertexConsumer(RenderLayer layer); + + @Override + public QuadEmitter getEmitter() { + editorQuad.clear(); + return editorQuad; + } + + public QuadEmitter getVanillaModelEmitter() { + // Do not clear the editorQuad since it is not accessible to API users. + return vanillaModelEditorQuad; + } + + @Override + public boolean isFaceCulled(@Nullable Direction face) { + return !blockInfo.shouldDrawFace(face); + } + + @Override + public ModelTransformationMode itemTransformationMode() { + throw new IllegalStateException("itemTransformationMode() can only be called on an item render context."); + } + + @Override + public BakedModelConsumer bakedModelConsumer() { + return vanillaModelConsumer; + } + + private void renderQuad(MutableQuadViewImpl quad, boolean isVanilla) { + if (!transform(quad)) { + return; + } + + if (isFaceCulled(quad.cullFace())) { + return; + } + + final RenderMaterial mat = quad.material(); + final int colorIndex = mat.disableColorIndex() ? -1 : quad.colorIndex(); + final TriState aoMode = mat.ambientOcclusion(); + final boolean ao = blockInfo.useAo && (aoMode == TriState.TRUE || (aoMode == TriState.DEFAULT && blockInfo.defaultAo)); + final boolean emissive = mat.emissive(); + final VertexConsumer vertexConsumer = getVertexConsumer(blockInfo.effectiveRenderLayer(mat.blendMode())); + + colorizeQuad(quad, colorIndex); + shadeQuad(quad, isVanilla, ao, emissive); + bufferQuad(quad, vertexConsumer); + } + + /** handles block color, common to all renders. */ + private void colorizeQuad(MutableQuadViewImpl quad, int colorIndex) { + if (colorIndex != -1) { + final int blockColor = blockInfo.blockColor(colorIndex); + + for (int i = 0; i < 4; i++) { + quad.color(i, ColorHelper.multiplyColor(blockColor, quad.color(i))); + } + } + } + + private void shadeQuad(MutableQuadViewImpl quad, boolean isVanilla, boolean ao, boolean emissive) { + // routines below have a bit of copy-paste code reuse to avoid conditional execution inside a hot loop + if (ao) { + aoCalc.compute(quad, isVanilla); + + if (emissive) { + for (int i = 0; i < 4; i++) { + quad.color(i, ColorHelper.multiplyRGB(quad.color(i), aoCalc.ao[i])); + quad.lightmap(i, LightmapTextureManager.MAX_LIGHT_COORDINATE); + } + } else { + for (int i = 0; i < 4; i++) { + quad.color(i, ColorHelper.multiplyRGB(quad.color(i), aoCalc.ao[i])); + quad.lightmap(i, ColorHelper.maxBrightness(quad.lightmap(i), aoCalc.light[i])); + } + } + } else { + shadeFlatQuad(quad, isVanilla); + + if (emissive) { + for (int i = 0; i < 4; i++) { + quad.lightmap(i, LightmapTextureManager.MAX_LIGHT_COORDINATE); + } + } else { + final int brightness = flatBrightness(quad, blockInfo.blockState, blockInfo.blockPos); + + for (int i = 0; i < 4; i++) { + quad.lightmap(i, ColorHelper.maxBrightness(quad.lightmap(i), brightness)); + } + } + } + } + + /** + * Starting in 1.16 flat shading uses dimension-specific diffuse factors that can be < 1.0 + * even for un-shaded quads. These are also applied with AO shading but that is done in AO calculator. + */ + private void shadeFlatQuad(MutableQuadViewImpl quad, boolean isVanilla) { + final boolean hasShade = quad.hasShade(); + + // Check the AO mode to match how shade is applied during smooth lighting + if ((Indigo.AMBIENT_OCCLUSION_MODE == AoConfig.HYBRID && !isVanilla) || Indigo.AMBIENT_OCCLUSION_MODE == AoConfig.ENHANCED) { + if (quad.hasAllVertexNormals()) { + for (int i = 0; i < 4; i++) { + float shade = normalShade(quad.normalX(i), quad.normalY(i), quad.normalZ(i), hasShade); + quad.color(i, ColorHelper.multiplyRGB(quad.color(i), shade)); + } + } else { + final float faceShade; + + if ((quad.geometryFlags() & AXIS_ALIGNED_FLAG) != 0) { + faceShade = blockInfo.blockView.getBrightness(quad.lightFace(), hasShade); + } else { + Vector3f faceNormal = quad.faceNormal(); + faceShade = normalShade(faceNormal.x, faceNormal.y, faceNormal.z, hasShade); + } + + if (quad.hasVertexNormals()) { + for (int i = 0; i < 4; i++) { + float shade; + + if (quad.hasNormal(i)) { + shade = normalShade(quad.normalX(i), quad.normalY(i), quad.normalZ(i), hasShade); + } else { + shade = faceShade; + } + + quad.color(i, ColorHelper.multiplyRGB(quad.color(i), shade)); + } + } else { + if (faceShade != 1.0f) { + for (int i = 0; i < 4; i++) { + quad.color(i, ColorHelper.multiplyRGB(quad.color(i), faceShade)); + } + } + } + } + } else { + final float faceShade = blockInfo.blockView.getBrightness(quad.lightFace(), hasShade); + + if (faceShade != 1.0f) { + for (int i = 0; i < 4; i++) { + quad.color(i, ColorHelper.multiplyRGB(quad.color(i), faceShade)); + } + } + } + } + + /** + * Finds mean of per-face shading factors weighted by normal components. + * Not how light actually works but the vanilla diffuse shading model is a hack to start with + * and this gives reasonable results for non-cubic surfaces in a vanilla-style renderer. + */ + private float normalShade(float normalX, float normalY, float normalZ, boolean hasShade) { + float sum = 0; + float div = 0; + + if (normalX > 0) { + sum += normalX * blockInfo.blockView.getBrightness(Direction.EAST, hasShade); + div += normalX; + } else if (normalX < 0) { + sum += -normalX * blockInfo.blockView.getBrightness(Direction.WEST, hasShade); + div -= normalX; + } + + if (normalY > 0) { + sum += normalY * blockInfo.blockView.getBrightness(Direction.UP, hasShade); + div += normalY; + } else if (normalY < 0) { + sum += -normalY * blockInfo.blockView.getBrightness(Direction.DOWN, hasShade); + div -= normalY; + } + + if (normalZ > 0) { + sum += normalZ * blockInfo.blockView.getBrightness(Direction.SOUTH, hasShade); + div += normalZ; + } else if (normalZ < 0) { + sum += -normalZ * blockInfo.blockView.getBrightness(Direction.NORTH, hasShade); + div -= normalZ; + } + + return sum / div; + } + + /** + * Handles geometry-based check for using self brightness or neighbor brightness. + * That logic only applies in flat lighting. + */ + private int flatBrightness(MutableQuadViewImpl quad, BlockState blockState, BlockPos pos) { + lightPos.set(pos); + + // To mirror Vanilla's behavior, if the face has a cull-face, always sample the light value + // offset in that direction. See net.minecraft.client.render.block.BlockModelRenderer.renderQuadsFlat + // for reference. + if (quad.cullFace() != null) { + lightPos.move(quad.cullFace()); + } else { + final int flags = quad.geometryFlags(); + + if ((flags & LIGHT_FACE_FLAG) != 0 || ((flags & AXIS_ALIGNED_FLAG) != 0 && blockState.isFullCube(blockInfo.blockView, pos))) { + lightPos.move(quad.lightFace()); + } + } + + // Unfortunately cannot use brightness cache here unless we implement one specifically for flat lighting. See #329 + return WorldRenderer.getLightmapCoordinates(blockInfo.blockView, blockState, lightPos); + } + + /** + * Consumer for vanilla baked models. Generally intended to give visual results matching a vanilla render, + * however there could be subtle (and desirable) lighting variations so is good to be able to render + * everything consistently. + * + *

    Also, the API allows multi-part models that hold multiple vanilla models to render them without + * combining quad lists, but the vanilla logic only handles one model per block. To route all of + * them through vanilla logic would require additional hooks. + */ + private class BakedModelConsumerImpl implements BakedModelConsumer { + @Override + public void accept(BakedModel model) { + accept(model, blockInfo.blockState); + } + + @Override + public void accept(BakedModel model, @Nullable BlockState state) { + VanillaModelEncoder.emitBlockQuads(model, state, blockInfo.randomSupplier, AbstractBlockRenderContext.this, vanillaModelEditorQuad); + } + } +} diff --git a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/AbstractMeshConsumer.java b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/AbstractMeshConsumer.java deleted file mode 100644 index bb35c44793..0000000000 --- a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/AbstractMeshConsumer.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * 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 net.fabricmc.fabric.impl.client.indigo.renderer.render; - -import java.util.function.Consumer; -import java.util.function.Function; - -import net.minecraft.client.render.RenderLayer; -import net.minecraft.client.render.VertexConsumer; - -import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh; -import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter; -import net.fabricmc.fabric.api.renderer.v1.render.RenderContext.QuadTransform; -import net.fabricmc.fabric.impl.client.indigo.renderer.IndigoRenderer; -import net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoCalculator; -import net.fabricmc.fabric.impl.client.indigo.renderer.mesh.EncodingFormat; -import net.fabricmc.fabric.impl.client.indigo.renderer.mesh.MeshImpl; -import net.fabricmc.fabric.impl.client.indigo.renderer.mesh.MutableQuadViewImpl; - -/** - * Consumer for pre-baked meshes. Works by copying the mesh data to an - * "editor" quad held in the instance, where all transformations are applied before buffering. - */ -public abstract class AbstractMeshConsumer extends AbstractQuadRenderer implements Consumer { - protected AbstractMeshConsumer(BlockRenderInfo blockInfo, Function bufferFunc, AoCalculator aoCalc, QuadTransform transform) { - super(blockInfo, bufferFunc, aoCalc, transform); - } - - /** - * Where we handle all pre-buffer coloring, lighting, transformation, etc. - * Reused for all mesh quads. Fixed baking array sized to hold largest possible mesh quad. - */ - private class Maker extends MutableQuadViewImpl { - { - data = new int[EncodingFormat.TOTAL_STRIDE]; - material(IndigoRenderer.MATERIAL_STANDARD); - } - - // only used via RenderContext.getEmitter() - @Override - public Maker emit() { - computeGeometry(); - renderQuad(this, false); - clear(); - return this; - } - } - - private final Maker editorQuad = new Maker(); - - @Override - public void accept(Mesh mesh) { - final MeshImpl m = (MeshImpl) mesh; - final int[] data = m.data(); - final int limit = data.length; - int index = 0; - - while (index < limit) { - System.arraycopy(data, index, editorQuad.data(), 0, EncodingFormat.TOTAL_STRIDE); - editorQuad.load(); - index += EncodingFormat.TOTAL_STRIDE; - renderQuad(editorQuad, false); - } - } - - public QuadEmitter getEmitter() { - editorQuad.clear(); - return editorQuad; - } -} diff --git a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/AbstractQuadRenderer.java b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/AbstractQuadRenderer.java deleted file mode 100644 index b543b35f0e..0000000000 --- a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/AbstractQuadRenderer.java +++ /dev/null @@ -1,296 +0,0 @@ -/* - * 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 net.fabricmc.fabric.impl.client.indigo.renderer.render; - -import static net.fabricmc.fabric.impl.client.indigo.renderer.helper.GeometryHelper.AXIS_ALIGNED_FLAG; -import static net.fabricmc.fabric.impl.client.indigo.renderer.helper.GeometryHelper.LIGHT_FACE_FLAG; - -import java.util.function.Function; - -import org.joml.Matrix3f; -import org.joml.Matrix4f; -import org.joml.Vector3f; - -import net.minecraft.block.BlockState; -import net.minecraft.client.render.LightmapTextureManager; -import net.minecraft.client.render.RenderLayer; -import net.minecraft.client.render.VertexConsumer; -import net.minecraft.client.render.WorldRenderer; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.Direction; - -import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial; -import net.fabricmc.fabric.api.renderer.v1.render.RenderContext.QuadTransform; -import net.fabricmc.fabric.api.util.TriState; -import net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoCalculator; -import net.fabricmc.fabric.impl.client.indigo.renderer.helper.ColorHelper; -import net.fabricmc.fabric.impl.client.indigo.renderer.mesh.MutableQuadViewImpl; - -/** - * Base quad-rendering class for fallback and mesh consumers. - * Has most of the actual buffer-time lighting and coloring logic. - */ -public abstract class AbstractQuadRenderer { - protected final Function bufferFunc; - protected final BlockRenderInfo blockInfo; - protected final AoCalculator aoCalc; - protected final QuadTransform transform; - protected final Vector3f normalVec = new Vector3f(); - - protected abstract Matrix4f matrix(); - - protected abstract Matrix3f normalMatrix(); - - protected abstract int overlay(); - - AbstractQuadRenderer(BlockRenderInfo blockInfo, Function bufferFunc, AoCalculator aoCalc, QuadTransform transform) { - this.blockInfo = blockInfo; - this.bufferFunc = bufferFunc; - this.aoCalc = aoCalc; - this.transform = transform; - } - - protected void renderQuad(MutableQuadViewImpl quad, boolean isVanilla) { - if (!transform.transform(quad)) { - return; - } - - if (!blockInfo.shouldDrawFace(quad.cullFace())) { - return; - } - - tessellateQuad(quad, isVanilla); - } - - /** - * Determines color index and render layer, then routes to appropriate - * tessellate routine based on material properties. - */ - private void tessellateQuad(MutableQuadViewImpl quad, boolean isVanilla) { - final RenderMaterial mat = quad.material(); - final int colorIndex = mat.disableColorIndex() ? -1 : quad.colorIndex(); - final RenderLayer renderLayer = blockInfo.effectiveRenderLayer(mat.blendMode()); - final TriState ao = mat.ambientOcclusion(); - - if (blockInfo.useAo && (ao == TriState.TRUE || (ao == TriState.DEFAULT && blockInfo.defaultAo))) { - // needs to happen before offsets are applied - aoCalc.compute(quad, isVanilla); - - if (mat.emissive()) { - tessellateSmoothEmissive(quad, renderLayer, colorIndex); - } else { - tessellateSmooth(quad, renderLayer, colorIndex); - } - } else { - if (mat.emissive()) { - tessellateFlatEmissive(quad, renderLayer, colorIndex); - } else { - tessellateFlat(quad, renderLayer, colorIndex); - } - } - } - - /** handles block color and red-blue swizzle, common to all renders. */ - private void colorizeQuad(MutableQuadViewImpl q, int blockColorIndex) { - if (blockColorIndex == -1) { - for (int i = 0; i < 4; i++) { - q.color(i, ColorHelper.swapRedBlueIfNeeded(q.color(i))); - } - } else { - final int blockColor = blockInfo.blockColor(blockColorIndex); - - for (int i = 0; i < 4; i++) { - q.color(i, ColorHelper.swapRedBlueIfNeeded(ColorHelper.multiplyColor(blockColor, q.color(i)))); - } - } - } - - /** final output step, common to all renders. */ - private void bufferQuad(MutableQuadViewImpl quad, RenderLayer renderLayer) { - bufferQuad(bufferFunc.apply(renderLayer), quad, matrix(), overlay(), normalMatrix(), normalVec); - } - - public static void bufferQuad(VertexConsumer buff, MutableQuadViewImpl quad, Matrix4f matrix, int overlay, Matrix3f normalMatrix, Vector3f normalVec) { - final boolean useNormals = quad.hasVertexNormals(); - - if (useNormals) { - quad.populateMissingNormals(); - } else { - normalVec.set(quad.faceNormal()); - normalVec.mul(normalMatrix); - } - - for (int i = 0; i < 4; i++) { - buff.vertex(matrix, quad.x(i), quad.y(i), quad.z(i)); - final int color = quad.color(i); - buff.color(color & 0xFF, (color >> 8) & 0xFF, (color >> 16) & 0xFF, (color >> 24) & 0xFF); - buff.texture(quad.u(i), quad.v(i)); - buff.overlay(overlay); - buff.light(quad.lightmap(i)); - - if (useNormals) { - quad.copyNormal(i, normalVec); - normalVec.mul(normalMatrix); - } - - buff.normal(normalVec.x(), normalVec.y(), normalVec.z()); - buff.next(); - } - } - - // routines below have a bit of copy-paste code reuse to avoid conditional execution inside a hot loop - - /** for non-emissive mesh quads and all fallback quads with smooth lighting. */ - protected void tessellateSmooth(MutableQuadViewImpl q, RenderLayer renderLayer, int blockColorIndex) { - colorizeQuad(q, blockColorIndex); - - for (int i = 0; i < 4; i++) { - q.color(i, ColorHelper.multiplyRGB(q.color(i), aoCalc.ao[i])); - q.lightmap(i, ColorHelper.maxBrightness(q.lightmap(i), aoCalc.light[i])); - } - - bufferQuad(q, renderLayer); - } - - /** for emissive mesh quads with smooth lighting. */ - protected void tessellateSmoothEmissive(MutableQuadViewImpl q, RenderLayer renderLayer, int blockColorIndex) { - colorizeQuad(q, blockColorIndex); - - for (int i = 0; i < 4; i++) { - q.color(i, ColorHelper.multiplyRGB(q.color(i), aoCalc.ao[i])); - q.lightmap(i, LightmapTextureManager.MAX_LIGHT_COORDINATE); - } - - bufferQuad(q, renderLayer); - } - - /** for non-emissive mesh quads and all fallback quads with flat lighting. */ - protected void tessellateFlat(MutableQuadViewImpl quad, RenderLayer renderLayer, int blockColorIndex) { - colorizeQuad(quad, blockColorIndex); - shadeFlatQuad(quad); - - final int brightness = flatBrightness(quad, blockInfo.blockState, blockInfo.blockPos); - - for (int i = 0; i < 4; i++) { - quad.lightmap(i, ColorHelper.maxBrightness(quad.lightmap(i), brightness)); - } - - bufferQuad(quad, renderLayer); - } - - /** for emissive mesh quads with flat lighting. */ - protected void tessellateFlatEmissive(MutableQuadViewImpl quad, RenderLayer renderLayer, int blockColorIndex) { - colorizeQuad(quad, blockColorIndex); - shadeFlatQuad(quad); - - for (int i = 0; i < 4; i++) { - quad.lightmap(i, LightmapTextureManager.MAX_LIGHT_COORDINATE); - } - - bufferQuad(quad, renderLayer); - } - - private final BlockPos.Mutable mpos = new BlockPos.Mutable(); - - /** - * Handles geometry-based check for using self brightness or neighbor brightness. - * That logic only applies in flat lighting. - */ - int flatBrightness(MutableQuadViewImpl quad, BlockState blockState, BlockPos pos) { - mpos.set(pos); - - // To mirror Vanilla's behavior, if the face has a cull-face, always sample the light value - // offset in that direction. See net.minecraft.client.render.block.BlockModelRenderer.renderQuadsFlat - // for reference. - if (quad.cullFace() != null) { - mpos.move(quad.cullFace()); - } else { - final int flags = quad.geometryFlags(); - - if ((flags & LIGHT_FACE_FLAG) != 0 || ((flags & AXIS_ALIGNED_FLAG) != 0 && blockState.isFullCube(blockInfo.blockView, pos))) { - mpos.move(quad.lightFace()); - } - } - - // Unfortunately cannot use brightness cache here unless we implement one specifically for flat lighting. See #329 - return WorldRenderer.getLightmapCoordinates(blockInfo.blockView, blockState, mpos); - } - - /** - * Starting in 1.16 flat shading uses dimension-specific diffuse factors that can be < 1.0 - * even for un-shaded quads. These are also applied with AO shading but that is done in AO calculator. - */ - private void shadeFlatQuad(MutableQuadViewImpl quad) { - if (quad.hasVertexNormals()) { - // Quads that have vertex normals need to be shaded using interpolation - vanilla can't - // handle them. Generally only applies to modded models. - final float faceShade = blockInfo.blockView.getBrightness(quad.lightFace(), quad.hasShade()); - - for (int i = 0; i < 4; i++) { - quad.color(i, ColorHelper.multiplyRGB(quad.color(i), vertexShade(quad, i, faceShade))); - } - } else { - final float diffuseShade = blockInfo.blockView.getBrightness(quad.lightFace(), quad.hasShade()); - - if (diffuseShade != 1.0f) { - for (int i = 0; i < 4; i++) { - quad.color(i, ColorHelper.multiplyRGB(quad.color(i), diffuseShade)); - } - } - } - } - - private float vertexShade(MutableQuadViewImpl quad, int vertexIndex, float faceShade) { - return quad.hasNormal(vertexIndex) ? normalShade(quad.normalX(vertexIndex), quad.normalY(vertexIndex), quad.normalZ(vertexIndex), quad.hasShade()) : faceShade; - } - - /** - * Finds mean of per-face shading factors weighted by normal components. - * Not how light actually works but the vanilla diffuse shading model is a hack to start with - * and this gives reasonable results for non-cubic surfaces in a vanilla-style renderer. - */ - private float normalShade(float normalX, float normalY, float normalZ, boolean hasShade) { - float sum = 0; - float div = 0; - - if (normalX > 0) { - sum += normalX * blockInfo.blockView.getBrightness(Direction.EAST, hasShade); - div += normalX; - } else if (normalX < 0) { - sum += -normalX * blockInfo.blockView.getBrightness(Direction.WEST, hasShade); - div -= normalX; - } - - if (normalY > 0) { - sum += normalY * blockInfo.blockView.getBrightness(Direction.UP, hasShade); - div += normalY; - } else if (normalY < 0) { - sum += -normalY * blockInfo.blockView.getBrightness(Direction.DOWN, hasShade); - div -= normalY; - } - - if (normalZ > 0) { - sum += normalZ * blockInfo.blockView.getBrightness(Direction.SOUTH, hasShade); - div += normalZ; - } else if (normalZ < 0) { - sum += -normalZ * blockInfo.blockView.getBrightness(Direction.NORTH, hasShade); - div -= normalZ; - } - - return sum / div; - } -} diff --git a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/AbstractRenderContext.java b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/AbstractRenderContext.java index 67e06cd7d7..81969f7a4c 100644 --- a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/AbstractRenderContext.java +++ b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/AbstractRenderContext.java @@ -16,19 +16,27 @@ package net.fabricmc.fabric.impl.client.indigo.renderer.render; +import java.util.function.Consumer; + import it.unimi.dsi.fastutil.objects.ObjectArrayList; import org.joml.Matrix3f; import org.joml.Matrix4f; +import org.joml.Vector3f; +import org.joml.Vector4f; + +import net.minecraft.client.render.VertexConsumer; +import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh; import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView; import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; +import net.fabricmc.fabric.impl.client.indigo.renderer.mesh.MutableQuadViewImpl; abstract class AbstractRenderContext implements RenderContext { - private static final QuadTransform NO_TRANSFORM = (q) -> true; + private static final QuadTransform NO_TRANSFORM = q -> true; private QuadTransform activeTransform = NO_TRANSFORM; private final ObjectArrayList transformStack = new ObjectArrayList<>(); - private final QuadTransform stackTransform = (q) -> { + private final QuadTransform stackTransform = q -> { int i = transformStack.size() - 1; while (i >= 0) { @@ -40,15 +48,21 @@ abstract class AbstractRenderContext implements RenderContext { return true; }; + @Deprecated + private final Consumer meshConsumer = mesh -> mesh.outputTo(getEmitter()); + protected Matrix4f matrix; protected Matrix3f normalMatrix; protected int overlay; + private final Vector4f posVec = new Vector4f(); + private final Vector3f normalVec = new Vector3f(); protected final boolean transform(MutableQuadView q) { return activeTransform.transform(q); } - protected boolean hasTransform() { + @Override + public boolean hasTransform() { return activeTransform != NO_TRANSFORM; } @@ -77,4 +91,45 @@ public void popTransform() { activeTransform = transformStack.get(0); } } + + // Overridden to prevent allocating a lambda every time this method is called. + @Deprecated + @Override + public Consumer meshConsumer() { + return meshConsumer; + } + + /** final output step, common to all renders. */ + protected void bufferQuad(MutableQuadViewImpl quad, VertexConsumer vertexConsumer) { + final Vector4f posVec = this.posVec; + final Vector3f normalVec = this.normalVec; + final boolean useNormals = quad.hasVertexNormals(); + + if (useNormals) { + quad.populateMissingNormals(); + } else { + normalVec.set(quad.faceNormal()); + normalVec.mul(normalMatrix); + } + + for (int i = 0; i < 4; i++) { + posVec.set(quad.x(i), quad.y(i), quad.z(i), 1.0f); + posVec.mul(matrix); + vertexConsumer.vertex(posVec.x(), posVec.y(), posVec.z()); + + final int color = quad.color(i); + vertexConsumer.color((color >>> 16) & 0xFF, (color >>> 8) & 0xFF, color & 0xFF, (color >>> 24) & 0xFF); + vertexConsumer.texture(quad.u(i), quad.v(i)); + vertexConsumer.overlay(overlay); + vertexConsumer.light(quad.lightmap(i)); + + if (useNormals) { + quad.copyNormal(i, normalVec); + normalVec.mul(normalMatrix); + } + + vertexConsumer.normal(normalVec.x(), normalVec.y(), normalVec.z()); + vertexConsumer.next(); + } + } } diff --git a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/BlockRenderContext.java b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/BlockRenderContext.java index aec399d050..88e6dd9f86 100644 --- a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/BlockRenderContext.java +++ b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/BlockRenderContext.java @@ -16,130 +16,62 @@ package net.fabricmc.fabric.impl.client.indigo.renderer.render; -import java.util.function.Consumer; -import java.util.function.Supplier; - -import org.joml.Matrix3f; -import org.joml.Matrix4f; - import net.minecraft.block.BlockState; import net.minecraft.client.render.RenderLayer; import net.minecraft.client.render.VertexConsumer; -import net.minecraft.client.render.WorldRenderer; import net.minecraft.client.render.model.BakedModel; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.random.Random; import net.minecraft.world.BlockRenderView; -import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh; -import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter; -import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel; import net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoCalculator; import net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoLuminanceFix; /** * Context for non-terrain block rendering. */ -public class BlockRenderContext extends AbstractRenderContext { - private final BlockRenderInfo blockInfo = new BlockRenderInfo(); - - private final AoCalculator aoCalc = new AoCalculator(blockInfo) { - @Override - public int light(BlockPos pos, BlockState state) { - return WorldRenderer.getLightmapCoordinates(blockInfo.blockView, state, pos); - } - - @Override - public float ao(BlockPos pos, BlockState state) { - return AoLuminanceFix.INSTANCE.apply(blockInfo.blockView, pos, state); - } - }; - - private VertexConsumer bufferBuilder; - // These are kept as fields to avoid the heap allocation for a supplier. - // BlockModelRenderer allows the caller to supply both the random object and seed. - private Random random; - private long seed; - private final Supplier randomSupplier = () -> { - random.setSeed(seed); - return random; - }; - - private final AbstractMeshConsumer meshConsumer = new AbstractMeshConsumer(blockInfo, this::outputBuffer, aoCalc, this::transform) { - @Override - protected Matrix4f matrix() { - return matrix; - } - - @Override - protected Matrix3f normalMatrix() { - return normalMatrix; - } +public class BlockRenderContext extends AbstractBlockRenderContext { + private VertexConsumer vertexConsumer; - @Override - protected int overlay() { - return overlay; - } - }; - - /** - * Reuse the fallback consumer from the render context used during chunk rebuild to make it properly - * apply the current transforms to vanilla models. - */ - private final TerrainFallbackConsumer fallbackConsumer = new TerrainFallbackConsumer(blockInfo, this::outputBuffer, aoCalc, this::transform) { - @Override - protected Matrix4f matrix() { - return matrix; - } - - @Override - protected Matrix3f normalMatrix() { - return normalMatrix; - } - - @Override - protected int overlay() { - return overlay; - } - }; + @Override + protected AoCalculator createAoCalc(BlockRenderInfo blockInfo) { + return new AoCalculator(blockInfo) { + @Override + public int light(BlockPos pos, BlockState state) { + return AoCalculator.getLightmapCoordinates(blockInfo.blockView, state, pos); + } + + @Override + public float ao(BlockPos pos, BlockState state) { + return AoLuminanceFix.INSTANCE.apply(blockInfo.blockView, pos, state); + } + }; + } - private VertexConsumer outputBuffer(RenderLayer renderLayer) { - return bufferBuilder; + @Override + protected VertexConsumer getVertexConsumer(RenderLayer layer) { + return vertexConsumer; } public void render(BlockRenderView blockView, BakedModel model, BlockState state, BlockPos pos, MatrixStack matrixStack, VertexConsumer buffer, boolean cull, Random random, long seed, int overlay) { - this.bufferBuilder = buffer; + this.vertexConsumer = buffer; this.matrix = matrixStack.peek().getPositionMatrix(); this.normalMatrix = matrixStack.peek().getNormalMatrix(); - this.random = random; - this.seed = seed; - this.overlay = overlay; + + blockInfo.random = random; + blockInfo.seed = seed; + blockInfo.recomputeSeed = false; + aoCalc.clear(); blockInfo.prepareForWorld(blockView, cull); blockInfo.prepareForBlock(state, pos, model.useAmbientOcclusion()); - ((FabricBakedModel) model).emitBlockQuads(blockView, state, pos, randomSupplier, this); + model.emitBlockQuads(blockView, state, pos, blockInfo.randomSupplier, this); blockInfo.release(); - this.bufferBuilder = null; - this.random = null; - this.seed = seed; - } - - @Override - public Consumer meshConsumer() { - return meshConsumer; - } - - @Override - public BakedModelConsumer bakedModelConsumer() { - return fallbackConsumer; - } - - @Override - public QuadEmitter getEmitter() { - return meshConsumer.getEmitter(); + blockInfo.random = null; + this.vertexConsumer = null; } } diff --git a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/BlockRenderInfo.java b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/BlockRenderInfo.java index 899e4e97b7..6bef8506dc 100644 --- a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/BlockRenderInfo.java +++ b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/BlockRenderInfo.java @@ -34,8 +34,8 @@ import net.fabricmc.fabric.api.renderer.v1.material.BlendMode; /** - * Holds, manages and provides access to the block/world related state - * needed by fallback and mesh consumers. + * Holds, manages, and provides access to the block/world related state + * needed to render quads. * *

    Exception: per-block position offsets are tracked in {@link ChunkRenderInfo} * so they can be applied together with chunk offsets. @@ -43,44 +43,47 @@ public class BlockRenderInfo { private final BlockColors blockColorMap = MinecraftClient.getInstance().getBlockColors(); private final BlockPos.Mutable searchPos = new BlockPos.Mutable(); - private final Random random = Random.create(); + public BlockRenderView blockView; public BlockPos blockPos; public BlockState blockState; - public long seed; + boolean useAo; boolean defaultAo; RenderLayer defaultLayer; - private boolean enableCulling; - private int cullCompletionFlags; - private int cullResultFlags; - + Random random; + long seed; + boolean recomputeSeed; public final Supplier randomSupplier = () -> { - final Random result = random; long seed = this.seed; - if (seed == -1L) { + if (recomputeSeed) { seed = blockState.getRenderingSeed(blockPos); this.seed = seed; + recomputeSeed = false; } - result.setSeed(seed); - return result; + final Random random = this.random; + random.setSeed(seed); + return random; }; + private boolean enableCulling; + private int cullCompletionFlags; + private int cullResultFlags; + public void prepareForWorld(BlockRenderView blockView, boolean enableCulling) { this.blockView = blockView; this.enableCulling = enableCulling; } - public void prepareForBlock(BlockState blockState, BlockPos blockPos, boolean modelAO) { + public void prepareForBlock(BlockState blockState, BlockPos blockPos, boolean modelAo) { this.blockPos = blockPos; this.blockState = blockState; - // in the unlikely case seed actually matches this, we'll simply retrieve it more than once - seed = -1L; + useAo = MinecraftClient.isAmbientOcclusionEnabled(); - defaultAo = useAo && modelAO && blockState.getLuminance() == 0; + defaultAo = useAo && modelAo && blockState.getLuminance() == 0; defaultLayer = RenderLayers.getBlockLayer(blockState); @@ -89,6 +92,7 @@ public void prepareForBlock(BlockState blockState, BlockPos blockPos, boolean mo } public void release() { + blockView = null; blockPos = null; blockState = null; } diff --git a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/ChunkRenderInfo.java b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/ChunkRenderInfo.java index b1bab04490..27d275ff4b 100644 --- a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/ChunkRenderInfo.java +++ b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/ChunkRenderInfo.java @@ -32,6 +32,7 @@ import net.minecraft.util.math.BlockPos; import net.minecraft.world.BlockRenderView; +import net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoCalculator; import net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoLuminanceFix; /** @@ -127,7 +128,7 @@ int cachedBrightness(BlockPos pos, BlockState state) { int result = brightnessCache.get(key); if (result == Integer.MAX_VALUE) { - result = WorldRenderer.getLightmapCoordinates(blockView, state, pos); + result = AoCalculator.getLightmapCoordinates(blockView, state, pos); brightnessCache.put(key, result); } diff --git a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/IndigoQuadHandler.java b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/IndigoQuadHandler.java deleted file mode 100644 index 9ecc8e4a78..0000000000 --- a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/IndigoQuadHandler.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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 net.fabricmc.fabric.impl.client.indigo.renderer.render; - -import net.minecraft.client.render.VertexConsumer; -import net.minecraft.client.render.item.ItemRenderer; -import net.minecraft.client.render.model.BakedModel; -import net.minecraft.client.util.math.MatrixStack; -import net.minecraft.item.ItemStack; - -public class IndigoQuadHandler implements ItemRenderContext.VanillaQuadHandler { - private final ItemRenderer itemRenderer; - - public IndigoQuadHandler(ItemRenderer itemRenderer) { - this.itemRenderer = itemRenderer; - } - - @Override - public void accept(BakedModel model, ItemStack stack, int color, int overlay, MatrixStack matrixStack, VertexConsumer buffer) { - itemRenderer.renderBakedItemModel(model, stack, color, overlay, matrixStack, buffer); - } -} diff --git a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/ItemRenderContext.java b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/ItemRenderContext.java index c4a76019e3..6badb4ce3e 100644 --- a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/ItemRenderContext.java +++ b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/ItemRenderContext.java @@ -16,12 +16,9 @@ package net.fabricmc.fabric.impl.client.indigo.renderer.render; -import java.util.List; -import java.util.function.Consumer; import java.util.function.Supplier; import org.jetbrains.annotations.Nullable; -import org.joml.Vector3f; import net.minecraft.block.BlockState; import net.minecraft.client.MinecraftClient; @@ -34,7 +31,6 @@ import net.minecraft.client.render.VertexConsumerProvider; import net.minecraft.client.render.item.ItemRenderer; import net.minecraft.client.render.model.BakedModel; -import net.minecraft.client.render.model.BakedQuad; import net.minecraft.client.render.model.json.ModelTransformationMode; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.item.BlockItem; @@ -45,16 +41,12 @@ import net.fabricmc.fabric.api.renderer.v1.material.BlendMode; import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial; -import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh; import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter; -import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel; -import net.fabricmc.fabric.api.renderer.v1.model.ModelHelper; import net.fabricmc.fabric.api.util.TriState; -import net.fabricmc.fabric.impl.client.indigo.renderer.IndigoRenderer; import net.fabricmc.fabric.impl.client.indigo.renderer.helper.ColorHelper; import net.fabricmc.fabric.impl.client.indigo.renderer.mesh.EncodingFormat; -import net.fabricmc.fabric.impl.client.indigo.renderer.mesh.MeshImpl; import net.fabricmc.fabric.impl.client.indigo.renderer.mesh.MutableQuadViewImpl; +import net.fabricmc.fabric.impl.renderer.VanillaModelEncoder; /** * The render context used for item rendering. @@ -63,31 +55,32 @@ public class ItemRenderContext extends AbstractRenderContext { /** Value vanilla uses for item rendering. The only sensible choice, of course. */ private static final long ITEM_RANDOM_SEED = 42L; - /** used to accept a method reference from the ItemRenderer. */ - @FunctionalInterface - public interface VanillaQuadHandler { - void accept(BakedModel model, ItemStack stack, int color, int overlay, MatrixStack matrixStack, VertexConsumer buffer); - } - private final ItemColors colorMap; private final Random random = Random.create(); - private final Vector3f normalVec = new Vector3f(); - private final Supplier randomSupplier = () -> { random.setSeed(ITEM_RANDOM_SEED); return random; }; - private final Maker editorQuad = new Maker(); - private final MeshConsumer meshConsumer = new MeshConsumer(); - private final FallbackConsumer fallbackConsumer = new FallbackConsumer(); + private final MutableQuadViewImpl editorQuad = new MutableQuadViewImpl() { + { + data = new int[EncodingFormat.TOTAL_STRIDE]; + clear(); + } + + @Override + public void emitDirectly() { + renderQuad(this); + } + }; + + private final BakedModelConsumerImpl vanillaModelConsumer = new BakedModelConsumerImpl(); private ItemStack itemStack; private ModelTransformationMode transformMode; private MatrixStack matrixStack; private VertexConsumerProvider vertexConsumerProvider; private int lightmap; - private VanillaQuadHandler vanillaHandler; private boolean isDefaultTranslucent; private boolean isTranslucentDirect; @@ -97,36 +90,54 @@ public interface VanillaQuadHandler { private VertexConsumer cutoutVertexConsumer; private VertexConsumer translucentGlintVertexConsumer; private VertexConsumer cutoutGlintVertexConsumer; - private VertexConsumer defaultVertexConsumer; public ItemRenderContext(ItemColors colorMap) { this.colorMap = colorMap; } - public void renderModel(ItemStack itemStack, ModelTransformationMode transformMode, boolean invert, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int lightmap, int overlay, BakedModel model, VanillaQuadHandler vanillaHandler) { + @Override + public QuadEmitter getEmitter() { + editorQuad.clear(); + return editorQuad; + } + + @Override + public boolean isFaceCulled(@Nullable Direction face) { + throw new IllegalStateException("isFaceCulled can only be called on a block render context."); + } + + @Override + public ModelTransformationMode itemTransformationMode() { + return transformMode; + } + + @Override + public BakedModelConsumer bakedModelConsumer() { + return vanillaModelConsumer; + } + + public void renderModel(ItemStack itemStack, ModelTransformationMode transformMode, boolean invert, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int lightmap, int overlay, BakedModel model) { this.itemStack = itemStack; this.transformMode = transformMode; this.matrixStack = matrixStack; this.vertexConsumerProvider = vertexConsumerProvider; this.lightmap = lightmap; this.overlay = overlay; - this.vanillaHandler = vanillaHandler; computeOutputInfo(); matrix = matrixStack.peek().getPositionMatrix(); normalMatrix = matrixStack.peek().getNormalMatrix(); - ((FabricBakedModel) model).emitItemQuads(itemStack, randomSupplier, this); + model.emitItemQuads(itemStack, randomSupplier, this); this.itemStack = null; this.matrixStack = null; - this.vanillaHandler = null; + this.vertexConsumerProvider = null; translucentVertexConsumer = null; cutoutVertexConsumer = null; translucentGlintVertexConsumer = null; cutoutGlintVertexConsumer = null; - defaultVertexConsumer = null; } private void computeOutputInfo() { @@ -149,22 +160,45 @@ private void computeOutputInfo() { } isDefaultGlint = itemStack.hasGlint(); + } + + private void renderQuad(MutableQuadViewImpl quad) { + if (!transform(quad)) { + return; + } + + final RenderMaterial mat = quad.material(); + final int colorIndex = mat.disableColorIndex() ? -1 : quad.colorIndex(); + final boolean emissive = mat.emissive(); + final VertexConsumer vertexConsumer = getVertexConsumer(mat.blendMode(), mat.glint()); - defaultVertexConsumer = quadVertexConsumer(BlendMode.DEFAULT, TriState.DEFAULT); + colorizeQuad(quad, colorIndex); + shadeQuad(quad, emissive); + bufferQuad(quad, vertexConsumer); } - private VertexConsumer createTranslucentVertexConsumer(boolean glint) { - if (isTranslucentDirect) { - return ItemRenderer.getDirectItemGlintConsumer(vertexConsumerProvider, TexturedRenderLayers.getEntityTranslucentCull(), true, glint); - } else if (MinecraftClient.isFabulousGraphicsOrBetter()) { - return ItemRenderer.getItemGlintConsumer(vertexConsumerProvider, TexturedRenderLayers.getItemEntityTranslucentCull(), true, glint); - } else { - return ItemRenderer.getItemGlintConsumer(vertexConsumerProvider, TexturedRenderLayers.getEntityTranslucentCull(), true, glint); + private void colorizeQuad(MutableQuadViewImpl quad, int colorIndex) { + if (colorIndex != -1) { + final int itemColor = 0xFF000000 | colorMap.getColor(itemStack, colorIndex); + + for (int i = 0; i < 4; i++) { + quad.color(i, ColorHelper.multiplyColor(itemColor, quad.color(i))); + } } } - private VertexConsumer createCutoutVertexConsumer(boolean glint) { - return ItemRenderer.getDirectItemGlintConsumer(vertexConsumerProvider, TexturedRenderLayers.getEntityCutout(), true, glint); + private void shadeQuad(MutableQuadViewImpl quad, boolean emissive) { + if (emissive) { + for (int i = 0; i < 4; i++) { + quad.lightmap(i, LightmapTextureManager.MAX_LIGHT_COORDINATE); + } + } else { + final int lightmap = this.lightmap; + + for (int i = 0; i < 4; i++) { + quad.lightmap(i, ColorHelper.maxBrightness(quad.lightmap(i), lightmap)); + } + } } /** @@ -172,7 +206,7 @@ private VertexConsumer createCutoutVertexConsumer(boolean glint) { * in {@code RenderLayers.getEntityBlockLayer}. Layers other than * translucent are mapped to cutout. */ - private VertexConsumer quadVertexConsumer(BlendMode blendMode, TriState glintMode) { + private VertexConsumer getVertexConsumer(BlendMode blendMode, TriState glintMode) { boolean translucent; boolean glint; @@ -219,97 +253,21 @@ private VertexConsumer quadVertexConsumer(BlendMode blendMode, TriState glintMod } } - private void bufferQuad(MutableQuadViewImpl quad, BlendMode blendMode, TriState glint) { - AbstractQuadRenderer.bufferQuad(quadVertexConsumer(blendMode, glint), quad, matrix, overlay, normalMatrix, normalVec); - } - - private void colorizeQuad(MutableQuadViewImpl q, int colorIndex) { - if (colorIndex == -1) { - for (int i = 0; i < 4; i++) { - q.color(i, ColorHelper.swapRedBlueIfNeeded(q.color(i))); - } - } else { - final int itemColor = 0xFF000000 | colorMap.getColor(itemStack, colorIndex); - - for (int i = 0; i < 4; i++) { - q.color(i, ColorHelper.swapRedBlueIfNeeded(ColorHelper.multiplyColor(itemColor, q.color(i)))); - } - } - } - - private void renderQuad(MutableQuadViewImpl quad, BlendMode blendMode, TriState glint, int colorIndex) { - colorizeQuad(quad, colorIndex); - - final int lightmap = this.lightmap; - - for (int i = 0; i < 4; i++) { - quad.lightmap(i, ColorHelper.maxBrightness(quad.lightmap(i), lightmap)); - } - - bufferQuad(quad, blendMode, glint); - } - - private void renderQuadEmissive(MutableQuadViewImpl quad, BlendMode blendMode, TriState glint, int colorIndex) { - colorizeQuad(quad, colorIndex); - - for (int i = 0; i < 4; i++) { - quad.lightmap(i, LightmapTextureManager.MAX_LIGHT_COORDINATE); - } - - bufferQuad(quad, blendMode, glint); - } - - private void renderMeshQuad(MutableQuadViewImpl quad) { - if (!transform(quad)) { - return; - } - - final RenderMaterial mat = quad.material(); - - final int colorIndex = mat.disableColorIndex() ? -1 : quad.colorIndex(); - final BlendMode blendMode = mat.blendMode(); - final TriState glint = mat.glint(); - - if (mat.emissive()) { - renderQuadEmissive(quad, blendMode, glint, colorIndex); + private VertexConsumer createTranslucentVertexConsumer(boolean glint) { + if (isTranslucentDirect) { + return ItemRenderer.getDirectItemGlintConsumer(vertexConsumerProvider, TexturedRenderLayers.getEntityTranslucentCull(), true, glint); + } else if (MinecraftClient.isFabulousGraphicsOrBetter()) { + return ItemRenderer.getItemGlintConsumer(vertexConsumerProvider, TexturedRenderLayers.getItemEntityTranslucentCull(), true, glint); } else { - renderQuad(quad, blendMode, glint, colorIndex); - } - } - - private class Maker extends MutableQuadViewImpl implements QuadEmitter { - { - data = new int[EncodingFormat.TOTAL_STRIDE]; - clear(); - } - - @Override - public Maker emit() { - computeGeometry(); - renderMeshQuad(this); - clear(); - return this; + return ItemRenderer.getItemGlintConsumer(vertexConsumerProvider, TexturedRenderLayers.getEntityTranslucentCull(), true, glint); } } - private class MeshConsumer implements Consumer { - @Override - public void accept(Mesh mesh) { - final MeshImpl m = (MeshImpl) mesh; - final int[] data = m.data(); - final int limit = data.length; - int index = 0; - - while (index < limit) { - System.arraycopy(data, index, editorQuad.data(), 0, EncodingFormat.TOTAL_STRIDE); - editorQuad.load(); - index += EncodingFormat.TOTAL_STRIDE; - renderMeshQuad(editorQuad); - } - } + private VertexConsumer createCutoutVertexConsumer(boolean glint) { + return ItemRenderer.getDirectItemGlintConsumer(vertexConsumerProvider, TexturedRenderLayers.getEntityCutout(), true, glint); } - private class FallbackConsumer implements BakedModelConsumer { + private class BakedModelConsumerImpl implements BakedModelConsumer { @Override public void accept(BakedModel model) { accept(model, null); @@ -317,41 +275,7 @@ public void accept(BakedModel model) { @Override public void accept(BakedModel model, @Nullable BlockState state) { - if (hasTransform()) { - // if there's a transform in effect, convert to mesh-based quads so that we can apply it - for (int i = 0; i <= ModelHelper.NULL_FACE_ID; i++) { - final Direction cullFace = ModelHelper.faceFromIndex(i); - random.setSeed(ITEM_RANDOM_SEED); - final List quads = model.getQuads(state, cullFace, random); - final int count = quads.size(); - - if (count != 0) { - for (int j = 0; j < count; j++) { - final BakedQuad q = quads.get(j); - editorQuad.fromVanilla(q, IndigoRenderer.MATERIAL_STANDARD, cullFace); - renderMeshQuad(editorQuad); - } - } - } - } else { - vanillaHandler.accept(model, itemStack, lightmap, overlay, matrixStack, defaultVertexConsumer); - } + VanillaModelEncoder.emitItemQuads(model, state, randomSupplier, ItemRenderContext.this); } } - - @Override - public Consumer meshConsumer() { - return meshConsumer; - } - - @Override - public BakedModelConsumer bakedModelConsumer() { - return fallbackConsumer; - } - - @Override - public QuadEmitter getEmitter() { - editorQuad.clear(); - return editorQuad; - } } diff --git a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/TerrainFallbackConsumer.java b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/TerrainFallbackConsumer.java deleted file mode 100644 index 0924373c97..0000000000 --- a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/TerrainFallbackConsumer.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * 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 net.fabricmc.fabric.impl.client.indigo.renderer.render; - -import java.util.List; -import java.util.function.Function; -import java.util.function.Supplier; - -import org.jetbrains.annotations.Nullable; - -import net.minecraft.block.BlockState; -import net.minecraft.client.render.RenderLayer; -import net.minecraft.client.render.VertexConsumer; -import net.minecraft.client.render.model.BakedModel; -import net.minecraft.client.render.model.BakedQuad; -import net.minecraft.util.math.Direction; -import net.minecraft.util.math.random.Random; - -import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial; -import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter; -import net.fabricmc.fabric.api.renderer.v1.model.ModelHelper; -import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; -import net.fabricmc.fabric.api.renderer.v1.render.RenderContext.QuadTransform; -import net.fabricmc.fabric.api.util.TriState; -import net.fabricmc.fabric.impl.client.indigo.renderer.IndigoRenderer; -import net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoCalculator; -import net.fabricmc.fabric.impl.client.indigo.renderer.mesh.EncodingFormat; -import net.fabricmc.fabric.impl.client.indigo.renderer.mesh.MutableQuadViewImpl; - -/** - * Consumer for vanilla baked models. Generally intended to give visual results matching a vanilla render, - * however there could be subtle (and desirable) lighting variations so is good to be able to render - * everything consistently. - * - *

    Also, the API allows multi-part models that hold multiple vanilla models to render them without - * combining quad lists, but the vanilla logic only handles one model per block. To route all of - * them through vanilla logic would require additional hooks. - * - *

    Works by copying the quad data to an "editor" quad held in the instance, - * where all transformations are applied before buffering. Transformations should be - * the same as they would be in a vanilla render - the editor is serving mainly - * as a way to access vertex data without magical numbers. It also allows a consistent interface - * for downstream tesselation routines. - * - *

    Another difference from vanilla render is that all transformation happens before the - * vertex data is sent to the byte buffer. Generally POJO array access will be faster than - * manipulating the data via NIO. - */ -public abstract class TerrainFallbackConsumer extends AbstractQuadRenderer implements RenderContext.BakedModelConsumer { - private static final RenderMaterial MATERIAL_FLAT = IndigoRenderer.INSTANCE.materialFinder().ambientOcclusion(TriState.FALSE).find(); - private static final RenderMaterial MATERIAL_SHADED = IndigoRenderer.INSTANCE.materialFinder().find(); - - TerrainFallbackConsumer(BlockRenderInfo blockInfo, Function bufferFunc, AoCalculator aoCalc, QuadTransform transform) { - super(blockInfo, bufferFunc, aoCalc, transform); - } - - private final MutableQuadViewImpl editorQuad = new MutableQuadViewImpl() { - { - data = new int[EncodingFormat.TOTAL_STRIDE]; - material(MATERIAL_SHADED); - } - - @Override - public QuadEmitter emit() { - // should not be called - throw new UnsupportedOperationException("Fallback consumer does not support .emit()"); - } - }; - - @Override - public void accept(BakedModel bakedModel) { - accept(bakedModel, blockInfo.blockState); - } - - @Override - public void accept(BakedModel model, @Nullable BlockState blockState) { - final Supplier random = blockInfo.randomSupplier; - final RenderMaterial defaultMaterial = model.useAmbientOcclusion() ? MATERIAL_SHADED : MATERIAL_FLAT; - - for (int i = 0; i <= ModelHelper.NULL_FACE_ID; i++) { - final Direction cullFace = ModelHelper.faceFromIndex(i); - final List quads = model.getQuads(blockState, cullFace, random.get()); - final int count = quads.size(); - - if (count != 0) { - for (int j = 0; j < count; j++) { - final BakedQuad q = quads.get(j); - renderQuad(q, cullFace, defaultMaterial); - } - } - } - } - - private void renderQuad(BakedQuad quad, Direction cullFace, RenderMaterial defaultMaterial) { - final MutableQuadViewImpl editorQuad = this.editorQuad; - editorQuad.fromVanilla(quad, defaultMaterial, cullFace); - - renderQuad(editorQuad, true); - } -} diff --git a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/TerrainRenderContext.java b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/TerrainRenderContext.java index 2137e3fef6..2aa5e8f2cc 100644 --- a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/TerrainRenderContext.java +++ b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/TerrainRenderContext.java @@ -17,13 +17,11 @@ package net.fabricmc.fabric.impl.client.indigo.renderer.render; import java.util.Set; -import java.util.function.Consumer; - -import org.joml.Matrix3f; -import org.joml.Matrix4f; import net.minecraft.block.BlockState; +import net.minecraft.client.render.OverlayTexture; import net.minecraft.client.render.RenderLayer; +import net.minecraft.client.render.VertexConsumer; import net.minecraft.client.render.chunk.BlockBufferBuilderStorage; import net.minecraft.client.render.chunk.ChunkBuilder.BuiltChunk; import net.minecraft.client.render.chunk.ChunkRendererRegion; @@ -33,10 +31,9 @@ import net.minecraft.util.crash.CrashReport; import net.minecraft.util.crash.CrashReportSection; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.util.math.random.Random; -import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh; -import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter; -import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel; import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; import net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoCalculator; @@ -45,56 +42,35 @@ * Dispatches calls from models during chunk rebuild to the appropriate consumer, * and holds/manages all of the state needed by them. */ -public class TerrainRenderContext extends AbstractRenderContext { +public class TerrainRenderContext extends AbstractBlockRenderContext { public static final ThreadLocal POOL = ThreadLocal.withInitial(TerrainRenderContext::new); - private final BlockRenderInfo blockInfo = new BlockRenderInfo(); private final ChunkRenderInfo chunkInfo = new ChunkRenderInfo(); - private final AoCalculator aoCalc = new AoCalculator(blockInfo) { - @Override - public int light(BlockPos pos, BlockState state) { - return chunkInfo.cachedBrightness(pos, state); - } - - @Override - public float ao(BlockPos pos, BlockState state) { - return chunkInfo.cachedAoLevel(pos, state); - } - }; - - private final AbstractMeshConsumer meshConsumer = new AbstractMeshConsumer(blockInfo, chunkInfo::getInitializedBuffer, aoCalc, this::transform) { - @Override - protected int overlay() { - return overlay; - } - - @Override - protected Matrix4f matrix() { - return matrix; - } - @Override - protected Matrix3f normalMatrix() { - return normalMatrix; - } - }; - - private final TerrainFallbackConsumer fallbackConsumer = new TerrainFallbackConsumer(blockInfo, chunkInfo::getInitializedBuffer, aoCalc, this::transform) { - @Override - protected int overlay() { - return overlay; - } + public TerrainRenderContext() { + overlay = OverlayTexture.DEFAULT_UV; + blockInfo.random = Random.create(); + } - @Override - protected Matrix4f matrix() { - return matrix; - } + @Override + protected AoCalculator createAoCalc(BlockRenderInfo blockInfo) { + return new AoCalculator(blockInfo) { + @Override + public int light(BlockPos pos, BlockState state) { + return chunkInfo.cachedBrightness(pos, state); + } + + @Override + public float ao(BlockPos pos, BlockState state) { + return chunkInfo.cachedAoLevel(pos, state); + } + }; + } - @Override - protected Matrix3f normalMatrix() { - return normalMatrix; - } - }; + @Override + protected VertexConsumer getVertexConsumer(RenderLayer layer) { + return chunkInfo.getInitializedBuffer(layer); + } public void prepare(ChunkRendererRegion blockView, BuiltChunk chunkRenderer, BuiltChunk.RebuildTask.RenderData renderData, BlockBufferBuilderStorage builders, Set initializedLayers) { blockInfo.prepareForWorld(blockView, true); @@ -108,13 +84,18 @@ public void release() { /** Called from chunk renderer hook. */ public void tessellateBlock(BlockState blockState, BlockPos blockPos, final BakedModel model, MatrixStack matrixStack) { - this.matrix = matrixStack.peek().getPositionMatrix(); - this.normalMatrix = matrixStack.peek().getNormalMatrix(); - try { + Vec3d vec3d = blockState.getModelOffset(chunkInfo.blockView, blockPos); + matrixStack.translate(vec3d.x, vec3d.y, vec3d.z); + + this.matrix = matrixStack.peek().getPositionMatrix(); + this.normalMatrix = matrixStack.peek().getNormalMatrix(); + + blockInfo.recomputeSeed = true; + aoCalc.clear(); blockInfo.prepareForBlock(blockState, blockPos, model.useAmbientOcclusion()); - ((FabricBakedModel) model).emitBlockQuads(blockInfo.blockView, blockInfo.blockState, blockInfo.blockPos, blockInfo.randomSupplier, this); + model.emitBlockQuads(blockInfo.blockView, blockInfo.blockState, blockInfo.blockPos, blockInfo.randomSupplier, this); } catch (Throwable throwable) { CrashReport crashReport = CrashReport.create(throwable, "Tessellating block in world - Indigo Renderer"); CrashReportSection crashReportSection = crashReport.addElement("Block being tessellated"); @@ -122,19 +103,4 @@ public void tessellateBlock(BlockState blockState, BlockPos blockPos, final Bake throw new CrashException(crashReport); } } - - @Override - public Consumer meshConsumer() { - return meshConsumer; - } - - @Override - public BakedModelConsumer bakedModelConsumer() { - return fallbackConsumer; - } - - @Override - public QuadEmitter getEmitter() { - return meshConsumer.getEmitter(); - } } diff --git a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/mixin/client/indigo/renderer/BakedModelMixin.java b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/mixin/client/indigo/renderer/BakedModelMixin.java new file mode 100644 index 0000000000..10218850f8 --- /dev/null +++ b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/mixin/client/indigo/renderer/BakedModelMixin.java @@ -0,0 +1,43 @@ +/* + * 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 net.fabricmc.fabric.mixin.client.indigo.renderer; + +import java.util.function.Supplier; + +import org.spongepowered.asm.mixin.Mixin; + +import net.minecraft.block.BlockState; +import net.minecraft.client.render.model.BakedModel; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.random.Random; +import net.minecraft.world.BlockRenderView; + +import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel; +import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; +import net.fabricmc.fabric.impl.client.indigo.renderer.render.AbstractBlockRenderContext; +import net.fabricmc.fabric.impl.renderer.VanillaModelEncoder; + +@Mixin(BakedModel.class) +public interface BakedModelMixin extends FabricBakedModel { + /** + * Override the fallback path to shade vanilla quads differently. + */ + @Override + default void emitBlockQuads(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier randomSupplier, RenderContext context) { + VanillaModelEncoder.emitBlockQuads((BakedModel) this, state, randomSupplier, context, ((AbstractBlockRenderContext) context).getVanillaModelEmitter()); + } +} diff --git a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/mixin/client/indigo/renderer/BlockModelRendererMixin.java b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/mixin/client/indigo/renderer/BlockModelRendererMixin.java index f0c88c5981..69f7bb6ebd 100644 --- a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/mixin/client/indigo/renderer/BlockModelRendererMixin.java +++ b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/mixin/client/indigo/renderer/BlockModelRendererMixin.java @@ -31,7 +31,6 @@ import net.minecraft.util.math.random.Random; import net.minecraft.world.BlockRenderView; -import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel; import net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.VanillaAoHelper; import net.fabricmc.fabric.impl.client.indigo.renderer.render.BlockRenderContext; @@ -42,7 +41,7 @@ public abstract class BlockModelRendererMixin { @Inject(at = @At("HEAD"), method = "render(Lnet/minecraft/world/BlockRenderView;Lnet/minecraft/client/render/model/BakedModel;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumer;ZLnet/minecraft/util/math/random/Random;JI)V", cancellable = true) private void hookRender(BlockRenderView blockView, BakedModel model, BlockState state, BlockPos pos, MatrixStack matrix, VertexConsumer buffer, boolean cull, Random rand, long seed, int overlay, CallbackInfo ci) { - if (!((FabricBakedModel) model).isVanillaAdapter()) { + if (!model.isVanillaAdapter()) { BlockRenderContext context = fabric_contexts.get(); context.render(blockView, model, state, pos, matrix, buffer, cull, rand, seed, overlay); ci.cancel(); diff --git a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/mixin/client/indigo/renderer/ChunkBuilderBuiltChunkRebuildTaskMixin.java b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/mixin/client/indigo/renderer/ChunkBuilderBuiltChunkRebuildTaskMixin.java index cb88b25603..2dd82d37b1 100644 --- a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/mixin/client/indigo/renderer/ChunkBuilderBuiltChunkRebuildTaskMixin.java +++ b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/mixin/client/indigo/renderer/ChunkBuilderBuiltChunkRebuildTaskMixin.java @@ -40,11 +40,9 @@ import net.minecraft.client.render.model.BakedModel; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.random.Random; import net.minecraft.world.BlockRenderView; -import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel; import net.fabricmc.fabric.impl.client.indigo.Indigo; import net.fabricmc.fabric.impl.client.indigo.renderer.accessor.AccessChunkRendererRegion; import net.fabricmc.fabric.impl.client.indigo.renderer.render.TerrainRenderContext; @@ -104,13 +102,11 @@ private void hookChunkBuild(float cameraX, float cameraY, float cameraZ, */ @Redirect(method = "render", require = 1, at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/block/BlockRenderManager;renderBlock(Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/world/BlockRenderView;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumer;ZLnet/minecraft/util/math/random/Random;)V")) - private void hookChunkBuildTesselate(BlockRenderManager renderManager, BlockState blockState, BlockPos blockPos, BlockRenderView blockView, MatrixStack matrix, VertexConsumer bufferBuilder, boolean checkSides, Random random) { + private void hookChunkBuildTessellate(BlockRenderManager renderManager, BlockState blockState, BlockPos blockPos, BlockRenderView blockView, MatrixStack matrix, VertexConsumer bufferBuilder, boolean checkSides, Random random) { if (blockState.getRenderType() == BlockRenderType.MODEL) { final BakedModel model = renderManager.getModel(blockState); - if (Indigo.ALWAYS_TESSELATE_INDIGO || !((FabricBakedModel) model).isVanillaAdapter()) { - Vec3d vec3d = blockState.getModelOffset(blockView, blockPos); - matrix.translate(vec3d.x, vec3d.y, vec3d.z); + if (Indigo.ALWAYS_TESSELATE_INDIGO || !model.isVanillaAdapter()) { ((AccessChunkRendererRegion) blockView).fabric_getRenderer().tessellateBlock(blockState, blockPos, model, matrix); return; } diff --git a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/mixin/client/indigo/renderer/ItemRendererMixin.java b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/mixin/client/indigo/renderer/ItemRendererMixin.java index 83aea489eb..40890cc213 100644 --- a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/mixin/client/indigo/renderer/ItemRendererMixin.java +++ b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/mixin/client/indigo/renderer/ItemRendererMixin.java @@ -32,10 +32,7 @@ import net.minecraft.client.util.math.MatrixStack; import net.minecraft.item.ItemStack; -import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel; -import net.fabricmc.fabric.impl.client.indigo.renderer.render.IndigoQuadHandler; import net.fabricmc.fabric.impl.client.indigo.renderer.render.ItemRenderContext; -import net.fabricmc.fabric.impl.client.indigo.renderer.render.ItemRenderContext.VanillaQuadHandler; @Mixin(ItemRenderer.class) public abstract class ItemRendererMixin { @@ -46,13 +43,10 @@ public abstract class ItemRendererMixin { @Unique private final ThreadLocal fabric_contexts = ThreadLocal.withInitial(() -> new ItemRenderContext(colors)); - @Unique - private final VanillaQuadHandler fabric_vanillaHandler = new IndigoQuadHandler((ItemRenderer) (Object) this); - @Inject(at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/model/BakedModel;isBuiltin()Z"), method = "renderItem(Lnet/minecraft/item/ItemStack;Lnet/minecraft/client/render/model/json/ModelTransformationMode;ZLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;IILnet/minecraft/client/render/model/BakedModel;)V", cancellable = true) public void hook_renderItem(ItemStack stack, ModelTransformationMode transformMode, boolean invert, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int light, int overlay, BakedModel model, CallbackInfo ci) { - if (!((FabricBakedModel) model).isVanillaAdapter()) { - fabric_contexts.get().renderModel(stack, transformMode, invert, matrixStack, vertexConsumerProvider, light, overlay, model, fabric_vanillaHandler); + if (!model.isVanillaAdapter()) { + fabric_contexts.get().renderModel(stack, transformMode, invert, matrixStack, vertexConsumerProvider, light, overlay, model); matrixStack.pop(); ci.cancel(); } diff --git a/fabric-renderer-indigo/src/client/resources/fabric-renderer-indigo.mixins.json b/fabric-renderer-indigo/src/client/resources/fabric-renderer-indigo.mixins.json index 929ca21453..f15cbeb367 100644 --- a/fabric-renderer-indigo/src/client/resources/fabric-renderer-indigo.mixins.json +++ b/fabric-renderer-indigo/src/client/resources/fabric-renderer-indigo.mixins.json @@ -6,6 +6,7 @@ "mixins": [ ], "client": [ + "BakedModelMixin", "BlockModelRendererMixin", "ChunkBuilderBuiltChunkRebuildTaskMixin", "ChunkRendererRegionMixin", diff --git a/fabric-rendering-data-attachment-v1/build.gradle b/fabric-rendering-data-attachment-v1/build.gradle deleted file mode 100644 index 329d2499e7..0000000000 --- a/fabric-rendering-data-attachment-v1/build.gradle +++ /dev/null @@ -1,8 +0,0 @@ -archivesBaseName = "fabric-rendering-data-attachment-v1" -version = getSubprojectVersion(project) - -moduleDependencies(project, ['fabric-api-base']) - -loom { - accessWidenerPath = file("src/main/resources/fabric-rendering-data-attachment-v1.accesswidener") -} diff --git a/fabric-rendering-data-attachment-v1/src/main/java/net/fabricmc/fabric/api/rendering/data/v1/RenderAttachedBlockView.java b/fabric-rendering-data-attachment-v1/src/main/java/net/fabricmc/fabric/api/rendering/data/v1/RenderAttachedBlockView.java deleted file mode 100644 index dbfd88d370..0000000000 --- a/fabric-rendering-data-attachment-v1/src/main/java/net/fabricmc/fabric/api/rendering/data/v1/RenderAttachedBlockView.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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 net.fabricmc.fabric.api.rendering.data.v1; - -import org.jetbrains.annotations.Nullable; - -import net.minecraft.block.entity.BlockEntity; -import net.minecraft.util.math.BlockPos; -import net.minecraft.world.BlockRenderView; -import net.minecraft.world.World; - -/** - * {@link BlockRenderView}-extending interface to be used by {@code FabricBakedModel} - * for dynamic model customization. It ensures thread safety and exploits data cached in render - * chunks for performance and data consistency. This interface is guaranteed to be implemented on - * every {@link BlockRenderView} subclass, and as such any {@link BlockRenderView} - * can be safely cast to {@link RenderAttachedBlockView}. - * - *

    There are differences from regular {@link World} access that consumers must understand: - * - *

    BlockEntity implementations that provide data for model customization should implement - * {@link RenderAttachmentBlockEntity} which will be queried on the main thread when a render - * chunk is enqueued for rebuild. The model should retrieve the results by casting the - * {@link BlockRenderView} to this class and then calling {@link #getBlockEntityRenderAttachment(BlockPos)}. - * While {@link #getBlockEntity(net.minecraft.util.math.BlockPos)} is not disabled, it - * is not thread-safe for use on render threads. Models that violate this guidance are - * responsible for any necessary synchronization or collision detection. - * - *

    {@link #getBlockState(net.minecraft.util.math.BlockPos)} and {@link #getFluidState(net.minecraft.util.math.BlockPos)} - * will always reflect the state cached with the render chunk. Block and fluid states - * can thus be different from main-thread world state due to lag between block update - * application from network packets and render chunk rebuilds. Use of {link #getCachedRenderData()} - * will ensure consistency of model state with the rest of the chunk being rendered. - * - *

    Models should avoid using {@link BlockRenderView#getBlockEntity(BlockPos)} - * to ensure thread safety because this view may be accessed outside the main client thread. - * Models that require Block Entity data should implement {@link RenderAttachmentBlockEntity} - * on their block entity class, cast the {@link BlockRenderView} to {@link RenderAttachedBlockView} - * and then use {@link #getBlockEntityRenderAttachment(BlockPos)} to retrieve the data. When called from the - * main thread, that method will simply retrieve the data directly. - */ -public interface RenderAttachedBlockView extends BlockRenderView { - /** - * For models associated with Block Entities that implement {@link RenderAttachmentBlockEntity} - * this will be the most recent value provided by that implementation for the given block position. - * - *

    Null in all other cases, or if the result from the implementation was null. - * - * @param pos Position of the block for the block model. - */ - @Nullable - default Object getBlockEntityRenderAttachment(BlockPos pos) { - BlockEntity be = this.getBlockEntity(pos); - return be == null ? null : ((RenderAttachmentBlockEntity) be).getRenderAttachmentData(); - } -} diff --git a/fabric-rendering-data-attachment-v1/src/main/java/net/fabricmc/fabric/api/rendering/data/v1/RenderAttachmentBlockEntity.java b/fabric-rendering-data-attachment-v1/src/main/java/net/fabricmc/fabric/api/rendering/data/v1/RenderAttachmentBlockEntity.java deleted file mode 100644 index b96c0bfc94..0000000000 --- a/fabric-rendering-data-attachment-v1/src/main/java/net/fabricmc/fabric/api/rendering/data/v1/RenderAttachmentBlockEntity.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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 net.fabricmc.fabric.api.rendering.data.v1; - -import org.jetbrains.annotations.Nullable; - -import net.minecraft.block.entity.BlockEntity; -import net.minecraft.world.BlockRenderView; - -/** - * Interface for {@link BlockEntity}s which provide dynamic model state data. - * - *

    Dynamic model state data is separate from BlockState, and will be - * cached during render chunk building on the main thread (safely) and accessible - * during chunk rendering on non-main threads. - * - *

    To access the dynamic data, cast the {@link BlockRenderView} to {@link RenderAttachedBlockView}, - * and then call {@link #getRenderAttachmentData()} with the correct position. - * - *

    Due to chunk meshing happening on non-main threads, please ensure that all accesses to the passed model data are - * thread-safe. This can be achieved by, for example, passing a pre-generated - * immutable object, or ensuring all gets performed on the passed object are atomic - * and well-checked for unusual states. - */ -@FunctionalInterface -public interface RenderAttachmentBlockEntity { - /** - * @return The model state data provided by this block entity. Can be null. - */ - @Nullable - Object getRenderAttachmentData(); -} diff --git a/fabric-rendering-fluids-v1/build.gradle b/fabric-rendering-fluids-v1/build.gradle index 7cea017230..9ced6ef8b4 100644 --- a/fabric-rendering-fluids-v1/build.gradle +++ b/fabric-rendering-fluids-v1/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-rendering-fluids-v1" version = getSubprojectVersion(project) moduleDependencies(project, ['fabric-api-base']) diff --git a/fabric-rendering-v1/build.gradle b/fabric-rendering-v1/build.gradle index 3bc0f4aea4..61680942a1 100644 --- a/fabric-rendering-v1/build.gradle +++ b/fabric-rendering-v1/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-rendering-v1" version = getSubprojectVersion(project) moduleDependencies(project, ['fabric-api-base']) diff --git a/fabric-rendering-v1/src/client/java/net/fabricmc/fabric/mixin/client/rendering/EntityRenderersMixin.java b/fabric-rendering-v1/src/client/java/net/fabricmc/fabric/mixin/client/rendering/EntityRenderersMixin.java index 0967783f30..8e1a1b454f 100644 --- a/fabric-rendering-v1/src/client/java/net/fabricmc/fabric/mixin/client/rendering/EntityRenderersMixin.java +++ b/fabric-rendering-v1/src/client/java/net/fabricmc/fabric/mixin/client/rendering/EntityRenderersMixin.java @@ -27,10 +27,9 @@ import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import net.minecraft.client.render.entity.EntityRenderer; import net.minecraft.client.render.entity.EntityRendererFactory; import net.minecraft.client.render.entity.EntityRenderers; -import net.minecraft.client.network.AbstractClientPlayerEntity; -import net.minecraft.client.render.entity.EntityRenderer; import net.minecraft.client.render.entity.LivingEntityRenderer; import net.minecraft.entity.EntityType; import net.minecraft.entity.LivingEntity; @@ -69,7 +68,7 @@ private static EntityRenderer createEntityRenderer(EntityRendererFactory e // private static synthetic method_32175(Lcom/google/common/collect/ImmutableMap$Builder;Lnet/minecraft/class_5617$class_5618;Ljava/lang/String;Lnet/minecraft/class_5617;)V @SuppressWarnings({"unchecked", "rawtypes"}) @Redirect(method = "method_32175", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/entity/EntityRendererFactory;create(Lnet/minecraft/client/render/entity/EntityRendererFactory$Context;)Lnet/minecraft/client/render/entity/EntityRenderer;")) - private static EntityRenderer createPlayerEntityRenderer(EntityRendererFactory playerEntityRendererFactory, EntityRendererFactory.Context context, ImmutableMap.Builder builder, EntityRendererFactory.Context context2, String str, EntityRendererFactory playerEntityRendererFactory2) { + private static EntityRenderer createPlayerEntityRenderer(EntityRendererFactory playerEntityRendererFactory, EntityRendererFactory.Context context) { EntityRenderer entityRenderer = playerEntityRendererFactory.create(context); LivingEntityRendererAccessor accessor = (LivingEntityRendererAccessor) entityRenderer; diff --git a/fabric-rendering-v1/src/testmod/resources/fabric.mod.json b/fabric-rendering-v1/src/testmod/resources/fabric.mod.json index d480966568..f51ecb36c6 100644 --- a/fabric-rendering-v1/src/testmod/resources/fabric.mod.json +++ b/fabric-rendering-v1/src/testmod/resources/fabric.mod.json @@ -5,9 +5,6 @@ "version": "1.0.0", "environment": "*", "license": "Apache-2.0", - "depends": { - "fabric-rendering-v1": "*" - }, "entrypoints": { "main": [ "net.fabricmc.fabric.test.rendering.TooltipComponentTestInit" diff --git a/fabric-resource-conditions-api-v1/build.gradle b/fabric-resource-conditions-api-v1/build.gradle index 9975db89a1..6c3e7f4074 100644 --- a/fabric-resource-conditions-api-v1/build.gradle +++ b/fabric-resource-conditions-api-v1/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-resource-conditions-api-v1" version = getSubprojectVersion(project) testDependencies(project, [':fabric-gametest-api-v1']) diff --git a/fabric-resource-conditions-api-v1/src/main/resources/fabric.mod.json b/fabric-resource-conditions-api-v1/src/main/resources/fabric.mod.json index f6ffdfb8e3..17da02e358 100644 --- a/fabric-resource-conditions-api-v1/src/main/resources/fabric.mod.json +++ b/fabric-resource-conditions-api-v1/src/main/resources/fabric.mod.json @@ -23,6 +23,6 @@ "fabric-resource-conditions-api-v1.mixins.json" ], "custom": { - "fabric-api:module-lifecycle": "experimental" + "fabric-api:module-lifecycle": "stable" } } diff --git a/fabric-resource-conditions-api-v1/src/testmod/java/net/fabricmc/fabric/test/resource/conditions/ConditionalResourcesTest.java b/fabric-resource-conditions-api-v1/src/testmod/java/net/fabricmc/fabric/test/resource/conditions/ConditionalResourcesTest.java index d86924d2fc..86a823bdfe 100644 --- a/fabric-resource-conditions-api-v1/src/testmod/java/net/fabricmc/fabric/test/resource/conditions/ConditionalResourcesTest.java +++ b/fabric-resource-conditions-api-v1/src/testmod/java/net/fabricmc/fabric/test/resource/conditions/ConditionalResourcesTest.java @@ -64,7 +64,7 @@ public void conditionalRecipes(TestContext context) { throw new AssertionError("features_enabled recipe should have been loaded."); } - long loadedRecipes = manager.values().stream().filter(r -> r.getId().getNamespace().equals(MOD_ID)).count(); + long loadedRecipes = manager.values().stream().filter(r -> r.id().getNamespace().equals(MOD_ID)).count(); if (loadedRecipes != 5) throw new AssertionError("Unexpected loaded recipe count: " + loadedRecipes); context.complete(); diff --git a/fabric-resource-loader-v0/build.gradle b/fabric-resource-loader-v0/build.gradle index e6c827bfaf..3c7ab2966e 100644 --- a/fabric-resource-loader-v0/build.gradle +++ b/fabric-resource-loader-v0/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-resource-loader-v0" version = getSubprojectVersion(project) loom { diff --git a/fabric-resource-loader-v0/src/client/java/net/fabricmc/fabric/impl/client/resource/loader/FabricWrappedVanillaResourcePack.java b/fabric-resource-loader-v0/src/client/java/net/fabricmc/fabric/impl/client/resource/loader/FabricWrappedVanillaResourcePack.java index a1a3eb3272..8d21cac073 100644 --- a/fabric-resource-loader-v0/src/client/java/net/fabricmc/fabric/impl/client/resource/loader/FabricWrappedVanillaResourcePack.java +++ b/fabric-resource-loader-v0/src/client/java/net/fabricmc/fabric/impl/client/resource/loader/FabricWrappedVanillaResourcePack.java @@ -19,7 +19,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.List; -import java.util.Set; +import java.util.stream.Stream; import org.jetbrains.annotations.Nullable; @@ -27,8 +27,6 @@ import net.minecraft.resource.InputSupplier; import net.minecraft.resource.ResourceType; import net.minecraft.resource.metadata.ResourceMetadataReader; -import net.minecraft.util.Identifier; -import net.minecraft.util.PathUtil; import net.fabricmc.fabric.api.resource.ModResourcePack; import net.fabricmc.fabric.impl.resource.loader.GroupResourcePack; @@ -36,48 +34,21 @@ /** * Represents a vanilla built-in resource pack with support for modded content. * - *

    Vanilla resources are provided as usual through the original resource pack, + *

    Vanilla resources are provided as usual through the original resource pack (if not overridden), * all other resources will be searched for in the provided modded resource packs.

    */ public class FabricWrappedVanillaResourcePack extends GroupResourcePack { private final AbstractFileResourcePack originalResourcePack; public FabricWrappedVanillaResourcePack(AbstractFileResourcePack originalResourcePack, List modResourcePacks) { - super(ResourceType.CLIENT_RESOURCES, modResourcePacks); + // Mod resource packs have higher priority, add them last (so vanilla assets can be overridden) + super(ResourceType.CLIENT_RESOURCES, Stream.concat(Stream.of(originalResourcePack), modResourcePacks.stream()).toList()); this.originalResourcePack = originalResourcePack; } @Override public InputSupplier openRoot(String... pathSegments) { - PathUtil.validatePath(pathSegments); - - return this.originalResourcePack.openRoot(String.join("/", pathSegments)); - } - - @Override - public InputSupplier open(ResourceType type, Identifier id) { - InputSupplier originalPackData = this.originalResourcePack.open(type, id); - - if (originalPackData != null) { - return originalPackData; - } - - return super.open(type, id); - } - - @Override - public void findResources(ResourceType type, String namespace, String prefix, ResultConsumer consumer) { - super.findResources(type, namespace, prefix, consumer); - this.originalResourcePack.findResources(type, namespace, prefix, consumer); - } - - @Override - public Set getNamespaces(ResourceType type) { - Set namespaces = this.originalResourcePack.getNamespaces(type); - - namespaces.addAll(super.getNamespaces(type)); - - return namespaces; + return this.originalResourcePack.openRoot(pathSegments); } @Override @@ -89,10 +60,4 @@ public Set getNamespaces(ResourceType type) { public String getName() { return this.originalResourcePack.getName(); } - - @Override - public void close() { - this.originalResourcePack.close(); - super.close(); - } } diff --git a/fabric-resource-loader-v0/src/client/java/net/fabricmc/fabric/mixin/resource/loader/client/DefaultClientResourcePackProviderMixin.java b/fabric-resource-loader-v0/src/client/java/net/fabricmc/fabric/mixin/resource/loader/client/DefaultClientResourcePackProviderMixin.java index fdc649f1e7..b3d1015e9a 100644 --- a/fabric-resource-loader-v0/src/client/java/net/fabricmc/fabric/mixin/resource/loader/client/DefaultClientResourcePackProviderMixin.java +++ b/fabric-resource-loader-v0/src/client/java/net/fabricmc/fabric/mixin/resource/loader/client/DefaultClientResourcePackProviderMixin.java @@ -25,6 +25,7 @@ import net.minecraft.client.resource.DefaultClientResourcePackProvider; import net.minecraft.resource.AbstractFileResourcePack; +import net.minecraft.resource.ResourcePack; import net.minecraft.resource.ResourcePackProfile; import net.minecraft.resource.ResourcePackSource; import net.minecraft.resource.ResourceType; @@ -51,7 +52,18 @@ public class DefaultClientResourcePackProviderMixin { ) private ResourcePackProfile.PackFactory onCreateVanillaBuiltinResourcePack(String name, Text displayName, boolean alwaysEnabled, ResourcePackProfile.PackFactory packFactory, ResourceType type, ResourcePackProfile.InsertionPosition position, ResourcePackSource source) { - return factory -> new FabricWrappedVanillaResourcePack((AbstractFileResourcePack) packFactory.open(name), getModResourcePacks(name)); + return new ResourcePackProfile.PackFactory() { + @Override + public ResourcePack open(String name) { + return new FabricWrappedVanillaResourcePack((AbstractFileResourcePack) packFactory.open(name), getModResourcePacks(name)); + } + + @Override + public ResourcePack openWithOverlays(String string, ResourcePackProfile.Metadata metadata) { + // VanillaResourcePackProvider does not handle overlays + return open(name); + } + }; } /** diff --git a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/GroupResourcePack.java b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/GroupResourcePack.java index 5fb752a997..f4c8981afd 100644 --- a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/GroupResourcePack.java +++ b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/GroupResourcePack.java @@ -33,17 +33,15 @@ import net.minecraft.resource.metadata.ResourceMetadata; import net.minecraft.util.Identifier; -import net.fabricmc.fabric.api.resource.ModResourcePack; - /** * Represents a group resource pack, holds multiple resource packs as one. */ public abstract class GroupResourcePack implements ResourcePack { protected final ResourceType type; - protected final List packs; - protected final Map> namespacedPacks = new Object2ObjectOpenHashMap<>(); + protected final List packs; + protected final Map> namespacedPacks = new Object2ObjectOpenHashMap<>(); - public GroupResourcePack(ResourceType type, List packs) { + public GroupResourcePack(ResourceType type, List packs) { this.type = type; this.packs = packs; this.packs.forEach(pack -> pack.getNamespaces(this.type) @@ -53,9 +51,10 @@ public GroupResourcePack(ResourceType type, List packs) { @Override public InputSupplier open(ResourceType type, Identifier id) { - List packs = this.namespacedPacks.get(id.getNamespace()); + List packs = this.namespacedPacks.get(id.getNamespace()); if (packs != null) { + // Last to first, since higher priority packs are at the end for (int i = packs.size() - 1; i >= 0; i--) { ResourcePack pack = packs.get(i); InputSupplier supplier = pack.open(type, id); @@ -71,15 +70,14 @@ public InputSupplier open(ResourceType type, Identifier id) { @Override public void findResources(ResourceType type, String namespace, String prefix, ResultConsumer consumer) { - List packs = this.namespacedPacks.get(namespace); + List packs = this.namespacedPacks.get(namespace); if (packs == null) { return; } - for (int i = packs.size() - 1; i >= 0; i--) { - ResourcePack pack = packs.get(i); - + // First to last, since later calls override previously returned data + for (ResourcePack pack : packs) { pack.findResources(type, namespace, prefix, consumer); } } @@ -90,7 +88,7 @@ public Set getNamespaces(ResourceType type) { } public void appendResources(ResourceType type, Identifier id, List resources) { - List packs = this.namespacedPacks.get(id.getNamespace()); + List packs = this.namespacedPacks.get(id.getNamespace()); if (packs == null) { return; @@ -98,7 +96,9 @@ public void appendResources(ResourceType type, Identifier id, List res Identifier metadataId = NamespaceResourceManager.getMetadataPath(id); - for (ModResourcePack pack : packs) { + // Last to first, since higher priority packs are at the end + for (int i = packs.size() - 1; i >= 0; i--) { + ResourcePack pack = packs.get(i); InputSupplier supplier = pack.open(type, id); if (supplier != null) { diff --git a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ModResourcePackCreator.java b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ModResourcePackCreator.java index d1380ed455..484c2deab0 100644 --- a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ModResourcePackCreator.java +++ b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ModResourcePackCreator.java @@ -20,6 +20,8 @@ import java.util.List; import java.util.function.Consumer; +import net.minecraft.resource.OverlayResourcePack; +import net.minecraft.resource.ResourcePack; import net.minecraft.resource.ResourcePackProfile; import net.minecraft.resource.ResourcePackProvider; import net.minecraft.resource.ResourcePackSource; @@ -79,9 +81,33 @@ public void register(Consumer consumer) { // Mod resource packs must always be enabled to avoid issues, and they are inserted // on top to ensure that they are applied after vanilla built-in resource packs. MutableText title = Text.translatable("pack.name.fabricMods"); - ResourcePackProfile resourcePackProfile = ResourcePackProfile.create("fabric", title, - true, factory -> new FabricModResourcePack(this.type, packs), type, ResourcePackProfile.InsertionPosition.TOP, - RESOURCE_PACK_SOURCE); + ResourcePackProfile resourcePackProfile = ResourcePackProfile.create("fabric", title, true, new ResourcePackProfile.PackFactory() { + @Override + public ResourcePack open(String name) { + return new FabricModResourcePack(type, packs); + } + + @Override + public ResourcePack openWithOverlays(String name, ResourcePackProfile.Metadata metadata) { + final ResourcePack basePack = open(name); + final List overlays = metadata.overlays(); + + if (overlays.isEmpty()) { + return basePack; + } + + final List overlayedPacks = new ArrayList<>(overlays.size()); + + for (String overlay : overlays) { + List innerPacks = new ArrayList<>(); + ModResourcePackUtil.appendModResourcePacks(innerPacks, type, overlay); + + overlayedPacks.add(new FabricModResourcePack(type, innerPacks)); + } + + return new OverlayResourcePack(basePack, overlayedPacks); + } + }, type, ResourcePackProfile.InsertionPosition.TOP, RESOURCE_PACK_SOURCE); if (resourcePackProfile != null) { consumer.accept(resourcePackProfile); diff --git a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ResourceManagerHelperImpl.java b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ResourceManagerHelperImpl.java index f9e65a6662..e7b25f74a2 100644 --- a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ResourceManagerHelperImpl.java +++ b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ResourceManagerHelperImpl.java @@ -32,6 +32,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import net.minecraft.resource.ResourcePack; import net.minecraft.resource.ResourcePackProfile; import net.minecraft.resource.ResourceReloader; import net.minecraft.resource.ResourceType; @@ -111,15 +112,18 @@ public static void registerBuiltinResourcePacks(ResourceType resourceType, Consu // Add the built-in pack only if namespaces for the specified resource type are present. if (!pack.getNamespaces(resourceType).isEmpty()) { // Make the resource pack profile for built-in pack, should never be always enabled. - ResourcePackProfile profile = ResourcePackProfile.create( - entry.getRight().getName(), - entry.getLeft(), - pack.getActivationType() == ResourcePackActivationType.ALWAYS_ENABLED, - ignored -> entry.getRight(), - resourceType, - ResourcePackProfile.InsertionPosition.TOP, - new BuiltinModResourcePackSource(pack.getFabricModMetadata().getName()) - ); + ResourcePackProfile profile = ResourcePackProfile.create(entry.getRight().getName(), entry.getLeft(), pack.getActivationType() == ResourcePackActivationType.ALWAYS_ENABLED, new ResourcePackProfile.PackFactory() { + @Override + public ResourcePack open(String name) { + return entry.getRight(); + } + + @Override + public ResourcePack openWithOverlays(String string, ResourcePackProfile.Metadata metadata) { + // Don't support overlays in builtin res packs. + return entry.getRight(); + } + }, resourceType, ResourcePackProfile.InsertionPosition.TOP, new BuiltinModResourcePackSource(pack.getFabricModMetadata().getName())); consumer.accept(profile); } } diff --git a/fabric-resource-loader-v0/src/main/resources/assets/fabric-resource-loader-v0/lang/es_ar.json b/fabric-resource-loader-v0/src/main/resources/assets/fabric-resource-loader-v0/lang/es_ar.json new file mode 100644 index 0000000000..a774e7c7a5 --- /dev/null +++ b/fabric-resource-loader-v0/src/main/resources/assets/fabric-resource-loader-v0/lang/es_ar.json @@ -0,0 +1,6 @@ +{ + "pack.source.fabricmod": "Mod de Fabric", + "pack.source.builtinMod": "Integrado: %s", + "pack.name.fabricMod": "Mod de Fabric \"%s\"", + "pack.name.fabricMods": "Mods de Fabric" +} diff --git a/fabric-resource-loader-v0/src/main/resources/assets/fabric-resource-loader-v0/lang/es_cl.json b/fabric-resource-loader-v0/src/main/resources/assets/fabric-resource-loader-v0/lang/es_cl.json new file mode 100644 index 0000000000..a774e7c7a5 --- /dev/null +++ b/fabric-resource-loader-v0/src/main/resources/assets/fabric-resource-loader-v0/lang/es_cl.json @@ -0,0 +1,6 @@ +{ + "pack.source.fabricmod": "Mod de Fabric", + "pack.source.builtinMod": "Integrado: %s", + "pack.name.fabricMod": "Mod de Fabric \"%s\"", + "pack.name.fabricMods": "Mods de Fabric" +} diff --git a/fabric-resource-loader-v0/src/main/resources/assets/fabric-resource-loader-v0/lang/es_ec.json b/fabric-resource-loader-v0/src/main/resources/assets/fabric-resource-loader-v0/lang/es_ec.json new file mode 100644 index 0000000000..a774e7c7a5 --- /dev/null +++ b/fabric-resource-loader-v0/src/main/resources/assets/fabric-resource-loader-v0/lang/es_ec.json @@ -0,0 +1,6 @@ +{ + "pack.source.fabricmod": "Mod de Fabric", + "pack.source.builtinMod": "Integrado: %s", + "pack.name.fabricMod": "Mod de Fabric \"%s\"", + "pack.name.fabricMods": "Mods de Fabric" +} diff --git a/fabric-resource-loader-v0/src/main/resources/assets/fabric-resource-loader-v0/lang/es_es.json b/fabric-resource-loader-v0/src/main/resources/assets/fabric-resource-loader-v0/lang/es_es.json index 1c1591b8f3..a774e7c7a5 100644 --- a/fabric-resource-loader-v0/src/main/resources/assets/fabric-resource-loader-v0/lang/es_es.json +++ b/fabric-resource-loader-v0/src/main/resources/assets/fabric-resource-loader-v0/lang/es_es.json @@ -1,4 +1,6 @@ { "pack.source.fabricmod": "Mod de Fabric", - "pack.source.builtinMod": "integrado en: %s" + "pack.source.builtinMod": "Integrado: %s", + "pack.name.fabricMod": "Mod de Fabric \"%s\"", + "pack.name.fabricMods": "Mods de Fabric" } diff --git a/fabric-resource-loader-v0/src/main/resources/assets/fabric-resource-loader-v0/lang/es_mx.json b/fabric-resource-loader-v0/src/main/resources/assets/fabric-resource-loader-v0/lang/es_mx.json new file mode 100644 index 0000000000..a774e7c7a5 --- /dev/null +++ b/fabric-resource-loader-v0/src/main/resources/assets/fabric-resource-loader-v0/lang/es_mx.json @@ -0,0 +1,6 @@ +{ + "pack.source.fabricmod": "Mod de Fabric", + "pack.source.builtinMod": "Integrado: %s", + "pack.name.fabricMod": "Mod de Fabric \"%s\"", + "pack.name.fabricMods": "Mods de Fabric" +} diff --git a/fabric-resource-loader-v0/src/main/resources/assets/fabric-resource-loader-v0/lang/es_uy.json b/fabric-resource-loader-v0/src/main/resources/assets/fabric-resource-loader-v0/lang/es_uy.json new file mode 100644 index 0000000000..a774e7c7a5 --- /dev/null +++ b/fabric-resource-loader-v0/src/main/resources/assets/fabric-resource-loader-v0/lang/es_uy.json @@ -0,0 +1,6 @@ +{ + "pack.source.fabricmod": "Mod de Fabric", + "pack.source.builtinMod": "Integrado: %s", + "pack.name.fabricMod": "Mod de Fabric \"%s\"", + "pack.name.fabricMods": "Mods de Fabric" +} diff --git a/fabric-resource-loader-v0/src/main/resources/assets/fabric-resource-loader-v0/lang/es_ve.json b/fabric-resource-loader-v0/src/main/resources/assets/fabric-resource-loader-v0/lang/es_ve.json new file mode 100644 index 0000000000..a774e7c7a5 --- /dev/null +++ b/fabric-resource-loader-v0/src/main/resources/assets/fabric-resource-loader-v0/lang/es_ve.json @@ -0,0 +1,6 @@ +{ + "pack.source.fabricmod": "Mod de Fabric", + "pack.source.builtinMod": "Integrado: %s", + "pack.name.fabricMod": "Mod de Fabric \"%s\"", + "pack.name.fabricMods": "Mods de Fabric" +} diff --git a/fabric-resource-loader-v0/src/main/resources/assets/fabric-resource-loader-v0/lang/fi_fi.json b/fabric-resource-loader-v0/src/main/resources/assets/fabric-resource-loader-v0/lang/fi_fi.json index 1f99227e72..cfd720e815 100644 --- a/fabric-resource-loader-v0/src/main/resources/assets/fabric-resource-loader-v0/lang/fi_fi.json +++ b/fabric-resource-loader-v0/src/main/resources/assets/fabric-resource-loader-v0/lang/fi_fi.json @@ -1,4 +1,7 @@ { + "pack.description.modResources": "Modien resurssit.", "pack.source.fabricmod": "Fabric-modi", - "pack.source.builtinMod": "sisäänrakennettu: %s" + "pack.source.builtinMod": "sisäänrakennettu: %s", + "pack.name.fabricMod": "Fabric-modi \"%s\"", + "pack.name.fabricMods": "Fabric-modit" } diff --git a/fabric-resource-loader-v0/src/main/resources/assets/fabric-resource-loader-v0/lang/uk_ua.json b/fabric-resource-loader-v0/src/main/resources/assets/fabric-resource-loader-v0/lang/uk_ua.json new file mode 100644 index 0000000000..d77a63cedf --- /dev/null +++ b/fabric-resource-loader-v0/src/main/resources/assets/fabric-resource-loader-v0/lang/uk_ua.json @@ -0,0 +1,7 @@ +{ + "pack.description.modResources": "Ресурси модів.", + "pack.source.fabricmod": "Мод Fabric", + "pack.source.builtinMod": "вбудований: %s", + "pack.name.fabricMod": "Мод Fabric \"%s\"", + "pack.name.fabricMods": "Моди Fabric" +} diff --git a/fabric-resource-loader-v0/src/main/resources/assets/fabric-resource-loader-v0/lang/vi_vn.json b/fabric-resource-loader-v0/src/main/resources/assets/fabric-resource-loader-v0/lang/vi_vn.json new file mode 100644 index 0000000000..b631f2aa6a --- /dev/null +++ b/fabric-resource-loader-v0/src/main/resources/assets/fabric-resource-loader-v0/lang/vi_vn.json @@ -0,0 +1,7 @@ +{ + "pack.description.modResources": "Tài nguyên mod.", + "pack.source.fabricmod": "Mod Fabric", + "pack.source.builtinMod": "tích hợp: %s", + "pack.name.fabricMod": "Mod Fabric \"%s\"", + "pack.name.fabricMods": "Các Mod Fabric" +} diff --git a/fabric-resource-loader-v0/src/testmod/resources/assets/minecraft/textures/block/diamond_block.png b/fabric-resource-loader-v0/src/testmod/resources/assets/minecraft/textures/block/diamond_block.png new file mode 100644 index 0000000000..654fd938e9 Binary files /dev/null and b/fabric-resource-loader-v0/src/testmod/resources/assets/minecraft/textures/block/diamond_block.png differ diff --git a/fabric-resource-loader-v0/src/testmod/resources/high_contrast/assets/minecraft/textures/block/diamond_block.png b/fabric-resource-loader-v0/src/testmod/resources/high_contrast/assets/minecraft/textures/block/diamond_block.png new file mode 100644 index 0000000000..720760044f Binary files /dev/null and b/fabric-resource-loader-v0/src/testmod/resources/high_contrast/assets/minecraft/textures/block/diamond_block.png differ diff --git a/fabric-resource-loader-v0/src/testmod/resources/programmer_art/assets/minecraft/textures/block/diamond_block.png b/fabric-resource-loader-v0/src/testmod/resources/programmer_art/assets/minecraft/textures/block/diamond_block.png new file mode 100644 index 0000000000..fef4498f10 Binary files /dev/null and b/fabric-resource-loader-v0/src/testmod/resources/programmer_art/assets/minecraft/textures/block/diamond_block.png differ diff --git a/fabric-screen-api-v1/build.gradle b/fabric-screen-api-v1/build.gradle index b1e70a3f58..9ced6ef8b4 100644 --- a/fabric-screen-api-v1/build.gradle +++ b/fabric-screen-api-v1/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-screen-api-v1" version = getSubprojectVersion(project) moduleDependencies(project, ['fabric-api-base']) diff --git a/fabric-screen-api-v1/src/client/java/net/fabricmc/fabric/mixin/screen/GameRendererMixin.java b/fabric-screen-api-v1/src/client/java/net/fabricmc/fabric/mixin/screen/GameRendererMixin.java index 3fb3f8fb71..e791e906b3 100644 --- a/fabric-screen-api-v1/src/client/java/net/fabricmc/fabric/mixin/screen/GameRendererMixin.java +++ b/fabric-screen-api-v1/src/client/java/net/fabricmc/fabric/mixin/screen/GameRendererMixin.java @@ -42,8 +42,8 @@ abstract class GameRendererMixin { @Unique private Screen renderingScreen; - @Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;renderWithTooltip(Lnet/minecraft/client/gui/DrawContext;IIF)V"), locals = LocalCapture.CAPTURE_FAILEXCEPTION) - private void onBeforeRenderScreen(float tickDelta, long startTime, boolean tick, CallbackInfo ci, int mouseX, int mouseY, MatrixStack matrixStack, DrawContext drawContext) { + @Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;renderWithTooltip(Lnet/minecraft/client/gui/DrawContext;IIF)V"), locals = LocalCapture.CAPTURE_FAILHARD) + private void onBeforeRenderScreen(float tickDelta, long startTime, boolean tick, CallbackInfo ci, boolean b1, int mouseX, int mouseY, MatrixStack matrixStack, DrawContext drawContext) { // Store the screen in a variable in case someone tries to change the screen during this before render event. // If someone changes the screen, the after render event will likely have class cast exceptions or an NPE. this.renderingScreen = this.client.currentScreen; @@ -51,8 +51,8 @@ private void onBeforeRenderScreen(float tickDelta, long startTime, boolean tick, } // This injection should end up in the try block so exceptions are caught - @Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;renderWithTooltip(Lnet/minecraft/client/gui/DrawContext;IIF)V", shift = At.Shift.AFTER), locals = LocalCapture.CAPTURE_FAILEXCEPTION) - private void onAfterRenderScreen(float tickDelta, long startTime, boolean tick, CallbackInfo ci, int mouseX, int mouseY, MatrixStack matrixStack, DrawContext drawContext) { + @Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;renderWithTooltip(Lnet/minecraft/client/gui/DrawContext;IIF)V", shift = At.Shift.AFTER), locals = LocalCapture.CAPTURE_FAILHARD) + private void onAfterRenderScreen(float tickDelta, long startTime, boolean tick, CallbackInfo ci, boolean b1, int mouseX, int mouseY, MatrixStack matrixStack, DrawContext drawContext) { ScreenEvents.afterRender(this.renderingScreen).invoker().afterRender(this.renderingScreen, drawContext, mouseX, mouseY, tickDelta); // Finally set the currently rendering screen to null this.renderingScreen = null; diff --git a/fabric-screen-api-v1/src/client/java/net/fabricmc/fabric/mixin/screen/MouseMixin.java b/fabric-screen-api-v1/src/client/java/net/fabricmc/fabric/mixin/screen/MouseMixin.java index 79017698fa..086e438118 100644 --- a/fabric-screen-api-v1/src/client/java/net/fabricmc/fabric/mixin/screen/MouseMixin.java +++ b/fabric-screen-api-v1/src/client/java/net/fabricmc/fabric/mixin/screen/MouseMixin.java @@ -38,8 +38,6 @@ abstract class MouseMixin { private MinecraftClient client; @Unique private Screen currentScreen; - @Unique - private Double horizontalScrollAmount; // private synthetic method_1611([ZDDI)V @Inject(method = "method_1611([ZLnet/minecraft/client/gui/screen/Screen;DDI)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;mouseClicked(DDI)Z"), cancellable = true) @@ -116,8 +114,8 @@ private static void afterMouseReleasedEvent(boolean[] resultHack, Screen screen, thisRef.currentScreen = null; } - @Inject(method = "onMouseScroll", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;mouseScrolled(DDD)Z"), locals = LocalCapture.CAPTURE_FAILEXCEPTION, cancellable = true) - private void beforeMouseScrollEvent(long window, double horizontal, double vertical, CallbackInfo ci, double verticalAmount, double mouseX, double mouseY) { + @Inject(method = "onMouseScroll", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;mouseScrolled(DDDD)Z"), locals = LocalCapture.CAPTURE_FAILHARD, cancellable = true) + private void beforeMouseScrollEvent(long window, double horizontal, double vertical, CallbackInfo ci, boolean sensitivity, double discreteScroll, double horizontalAmount, double verticalAmount, double mouseX, double mouseY) { // Store the screen in a variable in case someone tries to change the screen during this before event. // If someone changes the screen, the after event will likely have class cast exceptions or throw a NPE. this.currentScreen = this.client.currentScreen; @@ -126,27 +124,22 @@ private void beforeMouseScrollEvent(long window, double horizontal, double verti return; } - // Apply same calculations to horizontal scroll as vertical scroll amount has - this.horizontalScrollAmount = this.client.options.getDiscreteMouseScroll().getValue() ? Math.signum(horizontal) : horizontal * this.client.options.getMouseWheelSensitivity().getValue(); - - if (!ScreenMouseEvents.allowMouseScroll(this.currentScreen).invoker().allowMouseScroll(this.currentScreen, mouseX, mouseY, this.horizontalScrollAmount, verticalAmount)) { + if (!ScreenMouseEvents.allowMouseScroll(this.currentScreen).invoker().allowMouseScroll(this.currentScreen, mouseX, mouseY, horizontalAmount, verticalAmount)) { this.currentScreen = null; - this.horizontalScrollAmount = null; ci.cancel(); return; } - ScreenMouseEvents.beforeMouseScroll(this.currentScreen).invoker().beforeMouseScroll(this.currentScreen, mouseX, mouseY, this.horizontalScrollAmount, verticalAmount); + ScreenMouseEvents.beforeMouseScroll(this.currentScreen).invoker().beforeMouseScroll(this.currentScreen, mouseX, mouseY, horizontalAmount, verticalAmount); } - @Inject(method = "onMouseScroll", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;mouseScrolled(DDD)Z", shift = At.Shift.AFTER), locals = LocalCapture.CAPTURE_FAILEXCEPTION) - private void afterMouseScrollEvent(long window, double horizontal, double vertical, CallbackInfo ci, double verticalAmount, double mouseX, double mouseY) { + @Inject(method = "onMouseScroll", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;mouseScrolled(DDDD)Z", shift = At.Shift.AFTER), locals = LocalCapture.CAPTURE_FAILHARD) + private void afterMouseScrollEvent(long window, double horizontal, double vertical, CallbackInfo ci, boolean sensitivity, double discreteScroll, double horizontalAmount, double verticalAmount, double mouseX, double mouseY) { if (this.currentScreen == null) { return; } - ScreenMouseEvents.afterMouseScroll(this.currentScreen).invoker().afterMouseScroll(this.currentScreen, mouseX, mouseY, this.horizontalScrollAmount, verticalAmount); + ScreenMouseEvents.afterMouseScroll(this.currentScreen).invoker().afterMouseScroll(this.currentScreen, mouseX, mouseY, horizontalAmount, verticalAmount); this.currentScreen = null; - this.horizontalScrollAmount = null; } } diff --git a/fabric-screen-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/screen/ScreenTests.java b/fabric-screen-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/screen/ScreenTests.java index f3a867a156..ea8f19c04a 100644 --- a/fabric-screen-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/screen/ScreenTests.java +++ b/fabric-screen-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/screen/ScreenTests.java @@ -33,7 +33,7 @@ import net.fabricmc.fabric.api.client.screen.v1.Screens; public final class ScreenTests implements ClientModInitializer { - public static final Identifier GUI_ICONS_TEXTURE = new Identifier("textures/gui/icons.png"); + public static final Identifier ARMOR_FULL_TEXTURE = new Identifier("hud/armor_full"); private static final Logger LOGGER = LoggerFactory.getLogger("FabricScreenApiTests"); @Override @@ -76,7 +76,7 @@ private void afterInitScreen(MinecraftClient client, Screen screen, int windowWi // Register render event to draw an icon on the screen ScreenEvents.afterRender(screen).register((_screen, drawContext, mouseX, mouseY, tickDelta) -> { // Render an armor icon to test - drawContext.drawTexture(ScreenTests.GUI_ICONS_TEXTURE, (screen.width / 2) - 124, (screen.height / 4) + 96, 20, 20, 34, 9, 9, 9, 256, 256); + drawContext.drawGuiTexture(ScreenTests.ARMOR_FULL_TEXTURE, (screen.width / 2) - 124, (screen.height / 4) + 96, 20, 20); }); ScreenKeyboardEvents.allowKeyPress(screen).register((_screen, key, scancode, modifiers) -> { diff --git a/fabric-screen-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/screen/StopSoundButton.java b/fabric-screen-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/screen/StopSoundButton.java index b963cbff67..e4da64f52e 100644 --- a/fabric-screen-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/screen/StopSoundButton.java +++ b/fabric-screen-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/screen/StopSoundButton.java @@ -36,7 +36,7 @@ class StopSoundButton extends PressableWidget { @Override public void render(DrawContext drawContext, int mouseX, int mouseY, float tickDelta) { // Render the armor icon to test - drawContext.drawTexture(ScreenTests.GUI_ICONS_TEXTURE, this.getX(), this.getY(), this.width, this.height, 43, 27, 9, 9, 256, 256); + drawContext.drawGuiTexture(ScreenTests.ARMOR_FULL_TEXTURE, this.getX(), this.getY(), this.width, this.height); if (this.isMouseOver(mouseX, mouseY)) { drawContext.drawTooltip(Screens.getTextRenderer(this.screen), Text.literal("Click to stop all sounds"), this.getX(), this.getY()); diff --git a/fabric-screen-handler-api-v1/build.gradle b/fabric-screen-handler-api-v1/build.gradle index 089aa943d5..ac72cee2e8 100644 --- a/fabric-screen-handler-api-v1/build.gradle +++ b/fabric-screen-handler-api-v1/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-screen-handler-api-v1" version = getSubprojectVersion(project) loom { diff --git a/fabric-screen-handler-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/screenhandler/client/PositionedScreen.java b/fabric-screen-handler-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/screenhandler/client/PositionedScreen.java index e3309ec595..13a655de80 100644 --- a/fabric-screen-handler-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/screenhandler/client/PositionedScreen.java +++ b/fabric-screen-handler-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/screenhandler/client/PositionedScreen.java @@ -48,7 +48,7 @@ private static Optional getPositionText(ScreenHandler handler) { @Override public void render(DrawContext drawContext, int mouseX, int mouseY, float delta) { - renderBackground(drawContext); + renderBackground(drawContext, mouseX, mouseY, delta); super.render(drawContext, mouseX, mouseY, delta); drawMouseoverTooltip(drawContext, mouseX, mouseY); } diff --git a/fabric-sound-api-v1/build.gradle b/fabric-sound-api-v1/build.gradle index 2a9df474d7..b484a8a73e 100644 --- a/fabric-sound-api-v1/build.gradle +++ b/fabric-sound-api-v1/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-sound-api-v1" version = getSubprojectVersion(project) testDependencies(project, [ diff --git a/fabric-transfer-api-v1/build.gradle b/fabric-transfer-api-v1/build.gradle index e63162015a..9f81ef61be 100644 --- a/fabric-transfer-api-v1/build.gradle +++ b/fabric-transfer-api-v1/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-transfer-api-v1" version = getSubprojectVersion(project) moduleDependencies(project, [ diff --git a/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/api/transfer/v1/fluid/FluidVariantAttributeHandler.java b/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/api/transfer/v1/fluid/FluidVariantAttributeHandler.java index 0aa8febc75..5d7018d1a5 100644 --- a/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/api/transfer/v1/fluid/FluidVariantAttributeHandler.java +++ b/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/api/transfer/v1/fluid/FluidVariantAttributeHandler.java @@ -21,11 +21,15 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; +import net.minecraft.block.Block; +import net.minecraft.block.Blocks; import net.minecraft.fluid.FlowableFluid; import net.minecraft.fluid.Fluid; import net.minecraft.item.BucketItem; +import net.minecraft.registry.Registries; import net.minecraft.sound.SoundEvent; import net.minecraft.text.Text; +import net.minecraft.util.Util; import net.minecraft.world.World; /** @@ -41,7 +45,14 @@ public interface FluidVariantAttributeHandler { * Return the name that should be used for the passed fluid variant. */ default Text getName(FluidVariant fluidVariant) { - return fluidVariant.getFluid().getDefaultState().getBlockState().getBlock().getName(); + Block fluidBlock = fluidVariant.getFluid().getDefaultState().getBlockState().getBlock(); + + if (!fluidVariant.isBlank() && fluidBlock == Blocks.AIR) { + // Some non-placeable fluids use air as their fluid block, in that case infer translation key from the fluid id. + return Text.translatable(Util.createTranslationKey("block", Registries.FLUID.getId(fluidVariant.getFluid()))); + } else { + return fluidBlock.getName(); + } } /** diff --git a/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/api/transfer/v1/storage/Storage.java b/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/api/transfer/v1/storage/Storage.java index 5737d8c584..7398140fb0 100644 --- a/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/api/transfer/v1/storage/Storage.java +++ b/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/api/transfer/v1/storage/Storage.java @@ -32,11 +32,14 @@ /** * An object that can store resources. * + *

    Most of the documentation that follows is quite technical. + * For an easier introduction to the API, see the wiki page. + * *

      *
    • {@link #supportsInsertion} and {@link #supportsExtraction} can be used to tell if insertion and extraction * functionality are possibly supported by this storage.
    • *
    • {@link #insert} and {@link #extract} can be used to insert or extract resources from this storage.
    • - *
    • {@link #iterator} and {@link #exactView} can be used to inspect the contents of this storage.
    • + *
    • {@link #iterator} can be used to inspect the contents of this storage.
    • *
    • {@link #getVersion()} can be used to quickly check if a storage has changed, without having to rescan its contents.
    • *
    * @@ -92,17 +95,6 @@ default boolean supportsInsertion() { */ long insert(T resource, long maxAmount, TransactionContext transaction); - /** - * Convenient helper to simulate an insertion, i.e. get the result of insert without modifying any state. - * The passed transaction may be null if a new transaction should be opened for the simulation. - * @see #insert - */ - default long simulateInsert(T resource, long maxAmount, @Nullable TransactionContext transaction) { - try (Transaction simulateTransaction = Transaction.openNested(transaction)) { - return insert(resource, maxAmount, simulateTransaction); - } - } - /** * Return false if calling {@link #extract} will absolutely always return 0, or true otherwise or in doubt. * @@ -123,17 +115,6 @@ default boolean supportsExtraction() { */ long extract(T resource, long maxAmount, TransactionContext transaction); - /** - * Convenient helper to simulate an extraction, i.e. get the result of extract without modifying any state. - * The passed transaction may be null if a new transaction should be opened for the simulation. - * @see #extract - */ - default long simulateExtract(T resource, long maxAmount, @Nullable TransactionContext transaction) { - try (Transaction simulateTransaction = Transaction.openNested(transaction)) { - return extract(resource, maxAmount, simulateTransaction); - } - } - /** * Iterate through the contents of this storage. * Every visited {@link StorageView} represents a stored resource and an amount. @@ -146,6 +127,9 @@ default long simulateExtract(T resource, long maxAmount, @Nullable TransactionCo * but inventories with a dynamic or very large amount of slots should not do that to ensure timely termination of * the iteration. * + *

    If a modification is made to the storage during iteration, the iterator might become invalid at the end of the outermost transaction. + * In particular, if multiple storage views are extracted from, the entire iteration should be wrapped in a transaction. + * * @return An iterator over the contents of this storage. Calling remove on the iterator is not allowed. */ @Override @@ -184,22 +168,6 @@ default Iterable> nonEmptyViews() { return this::nonEmptyIterator; } - /** - * Return a view over this storage, for a specific resource, or {@code null} if none is quickly available. - * - *

    This function should only return a non-null view if this storage can provide it quickly, - * for example with a hashmap lookup. - * If returning the requested view would require iteration through a potentially large number of views, - * {@code null} should be returned instead. - * - * @param resource The resource for which a storage view is requested. May be blank, for example to estimate capacity. - * @return A view over this storage for the passed resource, or {@code null} if none is quickly available. - */ - @Nullable - default StorageView exactView(T resource) { - return null; - } - /** * Return an integer representing the current version of this storage instance to allow for fast change detection: * if the version hasn't changed since the last time, and the storage instance is the same, the storage has the same contents. @@ -244,4 +212,44 @@ default long getVersion() { static Class> asClass() { return (Class>) (Object) Storage.class; } + + /** + * Convenient helper to simulate an insertion, i.e. get the result of insert without modifying any state. + * The passed transaction may be null if a new transaction should be opened for the simulation. + * @see #insert + * @deprecated Either use transactions directly, or use {@link StorageUtil#simulateInsert}. + */ + @Deprecated(forRemoval = true) + default long simulateInsert(T resource, long maxAmount, @Nullable TransactionContext transaction) { + return StorageUtil.simulateInsert(this, resource, maxAmount, transaction); + } + + /** + * Convenient helper to simulate an extraction, i.e. get the result of extract without modifying any state. + * The passed transaction may be null if a new transaction should be opened for the simulation. + * @see #extract + * @deprecated Either use transactions directly, or use {@link StorageUtil#simulateExtract}. + */ + @Deprecated(forRemoval = true) + default long simulateExtract(T resource, long maxAmount, @Nullable TransactionContext transaction) { + return StorageUtil.simulateExtract(this, resource, maxAmount, transaction); + } + + /** + * Return a view over this storage, for a specific resource, or {@code null} if none is quickly available. + * + *

    This function should only return a non-null view if this storage can provide it quickly, + * for example with a hashmap lookup. + * If returning the requested view would require iteration through a potentially large number of views, + * {@code null} should be returned instead. + * + * @param resource The resource for which a storage view is requested. May be blank, for example to estimate capacity. + * @return A view over this storage for the passed resource, or {@code null} if none is quickly available. + * @deprecated Deprecated for removal without direct replacement. Use {@link #insert}, {@link #extract} or {@link #iterator} instead. + */ + @Deprecated(forRemoval = true) + @Nullable + default StorageView exactView(T resource) { + return null; + } } diff --git a/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/api/transfer/v1/storage/StorageUtil.java b/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/api/transfer/v1/storage/StorageUtil.java index 6bc47851a3..2c69f78f11 100644 --- a/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/api/transfer/v1/storage/StorageUtil.java +++ b/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/api/transfer/v1/storage/StorageUtil.java @@ -93,13 +93,9 @@ public static long move(@Nullable Storage from, @Nullable Storage to, for (StorageView view : from.nonEmptyViews()) { T resource = view.getResource(); if (!filter.test(resource)) continue; - long maxExtracted; // check how much can be extracted - try (Transaction extractionTestTransaction = iterationTransaction.openNested()) { - maxExtracted = view.extract(resource, maxAmount - totalMoved, extractionTestTransaction); - extractionTestTransaction.abort(); - } + long maxExtracted = simulateExtract(view, resource, maxAmount - totalMoved, iterationTransaction); try (Transaction transferTransaction = iterationTransaction.openNested()) { // check how much can be inserted @@ -134,6 +130,52 @@ public static long move(@Nullable Storage from, @Nullable Storage to, return totalMoved; } + /** + * Convenient helper to simulate an insertion, i.e. get the result of insert without modifying any state. + * The passed transaction may be null if a new transaction should be opened for the simulation. + * @see Storage#insert + */ + public static long simulateInsert(Storage storage, T resource, long maxAmount, @Nullable TransactionContext transaction) { + try (Transaction simulateTransaction = Transaction.openNested(transaction)) { + return storage.insert(resource, maxAmount, simulateTransaction); + } + } + + /** + * Convenient helper to simulate an extraction, i.e. get the result of extract without modifying any state. + * The passed transaction may be null if a new transaction should be opened for the simulation. + * @see Storage#insert + */ + public static long simulateExtract(Storage storage, T resource, long maxAmount, @Nullable TransactionContext transaction) { + try (Transaction simulateTransaction = Transaction.openNested(transaction)) { + return storage.extract(resource, maxAmount, simulateTransaction); + } + } + + /** + * Convenient helper to simulate an extraction, i.e. get the result of extract without modifying any state. + * The passed transaction may be null if a new transaction should be opened for the simulation. + * @see Storage#insert + */ + public static long simulateExtract(StorageView storageView, T resource, long maxAmount, @Nullable TransactionContext transaction) { + try (Transaction simulateTransaction = Transaction.openNested(transaction)) { + return storageView.extract(resource, maxAmount, simulateTransaction); + } + } + + /** + * Convenient helper to simulate an extraction, i.e. get the result of extract without modifying any state. + * The passed transaction may be null if a new transaction should be opened for the simulation. + * @see Storage#insert + * @apiNote This function handles the method overload conflict for objects that implement both {@link Storage} and {@link StorageView}. + */ + // Object & is used to have a different erasure than the other overloads. + public static & StorageView> long simulateExtract(S storage, T resource, long maxAmount, @Nullable TransactionContext transaction) { + try (Transaction simulateTransaction = Transaction.openNested(transaction)) { + return storage.extract(resource, maxAmount, simulateTransaction); + } + } + /** * Try to extract any resource from a storage, up to a maximum amount. * @@ -336,7 +378,7 @@ public static ResourceAmount findExtractableContent(@Nullable Storage T extractableResource = findExtractableResource(storage, filter, transaction); if (extractableResource != null) { - long extractableAmount = storage.simulateExtract(extractableResource, Long.MAX_VALUE, transaction); + long extractableAmount = simulateExtract(storage, extractableResource, Long.MAX_VALUE, transaction); if (extractableAmount > 0) { return new ResourceAmount<>(extractableResource, extractableAmount); diff --git a/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/api/transfer/v1/storage/StorageView.java b/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/api/transfer/v1/storage/StorageView.java index 56a5db98fe..5761db4874 100644 --- a/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/api/transfer/v1/storage/StorageView.java +++ b/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/api/transfer/v1/storage/StorageView.java @@ -21,7 +21,7 @@ import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext; /** - * A view of a single stored resource in a {@link Storage}, for use with {@link Storage#iterator} or {@link Storage#exactView}. + * A view of a single stored resource in a {@link Storage}, for use with {@link Storage#iterator}. * * @param The type of the stored resource. * @@ -57,7 +57,7 @@ public interface StorageView { /** * @return The total amount of {@link #getResource} that could be stored in this view, - * or an estimate of the number of resources that could be stored if this view has a blank resource. + * or an estimated upper bound on the number of resources that could be stored if this view has a blank resource. */ long getCapacity(); diff --git a/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/api/transfer/v1/storage/base/SidedStorageBlockEntity.java b/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/api/transfer/v1/storage/base/SidedStorageBlockEntity.java index 09c20208fa..b7f466271b 100644 --- a/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/api/transfer/v1/storage/base/SidedStorageBlockEntity.java +++ b/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/api/transfer/v1/storage/base/SidedStorageBlockEntity.java @@ -47,19 +47,23 @@ public interface SidedStorageBlockEntity { /** * Return a fluid storage if available on the queried side, or null otherwise. + * + * @param side The side of the storage to query, {@code null} means that the full storage without the restriction should be returned instead. */ @ApiStatus.OverrideOnly @Nullable - default Storage getFluidStorage(Direction side) { + default Storage getFluidStorage(@Nullable Direction side) { return null; } /** * Return an item storage if available on the queried side, or null otherwise. + * + * @param side The side of the storage to query, {@code null} means that the full storage without the restriction should be returned instead. */ @ApiStatus.OverrideOnly @Nullable - default Storage getItemStorage(Direction side) { + default Storage getItemStorage(@Nullable Direction side) { return null; } } diff --git a/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/mixin/transfer/DropperBlockMixin.java b/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/mixin/transfer/DropperBlockMixin.java index e04780c2da..881c327905 100644 --- a/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/mixin/transfer/DropperBlockMixin.java +++ b/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/mixin/transfer/DropperBlockMixin.java @@ -21,6 +21,7 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import net.minecraft.block.BlockState; import net.minecraft.block.DispenserBlock; import net.minecraft.block.DropperBlock; import net.minecraft.block.entity.DispenserBlockEntity; @@ -49,7 +50,7 @@ public class DropperBlockMixin { cancellable = true, allow = 1 ) - public void hookDispense(ServerWorld world, BlockPos pos, CallbackInfo ci) { + public void hookDispense(ServerWorld world, BlockState blockState, BlockPos pos, CallbackInfo ci) { DispenserBlockEntity dispenser = (DispenserBlockEntity) world.getBlockEntity(pos); Direction direction = dispenser.getCachedState().get(DispenserBlock.FACING); diff --git a/fabric-transfer-api-v1/src/testmod/java/net/fabricmc/fabric/test/transfer/gametests/VanillaStorageTests.java b/fabric-transfer-api-v1/src/testmod/java/net/fabricmc/fabric/test/transfer/gametests/VanillaStorageTests.java index 97ee26ceca..3080a914f7 100644 --- a/fabric-transfer-api-v1/src/testmod/java/net/fabricmc/fabric/test/transfer/gametests/VanillaStorageTests.java +++ b/fabric-transfer-api-v1/src/testmod/java/net/fabricmc/fabric/test/transfer/gametests/VanillaStorageTests.java @@ -45,6 +45,7 @@ import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant; import net.fabricmc.fabric.api.transfer.v1.storage.SlottedStorage; 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.fabricmc.fabric.test.transfer.mixin.AbstractFurnaceBlockEntityAccessor; @@ -224,7 +225,7 @@ public void testShulkerNoInsert(TestContext context) { ShulkerBoxBlockEntity shulker = (ShulkerBoxBlockEntity) context.getBlockEntity(pos); InventoryStorage storage = InventoryStorage.of(shulker, null); - if (storage.simulateInsert(ItemVariant.of(Items.SHULKER_BOX), 1, null) > 0) { + if (StorageUtil.simulateInsert(storage, ItemVariant.of(Items.SHULKER_BOX), 1, null) > 0) { context.throwPositionedException("Expected shulker box to be rejected", pos); } diff --git a/fabric-transfer-api-v1/src/testmod/java/net/fabricmc/fabric/test/transfer/ingame/ExtractStickItem.java b/fabric-transfer-api-v1/src/testmod/java/net/fabricmc/fabric/test/transfer/ingame/ExtractStickItem.java index c641ea80b9..dbae074ba2 100644 --- a/fabric-transfer-api-v1/src/testmod/java/net/fabricmc/fabric/test/transfer/ingame/ExtractStickItem.java +++ b/fabric-transfer-api-v1/src/testmod/java/net/fabricmc/fabric/test/transfer/ingame/ExtractStickItem.java @@ -18,11 +18,13 @@ import net.minecraft.item.Item; import net.minecraft.item.ItemUsageContext; +import net.minecraft.text.Text; import net.minecraft.util.ActionResult; import net.fabricmc.fabric.api.transfer.v1.fluid.FluidConstants; 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.fluid.FluidVariantAttributes; 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; @@ -47,6 +49,12 @@ public ActionResult useOnBlock(ItemUsageContext context) { boolean requireExact = context.getPlayer() != null && context.getPlayer().isSneaking(); if (!requireExact || extracted == FluidConstants.BUCKET) { + if (context.getPlayer() != null) { + context.getPlayer().sendMessage( + Text.literal("Extracted some ").append(FluidVariantAttributes.getName(stored)).append("."), + true); + } + transaction.commit(); return ActionResult.success(context.getWorld().isClient()); } diff --git a/fabric-transfer-api-v1/src/testmod/java/net/fabricmc/fabric/test/transfer/unittests/BaseStorageTests.java b/fabric-transfer-api-v1/src/testmod/java/net/fabricmc/fabric/test/transfer/unittests/BaseStorageTests.java index 1c0a9fbd04..d8f9b22ac0 100644 --- a/fabric-transfer-api-v1/src/testmod/java/net/fabricmc/fabric/test/transfer/unittests/BaseStorageTests.java +++ b/fabric-transfer-api-v1/src/testmod/java/net/fabricmc/fabric/test/transfer/unittests/BaseStorageTests.java @@ -66,9 +66,9 @@ protected boolean canInsert(FluidVariant resource) { } // Insertion through the filter should fail. - assertEquals(0L, noWater.simulateInsert(water, BUCKET, null)); + assertEquals(0L, StorageUtil.simulateInsert(noWater, water, BUCKET, null)); // Extraction should also fail. - assertEquals(0L, noWater.simulateExtract(water, BUCKET, null)); + assertEquals(0L, StorageUtil.simulateExtract(noWater, water, BUCKET, null)); // The fluid should be visible. assertEquals(water, StorageUtil.findStoredResource(noWater)); // Test the filter. @@ -83,13 +83,13 @@ protected boolean canInsert(FluidVariant resource) { // Lava insertion and extract should proceed just fine. try (Transaction tx = Transaction.openOuter()) { assertEquals(BUCKET, noWater.insert(lava, BUCKET, tx)); - assertEquals(BUCKET, noWater.simulateExtract(lava, BUCKET, tx)); + assertEquals(BUCKET, StorageUtil.simulateExtract(noWater, lava, BUCKET, tx)); // Test that simulating doesn't change the state... - assertEquals(BUCKET, noWater.simulateExtract(lava, BUCKET, tx)); - assertEquals(BUCKET, noWater.simulateExtract(lava, BUCKET, tx)); + assertEquals(BUCKET, StorageUtil.simulateExtract(noWater, lava, BUCKET, tx)); + assertEquals(BUCKET, StorageUtil.simulateExtract(noWater, lava, BUCKET, tx)); tx.commit(); } - assertEquals(BUCKET, storage.simulateExtract(lava, BUCKET, null)); + assertEquals(BUCKET, StorageUtil.simulateExtract(storage, lava, BUCKET, null)); } } diff --git a/fabric-transfer-api-v1/src/testmod/java/net/fabricmc/fabric/test/transfer/unittests/FluidTests.java b/fabric-transfer-api-v1/src/testmod/java/net/fabricmc/fabric/test/transfer/unittests/FluidTests.java index b2d57ebbfe..42f7040efe 100644 --- a/fabric-transfer-api-v1/src/testmod/java/net/fabricmc/fabric/test/transfer/unittests/FluidTests.java +++ b/fabric-transfer-api-v1/src/testmod/java/net/fabricmc/fabric/test/transfer/unittests/FluidTests.java @@ -22,6 +22,7 @@ import net.minecraft.nbt.NbtCompound; import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant; +import net.fabricmc.fabric.api.transfer.v1.storage.StorageUtil; import net.fabricmc.fabric.api.transfer.v1.storage.base.SingleSlotStorage; import net.fabricmc.fabric.api.transfer.v1.storage.base.SingleVariantStorage; import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction; @@ -78,7 +79,7 @@ private static void testFluidStorage() { // Should not allow lava (canInsert returns false) if (waterStorage.insert(LAVA, BUCKET, tx) != 0) throw new AssertionError("Lava inserted"); // Should allow insert, but without mutating the storage. - if (waterStorage.simulateInsert(WATER, BUCKET, tx) != BUCKET) throw new AssertionError("Simulated insert failed"); + if (StorageUtil.simulateInsert(waterStorage, WATER, BUCKET, tx) != BUCKET) throw new AssertionError("Simulated insert failed"); // Should allow insert if (waterStorage.insert(TAGGED_WATER, BUCKET, tx) != BUCKET) throw new AssertionError("Tagged water insert 1 failed"); // Variants are different, should not allow insert @@ -90,7 +91,7 @@ private static void testFluidStorage() { // Should allow extraction if (waterStorage.extract(TAGGED_WATER_2, BUCKET, tx) != BUCKET) throw new AssertionError("Extraction failed"); // Simulated extraction should succeed but do nothing - if (waterStorage.simulateExtract(TAGGED_WATER, Long.MAX_VALUE, tx) != BUCKET) throw new AssertionError("Simulated extraction failed"); + if (StorageUtil.simulateExtract(waterStorage, TAGGED_WATER, Long.MAX_VALUE, tx) != BUCKET) throw new AssertionError("Simulated extraction failed"); // Re-insert if (waterStorage.insert(TAGGED_WATER_2, BUCKET, tx) != BUCKET) throw new AssertionError("Tagged water insert 3 failed"); // Test contents diff --git a/fabric-transfer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/transfer/ingame/client/FluidVariantRenderTest.java b/fabric-transfer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/transfer/ingame/client/FluidVariantRenderTest.java index d5a4bd2369..bcac9e2b94 100644 --- a/fabric-transfer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/transfer/ingame/client/FluidVariantRenderTest.java +++ b/fabric-transfer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/transfer/ingame/client/FluidVariantRenderTest.java @@ -54,7 +54,7 @@ public void onInitializeClient() { PlayerEntity player = MinecraftClient.getInstance().player; if (player == null) return; - if (MinecraftClient.getInstance().options.debugEnabled) return; + if (MinecraftClient.getInstance().inGameHud.getDebugHud().shouldShowDebugHud()) return; int renderY = 0; List variants = List.of(FluidVariant.of(Fluids.WATER), FluidVariant.of(Fluids.LAVA)); diff --git a/fabric-transitive-access-wideners-v1/build.gradle b/fabric-transitive-access-wideners-v1/build.gradle index 6154218712..0f33e7de46 100644 --- a/fabric-transitive-access-wideners-v1/build.gradle +++ b/fabric-transitive-access-wideners-v1/build.gradle @@ -1,4 +1,3 @@ -archivesBaseName = "fabric-transitive-access-wideners-v1" version = getSubprojectVersion(project) loom { diff --git a/fabric-transitive-access-wideners-v1/src/main/resources/fabric-transitive-access-wideners-v1.accesswidener b/fabric-transitive-access-wideners-v1/src/main/resources/fabric-transitive-access-wideners-v1.accesswidener index eb8f468757..df0f4a66ec 100644 --- a/fabric-transitive-access-wideners-v1/src/main/resources/fabric-transitive-access-wideners-v1.accesswidener +++ b/fabric-transitive-access-wideners-v1/src/main/resources/fabric-transitive-access-wideners-v1.accesswidener @@ -9,7 +9,7 @@ transitive-accessible method net/minecraft/client/item/ModelPredicateProviderReg transitive-accessible method net/minecraft/client/item/ModelPredicateProviderRegistry registerCustomModelData (Lnet/minecraft/client/item/ModelPredicateProvider;)V # Registering custom advancement criteria -transitive-accessible method net/minecraft/advancement/criterion/Criteria register (Lnet/minecraft/advancement/criterion/Criterion;)Lnet/minecraft/advancement/criterion/Criterion; +transitive-accessible method net/minecraft/advancement/criterion/Criteria register (Ljava/lang/String;Lnet/minecraft/advancement/criterion/Criterion;)Lnet/minecraft/advancement/criterion/Criterion; # Creating custom screen handler types transitive-accessible class net/minecraft/screen/ScreenHandlerType$Factory @@ -20,23 +20,22 @@ transitive-accessible class net/minecraft/client/gui/screen/ingame/HandledScreen transitive-accessible method net/minecraft/client/gui/screen/ingame/HandledScreens register (Lnet/minecraft/screen/ScreenHandlerType;Lnet/minecraft/client/gui/screen/ingame/HandledScreens$Provider;)V # Data contained in loot tables and pools -transitive-accessible field net/minecraft/loot/LootPool entries [Lnet/minecraft/loot/entry/LootPoolEntry; -transitive-accessible field net/minecraft/loot/LootPool conditions [Lnet/minecraft/loot/condition/LootCondition; -transitive-accessible field net/minecraft/loot/LootPool functions [Lnet/minecraft/loot/function/LootFunction; +transitive-accessible field net/minecraft/loot/LootPool entries Ljava/util/List; +transitive-accessible field net/minecraft/loot/LootPool conditions Ljava/util/List; +transitive-accessible field net/minecraft/loot/LootPool functions Ljava/util/List; transitive-accessible field net/minecraft/loot/LootPool rolls Lnet/minecraft/loot/provider/number/LootNumberProvider; transitive-accessible field net/minecraft/loot/LootPool bonusRolls Lnet/minecraft/loot/provider/number/LootNumberProvider; -transitive-accessible field net/minecraft/loot/LootTable pools [Lnet/minecraft/loot/LootPool; -transitive-accessible field net/minecraft/loot/LootTable functions [Lnet/minecraft/loot/function/LootFunction; # Villager trade factories -transitive-accessible class net/minecraft/village/TradeOffers$BuyForOneEmeraldFactory +transitive-accessible class net/minecraft/village/TradeOffers$TypedWrapperFactory +transitive-accessible class net/minecraft/village/TradeOffers$EnchantBookFactory +transitive-accessible class net/minecraft/village/TradeOffers$BuyItemFactory transitive-accessible class net/minecraft/village/TradeOffers$SellItemFactory transitive-accessible class net/minecraft/village/TradeOffers$SellSuspiciousStewFactory transitive-accessible class net/minecraft/village/TradeOffers$ProcessItemFactory transitive-accessible class net/minecraft/village/TradeOffers$SellEnchantedToolFactory transitive-accessible class net/minecraft/village/TradeOffers$TypeAwareBuyForOneEmeraldFactory transitive-accessible class net/minecraft/village/TradeOffers$SellPotionHoldingItemFactory -transitive-accessible class net/minecraft/village/TradeOffers$EnchantBookFactory transitive-accessible class net/minecraft/village/TradeOffers$SellMapFactory transitive-accessible class net/minecraft/village/TradeOffers$SellDyedArmorFactory @@ -130,6 +129,23 @@ transitive-accessible method net/minecraft/entity/damage/DamageSources create (L # The attack cooldown transitive-accessible field net/minecraft/client/MinecraftClient attackCooldown I +# Creating certain types of blocks +transitive-accessible method net/minecraft/block/Blocks createBambooBlock (Lnet/minecraft/block/MapColor;Lnet/minecraft/block/MapColor;Lnet/minecraft/sound/BlockSoundGroup;)Lnet/minecraft/block/PillarBlock; +transitive-accessible method net/minecraft/block/Blocks createFlowerPotBlock (Lnet/minecraft/block/Block;[Lnet/minecraft/resource/featuretoggle/FeatureFlag;)Lnet/minecraft/block/FlowerPotBlock; +transitive-accessible method net/minecraft/block/Blocks createLeavesBlock (Lnet/minecraft/sound/BlockSoundGroup;)Lnet/minecraft/block/LeavesBlock; +transitive-accessible method net/minecraft/block/Blocks createLogBlock (Lnet/minecraft/block/MapColor;Lnet/minecraft/block/MapColor;)Lnet/minecraft/block/PillarBlock; +transitive-accessible method net/minecraft/block/Blocks createNetherStemBlock (Lnet/minecraft/block/MapColor;)Lnet/minecraft/block/Block; +transitive-accessible method net/minecraft/block/Blocks createStoneButtonBlock ()Lnet/minecraft/block/ButtonBlock; +transitive-accessible method net/minecraft/block/Blocks createWoodenButtonBlock (Lnet/minecraft/block/BlockSetType;[Lnet/minecraft/resource/featuretoggle/FeatureFlag;)Lnet/minecraft/block/ButtonBlock; + +# Methods used in block creation +transitive-accessible method net/minecraft/block/Blocks always (Lnet/minecraft/block/BlockState;Lnet/minecraft/world/BlockView;Lnet/minecraft/util/math/BlockPos;)Z +transitive-accessible method net/minecraft/block/Blocks always (Lnet/minecraft/block/BlockState;Lnet/minecraft/world/BlockView;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/entity/EntityType;)Ljava/lang/Boolean; +transitive-accessible method net/minecraft/block/Blocks canSpawnOnLeaves (Lnet/minecraft/block/BlockState;Lnet/minecraft/world/BlockView;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/entity/EntityType;)Ljava/lang/Boolean; +transitive-accessible method net/minecraft/block/Blocks createLightLevelFromLitBlockState (I)Ljava/util/function/ToIntFunction; +transitive-accessible method net/minecraft/block/Blocks never (Lnet/minecraft/block/BlockState;Lnet/minecraft/world/BlockView;Lnet/minecraft/util/math/BlockPos;)Z +transitive-accessible method net/minecraft/block/Blocks never (Lnet/minecraft/block/BlockState;Lnet/minecraft/world/BlockView;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/entity/EntityType;)Ljava/lang/Boolean; + ### Generated access wideners below # Constructors of non-abstract block classes transitive-accessible method net/minecraft/block/AirBlock (Lnet/minecraft/block/AbstractBlock$Settings;)V diff --git a/fabric-transitive-access-wideners-v1/template.accesswidener b/fabric-transitive-access-wideners-v1/template.accesswidener index 76bf4fa288..feab2c8c4f 100644 --- a/fabric-transitive-access-wideners-v1/template.accesswidener +++ b/fabric-transitive-access-wideners-v1/template.accesswidener @@ -4,7 +4,7 @@ transitive-accessible method net/minecraft/client/item/ModelPredicateProviderReg transitive-accessible method net/minecraft/client/item/ModelPredicateProviderRegistry registerCustomModelData (Lnet/minecraft/client/item/ModelPredicateProvider;)V # Registering custom advancement criteria -transitive-accessible method net/minecraft/advancement/criterion/Criteria register (Lnet/minecraft/advancement/criterion/Criterion;)Lnet/minecraft/advancement/criterion/Criterion; +transitive-accessible method net/minecraft/advancement/criterion/Criteria register (Ljava/lang/String;Lnet/minecraft/advancement/criterion/Criterion;)Lnet/minecraft/advancement/criterion/Criterion; # Creating custom screen handler types transitive-accessible class net/minecraft/screen/ScreenHandlerType$Factory @@ -15,23 +15,22 @@ transitive-accessible class net/minecraft/client/gui/screen/ingame/HandledScreen transitive-accessible method net/minecraft/client/gui/screen/ingame/HandledScreens register (Lnet/minecraft/screen/ScreenHandlerType;Lnet/minecraft/client/gui/screen/ingame/HandledScreens$Provider;)V # Data contained in loot tables and pools -transitive-accessible field net/minecraft/loot/LootPool entries [Lnet/minecraft/loot/entry/LootPoolEntry; -transitive-accessible field net/minecraft/loot/LootPool conditions [Lnet/minecraft/loot/condition/LootCondition; -transitive-accessible field net/minecraft/loot/LootPool functions [Lnet/minecraft/loot/function/LootFunction; +transitive-accessible field net/minecraft/loot/LootPool entries Ljava/util/List; +transitive-accessible field net/minecraft/loot/LootPool conditions Ljava/util/List; +transitive-accessible field net/minecraft/loot/LootPool functions Ljava/util/List; transitive-accessible field net/minecraft/loot/LootPool rolls Lnet/minecraft/loot/provider/number/LootNumberProvider; transitive-accessible field net/minecraft/loot/LootPool bonusRolls Lnet/minecraft/loot/provider/number/LootNumberProvider; -transitive-accessible field net/minecraft/loot/LootTable pools [Lnet/minecraft/loot/LootPool; -transitive-accessible field net/minecraft/loot/LootTable functions [Lnet/minecraft/loot/function/LootFunction; # Villager trade factories -transitive-accessible class net/minecraft/village/TradeOffers$BuyForOneEmeraldFactory +transitive-accessible class net/minecraft/village/TradeOffers$TypedWrapperFactory +transitive-accessible class net/minecraft/village/TradeOffers$EnchantBookFactory +transitive-accessible class net/minecraft/village/TradeOffers$BuyItemFactory transitive-accessible class net/minecraft/village/TradeOffers$SellItemFactory transitive-accessible class net/minecraft/village/TradeOffers$SellSuspiciousStewFactory transitive-accessible class net/minecraft/village/TradeOffers$ProcessItemFactory transitive-accessible class net/minecraft/village/TradeOffers$SellEnchantedToolFactory transitive-accessible class net/minecraft/village/TradeOffers$TypeAwareBuyForOneEmeraldFactory transitive-accessible class net/minecraft/village/TradeOffers$SellPotionHoldingItemFactory -transitive-accessible class net/minecraft/village/TradeOffers$EnchantBookFactory transitive-accessible class net/minecraft/village/TradeOffers$SellMapFactory transitive-accessible class net/minecraft/village/TradeOffers$SellDyedArmorFactory @@ -125,4 +124,21 @@ transitive-accessible method net/minecraft/entity/damage/DamageSources create (L # The attack cooldown transitive-accessible field net/minecraft/client/MinecraftClient attackCooldown I +# Creating certain types of blocks +transitive-accessible method net/minecraft/block/Blocks createBambooBlock (Lnet/minecraft/block/MapColor;Lnet/minecraft/block/MapColor;Lnet/minecraft/sound/BlockSoundGroup;)Lnet/minecraft/block/PillarBlock; +transitive-accessible method net/minecraft/block/Blocks createFlowerPotBlock (Lnet/minecraft/block/Block;[Lnet/minecraft/resource/featuretoggle/FeatureFlag;)Lnet/minecraft/block/FlowerPotBlock; +transitive-accessible method net/minecraft/block/Blocks createLeavesBlock (Lnet/minecraft/sound/BlockSoundGroup;)Lnet/minecraft/block/LeavesBlock; +transitive-accessible method net/minecraft/block/Blocks createLogBlock (Lnet/minecraft/block/MapColor;Lnet/minecraft/block/MapColor;)Lnet/minecraft/block/PillarBlock; +transitive-accessible method net/minecraft/block/Blocks createNetherStemBlock (Lnet/minecraft/block/MapColor;)Lnet/minecraft/block/Block; +transitive-accessible method net/minecraft/block/Blocks createStoneButtonBlock ()Lnet/minecraft/block/ButtonBlock; +transitive-accessible method net/minecraft/block/Blocks createWoodenButtonBlock (Lnet/minecraft/block/BlockSetType;[Lnet/minecraft/resource/featuretoggle/FeatureFlag;)Lnet/minecraft/block/ButtonBlock; + +# Methods used in block creation +transitive-accessible method net/minecraft/block/Blocks always (Lnet/minecraft/block/BlockState;Lnet/minecraft/world/BlockView;Lnet/minecraft/util/math/BlockPos;)Z +transitive-accessible method net/minecraft/block/Blocks always (Lnet/minecraft/block/BlockState;Lnet/minecraft/world/BlockView;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/entity/EntityType;)Ljava/lang/Boolean; +transitive-accessible method net/minecraft/block/Blocks canSpawnOnLeaves (Lnet/minecraft/block/BlockState;Lnet/minecraft/world/BlockView;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/entity/EntityType;)Ljava/lang/Boolean; +transitive-accessible method net/minecraft/block/Blocks createLightLevelFromLitBlockState (I)Ljava/util/function/ToIntFunction; +transitive-accessible method net/minecraft/block/Blocks never (Lnet/minecraft/block/BlockState;Lnet/minecraft/world/BlockView;Lnet/minecraft/util/math/BlockPos;)Z +transitive-accessible method net/minecraft/block/Blocks never (Lnet/minecraft/block/BlockState;Lnet/minecraft/world/BlockView;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/entity/EntityType;)Ljava/lang/Boolean; + ### Generated access wideners below diff --git a/gradle.properties b/gradle.properties index 7592b3dffc..cf38355825 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,63 +2,64 @@ org.gradle.jvmargs=-Xmx2560M org.gradle.parallel=true fabric.loom.multiProjectOptimisation=true -version=0.83.1 -minecraft_version=1.20.1 +version=0.90.0 +minecraft_version=1.20.2 yarn_version=+build.1 -loader_version=0.14.21 +loader_version=0.14.22 installer_version=0.11.1 prerelease=false +curseforge_minecraft_version=1.20.2 # Do not manually update, use the bumpversions task: -fabric-api-base-version=0.4.29 -fabric-api-lookup-api-v1-version=1.6.34 -fabric-biome-api-v1-version=13.0.10 -fabric-block-api-v1-version=1.0.9 -fabric-blockrenderlayer-v1-version=1.1.39 -fabric-command-api-v1-version=1.2.32 -fabric-command-api-v2-version=2.2.11 -fabric-commands-v0-version=0.2.49 -fabric-containers-v0-version=0.1.61 -fabric-content-registries-v0-version=4.0.7 -fabric-crash-report-info-v1-version=0.2.18 -fabric-data-generation-api-v1-version=12.1.11 -fabric-dimensions-v1-version=2.1.51 -fabric-entity-events-v1-version=1.5.21 -fabric-events-interaction-v0-version=0.6.0 -fabric-events-lifecycle-v0-version=0.2.61 -fabric-game-rule-api-v1-version=1.0.38 -fabric-gametest-api-v1-version=1.2.10 -fabric-item-api-v1-version=2.1.26 -fabric-item-group-api-v1-version=4.0.7 -fabric-key-binding-api-v1-version=1.0.36 -fabric-keybindings-v0-version=0.2.34 -fabric-lifecycle-events-v1-version=2.2.20 -fabric-loot-api-v2-version=1.1.37 -fabric-loot-tables-v1-version=1.1.41 -fabric-message-api-v1-version=5.1.6 -fabric-mining-level-api-v1-version=2.1.47 -fabric-models-v0-version=0.3.35 -fabric-networking-api-v1-version=1.3.8 -fabric-networking-v0-version=0.3.48 -fabric-object-builder-api-v1-version=11.0.6 -fabric-particles-v1-version=1.0.28 -fabric-recipe-api-v1-version=1.0.18 -fabric-registry-sync-v0-version=2.2.6 -fabric-renderer-api-v1-version=3.0.1 -fabric-renderer-indigo-version=1.3.1 -fabric-renderer-registries-v1-version=3.2.44 -fabric-rendering-data-attachment-v1-version=0.3.33 -fabric-rendering-fluids-v1-version=3.0.26 -fabric-rendering-v0-version=1.1.47 -fabric-rendering-v1-version=3.0.6 -fabric-resource-conditions-api-v1-version=2.3.5 -fabric-resource-loader-v0-version=0.11.7 -fabric-screen-api-v1-version=2.0.6 -fabric-screen-handler-api-v1-version=1.3.27 +fabric-api-base-version=0.4.33 +fabric-api-lookup-api-v1-version=1.6.41 +fabric-biome-api-v1-version=13.0.13 +fabric-block-api-v1-version=1.0.12 +fabric-block-view-api-v2-version=1.0.1 +fabric-blockrenderlayer-v1-version=1.1.43 +fabric-command-api-v1-version=1.2.36 +fabric-command-api-v2-version=2.2.15 +fabric-commands-v0-version=0.2.53 +fabric-containers-v0-version=0.1.73 +fabric-content-registries-v0-version=5.0.4 +fabric-crash-report-info-v1-version=0.2.20 +fabric-data-generation-api-v1-version=13.1.3 +fabric-dimensions-v1-version=2.1.56 +fabric-entity-events-v1-version=1.5.25 +fabric-events-interaction-v0-version=0.6.9 +fabric-events-lifecycle-v0-version=0.2.68 +fabric-game-rule-api-v1-version=1.0.40 +fabric-gametest-api-v1-version=1.2.15 +fabric-item-api-v1-version=2.1.32 +fabric-item-group-api-v1-version=4.0.14 +fabric-key-binding-api-v1-version=1.0.38 +fabric-keybindings-v0-version=0.2.36 +fabric-lifecycle-events-v1-version=2.2.26 +fabric-loot-api-v2-version=2.1.0 +fabric-message-api-v1-version=6.0.2 +fabric-mining-level-api-v1-version=2.1.54 +fabric-model-loading-api-v1-version=1.0.5 +fabric-models-v0-version=0.4.4 +fabric-networking-api-v1-version=3.0.9 +fabric-object-builder-api-v1-version=12.1.1 +fabric-particles-v1-version=1.1.4 +fabric-recipe-api-v1-version=2.0.6 +fabric-registry-sync-v0-version=4.0.4 +fabric-renderer-api-v1-version=3.2.1 +fabric-renderer-indigo-version=1.5.1 +fabric-renderer-registries-v1-version=3.2.48 +fabric-rendering-data-attachment-v1-version=0.3.39 +fabric-rendering-fluids-v1-version=3.0.30 +fabric-rendering-v0-version=1.1.51 +fabric-rendering-v1-version=3.0.10 +fabric-resource-conditions-api-v1-version=2.3.10 +fabric-resource-loader-v0-version=0.11.11 +fabric-screen-api-v1-version=2.0.11 +fabric-screen-handler-api-v1-version=1.3.39 fabric-server-consent-api-v1-version=1.0.0 -fabric-sound-api-v1-version=1.0.12 -fabric-transfer-api-v1-version=3.2.2 -fabric-transitive-access-wideners-v1-version=4.2.0 -fabric-convention-tags-v1-version=1.5.3 -fabric-client-tags-api-v1-version=1.0.20 +fabric-sound-api-v1-version=1.0.14 +fabric-transfer-api-v1-version=3.3.8 +fabric-transitive-access-wideners-v1-version=5.0.3 +fabric-convention-tags-v1-version=1.5.7 +fabric-client-tags-api-v1-version=1.1.4 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index c1962a79e2..7f93135c49 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 37aef8d3f0..ac72c34e8a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ 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.3-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index aeb74cbb43..0adc8e1a53 100755 --- a/gradlew +++ b/gradlew @@ -83,7 +83,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -130,10 +131,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. diff --git a/settings.gradle b/settings.gradle index e9f685a677..f70a1286ae 100644 --- a/settings.gradle +++ b/settings.gradle @@ -16,9 +16,12 @@ include 'fabric-api-base' include 'fabric-api-lookup-api-v1' include 'fabric-biome-api-v1' include 'fabric-block-api-v1' +include 'fabric-block-view-api-v2' include 'fabric-blockrenderlayer-v1' +include 'fabric-client-tags-api-v1' include 'fabric-command-api-v2' include 'fabric-content-registries-v0' +include 'fabric-convention-tags-v1' include 'fabric-crash-report-info-v1' include 'fabric-data-generation-api-v1' include 'fabric-dimensions-v1' @@ -33,7 +36,7 @@ include 'fabric-lifecycle-events-v1' include 'fabric-loot-api-v2' include 'fabric-message-api-v1' include 'fabric-mining-level-api-v1' -include 'fabric-models-v0' +include 'fabric-model-loading-api-v1' include 'fabric-networking-api-v1' include 'fabric-object-builder-api-v1' include 'fabric-particles-v1' @@ -41,28 +44,24 @@ include 'fabric-recipe-api-v1' include 'fabric-registry-sync-v0' include 'fabric-renderer-api-v1' include 'fabric-renderer-indigo' -include 'fabric-server-consent-api-v1' - -include 'fabric-rendering-v1' -include 'fabric-rendering-data-attachment-v1' include 'fabric-rendering-fluids-v1' +include 'fabric-rendering-v1' include 'fabric-resource-conditions-api-v1' include 'fabric-resource-loader-v0' include 'fabric-screen-api-v1' include 'fabric-screen-handler-api-v1' +include 'fabric-server-consent-api-v1' include 'fabric-sound-api-v1' include 'fabric-transfer-api-v1' -include 'fabric-convention-tags-v1' -include 'fabric-client-tags-api-v1' include 'fabric-transitive-access-wideners-v1' include 'deprecated' -include 'deprecated:fabric-commands-v0' include 'deprecated:fabric-command-api-v1' +include 'deprecated:fabric-commands-v0' include 'deprecated:fabric-containers-v0' include 'deprecated:fabric-events-lifecycle-v0' include 'deprecated:fabric-keybindings-v0' -include 'deprecated:fabric-networking-v0' +include 'deprecated:fabric-models-v0' include 'deprecated:fabric-renderer-registries-v1' +include 'deprecated:fabric-rendering-data-attachment-v1' include 'deprecated:fabric-rendering-v0' -include 'deprecated:fabric-loot-tables-v1' diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 77274002d1..08aeb5ae21 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -19,9 +19,9 @@ "FabricMC" ], "depends": { - "fabricloader": ">=0.14.21", + "fabricloader": ">=0.14.22", "java": ">=17", - "minecraft": ">=1.20 <1.20.2-" + "minecraft": ">=1.20.2- <1.20.3-" }, "description": "Core API module providing key hooks and intercompatibility features." }