From 8abc37aba1c68edf4013ff1be6249adb011c0392 Mon Sep 17 00:00:00 2001 From: teddy Date: Wed, 19 Aug 2026 13:52:36 -0300 Subject: [PATCH 01/15] fabric: migrate config registration to public forgeconfigapiport-fabric v5 API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Upstream's e67a435e wired up forgeconfigapiport-fabric:26.1.5 but called through fuzs.forgeconfigapiport.fabric.impl.core.ConfigRegistryImpl, an internal implementation package. The jar also ships the intended public API, fuzs.forgeconfigapiport.fabric.api.v5.ConfigRegistry, with an identical register(String, ModConfig.Type, IConfigSpec) signature (verified via javap against the 26.1.5 jar) — switch both DynamicTreesFabric and DynamicTreesFabricClient to depend on that instead. Also drops a stale, no-longer-resolvable fuzs.forgeconfigapiport.fabric.api.neoforge.v4.* import left over from the pre-migration code in DynamicTreesFabricClient.java, which was itself causing a real "package does not exist" compile error. --- .../java/com/dtteam/dynamictrees/DynamicTreesFabric.java | 6 +++--- .../com/dtteam/dynamictrees/DynamicTreesFabricClient.java | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabric.java b/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabric.java index 2c5df41c7..5ce3bea74 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabric.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabric.java @@ -6,7 +6,7 @@ import com.dtteam.dynamictrees.platform.*; import com.dtteam.dynamictrees.registry.*; import com.dtteam.dynamictrees.worldgen.*; -import fuzs.forgeconfigapiport.fabric.impl.core.*; +import fuzs.forgeconfigapiport.fabric.api.v5.ConfigRegistry; import net.fabricmc.api.*; import net.fabricmc.fabric.api.event.lifecycle.v1.*; import net.fabricmc.loader.api.*; @@ -18,8 +18,8 @@ public class DynamicTreesFabric implements ModInitializer { @Override public void onInitialize() { - ConfigRegistryImpl.INSTANCE.register(DynamicTrees.MOD_ID,ModConfig.Type.SERVER, DTConfigs.SERVER_CONFIG); - ConfigRegistryImpl.INSTANCE.register(DynamicTrees.MOD_ID,ModConfig.Type.COMMON, DTConfigs.COMMON_CONFIG); + ConfigRegistry.INSTANCE.register(DynamicTrees.MOD_ID,ModConfig.Type.SERVER, DTConfigs.SERVER_CONFIG); + ConfigRegistry.INSTANCE.register(DynamicTrees.MOD_ID,ModConfig.Type.COMMON, DTConfigs.COMMON_CONFIG); FabricRegistryHandler.setup(DynamicTrees.MOD_ID); diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabricClient.java b/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabricClient.java index d9f1a0554..af24cc516 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabricClient.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabricClient.java @@ -16,8 +16,7 @@ import com.dtteam.dynamictrees.tree.*; import com.dtteam.dynamictrees.tree.family.*; import com.dtteam.dynamictrees.tree.species.*; -import fuzs.forgeconfigapiport.fabric.api.neoforge.v4.*; -import fuzs.forgeconfigapiport.fabric.impl.core.*; +import fuzs.forgeconfigapiport.fabric.api.v5.ConfigRegistry; import net.fabricmc.api.*; import net.fabricmc.fabric.api.blockrenderlayer.v1.*; import net.fabricmc.fabric.api.client.event.lifecycle.v1.*; @@ -47,7 +46,7 @@ public class DynamicTreesFabricClient implements ClientModInitializer { @Override public void onInitializeClient() { - ConfigRegistryImpl.INSTANCE.register(DynamicTrees.MOD_ID, ModConfig.Type.CLIENT, DTConfigs.CLIENT_CONFIG); + ConfigRegistry.INSTANCE.register(DynamicTrees.MOD_ID, ModConfig.Type.CLIENT, DTConfigs.CLIENT_CONFIG); AtlasSourceTypeRegistryImpl.register(ThickBranchRingsSource.ID, ThickBranchRingsSource.setType(ThickBranchRingsSource.CODEC)); registerModelLoaders(); registerEntityRenderers(); From b2e853686da9adde479ee6f78acf4ea084f0a1ad Mon Sep 17 00:00:00 2001 From: teddy Date: Wed, 19 Aug 2026 13:52:36 -0300 Subject: [PATCH 02/15] fabric: temporarily disable javac's 100-error display cap Needed to see the full error count while working through the Fabric port plan instead of only the first 100. Per the plan, remove before the final PR (or keep, see Task 7). --- fabric/build.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fabric/build.gradle b/fabric/build.gradle index 648ba5cc3..1cb39e620 100644 --- a/fabric/build.gradle +++ b/fabric/build.gradle @@ -33,6 +33,10 @@ loom { } } +tasks.withType(JavaCompile).configureEach { + options.compilerArgs << '-Xmaxerrs' << '100000' +} + // Implement mcgradleconventions loader attribute def loaderAttribute = Attribute.of('io.github.mcgradleconventions.loader', String) ['apiElements', 'runtimeElements', 'sourcesElements', 'javadocElements', 'includeInternal', 'modCompileClasspath'].each { variant -> From 970780f2bbceb8cfafff6bb58c2ddae15c38907e Mon Sep 17 00:00:00 2001 From: teddy Date: Wed, 19 Aug 2026 13:58:26 -0300 Subject: [PATCH 03/15] fabric: eager block/item registration, real entity data serializer registration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Applies upstream PR #1208 verbatim (verified: applies cleanly against the current tree, and FabricEntityDataRegistry is confirmed present in the pinned fabric-api 0.145.3+26.1.1 via the fabric-object-builder-api-v1 submodule, and RegistryLoader.registerEntityDataSerializer's signature matches). - registerBlock/registerItem previously returned a Supplier that lazily called Registry.register(...) on first .get() — entries nobody ever polls during init (e.g. DIRT_BUCKET) never actually registered, breaking recipes referencing them. Now registers eagerly and returns a supplier over the already-registered instance. - Adds registerEntityDataSerializer, previously unimplemented (falling back to vanilla's EntityDataSerializers.registerSerializer, which Fabric API forbids at runtime for desync-safety reasons), via FabricEntityDataRegistry.register(...). Also removes the unused net.fabricmc.fabric.api.itemgroup.v1.FabricItemGroup import — that package doesn't exist in fabric-api 0.145.3+26.1.1, but registerCreativeTab already builds a plain vanilla CreativeModeTab.builder directly and never referenced FabricItemGroup, so this was dead code causing a live "package does not exist" compile error, not a real migration. --- .../registry/FabricRegistryLoader.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/registry/FabricRegistryLoader.java b/fabric/src/main/java/com/dtteam/dynamictrees/registry/FabricRegistryLoader.java index c659f55d3..9664a4c43 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/registry/FabricRegistryLoader.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/registry/FabricRegistryLoader.java @@ -4,13 +4,14 @@ import com.dtteam.dynamictrees.recipe.DendroPotionRecipeHandler; import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.serialization.MapCodec; -import net.fabricmc.fabric.api.itemgroup.v1.FabricItemGroup; +import net.fabricmc.fabric.api.object.builder.v1.entity.FabricEntityDataRegistry; import net.minecraft.commands.synchronization.ArgumentTypeInfo; import net.minecraft.commands.synchronization.ArgumentTypeInfos; import net.minecraft.core.Registry; import net.minecraft.core.component.DataComponentType; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.syncher.EntityDataSerializer; import net.minecraft.resources.Identifier; import net.minecraft.sounds.SoundEvent; import net.minecraft.world.entity.Entity; @@ -51,13 +52,15 @@ public static void setup (){ @Override public Supplier registerBlock(String name, Function newBlock) { Identifier id = DynamicTrees.location(name); - return ()-> Registry.register(BuiltInRegistries.BLOCK, id, newBlock.apply(id)); + T block = Registry.register(BuiltInRegistries.BLOCK, id, newBlock.apply(id)); + return ()-> block; } @Override public Supplier registerItem(String name, Function newBlock) { Identifier id = DynamicTrees.location(name); - return ()-> Registry.register(BuiltInRegistries.ITEM, id, newBlock.apply(id)); + T item = Registry.register(BuiltInRegistries.ITEM, id, newBlock.apply(id)); + return ()-> item; } @Override @@ -100,6 +103,15 @@ public Supplier> registerDataComponentType(String name, return ()-> type; } + @Override + public Supplier> registerEntityDataSerializer(String name, Supplier> operator) { + EntityDataSerializer serializer = operator.get(); + // Fabric API forbids vanilla's EntityDataSerializers.registerSerializer at runtime + // ("use FabricEntityDataRegistry.register instead"), so register through its API. + FabricEntityDataRegistry.register(DynamicTrees.location(name), serializer); + return ()-> serializer; + } + @Override public , T extends ArgumentTypeInfo.Template, I extends ArgumentTypeInfo> Supplier registerCommandArgumentType(String name, Class infoClass, I argumentTypeInfo) { ArgumentTypeInfos.BY_CLASS.put(infoClass, argumentTypeInfo); From 2ecf8b60c7236b107026691f3666454ee4bf91da Mon Sep 17 00:00:00 2001 From: teddy Date: Wed, 19 Aug 2026 14:35:19 -0300 Subject: [PATCH 04/15] fabric: add Jade, Continuity, SereneSeasons (+ glitchcore) as compileOnly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Existing compat classes (compat/waila/*.java, compat/SereneSeasonsSeasonProvider.java, compat/continuity/*.java) import snownee.jade.*, sereneseasons.*, and me.pepperbell.continuity.* respectively, but fabric/build.gradle declared none of these — unlike neoforge/build.gradle, which already pulls in Jade and SereneSeasons (from CurseForge/Modrinth respectively). Versions picked by querying the Modrinth API for releases declaring compatibility with game version 26.1.2 and loader fabric: - jade:26.1.9+fabric (latest matching release) - continuity:3.0.1-beta.2+26.1 (only fabric/quilt releases available for this game version; no separate loader-specific pin needed) - serene-seasons:26.1.2.0.2, matching the exact version string neoforge/build.gradle already pins, for cross-loader consistency (a newer 26.1.2.0.4 exists but wasn't picked, to avoid unrelated version drift between loaders) Confirmed all three are compileOnly-appropriate: SereneSeasons's compat path is gated behind Services.PLATFORM.isModLoaded(...) before the sereneseasons-importing class is ever touched; Continuity's is gated the same way; Jade's only entry point (WailaCompat.java) is fully commented out already (dormant), so its handler classes only need to compile, not run. Also adds glitchcore as compileOnly — SereneSeasonsSeasonProvider.java references glitchcore.config.Config directly (a transitive dependency of SereneSeasons at compile time, same as neoforge/build.gradle's curse.maven:glitchcore-955399:8109770), which doesn't resolve merely by adding serene-seasons itself. Result: 508 -> 452 compile errors. Remaining errors in these files are real API-shape mismatches (Jade's IElement/ElementHelper, SereneSeasons's warmEnoughToRain signature, Continuity's dependency on the vanilla BakedModel rendering-API rewrite) — out of scope for this task, which only needed the packages to resolve on the classpath. --- fabric/build.gradle | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/fabric/build.gradle b/fabric/build.gradle index 1cb39e620..d8ad7f5df 100644 --- a/fabric/build.gradle +++ b/fabric/build.gradle @@ -5,11 +5,28 @@ plugins { //mod_version = "${mod_version}${fabricVersionAppend}" version = mod_version +repositories { + exclusiveContent { + forRepository { + maven { + name = 'Modrinth' + url = 'https://api.modrinth.com/maven' + } + } + filter { includeGroup 'maven.modrinth' } + } +} + dependencies { minecraft "com.mojang:minecraft:${minecraft_version}" implementation "net.fabricmc:fabric-loader:${fabric_loader_version}" implementation "net.fabricmc.fabric-api:fabric-api:${fabric_version}" implementation "fuzs.forgeconfigapiport:forgeconfigapiport-fabric:26.1.5" + + compileOnly "maven.modrinth:jade:26.1.9+fabric" + compileOnly "maven.modrinth:continuity:3.0.1-beta.2+26.1" + compileOnly "maven.modrinth:serene-seasons:26.1.2.0.2" + compileOnly "maven.modrinth:glitchcore:26.1.2.0.2" } loom { From 40f75830ff3bbe9573af6ccd94a5000436fad90e Mon Sep 17 00:00:00 2001 From: teddy Date: Wed, 19 Aug 2026 14:45:41 -0300 Subject: [PATCH 05/15] fabric: rewrite custom block rendering onto the new FabricBlockStateModel API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ports upstream PR #1209's fabric/model/ rewrite (targets Minecraft 26.2) back onto this project's pinned 26.1.2 toolchain, per the plan's Task 5. Every referenced Fabric API class (FabricBlockStateModel, FabricBlockStateModelPart, QuadEmitter, CustomUnbakedBlockStateModel, WrapperBlockStateModel) and vanilla class (BlockStateModel, BlockStateModelPart, ModelState, ModelBaker, ResolvableModel, ResolvedModel, SimpleModelWrapper, Material, TextureSlots, BlockAndTintGetter, BakedQuad) was individually confirmed present at the exact same package path in this project's pinned fabric-api (0.145.3+26.1.1) and Minecraft (26.1.2) jars before porting any code, so despite the reference PR targeting a different Minecraft version, its model/ files needed no adaptation beyond what's described below. Restructures model/baked/ (old BakedModel-based approach) into a mix of updated model/baked/ (still used for procedural fallback baking) and a new model/blockstate/ package (codec-based blockstate JSON deserialization via CustomUnbakedBlockStateModel), mirroring NeoForge's existing BlockStateModel-based architecture. BakedModelBlockPottedSapling.java and BranchBlockUnbakedModel.java are deleted, superseded by blockstate/PottedSaplingBlockStateModel.java and blockstate/UnbakedBranchModel.java respectively. All actual geometry/quad-generation logic (branch rings, thick-branch trunks, surface roots, aerial roots) is unchanged — it already lived in loader-agnostic common/ helper classes (BranchMultiPartHolder, ModelHelper, ModelConnections, model/parts/*) used by NeoForge's already-working implementation; every method these ported files call against those helpers was individually confirmed present with a matching signature before porting. Only the Fabric-side API adapter code changes. common/ additions (needed by the above, not previously present): - New DynamicModelRegistry: records DT's own baked block state models as they're baked, so FallingTreeEntityModel's falling-tree geometry can find DT's real model even when another mod (e.g. Continuity) has wrapped/replaced what the model manager hands back for a state - without this, a wrapped model would silently fail DT's BlockStateModelWithConnectionData/BlockStateModelWithRadius instanceof checks and render the falling tree's trunk invisible. - FallingTreeEntityModel's three modelSet.get(state) call sites now go through DynamicModelRegistry.getOrFallback(state, modelSet) instead. Verified :neoforge:compileJava still succeeds after this shared-code change (no regression on the already-working loader). Also fixes compat/continuity/WrappedModelHandler.java and ContinuityWrappedModelHandler.java for the same rendering-API rewrite, found during Task 4 and folded into this task's scope: BakedModel -> BlockStateModel, and CtmBakedModel -> CtmBlockStateModel (Continuity's own class renamed to match). ContinuityWrappedModelHandler now reaches Continuity's wrapped model via a VarHandle into Fabric API's WrapperBlockStateModel#wrapped (protected, no public accessor) - verified via javap that the field exists with the expected name/type in the pinned fabric-api jar. This compat path has no live caller yet (WailaCompat-style dormant code), so this is a compile fix only, not yet exercised at runtime. Deliberately NOT ported: PR #1209 also fixes an unrelated alpha-channel bug in FallingTreeEntityModel#renderToBuffer's color packing (`0xFF000000 | ...`). Left out to keep this commit scoped to the API migration; worth a follow-up look separately. Result: 452 -> 102 compile errors. Confirmed zero errors remain in any file this task touches (model/baked, model/blockstate, DTModelLoadingPlugin, FabricDynamicBlockStateModel, FallingTreeEntityModelFabric, compat/continuity). --- .../model/DynamicModelRegistry.java | 66 +++ .../model/entity/FallingTreeEntityModel.java | 7 +- .../ContinuityWrappedModelHandler.java | 29 +- .../continuity/WrappedModelHandler.java | 11 +- .../model/BakedModelBlockPottedSapling.java | 30 -- .../model/BranchBlockUnbakedModel.java | 59 --- .../model/DTModelLoadingPlugin.java | 352 ++++++++------- .../model/FabricDynamicBlockStateModel.java | 43 ++ .../model/FallingTreeEntityModelFabric.java | 176 +------- .../baked/BasicBranchBlockBakedModel.java | 405 ++++++------------ .../baked/BasicRootsBlockBakedModel.java | 150 ++----- .../baked/SurfaceRootBlockBakedModel.java | 395 +++++------------ .../baked/ThickBranchBlockBakedModel.java | 242 +++-------- .../AerialRootsSoilBlockStateModel.java | 117 +++++ .../PottedSaplingBlockStateModel.java | 138 ++++++ .../model/blockstate/UnbakedBranchModel.java | 79 ++++ .../blockstate/UnbakedCreakingHeartModel.java | 62 +++ .../model/blockstate/UnbakedRootsModel.java | 54 +++ .../blockstate/UnbakedRootsMossModel.java | 51 +++ .../blockstate/UnbakedSurfaceRootModel.java | 43 ++ 20 files changed, 1182 insertions(+), 1327 deletions(-) create mode 100644 common/src/main/java/com/dtteam/dynamictrees/model/DynamicModelRegistry.java delete mode 100644 fabric/src/main/java/com/dtteam/dynamictrees/model/BakedModelBlockPottedSapling.java delete mode 100644 fabric/src/main/java/com/dtteam/dynamictrees/model/BranchBlockUnbakedModel.java create mode 100644 fabric/src/main/java/com/dtteam/dynamictrees/model/FabricDynamicBlockStateModel.java create mode 100644 fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/AerialRootsSoilBlockStateModel.java create mode 100644 fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/PottedSaplingBlockStateModel.java create mode 100644 fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/UnbakedBranchModel.java create mode 100644 fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/UnbakedCreakingHeartModel.java create mode 100644 fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/UnbakedRootsModel.java create mode 100644 fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/UnbakedRootsMossModel.java create mode 100644 fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/UnbakedSurfaceRootModel.java diff --git a/common/src/main/java/com/dtteam/dynamictrees/model/DynamicModelRegistry.java b/common/src/main/java/com/dtteam/dynamictrees/model/DynamicModelRegistry.java new file mode 100644 index 000000000..9ddbdc9a1 --- /dev/null +++ b/common/src/main/java/com/dtteam/dynamictrees/model/DynamicModelRegistry.java @@ -0,0 +1,66 @@ +package com.dtteam.dynamictrees.model; + +import net.minecraft.client.renderer.block.BlockStateModelSet; +import net.minecraft.client.renderer.block.dispatch.BlockStateModel; +import net.minecraft.world.level.block.state.BlockState; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Direct handle on DT's own dynamic block state models, bypassing the model manager. + * + *

DT's falling-tree geometry is built by asking a model for its parts through + * {@link BlockStateModelWithConnectionData} / {@link BlockStateModelWithRadius}. Those are DT-specific + * interfaces, so the lookup only works if the object handed back is genuinely DT's model. + * + *

Other mods are free to replace or decorate baked models — Continuity, for instance, wraps them in + * its own emissive/connected-texture model. Such a wrapper implements neither DT interface, so a plain + * {@code modelSet.get(state)} would silently fall through to + * {@code BlockStateModel#collectParts(RandomSource, List)}, which for a dynamic model is a no-op: the + * tree would fall with its trunk invisible while the leaves (ordinary models) still render. + * + *

So DT records its own models here as they are baked and consults this registry first. Wrappers keep + * working for normal in-world rendering — where they are applied through the level-aware path and are + * exactly what we want — while DT's own geometry passes stay on the real model. + */ +public final class DynamicModelRegistry { + + private static final Map MODELS = new ConcurrentHashMap<>(); + + private DynamicModelRegistry() { + } + + /** + * Drops every recorded model. Must be called at the start of each model bake, before + * {@link #register} runs for the new set. + */ + public static void clear() { + MODELS.clear(); + } + + /** + * Records DT's model for the given state. Called by the platform's model loading hook. + */ + public static void register(BlockState state, BlockStateModel model) { + MODELS.put(state, model); + } + + /** + * @return DT's own model for {@code state}, or {@code null} if DT did not supply one. + */ + @Nullable + public static BlockStateModel get(BlockState state) { + return MODELS.get(state); + } + + /** + * DT's own model for {@code state} if there is one, otherwise whatever the model manager holds + * (which may be another mod's wrapper). Use this anywhere DT needs to interrogate its own geometry. + */ + public static BlockStateModel getOrFallback(BlockState state, BlockStateModelSet modelSet) { + final BlockStateModel own = MODELS.get(state); + return own != null ? own : modelSet.get(state); + } +} diff --git a/common/src/main/java/com/dtteam/dynamictrees/model/entity/FallingTreeEntityModel.java b/common/src/main/java/com/dtteam/dynamictrees/model/entity/FallingTreeEntityModel.java index 483f5e34d..ceb696993 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/model/entity/FallingTreeEntityModel.java +++ b/common/src/main/java/com/dtteam/dynamictrees/model/entity/FallingTreeEntityModel.java @@ -5,6 +5,7 @@ import com.dtteam.dynamictrees.block.soil.SoilBlock; import com.dtteam.dynamictrees.client.TintSourceHelper; import com.dtteam.dynamictrees.entity.FallingTreeEntity; +import com.dtteam.dynamictrees.model.DynamicModelRegistry; import com.dtteam.dynamictrees.model.ModelConnections; import com.dtteam.dynamictrees.model.QuadManipulator; import com.dtteam.dynamictrees.model.entity.render.FallingTreeRenderState; @@ -76,7 +77,7 @@ public List generateTreeQuads(FallingTreeEntity entity) { BlockState soilState = destructionData.soilState; if (TreeHelper.isRooty(soilState)) { SoilBlock soilBlock = TreeHelper.getRooty(soilState); - BlockStateModel rootyModel = modelSet.get(soilState); + BlockStateModel rootyModel = DynamicModelRegistry.getOrFallback(soilState, modelSet); BlockPos cutOffset = destructionData.getRelativeCutPos(); treeQuads.addAll(toTreeQuadData(QuadManipulator.getQuads(rootyModel, soilState, new Vec3(cutOffset.getX(), cutOffset.getY()-1, cutOffset.getZ()), entity.getRandom(), null), species.getFamily().getRootColor(soilState, soilBlock != null && soilBlock.getColorFromBark()), @@ -86,7 +87,7 @@ public List generateTreeQuads(FallingTreeEntity entity) { } - BlockStateModel branchModel = modelSet.get(exState); + BlockStateModel branchModel = DynamicModelRegistry.getOrFallback(exState, modelSet); //Draw the ring texture cap on the cut block if the rings connection is above 0 destructionData.getConnections(0, connectionArray); boolean bottomRingsAdded = false; @@ -106,7 +107,7 @@ public List generateTreeQuads(FallingTreeEntity entity) { if (exState == null) continue; if (!previousBranch.equals(exState.getBlock())) //Update the branch model only if the block is different { - branchModel = modelSet.get(exState); + branchModel = DynamicModelRegistry.getOrFallback(exState, modelSet); } BlockPos relPos = destructionData.getBranchRelPos(index); destructionData.getConnections(index, connectionArray); diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/compat/continuity/ContinuityWrappedModelHandler.java b/fabric/src/main/java/com/dtteam/dynamictrees/compat/continuity/ContinuityWrappedModelHandler.java index d357eeba8..47310c4b1 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/compat/continuity/ContinuityWrappedModelHandler.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/compat/continuity/ContinuityWrappedModelHandler.java @@ -1,16 +1,35 @@ package com.dtteam.dynamictrees.compat.continuity; import com.dtteam.dynamictrees.model.baked.BasicBranchBlockBakedModel; -import me.pepperbell.continuity.client.model.CtmBakedModel; -import net.minecraft.client.resources.model.BakedModel; +import me.pepperbell.continuity.client.model.CtmBlockStateModel; +import net.fabricmc.fabric.api.client.model.loading.v1.wrapper.WrapperBlockStateModel; +import net.minecraft.client.renderer.block.dispatch.BlockStateModel; import org.jetbrains.annotations.Nullable; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; + public class ContinuityWrappedModelHandler extends WrappedModelHandler { + // Continuity's CtmBlockStateModel extends Fabric's WrapperBlockStateModel, whose + // wrapped model field is protected with no public accessor. + @Nullable + private static final VarHandle WRAPPED_FIELD = findWrappedField(); + + @Nullable + private static VarHandle findWrappedField() { + try { + return MethodHandles.privateLookupIn(WrapperBlockStateModel.class, MethodHandles.lookup()) + .findVarHandle(WrapperBlockStateModel.class, "wrapped", BlockStateModel.class); + } catch (ReflectiveOperationException e) { + return null; + } + } + @Override - public @Nullable BasicBranchBlockBakedModel unwrapBranchModel(BakedModel model) { - if (model instanceof CtmBakedModel ctmModel){ - return super.unwrapBranchModel(ctmModel.getWrappedModel()); + public @Nullable BasicBranchBlockBakedModel unwrapBranchModel(BlockStateModel model) { + if (model instanceof CtmBlockStateModel ctmModel && WRAPPED_FIELD != null) { + return super.unwrapBranchModel((BlockStateModel) WRAPPED_FIELD.get(ctmModel)); } return super.unwrapBranchModel(model); } diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/compat/continuity/WrappedModelHandler.java b/fabric/src/main/java/com/dtteam/dynamictrees/compat/continuity/WrappedModelHandler.java index 36ac0d73d..0ec9d895c 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/compat/continuity/WrappedModelHandler.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/compat/continuity/WrappedModelHandler.java @@ -2,16 +2,16 @@ import com.dtteam.dynamictrees.model.baked.BasicBranchBlockBakedModel; import com.dtteam.dynamictrees.platform.Services; -import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.client.renderer.block.dispatch.BlockStateModel; import org.jetbrains.annotations.Nullable; public abstract class WrappedModelHandler { private static WrappedModelHandler INSTANCE = null; - public static WrappedModelHandler getInstance(){ + public static WrappedModelHandler getInstance() { if (INSTANCE == null) { - if (Services.PLATFORM.isModLoaded("continuity")){ + if (Services.PLATFORM.isModLoaded("continuity")) { INSTANCE = new ContinuityWrappedModelHandler(); } else { INSTANCE = new WrappedModelHandler() {}; @@ -21,9 +21,10 @@ public static WrappedModelHandler getInstance(){ } @Nullable - public BasicBranchBlockBakedModel unwrapBranchModel(BakedModel model){ - if (model instanceof BasicBranchBlockBakedModel branchModel) + public BasicBranchBlockBakedModel unwrapBranchModel(BlockStateModel model) { + if (model instanceof BasicBranchBlockBakedModel branchModel) { return branchModel; + } return null; } diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/BakedModelBlockPottedSapling.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/BakedModelBlockPottedSapling.java deleted file mode 100644 index 38e47de31..000000000 --- a/fabric/src/main/java/com/dtteam/dynamictrees/model/BakedModelBlockPottedSapling.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.dtteam.dynamictrees.model; - -import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel; -import net.minecraft.client.renderer.block.model.BakedQuad; -import net.minecraft.client.resources.model.BakedModel; -import net.minecraft.core.Direction; -import net.minecraft.util.RandomSource; -import net.minecraft.world.level.block.state.BlockState; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.List; - -public class BakedModelBlockPottedSapling extends ForwardingBakedModel { - - public BakedModelBlockPottedSapling(BakedModel basePotModel) { - this.wrapped = basePotModel; - } - - @Override - public boolean isVanillaAdapter() { - return true; - } - - @Override - @NotNull - public List getQuads(BlockState state, Direction face, RandomSource random) { - return new ArrayList<>(wrapped.getQuads(state, face, random)); - } -} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/BranchBlockUnbakedModel.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/BranchBlockUnbakedModel.java deleted file mode 100644 index 5217b2522..000000000 --- a/fabric/src/main/java/com/dtteam/dynamictrees/model/BranchBlockUnbakedModel.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.dtteam.dynamictrees.model; - -import com.dtteam.dynamictrees.model.baked.BasicBranchBlockBakedModel; -import com.dtteam.dynamictrees.model.baked.ThickBranchBlockBakedModel; -import com.dtteam.dynamictrees.tree.family.Family; -import net.minecraft.client.renderer.texture.TextureAtlasSprite; -import net.minecraft.client.resources.model.BakedModel; -import net.minecraft.client.resources.model.Material; -import net.minecraft.client.resources.model.ModelBaker; -import net.minecraft.client.resources.model.ModelState; -import net.minecraft.client.resources.model.UnbakedModel; -import net.minecraft.resources.Identifier; -import net.minecraft.world.inventory.InventoryMenu; -import org.jetbrains.annotations.Nullable; - -import java.util.Collection; -import java.util.Collections; -import java.util.function.Function; - -public class BranchBlockUnbakedModel implements UnbakedModel { - - protected final Identifier barkTextureLocation; - protected final Identifier ringsTextureLocation; - protected final Identifier familyName; - protected final boolean forceThickness; - - public BranchBlockUnbakedModel(Identifier barkTextureLocation, Identifier ringsTextureLocation, @Nullable Identifier familyName, boolean forceThickness) { - this.barkTextureLocation = barkTextureLocation; - this.ringsTextureLocation = ringsTextureLocation; - this.familyName = familyName; - this.forceThickness = forceThickness; - } - - @Override - public Collection getDependencies() { - return Collections.emptyList(); - } - - @Override - public void resolveParents(Function resolver) { - } - - @Override - public BakedModel bake(ModelBaker baker, Function spriteGetter, ModelState state) { - TextureAtlasSprite barkSprite = spriteGetter.apply(new Material(InventoryMenu.BLOCK_ATLAS, barkTextureLocation)); - TextureAtlasSprite ringsSprite = spriteGetter.apply(new Material(InventoryMenu.BLOCK_ATLAS, ringsTextureLocation)); - - Family family = familyName != null ? Family.REGISTRY.get(familyName) : null; - boolean useThickModel = forceThickness || (family != null && family.isThick()); - - if (useThickModel) { - Identifier thickRingsLocation = ringsTextureLocation.withSuffix("_thick"); - TextureAtlasSprite thickRingsSprite = spriteGetter.apply(new Material(InventoryMenu.BLOCK_ATLAS, thickRingsLocation)); - return new ThickBranchBlockBakedModel(barkSprite, ringsSprite, thickRingsSprite); - } - - return new BasicBranchBlockBakedModel(barkSprite, ringsSprite); - } -} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/DTModelLoadingPlugin.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/DTModelLoadingPlugin.java index ec4c14306..1513a0ecc 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/model/DTModelLoadingPlugin.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/DTModelLoadingPlugin.java @@ -8,239 +8,227 @@ import com.dtteam.dynamictrees.model.baked.BasicRootsBlockBakedModel; import com.dtteam.dynamictrees.model.baked.SurfaceRootBlockBakedModel; import com.dtteam.dynamictrees.model.baked.ThickBranchBlockBakedModel; -import com.dtteam.dynamictrees.tree.family.Family; +import com.dtteam.dynamictrees.model.blockstate.AerialRootsSoilBlockStateModel; +import com.dtteam.dynamictrees.model.blockstate.PottedSaplingBlockStateModel; +import com.dtteam.dynamictrees.model.blockstate.UnbakedBranchModel; +import com.dtteam.dynamictrees.model.blockstate.UnbakedCreakingHeartModel; +import com.dtteam.dynamictrees.model.blockstate.UnbakedRootsMossModel; +import com.dtteam.dynamictrees.model.blockstate.UnbakedRootsModel; +import com.dtteam.dynamictrees.model.blockstate.UnbakedSurfaceRootModel; +import com.dtteam.dynamictrees.model.parts.BranchModelPart; import com.dtteam.dynamictrees.tree.family.AerialRootsFamily; +import com.dtteam.dynamictrees.tree.family.Family; +import com.dtteam.dynamictrees.utility.IdentifierUtils; +import net.fabricmc.fabric.api.client.model.loading.v1.CustomUnbakedBlockStateModel; import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin; import net.fabricmc.fabric.api.client.model.loading.v1.ModelModifier; -import net.minecraft.client.renderer.texture.TextureAtlasSprite; -import net.minecraft.client.resources.model.BakedModel; -import net.minecraft.client.resources.model.Material; -import net.minecraft.client.resources.model.ModelIdentifier; +import net.minecraft.client.renderer.block.dispatch.BlockStateModel; +import net.minecraft.client.resources.model.ModelBaker; +import net.minecraft.client.resources.model.sprite.Material; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.Identifier; -import net.minecraft.world.inventory.InventoryMenu; import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import org.jetbrains.annotations.Nullable; -import java.util.HashMap; import java.util.Map; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Function; - +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Model loading hooks for DT's dynamic models on Fabric. + * + *

The primary path is codec-based: blockstate JSONs using DT's custom model types + * ({@code dynamictrees:branch}, {@code dynamictrees:roots}, ...) are deserialized through the + * codecs registered in {@link #registerModelTypes()}, exactly mirroring NeoForge's + * {@code RegisterBlockStateModels} registrations. Note that Fabric dispatches these on the + * {@code "fabric:type"} key (NeoForge uses {@code "type"}), so DT's generated blockstates carry + * both keys. + * + *

The secondary path is an after-bake fallback for dynamic blocks whose blockstate definitions + * are missing or unparseable (e.g. add-on tree packs still shipping pre-26.2 assets): any branch, + * surface root or underground roots block state that did not resolve to a DT model gets a + * procedurally built model based on its family's primitive log textures. + */ public class DTModelLoadingPlugin implements ModelLoadingPlugin { - public static final Identifier POTTED_SAPLING_MODEL = DynamicTrees.location("potted_sapling"); - private static final Map BRANCH_MODEL_CACHE = new HashMap<>(); - private static final Map ROOT_MODEL_CACHE = new HashMap<>(); - private static final Map UNDERGROUND_ROOTS_MODEL_CACHE = new HashMap<>(); - private static boolean modelsInitialized = false; + public static final Identifier BRANCH = DynamicTrees.location("branch"); + public static final Identifier SURFACE_ROOT = DynamicTrees.location("surface_root"); + public static final Identifier ROOTS = DynamicTrees.location("roots"); + public static final Identifier CREAKING_HEART = DynamicTrees.location("creaking_heart"); + public static final Identifier POTTED_DYNAMIC_SAPLING = DynamicTrees.location("potted_dynamic_sapling"); + public static final Identifier AERIAL_ROOTS_SOIL = DynamicTrees.location("aerial_roots_soil"); + public static final Identifier ROOTS_MOSS = DynamicTrees.location("roots_moss"); + + private static final Map FALLBACK_MODEL_CACHE = new ConcurrentHashMap<>(); + + /** + * Registers the custom blockstate model codecs. Must only be called once. + */ + public static void registerModelTypes() { + CustomUnbakedBlockStateModel.register(BRANCH, UnbakedBranchModel.CODEC); + CustomUnbakedBlockStateModel.register(ROOTS, UnbakedRootsModel.CODEC); + CustomUnbakedBlockStateModel.register(CREAKING_HEART, UnbakedCreakingHeartModel.CODEC); + CustomUnbakedBlockStateModel.register(SURFACE_ROOT, UnbakedSurfaceRootModel.CODEC); + CustomUnbakedBlockStateModel.register(POTTED_DYNAMIC_SAPLING, PottedSaplingBlockStateModel.Unbaked.CODEC); + CustomUnbakedBlockStateModel.register(AERIAL_ROOTS_SOIL, AerialRootsSoilBlockStateModel.Unbaked.CODEC); + CustomUnbakedBlockStateModel.register(ROOTS_MOSS, UnbakedRootsMossModel.CODEC); + } @Override - public void onInitializeModelLoader(Context pluginContext) { - modelsInitialized = false; - BRANCH_MODEL_CACHE.clear(); - ROOT_MODEL_CACHE.clear(); - UNDERGROUND_ROOTS_MODEL_CACHE.clear(); - pluginContext.modifyModelAfterBake().register(ModelModifier.WRAP_PHASE, this::modifyModelAfterBake); + public void initialize(Context pluginContext) { + FALLBACK_MODEL_CACHE.clear(); + DynamicModelRegistry.clear(); + pluginContext.modifyBlockModelAfterBake().register(ModelModifier.WRAP_PHASE, DTModelLoadingPlugin::modifyModelAfterBake); } - private void initBranchModels(Function spriteGetter) { - if (modelsInitialized) return; - modelsInitialized = true; - - for (Family family : Family.REGISTRY.getAll()) { - if (!family.isValid()) continue; + private static BlockStateModel modifyModelAfterBake(BlockStateModel model, ModelModifier.AfterBakeBlock.Context context) { + return record(context.state(), resolveModel(model, context)); + } - family.getPrimitiveLog().ifPresent(primitiveLog -> { - Identifier primitiveLogId = BuiltInRegistries.BLOCK.getKey(primitiveLog); - Identifier barkTexture = Identifier.fromNamespaceAndPath(primitiveLogId.getNamespace(), "block/" + primitiveLogId.getPath()); - Identifier ringsTexture = barkTexture.withSuffix("_top"); + /** + * Remembers DT's own models so DT can reach them even if another mod later decorates or replaces + * what the model manager hands out. See {@link DynamicModelRegistry}. + */ + private static BlockStateModel record(BlockState state, BlockStateModel model) { + if (model instanceof FabricDynamicBlockStateModel || model instanceof PottedSaplingBlockStateModel) { + DynamicModelRegistry.register(state, model); + } + return model; + } - AtomicReference barkRef = new AtomicReference<>(barkTexture); - AtomicReference ringsRef = new AtomicReference<>(ringsTexture); + private static BlockStateModel resolveModel(BlockStateModel model, ModelModifier.AfterBakeBlock.Context context) { + // Models already loaded through DT's codecs (or another DT path) are left untouched. + if (model instanceof FabricDynamicBlockStateModel || model instanceof PottedSaplingBlockStateModel) { + return model; + } - family.getTexturePath(Family.BRANCH).ifPresent(barkRef::set); - family.getTexturePath(Family.BRANCH_TOP).ifPresent(ringsRef::set); + BlockState state = context.state(); + Block block = state.getBlock(); - boolean isThick = family.isThick(); + if (block instanceof BasicRootsBlock rootsBlock) { + BlockStateModel rootsModel = getOrCreateRootsModel(rootsBlock, state, context.baker()); + return rootsModel != null ? rootsModel : model; + } - family.getBranch().ifPresent(branch -> { - Identifier blockId = BuiltInRegistries.BLOCK.getKey(branch); - BakedModel model = createBranchModel(barkRef.get(), ringsRef.get(), isThick, spriteGetter); - BRANCH_MODEL_CACHE.put(blockId, model); - }); - }); + if (block instanceof SurfaceRootBlock surfaceRootBlock) { + BlockStateModel rootModel = getOrCreateSurfaceRootModel(surfaceRootBlock, context.baker()); + return rootModel != null ? rootModel : model; + } - family.getPrimitiveStrippedLog().ifPresent(strippedLog -> { - Identifier strippedLogId = BuiltInRegistries.BLOCK.getKey(strippedLog); - Identifier strippedBarkTexture = Identifier.fromNamespaceAndPath(strippedLogId.getNamespace(), "block/" + strippedLogId.getPath()); - Identifier strippedRingsTexture = strippedBarkTexture.withSuffix("_top"); + if (block instanceof BranchBlock branchBlock) { + BlockStateModel branchModel = getOrCreateBranchModel(branchBlock, context.baker()); + return branchModel != null ? branchModel : model; + } - AtomicReference barkRef = new AtomicReference<>(strippedBarkTexture); - AtomicReference ringsRef = new AtomicReference<>(strippedRingsTexture); + return model; + } - family.getTexturePath(Family.STRIPPED_BRANCH).ifPresent(barkRef::set); - family.getTexturePath(Family.STRIPPED_BRANCH_TOP).ifPresent(ringsRef::set); + /////////////////////////////////////////// + // BRANCHES + /////////////////////////////////////////// - boolean isThick = family.isThick(); + @Nullable + private static BlockStateModel getOrCreateBranchModel(BranchBlock branchBlock, ModelBaker baker) { + Family family = branchBlock.getFamily(); + if (family == null || !family.isValid()) return null; - family.getStrippedBranch().ifPresent(strippedBranch -> { - Identifier blockId = BuiltInRegistries.BLOCK.getKey(strippedBranch); - BakedModel model = createBranchModel(barkRef.get(), ringsRef.get(), isThick, spriteGetter); - BRANCH_MODEL_CACHE.put(blockId, model); - }); - }); + Identifier blockId = BuiltInRegistries.BLOCK.getKey(branchBlock); - family.getSurfaceRoot().ifPresent(surfaceRoot -> { - family.getPrimitiveLog().ifPresent(primitiveLog -> { - Identifier primitiveLogId = BuiltInRegistries.BLOCK.getKey(primitiveLog); - Identifier barkTexture = Identifier.fromNamespaceAndPath(primitiveLogId.getNamespace(), "block/" + primitiveLogId.getPath()); - AtomicReference barkRef = new AtomicReference<>(barkTexture); - family.getTexturePath(Family.BRANCH).ifPresent(barkRef::set); + boolean stripped = family.getStrippedBranch().map(b -> b == branchBlock).orElse(false); + Optional primitiveLog = stripped ? family.getPrimitiveStrippedLog() : family.getPrimitiveLog(); + if (primitiveLog.isEmpty()) return null; - Identifier blockId = BuiltInRegistries.BLOCK.getKey(surfaceRoot); - BakedModel model = createRootModel(barkRef.get(), spriteGetter); - ROOT_MODEL_CACHE.put(blockId, model); - }); - }); + Identifier primitiveLogId = BuiltInRegistries.BLOCK.getKey(primitiveLog.get()); + Identifier barkTexture = Identifier.fromNamespaceAndPath(primitiveLogId.getNamespace(), "block/" + primitiveLogId.getPath()); + Identifier ringsTexture = IdentifierUtils.suffix(barkTexture, "_top"); - if (family instanceof AerialRootsFamily rootsFamily) { - rootsFamily.getRoots().ifPresent(roots -> { - Identifier blockId = BuiltInRegistries.BLOCK.getKey(roots); + Identifier barkOverride = family.getTexturePath(stripped ? Family.STRIPPED_BRANCH : Family.BRANCH).orElse(barkTexture); + Identifier ringsOverride = family.getTexturePath(stripped ? Family.STRIPPED_BRANCH_TOP : Family.BRANCH_TOP).orElse(ringsTexture); - rootsFamily.getPrimitiveRoots().ifPresent(primitiveRoots -> { - Identifier primitiveRootsId = BuiltInRegistries.BLOCK.getKey(primitiveRoots); - Identifier barkTexture = Identifier.fromNamespaceAndPath(primitiveRootsId.getNamespace(), "block/" + primitiveRootsId.getPath() + "_side"); - Identifier ringsTexture = Identifier.fromNamespaceAndPath(primitiveRootsId.getNamespace(), "block/" + primitiveRootsId.getPath() + "_top"); + boolean isThick = family.isThick(); - AtomicReference barkRef = new AtomicReference<>(barkTexture); - AtomicReference ringsRef = new AtomicReference<>(ringsTexture); + return FALLBACK_MODEL_CACHE.computeIfAbsent(blockId, id -> createBranchModel(barkOverride, ringsOverride, isThick, baker)); + } - family.getTexturePath(Family.ROOTS_SIDE).ifPresent(barkRef::set); - family.getTexturePath(Family.ROOTS_TOP).ifPresent(ringsRef::set); + private static BlockStateModel createBranchModel(Identifier barkTexture, Identifier ringsTexture, boolean isThick, ModelBaker baker) { + Material.Baked barkMat = bakeMaterial(baker, barkTexture); + Material.Baked ringsMat = bakeMaterial(baker, ringsTexture); - BakedModel model = createRootsBlockModel(barkRef.get(), ringsRef.get(), spriteGetter); - UNDERGROUND_ROOTS_MODEL_CACHE.put(blockId.withSuffix("_exposed"), model); - }); + BasicBranchBlockBakedModel regular = BasicBranchBlockBakedModel.bakeBasic(baker, + new BranchModelPart.UnbakedCore(barkMat), + new BranchModelPart.UnbakedSleeve(barkMat), + new BranchModelPart.UnbakedCore(ringsMat), + null); - rootsFamily.getPrimitiveFilledRoots().ifPresent(primitiveFilledRoots -> { - Identifier primitiveFilledRootsId = BuiltInRegistries.BLOCK.getKey(primitiveFilledRoots); - Identifier barkTexture = Identifier.fromNamespaceAndPath(primitiveFilledRootsId.getNamespace(), "block/" + primitiveFilledRootsId.getPath() + "_side"); - Identifier ringsTexture = Identifier.fromNamespaceAndPath(primitiveFilledRootsId.getNamespace(), "block/" + primitiveFilledRootsId.getPath() + "_top"); + if (isThick) { + Identifier thickRingsTexture = IdentifierUtils.suffix(ringsTexture, "_thick"); + Material.Baked thickRingsMat = bakeMaterial(baker, thickRingsTexture); + return ThickBranchBlockBakedModel.bakeThick(baker, regular, + new BranchModelPart.UnbakedThickTrunk(barkMat, false), + new BranchModelPart.UnbakedThickTrunk(thickRingsMat, true)); + } - BakedModel model = createRootsBlockModel(barkTexture, ringsTexture, spriteGetter); - UNDERGROUND_ROOTS_MODEL_CACHE.put(blockId.withSuffix("_filled"), model); - }); + return regular; + } + /////////////////////////////////////////// + // SURFACE ROOTS + /////////////////////////////////////////// - }); - } - } - } + @Nullable + private static BlockStateModel getOrCreateSurfaceRootModel(SurfaceRootBlock surfaceRootBlock, ModelBaker baker) { + Family family = surfaceRootBlock.getFamily(); + if (family == null || !family.isValid()) return null; - private BakedModel createBranchModel(Identifier barkTexture, Identifier ringsTexture, boolean isThick, Function spriteGetter) { - TextureAtlasSprite barkSprite = spriteGetter.apply(new Material(InventoryMenu.BLOCK_ATLAS, barkTexture)); - TextureAtlasSprite ringsSprite = spriteGetter.apply(new Material(InventoryMenu.BLOCK_ATLAS, ringsTexture)); + Optional primitiveLog = family.getPrimitiveLog(); + if (primitiveLog.isEmpty()) return null; - if (isThick) { - Identifier thickRingsTexture = ringsTexture.withSuffix("_thick"); - TextureAtlasSprite thickRingsSprite = spriteGetter.apply(new Material(InventoryMenu.BLOCK_ATLAS, thickRingsTexture)); - return new ThickBranchBlockBakedModel(barkSprite, ringsSprite, thickRingsSprite); - } + Identifier blockId = BuiltInRegistries.BLOCK.getKey(surfaceRootBlock); + Identifier primitiveLogId = BuiltInRegistries.BLOCK.getKey(primitiveLog.get()); + Identifier barkTexture = Identifier.fromNamespaceAndPath(primitiveLogId.getNamespace(), "block/" + primitiveLogId.getPath()); + Identifier barkOverride = family.getTexturePath(Family.BRANCH).orElse(barkTexture); - return new BasicBranchBlockBakedModel(barkSprite, ringsSprite); + return FALLBACK_MODEL_CACHE.computeIfAbsent(blockId, id -> SurfaceRootBlockBakedModel.bake(baker, barkOverride)); } - private BakedModel createRootModel(Identifier barkTexture, Function spriteGetter) { - TextureAtlasSprite barkSprite = spriteGetter.apply(new Material(InventoryMenu.BLOCK_ATLAS, barkTexture)); - return new SurfaceRootBlockBakedModel(barkSprite); - } + /////////////////////////////////////////// + // UNDERGROUND (AERIAL) ROOTS + /////////////////////////////////////////// - private BakedModel createRootsBlockModel(Identifier barkTexture, Identifier ringsTexture, Function spriteGetter) { - TextureAtlasSprite barkSprite = spriteGetter.apply(new Material(InventoryMenu.BLOCK_ATLAS, barkTexture)); - TextureAtlasSprite ringsSprite = spriteGetter.apply(new Material(InventoryMenu.BLOCK_ATLAS, ringsTexture)); - return new BasicRootsBlockBakedModel(barkSprite, ringsSprite); - } + @Nullable + private static BlockStateModel getOrCreateRootsModel(BasicRootsBlock rootsBlock, BlockState state, ModelBaker baker) { + if (!(rootsBlock.getFamily() instanceof AerialRootsFamily rootsFamily) || !rootsFamily.isValid()) return null; + if (!state.hasProperty(BasicRootsBlock.LAYER)) return null; + + BasicRootsBlock.Layer layer = state.getValue(BasicRootsBlock.LAYER); + Identifier blockId = BuiltInRegistries.BLOCK.getKey(rootsBlock); - private BakedModel createFallbackRootsModel(AerialRootsFamily family, String variant, Function spriteGetter) { - if (variant.contains("layer=exposed")) { - return family.getPrimitiveRoots().map(primitiveRoots -> { + return switch (layer) { + case EXPOSED -> rootsFamily.getPrimitiveRoots().map(primitiveRoots -> { Identifier primitiveRootsId = BuiltInRegistries.BLOCK.getKey(primitiveRoots); Identifier barkTexture = Identifier.fromNamespaceAndPath(primitiveRootsId.getNamespace(), "block/" + primitiveRootsId.getPath() + "_side"); Identifier ringsTexture = Identifier.fromNamespaceAndPath(primitiveRootsId.getNamespace(), "block/" + primitiveRootsId.getPath() + "_top"); - return createRootsBlockModel(barkTexture, ringsTexture, spriteGetter); + + Identifier barkOverride = rootsFamily.getTexturePath(Family.ROOTS_SIDE).orElse(barkTexture); + Identifier ringsOverride = rootsFamily.getTexturePath(Family.ROOTS_TOP).orElse(ringsTexture); + + return FALLBACK_MODEL_CACHE.computeIfAbsent(IdentifierUtils.suffix(blockId, "_exposed"), id -> + BasicRootsBlockBakedModel.bakeRoots(baker, bakeMaterial(baker, barkOverride), bakeMaterial(baker, ringsOverride), false)); }).orElse(null); - } else if (variant.contains("layer=filled")) { - return family.getPrimitiveFilledRoots().map(primitiveFilledRoots -> { + case FILLED -> rootsFamily.getPrimitiveFilledRoots().map(primitiveFilledRoots -> { Identifier primitiveFilledRootsId = BuiltInRegistries.BLOCK.getKey(primitiveFilledRoots); Identifier barkTexture = Identifier.fromNamespaceAndPath(primitiveFilledRootsId.getNamespace(), "block/" + primitiveFilledRootsId.getPath() + "_side"); Identifier ringsTexture = Identifier.fromNamespaceAndPath(primitiveFilledRootsId.getNamespace(), "block/" + primitiveFilledRootsId.getPath() + "_top"); - return createRootsBlockModel(barkTexture, ringsTexture, spriteGetter); + + return FALLBACK_MODEL_CACHE.computeIfAbsent(IdentifierUtils.suffix(blockId, "_filled"), id -> + BasicRootsBlockBakedModel.bakeRoots(baker, bakeMaterial(baker, barkTexture), bakeMaterial(baker, ringsTexture), true)); }).orElse(null); - } - return null; + case COVERED -> null; // Covered roots keep their regular (soil-like) model. + }; } - private BakedModel modifyModelAfterBake(BakedModel model, ModelModifier.AfterBake.Context context) { - ModelIdentifier modelId = context.topLevelId(); - if (modelId == null) return model; - - if (modelId.id().equals(POTTED_SAPLING_MODEL)) { - return new BakedModelBlockPottedSapling(model); - } - - Identifier blockId = modelId.id(); - Block block = BuiltInRegistries.BLOCK.get(blockId); - - if (block instanceof BasicRootsBlock rootsBlock) { - initBranchModels(context.textureGetter()); - - String variant = modelId.variant(); - Identifier cacheKey; - if (variant.contains("layer=filled")) { - cacheKey = blockId.withSuffix("_filled"); - } else if (variant.contains("layer=exposed")) { - cacheKey = blockId.withSuffix("_exposed"); - } else if (variant.contains("layer=covered")) { - return model; - } else { - return model; - } - - BakedModel rootsModel = UNDERGROUND_ROOTS_MODEL_CACHE.get(cacheKey); - if (rootsModel != null) { - return rootsModel; - } - - if (rootsBlock.getFamily() instanceof AerialRootsFamily undergroundFamily) { - BakedModel fallbackModel = createFallbackRootsModel(undergroundFamily, variant, context.textureGetter()); - if (fallbackModel != null) { - UNDERGROUND_ROOTS_MODEL_CACHE.put(cacheKey, fallbackModel); - return fallbackModel; - } - } - return model; - } - - if (block instanceof SurfaceRootBlock) { - initBranchModels(context.textureGetter()); - - BakedModel rootModel = ROOT_MODEL_CACHE.get(blockId); - if (rootModel != null) { - return rootModel; - } - return model; - } - - if (block instanceof BranchBlock) { - initBranchModels(context.textureGetter()); - - BakedModel branchModel = BRANCH_MODEL_CACHE.get(blockId); - if (branchModel != null) { - return branchModel; - } - } - - - return model; + private static Material.Baked bakeMaterial(ModelBaker baker, Identifier texture) { + return baker.materials().get(new Material(texture), texture::toDebugFileName); } } diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/FabricDynamicBlockStateModel.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/FabricDynamicBlockStateModel.java new file mode 100644 index 000000000..df75da7d5 --- /dev/null +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/FabricDynamicBlockStateModel.java @@ -0,0 +1,43 @@ +package com.dtteam.dynamictrees.model; + +import net.fabricmc.fabric.api.client.renderer.v1.mesh.QuadEmitter; +import net.fabricmc.fabric.api.client.renderer.v1.model.FabricBlockStateModel; +import net.fabricmc.fabric.api.client.renderer.v1.model.FabricBlockStateModelPart; +import net.minecraft.client.renderer.block.BlockAndTintGetter; +import net.minecraft.client.renderer.block.dispatch.BlockStateModel; +import net.minecraft.client.renderer.block.dispatch.BlockStateModelPart; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.block.state.BlockState; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +/** + * Fabric mirror of NeoForge's {@code DynamicBlockStateModel}: a {@link BlockStateModel} whose + * geometry depends on level context. Implementors provide the level-aware + * {@link #collectParts(BlockAndTintGetter, BlockPos, BlockState, RandomSource, List)}; quads are + * emitted through the Fabric Renderer API's {@link FabricBlockStateModel#emitQuads} path. + */ +public interface FabricDynamicBlockStateModel extends BlockStateModel, FabricBlockStateModel { + + void collectParts(BlockAndTintGetter level, BlockPos pos, BlockState state, RandomSource random, List parts); + + @Override + default void collectParts(RandomSource random, List parts) { + // Geometry is dynamic; without level context there is nothing meaningful to collect. + } + + @Override + default void emitQuads(QuadEmitter emitter, BlockAndTintGetter level, BlockPos pos, BlockState state, + RandomSource random, Predicate<@Nullable Direction> cullTest) { + List parts = new ArrayList<>(); + collectParts(level, pos, state, random, parts); + for (BlockStateModelPart part : parts) { + ((FabricBlockStateModelPart) part).emitQuads(emitter, cullTest); + } + } +} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/FallingTreeEntityModelFabric.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/FallingTreeEntityModelFabric.java index d1baafeb4..9f5f52a73 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/model/FallingTreeEntityModelFabric.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/FallingTreeEntityModelFabric.java @@ -1,181 +1,17 @@ package com.dtteam.dynamictrees.model; -import com.dtteam.dynamictrees.api.network.BranchDestructionData; -import com.dtteam.dynamictrees.block.branch.BranchBlock; -import com.dtteam.dynamictrees.block.soil.SoilBlock; -import com.dtteam.dynamictrees.compat.continuity.WrappedModelHandler; import com.dtteam.dynamictrees.entity.FallingTreeEntity; -import com.dtteam.dynamictrees.model.baked.BasicBranchBlockBakedModel; import com.dtteam.dynamictrees.model.entity.FallingTreeEntityModel; -import com.dtteam.dynamictrees.tree.TreeHelper; -import com.dtteam.dynamictrees.tree.species.Species; -import net.minecraft.client.Minecraft; -import net.minecraft.client.renderer.block.BlockRenderDispatcher; -import net.minecraft.client.renderer.block.model.BakedQuad; -import net.minecraft.client.resources.model.BakedModel; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.util.RandomSource; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.phys.Vec3; -import org.apache.commons.lang3.tuple.Pair; - -import java.util.ArrayList; -import java.util.List; +/** + * Fabric falling tree entity model. Since the 26.2 port, quad generation is fully handled by the + * common {@link FallingTreeEntityModel} (via {@code QuadManipulator} and the + * {@code BlockStateModelWithConnectionData} interface implemented by DT's Fabric block state models), + * so no Fabric-specific behavior remains. + */ public class FallingTreeEntityModelFabric extends FallingTreeEntityModel { public FallingTreeEntityModelFabric(FallingTreeEntity entity) { super(entity); } - - @Override - public List generateTreeQuads(FallingTreeEntity entity) { - BlockRenderDispatcher dispatcher = Minecraft.getInstance().getBlockRenderer(); - BranchDestructionData destructionData = entity.getDestroyData(); - Direction cutDir = destructionData.cutDir; - - ArrayList treeQuads = new ArrayList<>(); - - int[] connectionArray = new int[6]; - - if (destructionData.getNumBranches() > 0) { - BlockState exState = destructionData.getBranchBlockState(0); - BlockPos cutPos = destructionData.cutPos; - if (exState != null) { - Species species = destructionData.species; - RandomSource random = entity.getRandom(); - - boolean rootyBlockAdded = false; - if (destructionData.soilState != null) { - BlockState soilState = destructionData.soilState; - if (soilState != null) { - BlockState soilBlock = soilState.getBlock(); - BakedModel rootyModel = dispatcher.getBlockModel(soilState); - BlockPos cutOffset = destructionData.getRelativeCutPos(); - treeQuads.addAll(toTreeQuadData( - getQuadsWithOffset(rootyModel, soilState, new Vec3(cutOffset.getX(), cutOffset.getY() - 1, cutOffset.getZ()), random), - destructionData.species.getFamily().getRootColor(soilState, soilBlock.getColorFromBark()), - soilState)); - rootyBlockAdded = true; - } - } - - BakedModel branchModel = dispatcher.getBlockModel(exState); - destructionData.getConnections(0, connectionArray); - boolean bottomRingsAdded = false; - if (!rootyBlockAdded && connectionArray[cutDir.get3DDataValue()] > 0) { - BlockPos offsetPos = destructionData.getRelativeCutPos().relative(cutDir); - float offset = (8 - Math.min(((BranchBlock) exState.getBlock()).getRadius(exState), BranchBlock.MAX_RADIUS)) / 16f; - int coreRadius = ((BranchBlock) exState.getBlock()).getRadius(exState); - treeQuads.addAll(toTreeQuadData( - getBottomRingQuads(branchModel, new Vec3(offsetPos.getX(), offsetPos.getY(), offsetPos.getZ()).scale(offset), coreRadius, cutDir), - exState)); - bottomRingsAdded = true; - } - - for (int index = 0; index < destructionData.getNumBranches(); index++) { - Block previousBranch = exState.getBlock(); - exState = destructionData.getBranchBlockState(index); - if (!previousBranch.equals(exState.getBlock())) { - branchModel = dispatcher.getBlockModel(exState); - } - BlockPos relPos = destructionData.getBranchRelPos(index); - destructionData.getConnections(index, connectionArray); - int coreRadius = ((BranchBlock) exState.getBlock()).getRadius(exState); - Direction forceRingDir = (index == 0 && bottomRingsAdded) ? cutDir : null; - treeQuads.addAll(toTreeQuadData( - getBranchQuadsWithConnections(branchModel, exState, new Vec3(relPos.getX(), relPos.getY(), relPos.getZ()), random, connectionArray, coreRadius, forceRingDir), - exState)); - } - - for (Pair leafLoc : destructionData.getAllLeavesWithPos()) { - BlockState leafState = leafLoc.getValue(); - List bakedQuads = getQuadsWithOffset(dispatcher.getBlockModel(leafState), leafState, - new Vec3(leafLoc.getKey().getX(), leafLoc.getKey().getY(), leafLoc.getKey().getZ()), random); - - treeQuads.addAll(toTreeQuadData(bakedQuads, species.leafColorMultiplier(entity.level(), - cutPos.offset(leafLoc.getKey())), leafState)); - } - } - } - - return treeQuads; - } - - private List getBottomRingQuads(BakedModel model, Vec3 offset, int coreRadius, Direction cutDir) { - List allQuads = new ArrayList<>(); - - if (model instanceof BasicBranchBlockBakedModel branchModel) { - for (BakedQuad quad : branchModel.getRingQuads(coreRadius)) { - if (quad.getDirection() == cutDir) { - allQuads.add(quad); - } - } - } - - return offsetAllQuads(offset, allQuads); - } - - private List getBranchQuadsWithConnections(BakedModel model, BlockState state, Vec3 offset, RandomSource random, int[] connections, int coreRadius, Direction forceRingDir) { - List allQuads = new ArrayList<>(); - - BasicBranchBlockBakedModel branchModel = WrappedModelHandler.getInstance().unwrapBranchModel(model); - if (branchModel != null) { - int twigRadius = state.getBlock() instanceof BranchBlock branchBlock - ? branchBlock.getFamily().getPrimaryThickness() - : 1; - - branchModel.collectQuads(coreRadius, connections, twigRadius, forceRingDir) - .values().forEach(allQuads::addAll); - } else { - for (Direction direction : Direction.values()) { - allQuads.addAll(model.getQuads(state, direction, random)); - } - allQuads.addAll(model.getQuads(state, null, random)); - } - - return offsetAllQuads(offset, allQuads); - } - - private List getQuadsWithOffset(BakedModel model, BlockState state, Vec3 offset, RandomSource random) { - List allQuads = new ArrayList<>(); - - for (Direction direction : Direction.values()) { - allQuads.addAll(model.getQuads(state, direction, random)); - } - allQuads.addAll(model.getQuads(state, null, random)); - - return offsetAllQuads(offset, allQuads); - } - - private List offsetAllQuads(Vec3 offset, List allQuads) { - if (offset.x() != 0 || offset.y() != 0 || offset.z() != 0) { - List offsetQuads = new ArrayList<>(); - for (BakedQuad quad : allQuads) { - offsetQuads.add(offsetQuad(quad, offset)); - } - return offsetQuads; - } - - return allQuads; - } - - private BakedQuad offsetQuad(BakedQuad quad, Vec3 offset) { - int[] vertexData = quad.getVertices().clone(); - - for (int i = 0; i < 4; i++) { - int baseIndex = i * 8; - float x = Float.intBitsToFloat(vertexData[baseIndex]) + (float) offset.x(); - float y = Float.intBitsToFloat(vertexData[baseIndex + 1]) + (float) offset.y(); - float z = Float.intBitsToFloat(vertexData[baseIndex + 2]) + (float) offset.z(); - vertexData[baseIndex] = Float.floatToRawIntBits(x); - vertexData[baseIndex + 1] = Float.floatToRawIntBits(y); - vertexData[baseIndex + 2] = Float.floatToRawIntBits(z); - } - - return new BakedQuad(vertexData, quad.getTintIndex(), quad.getDirection(), quad.getSprite(), quad.isShade()); - } } diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/BasicBranchBlockBakedModel.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/BasicBranchBlockBakedModel.java index eeed78a92..08057a83e 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/BasicBranchBlockBakedModel.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/BasicBranchBlockBakedModel.java @@ -1,270 +1,168 @@ package com.dtteam.dynamictrees.model.baked; +import com.dtteam.dynamictrees.api.network.Connections; import com.dtteam.dynamictrees.block.branch.BranchBlock; -import com.dtteam.dynamictrees.block.branch.ThickBranchBlock; -import com.google.common.collect.Maps; -import net.fabricmc.fabric.api.renderer.v1.mesh.*; -import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel; -import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; -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.RendererAccess; -import net.minecraft.client.renderer.block.model.BakedQuad; -import net.minecraft.client.renderer.block.model.BlockElement; -import net.minecraft.client.renderer.block.model.BlockElementFace; -import net.minecraft.client.renderer.block.model.BlockFaceUV; -import net.minecraft.client.renderer.block.model.FaceBakery; -import net.minecraft.client.renderer.block.model.ItemOverrides; -import net.minecraft.client.renderer.block.model.ItemTransforms; -import net.minecraft.client.renderer.texture.TextureAtlasSprite; -import net.minecraft.client.resources.model.BakedModel; -import net.minecraft.client.resources.model.BlockModelRotation; +import com.dtteam.dynamictrees.model.BlockStateModelWithConnectionData; +import com.dtteam.dynamictrees.model.BranchMultiPartHolder; +import com.dtteam.dynamictrees.model.FabricDynamicBlockStateModel; +import com.dtteam.dynamictrees.model.ModelConnections; +import com.dtteam.dynamictrees.model.ModelHelper; +import com.dtteam.dynamictrees.model.parts.BranchModelPart; +import com.dtteam.dynamictrees.tree.TreeHelper; +import net.minecraft.client.renderer.block.BlockAndTintGetter; +import net.minecraft.client.renderer.block.dispatch.BlockStateModelPart; +import net.minecraft.client.resources.model.ModelBaker; +import net.minecraft.client.resources.model.geometry.BakedQuad; +import net.minecraft.client.resources.model.sprite.Material; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; -import net.minecraft.core.Direction.Axis; -import net.minecraft.core.Direction.AxisDirection; import net.minecraft.util.RandomSource; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.BlockAndTintGetter; import net.minecraft.world.level.block.state.BlockState; import org.jetbrains.annotations.Nullable; -import org.joml.Vector3f; -import java.util.*; -import java.util.function.Supplier; - -@SuppressWarnings("unchecked") -public class BasicBranchBlockBakedModel implements BakedModel, FabricBakedModel { - - protected final TextureAtlasSprite barkTexture; - protected final TextureAtlasSprite ringsTexture; - - public final List[][] sleevesQuads = new List[6][8]; - public final List[][] coresQuads = new List[3][8]; - public final List[] ringsQuads = new List[8]; - - public BasicBranchBlockBakedModel(TextureAtlasSprite barkTexture, TextureAtlasSprite ringsTexture) { - this.barkTexture = barkTexture; - this.ringsTexture = ringsTexture; - initModels(); - } - - private void initModels() { - for (int i = 0; i < 8; i++) { - int radius = i + 1; - if (radius < 8) { - for (Direction dir : Direction.values()) { - sleevesQuads[dir.get3DDataValue()][i] = bakeSleeve(radius, dir, barkTexture); - } +import java.util.List; + +/** + * Dynamic branch block model for Fabric. Mirrors the NeoForge {@code BranchBlockStateModel}, + * emitting level-aware geometry (branch connections) at chunk-build time via the Fabric + * Renderer API. + */ +public class BasicBranchBlockBakedModel implements FabricDynamicBlockStateModel, BlockStateModelWithConnectionData { + + protected final BranchMultiPartHolder cores; + protected final BranchMultiPartHolder sleeves; + protected final BranchMultiPartHolder rings; + protected final BranchMultiPartHolder sleeveRings; + + public BasicBranchBlockBakedModel(BranchMultiPartHolder cores, BranchMultiPartHolder sleeves, + BranchMultiPartHolder rings, BranchMultiPartHolder sleeveRings) { + this.cores = cores; + this.sleeves = sleeves; + this.rings = rings; + this.sleeveRings = sleeveRings; + } + + /** + * Bakes a basic branch model out of the given unbaked parts. Mirrors the NeoForge + * {@code UnbakedBranchModel#bakeBasic}. + */ + public static BasicBranchBlockBakedModel bakeBasic( + ModelBaker baker, BranchModelPart.UnbakedCore unbakedCores, BranchModelPart.UnbakedSleeve unbakedSleeves, + BranchModelPart.UnbakedCore unbakedRings, @Nullable BranchModelPart.UnbakedSleeve unbakedSleeveRings) { + BranchMultiPartHolder sleeves = new BranchMultiPartHolder(); + BranchMultiPartHolder cores = new BranchMultiPartHolder(); + BranchMultiPartHolder rings = new BranchMultiPartHolder(); + BranchMultiPartHolder sleeveRings = new BranchMultiPartHolder(); + + for (int radius = 1; radius <= BranchBlock.MAX_RADIUS; radius++) { + if (radius < BranchBlock.MAX_RADIUS) { + sleeves.putAllParts(radius, unbakedSleeves.bakeAllSides(baker, radius)); } - coresQuads[0][i] = bakeCore(radius, Axis.Y, barkTexture); - coresQuads[1][i] = bakeCore(radius, Axis.Z, barkTexture); - coresQuads[2][i] = bakeCore(radius, Axis.X, barkTexture); - - ringsQuads[i] = bakeCore(radius, Axis.Y, ringsTexture); - } - } - - public BlockElement generateSleevePart(int radius, Direction dir) { - int dradius = radius * 2; - int halfSize = (16 - dradius) / 2; - int halfSizeX = dir.getStepX() != 0 ? halfSize : dradius; - int halfSizeY = dir.getStepY() != 0 ? halfSize : dradius; - int halfSizeZ = dir.getStepZ() != 0 ? halfSize : dradius; - int move = 16 - halfSize; - int centerX = 16 + (dir.getStepX() * move); - int centerY = 16 + (dir.getStepY() * move); - int centerZ = 16 + (dir.getStepZ() * move); - - Vector3f posFrom = new Vector3f((centerX - halfSizeX) / 2f, (centerY - halfSizeY) / 2f, (centerZ - halfSizeZ) / 2f); - Vector3f posTo = new Vector3f((centerX + halfSizeX) / 2f, (centerY + halfSizeY) / 2f, (centerZ + halfSizeZ) / 2f); - - boolean negative = dir.getAxisDirection() == AxisDirection.NEGATIVE; - if (dir.getAxis() == Axis.Z) { - negative = !negative; - } - - Map mapFacesIn = Maps.newEnumMap(Direction.class); - - for (Direction face : Direction.values()) { - if (dir.getOpposite() != face) { - BlockFaceUV uvface = null; - if (dir == face) { - if (radius == 1) { - uvface = new BlockFaceUV(new float[]{8 - radius, 8 - radius, 8 + radius, 8 + radius}, 0); - } - } else { - uvface = new BlockFaceUV(new float[]{8 - radius, negative ? 16 - halfSize : 0, 8 + radius, negative ? 16 : halfSize}, getFaceAngle(dir.getAxis(), face)); - } - if (uvface != null) { - mapFacesIn.put(face, new BlockElementFace(null, -1, null, uvface)); - } + if (unbakedSleeveRings != null) { + sleeveRings.putAllParts(radius, unbakedSleeveRings.bakeAllSides(baker, radius)); } - } - return new BlockElement(posFrom, posTo, mapFacesIn, null, true); - } + cores.putAllParts(Direction.Axis.Y, radius, unbakedCores.bakeAllSides(baker, radius, Direction.Axis.Y)); //DOWN<->UP + cores.putAllParts(Direction.Axis.Z, radius, unbakedCores.bakeAllSides(baker, radius, Direction.Axis.Z)); //NORTH<->SOUTH + cores.putAllParts(Direction.Axis.X, radius, unbakedCores.bakeAllSides(baker, radius, Direction.Axis.X)); //WEST<->EAST - public List bakeSleeve(int radius, Direction dir, TextureAtlasSprite bark) { - BlockElement part = generateSleevePart(radius, dir); - List quads = new ArrayList<>(); - FaceBakery faceBakery = new FaceBakery(); - - for (Map.Entry e : part.faces.entrySet()) { - Direction face = e.getKey(); - quads.add(faceBakery.bakeQuad(part.from, part.to, e.getValue(), bark, face, BlockModelRotation.X0_Y0, part.rotation, true)); + rings.putAllParts(radius, unbakedRings.bakeAllSides(baker, radius, Direction.Axis.Y)); } - return quads; + return new BasicBranchBlockBakedModel(cores, sleeves, rings, sleeveRings); } - protected BlockElement generateCorePart(int radius, Axis axis) { - Vector3f posFrom = new Vector3f(8 - radius, 8 - radius, 8 - radius); - Vector3f posTo = new Vector3f(8 + radius, 8 + radius, 8 + radius); - - Map mapFacesIn = Maps.newEnumMap(Direction.class); - - for (Direction face : Direction.values()) { - BlockFaceUV uvface = new BlockFaceUV(new float[]{8 - radius, 8 - radius, 8 + radius, 8 + radius}, getFaceAngle(axis, face)); - mapFacesIn.put(face, new BlockElementFace(null, -1, null, uvface)); - } - - return new BlockElement(posFrom, posTo, mapFacesIn, null, true); - } - - public List bakeCore(int radius, Axis axis, TextureAtlasSprite icon) { - BlockElement part = generateCorePart(radius, axis); - List quads = new ArrayList<>(); - FaceBakery faceBakery = new FaceBakery(); - - for (Map.Entry e : part.faces.entrySet()) { - Direction face = e.getKey(); - quads.add(faceBakery.bakeQuad(part.from, part.to, e.getValue(), icon, face, BlockModelRotation.X0_Y0, part.rotation, true)); - } - - return quads; - } - - public int getFaceAngle(Axis axis, Direction face) { - if (axis == Axis.Y) { - return 0; - } else if (axis == Axis.Z) { - return switch (face) { - case UP -> 0; - case WEST -> 270; - case DOWN -> 180; - default -> 90; - }; - } else { - return (face == Direction.NORTH) ? 270 : 90; - } - } + /////////////////////////////////////////// + // FABRIC DYNAMIC GEOMETRY + /////////////////////////////////////////// @Override - public boolean isVanillaAdapter() { - return false; + public void collectParts(BlockAndTintGetter level, BlockPos pos, BlockState state, RandomSource random, List parts) { + collectParts(state, parts, ModelHelper.getModelConnections(level, pos, state)); } @Override - public void emitBlockQuads(BlockAndTintGetter blockView, BlockState state, BlockPos pos, Supplier randomSupplier, RenderContext context) { - EnumMap> bakedQuads = collectQuads(blockView, state, pos); - if (bakedQuads == null) return; - - QuadEmitter emitter = context.getEmitter(); - RenderMaterial material = getRenderMaterial(); - if (material == null) return; - - bakedQuads.forEach((dir, quads) -> - emitQuads(dir, emitter, material, quads)); - } - - protected @Nullable EnumMap> collectQuads(BlockAndTintGetter getter, BlockState state, BlockPos pos) { - if (state == null) return null; - - final int coreRadius = getRadius(state); - if (coreRadius <= 0 || coreRadius > maxBranchRadius()) return null; - - int[] connections = new int[]{0, 0, 0, 0, 0, 0}; - int twigRadius = 1; - - if (state.getBlock() instanceof BranchBlock branchBlock) { - connections = branchBlock.getConnectionData(getter, pos, state).getAllRadii(); - twigRadius = branchBlock.getFamily().getPrimaryThickness(); - } - - return collectQuads(coreRadius, connections, twigRadius, null); + public Object createGeometryKey(BlockAndTintGetter level, BlockPos pos, BlockState state, RandomSource random) { + return ModelHelper.getModelConnections(level, pos, state); } - protected int maxBranchRadius() { - return ThickBranchBlock.MAX_RADIUS; - } + /////////////////////////////////////////// + // PART COLLECTION + /////////////////////////////////////////// - public EnumMap> collectQuads(int coreRadius, int[] connections, int twigRadius, Direction forceRingDir) { - int numConnections = 0; - for (int i : connections) { - numConnections += (i != 0) ? 1 : 0; + @Override + public void collectParts(BlockState state, List parts, Connections connectionsData) { + final int coreRadius = TreeHelper.getRadius(state); + if (coreRadius > 8 || coreRadius == 0) return; + if (!(connectionsData instanceof ModelConnections modelConnections)) return; + + final int[] connections = connectionsData.getAllRadii(); + final Direction forceRingDir = modelConnections.getRingOnly(); + final int twigRadius = modelConnections.getFamily().getPrimaryThickness(); + final int numConnections = countConnections(connections); + + final Direction sourceDir = get3DSourceDir(coreRadius, connections); + final Direction.Axis coreDir = sourceDir == null ? Direction.Axis.Y : sourceDir.getAxis(); + final Direction coreRingDir = (numConnections == 1 && sourceDir != null) ? sourceDir.getOpposite() : null; + + if (forceRings(numConnections, forceRingDir)) { + addPart(parts, rings.getPart(forceRingDir, coreRadius)); + } else { + for (Direction face : Direction.values()) { + gatherCoreParts(parts, face, coreRadius, connections, coreRingDir, coreDir); + gatherSleeveParts(parts, face, coreRadius, connections, twigRadius); + } } - - Direction sourceDir = getSourceDir(coreRadius, connections); - int coreDir = resolveCoreDir(sourceDir); - Direction coreRingDir = forceRingDir != null ? forceRingDir : - ((numConnections == 1 && sourceDir != null) ? sourceDir.getOpposite() : null); - - EnumMap> bakedQuads = new EnumMap<>(Direction.class); - - for (Direction face : Direction.values()) { - List quads = bakedQuads.computeIfAbsent(face, dir->new ArrayList<>()); - if (coreRadius != connections[face.get3DDataValue()]) { - if (coreRingDir == null || coreRingDir != face) { - quads.addAll(coresQuads[coreDir][coreRadius - 1]); - } else { - quads.addAll(ringsQuads[coreRadius - 1]); - } + //The null side is usually empty, but roots have the cross. + addPart(parts, cores.getPart(coreDir, null, coreRadius)); + } + + private void gatherSleeveParts(List parts, Direction face, int coreRadius, int[] connections, int twigRadius) { + // Get quads for sleeves models. + for (Direction connDir : Direction.values()) { + final int idx = connDir.get3DDataValue(); + final int connRadius = connections[idx]; + if (connRadius == 0) continue; + // If the connection side matches the quadpull side then cull the sleeve face. + // Don't cull radius-1 connections for leaves (which are partly transparent). + if (coreRadius < 8 && connRadius <= twigRadius || face != connDir) { + addPart(parts, sleeves.getPart(connDir, connRadius)); } - - if (coreRadius != 8) { - for (Direction connDir : Direction.values()) { - int idx = connDir.get3DDataValue(); - int connRadius = connections[idx]; - if (connRadius > 0 && connRadius < 8 && (connRadius <= twigRadius || face != connDir)) { - quads.addAll(sleevesQuads[idx][connRadius - 1]); - } - } + if (face == connDir && !sleeveRings.isEmpty()) { + addPart(parts, sleeveRings.getPart(connDir, connRadius)); } } - return bakedQuads; } - public List getRingQuads(int radius){ - return ringsQuads[radius - 1]; - } + private void gatherCoreParts(List parts, Direction face, int coreRadius, int[] connections, Direction coreRingDir, Direction.Axis coreDir) { + if (coreRadius == connections[face.get3DDataValue()]) return; - protected static RenderMaterial getRenderMaterial() { - var renderer = RendererAccess.INSTANCE.getRenderer(); - if (renderer == null) return null; - MaterialFinder finder = renderer.materialFinder(); -// finder.disableAo(0, true); -// finder.disableDiffuse(0, true); + if (coreRingDir != null && coreRingDir == face) { + addPart(parts, rings.getPart(face, coreRadius)); + } else { + addPart(parts, cores.getPart(coreDir, face, coreRadius)); + } + } - return finder.find(); + protected static void addPart(List parts, @Nullable BlockStateModelPart part) { + if (part == null) return; + parts.add(part); } - protected void emitQuads(Direction face, QuadEmitter emitter, RenderMaterial material, List quads) { - if (quads == null) return; - for (BakedQuad quad : quads) { - if (quad.getDirection() == face) { - emitter.fromVanilla(quad, material, face); - emitter.emit(); - } - } + private static boolean forceRings(int numConnections, Direction forceRingDir) { + return numConnections == 0 && forceRingDir != null; } - @Override - public void emitItemQuads(ItemStack stack, Supplier randomSupplier, RenderContext context) { + public static int countConnections(int[] connections) { + int numConnections = 0; + for (int i : connections) { + numConnections += (i != 0) ? 1 : 0; + } + return numConnections; } @Nullable - protected Direction getSourceDir(int coreRadius, int[] connections) { + public static Direction get3DSourceDir(int coreRadius, int[] connections) { int largestConnection = 0; Direction sourceDir = null; @@ -277,59 +175,22 @@ protected Direction getSourceDir(int coreRadius, int[] connections) { } if (largestConnection < coreRadius) { - sourceDir = null; + sourceDir = null; //Has no source node } return sourceDir; } - protected int resolveCoreDir(@Nullable Direction dir) { - if (dir == null) { - return 0; - } - return dir.get3DDataValue() >> 1; - } - - protected int getRadius(BlockState blockState) { - return ((BranchBlock) blockState.getBlock()).getRadius(blockState); - } - - @Override - public List getQuads(@Nullable BlockState state, @Nullable Direction direction, RandomSource random) { - return Collections.emptyList(); - } - - @Override - public boolean useAmbientOcclusion() { - return true; - } - - @Override - public boolean isGui3d() { - return false; - } - - @Override - public boolean usesBlockLight() { - return false; - } - - @Override - public boolean isCustomRenderer() { - return false; - } - - @Override - public TextureAtlasSprite getParticleIcon() { - return barkTexture; - } + /////////////////////////////////////////// + // VANILLA MODEL METHODS + /////////////////////////////////////////// @Override - public ItemTransforms getTransforms() { - return ItemTransforms.NO_TRANSFORMS; + public Material.Baked particleMaterial() { + return cores.getFirstMaterial(); } @Override - public ItemOverrides getOverrides() { - return ItemOverrides.EMPTY; + public @BakedQuad.MaterialFlags int materialFlags() { + return cores.materialFlags(); } } diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/BasicRootsBlockBakedModel.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/BasicRootsBlockBakedModel.java index 37ab0a461..196a17ade 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/BasicRootsBlockBakedModel.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/BasicRootsBlockBakedModel.java @@ -1,132 +1,36 @@ package com.dtteam.dynamictrees.model.baked; -import com.dtteam.dynamictrees.block.branch.BasicRootsBlock; -import com.dtteam.dynamictrees.block.branch.BranchBlock; -import com.google.common.collect.Maps; -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.render.RenderContext; -import net.minecraft.client.renderer.block.model.BakedQuad; -import net.minecraft.client.renderer.block.model.BlockElement; -import net.minecraft.client.renderer.block.model.BlockElementFace; -import net.minecraft.client.renderer.block.model.BlockFaceUV; -import net.minecraft.client.renderer.block.model.FaceBakery; -import net.minecraft.client.renderer.texture.TextureAtlasSprite; -import net.minecraft.client.resources.model.BlockModelRotation; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.util.RandomSource; -import net.minecraft.world.level.BlockAndTintGetter; -import net.minecraft.world.level.block.state.BlockState; -import org.jetbrains.annotations.Nullable; -import org.joml.Vector3f; - -import java.util.ArrayList; -import java.util.EnumMap; -import java.util.List; -import java.util.Map; -import java.util.function.Supplier; - -@SuppressWarnings("unchecked") +import com.dtteam.dynamictrees.model.BranchMultiPartHolder; +import com.dtteam.dynamictrees.model.parts.BranchModelPart; +import net.minecraft.client.resources.model.ModelBaker; +import net.minecraft.client.resources.model.sprite.Material; + +/** + * Dynamic underground roots model for Fabric. Baked out of root-specific branch parts; + * the part collection logic is shared with {@link BasicBranchBlockBakedModel}. + * Mirrors the NeoForge {@code UnbakedRootsModel} baking. + */ public class BasicRootsBlockBakedModel extends BasicBranchBlockBakedModel { - final static float Z_FIGHTING_OFFSET = 0.001f; - - private final List[][] sleeveEndFaces = new List[6][8]; - - public BasicRootsBlockBakedModel(TextureAtlasSprite barkTexture, TextureAtlasSprite ringsTexture) { - super(barkTexture, ringsTexture); - initRootsModels(); + public BasicRootsBlockBakedModel(BasicBranchBlockBakedModel baked) { + super(baked.cores, baked.sleeves, baked.rings, baked.sleeveRings); } - private void initRootsModels() { - for (int i = 0; i < 8; i++) { - int radius = i + 1; - for (Direction dir : Direction.values()) { - sleeveEndFaces[dir.get3DDataValue()][i] = bakeSleeveFace(radius, dir, ringsTexture); - } + public static BasicRootsBlockBakedModel bakeRoots(ModelBaker baker, Material.Baked barkMat, Material.Baked ringsMat, boolean opaque) { + BasicBranchBlockBakedModel model; + if (opaque) { + model = bakeBasic(baker, + new BranchModelPart.UnbakedCore(barkMat), + new BranchModelPart.UnbakedSleeve(barkMat), + new BranchModelPart.UnbakedCore(ringsMat), + new BranchModelPart.UnbakedRootSleeveEnds(ringsMat)); + } else { + model = bakeBasic(baker, + new BranchModelPart.UnbakedRootCore(barkMat, true), + new BranchModelPart.UnbakedRootSleeve(barkMat), + new BranchModelPart.UnbakedRootCore(ringsMat, false), + null); } - } - - public List bakeSleeveFace(int radius, Direction dir, TextureAtlasSprite rings) { - int dradius = radius * 2; - int halfSize = (16 - dradius) / 2; - float halfSizeX = dir.getStepX() != 0 ? halfSize + Z_FIGHTING_OFFSET : dradius; - float halfSizeY = dir.getStepY() != 0 ? halfSize + Z_FIGHTING_OFFSET : dradius; - float halfSizeZ = dir.getStepZ() != 0 ? halfSize + Z_FIGHTING_OFFSET : dradius; - int move = 16 - halfSize; - int centerX = 16 + (dir.getStepX() * move); - int centerY = 16 + (dir.getStepY() * move); - int centerZ = 16 + (dir.getStepZ() * move); - - Vector3f posFrom = new Vector3f((centerX - halfSizeX) / 2f, (centerY - halfSizeY) / 2f, (centerZ - halfSizeZ) / 2f); - Vector3f posTo = new Vector3f((centerX + halfSizeX) / 2f, (centerY + halfSizeY) / 2f, (centerZ + halfSizeZ) / 2f); - - Map mapFacesIn = Maps.newEnumMap(Direction.class); - BlockFaceUV uvface = new BlockFaceUV(new float[]{8 - radius, 8 - radius, 8 + radius, 8 + radius}, 0); - mapFacesIn.put(dir, new BlockElementFace(dir, -1, "", uvface)); - - BlockElement part = new BlockElement(posFrom, posTo, mapFacesIn, null, true); - List quads = new ArrayList<>(); - FaceBakery faceBakery = new FaceBakery(); - - for (Map.Entry e : part.faces.entrySet()) { - Direction face = e.getKey(); - quads.add(faceBakery.bakeQuad(part.from, part.to, e.getValue(), rings, face, BlockModelRotation.X0_Y0, part.rotation, true)); - } - - return quads; - } - - @Override - protected int getRadius(BlockState blockState) { - if (blockState.getBlock() instanceof BasicRootsBlock) { - if (blockState.hasProperty(BasicRootsBlock.RADIUS)) { - return blockState.getValue(BasicRootsBlock.RADIUS); - } - } - return super.getRadius(blockState); - } - - @Override - public EnumMap> collectQuads(int coreRadius, int[] connections, int twigRadius, Direction forceRingDir) { - int numConnections = 0; - for (int i : connections) { - numConnections += (i != 0) ? 1 : 0; - } - - Direction sourceDir = getSourceDir(coreRadius, connections); - int coreDir = resolveCoreDir(sourceDir); - Direction coreRingDir = (numConnections == 1 && sourceDir != null) ? sourceDir.getOpposite() : null; - - EnumMap> bakedQuads = new EnumMap<>(Direction.class); - - for (Direction face : Direction.values()) { - List quads = bakedQuads.computeIfAbsent(face, dir->new ArrayList<>()); - int connectionOnFace = connections[face.get3DDataValue()]; - if (coreRadius != connectionOnFace) { - if (coreRingDir == null || coreRingDir != face) { - quads.addAll(coresQuads[coreDir][coreRadius - 1]); - } else { - quads.addAll(ringsQuads[coreRadius - 1]); - } - } - - if (coreRadius != 8) { - for (Direction connDir : Direction.values()) { - int idx = connDir.get3DDataValue(); - int connRadius = connections[idx]; - if (connRadius > 0 && connRadius < 8 && (connRadius <= twigRadius || face != connDir)) { - quads.addAll(sleevesQuads[idx][connRadius - 1]); - } - } - } - - if (connectionOnFace > 0 && connectionOnFace <= coreRadius) { - quads.addAll(sleeveEndFaces[face.get3DDataValue()][connectionOnFace - 1]); - } - } - - return bakedQuads; + return new BasicRootsBlockBakedModel(model); } } diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/SurfaceRootBlockBakedModel.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/SurfaceRootBlockBakedModel.java index e7ad85fbf..1659e2f86 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/SurfaceRootBlockBakedModel.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/SurfaceRootBlockBakedModel.java @@ -1,323 +1,158 @@ package com.dtteam.dynamictrees.model.baked; +import com.dtteam.dynamictrees.api.network.Connections; import com.dtteam.dynamictrees.api.network.RootConnections; import com.dtteam.dynamictrees.block.branch.SurfaceRootBlock; +import com.dtteam.dynamictrees.model.BlockStateModelWithConnectionData; +import com.dtteam.dynamictrees.model.FabricDynamicBlockStateModel; +import com.dtteam.dynamictrees.model.ModelHelper; +import com.dtteam.dynamictrees.model.parts.SurfaceRootModelPart; import com.dtteam.dynamictrees.utility.CoordUtils; -import com.google.common.collect.Maps; -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.api.renderer.v1.material.MaterialFinder; -import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial; -import net.fabricmc.fabric.api.renderer.v1.RendererAccess; -import net.minecraft.client.renderer.block.model.BakedQuad; -import net.minecraft.client.renderer.block.model.BlockElement; -import net.minecraft.client.renderer.block.model.BlockElementFace; -import net.minecraft.client.renderer.block.model.BlockFaceUV; -import net.minecraft.client.renderer.block.model.FaceBakery; -import net.minecraft.client.renderer.block.model.ItemOverrides; -import net.minecraft.client.renderer.block.model.ItemTransforms; -import net.minecraft.client.renderer.texture.TextureAtlasSprite; -import net.minecraft.client.resources.model.BakedModel; -import net.minecraft.client.resources.model.BlockModelRotation; +import net.minecraft.client.renderer.block.BlockAndTintGetter; +import net.minecraft.client.renderer.block.dispatch.BlockStateModelPart; +import net.minecraft.client.resources.model.ModelBaker; +import net.minecraft.client.resources.model.geometry.BakedQuad; +import net.minecraft.client.resources.model.sprite.Material; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; +import net.minecraft.resources.Identifier; import net.minecraft.util.Mth; import net.minecraft.util.RandomSource; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.BlockAndTintGetter; import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.phys.AABB; -import org.apache.commons.lang3.tuple.Pair; import org.jetbrains.annotations.Nullable; -import org.joml.Vector3f; -import java.util.*; -import java.util.function.Supplier; +import java.util.List; -@SuppressWarnings("unchecked") -public class SurfaceRootBlockBakedModel implements BakedModel, FabricBakedModel { +/** + * Dynamic surface root model for Fabric. Mirrors the NeoForge {@code SurfaceRootBlockStateModel}. + */ +public class SurfaceRootBlockBakedModel implements FabricDynamicBlockStateModel, BlockStateModelWithConnectionData { - protected final TextureAtlasSprite barkTexture; + protected final SurfaceRootModelPart[][] cores; + protected final SurfaceRootModelPart[][] sleeves; + protected final SurfaceRootModelPart[][] verts; + protected final Material.Baked particleMaterial; - public final List[][] sleevesQuads = new List[4][7]; - public final List[][] coresQuads = new List[2][8]; - public final List[][] vertsQuads = new List[4][8]; - - public SurfaceRootBlockBakedModel(TextureAtlasSprite barkTexture) { - this.barkTexture = barkTexture; - initModels(); + public SurfaceRootBlockBakedModel(SurfaceRootModelPart[][] cores, SurfaceRootModelPart[][] sleeves, + SurfaceRootModelPart[][] verts, Material.Baked particleMaterial) { + this.cores = cores; + this.sleeves = sleeves; + this.verts = verts; + this.particleMaterial = particleMaterial; } - private void initModels() { + /** + * Bakes a surface root model for the given bark texture. Mirrors the NeoForge + * {@code SurfaceRootBlockStateModel.Unbaked#bake}. + */ + public static SurfaceRootBlockBakedModel bake(ModelBaker baker, Identifier barkTexture) { + SurfaceRootModelPart[][] sleeves = new SurfaceRootModelPart[4][7]; + SurfaceRootModelPart[][] cores = new SurfaceRootModelPart[2][8]; //8 Cores for 2 axis(X, Z) with the bark texture on all 6 sides rotated appropriately. + SurfaceRootModelPart[][] verts = new SurfaceRootModelPart[4][8]; + + Material.Baked barkMat = baker.materials().get(new Material(barkTexture, false), barkTexture::toDebugFileName); + + SurfaceRootModelPart.UnbakedCore unbakedCores = new SurfaceRootModelPart.UnbakedCore(barkMat); + SurfaceRootModelPart.UnbakedSleeve unbakedSleeves = new SurfaceRootModelPart.UnbakedSleeve(barkMat); + SurfaceRootModelPart.UnbakedVert unbakedVerts = new SurfaceRootModelPart.UnbakedVert(barkMat); + for (int r = 0; r < 8; r++) { int radius = r + 1; if (radius < 8) { for (Direction dir : CoordUtils.HORIZONTALS) { int horIndex = dir.get2DDataValue(); - sleevesQuads[horIndex][r] = bakeSleeve(radius, dir); - vertsQuads[horIndex][r] = bakeVert(radius, dir); - } - } - coresQuads[0][r] = bakeCore(radius, Direction.Axis.Z); - coresQuads[1][r] = bakeCore(radius, Direction.Axis.X); - } - } - - public int getRadialHeight(int radius) { - return radius * 2; - } - - public List bakeSleeve(int radius, Direction dir) { - int radialHeight = getRadialHeight(radius); - - int dradius = radius * 2; - int halfSize = (16 - dradius) / 2; - int halfSizeX = dir.getStepX() != 0 ? halfSize : dradius; - int halfSizeZ = dir.getStepZ() != 0 ? halfSize : dradius; - int move = 16 - halfSize; - int centerX = 16 + (dir.getStepX() * move); - int centerZ = 16 + (dir.getStepZ() * move); - - Vector3f posFrom = new Vector3f((centerX - halfSizeX) / 2f, 0, (centerZ - halfSizeZ) / 2f); - Vector3f posTo = new Vector3f((centerX + halfSizeX) / 2f, radialHeight, (centerZ + halfSizeZ) / 2f); - - boolean sleeveNegative = dir.getAxisDirection() == Direction.AxisDirection.NEGATIVE; - if (dir.getAxis() == Direction.Axis.Z) { - sleeveNegative = !sleeveNegative; - } - - Map mapFacesIn = Maps.newEnumMap(Direction.class); - - for (Direction face : Direction.values()) { - if (dir.getOpposite() != face) { - BlockFaceUV uvface; - if (face.getAxis().isHorizontal()) { - boolean facePositive = face.getAxisDirection() == Direction.AxisDirection.POSITIVE; - uvface = new BlockFaceUV(new float[]{facePositive ? 16 - radialHeight : 0, (sleeveNegative ? 16 - halfSize : 0), facePositive ? 16 : radialHeight, (sleeveNegative ? 16 : halfSize)}, getFaceAngle(dir.getAxis(), face)); - } else { - uvface = new BlockFaceUV(new float[]{8 - radius, sleeveNegative ? 16 - halfSize : 0, 8 + radius, sleeveNegative ? 16 : halfSize}, getFaceAngle(dir.getAxis(), face)); + sleeves[horIndex][r] = unbakedSleeves.bake(baker, radius, dir); + verts[horIndex][r] = unbakedVerts.bake(baker, radius, dir); } - mapFacesIn.put(face, new BlockElementFace(null, -1, null, uvface)); - } - } - - BlockElement part = new BlockElement(posFrom, posTo, mapFacesIn, null, true); - List quads = new ArrayList<>(); - FaceBakery faceBakery = new FaceBakery(); - - for (Map.Entry e : part.faces.entrySet()) { - Direction face = e.getKey(); - quads.add(faceBakery.bakeQuad(part.from, part.to, e.getValue(), barkTexture, face, BlockModelRotation.X0_Y0, part.rotation, true)); - } - - return quads; - } - - private List bakeVert(int radius, Direction dir) { - int radialHeight = getRadialHeight(radius); - List quads = new ArrayList<>(); - FaceBakery faceBakery = new FaceBakery(); - - AABB partBoundary = new AABB(8 - radius, radialHeight, 8 - radius, 8 + radius, 16 + radialHeight, 8 + radius) - .move(dir.getStepX() * 7, 0, dir.getStepZ() * 7); - - for (int i = 0; i < 2; i++) { - AABB pieceBoundary = partBoundary.intersect(new AABB(0, 0, 0, 16, 16, 16).move(0, 16 * i, 0)); - - for (Direction face : Direction.values()) { - Map mapFacesIn = Maps.newEnumMap(Direction.class); - - BlockFaceUV uvface = new BlockFaceUV(modUV(getUVs(pieceBoundary, face)), getFaceAngle(Direction.Axis.Y, face)); - mapFacesIn.put(face, new BlockElementFace(null, -1, null, uvface)); - - Vector3f[] limits = AABBLimits(pieceBoundary); - - BlockElement part = new BlockElement(limits[0], limits[1], mapFacesIn, null, true); - quads.add(faceBakery.bakeQuad(part.from, part.to, part.faces.get(face), barkTexture, face, BlockModelRotation.X0_Y0, part.rotation, true)); - } - } - - return quads; - } - - public List bakeCore(int radius, Direction.Axis axis) { - int radialHeight = getRadialHeight(radius); - - Vector3f posFrom = new Vector3f(8 - radius, 0, 8 - radius); - Vector3f posTo = new Vector3f(8 + radius, radialHeight, 8 + radius); - - Map mapFacesIn = Maps.newEnumMap(Direction.class); - - for (Direction face : Direction.values()) { - BlockFaceUV uvface; - if (face.getAxis().isHorizontal()) { - boolean positive = face.getAxisDirection() == Direction.AxisDirection.POSITIVE; - uvface = new BlockFaceUV(new float[]{positive ? 16 - radialHeight : 0, 8 - radius, positive ? 16 : radialHeight, 8 + radius}, getFaceAngle(axis, face)); - } else { - uvface = new BlockFaceUV(new float[]{8 - radius, 8 - radius, 8 + radius, 8 + radius}, getFaceAngle(axis, face)); } - - mapFacesIn.put(face, new BlockElementFace(null, -1, null, uvface)); - } - - BlockElement part = new BlockElement(posFrom, posTo, mapFacesIn, null, true); - List quads = new ArrayList<>(); - FaceBakery faceBakery = new FaceBakery(); - - for (Map.Entry e : part.faces.entrySet()) { - Direction face = e.getKey(); - quads.add(faceBakery.bakeQuad(part.from, part.to, e.getValue(), barkTexture, face, BlockModelRotation.X0_Y0, part.rotation, true)); - } - - return quads; - } - - public int getFaceAngle(Direction.Axis axis, Direction face) { - if (axis == Direction.Axis.Y) { - return 0; - } else if (axis == Direction.Axis.Z) { - return switch (face) { - case UP -> 0; - case WEST, NORTH -> 270; - case DOWN -> 180; - default -> 90; - }; - } else { - return (face == Direction.NORTH) ? 270 : 90; + cores[0][r] = unbakedCores.bake(baker, radius, Direction.Axis.Z); //NORTH<->SOUTH + cores[1][r] = unbakedCores.bake(baker, radius, Direction.Axis.X); //WEST<->EAST } - } - public float[] getUVs(AABB box, Direction face) { - return switch (face) { - case UP -> new float[]{(float) box.minX, (float) box.minZ, (float) box.maxX, (float) box.maxZ}; - case NORTH -> new float[]{16f - (float) box.maxX, (float) box.minY, 16f - (float) box.minX, (float) box.maxY}; - case SOUTH -> new float[]{(float) box.minX, (float) box.minY, (float) box.maxX, (float) box.maxY}; - case WEST -> new float[]{(float) box.minZ, (float) box.minY, (float) box.maxZ, (float) box.maxY}; - case EAST -> new float[]{16f - (float) box.maxZ, (float) box.minY, 16f - (float) box.minZ, (float) box.maxY}; - default -> new float[]{(float) box.minX, 16f - (float) box.minZ, (float) box.maxX, 16f - (float) box.maxZ}; - }; + return new SurfaceRootBlockBakedModel(cores, sleeves, verts, barkMat); } - public float[] modUV(float[] uvs) { - uvs[0] = (int) uvs[0] & 0xf; - uvs[1] = (int) uvs[1] & 0xf; - uvs[2] = (((int) uvs[2] - 1) & 0xf) + 1; - uvs[3] = (((int) uvs[3] - 1) & 0xf) + 1; - return uvs; - } - - public Vector3f[] AABBLimits(AABB aabb) { - return new Vector3f[]{ - new Vector3f((float) aabb.minX, (float) aabb.minY, (float) aabb.minZ), - new Vector3f((float) aabb.maxX, (float) aabb.maxY, (float) aabb.maxZ), - }; - } + /////////////////////////////////////////// + // FABRIC DYNAMIC GEOMETRY + /////////////////////////////////////////// @Override - public boolean isVanillaAdapter() { - return false; + public void collectParts(BlockAndTintGetter level, BlockPos pos, BlockState state, RandomSource random, List parts) { + collectParts(state, parts, ModelHelper.getRootConnections(level, pos, state)); } @Override - public void emitBlockQuads(BlockAndTintGetter blockView, BlockState state, BlockPos pos, Supplier randomSupplier, RenderContext context) { - EnumMap> bakedQuads = collectQuads(blockView, state, pos); - if (bakedQuads == null) return; - - QuadEmitter emitter = context.getEmitter(); - RenderMaterial material = getRenderMaterial(); - if (material == null) return; - - bakedQuads.forEach((dir, quads) -> - emitQuads(dir, emitter, material, quads)); + public Object createGeometryKey(BlockAndTintGetter level, BlockPos pos, BlockState state, RandomSource random) { + return ModelHelper.getRootConnections(level, pos, state); } - @Nullable - public EnumMap> collectQuads(BlockAndTintGetter getter, BlockState state, BlockPos pos) { - if (state == null) return null; + /////////////////////////////////////////// + // PART COLLECTION + /////////////////////////////////////////// - int coreRadius = getRadius(state); - if (coreRadius <= 0 || coreRadius > 8) return null; + @Override + public void collectParts(BlockState state, List parts, Connections connectionsData) { + int coreRadius = 0; + if (state.getBlock() instanceof SurfaceRootBlock root) { + coreRadius = root.getRadius(state); + } + if (coreRadius == 0) return; int[] connections = new int[]{0, 0, 0, 0}; RootConnections.ConnectionLevel[] connectionLevels = RootConnections.PLACEHOLDER_CONNECTION_LEVELS.clone(); - if (state.getBlock() instanceof SurfaceRootBlock surfaceRootBlock) { - RootConnections connectionData = surfaceRootBlock.getConnectionData(getter, pos); - connections = connectionData.getAllRadii(); - connectionLevels = connectionData.getConnectionLevels(); + if (connectionsData instanceof RootConnections rootConnections) { + connections = rootConnections.getAllRadii(); + connectionLevels = rootConnections.getConnectionLevels(); } for (int i = 0; i < connections.length; i++) { connections[i] = Mth.clamp(connections[i], 0, coreRadius); } - Direction sourceDir = getSourceDir(coreRadius, connections); + //The source direction is the biggest connection from one of the horizontal directions + Direction sourceDir = get2DSourceDir(coreRadius, connections); if (sourceDir == null) { sourceDir = Direction.DOWN; } int coreDir = resolveCoreDir(sourceDir); boolean isGrounded = state.getValue(SurfaceRootBlock.GROUNDED); + if (isGrounded) { + parts.add(cores[coreDir][coreRadius - 1]); + } - EnumMap> bakedQuads = new EnumMap<>(Direction.class); - - for (Direction face : Direction.values()) { - List quads = bakedQuads.computeIfAbsent(face, dir->new ArrayList<>()); - if (isGrounded) { - quads.addAll(coresQuads[coreDir][coreRadius - 1]); - } - - if (coreRadius != 8) { - for (Direction connDir : CoordUtils.HORIZONTALS) { - int idx = connDir.get2DDataValue(); - int connRadius = connections[idx]; - if (connRadius > 0) { - if (isGrounded && sleevesQuads[idx][connRadius - 1] != null) { - quads.addAll(sleevesQuads[idx][connRadius - 1]); - } - if (connectionLevels[idx] == RootConnections.ConnectionLevel.HIGH && vertsQuads[idx][connRadius - 1] != null) { - quads.addAll(vertsQuads[idx][connRadius - 1]); - } + //Get quads for sleeves models + if (coreRadius != 8) { //Special case for r!=8.. If it's a solid block so it has no sleeves + for (Direction connDir : CoordUtils.HORIZONTALS) { + int idx = connDir.get2DDataValue(); + int connRadius = connections[idx]; + if (connRadius > 0) { + if (isGrounded && sleeves[idx][connRadius - 1] != null) { + parts.add(sleeves[idx][connRadius - 1]); + } + if (connectionLevels[idx] == RootConnections.ConnectionLevel.HIGH && verts[idx][connRadius - 1] != null) { + parts.add(verts[idx][connRadius - 1]); } } } } - return bakedQuads; - } - - protected static void emitQuads(Direction face, QuadEmitter emitter, RenderMaterial material, List quads) { - if (quads == null) return; - for (BakedQuad quad : quads) { - if (quad.getDirection() == face) { - emitter.fromVanilla(quad, material, null); - emitter.emit(); - } - } - } - - protected static RenderMaterial getRenderMaterial() { - var renderer = RendererAccess.INSTANCE.getRenderer(); - if (renderer == null) return null; - MaterialFinder finder = renderer.materialFinder(); - -// finder.disableAo(0, true); -// finder.disableDiffuse(0, true); - - return finder.find(); } - @Override - public void emitItemQuads(ItemStack stack, Supplier randomSupplier, RenderContext context) { + /** + * Converts direction DUNSWE to 2 axis numbers for Z,X + */ + protected int resolveCoreDir(Direction dir) { + return dir.getAxis() == Direction.Axis.X ? 1 : 0; } - protected Direction getSourceDir(int coreRadius, int[] connections) { + @Nullable + public static Direction get2DSourceDir(int coreRadius, int[] connections) { int largestConnection = 0; Direction sourceDir = null; for (Direction dir : CoordUtils.HORIZONTALS) { - int horIndex = dir.get2DDataValue(); - int connRadius = connections[horIndex]; + int connRadius = connections[dir.get2DDataValue()]; if (connRadius > largestConnection) { largestConnection = connRadius; sourceDir = dir; @@ -325,56 +160,22 @@ protected Direction getSourceDir(int coreRadius, int[] connections) { } if (largestConnection < coreRadius) { - sourceDir = null; + sourceDir = null; //Has no source node } return sourceDir; } - protected int resolveCoreDir(Direction dir) { - return dir.getAxis() == Direction.Axis.X ? 1 : 0; - } - - protected int getRadius(BlockState blockState) { - return ((SurfaceRootBlock) blockState.getBlock()).getRadius(blockState); - } - - @Override - public List getQuads(@Nullable BlockState state, @Nullable Direction direction, RandomSource random) { - return Collections.emptyList(); - } - - @Override - public boolean useAmbientOcclusion() { - return true; - } - - @Override - public boolean isGui3d() { - return false; - } - - @Override - public boolean usesBlockLight() { - return false; - } - - @Override - public boolean isCustomRenderer() { - return true; - } - - @Override - public TextureAtlasSprite getParticleIcon() { - return barkTexture; - } + /////////////////////////////////////////// + // VANILLA MODEL METHODS + /////////////////////////////////////////// @Override - public ItemTransforms getTransforms() { - return ItemTransforms.NO_TRANSFORMS; + public Material.Baked particleMaterial() { + return particleMaterial; } @Override - public ItemOverrides getOverrides() { - return ItemOverrides.EMPTY; + public @BakedQuad.MaterialFlags int materialFlags() { + return this.cores[0][0].materialFlags(); } } diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/ThickBranchBlockBakedModel.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/ThickBranchBlockBakedModel.java index 8616a2817..a53f60c29 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/ThickBranchBlockBakedModel.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/ThickBranchBlockBakedModel.java @@ -1,217 +1,97 @@ package com.dtteam.dynamictrees.model.baked; +import com.dtteam.dynamictrees.api.network.Connections; import com.dtteam.dynamictrees.block.branch.BranchBlock; import com.dtteam.dynamictrees.block.branch.ThickBranchBlock; -import com.dtteam.dynamictrees.utility.CoordUtils; -import com.dtteam.dynamictrees.utility.CoordUtils.Surround; -import com.google.common.collect.Maps; -import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter; -import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; -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.RendererAccess; -import net.minecraft.client.renderer.block.model.BakedQuad; -import net.minecraft.client.renderer.block.model.BlockElement; -import net.minecraft.client.renderer.block.model.BlockElementFace; -import net.minecraft.client.renderer.block.model.BlockFaceUV; -import net.minecraft.client.renderer.block.model.FaceBakery; -import net.minecraft.client.renderer.texture.TextureAtlasSprite; -import net.minecraft.client.resources.model.BlockModelRotation; -import net.minecraft.core.BlockPos; +import com.dtteam.dynamictrees.model.BranchMultiPartHolder; +import com.dtteam.dynamictrees.model.ModelConnections; +import com.dtteam.dynamictrees.model.parts.BranchModelPart; +import com.dtteam.dynamictrees.tree.TreeHelper; +import net.minecraft.client.renderer.block.dispatch.BlockStateModelPart; +import net.minecraft.client.resources.model.ModelBaker; import net.minecraft.core.Direction; -import net.minecraft.core.Direction.Axis; -import net.minecraft.core.Vec3i; import net.minecraft.util.Mth; -import net.minecraft.util.RandomSource; -import net.minecraft.world.level.BlockAndTintGetter; import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.phys.AABB; -import net.minecraft.world.phys.Vec3; -import org.joml.Vector3f; -import java.util.ArrayList; -import java.util.EnumMap; +import java.util.EnumSet; import java.util.List; -import java.util.Map; -import java.util.function.Supplier; -@SuppressWarnings("unchecked") +/** + * Dynamic thick branch (trunk) model for Fabric. Mirrors the NeoForge {@code ThickBranchBlockStateModel}. + */ public class ThickBranchBlockBakedModel extends BasicBranchBlockBakedModel { - private final TextureAtlasSprite thickRingsTexture; - private final List[] trunksBarkQuads = new List[16]; - private final List[] trunksTopBarkQuads = new List[16]; - private final List[] trunksTopRingsQuads = new List[16]; - public final List[] trunksBotRingsQuads = new List[16]; + private final BranchMultiPartHolder trunkBark; // The trunk will always feature bark on its sides. + private final BranchMultiPartHolder trunkRings; // The trunk will feature rings on its top and bottom. - public ThickBranchBlockBakedModel(TextureAtlasSprite barkTexture, TextureAtlasSprite ringsTexture, TextureAtlasSprite thickRingsTexture) { - super(barkTexture, ringsTexture); - this.thickRingsTexture = thickRingsTexture; - initThickModels(); + public ThickBranchBlockBakedModel(BasicBranchBlockBakedModel fallback, + BranchMultiPartHolder trunkBark, BranchMultiPartHolder trunkRings) { + super(fallback.cores, fallback.sleeves, fallback.rings, fallback.sleeveRings); + this.trunkBark = trunkBark; + this.trunkRings = trunkRings; } - private void initThickModels() { - for (int i = 0; i < ThickBranchBlock.MAX_RADIUS_THICK - ThickBranchBlock.MAX_RADIUS; i++) { - int radius = i + ThickBranchBlock.MAX_RADIUS + 1; - trunksBarkQuads[i] = bakeTrunkBark(radius, this.barkTexture, true); - trunksTopBarkQuads[i] = bakeTrunkBark(radius, this.barkTexture, false); - trunksTopRingsQuads[i] = bakeTrunkRings(radius, thickRingsTexture, Direction.UP); - trunksBotRingsQuads[i] = bakeTrunkRings(radius, thickRingsTexture, Direction.DOWN); - } - } - - public List bakeTrunkBark(int radius, TextureAtlasSprite bark, boolean side) { - List quads = new ArrayList<>(); - FaceBakery faceBakery = new FaceBakery(); - AABB wholeVolume = new AABB(8 - radius, 0, 8 - radius, 8 + radius, 16, 8 + radius); - - final Direction[] run = side ? CoordUtils.HORIZONTALS : new Direction[]{Direction.UP, Direction.DOWN}; - ArrayList offsets = new ArrayList<>(); - - for (Surround dir : Surround.values()) { - offsets.add(dir.getOffset()); - } - offsets.add(new Vec3i(0, 0, 0)); - - for (Direction face : run) { - final Vec3i dirVector = face.getNormal(); - - for (Vec3i offset : offsets) { - if (face.getAxis() == Axis.Y || new Vec3(dirVector.getX(), dirVector.getY(), dirVector.getZ()).add(new Vec3(offset.getX(), offset.getY(), offset.getZ())).lengthSqr() > 2.25) { - Vec3 scaledOffset = new Vec3(offset.getX() * 16, offset.getY() * 16, offset.getZ() * 16); - AABB partBoundary = new AABB(0, 0, 0, 16, 16, 16).move(scaledOffset).intersect(wholeVolume); - - Vector3f[] limits = aabbLimits(partBoundary); - - Map mapFacesIn = Maps.newEnumMap(Direction.class); - - BlockFaceUV uvface = new BlockFaceUV(modUV(getUVs(partBoundary, face)), getFaceAngle(Axis.Y, face)); - mapFacesIn.put(face, new BlockElementFace(null, -1, null, uvface)); - - BlockElement part = new BlockElement(limits[0], limits[1], mapFacesIn, null, true); - quads.add(faceBakery.bakeQuad(part.from, part.to, part.faces.get(face), bark, face, BlockModelRotation.X0_Y0, part.rotation, true)); - } - } + /** + * Bakes a thick branch model out of the given unbaked trunk parts and a regular branch fallback. + * Mirrors the NeoForge {@code UnbakedBranchModel#bakeThick}. + */ + public static ThickBranchBlockBakedModel bakeThick( + ModelBaker baker, BasicBranchBlockBakedModel fallback, + BranchModelPart.UnbakedThickTrunk unbakedBark, BranchModelPart.UnbakedThickTrunk unbakedRings) { + BranchMultiPartHolder trunksBark = new BranchMultiPartHolder(); + BranchMultiPartHolder trunksRings = new BranchMultiPartHolder(); + + for (int radius = BranchBlock.MAX_RADIUS + 1; radius <= ThickBranchBlock.MAX_RADIUS_THICK; radius++) { + trunksBark.putAllParts(radius, unbakedBark.bakeAllSides(baker, radius)); + trunksRings.putAllParts(radius, unbakedRings.bakeSides(baker, radius, EnumSet.of(Direction.UP, Direction.DOWN))); } - return quads; + return new ThickBranchBlockBakedModel(fallback, trunksBark, trunksRings); } - public List bakeTrunkRings(int radius, TextureAtlasSprite ring, Direction face) { - List quads = new ArrayList<>(); - FaceBakery faceBakery = new FaceBakery(); - AABB wholeVolume = new AABB(8 - radius, 0, 8 - radius, 8 + radius, 16, 8 + radius); - int wholeVolumeWidth = 48; - - ArrayList offsets = new ArrayList<>(); - - for (Surround dir : Surround.values()) { - offsets.add(dir.getOffset()); + @Override + public void collectParts(BlockState state, List parts, Connections connectionsData) { + int coreRadius = TreeHelper.getRadius(state); + if (coreRadius <= BranchBlock.MAX_RADIUS) { + super.collectParts(state, parts, connectionsData); + return; } - offsets.add(new Vec3i(0, 0, 0)); + coreRadius = Mth.clamp(coreRadius, BranchBlock.MAX_RADIUS + 1, ThickBranchBlock.MAX_RADIUS_THICK); + if (!(connectionsData instanceof ModelConnections modelConnections)) return; - for (Vec3i offset : offsets) { - Vec3 scaledOffset = new Vec3(offset.getX() * 16, offset.getY() * 16, offset.getZ() * 16); - AABB partBoundary = new AABB(0, 0, 0, 16, 16, 16).move(scaledOffset).intersect(wholeVolume); + final int[] connections = connectionsData.getAllRadii(); + final Direction forceRingDir = modelConnections.getRingOnly(); + final int twigRadius = modelConnections.getFamily().getPrimaryThickness(); - Vector3f posFrom = new Vector3f((float) partBoundary.minX, (float) partBoundary.minY, (float) partBoundary.minZ); - Vector3f posTo = new Vector3f((float) partBoundary.maxX, (float) partBoundary.maxY, (float) partBoundary.maxZ); + int numConnections = countConnections(connections); - Map mapFacesIn = Maps.newEnumMap(Direction.class); - float[] uvs = getRingsUvs(face, partBoundary, wholeVolumeWidth); + if (numConnections == 0 && forceRingDir != null) return; - BlockFaceUV uvFace = new BlockFaceUV(uvs, getFaceAngle(Axis.Y, face)); - mapFacesIn.put(face, new BlockElementFace(null, -1, null, uvFace)); - - BlockElement part = new BlockElement(posFrom, posTo, mapFacesIn, null, true); - quads.add(faceBakery.bakeQuad(part.from, part.to, part.faces.get(face), ring, face, BlockModelRotation.X0_Y0, part.rotation, true)); + if (forceRingDir != null) { + connections[forceRingDir.get3DDataValue()] = 0; + addPart(parts, trunkRings.getPart(forceRingDir, coreRadius)); } - return quads; - } - - private static float[] getRingsUvs(Direction face, AABB partBoundary, int wholeVolumeWidth) { - float textureOffsetX = -16f; - float textureOffsetZ = -16f; + boolean branchesAround = areBranchesAround(connections); - float minX = ((float) ((partBoundary.minX - textureOffsetX) / wholeVolumeWidth)) * 16f; - float maxX = ((float) ((partBoundary.maxX - textureOffsetX) / wholeVolumeWidth)) * 16f; - float minZ = ((float) ((partBoundary.minZ - textureOffsetZ) / wholeVolumeWidth)) * 16f; - float maxZ = ((float) ((partBoundary.maxZ - textureOffsetZ) / wholeVolumeWidth)) * 16f; - - if (face == Direction.DOWN) { - minZ = ((float) ((partBoundary.maxZ - textureOffsetZ) / wholeVolumeWidth)) * 16f; - maxZ = ((float) ((partBoundary.minZ - textureOffsetZ) / wholeVolumeWidth)) * 16f; + for (Direction face : Direction.values()) { + gatherTrunkParts(parts, face, connections, twigRadius, branchesAround, coreRadius); } - - return new float[]{minX, minZ, maxX, maxZ}; - } - - public static float[] getUVs(AABB box, Direction face) { - return switch (face) { - case UP -> new float[]{(float) box.minX, (float) box.minZ, (float) box.maxX, (float) box.maxZ}; - case NORTH -> new float[]{16f - (float) box.maxX, (float) box.minY, 16f - (float) box.minX, (float) box.maxY}; - case SOUTH -> new float[]{(float) box.minX, (float) box.minY, (float) box.maxX, (float) box.maxY}; - case WEST -> new float[]{(float) box.minZ, (float) box.minY, (float) box.maxZ, (float) box.maxY}; - case EAST -> new float[]{16f - (float) box.maxZ, (float) box.minY, 16f - (float) box.minZ, (float) box.maxY}; - default -> new float[]{(float) box.minX, 16f - (float) box.minZ, (float) box.maxX, 16f - (float) box.maxZ}; - }; } - public static float[] modUV(float[] uvs) { - uvs[0] = (int) uvs[0] & 0xf; - uvs[1] = (int) uvs[1] & 0xf; - uvs[2] = (((int) uvs[2] - 1) & 0xf) + 1; - uvs[3] = (((int) uvs[3] - 1) & 0xf) + 1; - return uvs; + private static boolean areBranchesAround(int[] connections) { + return connections[2] + connections[3] + connections[4] + connections[5] != 0; } - public static Vector3f[] aabbLimits(AABB aabb) { - return new Vector3f[]{ - new Vector3f((float) aabb.minX, (float) aabb.minY, (float) aabb.minZ), - new Vector3f((float) aabb.maxX, (float) aabb.maxY, (float) aabb.maxZ), - }; - } - - @Override - protected int maxBranchRadius() { - return ThickBranchBlock.MAX_RADIUS_THICK; - } - - @Override - public EnumMap> collectQuads(int coreRadius, int[] connections, int twigRadius, Direction forceRingDir) { - if (coreRadius <= BranchBlock.MAX_RADIUS) { - return super.collectQuads(coreRadius, connections, twigRadius, forceRingDir); - } - - boolean branchesAround = connections[2] + connections[3] + connections[4] + connections[5] != 0; - - int radiusIndex = coreRadius - BranchBlock.MAX_RADIUS - 1; - if (radiusIndex >= trunksBarkQuads.length) return null; - - EnumMap> bakedQuads = new EnumMap<>(Direction.class); - - for (Direction face : Direction.values()) { - List quads = bakedQuads.computeIfAbsent(face, dir->new ArrayList<>()); - quads.addAll(trunksBarkQuads[radiusIndex]); - - if (face == Direction.UP || face == Direction.DOWN) { - if (connections[face.get3DDataValue()] < twigRadius && !branchesAround) { - quads.addAll(trunksTopRingsQuads[radiusIndex]); - } else if (connections[face.get3DDataValue()] < coreRadius) { - quads.addAll(trunksTopBarkQuads[radiusIndex]); - } + private void gatherTrunkParts(List parts, Direction face, int[] connections, int twigRadius, boolean branchesAround, int coreRadius) { + if (face == Direction.UP || face == Direction.DOWN) { + if (connections[face.get3DDataValue()] < twigRadius && !branchesAround) { + addPart(parts, this.trunkRings.getPart(face, coreRadius)); + } else if (connections[face.get3DDataValue()] < coreRadius) { + addPart(parts, this.trunkBark.getPart(face, coreRadius)); } + } else { + addPart(parts, trunkBark.getPart(face, coreRadius)); } - - return bakedQuads; - } - - @Override - public List getRingQuads(int radius) { - if (radius <= BranchBlock.MAX_RADIUS){ - return super.getRingQuads(radius); - } - return trunksBotRingsQuads[radius - BranchBlock.MAX_RADIUS - 1]; } } diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/AerialRootsSoilBlockStateModel.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/AerialRootsSoilBlockStateModel.java new file mode 100644 index 000000000..fe8493ca4 --- /dev/null +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/AerialRootsSoilBlockStateModel.java @@ -0,0 +1,117 @@ +package com.dtteam.dynamictrees.model.blockstate; + +import com.dtteam.dynamictrees.model.BlockStateModelWithRadius; +import com.dtteam.dynamictrees.model.FabricDynamicBlockStateModel; +import com.dtteam.dynamictrees.model.parts.AerialRootSoilModelPart; +import com.dtteam.dynamictrees.tree.TreeHelper; +import com.dtteam.dynamictrees.tree.family.Family; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.fabricmc.fabric.api.client.model.loading.v1.CustomUnbakedBlockStateModel; +import net.minecraft.client.renderer.block.BlockAndTintGetter; +import net.minecraft.client.renderer.block.dispatch.BlockStateModel; +import net.minecraft.client.renderer.block.dispatch.BlockStateModelPart; +import net.minecraft.client.resources.model.ModelBaker; +import net.minecraft.client.resources.model.ResolvableModel; +import net.minecraft.client.resources.model.geometry.BakedQuad; +import net.minecraft.client.resources.model.sprite.Material; +import net.minecraft.core.BlockPos; +import net.minecraft.resources.Identifier; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.block.state.BlockState; + +import java.util.List; +import java.util.Optional; + +/** + * Fabric port of the NeoForge {@code AerialRootsSoilBlockStateModel}; deserialized from + * blockstate JSONs with type {@code dynamictrees:aerial_roots_soil}. + */ +public record AerialRootsSoilBlockStateModel( + AerialRootSoilModelPart[] soilParts +) implements FabricDynamicBlockStateModel, BlockStateModelWithRadius { + + private record RadiusGeometryKey(AerialRootsSoilBlockStateModel model, int radius) {} + + @Override + public Object createGeometryKey(BlockAndTintGetter level, BlockPos pos, BlockState state, RandomSource random) { + return new RadiusGeometryKey(this, TreeHelper.getRadius(state)); + } + + @Override + public void collectParts(BlockState state, List parts, int radius) { + if (radius == 0 || radius > 8) return; + parts.add(soilParts[radius - 1]); + } + + @Override + public void collectParts(BlockAndTintGetter level, BlockPos pos, BlockState state, RandomSource random, List parts) { + collectParts(state, parts, TreeHelper.getRadius(state)); + } + + public record Unbaked(Identifier end, Identifier overlay, Identifier overlay_end, Identifier side, Optional family) implements CustomUnbakedBlockStateModel { + + public static final String END_TEXTURE = "end"; + public static final String OVERLAY_TEXTURE = "overlay"; + public static final String OVERLAY_END_TEXTURE = "overlay_end"; + public static final String SIDE_TEXTURE = "side"; + public static final String TEXTURES = "textures"; + public static final String FAMILY = "family"; + + private record RootsSoilTextures(Identifier end, Identifier overlay, Identifier overlay_end, Identifier side) { + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(i -> i.group( + Identifier.CODEC.fieldOf(END_TEXTURE).forGetter(RootsSoilTextures::end), + Identifier.CODEC.fieldOf(OVERLAY_TEXTURE).forGetter(RootsSoilTextures::overlay), + Identifier.CODEC.fieldOf(OVERLAY_END_TEXTURE).forGetter(RootsSoilTextures::overlay_end), + Identifier.CODEC.fieldOf(SIDE_TEXTURE).forGetter(RootsSoilTextures::side) + ).apply(i, RootsSoilTextures::new)); + } + + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(i -> i.group( + RootsSoilTextures.CODEC.codec().fieldOf(TEXTURES).forGetter(m -> + new RootsSoilTextures(m.end(), m.overlay(), m.overlay_end(), m.side())), + Family.CODEC.optionalFieldOf(FAMILY).forGetter(Unbaked::family) + ).apply(i, (textures, family) -> + new Unbaked(textures.end(), textures.overlay(), textures.overlay_end(), textures.side(), family))); + + @Override + public MapCodec codec() { + return CODEC; + } + + @Override + public BlockStateModel bake(ModelBaker baker) { + AerialRootSoilModelPart[] soilParts = new AerialRootSoilModelPart[8]; + + Material.Baked endMat = bakeMaterial(baker, end); + Material.Baked overlayMat = bakeMaterial(baker, overlay); + Material.Baked overlayEndMat = bakeMaterial(baker, overlay_end); + Material.Baked sideMat = bakeMaterial(baker, side); + + AerialRootSoilModelPart.UnbakedPart unbakedPart = new AerialRootSoilModelPart.UnbakedPart(endMat, overlayMat, overlayEndMat, sideMat); + + for (int i = 0; i < 8; i++) { + soilParts[i] = unbakedPart.bake(baker, i + 1); + } + + return new AerialRootsSoilBlockStateModel(soilParts); + } + + private Material.Baked bakeMaterial(ModelBaker baker, Identifier texture) { + return baker.materials().get(new Material(texture, false), texture::toDebugFileName); + } + + @Override + public void resolveDependencies(ResolvableModel.Resolver resolver) {} + } + + @Override + public @BakedQuad.MaterialFlags int materialFlags() { + return soilParts[0].materialFlags(); + } + + @Override + public Material.Baked particleMaterial() { + return soilParts[0].particleMaterial(); + } +} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/PottedSaplingBlockStateModel.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/PottedSaplingBlockStateModel.java new file mode 100644 index 000000000..4b9680855 --- /dev/null +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/PottedSaplingBlockStateModel.java @@ -0,0 +1,138 @@ +package com.dtteam.dynamictrees.model.blockstate; + +import com.dtteam.dynamictrees.block.sapling.PottedSaplingBlockEntity; +import com.dtteam.dynamictrees.model.FabricDynamicBlockStateModel; +import com.dtteam.dynamictrees.model.ModelHelper; +import com.dtteam.dynamictrees.tree.species.Species; +import com.mojang.math.Transformation; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.fabricmc.fabric.api.client.model.loading.v1.CustomUnbakedBlockStateModel; +import net.minecraft.client.renderer.block.BlockAndTintGetter; +import net.minecraft.client.renderer.block.dispatch.BlockStateModel; +import net.minecraft.client.renderer.block.dispatch.BlockStateModelPart; +import net.minecraft.client.renderer.block.dispatch.ModelState; +import net.minecraft.client.resources.model.ModelBaker; +import net.minecraft.client.resources.model.ResolvableModel; +import net.minecraft.client.resources.model.ResolvedModel; +import net.minecraft.client.resources.model.SimpleModelWrapper; +import net.minecraft.client.resources.model.geometry.BakedQuad; +import net.minecraft.client.resources.model.sprite.Material; +import net.minecraft.client.resources.model.sprite.TextureSlots; +import net.minecraft.core.BlockPos; +import net.minecraft.resources.Identifier; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.block.state.BlockState; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.joml.Vector3f; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Fabric port of the NeoForge {@code PottedSaplingBlockStateModel}; deserialized from blockstate + * JSONs with type {@code dynamictrees:potted_dynamic_sapling}. Retrieves the potted species from + * the block entity (NeoForge uses model data instead). + */ +public record PottedSaplingBlockStateModel( + BlockStateModelPart pot, + Map saplings, + Material.Baked particleMaterial +) implements FabricDynamicBlockStateModel { + + @Override + public @BakedQuad.MaterialFlags int materialFlags() { + return this.pot.materialFlags(); + } + + @Nullable + private static Species getSpecies(BlockAndTintGetter level, BlockPos pos) { + return level.getBlockEntity(pos) instanceof PottedSaplingBlockEntity pottedSapling + ? pottedSapling.getSpecies() : null; + } + + @Override + public Object createGeometryKey(BlockAndTintGetter level, BlockPos pos, BlockState state, RandomSource random) { + return getSpecies(level, pos); + } + + @Override + public void collectParts(BlockAndTintGetter level, BlockPos pos, BlockState state, RandomSource random, List parts) { + Species species = getSpecies(level, pos); + if (species == null || !species.isValid() || species.getSapling().isEmpty()) return; + + SimpleModelWrapper sapling = saplings.get(species); + if (sapling == null) return; + + parts.add(pot); + parts.add(sapling); + } + + public record Unbaked(Identifier modelLocation) implements CustomUnbakedBlockStateModel { + + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(i -> i.group( + Identifier.CODEC.fieldOf("model").forGetter(Unbaked::modelLocation) + ).apply(i, Unbaked::new)); + + @Override + public MapCodec codec() { + return CODEC; + } + + @Override + public BlockStateModel bake(ModelBaker modelBaker) { + ResolvedModel pot = modelBaker.getModel(modelLocation); + TextureSlots potSlots = pot.getTopTextureSlots(); + Material.Baked material = pot.resolveParticleMaterial(potSlots, modelBaker); + + SimpleModelWrapper bakedPot = new SimpleModelWrapper( + pot.bakeTopGeometry(potSlots, modelBaker, ModelHelper.noState()), + pot.getTopAmbientOcclusion(), + material + ); + + Map saplings = new HashMap<>(); + + for (Species species : Species.REGISTRY) { + if (species.getSapling().isPresent()) { + Identifier saplingModelLocation = species.getSaplingModelLocation(); + ResolvedModel resolved = modelBaker.getModel(saplingModelLocation); + + TextureSlots saplingSlots = resolved.getTopTextureSlots(); + saplings.put(species, new SimpleModelWrapper( + resolved.bakeTopGeometry(saplingSlots, modelBaker, OFFSET_UP), + resolved.getTopAmbientOcclusion(), + resolved.resolveParticleMaterial(saplingSlots, modelBaker))); + } + } + + return new PottedSaplingBlockStateModel(bakedPot, saplings, material); + } + + @Override + public void resolveDependencies(ResolvableModel.Resolver resolver) { + resolver.markDependency(this.modelLocation); + // Also mark the sapling models so bake() can safely resolve them. + for (Species species : Species.REGISTRY) { + if (species.getSapling().isPresent()) { + resolver.markDependency(species.getSaplingModelLocation()); + } + } + } + } + + public static final ModelState OFFSET_UP = new ModelState() { + private static final Transformation TRANSFORM = new Transformation( + new Vector3f(0f, 0.25f, 0f), + null, null, null + ); + + @Override + @NotNull + public Transformation transformation() { + return TRANSFORM; + } + }; +} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/UnbakedBranchModel.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/UnbakedBranchModel.java new file mode 100644 index 000000000..d17f9a023 --- /dev/null +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/UnbakedBranchModel.java @@ -0,0 +1,79 @@ +package com.dtteam.dynamictrees.model.blockstate; + +import com.dtteam.dynamictrees.DynamicTrees; +import com.dtteam.dynamictrees.model.baked.BasicBranchBlockBakedModel; +import com.dtteam.dynamictrees.model.baked.ThickBranchBlockBakedModel; +import com.dtteam.dynamictrees.model.parts.BranchModelPart; +import com.dtteam.dynamictrees.tree.family.Family; +import com.dtteam.dynamictrees.utility.IdentifierUtils; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.fabricmc.fabric.api.client.model.loading.v1.CustomUnbakedBlockStateModel; +import net.minecraft.client.renderer.block.dispatch.BlockStateModel; +import net.minecraft.client.resources.model.ModelBaker; +import net.minecraft.client.resources.model.ResolvableModel; +import net.minecraft.client.resources.model.sprite.Material; +import net.minecraft.resources.Identifier; +import org.jetbrains.annotations.NotNull; + +import java.util.Optional; + +/** + * Fabric port of the NeoForge {@code UnbakedBranchModel}; deserialized from blockstate JSONs + * with type {@code dynamictrees:branch}. + */ +public record UnbakedBranchModel(Identifier barkTexture, Identifier ringsTexture, Optional family) implements CustomUnbakedBlockStateModel { + + public static final String BARK_TEXTURE = "bark"; + public static final String RINGS_TEXTURE = "rings"; + public static final String TEXTURES = "textures"; + public static final String FAMILY = "family"; + + private record BranchTextures(Identifier bark, Identifier rings) { + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(i -> i.group( + Identifier.CODEC.fieldOf(BARK_TEXTURE).forGetter(BranchTextures::bark), + Identifier.CODEC.fieldOf(RINGS_TEXTURE).forGetter(BranchTextures::rings) + ).apply(i, BranchTextures::new)); + } + + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(i -> i.group( + BranchTextures.CODEC.codec().fieldOf(TEXTURES).forGetter(m -> new BranchTextures(m.barkTexture(), m.ringsTexture())), + Family.CODEC.optionalFieldOf(FAMILY).forGetter(UnbakedBranchModel::family) + ).apply(i, (textures, family) -> new UnbakedBranchModel(textures.bark(), textures.rings(), family))); + + @Override + public MapCodec codec() { + return CODEC; + } + + @Override + public void resolveDependencies(ResolvableModel.Resolver resolver) {} + + @Override + public BlockStateModel bake(ModelBaker baker) { + Material.Baked barkMat = baker.materials().get(new Material(barkTexture), barkTexture::toDebugFileName); + Material.Baked ringsMat = baker.materials().get(new Material(ringsTexture), ringsTexture::toDebugFileName); + + BasicBranchBlockBakedModel regular = BasicBranchBlockBakedModel.bakeBasic(baker, + new BranchModelPart.UnbakedCore(barkMat), + new BranchModelPart.UnbakedSleeve(barkMat), + new BranchModelPart.UnbakedCore(ringsMat), + null); + + if (family.isPresent() && family.get().isThick()) { + Identifier thickRings = getThickRingsTexture(ringsTexture); + Material.Baked thickRingsMat = baker.materials().get(new Material(thickRings), thickRings::toDebugFileName); + + return ThickBranchBlockBakedModel.bakeThick(baker, regular, + new BranchModelPart.UnbakedThickTrunk(barkMat, false), + new BranchModelPart.UnbakedThickTrunk(thickRingsMat, true)); + } + return regular; + } + + private @NotNull Identifier getThickRingsTexture(Identifier ringsTexture) { + if (ringsTexture.equals(DynamicTrees.location("block/air"))) + return ringsTexture; + return IdentifierUtils.suffix(ringsTexture, "_thick"); + } +} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/UnbakedCreakingHeartModel.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/UnbakedCreakingHeartModel.java new file mode 100644 index 000000000..6dea5956d --- /dev/null +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/UnbakedCreakingHeartModel.java @@ -0,0 +1,62 @@ +package com.dtteam.dynamictrees.model.blockstate; + +import com.dtteam.dynamictrees.model.baked.BasicBranchBlockBakedModel; +import com.dtteam.dynamictrees.model.parts.BranchModelPart; +import com.dtteam.dynamictrees.tree.family.Family; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.fabricmc.fabric.api.client.model.loading.v1.CustomUnbakedBlockStateModel; +import net.minecraft.client.renderer.block.dispatch.BlockStateModel; +import net.minecraft.client.resources.model.ModelBaker; +import net.minecraft.client.resources.model.ResolvableModel; +import net.minecraft.client.resources.model.sprite.Material; +import net.minecraft.resources.Identifier; + +import java.util.Optional; + +/** + * Fabric port of the NeoForge {@code UnbakedCreakingHeartModel}; deserialized from blockstate + * JSONs with type {@code dynamictrees:creaking_heart}. + */ +public record UnbakedCreakingHeartModel(Identifier heartBark, Identifier heartRings, Identifier bark, Optional family) implements CustomUnbakedBlockStateModel { + + public static final String BARK_TEXTURE = "bark"; + public static final String HEART_BARK_TEXTURE = "heart_bark"; + public static final String HEART_RINGS_TEXTURE = "heart_rings"; + public static final String TEXTURES = "textures"; + public static final String FAMILY = "family"; + + private record HeartTextures(Identifier bark, Identifier heartBark, Identifier heartRings) { + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(i -> i.group( + Identifier.CODEC.fieldOf(BARK_TEXTURE).forGetter(HeartTextures::bark), + Identifier.CODEC.fieldOf(HEART_BARK_TEXTURE).forGetter(HeartTextures::heartBark), + Identifier.CODEC.fieldOf(HEART_RINGS_TEXTURE).forGetter(HeartTextures::heartRings) + ).apply(i, HeartTextures::new)); + } + + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(i -> i.group( + HeartTextures.CODEC.codec().fieldOf(TEXTURES).forGetter(m -> new HeartTextures(m.bark, m.heartBark, m.heartRings)), + Family.CODEC.optionalFieldOf(FAMILY).forGetter(UnbakedCreakingHeartModel::family) + ).apply(i, (textures, family) -> new UnbakedCreakingHeartModel(textures.heartBark, textures.heartRings, textures.bark, family))); + + @Override + public MapCodec codec() { + return CODEC; + } + + @Override + public void resolveDependencies(ResolvableModel.Resolver resolver) {} + + @Override + public BlockStateModel bake(ModelBaker baker) { + Material.Baked heartMat = baker.materials().get(new Material(heartBark), heartBark::toDebugFileName); + Material.Baked ringsMat = baker.materials().get(new Material(heartRings), heartRings::toDebugFileName); + Material.Baked barkMat = baker.materials().get(new Material(bark), bark::toDebugFileName); + + return BasicBranchBlockBakedModel.bakeBasic(baker, + new BranchModelPart.UnbakedHeartCore(heartMat, barkMat), + new BranchModelPart.UnbakedSleeve(heartMat), + new BranchModelPart.UnbakedCore(ringsMat), + null); + } +} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/UnbakedRootsModel.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/UnbakedRootsModel.java new file mode 100644 index 000000000..f729d08d8 --- /dev/null +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/UnbakedRootsModel.java @@ -0,0 +1,54 @@ +package com.dtteam.dynamictrees.model.blockstate; + +import com.dtteam.dynamictrees.model.baked.BasicRootsBlockBakedModel; +import com.mojang.serialization.Codec; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.fabricmc.fabric.api.client.model.loading.v1.CustomUnbakedBlockStateModel; +import net.minecraft.client.renderer.block.dispatch.BlockStateModel; +import net.minecraft.client.resources.model.ModelBaker; +import net.minecraft.client.resources.model.ResolvableModel; +import net.minecraft.client.resources.model.sprite.Material; +import net.minecraft.resources.Identifier; + +import java.util.Optional; + +/** + * Fabric port of the NeoForge {@code UnbakedRootsModel}; deserialized from blockstate JSONs + * with type {@code dynamictrees:roots}. + */ +public record UnbakedRootsModel(Identifier side, Identifier top, boolean opaque) implements CustomUnbakedBlockStateModel { + + public static final String SIDE = "side"; + public static final String TOP = "top"; + public static final String OPAQUE = "opaque"; + public static final String TEXTURES = "textures"; + + private record RootsTextures(Identifier exposedSide, Identifier exposedTop) { + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(i -> i.group( + Identifier.CODEC.fieldOf(SIDE).forGetter(RootsTextures::exposedSide), + Identifier.CODEC.fieldOf(TOP).forGetter(RootsTextures::exposedTop) + ).apply(i, RootsTextures::new)); + } + + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(i -> i.group( + RootsTextures.CODEC.codec().fieldOf(TEXTURES).forGetter(m -> new RootsTextures(m.side, m.top)), + Codec.BOOL.optionalFieldOf(OPAQUE).forGetter(o -> Optional.of(o.opaque)) + ).apply(i, (textures, opaque) -> new UnbakedRootsModel(textures.exposedSide, textures.exposedTop, opaque.orElse(false)))); + + @Override + public MapCodec codec() { + return CODEC; + } + + @Override + public void resolveDependencies(ResolvableModel.Resolver resolver) {} + + @Override + public BlockStateModel bake(ModelBaker baker) { + Material.Baked barkMat = baker.materials().get(new Material(side), side::toDebugFileName); + Material.Baked ringsMat = baker.materials().get(new Material(top), top::toDebugFileName); + + return BasicRootsBlockBakedModel.bakeRoots(baker, barkMat, ringsMat, opaque); + } +} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/UnbakedRootsMossModel.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/UnbakedRootsMossModel.java new file mode 100644 index 000000000..dd41d537c --- /dev/null +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/UnbakedRootsMossModel.java @@ -0,0 +1,51 @@ +package com.dtteam.dynamictrees.model.blockstate; + +import com.dtteam.dynamictrees.model.baked.BasicBranchBlockBakedModel; +import com.dtteam.dynamictrees.model.parts.BranchModelPart; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.fabricmc.fabric.api.client.model.loading.v1.CustomUnbakedBlockStateModel; +import net.minecraft.client.renderer.block.dispatch.BlockStateModel; +import net.minecraft.client.resources.model.ModelBaker; +import net.minecraft.client.resources.model.ResolvableModel; +import net.minecraft.client.resources.model.sprite.Material; +import net.minecraft.resources.Identifier; + +/** + * Fabric port of the NeoForge {@code UnbakedRootsMossModel}; deserialized from blockstate JSONs + * with type {@code dynamictrees:roots_moss}. + */ +public record UnbakedRootsMossModel(Identifier moss) implements CustomUnbakedBlockStateModel { + + public static final String MOSS = "moss"; + public static final String TEXTURES = "textures"; + + private record RootsTextures(Identifier moss) { + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(i -> i.group( + Identifier.CODEC.fieldOf(MOSS).forGetter(RootsTextures::moss) + ).apply(i, RootsTextures::new)); + } + + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(i -> i.group( + RootsTextures.CODEC.codec().fieldOf(TEXTURES).forGetter(m -> new RootsTextures(m.moss)) + ).apply(i, textures -> new UnbakedRootsMossModel(textures.moss))); + + @Override + public MapCodec codec() { + return CODEC; + } + + @Override + public void resolveDependencies(ResolvableModel.Resolver resolver) {} + + @Override + public BlockStateModel bake(ModelBaker baker) { + Material.Baked mossMat = baker.materials().get(new Material(moss), moss::toDebugFileName); + + return BasicBranchBlockBakedModel.bakeBasic(baker, + new BranchModelPart.UnbakedMossCore(mossMat), + new BranchModelPart.UnbakedMossSleeve(mossMat), + new BranchModelPart.UnbakedMossCore(mossMat), + null); + } +} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/UnbakedSurfaceRootModel.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/UnbakedSurfaceRootModel.java new file mode 100644 index 000000000..6b780e46c --- /dev/null +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/UnbakedSurfaceRootModel.java @@ -0,0 +1,43 @@ +package com.dtteam.dynamictrees.model.blockstate; + +import com.dtteam.dynamictrees.model.baked.SurfaceRootBlockBakedModel; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.fabricmc.fabric.api.client.model.loading.v1.CustomUnbakedBlockStateModel; +import net.minecraft.client.renderer.block.dispatch.BlockStateModel; +import net.minecraft.client.resources.model.ModelBaker; +import net.minecraft.client.resources.model.ResolvableModel; +import net.minecraft.resources.Identifier; + +/** + * Fabric port of the NeoForge {@code SurfaceRootBlockStateModel.Unbaked}; deserialized from + * blockstate JSONs with type {@code dynamictrees:surface_root}. + */ +public record UnbakedSurfaceRootModel(Identifier barkTexture) implements CustomUnbakedBlockStateModel { + + public static final String TEXTURES = "textures"; + public static final String BARK_TEXTURE = "bark"; + + private record Textures(Identifier bark) { + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(i -> i.group( + Identifier.CODEC.fieldOf(BARK_TEXTURE).forGetter(Textures::bark) + ).apply(i, Textures::new)); + } + + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(i -> i.group( + Textures.CODEC.codec().fieldOf(TEXTURES).forGetter(m -> new Textures(m.barkTexture())) + ).apply(i, textures -> new UnbakedSurfaceRootModel(textures.bark()))); + + @Override + public MapCodec codec() { + return CODEC; + } + + @Override + public void resolveDependencies(ResolvableModel.Resolver resolver) {} + + @Override + public BlockStateModel bake(ModelBaker baker) { + return SurfaceRootBlockBakedModel.bake(baker, barkTexture); + } +} From 31d7cd5caee62ff7ab0920c7fc4328dd6ed6f4c8 Mon Sep 17 00:00:00 2001 From: teddy Date: Fri, 21 Aug 2026 15:26:31 -0300 Subject: [PATCH 06/15] fabric: migrate color/render-layer/sprite-source registration to current Fabric+vanilla tint-source API ColorProviderRegistry and BlockRenderLayerMap no longer exist. Mirrors NeoForge's already-working ClientModEventHandler: block tint sources go through BlockColorRegistry with the existing common/ TintSources classes, item tint sources register by id via ItemTintSources.ID_MAPPER, and render-layer assignment is dropped entirely (NeoForge no longer registers it either - it's now declared elsewhere). Also fixes two adjacent dead/ renamed calls in the same file: AtlasSourceTypeRegistryImpl -> the current SpriteSourceRegistry, and LeavesProperties.postInitClient() (removed method, confirmed via a full-repo grep) is simply dropped. --- .../DynamicTreesFabricClient.java | 182 +++++++++--------- 1 file changed, 89 insertions(+), 93 deletions(-) diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabricClient.java b/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabricClient.java index af24cc516..5d01b5382 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabricClient.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabricClient.java @@ -6,6 +6,7 @@ import com.dtteam.dynamictrees.block.sapling.*; import com.dtteam.dynamictrees.block.soil.*; import com.dtteam.dynamictrees.client.*; +import com.dtteam.dynamictrees.client.TintSources.*; import com.dtteam.dynamictrees.config.*; import com.dtteam.dynamictrees.item.*; import com.dtteam.dynamictrees.model.*; @@ -18,13 +19,19 @@ import com.dtteam.dynamictrees.tree.species.*; import fuzs.forgeconfigapiport.fabric.api.v5.ConfigRegistry; import net.fabricmc.api.*; -import net.fabricmc.fabric.api.blockrenderlayer.v1.*; import net.fabricmc.fabric.api.client.event.lifecycle.v1.*; import net.fabricmc.fabric.api.client.item.v1.*; import net.fabricmc.fabric.api.client.model.loading.v1.*; import net.fabricmc.fabric.api.client.rendering.v1.*; import net.minecraft.client.*; +import net.minecraft.client.color.block.BlockColors; +import net.minecraft.client.color.block.BlockTintSource; +import net.minecraft.client.color.block.BlockTintSources; +import net.minecraft.client.color.item.ItemTintSources; +import net.minecraft.client.renderer.block.dispatch.BlockStateModel; +import net.minecraft.client.renderer.block.dispatch.BlockStateModelPart; import net.minecraft.client.renderer.texture.*; +import net.minecraft.client.resources.model.geometry.BakedQuad; import net.minecraft.core.*; import net.minecraft.resources.*; import net.minecraft.util.*; @@ -47,7 +54,7 @@ public class DynamicTreesFabricClient implements ClientModInitializer { @Override public void onInitializeClient() { ConfigRegistry.INSTANCE.register(DynamicTrees.MOD_ID, ModConfig.Type.CLIENT, DTConfigs.CLIENT_CONFIG); - AtlasSourceTypeRegistryImpl.register(ThickBranchRingsSource.ID, ThickBranchRingsSource.setType(ThickBranchRingsSource.CODEC)); + SpriteSourceRegistry.register(ThickBranchRingsSource.ID, ThickBranchRingsSource.CODEC); registerModelLoaders(); registerEntityRenderers(); registerColorHandlers(); @@ -61,9 +68,8 @@ private void registerClientWorldLoad() { ClientTickEvents.START_CLIENT_TICK.register(client -> { if (!initialized && client.level != null) { discoverWoodColors(); - LeavesProperties.postInitClient(); - BlockColorMultipliers.cleanUp(); registerBlockColors(); + BlockColorMultipliers.cleanUp(); initialized = true; } }); @@ -79,78 +85,60 @@ private void registerEntityRenderers() { } private void registerColorHandlers() { - BlockColorMultipliers.register("birch", (state, level, pos, tintIndex) -> FoliageColor.FOLIAGE_BIRCH); - BlockColorMultipliers.register("spruce", (state, level, pos, tintIndex) -> FoliageColor.FOLIAGE_EVERGREEN); - - ColorProviderRegistry.ITEM.register(DTRegistries.DENDRO_POTION.get()::getColor, DTRegistries.DENDRO_POTION.get()); - ColorProviderRegistry.ITEM.register(DTRegistries.STAFF.get()::getColor, DTRegistries.STAFF.get()); + BlockColorMultipliers.register("birch", BlockTintSources.constant(FoliageColor.FOLIAGE_BIRCH)); + BlockColorMultipliers.register("spruce", BlockTintSources.constant(FoliageColor.FOLIAGE_EVERGREEN)); + + // Item tint sources are now looked up by id from the item model JSON, mirroring + // NeoForge's RegisterColorHandlersEvent.ItemTintSources handling in ClientModEventHandler. + ItemTintSources.ID_MAPPER.put(DynamicTrees.location("dendro_potion"), DendroPotionItemTintSource.MAP_CODEC); + ItemTintSources.ID_MAPPER.put(DynamicTrees.location("staff_handle"), StaffHandleItemTintSource.MAP_CODEC); + ItemTintSources.ID_MAPPER.put(DynamicTrees.location("staff_crystal"), StaffCrystalItemTintSource.MAP_CODEC); } public static void registerBlockColors() { - final int white = 0xFFFFFFFF; - final int magenta = 0x00FF00FF; - final var blockColors = Minecraft.getInstance().getBlockColors(); - - for (SoilProperties soil : SoilProperties.REGISTRY) { - if (soil.getBlock().isEmpty()) continue; - SoilBlock roots = soil.getBlock().get(); - ColorProviderRegistry.BLOCK.register( - (state, level, pos, tintIndex) -> roots.colorMultiplier(blockColors, state, level, pos, tintIndex), - roots - ); - BlockRenderLayerMap.INSTANCE.putBlock(roots, RenderType.cutoutMipped()); - } - - for (Family family : Family.REGISTRY.getAll()) { - if (family instanceof AerialRootsFamily rootsFamily) { - rootsFamily.getRoots().ifPresent(roots -> - BlockRenderLayerMap.INSTANCE.putBlock(roots, RenderType.cutoutMipped()) - ); - } - } - - - ColorProviderRegistry.BLOCK.register( - (state, level, pos, tintIndex) -> isValidPos(level, pos) && (state.getBlock() instanceof PottedSaplingBlock) - ? DTRegistries.POTTED_SAPLING.get().getSpecies(level, pos).saplingColorMultiplier(state, level, pos, tintIndex) : white, - DTRegistries.POTTED_SAPLING.get() - ); - - for (Species species : Species.REGISTRY) { - if (species.getSapling().isPresent()) { - ColorProviderRegistry.BLOCK.register( - (state, level, pos, tintIndex) -> isValidPos(level, pos) - ? species.saplingColorMultiplier(state, level, pos, tintIndex) : white, - species.getSapling().get() - ); - } - species.getSapling().ifPresent(sapling -> BlockRenderLayerMap.INSTANCE.putBlock(sapling, RenderType.cutoutMipped())); - if(species.hasFruits()){ - species.getFruits().forEach(fruit -> - BlockRenderLayerMap.INSTANCE.putBlock(fruit.getBlock(), RenderType.cutoutMipped()) - ); - } - if(species.hasPods()){ - species.getPods().forEach(pod -> - BlockRenderLayerMap.INSTANCE.putBlock(pod.getBlock(), RenderType.cutoutMipped()) - ); - } - } + final BlockColors blockColors = Minecraft.getInstance().getBlockColors(); + + // Register Rooty Soils Tint Sources + SoilProperties.REGISTRY.getAll().stream().map(SoilProperties::getBlock).flatMap(Optional::stream) + .forEach(soilBlock -> { + SoilProperties properties = soilBlock.getSoilProperties(); + List sources = CloneTintSource.cloneAllSources( + blockColors, + () -> properties.getPrimitiveSoilState(soilBlock.defaultBlockState()), + properties.getFoliageTintLayerCount()); + sources.add(new SoilRootsTintSource(soilBlock)); + BlockColorRegistry.register(sources, soilBlock); + }); - for (DynamicLeavesBlock leaves : LeavesProperties.REGISTRY.getAll().stream() - .filter(lp -> lp.getDynamicLeavesBlock().isPresent()) - .map(lp -> lp.getDynamicLeavesBlock().get()) - .collect(Collectors.toSet())) { - ColorProviderRegistry.BLOCK.register( - (state, level, pos, tintIndex) -> isValidPos(level, pos) && TreeHelper.isLeaves(state.getBlock()) - ? ((DynamicLeavesBlock) state.getBlock()).getLeavesProperties().foliageColorMultiplier(state, level, pos) : magenta, - leaves - ); - } - } + // Register Leaves Tint Sources + LeavesProperties.REGISTRY.getAll().stream().map(LeavesProperties::getDynamicLeavesBlock).flatMap(Optional::stream) + .forEach(leaves -> { + LeavesProperties properties = leaves.getLeavesProperties(); + if (properties.hasCustomColor()) { + Integer customColor = properties.getCustomColor(); + if (customColor == null) //we use null as a way to default back to "biome" index source. + BlockColorRegistry.register(List.of(BlockTintSources.foliage()), leaves); + else + BlockColorRegistry.register(List.of(BlockTintSources.constant(customColor)), leaves); + } else { + BlockColorRegistry.register(CloneTintSource.cloneAllSources(blockColors, properties::getPrimitiveLeaves, properties.getFoliageTintLayerCount()), leaves); + } + }); - private static boolean isValidPos(BlockGetter level, BlockPos pos) { - return level != null && pos != null; + // Register Potted Sapling Tint Sources + BlockColorRegistry.register(List.of(new PottedSaplingTintSource(blockColors)), DTRegistries.POTTED_SAPLING.get()); + + // Register Sapling Tint Sources + Species.REGISTRY.getAll().stream().map(Species::getSapling).flatMap(Optional::stream) + .forEach(sapling -> { + Species species = sapling.getSpecies(); + BlockColorRegistry.register(List.of( + //Leaves for the leaves + new SaplingTintSource(blockColors, species), + //Mangrove saplings have roots so we provide a branch tint + new SuppliedConstantTintSource(() -> species.getFamily().woodBarkColor) + ), sapling); + }); } private void registerTooltipCallback() { @@ -182,43 +170,30 @@ private void registerTooltipCallback() { } private void registerClientTick() { - ClientTickEvents.START_WORLD_TICK.register(level -> { - SeasonHelper.updateTick(level, level.getDayTime()); + ClientTickEvents.START_LEVEL_TICK.register(level -> { + SeasonHelper.updateTick(level, level.getDefaultClockTime()); }); } public static void discoverWoodColors() { - final Function bakedTextureGetter = Minecraft.getInstance() - .getTextureAtlas(InventoryMenu.BLOCK_ATLAS); - for (Family family : Family.REGISTRY.getAll()) { family.woodRingColor = 0xFFF1AE; family.woodBarkColor = 0xB3A979; if (family != Family.NULL_FAMILY) { family.getPrimitiveLog().ifPresent(branch -> { BlockState state = branch.defaultBlockState(); - family.woodRingColor = getFaceColor(state, Direction.DOWN, bakedTextureGetter); - family.woodBarkColor = getFaceColor(state, Direction.NORTH, bakedTextureGetter); + family.woodRingColor = getFaceColor(state, Direction.DOWN); + family.woodBarkColor = getFaceColor(state, Direction.NORTH); }); } } } - private static int getFaceColor(BlockState state, Direction face, Function textureGetter) { - final BakedModel model = Minecraft.getInstance().getBlockRenderer().getBlockModel(state); - if (model == null) { - DynamicTrees.LOG.warn("Could not get model for {}! Branch needs to be handled manually!", state.getBlock()); - return 0; - } - List quads = model.getQuads(state, face, RandomSource.create()); - if (quads.isEmpty()) { - quads = model.getQuads(state, null, RandomSource.create()); - } - if (quads.isEmpty()) { - DynamicTrees.LOG.warn("Could not get color of {} side for {}! Branch needs to be handled manually!", face, state.getBlock()); - return 0; - } - TextureAtlasSprite sprite = quads.getFirst().getSprite(); + private static int getFaceColor(BlockState state, Direction face) { + final BlockStateModel model = Minecraft.getInstance().getModelManager().getBlockStateModelSet().get(state); + List quads = getQuads(state, face, model); + if (quads == null) return 0; + TextureAtlasSprite sprite = quads.getFirst().materialInfo().sprite(); final TextureHelper.PixelBuffer pixelBuffer = new TextureHelper.PixelBuffer(sprite); final int u = pixelBuffer.w / 16; final TextureHelper.PixelBuffer center = new TextureHelper.PixelBuffer(u * 8, u * 8); @@ -226,4 +201,25 @@ private static int getFaceColor(BlockState state, Direction face, Function getQuads(BlockState state, Direction face, BlockStateModel model) { + List parts = new ArrayList<>(); + model.collectParts(RandomSource.create(), parts); + + if (parts.isEmpty()) { // No parts? empty model + DynamicTrees.LOG.warn("Could not get any color from {}, model is empty! Branch color needs to be handled manually.", state.getBlock()); + return null; + } + //We only care about the first, we assume these are all regular blocks + List quads = parts.getFirst().getQuads(face); + if (quads.isEmpty()) // If the quad list is empty, means there is no face on that side, so we try with null. + { + quads = parts.getFirst().getQuads(null); + } + if (quads.isEmpty()) { // If null still returns empty, there is nothing we can do so we just warn and exit. + DynamicTrees.LOG.warn("Could not get color of {} side for {}! Branch color needs to be handled manually.", face, state.getBlock()); + return null; + } + return quads; + } } From f2706f2d9bc45693e59be33eb6722cd66b0fcb8b Mon Sep 17 00:00:00 2001 From: teddy Date: Fri, 21 Aug 2026 15:37:51 -0300 Subject: [PATCH 07/15] common/fabric: fix HolderSet.isBound(), onDestroyedByPlayer/registerSeasonProvider signature drift, FabricRegistryLoader vanilla-API drift, and access-widener gaps for 26.1.2 - HolderSet gained an abstract isBound() in this MC version; added it to every custom HolderSet impl in fabric/.../worldgen/holderset (eagerly resolvable sets report true, wrappers delegate to what they wrap). Also fixed net.minecraft.Util -> net.minecraft.util.Util and ResourceKey.location() -> .identifier() (both renamed/moved). - FabricInteractionHelper: Branch/TrunkShell/PottedSaplingBlock's onDestroyedByPlayer gained an ItemStack toolStack param; pass player.getMainHandItem() (matches NeoForgeInteractionHelper's approach - vanilla BlockState.onDestroyedByPlayer() is a NeoForge-only extension that doesn't exist on Fabric's plain jar). Dropped a stray @Override on canToolAxeDig, which isn't on ICompatHelper and is dead code (its one common/ call site is already commented out). - FabricCompatHelper.registerSeasonProvider gained a modId param on the interface; switch on it like NeoForgeCompatHelper (only Serene Seasons has a Fabric dependency wired up - Ecliptic Seasons doesn't). - FabricRegistryLoader: LootItemConditionType/LootPoolEntryType/ LootItemFunctionType no longer exist - their registries now hold the MapCodec directly (ported NeoForgeRegistryLoader's already-correct shape). EntityType.Builder.build(String) -> build(ResourceKey). Dropped BlockEntityType's stale 3-arg constructor's trailing null. - javax.annotation.Nullable -> org.jetbrains.annotations.Nullable in the two files still using it (JSR-305 isn't on Fabric's classpath; every other file in the codebase already uses the JetBrains annotation). - Access widener: fixed craftingRemainingItem's descriptor (Item wraps it in ItemStackTemplate now), added the missing `accessible field` half for BlockEntity.type/blockState (only `mutable` was present, which is a no-op on an already-non-final private field), and added entries for newly-private/final members surfaced by this MC version: SpriteContents .additionalMetadata, MultiPartModel.models, CreakingHeartBlockEntity .ticksExisted and its now-private spreadResin() (extendable), Item .getCraftingRemainder() (now final - extendable), and BlockEntityType's 2-arg constructor (now private - accessible). --- .../command/subcommand/SubCommand.java | 2 +- .../BranchDestructionDataComponent.java | 2 +- .../META-INF/dynamictrees.accesswidener | 12 ++++++++-- .../platform/FabricCompatHelper.java | 9 ++++++-- .../platform/FabricInteractionHelper.java | 7 +++--- .../registry/FabricRegistryLoader.java | 22 +++++++++---------- .../worldgen/holderset/DTBiomeHolderSet.java | 5 +++++ .../worldgen/holderset/DelayedHolderSet.java | 5 +++++ .../holderset/NameRegexMatchHolderSet.java | 2 +- .../worldgen/holderset/OrHolderSet.java | 5 +++++ .../holderset/StreamBackedHolderSet.java | 11 +++++++++- 11 files changed, 59 insertions(+), 23 deletions(-) diff --git a/common/src/main/java/com/dtteam/dynamictrees/command/subcommand/SubCommand.java b/common/src/main/java/com/dtteam/dynamictrees/command/subcommand/SubCommand.java index 0382197c1..b98ff2536 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/command/subcommand/SubCommand.java +++ b/common/src/main/java/com/dtteam/dynamictrees/command/subcommand/SubCommand.java @@ -29,8 +29,8 @@ import net.minecraft.resources.Identifier; import net.minecraft.server.permissions.PermissionCheck; import net.minecraft.world.level.block.state.BlockState; +import org.jetbrains.annotations.Nullable; -import javax.annotation.Nullable; import java.util.Collection; import java.util.List; import java.util.function.Consumer; diff --git a/common/src/main/java/com/dtteam/dynamictrees/data/components/BranchDestructionDataComponent.java b/common/src/main/java/com/dtteam/dynamictrees/data/components/BranchDestructionDataComponent.java index f46bd847d..6f84eb130 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/data/components/BranchDestructionDataComponent.java +++ b/common/src/main/java/com/dtteam/dynamictrees/data/components/BranchDestructionDataComponent.java @@ -11,8 +11,8 @@ import net.minecraft.network.codec.StreamCodec; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.state.BlockState; +import org.jetbrains.annotations.Nullable; -import javax.annotation.Nullable; import java.util.Arrays; import java.util.Optional; diff --git a/common/src/main/resources/META-INF/dynamictrees.accesswidener b/common/src/main/resources/META-INF/dynamictrees.accesswidener index 39ea50edd..18d661dff 100644 --- a/common/src/main/resources/META-INF/dynamictrees.accesswidener +++ b/common/src/main/resources/META-INF/dynamictrees.accesswidener @@ -9,8 +9,9 @@ accessible field net/minecraft/world/level/levelgen/structure/pools/StructureTem accessible field net/minecraft/world/level/levelgen/structure/pools/StructureTemplatePool templates Lit/unimi/dsi/fastutil/objects/ObjectArrayList; accessible field net/minecraft/world/item/crafting/RecipeManager byType Lcom/google/common/collect/Multimap; -accessible field net/minecraft/world/item/Item craftingRemainingItem Lnet/minecraft/world/item/Item; -mutable field net/minecraft/world/item/Item craftingRemainingItem Lnet/minecraft/world/item/Item; +accessible field net/minecraft/world/item/Item craftingRemainingItem Lnet/minecraft/world/item/ItemStackTemplate; +mutable field net/minecraft/world/item/Item craftingRemainingItem Lnet/minecraft/world/item/ItemStackTemplate; +extendable method net/minecraft/world/item/Item getCraftingRemainder ()Lnet/minecraft/world/item/ItemStackTemplate; accessible field net/minecraft/world/level/chunk/LevelChunk loaded Z accessible field net/minecraft/world/level/levelgen/feature/stateproviders/WeightedStateProvider weightedList Lnet/minecraft/util/random/SimpleWeightedRandomList; @@ -21,9 +22,16 @@ accessible class net/minecraft/world/inventory/BrewingStandMenu$PotionSlot accessible class net/minecraft/data/tags/IntrinsicHolderTagsProvider$IntrinsicTagAppender accessible method net/minecraft/client/renderer/texture/SpriteContents metadata ()Lnet/minecraft/server/packs/resources/ResourceMetadata; accessible field net/minecraft/client/renderer/texture/SpriteContents originalImage Lcom/mojang/blaze3d/platform/NativeImage; +accessible field net/minecraft/client/renderer/texture/SpriteContents additionalMetadata Ljava/util/List; +accessible field net/minecraft/client/renderer/block/dispatch/multipart/MultiPartModel models Ljava/util/List; +accessible field net/minecraft/world/level/block/entity/CreakingHeartBlockEntity ticksExisted J +extendable method net/minecraft/world/level/block/entity/CreakingHeartBlockEntity spreadResin (Lnet/minecraft/server/level/ServerLevel;)Ljava/util/Optional; +accessible method net/minecraft/world/level/block/entity/BlockEntityType (Lnet/minecraft/world/level/block/entity/BlockEntityType$BlockEntitySupplier;Ljava/util/Set;)V accessible method net/minecraft/client/renderer/texture/atlas/SpriteSources register (Ljava/lang/String;Lcom/mojang/serialization/MapCodec;)Lnet/minecraft/client/renderer/texture/atlas/SpriteSourceType; accessible method net/minecraft/client/data/models/model/TextureSlot create (Ljava/lang/String;)Lnet/minecraft/client/data/models/model/TextureSlot; +accessible field net/minecraft/world/level/block/entity/BlockEntity type Lnet/minecraft/world/level/block/entity/BlockEntityType; mutable field net/minecraft/world/level/block/entity/BlockEntity type Lnet/minecraft/world/level/block/entity/BlockEntityType; +accessible field net/minecraft/world/level/block/entity/BlockEntity blockState Lnet/minecraft/world/level/block/state/BlockState; mutable field net/minecraft/world/level/block/entity/BlockEntity blockState Lnet/minecraft/world/level/block/state/BlockState; \ No newline at end of file diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricCompatHelper.java b/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricCompatHelper.java index 5ac8c6ef9..f9157aa6f 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricCompatHelper.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricCompatHelper.java @@ -1,13 +1,18 @@ package com.dtteam.dynamictrees.platform; +import com.dtteam.dynamictrees.DynamicTrees; import com.dtteam.dynamictrees.compat.SereneSeasonsSeasonProvider; import com.dtteam.dynamictrees.platform.services.ICompatHelper; public class FabricCompatHelper implements ICompatHelper { @Override - public void registerSeasonProvider() { - SereneSeasonsSeasonProvider.registerSereneSeasonsProvider(); + public void registerSeasonProvider(String modId) { + // Only Serene Seasons has a Fabric compat dependency wired up (see fabric/build.gradle); + // Ecliptic Seasons (NeoForge's other branch here) has no Fabric port pinned yet. + if (DynamicTrees.SERENE_SEASONS.equals(modId)) { + SereneSeasonsSeasonProvider.registerSereneSeasonsProvider(); + } } } \ No newline at end of file diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricInteractionHelper.java b/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricInteractionHelper.java index ed3a3a0a7..ab9c0fa5b 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricInteractionHelper.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricInteractionHelper.java @@ -25,7 +25,6 @@ public boolean canToolAxeStrip(ItemStack stack) { return stack.is(ItemTags.AXES); } - @Override public boolean canToolAxeDig(ItemStack stack) { return stack.is(ItemTags.AXES); } @@ -39,13 +38,13 @@ public int setSeedItemEntityLifespan(ItemEntity entityItem, Seed seed) { public boolean blockDestroyByPlayer(BlockState state, Level level, BlockPos pos, Player player, boolean willHarvest, FluidState fluidState) { Block block = state.getBlock(); if (block instanceof BranchBlock branchBlock) { - return branchBlock.onDestroyedByPlayer(state, level, pos, player, willHarvest, fluidState); + return branchBlock.onDestroyedByPlayer(state, level, pos, player, player.getMainHandItem(), willHarvest, fluidState); } else if (block instanceof TrunkShellBlock trunkShellBlock) { - return trunkShellBlock.onDestroyedByPlayer(state, level, pos, player, willHarvest, fluidState); + return trunkShellBlock.onDestroyedByPlayer(state, level, pos, player, player.getMainHandItem(), willHarvest, fluidState); } else if (block instanceof SoilBlock soilBlock) { return soilBlock.onDestroyedByPlayer(state, level, pos, player, willHarvest, fluidState); } else if (block instanceof PottedSaplingBlock pottedSaplingBlock) { - return pottedSaplingBlock.onDestroyedByPlayer(state, level, pos, player, willHarvest, fluidState); + return pottedSaplingBlock.onDestroyedByPlayer(state, level, pos, player, player.getMainHandItem(), willHarvest, fluidState); } return true; } diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/registry/FabricRegistryLoader.java b/fabric/src/main/java/com/dtteam/dynamictrees/registry/FabricRegistryLoader.java index 9664a4c43..edfd82afc 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/registry/FabricRegistryLoader.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/registry/FabricRegistryLoader.java @@ -10,9 +10,11 @@ import net.minecraft.core.Registry; import net.minecraft.core.component.DataComponentType; import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; import net.minecraft.network.chat.MutableComponent; import net.minecraft.network.syncher.EntityDataSerializer; import net.minecraft.resources.Identifier; +import net.minecraft.resources.ResourceKey; import net.minecraft.sounds.SoundEvent; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; @@ -31,11 +33,8 @@ import net.minecraft.world.level.levelgen.structure.pools.StructurePoolElement; import net.minecraft.world.level.levelgen.structure.pools.StructurePoolElementType; import net.minecraft.world.level.storage.loot.entries.LootPoolEntryContainer; -import net.minecraft.world.level.storage.loot.entries.LootPoolEntryType; import net.minecraft.world.level.storage.loot.functions.LootItemFunction; -import net.minecraft.world.level.storage.loot.functions.LootItemFunctionType; import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; -import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType; import java.util.Set; import java.util.function.Function; @@ -80,13 +79,14 @@ public Supplier registerCreativeTab(String name, Supplier Supplier> registerEntity(String name, EntityType.Builder builder, boolean isTree) { // if (isTree) // builder.setShouldReceiveVelocityUpdates(true).setTrackingRange(512).setUpdateInterval(Integer.MAX_VALUE); - EntityType entityType = Registry.register(BuiltInRegistries.ENTITY_TYPE, DynamicTrees.location(name), builder.build(name)); + EntityType entityType = Registry.register(BuiltInRegistries.ENTITY_TYPE, DynamicTrees.location(name), + builder.build(ResourceKey.create(Registries.ENTITY_TYPE, DynamicTrees.location(name)))); return ()-> entityType; } @Override public Supplier> registerBlockEntity(String name, BlockEntityType.BlockEntitySupplier newBlockEntity, Supplier> validBlocks) { - BlockEntityType entityType = Registry.register(BuiltInRegistries.BLOCK_ENTITY_TYPE, DynamicTrees.location(name), new BlockEntityType<>(newBlockEntity, validBlocks.get(), null)); + BlockEntityType entityType = Registry.register(BuiltInRegistries.BLOCK_ENTITY_TYPE, DynamicTrees.location(name), new BlockEntityType<>(newBlockEntity, validBlocks.get())); return ()-> entityType; } @@ -120,20 +120,20 @@ public , T extends ArgumentTypeInfo.Template, I ext } @Override - public Supplier registerLootConditionType(String name, MapCodec serializerFactory) { - LootItemConditionType type = Registry.register(BuiltInRegistries.LOOT_CONDITION_TYPE, DynamicTrees.location(name), new LootItemConditionType(serializerFactory)); + public Supplier> registerLootConditionType(String name, MapCodec serializerFactory) { + MapCodec type = Registry.register(BuiltInRegistries.LOOT_CONDITION_TYPE, DynamicTrees.location(name), serializerFactory); return ()-> type; } @Override - public Supplier registerLootPoolEntryType(String name, MapCodec serializerFactory) { - LootPoolEntryType type = Registry.register(BuiltInRegistries.LOOT_POOL_ENTRY_TYPE, DynamicTrees.location(name), new LootPoolEntryType(serializerFactory)); + public Supplier> registerLootPoolEntryType(String name, MapCodec serializerFactory) { + MapCodec type = Registry.register(BuiltInRegistries.LOOT_POOL_ENTRY_TYPE, DynamicTrees.location(name), serializerFactory); return ()-> type; } @Override - public Supplier> registerLootFunctionType(String name, MapCodec serializerFactory) { - LootItemFunctionType type = Registry.register(BuiltInRegistries.LOOT_FUNCTION_TYPE, DynamicTrees.location(name), new LootItemFunctionType<>(serializerFactory)); + public Supplier> registerLootFunctionType(String name, MapCodec serializerFactory) { + MapCodec type = Registry.register(BuiltInRegistries.LOOT_FUNCTION_TYPE, DynamicTrees.location(name), serializerFactory); return ()-> type; } diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/DTBiomeHolderSet.java b/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/DTBiomeHolderSet.java index b108b8ad7..04a2b3f8a 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/DTBiomeHolderSet.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/DTBiomeHolderSet.java @@ -102,6 +102,11 @@ public boolean canSerializeIn(HolderOwner owner) { return true; } + @Override + public boolean isBound() { + return true; + } + @Override public Optional> unwrapKey() { return Optional.empty(); diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/DelayedHolderSet.java b/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/DelayedHolderSet.java index c70ab944e..c4b7c9406 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/DelayedHolderSet.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/DelayedHolderSet.java @@ -58,6 +58,11 @@ public boolean canSerializeIn(HolderOwner owner) { return this.holderSetSupplier.get().canSerializeIn(owner); } + @Override + public boolean isBound() { + return this.holderSetSupplier.get().isBound(); + } + @Override public Optional> unwrapKey() { return this.holderSetSupplier.get().unwrapKey(); diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/NameRegexMatchHolderSet.java b/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/NameRegexMatchHolderSet.java index f0c897065..74d19bb46 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/NameRegexMatchHolderSet.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/NameRegexMatchHolderSet.java @@ -13,6 +13,6 @@ public NameRegexMatchHolderSet(HolderLookup.RegistryLookup registryLookup, St @Override protected Stream getInput(Holder holder) { - return holder.unwrapKey().stream().map(key -> key.location().toString()); + return holder.unwrapKey().stream().map(key -> key.identifier().toString()); } } diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/OrHolderSet.java b/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/OrHolderSet.java index 279f2d5db..320bc7434 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/OrHolderSet.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/OrHolderSet.java @@ -24,4 +24,9 @@ public Stream> stream() { public boolean canSerializeIn(HolderOwner owner) { return this.values.stream().allMatch(set -> set.canSerializeIn(owner)); } + + @Override + public boolean isBound() { + return this.values.stream().allMatch(HolderSet::isBound); + } } diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/StreamBackedHolderSet.java b/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/StreamBackedHolderSet.java index a4b7ad1e0..1af57a690 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/StreamBackedHolderSet.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/StreamBackedHolderSet.java @@ -1,11 +1,11 @@ package com.dtteam.dynamictrees.worldgen.holderset; import com.mojang.datafixers.util.Either; -import net.minecraft.Util; import net.minecraft.core.Holder; import net.minecraft.core.HolderSet; import net.minecraft.tags.TagKey; import net.minecraft.util.RandomSource; +import net.minecraft.util.Util; import java.util.*; import java.util.stream.Collectors; @@ -15,6 +15,15 @@ public List> contents() { return this.stream().collect(Collectors.toList()); } + // These sets are eagerly computed from their backing source (a registry lookup, a + // supplier, etc.) rather than lazily resolved against a tag registry, so they're always + // "bound" once constructible - mirrors vanilla HolderSet.Direct's isBound(), not + // HolderSet.Named's (which tracks whether a deferred tag binding has resolved yet). + @Override + public boolean isBound() { + return true; + } + public Set> contentsSet() { return this.stream().collect(Collectors.toSet()); } From 627af0bebef0365e1570ed26ab78367fd46a397d Mon Sep 17 00:00:00 2001 From: teddy Date: Fri, 21 Aug 2026 15:49:30 -0300 Subject: [PATCH 08/15] fabric: fix remaining Fabric-API and vanilla-API relocations for 26.1.2 - CommonEventHandler: ServerWorldEvents -> ServerLevelEvents, START_WORLD_TICK -> START_LEVEL_TICK, level.getDayTime() -> getDefaultClockTime() (same rename family as Task 6/8's client-side fixes); PreparableReloadListener.reload(...) now takes (SharedState, Executor, PreparationBarrier, Executor) instead of the old 6-arg form - common/'s Resources.ReloadListener already has the correct signature, only this Fabric override needed updating; and the same onDestroyedByPlayer toolStack-arg fix as FabricInteractionHelper (Task 7). - FabricClientHelper: NativeImage.getPixelRGBA -> getPixel (confirmed via common/'s TextureHelper.PixelBuffer, which already uses getPixel for the same purpose on both loaders). - FabricBiomeModifications: BiomeSelectionContext.getBiomeRegistryEntry() -> getBiomeHolder(); PlacedFeature.getFeatures() now yields Holder> instead of ConfiguredFeature directly - unwrap with .value() at the call site. - DendroPotionRecipeHandler: Registry.getHolder(ResourceKey) -> the inherited HolderGetter.get(ResourceKey) (same return type). - SereneSeasonsSeasonProvider: Biome.warmEnoughToRain gained a sea-level int parameter; NeoForge's already-working SereneSeasonsProvider shows the value to pass (level.getSeaLevel()). --- .../compat/SereneSeasonsSeasonProvider.java | 2 +- .../event/handler/CommonEventHandler.java | 26 +++++++++---------- .../platform/FabricClientHelper.java | 2 +- .../recipe/DendroPotionRecipeHandler.java | 2 +- .../worldgen/FabricBiomeModifications.java | 6 ++--- 5 files changed, 18 insertions(+), 20 deletions(-) diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/compat/SereneSeasonsSeasonProvider.java b/fabric/src/main/java/com/dtteam/dynamictrees/compat/SereneSeasonsSeasonProvider.java index 58a05eb44..912a86b1b 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/compat/SereneSeasonsSeasonProvider.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/compat/SereneSeasonsSeasonProvider.java @@ -26,7 +26,7 @@ public void updateTick(Level level, long dayTime) { @Override public boolean shouldSnowMelt(Level level, BlockPos pos) { if (ModConfig.seasons.generateSnowAndIce && seasonValue < SeasonHelper.WINTER_START) { - return level.getBiome(pos).value().warmEnoughToRain(pos); + return level.getBiome(pos).value().warmEnoughToRain(pos, level.getSeaLevel()); } return false; } diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/CommonEventHandler.java b/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/CommonEventHandler.java index 769c923d6..d5b8d83a5 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/CommonEventHandler.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/CommonEventHandler.java @@ -13,16 +13,15 @@ import com.dtteam.dynamictrees.worldgen.BiomeDatabases; import com.dtteam.dynamictrees.worldgen.feature.DynamicTreeFeature; import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLevelEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; -import net.fabricmc.fabric.api.event.lifecycle.v1.ServerWorldEvents; import net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents; import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener; import net.fabricmc.fabric.api.resource.ResourceManagerHelper; import net.minecraft.resources.Identifier; import net.minecraft.server.packs.PackType; -import net.minecraft.server.packs.resources.ResourceManager; -import net.minecraft.util.profiling.ProfilerFiller; +import net.minecraft.server.packs.resources.PreparableReloadListener; import net.minecraft.world.level.block.Block; import java.util.concurrent.CompletableFuture; @@ -32,16 +31,16 @@ public class CommonEventHandler { public static void RegisterEvents(){ - ServerTickEvents.START_WORLD_TICK.register((level)->{ + ServerTickEvents.START_LEVEL_TICK.register((level)->{ FutureBreak.process(level); - SeasonHelper.updateTick(level, level.getDayTime()); + SeasonHelper.updateTick(level, level.getDefaultClockTime()); }); - ServerWorldEvents.LOAD.register(((minecraftServer, serverLevel) -> { + ServerLevelEvents.LOAD.register(((minecraftServer, serverLevel) -> { BiomeDatabases.populateBlacklistFromConfig(); })); - ServerWorldEvents.UNLOAD.register(((minecraftServer, serverLevel) -> { + ServerLevelEvents.UNLOAD.register(((minecraftServer, serverLevel) -> { DynamicTreeFeature.DISC_PROVIDER.unloadWorld(serverLevel); })); @@ -57,13 +56,13 @@ public static void RegisterEvents(){ PlayerBlockBreakEvents.BEFORE.register((level, player, pos, state, blockEntity) -> { Block block = state.getBlock(); if (block instanceof BranchBlock branchBlock) { - return branchBlock.onDestroyedByPlayer(state, level, pos, player, true, level.getFluidState(pos)); + return branchBlock.onDestroyedByPlayer(state, level, pos, player, player.getMainHandItem(), true, level.getFluidState(pos)); } else if (block instanceof TrunkShellBlock trunkShellBlock) { - return trunkShellBlock.onDestroyedByPlayer(state, level, pos, player, true, level.getFluidState(pos)); + return trunkShellBlock.onDestroyedByPlayer(state, level, pos, player, player.getMainHandItem(), true, level.getFluidState(pos)); } else if (block instanceof SoilBlock soilBlock) { return soilBlock.onDestroyedByPlayer(state, level, pos, player, true, level.getFluidState(pos)); } else if (block instanceof PottedSaplingBlock pottedSaplingBlock) { - return pottedSaplingBlock.onDestroyedByPlayer(state, level, pos, player, true, level.getFluidState(pos)); + return pottedSaplingBlock.onDestroyedByPlayer(state, level, pos, player, player.getMainHandItem(), true, level.getFluidState(pos)); } return true; }); @@ -79,10 +78,9 @@ public FabricReloadListener() { } @Override - public CompletableFuture reload(PreparationBarrier stage, ResourceManager resourceManager, - ProfilerFiller preparationsProfiler, ProfilerFiller reloadProfiler, - Executor backgroundExecutor, Executor gameExecutor) { - return super.reload(stage, resourceManager, preparationsProfiler, reloadProfiler, backgroundExecutor, gameExecutor); + public CompletableFuture reload(SharedState sharedState, Executor backgroundExecutor, + PreparationBarrier preparationBarrier, Executor gameExecutor) { + return super.reload(sharedState, backgroundExecutor, preparationBarrier, gameExecutor); } @Override diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricClientHelper.java b/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricClientHelper.java index 49e245526..ddbc17b2e 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricClientHelper.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricClientHelper.java @@ -17,7 +17,7 @@ public int getPixelRGBA(TextureAtlasSprite sprite, int x, int y) { SpriteContents contents = sprite.contents(); NativeImage image = contents.originalImage; if (image != null) { - return image.getPixelRGBA(x, y); + return image.getPixel(x, y); } return 0; } catch (Exception e) { diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/recipe/DendroPotionRecipeHandler.java b/fabric/src/main/java/com/dtteam/dynamictrees/recipe/DendroPotionRecipeHandler.java index a2f1bbfe8..c317784b6 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/recipe/DendroPotionRecipeHandler.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/recipe/DendroPotionRecipeHandler.java @@ -55,7 +55,7 @@ public static List getAllDendroRecipes() { } public static ItemStack setPotion(ItemStack pStack, String potionName) { - Optional> potion = BuiltInRegistries.POTION.getHolder(ResourceKey.create(Registries.POTION, Identifier.parse(potionName))); + Optional> potion = BuiltInRegistries.POTION.get(ResourceKey.create(Registries.POTION, Identifier.parse(potionName))); potion.ifPresent(holder -> pStack.set(DataComponents.POTION_CONTENTS, new PotionContents(holder))); return pStack; diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/FabricBiomeModifications.java b/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/FabricBiomeModifications.java index d8a41d609..9b545f8b7 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/FabricBiomeModifications.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/FabricBiomeModifications.java @@ -72,7 +72,7 @@ private static void removeVanillaTrees(BiomeSelectionContext selectionContext, B for (GenerationStep.Decoration stage : featureCancellations.getDecorationSteps()) { int stageIndex = stage.ordinal(); - List> features = selectionContext.getBiomeRegistryEntry() + List> features = selectionContext.getBiomeHolder() .value() .getGenerationSettings() .features(); @@ -90,9 +90,9 @@ private static void removeVanillaTrees(BiomeSelectionContext selectionContext, B PlacedFeature placedFeature = placedFeatureHolder.value(); - boolean shouldCancel = placedFeature.getFeatures().anyMatch(configuredFeature -> { + boolean shouldCancel = placedFeature.getFeatures().anyMatch(configuredFeatureHolder -> { for (FeatureCanceller featureCanceller : featureCancellations.getCancellers()) { - if (featureCanceller.shouldCancel(configuredFeature, featureCancellations)) { + if (featureCanceller.shouldCancel(configuredFeatureHolder.value(), featureCancellations)) { return true; } } From b0e6ceb335c02c5d486ba834a6ac08d759d6db59 Mon Sep 17 00:00:00 2001 From: teddy Date: Fri, 21 Aug 2026 15:49:36 -0300 Subject: [PATCH 09/15] fabric: migrate WailaBranchHandler to Jade's current UI API snownee.jade.api.ui.IElement and snownee.jade.impl.ui.ElementHelper don't exist in the pinned Jade version (26.1.9+fabric) - the UI API was restructured to snownee.jade.api.ui.Element (implements LayoutElement directly, so ITooltip's add/append overloads resolve unambiguously again) and the static factory methods moved to snownee.jade.api.ui.JadeUI (verified both via javap against the actual pinned jar). Also fixed CompoundTag.getString(String) returning Optional instead of String. --- .../compat/waila/WailaBranchHandler.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/compat/waila/WailaBranchHandler.java b/fabric/src/main/java/com/dtteam/dynamictrees/compat/waila/WailaBranchHandler.java index 89556b3fe..b99023334 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/compat/waila/WailaBranchHandler.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/compat/waila/WailaBranchHandler.java @@ -28,8 +28,8 @@ import snownee.jade.api.IBlockComponentProvider; import snownee.jade.api.ITooltip; import snownee.jade.api.config.IPluginConfig; -import snownee.jade.api.ui.IElement; -import snownee.jade.impl.ui.ElementHelper; +import snownee.jade.api.ui.Element; +import snownee.jade.api.ui.JadeUI; import java.util.LinkedList; import java.util.List; @@ -57,7 +57,7 @@ public void appendTooltip(ITooltip tooltip, BlockAccessor accessor, IPluginConfi //Attempt to get species from server via NBT data if (nbtData.contains("species")) { - species = Species.findSpecies(Identifier.parse(nbtData.getString("species"))); + species = Species.findSpecies(Identifier.parse(nbtData.getString("species").orElseThrow())); } //Attempt to get species by checking if we're still looking at the same block @@ -93,7 +93,7 @@ public void appendTooltip(ITooltip tooltip, BlockAccessor accessor, IPluginConfi ItemStack seedStack = species.getSeedStack(1); - List elements = new LinkedList<>(); + List elements = new LinkedList<>(); elements.add(getElement(seedStack)); //adds seed; if (species.hasFruits()){ @@ -131,7 +131,7 @@ public void appendTooltip(ITooltip tooltip, BlockAccessor accessor, IPluginConfi tooltip.add(elements.removeFirst()); elements.forEach(tooltip::append); - tooltip.add(ElementHelper.INSTANCE.spacer(0, 2)); + tooltip.add(JadeUI.spacer(0, 2)); } } @@ -167,11 +167,11 @@ private Species getWailaSpecies(Level level, BlockPos pos) { return TreeHelper.getBestGuessSpecies(level, pos); } - private static IElement getElement(ItemStack stack) { + private static Element getElement(ItemStack stack) { if (!stack.isEmpty()) { - return ElementHelper.INSTANCE.item(stack); + return JadeUI.item(stack); } else { - return ElementHelper.INSTANCE.spacer(0, 0); + return JadeUI.spacer(0, 0); } } From 274f654eb9953db883dbe82e97749b1e68240d17 Mon Sep 17 00:00:00 2001 From: teddy Date: Fri, 21 Aug 2026 15:49:41 -0300 Subject: [PATCH 10/15] fabric: fix MixinVegetationBlock's Family->AerialRootsFamily cast Not MC-version drift: BasicRootsBlock already exposes getAerialFamily() (an (AerialRootsFamily) super.getFamily() cast, used throughout the rest of the class) precisely for this. The mixin was calling the untyped getFamily() instead - use the existing accessor. --- .../com/dtteam/dynamictrees/mixin/MixinVegetationBlock.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinVegetationBlock.java b/fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinVegetationBlock.java index 390acb646..b81305029 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinVegetationBlock.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinVegetationBlock.java @@ -24,7 +24,7 @@ private void canSurvive(BlockState state, LevelReader level, BlockPos pos, Callb BlockState belowBlockState = level.getBlockState(blockpos); if (belowBlockState.getBlock() instanceof BasicRootsBlock roots){ if (belowBlockState.getValue(BasicRootsBlock.LAYER) == BasicRootsBlock.Layer.COVERED){ - Block block = BasicRootsBlock.Layer.COVERED.getPrimitive(roots.getFamily()).orElse(null); + Block block = BasicRootsBlock.Layer.COVERED.getPrimitive(roots.getAerialFamily()).orElse(null); if (block == null) return; cir.setReturnValue(mayPlaceOn(block.defaultBlockState(), level, blockpos)); } From bad1a4c63b42da38567abcfccdbc3c32c36a7d1c Mon Sep 17 00:00:00 2001 From: teddy Date: Fri, 21 Aug 2026 15:49:51 -0300 Subject: [PATCH 11/15] fabric: drop dead overrideSaplingReplacementWhenCrouching() call Species.overrideSaplingReplacementWhenCrouching() does not exist anywhere on this branch's common/ code (confirmed via full-repo grep) - its only history is a single commit ("added override_sapling_replacement_when_ crouching to species") that is not an ancestor of develop/26.1.2, so the Species field/accessor this call depended on was never present here to begin with; this is not something the MC version bump removed. NeoForge's VanillaSaplingEventHandler (the working, shipping behavior on the other loader) never checks crouching at all in its equivalent code path - it always proceeds to replace/plant regardless. Removing the gate here makes Fabric match that existing NeoForge behavior exactly, rather than inventing a guess at what "crouching" should do. --- .../event/handler/VanillaSaplingEventHandler.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/VanillaSaplingEventHandler.java b/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/VanillaSaplingEventHandler.java index 52614ead8..b682aa7aa 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/VanillaSaplingEventHandler.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/VanillaSaplingEventHandler.java @@ -50,10 +50,6 @@ private static InteractionResult onUseBlock(Player player, Level level, Interact Species targetSpecies = DynamicSaplingBlock.SAPLING_REPLACERS.get(block); Species species = targetSpecies.selfOrLocationOverride(level, placePos); - if (species.overrideSaplingReplacementWhenCrouching() && player.isCrouching()){ - return InteractionResult.PASS; - } - if (!species.plantSapling(level, placePos, targetSpecies != species)) { if (!player.isCreative()) stack.grow(1); return InteractionResult.SUCCESS; From 4e8de3e5491e0d08ef494fdd10f07c8de70107b4 Mon Sep 17 00:00:00 2001 From: teddy Date: Fri, 21 Aug 2026 16:20:27 -0300 Subject: [PATCH 12/15] fabric: remove 5 dead access-widener entries that failed validateAccessWidener :fabric:compileJava never validates the AW file's entries against the real Minecraft classes - it just applies whatever's there for compile- time widening. :fabric:build's validateAccessWidener task does check, and failed on 5 entries whose target field/method/class no longer exists in this Minecraft version (RecipeManager.byType, WeightedStateProvider .weightedList's descriptor - the field's type changed from SimpleWeightedRandomList to WeightedList, IntrinsicHolderTagsProvider .IntrinsicTagAppender, SpriteContents.metadata(), SpriteSources.register). Confirmed via a full-repo grep that none of the 5 have any live caller in common/ or fabric/ (the field even the closest one, weightedList, is referenced by - is only used from a neoforge/-only datagen file, which doesn't consume this Fabric-only AW file at all). Rather than guess a plausible descriptor fix for entries nothing depends on, removed them. --- .../src/main/resources/META-INF/dynamictrees.accesswidener | 5 ----- 1 file changed, 5 deletions(-) diff --git a/common/src/main/resources/META-INF/dynamictrees.accesswidener b/common/src/main/resources/META-INF/dynamictrees.accesswidener index 18d661dff..976f6d07e 100644 --- a/common/src/main/resources/META-INF/dynamictrees.accesswidener +++ b/common/src/main/resources/META-INF/dynamictrees.accesswidener @@ -7,27 +7,22 @@ accessible field net/minecraft/world/level/storage/loot/parameters/LootContextPa accessible method net/minecraft/world/level/levelgen/feature/stateproviders/BlockStateProviderType (Lcom/mojang/serialization/MapCodec;)V accessible field net/minecraft/world/level/levelgen/structure/pools/StructureTemplatePool rawTemplates Ljava/util/List; accessible field net/minecraft/world/level/levelgen/structure/pools/StructureTemplatePool templates Lit/unimi/dsi/fastutil/objects/ObjectArrayList; -accessible field net/minecraft/world/item/crafting/RecipeManager byType Lcom/google/common/collect/Multimap; accessible field net/minecraft/world/item/Item craftingRemainingItem Lnet/minecraft/world/item/ItemStackTemplate; mutable field net/minecraft/world/item/Item craftingRemainingItem Lnet/minecraft/world/item/ItemStackTemplate; extendable method net/minecraft/world/item/Item getCraftingRemainder ()Lnet/minecraft/world/item/ItemStackTemplate; accessible field net/minecraft/world/level/chunk/LevelChunk loaded Z -accessible field net/minecraft/world/level/levelgen/feature/stateproviders/WeightedStateProvider weightedList Lnet/minecraft/util/random/SimpleWeightedRandomList; accessible field net/minecraft/commands/synchronization/ArgumentTypeInfos BY_CLASS Ljava/util/Map; accessible class net/minecraft/world/inventory/BrewingStandMenu$PotionSlot -accessible class net/minecraft/data/tags/IntrinsicHolderTagsProvider$IntrinsicTagAppender -accessible method net/minecraft/client/renderer/texture/SpriteContents metadata ()Lnet/minecraft/server/packs/resources/ResourceMetadata; accessible field net/minecraft/client/renderer/texture/SpriteContents originalImage Lcom/mojang/blaze3d/platform/NativeImage; accessible field net/minecraft/client/renderer/texture/SpriteContents additionalMetadata Ljava/util/List; accessible field net/minecraft/client/renderer/block/dispatch/multipart/MultiPartModel models Ljava/util/List; accessible field net/minecraft/world/level/block/entity/CreakingHeartBlockEntity ticksExisted J extendable method net/minecraft/world/level/block/entity/CreakingHeartBlockEntity spreadResin (Lnet/minecraft/server/level/ServerLevel;)Ljava/util/Optional; accessible method net/minecraft/world/level/block/entity/BlockEntityType (Lnet/minecraft/world/level/block/entity/BlockEntityType$BlockEntitySupplier;Ljava/util/Set;)V -accessible method net/minecraft/client/renderer/texture/atlas/SpriteSources register (Ljava/lang/String;Lcom/mojang/serialization/MapCodec;)Lnet/minecraft/client/renderer/texture/atlas/SpriteSourceType; accessible method net/minecraft/client/data/models/model/TextureSlot create (Ljava/lang/String;)Lnet/minecraft/client/data/models/model/TextureSlot; From 9ef175499c00deb872be2e3c037328202fa2fb21 Mon Sep 17 00:00:00 2001 From: teddy Date: Fri, 21 Aug 2026 16:35:33 -0300 Subject: [PATCH 13/15] fabric: stop eagerly building DendroPotion recipe ItemStacks during mod init Runtime crash found during Task 9's server smoke test: FabricRegistryLoader .setup() called DendroPotionRecipeHandler.getAllDendroRecipes() directly from DynamicTreesFabric.onInitialize() (the main Fabric entrypoint, which runs very early). That method builds ItemStacks from vanilla item Holders (Items.CHARCOAL etc.), which aren't bound yet at that point - NullPointerException: "Components not bound yet" in Holder$Reference .components(). NeoForge never had this problem because its own DendroPotionRecipeHandler is only invoked later, from RegisterBrewingRecipesEvent. Fabric already has an equivalent lazy trigger: MixinPotionBrewing injects into vanilla PotionBrewing's instance methods and calls the same handler, which runs only when brewing is actually checked at runtime (well after registries are bound). getAllDendroRecipes() already caches its result on first call, so removing the premature eager call is enough - confirmed via a clean server startup afterward. --- .../dtteam/dynamictrees/registry/FabricRegistryLoader.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/registry/FabricRegistryLoader.java b/fabric/src/main/java/com/dtteam/dynamictrees/registry/FabricRegistryLoader.java index edfd82afc..e0b234d30 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/registry/FabricRegistryLoader.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/registry/FabricRegistryLoader.java @@ -1,7 +1,6 @@ package com.dtteam.dynamictrees.registry; import com.dtteam.dynamictrees.DynamicTrees; -import com.dtteam.dynamictrees.recipe.DendroPotionRecipeHandler; import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.serialization.MapCodec; import net.fabricmc.fabric.api.object.builder.v1.entity.FabricEntityDataRegistry; @@ -45,7 +44,10 @@ public class FabricRegistryLoader extends RegistryLoader { public static void setup (){ DTRegistries.setup(); - DendroPotionRecipeHandler.getAllDendroRecipes(); + // DendroPotionRecipeHandler.getAllDendroRecipes() is intentionally not called eagerly + // here: it builds ItemStacks from vanilla item Holders that aren't bound yet this early + // in mod init ("Components not bound yet" NPE). It's lazily populated and cached on + // first real use instead, via MixinPotionBrewing's injections into PotionBrewing. } @Override From e70472bdf715422b320bc370ff4440692ddf6847 Mon Sep 17 00:00:00 2001 From: teddy Date: Fri, 21 Aug 2026 18:40:02 -0300 Subject: [PATCH 14/15] fabric: wire up common's generated resources, remove temporary -Xmaxerrs flag Found during Task 9's client smoke test: every single Dynamic Trees block (saplings, branches, leaves, roots - ~3000 warnings, not a handful) logged "Missing model for variant" on launch, because none of common/'s datagen output (src/generated/resources - blockstates, models, tags, everything DTExtraModelGenerator etc. produce) was ever making it into fabric's built jar. Not a Task 1-8 regression: this gap predates the whole plan. Root cause: the shared commonResources Gradle configuration (defined in common/build.gradle, consumed by both loaders via multiloader-loader .gradle's processResources block) only publishes sourceSets.main .resources - never src/generated/resources. neoforge/build.gradle already works around this with its own extra "sourceSets.main.resources { srcDir project(':common').file ('src/generated/resources') }" line; fabric/build.gradle never had the equivalent. Added it. Also removes the temporary -Xmaxerrs compiler flag (added early in this port to see past javac's 100-error display cap) now that :fabric:build is clean, per this task's own instruction. --- fabric/build.gradle | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/fabric/build.gradle b/fabric/build.gradle index d8ad7f5df..93cd1ec3e 100644 --- a/fabric/build.gradle +++ b/fabric/build.gradle @@ -50,9 +50,7 @@ loom { } } -tasks.withType(JavaCompile).configureEach { - options.compilerArgs << '-Xmaxerrs' << '100000' -} +sourceSets.main.resources { srcDir project(':common').file('src/generated/resources') } // Implement mcgradleconventions loader attribute def loaderAttribute = Attribute.of('io.github.mcgradleconventions.loader', String) From 082c1b09c107110b9b14e2bd6994f9974f1bbb4a Mon Sep 17 00:00:00 2001 From: teddy Date: Fri, 21 Aug 2026 18:50:28 -0300 Subject: [PATCH 15/15] fabric: fix custom branch/root/roots model registration (blank textures at runtime) Two compounding bugs found via the client smoke test - the branches and roots of every tree rendered with no texture: 1. DTModelLoadingPlugin.registerModelTypes() (which calls CustomUnbakedBlockStateModel.register(...) for each of DT's 7 custom model types - branch, roots, surface_root, creaking_heart, etc.) was defined but never called from anywhere. DynamicTreesFabricClient .registerModelLoaders() only called ModelLoadingPlugin.register(new DTModelLoadingPlugin()), so Fabric's model-loading registry never learned what "dynamictrees:branch" et al. mean - every custom-typed blockstate variant failed to resolve to any model. 2. Separately, decompiling Fabric's own CustomUnbakedBlockStateModelRegistry (fabric-model-loading-api-v1) confirmed it dispatches custom model types on a "fabric:type" key, not NeoForge's "type" key. common/'s generated blockstate JSONs (produced by a NeoForge-only datagen tool - see BasicLoaderBuilder.java) only ever wrote "type", so even with issue 1 fixed, Fabric's codec had nothing to key its dispatch on. Patched the 31 already-generated blockstate JSON files to carry both keys side by side, matching what DTModelLoadingPlugin's own javadoc already assumed was true. Confirmed fixed via the actual client smoke test: sapling/tree/branch textures now render, trees can be chopped, and bonemeal growth produces correctly rendered branches. Server smoke test and :neoforge:build both still pass. --- .../assets/dynamictrees/blockstates/acacia_branch.json | 1 + .../assets/dynamictrees/blockstates/birch_branch.json | 1 + .../assets/dynamictrees/blockstates/cherry_branch.json | 1 + .../assets/dynamictrees/blockstates/crimson_branch.json | 1 + .../assets/dynamictrees/blockstates/dark_oak_branch.json | 1 + .../assets/dynamictrees/blockstates/dark_oak_root.json | 1 + .../assets/dynamictrees/blockstates/jungle_branch.json | 1 + .../assets/dynamictrees/blockstates/jungle_root.json | 1 + .../assets/dynamictrees/blockstates/mangrove_branch.json | 1 + .../assets/dynamictrees/blockstates/mangrove_roots.json | 2 ++ .../assets/dynamictrees/blockstates/mossy_mangrove_roots.json | 3 +++ .../resources/assets/dynamictrees/blockstates/oak_branch.json | 1 + .../assets/dynamictrees/blockstates/pale_oak_branch.json | 1 + .../blockstates/pale_oak_creaking_heart_branch.json | 4 ++++ .../assets/dynamictrees/blockstates/pale_oak_root.json | 1 + .../assets/dynamictrees/blockstates/potted_sapling.json | 1 + .../dynamictrees/blockstates/resin_pale_oak_branch.json | 2 ++ .../dynamictrees/blockstates/rooty_mangrove_aerial_roots.json | 1 + .../assets/dynamictrees/blockstates/spruce_branch.json | 1 + .../dynamictrees/blockstates/stripped_acacia_branch.json | 1 + .../dynamictrees/blockstates/stripped_birch_branch.json | 1 + .../dynamictrees/blockstates/stripped_cherry_branch.json | 1 + .../dynamictrees/blockstates/stripped_crimson_branch.json | 1 + .../dynamictrees/blockstates/stripped_dark_oak_branch.json | 1 + .../dynamictrees/blockstates/stripped_jungle_branch.json | 1 + .../dynamictrees/blockstates/stripped_mangrove_branch.json | 1 + .../assets/dynamictrees/blockstates/stripped_oak_branch.json | 1 + .../dynamictrees/blockstates/stripped_pale_oak_branch.json | 1 + .../dynamictrees/blockstates/stripped_spruce_branch.json | 1 + .../dynamictrees/blockstates/stripped_warped_branch.json | 1 + .../assets/dynamictrees/blockstates/warped_branch.json | 1 + .../com/dtteam/dynamictrees/DynamicTreesFabricClient.java | 1 + 32 files changed, 39 insertions(+) diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/acacia_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/acacia_branch.json index 4e352b493..197c0f435 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/acacia_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/acacia_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:acacia", "textures": { "bark": "minecraft:block/acacia_log", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/birch_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/birch_branch.json index e195b2b56..940851ba8 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/birch_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/birch_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:birch", "textures": { "bark": "minecraft:block/birch_log", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/cherry_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/cherry_branch.json index 63a2e8b3b..5e6c550ef 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/cherry_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/cherry_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:cherry", "textures": { "bark": "minecraft:block/cherry_log", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/crimson_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/crimson_branch.json index f6d350f23..8cea8af81 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/crimson_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/crimson_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:crimson", "textures": { "bark": "minecraft:block/crimson_stem", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/dark_oak_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/dark_oak_branch.json index 8c7a4bee5..269baf8a6 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/dark_oak_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/dark_oak_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:dark_oak", "textures": { "bark": "minecraft:block/dark_oak_log", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/dark_oak_root.json b/common/src/generated/resources/assets/dynamictrees/blockstates/dark_oak_root.json index 7dd987b46..faab8d72a 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/dark_oak_root.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/dark_oak_root.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:surface_root", + "fabric:type": "dynamictrees:surface_root", "textures": { "bark": "minecraft:block/dark_oak_log" } diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/jungle_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/jungle_branch.json index 9a5f34a4c..81f776d7a 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/jungle_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/jungle_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:jungle", "textures": { "bark": "minecraft:block/jungle_log", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/jungle_root.json b/common/src/generated/resources/assets/dynamictrees/blockstates/jungle_root.json index c974dbf4a..386bbf09e 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/jungle_root.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/jungle_root.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:surface_root", + "fabric:type": "dynamictrees:surface_root", "textures": { "bark": "minecraft:block/jungle_log" } diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/mangrove_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/mangrove_branch.json index 8fc27ad61..d7322170c 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/mangrove_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/mangrove_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:mangrove", "textures": { "bark": "minecraft:block/mangrove_log", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/mangrove_roots.json b/common/src/generated/resources/assets/dynamictrees/blockstates/mangrove_roots.json index 5a8c51b14..650a54048 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/mangrove_roots.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/mangrove_roots.json @@ -5,6 +5,7 @@ }, "layer=exposed": { "type": "dynamictrees:roots", + "fabric:type": "dynamictrees:roots", "opaque": false, "textures": { "side": "minecraft:block/mangrove_roots_side", @@ -13,6 +14,7 @@ }, "layer=filled": { "type": "dynamictrees:roots", + "fabric:type": "dynamictrees:roots", "opaque": true, "textures": { "side": "minecraft:block/muddy_mangrove_roots_side", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/mossy_mangrove_roots.json b/common/src/generated/resources/assets/dynamictrees/blockstates/mossy_mangrove_roots.json index f127ee36d..5d707ddd8 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/mossy_mangrove_roots.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/mossy_mangrove_roots.json @@ -3,6 +3,7 @@ { "apply": { "type": "dynamictrees:roots", + "fabric:type": "dynamictrees:roots", "opaque": false, "textures": { "side": "minecraft:block/mangrove_roots_side", @@ -16,6 +17,7 @@ { "apply": { "type": "dynamictrees:roots", + "fabric:type": "dynamictrees:roots", "opaque": true, "textures": { "side": "minecraft:block/muddy_mangrove_roots_side", @@ -37,6 +39,7 @@ { "apply": { "type": "dynamictrees:roots_moss", + "fabric:type": "dynamictrees:roots_moss", "textures": { "moss": "minecraft:block/moss_block" } diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/oak_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/oak_branch.json index f3b48d6af..d7e8492c9 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/oak_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/oak_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:oak", "textures": { "bark": "minecraft:block/oak_log", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/pale_oak_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/pale_oak_branch.json index f7ede77a9..6aeac8918 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/pale_oak_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/pale_oak_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:pale_oak", "textures": { "bark": "minecraft:block/pale_oak_log", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/pale_oak_creaking_heart_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/pale_oak_creaking_heart_branch.json index 04a02c417..2792203a3 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/pale_oak_creaking_heart_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/pale_oak_creaking_heart_branch.json @@ -3,6 +3,7 @@ { "apply": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:pale_oak", "textures": { "bark": "minecraft:block/pale_oak_log", @@ -16,6 +17,7 @@ { "apply": { "type": "dynamictrees:creaking_heart", + "fabric:type": "dynamictrees:creaking_heart", "family": "dynamictrees:pale_oak", "textures": { "bark": "minecraft:block/pale_oak_log", @@ -31,6 +33,7 @@ { "apply": { "type": "dynamictrees:creaking_heart", + "fabric:type": "dynamictrees:creaking_heart", "family": "dynamictrees:pale_oak", "textures": { "bark": "minecraft:block/pale_oak_log", @@ -46,6 +49,7 @@ { "apply": { "type": "dynamictrees:creaking_heart", + "fabric:type": "dynamictrees:creaking_heart", "family": "dynamictrees:pale_oak", "textures": { "bark": "minecraft:block/pale_oak_log", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/pale_oak_root.json b/common/src/generated/resources/assets/dynamictrees/blockstates/pale_oak_root.json index 388444ac2..f37aa57c6 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/pale_oak_root.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/pale_oak_root.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:surface_root", + "fabric:type": "dynamictrees:surface_root", "textures": { "bark": "minecraft:block/pale_oak_log" } diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/potted_sapling.json b/common/src/generated/resources/assets/dynamictrees/blockstates/potted_sapling.json index 5030d937f..e8d6c9d8c 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/potted_sapling.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/potted_sapling.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:potted_dynamic_sapling", + "fabric:type": "dynamictrees:potted_dynamic_sapling", "model": "minecraft:block/flower_pot" } } diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/resin_pale_oak_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/resin_pale_oak_branch.json index 82d1566c8..a3de17fec 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/resin_pale_oak_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/resin_pale_oak_branch.json @@ -3,6 +3,7 @@ { "apply": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:pale_oak", "textures": { "bark": "minecraft:block/pale_oak_log", @@ -13,6 +14,7 @@ { "apply": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:pale_oak", "textures": { "bark": "minecraft:block/resin_clump", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/rooty_mangrove_aerial_roots.json b/common/src/generated/resources/assets/dynamictrees/blockstates/rooty_mangrove_aerial_roots.json index 91f364844..83b6a7f25 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/rooty_mangrove_aerial_roots.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/rooty_mangrove_aerial_roots.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:aerial_roots_soil", + "fabric:type": "dynamictrees:aerial_roots_soil", "family": "dynamictrees:mangrove", "textures": { "end": "minecraft:block/mangrove_log_top", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/spruce_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/spruce_branch.json index 64dc4384e..c9c796c56 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/spruce_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/spruce_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:spruce", "textures": { "bark": "minecraft:block/spruce_log", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_acacia_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_acacia_branch.json index a90a7ecac..0f6ba1645 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_acacia_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_acacia_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:acacia", "textures": { "bark": "minecraft:block/stripped_acacia_log", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_birch_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_birch_branch.json index 4c57c2fbb..f7608639d 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_birch_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_birch_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:birch", "textures": { "bark": "minecraft:block/stripped_birch_log", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_cherry_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_cherry_branch.json index ba7d0635f..abc08300e 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_cherry_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_cherry_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:cherry", "textures": { "bark": "minecraft:block/stripped_cherry_log", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_crimson_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_crimson_branch.json index 00c6b7049..a441d95f9 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_crimson_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_crimson_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:crimson", "textures": { "bark": "minecraft:block/stripped_crimson_stem", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_dark_oak_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_dark_oak_branch.json index bedb2a996..024eed2b1 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_dark_oak_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_dark_oak_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:dark_oak", "textures": { "bark": "minecraft:block/stripped_dark_oak_log", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_jungle_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_jungle_branch.json index cf4ba940a..1262144d0 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_jungle_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_jungle_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:jungle", "textures": { "bark": "minecraft:block/stripped_jungle_log", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_mangrove_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_mangrove_branch.json index 361f6ad11..b325ea15e 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_mangrove_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_mangrove_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:mangrove", "textures": { "bark": "minecraft:block/stripped_mangrove_log", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_oak_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_oak_branch.json index edfd98b36..380da7bb8 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_oak_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_oak_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:oak", "textures": { "bark": "minecraft:block/stripped_oak_log", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_pale_oak_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_pale_oak_branch.json index 742fad84a..a9c146ec9 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_pale_oak_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_pale_oak_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:pale_oak", "textures": { "bark": "minecraft:block/stripped_pale_oak_log", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_spruce_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_spruce_branch.json index e0b2f21e0..bbc7a8904 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_spruce_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_spruce_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:spruce", "textures": { "bark": "minecraft:block/stripped_spruce_log", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_warped_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_warped_branch.json index 4b4b50564..87c28a563 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_warped_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_warped_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:warped", "textures": { "bark": "minecraft:block/stripped_warped_stem", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/warped_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/warped_branch.json index 6deee0565..1a55f1593 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/warped_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/warped_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:warped", "textures": { "bark": "minecraft:block/warped_stem", diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabricClient.java b/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabricClient.java index 5d01b5382..3f9acb612 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabricClient.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabricClient.java @@ -76,6 +76,7 @@ private void registerClientWorldLoad() { } private void registerModelLoaders() { + DTModelLoadingPlugin.registerModelTypes(); ModelLoadingPlugin.register(new DTModelLoadingPlugin()); }