From 0fbfc2ce53c14d932bedf8a8a911bc573d0ed6d0 Mon Sep 17 00:00:00 2001 From: Oleksandr Hanzha Date: Fri, 24 Jul 2026 20:24:08 +0300 Subject: [PATCH 1/9] Port to Minecraft 26.2: toolchain bump + common API migration - Gradle 9.5.1, Loom 1.17.17, ModDev 2.0.142, NeoForm 26.2-2, NeoForge 26.2.0.32-beta - Fabric Loader 0.19.3, Fabric API 0.155.2+26.2, ForgeConfigAPIPort 26.2.1 - TagAppender single-generic migration (new TagUtils helper for ResourceKey lookup) - Tuple->Pair, advancements predicates package move, EntityTypes/BlockEntityTypes, Vec3.atCenterOf, Minecraft.levelExtractor, record accessors for feature configs - Removed obsolete MethodsReturnNonnullByDefault package-info files - Fabric: declare Jade/Continuity/SereneSeasons 26.2 compat deps via Modrinth maven Co-Authored-By: Claude Fable 5 --- build.gradle | 4 ++-- .../api/configuration/package-info.java | 4 ---- .../block/branch/package-info.java | 4 ---- .../block/leaves/DynamicLeavesBlock.java | 14 +++++------ .../block/leaves/LeavesProperties.java | 12 ++++++---- .../block/leaves/package-info.java | 4 ---- .../dynamictrees/block/package-info.java | 4 ---- .../block/soil/SoilProperties.java | 8 ++++--- .../dynamictrees/block/soil/package-info.java | 4 ---- .../dynamictrees/command/package-info.java | 4 ---- .../dtteam/dynamictrees/data/BiGenerator.java | 8 +++---- .../dynamictrees/data/DTLootTableBuilder.java | 2 +- .../deserializer/worldgen/package-info.java | 4 ---- .../deserialization/package-info.java | 4 ---- .../entity/FallingTreeEntity.java | 2 +- .../animation/FalloverAnimationHandler.java | 6 ++--- .../dynamictrees/item/package-info.java | 4 ---- .../loot/condition/package-info.java | 4 ---- .../dynamictrees/loot/entry/package-info.java | 4 ---- .../loot/function/package-info.java | 4 ---- .../systems/genfeature/BeeNestGenFeature.java | 4 +++- .../systems/genfeature/package-info.java | 4 ---- .../systems/season/NormalSeasonManager.java | 12 +++++----- .../tree/family/AerialRootsFamily.java | 10 ++++---- .../tree/family/AltBranchFamily.java | 10 ++++---- .../tree/family/CreakingHeartFamily.java | 10 ++++---- .../dynamictrees/tree/family/Family.java | 24 ++++++++++--------- .../tree/family/MossyAerialRootsFamily.java | 10 ++++---- .../dynamictrees/tree/species/Species.java | 14 ++++++----- .../dynamictrees/treepack/package-info.java | 4 ---- .../dtteam/dynamictrees/utility/TagUtils.java | 23 ++++++++++++++++++ .../TreeFeatureCanceller.java | 4 ++-- .../worldgen/structure/TreePoolElement.java | 2 -- fabric/build.gradle | 17 +++++++++++++ gradle.properties | 16 ++++++------- gradle/wrapper/gradle-wrapper.properties | 2 +- 36 files changed, 136 insertions(+), 134 deletions(-) delete mode 100644 common/src/main/java/com/dtteam/dynamictrees/api/configuration/package-info.java delete mode 100644 common/src/main/java/com/dtteam/dynamictrees/block/branch/package-info.java delete mode 100644 common/src/main/java/com/dtteam/dynamictrees/block/leaves/package-info.java delete mode 100644 common/src/main/java/com/dtteam/dynamictrees/block/package-info.java delete mode 100644 common/src/main/java/com/dtteam/dynamictrees/block/soil/package-info.java delete mode 100644 common/src/main/java/com/dtteam/dynamictrees/command/package-info.java delete mode 100644 common/src/main/java/com/dtteam/dynamictrees/deserialization/deserializer/worldgen/package-info.java delete mode 100644 common/src/main/java/com/dtteam/dynamictrees/deserialization/package-info.java delete mode 100644 common/src/main/java/com/dtteam/dynamictrees/item/package-info.java delete mode 100644 common/src/main/java/com/dtteam/dynamictrees/loot/condition/package-info.java delete mode 100644 common/src/main/java/com/dtteam/dynamictrees/loot/entry/package-info.java delete mode 100644 common/src/main/java/com/dtteam/dynamictrees/loot/function/package-info.java delete mode 100644 common/src/main/java/com/dtteam/dynamictrees/systems/genfeature/package-info.java delete mode 100644 common/src/main/java/com/dtteam/dynamictrees/treepack/package-info.java create mode 100644 common/src/main/java/com/dtteam/dynamictrees/utility/TagUtils.java diff --git a/build.gradle b/build.gradle index 27e333ade..5531051b7 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ plugins { // see https://fabricmc.net/develop/ for new versions - id 'net.fabricmc.fabric-loom' version '1.15.5' apply false + id 'net.fabricmc.fabric-loom' version '1.17.17' apply false // see https://projects.neoforged.net/neoforged/moddevgradle for new versions - id 'net.neoforged.moddev' version '2.0.141' apply false + id 'net.neoforged.moddev' version '2.0.142' apply false id "me.modmuss50.mod-publish-plugin" version "1.1.0" apply false } \ No newline at end of file diff --git a/common/src/main/java/com/dtteam/dynamictrees/api/configuration/package-info.java b/common/src/main/java/com/dtteam/dynamictrees/api/configuration/package-info.java deleted file mode 100644 index 1abc681aa..000000000 --- a/common/src/main/java/com/dtteam/dynamictrees/api/configuration/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@MethodsReturnNonnullByDefault -package com.dtteam.dynamictrees.api.configuration; - -import com.mojang.logging.annotations.MethodsReturnNonnullByDefault; \ No newline at end of file diff --git a/common/src/main/java/com/dtteam/dynamictrees/block/branch/package-info.java b/common/src/main/java/com/dtteam/dynamictrees/block/branch/package-info.java deleted file mode 100644 index f9d4c45f8..000000000 --- a/common/src/main/java/com/dtteam/dynamictrees/block/branch/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@MethodsReturnNonnullByDefault -package com.dtteam.dynamictrees.block.branch; - -import com.mojang.logging.annotations.MethodsReturnNonnullByDefault; \ No newline at end of file diff --git a/common/src/main/java/com/dtteam/dynamictrees/block/leaves/DynamicLeavesBlock.java b/common/src/main/java/com/dtteam/dynamictrees/block/leaves/DynamicLeavesBlock.java index d3078af63..562b515ef 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/block/leaves/DynamicLeavesBlock.java +++ b/common/src/main/java/com/dtteam/dynamictrees/block/leaves/DynamicLeavesBlock.java @@ -31,7 +31,7 @@ import net.minecraft.util.Mth; import net.minecraft.util.ParticleUtils; import net.minecraft.util.RandomSource; -import net.minecraft.util.Tuple; +import com.mojang.datafixers.util.Pair; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.InsideBlockEffectApplier; import net.minecraft.world.entity.LivingEntity; @@ -175,15 +175,15 @@ public void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource */ public boolean updateAllLeaves(LevelAccessor level, BlockPos startPos, BlockState startState, RandomSource rand, boolean worldGen){ //We store the position and hydro of the next block. - Queue> toProcess = new ArrayDeque<>(); + Queue> toProcess = new ArrayDeque<>(); Set processedPositions = new HashSet<>(); int firstHydro = updateHydro(level, startPos, startState, worldGen); - toProcess.add(new Tuple<>(startPos, firstHydro)); + toProcess.add(new Pair<>(startPos, firstHydro)); if (firstHydro == 0) return false; while (!toProcess.isEmpty() && processedPositions.size() <= getLeavesProperties().maxLeavesRecursion()){ - Tuple tup = toProcess.remove(); - BlockPos pos = tup.getA(); - int hydro = tup.getB(); + Pair tup = toProcess.remove(); + BlockPos pos = tup.getFirst(); + int hydro = tup.getSecond(); processedPositions.add(pos); for (Direction dir : Direction.values()) { // Go on all 6 sides of this block if (hydro > 1 || rand.nextInt(4) == 0) { // we'll give it a 1 in 4 chance to grow leaves if hydro is low to help performance @@ -201,7 +201,7 @@ public boolean updateAllLeaves(LevelAccessor level, BlockPos startPos, BlockStat //Do not iterate back through bigger hydro values //or if the leaves failed to grow if (sideHydro == 0 || sideHydro <= hydro){ - toProcess.add(new Tuple<>(sidePos, sideHydro)); + toProcess.add(new Pair<>(sidePos, sideHydro)); } } } diff --git a/common/src/main/java/com/dtteam/dynamictrees/block/leaves/LeavesProperties.java b/common/src/main/java/com/dtteam/dynamictrees/block/leaves/LeavesProperties.java index 4e6184a70..5dc35ffb2 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/block/leaves/LeavesProperties.java +++ b/common/src/main/java/com/dtteam/dynamictrees/block/leaves/LeavesProperties.java @@ -1,5 +1,9 @@ package com.dtteam.dynamictrees.block.leaves; +import net.minecraft.world.entity.EntityTypes; + +import com.dtteam.dynamictrees.utility.TagUtils; + import com.dtteam.dynamictrees.DynamicTrees; import com.dtteam.dynamictrees.api.cell.CellKit; import com.dtteam.dynamictrees.api.registry.RegistryEntry; @@ -573,7 +577,7 @@ public BlockBehaviour.Properties getDefaultBlockProperties() { .randomTicks() .sound(SoundType.GRASS) .noOcclusion() - .isValidSpawn((s, r, p, e) -> e == EntityType.OCELOT || e == EntityType.PARROT) + .isValidSpawn((s, r, p, e) -> e == EntityTypes.OCELOT || e == EntityTypes.PARROT) .isSuffocating((s, r, p) -> false) .isViewBlocking((s, r, p) -> false) .isRedstoneConductor((s, r, p) -> false) @@ -710,13 +714,13 @@ public String toReloadDataString() { Pair.of("connectAnyRadius", this.connectAnyRadius)); } - public void addGeneratedBlockTags (Function, TagAppender> tagAppender){ + public void addGeneratedBlockTags (Function, TagAppender> tagAppender){ getDynamicLeavesBlock().ifPresent(leaves -> defaultLeavesTags().forEach(tag -> { if (isOnlyIfLoaded()) { - tagAppender.apply(tag).addOptional(leaves); + tagAppender.apply(tag).addOptional(TagUtils.key(leaves)); } else { - tagAppender.apply(tag).add(leaves); + tagAppender.apply(tag).add(TagUtils.key(leaves)); } }) ); diff --git a/common/src/main/java/com/dtteam/dynamictrees/block/leaves/package-info.java b/common/src/main/java/com/dtteam/dynamictrees/block/leaves/package-info.java deleted file mode 100644 index 8a2058796..000000000 --- a/common/src/main/java/com/dtteam/dynamictrees/block/leaves/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@MethodsReturnNonnullByDefault -package com.dtteam.dynamictrees.block.leaves; - -import com.mojang.logging.annotations.MethodsReturnNonnullByDefault; \ No newline at end of file diff --git a/common/src/main/java/com/dtteam/dynamictrees/block/package-info.java b/common/src/main/java/com/dtteam/dynamictrees/block/package-info.java deleted file mode 100644 index 2242cf487..000000000 --- a/common/src/main/java/com/dtteam/dynamictrees/block/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@MethodsReturnNonnullByDefault -package com.dtteam.dynamictrees.block; - -import com.mojang.logging.annotations.MethodsReturnNonnullByDefault; \ No newline at end of file diff --git a/common/src/main/java/com/dtteam/dynamictrees/block/soil/SoilProperties.java b/common/src/main/java/com/dtteam/dynamictrees/block/soil/SoilProperties.java index 9c321b702..f8567d1fa 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/block/soil/SoilProperties.java +++ b/common/src/main/java/com/dtteam/dynamictrees/block/soil/SoilProperties.java @@ -1,5 +1,7 @@ package com.dtteam.dynamictrees.block.soil; +import com.dtteam.dynamictrees.utility.TagUtils; + import com.dtteam.dynamictrees.DynamicTrees; import com.dtteam.dynamictrees.api.registry.RegistryEntry; import com.dtteam.dynamictrees.api.registry.RegistryHandler; @@ -294,14 +296,14 @@ public void setOnlyIfLoaded(String onlyIfLoaded) { this.onlyIfLoaded.add(onlyIfLoaded); } - public void addGeneratedBlockTags (Function, TagAppender> tagAppender){ + public void addGeneratedBlockTags (Function, TagAppender> tagAppender){ // add rooty blocks to the rooty soil tag. getBlock().ifPresent(rootyBlock -> defaultSoilBlockTags().forEach(tag -> { if (!isOnlyIfLoaded()) { - tagAppender.apply(tag).add(rootyBlock); + tagAppender.apply(tag).add(TagUtils.key(rootyBlock)); } else { - tagAppender.apply(tag).addOptional(rootyBlock); + tagAppender.apply(tag).addOptional(TagUtils.key(rootyBlock)); } })); } diff --git a/common/src/main/java/com/dtteam/dynamictrees/block/soil/package-info.java b/common/src/main/java/com/dtteam/dynamictrees/block/soil/package-info.java deleted file mode 100644 index 9dd2b758a..000000000 --- a/common/src/main/java/com/dtteam/dynamictrees/block/soil/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@MethodsReturnNonnullByDefault -package com.dtteam.dynamictrees.block.soil; - -import com.mojang.logging.annotations.MethodsReturnNonnullByDefault; \ No newline at end of file diff --git a/common/src/main/java/com/dtteam/dynamictrees/command/package-info.java b/common/src/main/java/com/dtteam/dynamictrees/command/package-info.java deleted file mode 100644 index 200973484..000000000 --- a/common/src/main/java/com/dtteam/dynamictrees/command/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@MethodsReturnNonnullByDefault -package com.dtteam.dynamictrees.command; - -import com.mojang.logging.annotations.MethodsReturnNonnullByDefault; \ No newline at end of file diff --git a/common/src/main/java/com/dtteam/dynamictrees/data/BiGenerator.java b/common/src/main/java/com/dtteam/dynamictrees/data/BiGenerator.java index ff6dde83e..cfab352bf 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/data/BiGenerator.java +++ b/common/src/main/java/com/dtteam/dynamictrees/data/BiGenerator.java @@ -1,6 +1,6 @@ package com.dtteam.dynamictrees.data; -import net.minecraft.util.Tuple; +import com.mojang.datafixers.util.Pair; /** * A generator for a resource, providing the means to construct a Json file using provided {@link Dependencies} and @@ -10,11 +10,11 @@ * @param the type of the second generator * @param the type of the input to get the dependencies from */ -public interface BiGenerator extends Generator, I> { +public interface BiGenerator extends Generator, I> { @Override - default void generate(Tuple providers, I input, Dependencies dependencies){ - generate(providers.getA(), providers.getB(), input, dependencies); + default void generate(Pair providers, I input, Dependencies dependencies){ + generate(providers.getFirst(), providers.getSecond(), input, dependencies); } void generate(P1 provider1, P2 provider2, I input, Dependencies dependencies); diff --git a/common/src/main/java/com/dtteam/dynamictrees/data/DTLootTableBuilder.java b/common/src/main/java/com/dtteam/dynamictrees/data/DTLootTableBuilder.java index 63ba67c19..c633c7cd4 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/data/DTLootTableBuilder.java +++ b/common/src/main/java/com/dtteam/dynamictrees/data/DTLootTableBuilder.java @@ -8,7 +8,7 @@ import com.dtteam.dynamictrees.loot.function.MultiplyBySticksCount; import com.dtteam.dynamictrees.loot.function.MultiplyByTotalVolume; import com.dtteam.dynamictrees.utility.ItemUtils; -import net.minecraft.advancements.criterion.*; +import net.minecraft.advancements.predicates.*; import net.minecraft.core.HolderLookup; import net.minecraft.core.component.predicates.DataComponentPredicates; import net.minecraft.core.component.predicates.EnchantmentsPredicate; diff --git a/common/src/main/java/com/dtteam/dynamictrees/deserialization/deserializer/worldgen/package-info.java b/common/src/main/java/com/dtteam/dynamictrees/deserialization/deserializer/worldgen/package-info.java deleted file mode 100644 index 2a15654db..000000000 --- a/common/src/main/java/com/dtteam/dynamictrees/deserialization/deserializer/worldgen/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@MethodsReturnNonnullByDefault -package com.dtteam.dynamictrees.deserialization.deserializer.worldgen; - -import com.mojang.logging.annotations.MethodsReturnNonnullByDefault; \ No newline at end of file diff --git a/common/src/main/java/com/dtteam/dynamictrees/deserialization/package-info.java b/common/src/main/java/com/dtteam/dynamictrees/deserialization/package-info.java deleted file mode 100644 index cbec1edac..000000000 --- a/common/src/main/java/com/dtteam/dynamictrees/deserialization/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@MethodsReturnNonnullByDefault -package com.dtteam.dynamictrees.deserialization; - -import com.mojang.logging.annotations.MethodsReturnNonnullByDefault; \ No newline at end of file diff --git a/common/src/main/java/com/dtteam/dynamictrees/entity/FallingTreeEntity.java b/common/src/main/java/com/dtteam/dynamictrees/entity/FallingTreeEntity.java index 2d96262b6..8d6311716 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/entity/FallingTreeEntity.java +++ b/common/src/main/java/com/dtteam/dynamictrees/entity/FallingTreeEntity.java @@ -185,7 +185,7 @@ public void buildClient() { cleanupShellBlocks(destroyData); - Minecraft.getInstance().levelRenderer.setBlocksDirty(renderBounds.getMin().getX(), renderBounds.getMin().getY(), renderBounds.getMin().getZ(), renderBounds.getMax().getX(), renderBounds.getMax().getY(), renderBounds.getMax().getZ());//This forces the client to rerender the chunks + Minecraft.getInstance().levelExtractor.setBlocksDirty(renderBounds.getMin().getX(), renderBounds.getMin().getY(), renderBounds.getMin().getZ(), renderBounds.getMax().getX(), renderBounds.getMax().getY(), renderBounds.getMax().getZ());//This forces the client to rerender the chunks } protected void cleanupShellBlocks(BranchDestructionData destroyData) { diff --git a/common/src/main/java/com/dtteam/dynamictrees/entity/animation/FalloverAnimationHandler.java b/common/src/main/java/com/dtteam/dynamictrees/entity/animation/FalloverAnimationHandler.java index dbf41d0f0..be22de373 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/entity/animation/FalloverAnimationHandler.java +++ b/common/src/main/java/com/dtteam/dynamictrees/entity/animation/FalloverAnimationHandler.java @@ -191,7 +191,7 @@ private ParticleOptions getParticle(FallingTreeEntity entity, BlockState leavesS } protected void spawnParticlesAtLeaves(FallingTreeEntity entity, BlockPos leavesPos, ParticleOptions particle, Vec3 velocity, RandomSource rand, int particleCount, double limitChance){ - Vec3 newPos = getRelativeLeavesPosition(entity, leavesPos.getCenter()); + Vec3 newPos = getRelativeLeavesPosition(entity, Vec3.atCenterOf(leavesPos)); for (int j=0; j { + world.getBlockEntity(pos, BlockEntityTypes.BEEHIVE).ifPresent((blockEntity) -> { int j = 2 + random.nextInt(2); for(int k = 0; k < j; ++k) { diff --git a/common/src/main/java/com/dtteam/dynamictrees/systems/genfeature/package-info.java b/common/src/main/java/com/dtteam/dynamictrees/systems/genfeature/package-info.java deleted file mode 100644 index 11080a1e3..000000000 --- a/common/src/main/java/com/dtteam/dynamictrees/systems/genfeature/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@MethodsReturnNonnullByDefault -package com.dtteam.dynamictrees.systems.genfeature; - -import com.mojang.logging.annotations.MethodsReturnNonnullByDefault; \ No newline at end of file diff --git a/common/src/main/java/com/dtteam/dynamictrees/systems/season/NormalSeasonManager.java b/common/src/main/java/com/dtteam/dynamictrees/systems/season/NormalSeasonManager.java index 560a68cfd..490cdefec 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/systems/season/NormalSeasonManager.java +++ b/common/src/main/java/com/dtteam/dynamictrees/systems/season/NormalSeasonManager.java @@ -6,7 +6,7 @@ import com.dtteam.dynamictrees.api.season.SeasonProvider; import net.minecraft.core.BlockPos; import net.minecraft.resources.Identifier; -import net.minecraft.util.Tuple; +import com.mojang.datafixers.util.Pair; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.biome.Biome; @@ -22,23 +22,23 @@ public class NormalSeasonManager implements SeasonManager { public static final Supplier NULL = NormalSeasonManager::new; private final Map seasonContextMap = new HashMap<>(); - private Function> seasonMapper = w -> new Tuple<>(new NullSeasonProvider(), new NullSeasonGrowthCalculator()); + private Function> seasonMapper = w -> new Pair<>(new NullSeasonProvider(), new NullSeasonGrowthCalculator()); public NormalSeasonManager() { } - public NormalSeasonManager(Function> seasonMapper) { + public NormalSeasonManager(Function> seasonMapper) { this.seasonMapper = seasonMapper; } - private Tuple createProvider(Level level) { + private Pair createProvider(Level level) { return seasonMapper.apply(level); } private SeasonContext getContext(Level level) { return seasonContextMap.computeIfAbsent(level.dimension().identifier(), d -> { - Tuple tuple = createProvider(level); - return new SeasonContext(tuple.getA(), tuple.getB()); + Pair tuple = createProvider(level); + return new SeasonContext(tuple.getFirst(), tuple.getSecond()); }); } diff --git a/common/src/main/java/com/dtteam/dynamictrees/tree/family/AerialRootsFamily.java b/common/src/main/java/com/dtteam/dynamictrees/tree/family/AerialRootsFamily.java index 3e31f75e4..cdbe15304 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/tree/family/AerialRootsFamily.java +++ b/common/src/main/java/com/dtteam/dynamictrees/tree/family/AerialRootsFamily.java @@ -1,5 +1,7 @@ package com.dtteam.dynamictrees.tree.family; +import com.dtteam.dynamictrees.utility.TagUtils; + import com.dtteam.dynamictrees.DynamicTrees; import com.dtteam.dynamictrees.api.registry.TypedRegistry; import com.dtteam.dynamictrees.block.branch.BasicRootsBlock; @@ -214,16 +216,16 @@ public Optional getPrimitiveCoveredRoots() { } @Override - public void addGeneratedBlockTags (Function, TagAppender> tagAppender){ + public void addGeneratedBlockTags (Function, TagAppender> tagAppender){ super.addGeneratedBlockTags(tagAppender); //Create roots tag and root harvest tag if the family is mangrove-like. getRoots().ifPresent(roots -> { - this.tierTag(getDefaultRootsHarvestTier(), tagAppender).ifPresent(tagBuilder -> tagBuilder.add(roots)); + this.tierTag(getDefaultRootsHarvestTier(), tagAppender).ifPresent(tagBuilder -> tagBuilder.add(TagUtils.key(roots))); defaultRootsTags().forEach(tag -> { if (!isOnlyIfLoaded()) { - tagAppender.apply(tag).add(roots); + tagAppender.apply(tag).add(TagUtils.key(roots)); } else { - tagAppender.apply(tag).addOptional(roots); + tagAppender.apply(tag).addOptional(TagUtils.key(roots)); } }); }); diff --git a/common/src/main/java/com/dtteam/dynamictrees/tree/family/AltBranchFamily.java b/common/src/main/java/com/dtteam/dynamictrees/tree/family/AltBranchFamily.java index 870c72d2d..6353aa36a 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/tree/family/AltBranchFamily.java +++ b/common/src/main/java/com/dtteam/dynamictrees/tree/family/AltBranchFamily.java @@ -1,5 +1,7 @@ package com.dtteam.dynamictrees.tree.family; +import com.dtteam.dynamictrees.utility.TagUtils; + import com.dtteam.dynamictrees.DynamicTrees; import com.dtteam.dynamictrees.api.registry.TypedRegistry; import com.dtteam.dynamictrees.block.branch.BasicBranchBlock; @@ -110,15 +112,15 @@ public void addBranchTextures(BiConsumer textureConsumer, Id super.addBranchTextures(textureConsumer, primitiveLogLocation, sourceBlock); } - public void addGeneratedBlockTags (Function, TagAppender> tagAppender){ + public void addGeneratedBlockTags (Function, TagAppender> tagAppender){ super.addGeneratedBlockTags(tagAppender); getAltBranch().ifPresent(branch -> { - tierTag(getDefaultBranchHarvestTier(), tagAppender).ifPresent(tagBuilder -> tagBuilder.add(branch)); + tierTag(getDefaultBranchHarvestTier(), tagAppender).ifPresent(tagBuilder -> tagBuilder.add(TagUtils.key(branch))); defaultBranchTags().forEach(tag -> { if (!isOnlyIfLoaded()) { - tagAppender.apply(tag).add(branch); + tagAppender.apply(tag).add(TagUtils.key(branch)); } else { - tagAppender.apply(tag).addOptional(branch); + tagAppender.apply(tag).addOptional(TagUtils.key(branch)); } }); }); diff --git a/common/src/main/java/com/dtteam/dynamictrees/tree/family/CreakingHeartFamily.java b/common/src/main/java/com/dtteam/dynamictrees/tree/family/CreakingHeartFamily.java index 2c07153a0..3b7ebf3bc 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/tree/family/CreakingHeartFamily.java +++ b/common/src/main/java/com/dtteam/dynamictrees/tree/family/CreakingHeartFamily.java @@ -1,5 +1,7 @@ package com.dtteam.dynamictrees.tree.family; +import com.dtteam.dynamictrees.utility.TagUtils; + import com.dtteam.dynamictrees.DynamicTrees; import com.dtteam.dynamictrees.api.registry.TypedRegistry; import com.dtteam.dynamictrees.block.branch.*; @@ -113,15 +115,15 @@ public void addHeartTextures(BiConsumer textureConsumer, Ide } } - public void addGeneratedBlockTags (Function, TagAppender> tagAppender){ + public void addGeneratedBlockTags (Function, TagAppender> tagAppender){ super.addGeneratedBlockTags(tagAppender); getHeartBranch().ifPresent(branch -> { - tierTag(getDefaultBranchHarvestTier(), tagAppender).ifPresent(tagBuilder -> tagBuilder.add(branch)); + tierTag(getDefaultBranchHarvestTier(), tagAppender).ifPresent(tagBuilder -> tagBuilder.add(TagUtils.key(branch))); defaultBranchTags().forEach(tag -> { if (!isOnlyIfLoaded()) { - tagAppender.apply(tag).add(branch); + tagAppender.apply(tag).add(TagUtils.key(branch)); } else { - tagAppender.apply(tag).addOptional(branch); + tagAppender.apply(tag).addOptional(TagUtils.key(branch)); } }); }); diff --git a/common/src/main/java/com/dtteam/dynamictrees/tree/family/Family.java b/common/src/main/java/com/dtteam/dynamictrees/tree/family/Family.java index a478eb678..055856c79 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/tree/family/Family.java +++ b/common/src/main/java/com/dtteam/dynamictrees/tree/family/Family.java @@ -1,5 +1,7 @@ package com.dtteam.dynamictrees.tree.family; +import com.dtteam.dynamictrees.utility.TagUtils; + import com.dtteam.dynamictrees.DynamicTrees; import com.dtteam.dynamictrees.api.registry.RegistryEntry; import com.dtteam.dynamictrees.api.registry.RegistryHandler; @@ -715,33 +717,33 @@ public List> defaultStrippedBranchTags() { Collections.singletonList(DTBlockTags.STRIPPED_BRANCHES_THAT_BURN); } - public void addGeneratedBlockTags (Function, TagAppender> tagAppender){ + public void addGeneratedBlockTags (Function, TagAppender> tagAppender){ getBranch().ifPresent(branch -> { - tierTag(getDefaultBranchHarvestTier(), tagAppender).ifPresent(tagBuilder -> tagBuilder.add(branch)); + tierTag(getDefaultBranchHarvestTier(), tagAppender).ifPresent(tagBuilder -> tagBuilder.add(TagUtils.key(branch))); defaultBranchTags().forEach(tag -> { if (!isOnlyIfLoaded()) { - tagAppender.apply(tag).add(branch); + tagAppender.apply(tag).add(TagUtils.key(branch)); } else { - tagAppender.apply(tag).addOptional(branch); + tagAppender.apply(tag).addOptional(TagUtils.key(branch)); } }); }); // Create stripped branch tag and harvest tag if the family has a stripped branch. getStrippedBranch().ifPresent(strippedBranch -> { - tierTag(getDefaultStrippedBranchHarvestTier(), tagAppender).ifPresent(tagBuilder -> tagBuilder.add(strippedBranch)); + tierTag(getDefaultStrippedBranchHarvestTier(), tagAppender).ifPresent(tagBuilder -> tagBuilder.add(TagUtils.key(strippedBranch))); defaultStrippedBranchTags().forEach(tag -> { if (!isOnlyIfLoaded()) { - tagAppender.apply(tag).add(strippedBranch); + tagAppender.apply(tag).add(TagUtils.key(strippedBranch)); } else { - tagAppender.apply(tag).addOptional(strippedBranch); + tagAppender.apply(tag).addOptional(TagUtils.key(strippedBranch)); } }); }); } - protected Optional> tierTag(@Nullable ToolMaterial tier, Function, TagAppender> tagAppender) { + protected Optional> tierTag(@Nullable ToolMaterial tier, Function, TagAppender> tagAppender) { if (tier == null) return Optional.empty(); @@ -750,12 +752,12 @@ protected Optional> tierTag(@Nullable ToolMaterial tie return Optional.of(tagAppender.apply(tag)); } - public void addGeneratedItemTags (Function, TagAppender> tagAppender){ + public void addGeneratedItemTags (Function, TagAppender> tagAppender){ getBranchItem().ifPresent(item -> { if (!isOnlyIfLoaded()) { - defaultBranchItemTags().forEach(tag -> tagAppender.apply(tag).add(item)); + defaultBranchItemTags().forEach(tag -> tagAppender.apply(tag).add(TagUtils.key(item))); } else { - defaultBranchItemTags().forEach(tag -> tagAppender.apply(tag).addOptional(item)); + defaultBranchItemTags().forEach(tag -> tagAppender.apply(tag).addOptional(TagUtils.key(item))); } } ); diff --git a/common/src/main/java/com/dtteam/dynamictrees/tree/family/MossyAerialRootsFamily.java b/common/src/main/java/com/dtteam/dynamictrees/tree/family/MossyAerialRootsFamily.java index 9ecbcdcf5..4a2c5366c 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/tree/family/MossyAerialRootsFamily.java +++ b/common/src/main/java/com/dtteam/dynamictrees/tree/family/MossyAerialRootsFamily.java @@ -1,5 +1,7 @@ package com.dtteam.dynamictrees.tree.family; +import com.dtteam.dynamictrees.utility.TagUtils; + import com.dtteam.dynamictrees.DynamicTrees; import com.dtteam.dynamictrees.api.registry.TypedRegistry; import com.dtteam.dynamictrees.block.branch.BasicRootsBlock; @@ -107,16 +109,16 @@ public List getBlockModelGenerators() { } @Override - public void addGeneratedBlockTags (Function, TagAppender> tagAppender){ + public void addGeneratedBlockTags (Function, TagAppender> tagAppender){ super.addGeneratedBlockTags(tagAppender); //Create roots tag and root harvest tag if the family is mangrove-like. getMossyRoots().ifPresent(roots -> { - this.tierTag(getDefaultRootsHarvestTier(), tagAppender).ifPresent(tagBuilder -> tagBuilder.add(roots)); + this.tierTag(getDefaultRootsHarvestTier(), tagAppender).ifPresent(tagBuilder -> tagBuilder.add(TagUtils.key(roots))); defaultRootsTags().forEach(tag -> { if (!isOnlyIfLoaded()) { - tagAppender.apply(tag).add(roots); + tagAppender.apply(tag).add(TagUtils.key(roots)); } else { - tagAppender.apply(tag).addOptional(roots); + tagAppender.apply(tag).addOptional(TagUtils.key(roots)); } }); }); diff --git a/common/src/main/java/com/dtteam/dynamictrees/tree/species/Species.java b/common/src/main/java/com/dtteam/dynamictrees/tree/species/Species.java index adb8bc4be..0a059d87b 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/tree/species/Species.java +++ b/common/src/main/java/com/dtteam/dynamictrees/tree/species/Species.java @@ -1,5 +1,7 @@ package com.dtteam.dynamictrees.tree.species; +import com.dtteam.dynamictrees.utility.TagUtils; + import com.dtteam.dynamictrees.DynamicTrees; import com.dtteam.dynamictrees.api.lazyvalue.LazyValue; import com.dtteam.dynamictrees.api.network.BranchDestructionData; @@ -2387,20 +2389,20 @@ public Identifier getSeedParentModelLocation() { return DynamicTrees.location("item/standard_seed"); } - public void addGeneratedBlockTags (Function, TagAppender> tagAppender){ + public void addGeneratedBlockTags (Function, TagAppender> tagAppender){ // Create dynamic sapling block tags. getSapling().ifPresent(sapling -> defaultSaplingTags().forEach(tag -> { if (isOnlyIfLoaded()) { - tagAppender.apply(tag).addOptional(sapling); + tagAppender.apply(tag).addOptional(TagUtils.key(sapling)); } else { - tagAppender.apply(tag).add(sapling); + tagAppender.apply(tag).add(TagUtils.key(sapling)); } }) ); } - public void addGeneratedItemTags (Function, TagAppender> tagAppender){ + public void addGeneratedItemTags (Function, TagAppender> tagAppender){ // Some species return the common seed, so only return if the species has its own seed. if (!hasSeed()) { return; @@ -2409,9 +2411,9 @@ public void addGeneratedItemTags (Function, TagAppender getSeed().ifPresent(seed -> defaultSeedTags().forEach(tag ->{ if (isOnlyIfLoaded()) { - tagAppender.apply(tag).addOptional(seed); + tagAppender.apply(tag).addOptional(TagUtils.key(seed)); } else { - tagAppender.apply(tag).add(seed); + tagAppender.apply(tag).add(TagUtils.key(seed)); } }) ); diff --git a/common/src/main/java/com/dtteam/dynamictrees/treepack/package-info.java b/common/src/main/java/com/dtteam/dynamictrees/treepack/package-info.java deleted file mode 100644 index 0836ea062..000000000 --- a/common/src/main/java/com/dtteam/dynamictrees/treepack/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@MethodsReturnNonnullByDefault -package com.dtteam.dynamictrees.treepack; - -import com.mojang.logging.annotations.MethodsReturnNonnullByDefault; \ No newline at end of file diff --git a/common/src/main/java/com/dtteam/dynamictrees/utility/TagUtils.java b/common/src/main/java/com/dtteam/dynamictrees/utility/TagUtils.java new file mode 100644 index 000000000..5ecd81d71 --- /dev/null +++ b/common/src/main/java/com/dtteam/dynamictrees/utility/TagUtils.java @@ -0,0 +1,23 @@ +package com.dtteam.dynamictrees.utility; + +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; + +/** + * Helpers for converting registered objects to the {@link ResourceKey}s required by + * {@link net.minecraft.data.tags.TagAppender} since 26.2. + */ +public final class TagUtils { + + private TagUtils() {} + + public static ResourceKey key(Block block) { + return BuiltInRegistries.BLOCK.getResourceKey(block).orElseThrow(); + } + + public static ResourceKey key(Item item) { + return BuiltInRegistries.ITEM.getResourceKey(item).orElseThrow(); + } +} diff --git a/common/src/main/java/com/dtteam/dynamictrees/worldgen/featurecancellation/TreeFeatureCanceller.java b/common/src/main/java/com/dtteam/dynamictrees/worldgen/featurecancellation/TreeFeatureCanceller.java index 8c1e0b6b4..0c161117d 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/worldgen/featurecancellation/TreeFeatureCanceller.java +++ b/common/src/main/java/com/dtteam/dynamictrees/worldgen/featurecancellation/TreeFeatureCanceller.java @@ -47,8 +47,8 @@ but one (unclean) solution may be to add the non-tree features back to the gener private boolean doesContainTrees(RandomFeatureConfiguration featureConfig, BiomePropertySelectors.NormalFeatureCancellation featureCancellations) { - for (WeightedPlacedFeature feature : featureConfig.features) { - final PlacedFeature currentConfiguredFeature = feature.feature.value(); + for (WeightedPlacedFeature feature : featureConfig.features()) { + final PlacedFeature currentConfiguredFeature = feature.feature().value(); ConfiguredFeature cf = currentConfiguredFeature.getFeatures() .findFirst().map(Holder::value).orElse(null); if (cf == null) return false; diff --git a/common/src/main/java/com/dtteam/dynamictrees/worldgen/structure/TreePoolElement.java b/common/src/main/java/com/dtteam/dynamictrees/worldgen/structure/TreePoolElement.java index 132ef0d24..690d3de71 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/worldgen/structure/TreePoolElement.java +++ b/common/src/main/java/com/dtteam/dynamictrees/worldgen/structure/TreePoolElement.java @@ -2,7 +2,6 @@ import com.dtteam.dynamictrees.registry.DTRegistries; import com.dtteam.dynamictrees.tree.species.Species; -import com.mojang.logging.annotations.MethodsReturnNonnullByDefault; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; import net.minecraft.core.BlockPos; @@ -30,7 +29,6 @@ /** * @author Harley O'Connor */ -@MethodsReturnNonnullByDefault public final class TreePoolElement extends StructurePoolElement { public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance diff --git a/fabric/build.gradle b/fabric/build.gradle index 55794b978..90bbfc6c2 100644 --- a/fabric/build.gradle +++ b/fabric/build.gradle @@ -5,10 +5,27 @@ 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}" + compileOnly "maven.modrinth:jade:26.2.9+fabric" + compileOnly "maven.modrinth:continuity:3.0.1+26.2" + compileOnly "maven.modrinth:serene-seasons:26.1.2.0.4" } loom { diff --git a/gradle.properties b/gradle.properties index 37b51b298..c9c104bfc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,18 +17,18 @@ fabricVersionType=beta # Common java_version=25 -minecraft_version=26.1.2 -minecraft_version_range=[26.1, 26.2) -neo_form_version=26.1.2-1 -forge_config_api_port_version=26.1.4 +minecraft_version=26.2 +minecraft_version_range=[26.2, 26.3) +neo_form_version=26.2-2 +forge_config_api_port_version=26.2.1 # Fabric, see https://fabricmc.net/develop/ for new versions -fabric_version=0.145.3+26.1.1 -fabric_loader_version=0.18.6 -loom_version=1.9.2 +fabric_version=0.155.2+26.2 +fabric_loader_version=0.19.3 +loom_version=1.17.17 # NeoForge, see https://projects.neoforged.net/neoforged/neoforge for new versions -neoforge_version=26.1.2.71 +neoforge_version=26.2.0.32-beta neoforge_loader_version_range=[4,) # Gradle diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 23449a2b5..5dd3c0121 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From 369841b5b3a99e8bd2a640cca540fafc3bb2ae38 Mon Sep 17 00:00:00 2001 From: Oleksandr Hanzha Date: Fri, 24 Jul 2026 20:58:06 +0300 Subject: [PATCH 2/9] Port Fabric client to 26.2: new renderer API model layer, tints, compat fixes Co-Authored-By: Claude Fable 5 --- .../META-INF/dynamictrees.accesswidener | 24 +- fabric/build.gradle | 3 + .../dynamictrees/DynamicTreesFabric.java | 6 +- .../DynamicTreesFabricClient.java | 182 ++++---- .../compat/SereneSeasonsSeasonProvider.java | 8 +- .../ContinuityWrappedModelHandler.java | 29 +- .../continuity/WrappedModelHandler.java | 11 +- .../compat/waila/WailaBranchHandler.java | 102 ++--- .../event/handler/CommonEventHandler.java | 23 +- .../handler/VanillaSaplingEventHandler.java | 8 +- .../mixin/MixinVegetationBlock.java | 3 +- .../model/BakedModelBlockPottedSapling.java | 33 +- .../model/BranchBlockUnbakedModel.java | 59 --- .../model/DTModelLoadingPlugin.java | 296 +++++-------- .../model/FallingTreeEntityModelFabric.java | 176 +------- .../baked/BasicBranchBlockBakedModel.java | 412 ++++++------------ .../baked/BasicRootsBlockBakedModel.java | 150 ++----- .../baked/SurfaceRootBlockBakedModel.java | 404 +++++------------ .../baked/ThickBranchBlockBakedModel.java | 242 +++------- .../platform/FabricClientHelper.java | 4 +- .../platform/FabricCompatHelper.java | 13 +- .../platform/FabricInteractionHelper.java | 12 +- .../recipe/DendroPotionRecipeHandler.java | 2 +- .../registry/FabricRegistryLoader.java | 32 +- .../worldgen/FabricBiomeModifications.java | 6 +- .../worldgen/holderset/DTBiomeHolderSet.java | 5 + .../worldgen/holderset/DelayedHolderSet.java | 5 + .../holderset/NameRegexMatchHolderSet.java | 2 +- .../holderset/StreamBackedHolderSet.java | 8 +- 29 files changed, 745 insertions(+), 1515 deletions(-) delete mode 100644 fabric/src/main/java/com/dtteam/dynamictrees/model/BranchBlockUnbakedModel.java diff --git a/common/src/main/resources/META-INF/dynamictrees.accesswidener b/common/src/main/resources/META-INF/dynamictrees.accesswidener index 39ea50edd..e80d66106 100644 --- a/common/src/main/resources/META-INF/dynamictrees.accesswidener +++ b/common/src/main/resources/META-INF/dynamictrees.accesswidener @@ -7,23 +7,31 @@ 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/Item; -mutable field net/minecraft/world/item/Item craftingRemainingItem Lnet/minecraft/world/item/Item; +accessible method net/minecraft/world/item/Item getCraftingRemainder ()Lnet/minecraft/world/item/ItemStackTemplate; +extendable method net/minecraft/world/item/Item getCraftingRemainder ()Lnet/minecraft/world/item/ItemStackTemplate; +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; 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/world/level/levelgen/feature/stateproviders/WeightedStateProvider weightedList Lnet/minecraft/util/random/WeightedList; 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 method net/minecraft/client/renderer/texture/atlas/SpriteSources register (Ljava/lang/String;Lcom/mojang/serialization/MapCodec;)Lnet/minecraft/client/renderer/texture/atlas/SpriteSourceType; +accessible field net/minecraft/client/renderer/texture/SpriteContents additionalMetadata Ljava/util/List; 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; -mutable field net/minecraft/world/level/block/entity/BlockEntity blockState Lnet/minecraft/world/level/block/state/BlockState; \ No newline at end of file +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; + +extendable method net/minecraft/world/level/block/entity/CreakingHeartBlockEntity spreadResin (Lnet/minecraft/server/level/ServerLevel;)Ljava/util/Optional; +accessible field net/minecraft/world/level/block/entity/CreakingHeartBlockEntity ticksExisted J + +accessible field net/minecraft/client/renderer/block/dispatch/multipart/MultiPartModel models Ljava/util/List; + +accessible field net/minecraft/client/color/item/ItemTintSources ID_MAPPER Lnet/minecraft/util/ExtraCodecs$LateBoundIdMapper; diff --git a/fabric/build.gradle b/fabric/build.gradle index 90bbfc6c2..31ba3ec90 100644 --- a/fabric/build.gradle +++ b/fabric/build.gradle @@ -26,6 +26,9 @@ dependencies { compileOnly "maven.modrinth:jade:26.2.9+fabric" compileOnly "maven.modrinth:continuity:3.0.1+26.2" compileOnly "maven.modrinth:serene-seasons:26.1.2.0.4" + compileOnly "maven.modrinth:glitchcore:26.2.0.0.0" // transitive API of serene-seasons, needed at compile time + implementation "fuzs.forgeconfigapiport:forgeconfigapiport-fabric:${project.forge_config_api_port_version}" + compileOnly group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.2' // javax.annotation used by common sources } loom { diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabric.java b/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabric.java index 7023e005b..a04ee83d8 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabric.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabric.java @@ -9,7 +9,7 @@ import com.dtteam.dynamictrees.registry.FabricRegistryHandler; import com.dtteam.dynamictrees.registry.FabricRegistryLoader; import com.dtteam.dynamictrees.worldgen.FabricBiomeModifications; -import fuzs.forgeconfigapiport.fabric.api.neoforge.v4.*; +import fuzs.forgeconfigapiport.fabric.api.v5.ConfigRegistry; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.loader.api.FabricLoader; @@ -21,8 +21,8 @@ public class DynamicTreesFabric implements ModInitializer { @Override public void onInitialize() { - NeoForgeConfigRegistry.INSTANCE.register(DynamicTrees.MOD_ID,ModConfig.Type.SERVER, DTConfigs.SERVER_CONFIG); - NeoForgeConfigRegistry.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 4587c2371..9af0ba758 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.*; @@ -16,15 +17,20 @@ 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.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.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.*; @@ -46,11 +52,12 @@ public class DynamicTreesFabricClient implements ClientModInitializer { @Override public void onInitializeClient() { - NeoForgeConfigRegistry.INSTANCE.register(DynamicTrees.MOD_ID, ModConfig.Type.CLIENT, DTConfigs.CLIENT_CONFIG); - AtlasSourceTypeRegistryImpl.register(ThickBranchRingsSource.ID, ThickBranchRingsSource.setType(ThickBranchRingsSource.CODEC)); + ConfigRegistry.INSTANCE.register(DynamicTrees.MOD_ID, ModConfig.Type.CLIENT, DTConfigs.CLIENT_CONFIG); + SpriteSourceRegistry.register(ThickBranchRingsSource.ID, ThickBranchRingsSource.CODEC); registerModelLoaders(); registerEntityRenderers(); registerColorHandlers(); + registerItemTintSources(); registerTooltipCallback(); registerClientTick(); registerClientWorldLoad(); @@ -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,62 @@ private void registerEntityRenderers() { } private void registerColorHandlers() { - BlockColorMultipliers.register("birch", (state, level, pos, tintIndex) -> FoliageColor.getBirchColor()); - BlockColorMultipliers.register("spruce", (state, level, pos, tintIndex) -> FoliageColor.getEvergreenColor()); + BlockColorMultipliers.register("birch", BlockTintSources.constant(FoliageColor.FOLIAGE_BIRCH)); + BlockColorMultipliers.register("spruce", BlockTintSources.constant(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()); + private void registerItemTintSources() { + // Mirrors the NeoForge RegisterColorHandlersEvent.ItemTintSources registrations; the + // item model JSONs reference these tint source types by id. + 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()) - ); - } - } + // 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 +172,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 +203,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; + } } 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..1519d6894 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/compat/SereneSeasonsSeasonProvider.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/compat/SereneSeasonsSeasonProvider.java @@ -3,8 +3,8 @@ import com.dtteam.dynamictrees.DynamicTrees; import com.dtteam.dynamictrees.api.season.SeasonProvider; import com.dtteam.dynamictrees.systems.season.*; +import com.mojang.datafixers.util.Pair; import net.minecraft.core.BlockPos; -import net.minecraft.util.Tuple; import net.minecraft.world.level.Level; import sereneseasons.api.season.Season; import sereneseasons.init.ModConfig; @@ -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; } @@ -35,8 +35,8 @@ public static void registerSereneSeasonsProvider (){ SeasonCompatibilityHandler.registerSeasonManager(DynamicTrees.SERENE_SEASONS, () -> { NormalSeasonManager seasonManager = new NormalSeasonManager( world -> ModConfig.seasons.isDimensionWhitelisted(world.dimension()) ? - new Tuple<>(new SereneSeasonsSeasonProvider(), new ActiveSeasonGrowthCalculator()) : - new Tuple<>(new NullSeasonProvider(), new NullSeasonGrowthCalculator()) + new Pair<>(new SereneSeasonsSeasonProvider(), new ActiveSeasonGrowthCalculator()) : + new Pair<>(new NullSeasonProvider(), new NullSeasonGrowthCalculator()) ); seasonManager.setTropicalPredicate((world, pos) -> sereneseasons.api.season.SeasonHelper.usesTropicalSeasons(world.getBiome(pos))); return seasonManager; 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/compat/waila/WailaBranchHandler.java b/fabric/src/main/java/com/dtteam/dynamictrees/compat/waila/WailaBranchHandler.java index 89556b3fe..a66504ff3 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,6 @@ 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 java.util.LinkedList; import java.util.List; @@ -57,7 +55,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").get())); } //Attempt to get species by checking if we're still looking at the same block @@ -91,47 +89,49 @@ public void appendTooltip(ITooltip tooltip, BlockAccessor accessor, IPluginConfi tooltip.add(Component.literal(ChatFormatting.DARK_GRAY + species.getRegistryName().toString())); } - ItemStack seedStack = species.getSeedStack(1); - - List elements = new LinkedList<>(); - elements.add(getElement(seedStack)); //adds seed; - - if (species.hasFruits()){ - for (Fruit fruit : species.getFruits()){ - ItemStack fruitStack = fruit.getItemStack(); - if (fruitStack.getItem() != seedStack.getItem()) - elements.add(getElement(fruitStack)); - } - } - if (species.hasPods()){ - for (Pod pod : species.getPods()){ - ItemStack podStack = pod.getItemStack(); - if (podStack.getItem() != seedStack.getItem()) - elements.add(getElement(podStack)); - } - } - - int silkTouch = ItemUtils.getEnchantmentLevel(Enchantments.SILK_TOUCH, accessor.getPlayer().getMainHandItem(), accessor.getPlayer().registryAccess()); - int fortune = ItemUtils.getEnchantmentLevel(Enchantments.FORTUNE, accessor.getPlayer().getMainHandItem(), accessor.getPlayer().registryAccess()); - if (lastVolume.getVolume() > 0) { - LogsAndSticks las = species.getLogsAndSticks(lastVolume, silkTouch > 0, fortune); - List logStacks = las.logs; - if (!logStacks.isEmpty()) { - for (ItemStack logStack : logStacks) { - elements.add(getElement(logStack)); - } - } - if (las.sticks > 0) { - ItemStack stickStack = species.getFamily().getStickStack(las.sticks); - if (!stickStack.isEmpty()) { - elements.add(getElement(stickStack)); - } - } - } - - tooltip.add(elements.removeFirst()); - elements.forEach(tooltip::append); - tooltip.add(ElementHelper.INSTANCE.spacer(0, 2)); + // TODO 26.2 port: Jade removed the IElement/ElementHelper API; the item icon row + // (seed/fruit/pod/log/stick stacks) needs to be reimplemented against the new Jade API. +// ItemStack seedStack = species.getSeedStack(1); +// +// List elements = new LinkedList<>(); +// elements.add(getElement(seedStack)); //adds seed; +// +// if (species.hasFruits()){ +// for (Fruit fruit : species.getFruits()){ +// ItemStack fruitStack = fruit.getItemStack(); +// if (fruitStack.getItem() != seedStack.getItem()) +// elements.add(getElement(fruitStack)); +// } +// } +// if (species.hasPods()){ +// for (Pod pod : species.getPods()){ +// ItemStack podStack = pod.getItemStack(); +// if (podStack.getItem() != seedStack.getItem()) +// elements.add(getElement(podStack)); +// } +// } +// +// int silkTouch = ItemUtils.getEnchantmentLevel(Enchantments.SILK_TOUCH, accessor.getPlayer().getMainHandItem(), accessor.getPlayer().registryAccess()); +// int fortune = ItemUtils.getEnchantmentLevel(Enchantments.FORTUNE, accessor.getPlayer().getMainHandItem(), accessor.getPlayer().registryAccess()); +// if (lastVolume.getVolume() > 0) { +// LogsAndSticks las = species.getLogsAndSticks(lastVolume, silkTouch > 0, fortune); +// List logStacks = las.logs; +// if (!logStacks.isEmpty()) { +// for (ItemStack logStack : logStacks) { +// elements.add(getElement(logStack)); +// } +// } +// if (las.sticks > 0) { +// ItemStack stickStack = species.getFamily().getStickStack(las.sticks); +// if (!stickStack.isEmpty()) { +// elements.add(getElement(stickStack)); +// } +// } +// } +// +// tooltip.add(elements.removeFirst()); +// elements.forEach(tooltip::append); +// tooltip.add(ElementHelper.INSTANCE.spacer(0, 2)); } } @@ -167,13 +167,13 @@ private Species getWailaSpecies(Level level, BlockPos pos) { return TreeHelper.getBestGuessSpecies(level, pos); } - private static IElement getElement(ItemStack stack) { - if (!stack.isEmpty()) { - return ElementHelper.INSTANCE.item(stack); - } else { - return ElementHelper.INSTANCE.spacer(0, 0); - } - } +// private static IElement getElement(ItemStack stack) { +// if (!stack.isEmpty()) { +// return ElementHelper.INSTANCE.item(stack); +// } else { +// return ElementHelper.INSTANCE.spacer(0, 0); +// } +// } @Override public Identifier getUid() { 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..e1f4feb05 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 @@ -15,7 +15,7 @@ import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; 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.lifecycle.v1.ServerLevelEvents; import net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents; import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener; import net.fabricmc.fabric.api.resource.ResourceManagerHelper; @@ -32,16 +32,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 +57,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 +79,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/event/handler/VanillaSaplingEventHandler.java b/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/VanillaSaplingEventHandler.java index 52614ead8..05fd7eede 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 @@ -47,13 +47,13 @@ private static InteractionResult onUseBlock(Player player, Level level, Interact return InteractionResult.PASS; } - Species targetSpecies = DynamicSaplingBlock.SAPLING_REPLACERS.get(block); - Species species = targetSpecies.selfOrLocationOverride(level, placePos); - - if (species.overrideSaplingReplacementWhenCrouching() && player.isCrouching()){ + if (!DynamicSaplingBlock.shouldReplaceSaplingWhenPlaced(block.defaultBlockState())) { return InteractionResult.PASS; } + Species targetSpecies = DynamicSaplingBlock.SAPLING_REPLACERS.get(block); + Species species = targetSpecies.selfOrLocationOverride(level, placePos); + if (!species.plantSapling(level, placePos, targetSpecies != species)) { if (!player.isCreative()) stack.grow(1); return InteractionResult.SUCCESS; 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..d2cd47652 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,8 @@ 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); + if (!(roots.getFamily() instanceof com.dtteam.dynamictrees.tree.family.AerialRootsFamily rootsFamily)) return; + Block block = BasicRootsBlock.Layer.COVERED.getPrimitive(rootsFamily).orElse(null); if (block == null) return; cir.setReturnValue(mayPlaceOn(block.defaultBlockState(), level, blockpos)); } diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/BakedModelBlockPottedSapling.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/BakedModelBlockPottedSapling.java index 38e47de31..dd0448a39 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/model/BakedModelBlockPottedSapling.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/BakedModelBlockPottedSapling.java @@ -1,30 +1,15 @@ 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 net.fabricmc.fabric.api.client.model.loading.v1.wrapper.WrapperBlockStateModel; +import net.minecraft.client.renderer.block.dispatch.BlockStateModel; -import java.util.ArrayList; -import java.util.List; +/** + * Wraps the base flower pot model for the potted sapling block. Currently a plain passthrough + * (matching the pre-port Fabric behavior, where only the pot geometry came from the model). + */ +public class BakedModelBlockPottedSapling extends WrapperBlockStateModel { -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)); + public BakedModelBlockPottedSapling(BlockStateModel basePotModel) { + super(basePotModel); } } 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..01c0f56c1 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/model/DTModelLoadingPlugin.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/DTModelLoadingPlugin.java @@ -4,243 +4,177 @@ import com.dtteam.dynamictrees.block.branch.BasicRootsBlock; import com.dtteam.dynamictrees.block.branch.BranchBlock; import com.dtteam.dynamictrees.block.branch.SurfaceRootBlock; +import com.dtteam.dynamictrees.block.sapling.PottedSaplingBlock; import com.dtteam.dynamictrees.model.baked.BasicBranchBlockBakedModel; 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.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.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; +/** + * Swaps the baked block state models of dynamic branch, root and potted sapling blocks + * for DT's procedurally generated models, using the Fabric Model Loading API. + */ 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; + + private static final Map MODEL_CACHE = new ConcurrentHashMap<>(); @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) { + MODEL_CACHE.clear(); + pluginContext.modifyBlockModelAfterBake().register(ModelModifier.WRAP_PHASE, DTModelLoadingPlugin::modifyModelAfterBake); } - private void initBranchModels(Function spriteGetter) { - if (modelsInitialized) return; - modelsInitialized = true; + private static BlockStateModel modifyModelAfterBake(BlockStateModel model, ModelModifier.AfterBakeBlock.Context context) { + BlockState state = context.state(); + Block block = state.getBlock(); - for (Family family : Family.REGISTRY.getAll()) { - if (!family.isValid()) continue; + if (block instanceof PottedSaplingBlock) { + return new BakedModelBlockPottedSapling(model); + } - family.getPrimitiveLog().ifPresent(primitiveLog -> { - Identifier primitiveLogId = BuiltInRegistries.BLOCK.getKey(primitiveLog); - Identifier barkTexture = Identifier.fromNamespaceAndPath(primitiveLogId.getNamespace(), "block/" + primitiveLogId.getPath()); - Identifier ringsTexture = barkTexture.withSuffix("_top"); + if (block instanceof BasicRootsBlock rootsBlock) { + BlockStateModel rootsModel = getOrCreateRootsModel(rootsBlock, state, context.baker()); + return rootsModel != null ? rootsModel : model; + } - AtomicReference barkRef = new AtomicReference<>(barkTexture); - AtomicReference ringsRef = new AtomicReference<>(ringsTexture); + if (block instanceof SurfaceRootBlock surfaceRootBlock) { + BlockStateModel rootModel = getOrCreateSurfaceRootModel(surfaceRootBlock, context.baker()); + return rootModel != null ? rootModel : model; + } - family.getTexturePath(Family.BRANCH).ifPresent(barkRef::set); - family.getTexturePath(Family.BRANCH_TOP).ifPresent(ringsRef::set); + if (block instanceof BranchBlock branchBlock) { + BlockStateModel branchModel = getOrCreateBranchModel(branchBlock, context.baker()); + return branchModel != null ? branchModel : model; + } - boolean isThick = family.isThick(); + return 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); - }); - }); + /////////////////////////////////////////// + // BRANCHES + /////////////////////////////////////////// - family.getPrimitiveStrippedLog().ifPresent(strippedLog -> { - Identifier strippedLogId = BuiltInRegistries.BLOCK.getKey(strippedLog); - Identifier strippedBarkTexture = Identifier.fromNamespaceAndPath(strippedLogId.getNamespace(), "block/" + strippedLogId.getPath()); - Identifier strippedRingsTexture = strippedBarkTexture.withSuffix("_top"); + @Nullable + private static BlockStateModel getOrCreateBranchModel(BranchBlock branchBlock, ModelBaker baker) { + Family family = branchBlock.getFamily(); + if (family == null || !family.isValid()) return null; - AtomicReference barkRef = new AtomicReference<>(strippedBarkTexture); - AtomicReference ringsRef = new AtomicReference<>(strippedRingsTexture); + Identifier blockId = BuiltInRegistries.BLOCK.getKey(branchBlock); - family.getTexturePath(Family.STRIPPED_BRANCH).ifPresent(barkRef::set); - family.getTexturePath(Family.STRIPPED_BRANCH_TOP).ifPresent(ringsRef::set); + boolean stripped = family.getStrippedBranch().map(b -> b == branchBlock).orElse(false); + Optional primitiveLog = stripped ? family.getPrimitiveStrippedLog() : family.getPrimitiveLog(); + if (primitiveLog.isEmpty()) return null; - boolean isThick = family.isThick(); + Identifier primitiveLogId = BuiltInRegistries.BLOCK.getKey(primitiveLog.get()); + Identifier barkTexture = Identifier.fromNamespaceAndPath(primitiveLogId.getNamespace(), "block/" + primitiveLogId.getPath()); + Identifier ringsTexture = IdentifierUtils.suffix(barkTexture, "_top"); - 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 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); - 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 isThick = family.isThick(); - Identifier blockId = BuiltInRegistries.BLOCK.getKey(surfaceRoot); - BakedModel model = createRootModel(barkRef.get(), spriteGetter); - ROOT_MODEL_CACHE.put(blockId, model); - }); - }); + return MODEL_CACHE.computeIfAbsent(blockId, id -> createBranchModel(barkOverride, ringsOverride, isThick, baker)); + } - if (family instanceof AerialRootsFamily rootsFamily) { - rootsFamily.getRoots().ifPresent(roots -> { - Identifier blockId = BuiltInRegistries.BLOCK.getKey(roots); + private static BlockStateModel createBranchModel(Identifier barkTexture, Identifier ringsTexture, boolean isThick, ModelBaker baker) { + Material.Baked barkMat = bakeMaterial(baker, barkTexture); + Material.Baked ringsMat = bakeMaterial(baker, 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"); + BasicBranchBlockBakedModel regular = BasicBranchBlockBakedModel.bakeBasic(baker, + new BranchModelPart.UnbakedCore(barkMat), + new BranchModelPart.UnbakedSleeve(barkMat), + new BranchModelPart.UnbakedCore(ringsMat), + null); - AtomicReference barkRef = new AtomicReference<>(barkTexture); - AtomicReference ringsRef = new AtomicReference<>(ringsTexture); + 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)); + } - family.getTexturePath(Family.ROOTS_SIDE).ifPresent(barkRef::set); - family.getTexturePath(Family.ROOTS_TOP).ifPresent(ringsRef::set); + return regular; + } - BakedModel model = createRootsBlockModel(barkRef.get(), ringsRef.get(), spriteGetter); - UNDERGROUND_ROOTS_MODEL_CACHE.put(blockId.withSuffix("_exposed"), model); - }); + /////////////////////////////////////////// + // SURFACE ROOTS + /////////////////////////////////////////// - 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"); + @Nullable + private static BlockStateModel getOrCreateSurfaceRootModel(SurfaceRootBlock surfaceRootBlock, ModelBaker baker) { + Family family = surfaceRootBlock.getFamily(); + if (family == null || !family.isValid()) return null; - BakedModel model = createRootsBlockModel(barkTexture, ringsTexture, spriteGetter); - UNDERGROUND_ROOTS_MODEL_CACHE.put(blockId.withSuffix("_filled"), model); - }); + Optional primitiveLog = family.getPrimitiveLog(); + if (primitiveLog.isEmpty()) return null; + 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 MODEL_CACHE.computeIfAbsent(blockId, id -> SurfaceRootBlockBakedModel.bake(baker, barkOverride)); } - 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)); + /////////////////////////////////////////// + // UNDERGROUND (AERIAL) ROOTS + /////////////////////////////////////////// - if (isThick) { - Identifier thickRingsTexture = ringsTexture.withSuffix("_thick"); - TextureAtlasSprite thickRingsSprite = spriteGetter.apply(new Material(InventoryMenu.BLOCK_ATLAS, thickRingsTexture)); - return new ThickBranchBlockBakedModel(barkSprite, ringsSprite, thickRingsSprite); - } + @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; - return new BasicBranchBlockBakedModel(barkSprite, ringsSprite); - } + BasicRootsBlock.Layer layer = state.getValue(BasicRootsBlock.LAYER); + Identifier blockId = BuiltInRegistries.BLOCK.getKey(rootsBlock); - private BakedModel createRootModel(Identifier barkTexture, Function spriteGetter) { - TextureAtlasSprite barkSprite = spriteGetter.apply(new Material(InventoryMenu.BLOCK_ATLAS, barkTexture)); - return new SurfaceRootBlockBakedModel(barkSprite); - } - - 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); - } - - 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 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 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/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..3c332b571 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,178 @@ 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.ModelConnections; +import com.dtteam.dynamictrees.model.ModelHelper; +import com.dtteam.dynamictrees.model.parts.BranchModelPart; +import com.dtteam.dynamictrees.tree.TreeHelper; +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.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.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +/** + * Dynamic branch block model for Fabric. Mirrors the NeoForge {@code BranchBlockStateModel}, + * implementing the Fabric Renderer API's {@link FabricBlockStateModel} to emit level-aware + * geometry (branch connections) at chunk-build time. + */ +public class BasicBranchBlockBakedModel implements BlockStateModel, FabricBlockStateModel, 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); - } - - public List bakeSleeve(int radius, Direction dir, TextureAtlasSprite bark) { - BlockElement part = generateSleevePart(radius, dir); - List quads = new ArrayList<>(); - FaceBakery faceBakery = new FaceBakery(); + 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 - 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)); - } + /////////////////////////////////////////// + // FABRIC DYNAMIC GEOMETRY + /////////////////////////////////////////// - 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; + @Override + public void emitQuads(QuadEmitter emitter, BlockAndTintGetter level, BlockPos pos, BlockState state, + RandomSource random, Predicate<@Nullable Direction> cullTest) { + List parts = new ArrayList<>(); + collectParts(state, parts, ModelHelper.getModelConnections(level, pos, state)); + for (BlockStateModelPart part : parts) { + ((FabricBlockStateModelPart) part).emitQuads(emitter, cullTest); } } @Override - public boolean isVanillaAdapter() { - return false; + public Object createGeometryKey(BlockAndTintGetter level, BlockPos pos, BlockState state, RandomSource random) { + return 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; + /////////////////////////////////////////// + // PART COLLECTION + /////////////////////////////////////////// - bakedQuads.forEach((dir, quads) -> - emitQuads(dir, emitter, material, quads)); + @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); + } + } + //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 (face == connDir && !sleeveRings.isEmpty()) { + addPart(parts, sleeveRings.getPart(connDir, connRadius)); + } + } } - 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; + private void gatherCoreParts(List parts, Direction face, int coreRadius, int[] connections, Direction coreRingDir, Direction.Axis coreDir) { + if (coreRadius == connections[face.get3DDataValue()]) return; - if (state.getBlock() instanceof BranchBlock branchBlock) { - connections = branchBlock.getConnectionData(getter, pos, state).getAllRadii(); - twigRadius = branchBlock.getFamily().getPrimaryThickness(); + if (coreRingDir != null && coreRingDir == face) { + addPart(parts, rings.getPart(face, coreRadius)); + } else { + addPart(parts, cores.getPart(coreDir, face, coreRadius)); } + } - return collectQuads(coreRadius, connections, twigRadius, null); + protected static void addPart(List parts, @Nullable BlockStateModelPart part) { + if (part == null) return; + parts.add(part); } - protected int maxBranchRadius() { - return ThickBranchBlock.MAX_RADIUS; + private static boolean forceRings(int numConnections, Direction forceRingDir) { + return numConnections == 0 && forceRingDir != null; } - public EnumMap> collectQuads(int coreRadius, int[] connections, int twigRadius, Direction forceRingDir) { + public static int countConnections(int[] connections) { int numConnections = 0; for (int i : connections) { numConnections += (i != 0) ? 1 : 0; } - - 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]); - } - } - - 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]); - } - } - } - } - return bakedQuads; - } - - public List getRingQuads(int radius){ - return ringsQuads[radius - 1]; - } - - 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(); - } - - 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(); - } - } - } - - @Override - public void emitItemQuads(ItemStack stack, Supplier randomSupplier, RenderContext context) { + 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 +185,27 @@ 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; - } + /////////////////////////////////////////// + // VANILLA MODEL METHODS + /////////////////////////////////////////// @Override - public TextureAtlasSprite getParticleIcon() { - return barkTexture; + public void collectParts(RandomSource random, List parts) { + // Geometry is entirely dynamic; without level context there is nothing meaningful to collect. } @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..5b0595e24 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,168 @@ 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.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.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.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.ArrayList; +import java.util.List; +import java.util.function.Predicate; -@SuppressWarnings("unchecked") -public class SurfaceRootBlockBakedModel implements BakedModel, FabricBakedModel { +/** + * Dynamic surface root model for Fabric. Mirrors the NeoForge {@code SurfaceRootBlockStateModel}. + */ +public class SurfaceRootBlockBakedModel implements BlockStateModel, FabricBlockStateModel, 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)); } + cores[0][r] = unbakedCores.bake(baker, radius, Direction.Axis.Z); //NORTH<->SOUTH + cores[1][r] = unbakedCores.bake(baker, radius, Direction.Axis.X); //WEST<->EAST } - 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; - } - } - - 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}; - }; - } - - 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; + return new SurfaceRootBlockBakedModel(cores, sleeves, verts, barkMat); } - 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 emitQuads(QuadEmitter emitter, BlockAndTintGetter level, BlockPos pos, BlockState state, + RandomSource random, Predicate<@Nullable Direction> cullTest) { + List parts = new ArrayList<>(); + collectParts(state, parts, ModelHelper.getRootConnections(level, pos, state)); + for (BlockStateModelPart part : parts) { + ((FabricBlockStateModelPart) part).emitQuads(emitter, cullTest); + } } @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 +170,27 @@ 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; - } + /////////////////////////////////////////// + // VANILLA MODEL METHODS + /////////////////////////////////////////// @Override - public TextureAtlasSprite getParticleIcon() { - return barkTexture; + public void collectParts(RandomSource random, List parts) { + // Geometry is entirely dynamic; without level context there is nothing meaningful to collect. } @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/platform/FabricClientHelper.java b/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricClientHelper.java index 49e245526..ef43ae55f 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricClientHelper.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricClientHelper.java @@ -7,6 +7,7 @@ import com.dtteam.dynamictrees.platform.services.IClientHelper; import com.mojang.blaze3d.platform.NativeImage; import net.minecraft.client.renderer.texture.SpriteContents; +import net.minecraft.util.ARGB; import net.minecraft.client.renderer.texture.TextureAtlasSprite; public class FabricClientHelper implements IClientHelper { @@ -17,7 +18,8 @@ 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); + // getPixel returns ARGB; convert to the ABGR layout the old getPixelRGBA returned. + return ARGB.toABGR(image.getPixel(x, y)); } return 0; } catch (Exception e) { 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..2c1935e4b 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,20 @@ 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) { + switch (modId) { + case DynamicTrees.SERENE_SEASONS -> + SereneSeasonsSeasonProvider.registerSereneSeasonsProvider(); + default -> { + // No other season mods supported on Fabric yet. + } + } } -} \ 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..ac4577c5c 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricInteractionHelper.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricInteractionHelper.java @@ -25,11 +25,6 @@ public boolean canToolAxeStrip(ItemStack stack) { return stack.is(ItemTags.AXES); } - @Override - public boolean canToolAxeDig(ItemStack stack) { - return stack.is(ItemTags.AXES); - } - @Override public int setSeedItemEntityLifespan(ItemEntity entityItem, Seed seed) { return 0; @@ -38,14 +33,15 @@ public int setSeedItemEntityLifespan(ItemEntity entityItem, Seed seed) { @Override public boolean blockDestroyByPlayer(BlockState state, Level level, BlockPos pos, Player player, boolean willHarvest, FluidState fluidState) { Block block = state.getBlock(); + ItemStack toolStack = player.getMainHandItem(); if (block instanceof BranchBlock branchBlock) { - return branchBlock.onDestroyedByPlayer(state, level, pos, player, willHarvest, fluidState); + return branchBlock.onDestroyedByPlayer(state, level, pos, player, toolStack, willHarvest, fluidState); } else if (block instanceof TrunkShellBlock trunkShellBlock) { - return trunkShellBlock.onDestroyedByPlayer(state, level, pos, player, willHarvest, fluidState); + return trunkShellBlock.onDestroyedByPlayer(state, level, pos, player, toolStack, 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, toolStack, willHarvest, fluidState); } return true; } 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/registry/FabricRegistryLoader.java b/fabric/src/main/java/com/dtteam/dynamictrees/registry/FabricRegistryLoader.java index c659f55d3..1ecd6e223 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/registry/FabricRegistryLoader.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/registry/FabricRegistryLoader.java @@ -4,14 +4,17 @@ 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.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.core.registries.Registries; import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.syncher.EntityDataSerializer; +import net.minecraft.network.syncher.EntityDataSerializers; 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; @@ -30,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; @@ -77,13 +77,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)); + Identifier id = DynamicTrees.location(name); + EntityType entityType = Registry.register(BuiltInRegistries.ENTITY_TYPE, id, builder.build(ResourceKey.create(Registries.ENTITY_TYPE, id))); 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; } @@ -108,20 +109,27 @@ 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> registerEntityDataSerializer(String name, Supplier> operator) { + EntityDataSerializer serializer = operator.get(); + EntityDataSerializers.registerSerializer(serializer); + return ()-> serializer; + } + + @Override + 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/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; } } 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..dbb8613b9 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 @@ -42,6 +42,11 @@ private Set> getSet() { return tempSet; } + @Override + public boolean isBound() { + return true; + } + @Override public boolean containsKey(ResourceKey biomeKey) { Set> currentSet = this.getSet(); 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..6713bf116 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 @@ -23,6 +23,11 @@ public DelayedHolderSet(Supplier> holderSetSupplier) { this.holderSetSupplier = holderSetSupplier; } + @Override + public boolean isBound() { + return this.holderSetSupplier.get().isBound(); + } + @Override public Stream> stream() { return this.holderSetSupplier.get().stream(); 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/StreamBackedHolderSet.java b/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/StreamBackedHolderSet.java index a4b7ad1e0..6a1938eb2 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,7 +1,7 @@ package com.dtteam.dynamictrees.worldgen.holderset; import com.mojang.datafixers.util.Either; -import net.minecraft.Util; +import net.minecraft.util.Util; import net.minecraft.core.Holder; import net.minecraft.core.HolderSet; import net.minecraft.tags.TagKey; @@ -11,6 +11,12 @@ import java.util.stream.Collectors; public abstract class StreamBackedHolderSet implements HolderSet { + + @Override + public boolean isBound() { + return true; + } + public List> contents() { return this.stream().collect(Collectors.toList()); } From b6c6c10923ade4a0db58446bcd7e6328b991c783 Mon Sep 17 00:00:00 2001 From: Oleksandr Hanzha Date: Fri, 24 Jul 2026 21:40:09 +0300 Subject: [PATCH 3/9] Fix Fabric runtime: eager block/item registration, FabricEntityDataRegistry, ship generated data, defer brewing recipe cache Server smoke test passes with zero errors (23 species load). Co-Authored-By: Claude Fable 5 --- fabric/build.gradle | 9 +++++++++ .../dynamictrees/registry/FabricRegistryLoader.java | 12 ++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/fabric/build.gradle b/fabric/build.gradle index 31ba3ec90..1bc3b6352 100644 --- a/fabric/build.gradle +++ b/fabric/build.gradle @@ -31,6 +31,15 @@ dependencies { compileOnly group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.2' // javax.annotation used by common sources } +// Ship the generated data (tags, loot tables) in the fabric jar too. The generated assets +// (blockstates using the custom "dynamictrees:branch" model type) stay neoforge-only — +// the fabric model layer replaces branch models after bake and cannot parse them. +processResources { + from(project(':common').file('src/generated/resources')) { + include 'data/**' + } +} + loom { def aw = project(':common').file("src/main/resources/META-INF/${mod_id}.accesswidener") if (aw.exists()) { 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 1ecd6e223..6bbafc5d7 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/registry/FabricRegistryLoader.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/registry/FabricRegistryLoader.java @@ -11,6 +11,7 @@ import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.Registries; import net.minecraft.network.chat.MutableComponent; +import net.fabricmc.fabric.api.object.builder.v1.entity.FabricEntityDataRegistry; import net.minecraft.network.syncher.EntityDataSerializer; import net.minecraft.network.syncher.EntityDataSerializers; import net.minecraft.resources.Identifier; @@ -45,19 +46,22 @@ public class FabricRegistryLoader extends RegistryLoader { public static void setup (){ DTRegistries.setup(); - DendroPotionRecipeHandler.getAllDendroRecipes(); + // Dendro brewing recipes are built lazily by MixinPotionBrewing on first use; + // building them here is too early on 26.2 (item components are not bound yet). } @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 @@ -111,7 +115,7 @@ public , T extends ArgumentTypeInfo.Template, I ext @Override public Supplier> registerEntityDataSerializer(String name, Supplier> operator) { EntityDataSerializer serializer = operator.get(); - EntityDataSerializers.registerSerializer(serializer); + FabricEntityDataRegistry.register(DynamicTrees.location(name), serializer); return ()-> serializer; } From 4c4a1214cac94c262a67c3537b6e2ce39b95a965 Mon Sep 17 00:00:00 2001 From: Oleksandr Hanzha Date: Fri, 24 Jul 2026 22:17:30 +0300 Subject: [PATCH 4/9] Fix Fabric model loading in production: custom blockstate codecs + assets in jar - Register Fabric CustomUnbakedBlockStateModel codecs for all DT model types (branch, roots, creaking_heart, surface_root, potted_dynamic_sapling, aerial_roots_soil, roots_moss), mirroring NeoForge's RegisterBlockStateModels. New fabric model/blockstate/* classes port the NeoForge unbaked models; potted sapling reads the species from the block entity instead of model data. - Ship common/src/generated/resources in the fabric jar (blockstates, models, loot, ...) like the neoforge jar already did; vanilla-format blockstates for leaves/saplings/rooty soils now resolve. - Dual-key the generated custom blockstates: Fabric dispatches custom block state models on "fabric:type" while NeoForge uses "type", so the JSONs now carry both. (A datagen rerun will drop "fabric:type" until the builders emit it - rerun the script or update the builders when regenerating.) - Extract FabricDynamicBlockStateModel (mirror of NeoForge's DynamicBlockStateModel) and use it for all dynamic DT models; the renderer path does not depend on Indigo. - Keep the after-bake model swap as a guarded fallback for add-on tree packs that still ship pre-26.2 assets; it now skips models already loaded through DT codecs. - Fix IndexOutOfBoundsException in Family.getStrippedBranch()/getBranchItem() for families without a stripped branch (add-on tree packs) - bounds-check and return Optional.empty(). Co-Authored-By: Claude Fable 5 --- .../blockstates/acacia_branch.json | 3 +- .../blockstates/birch_branch.json | 3 +- .../blockstates/cherry_branch.json | 3 +- .../blockstates/crimson_branch.json | 3 +- .../blockstates/dark_oak_branch.json | 3 +- .../blockstates/dark_oak_root.json | 3 +- .../blockstates/jungle_branch.json | 3 +- .../dynamictrees/blockstates/jungle_root.json | 3 +- .../blockstates/mangrove_branch.json | 3 +- .../blockstates/mangrove_roots.json | 4 +- .../blockstates/mossy_mangrove_roots.json | 5 +- .../dynamictrees/blockstates/oak_branch.json | 3 +- .../blockstates/pale_oak_branch.json | 3 +- .../pale_oak_creaking_heart_branch.json | 6 +- .../blockstates/pale_oak_root.json | 3 +- .../blockstates/potted_sapling.json | 3 +- .../blockstates/resin_pale_oak_branch.json | 4 +- .../rooty_mangrove_aerial_roots.json | 3 +- .../blockstates/spruce_branch.json | 3 +- .../blockstates/stripped_acacia_branch.json | 3 +- .../blockstates/stripped_birch_branch.json | 3 +- .../blockstates/stripped_cherry_branch.json | 3 +- .../blockstates/stripped_crimson_branch.json | 3 +- .../blockstates/stripped_dark_oak_branch.json | 3 +- .../blockstates/stripped_jungle_branch.json | 3 +- .../blockstates/stripped_mangrove_branch.json | 3 +- .../blockstates/stripped_oak_branch.json | 3 +- .../blockstates/stripped_pale_oak_branch.json | 3 +- .../blockstates/stripped_spruce_branch.json | 3 +- .../blockstates/stripped_warped_branch.json | 3 +- .../blockstates/warped_branch.json | 3 +- .../dynamictrees/tree/family/Family.java | 2 + fabric/build.gradle | 6 + .../DynamicTreesFabricClient.java | 1 + .../model/BakedModelBlockPottedSapling.java | 15 -- .../model/DTModelLoadingPlugin.java | 68 +++++++-- .../model/FabricDynamicBlockStateModel.java | 43 ++++++ .../baked/BasicBranchBlockBakedModel.java | 25 +--- .../baked/SurfaceRootBlockBakedModel.java | 21 +-- .../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 ++++++ 46 files changed, 726 insertions(+), 99 deletions(-) delete mode 100644 fabric/src/main/java/com/dtteam/dynamictrees/model/BakedModelBlockPottedSapling.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/generated/resources/assets/dynamictrees/blockstates/acacia_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/acacia_branch.json index 4e352b493..6ed41cacd 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", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} 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..95fd5b705 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", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} 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..bebb223f8 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", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} 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..75f25dbbc 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", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} 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..ccb326b6d 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", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} 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..48a75cb95 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,9 +2,10 @@ "variants": { "": { "type": "dynamictrees:surface_root", + "fabric:type": "dynamictrees:surface_root", "textures": { "bark": "minecraft:block/dark_oak_log" } } } -} \ No newline at end of file +} 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..9c160cb08 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", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} 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..10a0f2451 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/jungle_root.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/jungle_root.json @@ -2,9 +2,10 @@ "variants": { "": { "type": "dynamictrees:surface_root", + "fabric:type": "dynamictrees:surface_root", "textures": { "bark": "minecraft:block/jungle_log" } } } -} \ No newline at end of file +} 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..189cb16f0 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", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} 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..51d249f72 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", @@ -20,4 +22,4 @@ } } } -} \ No newline at end of file +} 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..e36541a23 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" } @@ -46,4 +49,4 @@ } } ] -} \ No newline at end of file +} 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..2595332e1 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", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} 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..eb4a7bb43 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", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} 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..d53334578 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", @@ -59,4 +63,4 @@ } } ] -} \ No newline at end of file +} 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..84624835c 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,9 +2,10 @@ "variants": { "": { "type": "dynamictrees:surface_root", + "fabric:type": "dynamictrees:surface_root", "textures": { "bark": "minecraft:block/pale_oak_log" } } } -} \ No newline at end of file +} 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..fb9d8d810 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/potted_sapling.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/potted_sapling.json @@ -2,7 +2,8 @@ "variants": { "": { "type": "dynamictrees:potted_dynamic_sapling", + "fabric:type": "dynamictrees:potted_dynamic_sapling", "model": "minecraft:block/flower_pot" } } -} \ No newline at end of file +} 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..06d4bbd8a 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", @@ -21,4 +23,4 @@ } } ] -} \ No newline at end of file +} 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..308102979 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", @@ -11,4 +12,4 @@ } } } -} \ No newline at end of file +} 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..f3a51ca47 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", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} 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..54b2f1412 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", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} 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..ea1d9b71d 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", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} 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..5459a017f 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", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} 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..de8ee8678 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", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} 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..837e35f50 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", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} 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..af9e713c6 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", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} 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..18e818361 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", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} 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..285515531 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", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} 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..101658146 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", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} 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..e24d078dc 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", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} 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..be59b71bb 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", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} 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..660300534 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", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} diff --git a/common/src/main/java/com/dtteam/dynamictrees/tree/family/Family.java b/common/src/main/java/com/dtteam/dynamictrees/tree/family/Family.java index 055856c79..def313158 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/tree/family/Family.java +++ b/common/src/main/java/com/dtteam/dynamictrees/tree/family/Family.java @@ -313,9 +313,11 @@ public boolean isWood() { } protected Optional getBranchBlock(int index) { + if (index < 0 || index >= branches.size()) return Optional.empty(); // Some families (e.g. from add-on tree packs) have no stripped branch entry. return Optionals.ofBlock(branches.get(index).getBlock()); } protected Optional getBranchItem(int index) { + if (index < 0 || index >= branches.size()) return Optional.empty(); return branches.get(index).getItem(); } diff --git a/fabric/build.gradle b/fabric/build.gradle index 1bc3b6352..ecae446b8 100644 --- a/fabric/build.gradle +++ b/fabric/build.gradle @@ -31,6 +31,12 @@ dependencies { compileOnly group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.2' // javax.annotation used by common sources } +// Include the datagenned assets/data (blockstates, item/block models, loot, ...) like the neoforge jar does. +sourceSets.main.resources { srcDir project(':common').file('src/generated/resources') } +// The generated directory can be seen twice by the copy spec (identical files); first one wins. +tasks.named('processResources', ProcessResources) { duplicatesStrategy = DuplicatesStrategy.EXCLUDE } +tasks.named('sourcesJar', Jar) { duplicatesStrategy = DuplicatesStrategy.EXCLUDE } + // Ship the generated data (tags, loot tables) in the fabric jar too. The generated assets // (blockstates using the custom "dynamictrees:branch" model type) stay neoforge-only — // the fabric model layer replaces branch models after bake and cannot parse them. diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabricClient.java b/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabricClient.java index 9af0ba758..1d7508446 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()); } 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 dd0448a39..000000000 --- a/fabric/src/main/java/com/dtteam/dynamictrees/model/BakedModelBlockPottedSapling.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.dtteam.dynamictrees.model; - -import net.fabricmc.fabric.api.client.model.loading.v1.wrapper.WrapperBlockStateModel; -import net.minecraft.client.renderer.block.dispatch.BlockStateModel; - -/** - * Wraps the base flower pot model for the potted sapling block. Currently a plain passthrough - * (matching the pre-port Fabric behavior, where only the pot geometry came from the model). - */ -public class BakedModelBlockPottedSapling extends WrapperBlockStateModel { - - public BakedModelBlockPottedSapling(BlockStateModel basePotModel) { - super(basePotModel); - } -} 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 01c0f56c1..0cd0499d9 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/model/DTModelLoadingPlugin.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/DTModelLoadingPlugin.java @@ -4,15 +4,22 @@ import com.dtteam.dynamictrees.block.branch.BasicRootsBlock; import com.dtteam.dynamictrees.block.branch.BranchBlock; import com.dtteam.dynamictrees.block.branch.SurfaceRootBlock; -import com.dtteam.dynamictrees.block.sapling.PottedSaplingBlock; import com.dtteam.dynamictrees.model.baked.BasicBranchBlockBakedModel; 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.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.block.dispatch.BlockStateModel; @@ -29,29 +36,60 @@ import java.util.concurrent.ConcurrentHashMap; /** - * Swaps the baked block state models of dynamic branch, root and potted sapling blocks - * for DT's procedurally generated models, using the Fabric Model Loading API. + * 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 MODEL_CACHE = new ConcurrentHashMap<>(); + 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 initialize(Context pluginContext) { - MODEL_CACHE.clear(); + FALLBACK_MODEL_CACHE.clear(); pluginContext.modifyBlockModelAfterBake().register(ModelModifier.WRAP_PHASE, DTModelLoadingPlugin::modifyModelAfterBake); } private static BlockStateModel modifyModelAfterBake(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; + } + BlockState state = context.state(); Block block = state.getBlock(); - if (block instanceof PottedSaplingBlock) { - return new BakedModelBlockPottedSapling(model); - } - if (block instanceof BasicRootsBlock rootsBlock) { BlockStateModel rootsModel = getOrCreateRootsModel(rootsBlock, state, context.baker()); return rootsModel != null ? rootsModel : model; @@ -94,7 +132,7 @@ private static BlockStateModel getOrCreateBranchModel(BranchBlock branchBlock, M boolean isThick = family.isThick(); - return MODEL_CACHE.computeIfAbsent(blockId, id -> createBranchModel(barkOverride, ringsOverride, isThick, baker)); + return FALLBACK_MODEL_CACHE.computeIfAbsent(blockId, id -> createBranchModel(barkOverride, ringsOverride, isThick, baker)); } private static BlockStateModel createBranchModel(Identifier barkTexture, Identifier ringsTexture, boolean isThick, ModelBaker baker) { @@ -135,7 +173,7 @@ private static BlockStateModel getOrCreateSurfaceRootModel(SurfaceRootBlock surf Identifier barkTexture = Identifier.fromNamespaceAndPath(primitiveLogId.getNamespace(), "block/" + primitiveLogId.getPath()); Identifier barkOverride = family.getTexturePath(Family.BRANCH).orElse(barkTexture); - return MODEL_CACHE.computeIfAbsent(blockId, id -> SurfaceRootBlockBakedModel.bake(baker, barkOverride)); + return FALLBACK_MODEL_CACHE.computeIfAbsent(blockId, id -> SurfaceRootBlockBakedModel.bake(baker, barkOverride)); } /////////////////////////////////////////// @@ -159,7 +197,7 @@ private static BlockStateModel getOrCreateRootsModel(BasicRootsBlock rootsBlock, Identifier barkOverride = rootsFamily.getTexturePath(Family.ROOTS_SIDE).orElse(barkTexture); Identifier ringsOverride = rootsFamily.getTexturePath(Family.ROOTS_TOP).orElse(ringsTexture); - return MODEL_CACHE.computeIfAbsent(IdentifierUtils.suffix(blockId, "_exposed"), id -> + return FALLBACK_MODEL_CACHE.computeIfAbsent(IdentifierUtils.suffix(blockId, "_exposed"), id -> BasicRootsBlockBakedModel.bakeRoots(baker, bakeMaterial(baker, barkOverride), bakeMaterial(baker, ringsOverride), false)); }).orElse(null); case FILLED -> rootsFamily.getPrimitiveFilledRoots().map(primitiveFilledRoots -> { @@ -167,7 +205,7 @@ private static BlockStateModel getOrCreateRootsModel(BasicRootsBlock rootsBlock, Identifier barkTexture = Identifier.fromNamespaceAndPath(primitiveFilledRootsId.getNamespace(), "block/" + primitiveFilledRootsId.getPath() + "_side"); Identifier ringsTexture = Identifier.fromNamespaceAndPath(primitiveFilledRootsId.getNamespace(), "block/" + primitiveFilledRootsId.getPath() + "_top"); - return MODEL_CACHE.computeIfAbsent(IdentifierUtils.suffix(blockId, "_filled"), id -> + return FALLBACK_MODEL_CACHE.computeIfAbsent(IdentifierUtils.suffix(blockId, "_filled"), id -> BasicRootsBlockBakedModel.bakeRoots(baker, bakeMaterial(baker, barkTexture), bakeMaterial(baker, ringsTexture), true)); }).orElse(null); case COVERED -> null; // Covered roots keep their regular (soil-like) model. 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/baked/BasicBranchBlockBakedModel.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/BasicBranchBlockBakedModel.java index 3c332b571..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 @@ -4,15 +4,12 @@ import com.dtteam.dynamictrees.block.branch.BranchBlock; 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.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.client.resources.model.ModelBaker; import net.minecraft.client.resources.model.geometry.BakedQuad; @@ -23,16 +20,14 @@ 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; /** * Dynamic branch block model for Fabric. Mirrors the NeoForge {@code BranchBlockStateModel}, - * implementing the Fabric Renderer API's {@link FabricBlockStateModel} to emit level-aware - * geometry (branch connections) at chunk-build time. + * emitting level-aware geometry (branch connections) at chunk-build time via the Fabric + * Renderer API. */ -public class BasicBranchBlockBakedModel implements BlockStateModel, FabricBlockStateModel, BlockStateModelWithConnectionData { +public class BasicBranchBlockBakedModel implements FabricDynamicBlockStateModel, BlockStateModelWithConnectionData { protected final BranchMultiPartHolder cores; protected final BranchMultiPartHolder sleeves; @@ -82,13 +77,8 @@ public static BasicBranchBlockBakedModel bakeBasic( /////////////////////////////////////////// @Override - public void emitQuads(QuadEmitter emitter, BlockAndTintGetter level, BlockPos pos, BlockState state, - RandomSource random, Predicate<@Nullable Direction> cullTest) { - List parts = new ArrayList<>(); + public void collectParts(BlockAndTintGetter level, BlockPos pos, BlockState state, RandomSource random, List parts) { collectParts(state, parts, ModelHelper.getModelConnections(level, pos, state)); - for (BlockStateModelPart part : parts) { - ((FabricBlockStateModelPart) part).emitQuads(emitter, cullTest); - } } @Override @@ -194,11 +184,6 @@ public static Direction get3DSourceDir(int coreRadius, int[] connections) { // VANILLA MODEL METHODS /////////////////////////////////////////// - @Override - public void collectParts(RandomSource random, List parts) { - // Geometry is entirely dynamic; without level context there is nothing meaningful to collect. - } - @Override public Material.Baked particleMaterial() { return cores.getFirstMaterial(); 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 5b0595e24..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 @@ -4,14 +4,11 @@ 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 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.client.resources.model.ModelBaker; import net.minecraft.client.resources.model.geometry.BakedQuad; @@ -24,14 +21,12 @@ 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; /** * Dynamic surface root model for Fabric. Mirrors the NeoForge {@code SurfaceRootBlockStateModel}. */ -public class SurfaceRootBlockBakedModel implements BlockStateModel, FabricBlockStateModel, BlockStateModelWithConnectionData { +public class SurfaceRootBlockBakedModel implements FabricDynamicBlockStateModel, BlockStateModelWithConnectionData { protected final SurfaceRootModelPart[][] cores; protected final SurfaceRootModelPart[][] sleeves; @@ -82,13 +77,8 @@ public static SurfaceRootBlockBakedModel bake(ModelBaker baker, Identifier barkT /////////////////////////////////////////// @Override - public void emitQuads(QuadEmitter emitter, BlockAndTintGetter level, BlockPos pos, BlockState state, - RandomSource random, Predicate<@Nullable Direction> cullTest) { - List parts = new ArrayList<>(); + public void collectParts(BlockAndTintGetter level, BlockPos pos, BlockState state, RandomSource random, List parts) { collectParts(state, parts, ModelHelper.getRootConnections(level, pos, state)); - for (BlockStateModelPart part : parts) { - ((FabricBlockStateModelPart) part).emitQuads(emitter, cullTest); - } } @Override @@ -179,11 +169,6 @@ public static Direction get2DSourceDir(int coreRadius, int[] connections) { // VANILLA MODEL METHODS /////////////////////////////////////////// - @Override - public void collectParts(RandomSource random, List parts) { - // Geometry is entirely dynamic; without level context there is nothing meaningful to collect. - } - @Override public Material.Baked particleMaterial() { return particleMaterial; 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 6e138ccf8da2a28aa3904323a400e5d5e5ff5301 Mon Sep 17 00:00:00 2001 From: Oleksandr Hanzha Date: Sat, 25 Jul 2026 02:22:49 +0300 Subject: [PATCH 5/9] Fabric: defer commonSetup past the main entrypoint phase so add-on primitive blocks resolve On Fabric, DynamicTrees.commonSetup() (which runs the treepack 'setup' stage: primitive_log / primitive_leaves / primitive_stripped_log block lookups, etc.) ran inside this mod's own ModInitializer. Fabric may initialize this mod before content mods like Biomes O' Plenty, so every add-on treepack lookup failed on the first pass: Error whilst loading type "Family" with name "dtbop:willow": [primitive_log] Could not find block for registry name 'biomesoplenty:willow_log'. Client model bake then ran against unresolved families - add-on branches baked with missing sprites (magenta/black checkerboard) and leaf tint cloning found no primitive leaves (gray foliage). Move commonSetup to the client and (new) dedicated-server entrypoints, which Fabric invokes after the whole main entrypoint phase - mirroring NeoForge's FMLCommonSetupEvent timing. Nothing in commonSetup registers into frozen vanilla registries, so this is safe post-freeze, and it runs before the first resource load / model bake. Co-Authored-By: Claude Fable 5 --- .../dtteam/dynamictrees/DynamicTreesFabric.java | 9 ++++++++- .../dynamictrees/DynamicTreesFabricClient.java | 5 +++++ .../dynamictrees/DynamicTreesFabricServer.java | 17 +++++++++++++++++ fabric/src/main/resources/fabric.mod.json | 6 ++++-- 4 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabricServer.java diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabric.java b/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabric.java index a04ee83d8..dd336181b 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabric.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabric.java @@ -44,7 +44,14 @@ public void onInitialize() { FabricRegistryLoader.setup(); - DynamicTrees.commonSetup(); + // Note: DynamicTrees.commonSetup() is deliberately NOT called here. It applies the + // treepack "setup" stage (primitive log/leaves/stripped log block lookups etc.), which + // must run after ALL mods have registered their blocks. Fabric invokes ModInitializers + // in an order that may put this mod before content mods (e.g. Biomes O' Plenty), which + // previously made every add-on treepack primitive lookup fail on the first pass. + // It is instead called from the client/dedicated-server entrypoints, which Fabric runs + // after the whole main entrypoint phase (mirroring NeoForge's FMLCommonSetupEvent + // timing). See DynamicTreesFabricClient and DynamicTreesFabricServer. FabricBiomeModifications.register(); diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabricClient.java b/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabricClient.java index 1d7508446..70cfd83ba 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabricClient.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabricClient.java @@ -52,6 +52,11 @@ public class DynamicTreesFabricClient implements ClientModInitializer { @Override public void onInitializeClient() { + // Runs after ALL main entrypoints, so every mod's blocks are registered and the + // treepack setup stage (primitive log/leaves lookups) resolves add-on blocks. + // This must happen before the first resource load so models bake with correct data. + DynamicTrees.commonSetup(); + ConfigRegistry.INSTANCE.register(DynamicTrees.MOD_ID, ModConfig.Type.CLIENT, DTConfigs.CLIENT_CONFIG); SpriteSourceRegistry.register(ThickBranchRingsSource.ID, ThickBranchRingsSource.CODEC); registerModelLoaders(); diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabricServer.java b/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabricServer.java new file mode 100644 index 000000000..d280ee3a7 --- /dev/null +++ b/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabricServer.java @@ -0,0 +1,17 @@ +package com.dtteam.dynamictrees; + +import net.fabricmc.api.DedicatedServerModInitializer; + +/** + * Dedicated server entrypoint. Fabric invokes this after the whole main entrypoint phase, + * so every mod's blocks are registered by the time the treepack setup stage (primitive + * log/leaves lookups etc.) runs — mirroring NeoForge's FMLCommonSetupEvent timing. + * The client counterpart is {@link DynamicTreesFabricClient}. + */ +public class DynamicTreesFabricServer implements DedicatedServerModInitializer { + + @Override + public void onInitializeServer() { + DynamicTrees.commonSetup(); + } +} diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json index 27ccc7dd8..b60e965b3 100644 --- a/fabric/src/main/resources/fabric.mod.json +++ b/fabric/src/main/resources/fabric.mod.json @@ -22,7 +22,10 @@ "client": [ "com.dtteam.dynamictrees.DynamicTreesFabricClient" ], - "dynamictrees": [] + "dynamictrees": [], + "server": [ + "com.dtteam.dynamictrees.DynamicTreesFabricServer" + ] }, "mixins": [ "${mod_id}.mixins.json", @@ -41,4 +44,3 @@ "forgeconfigapiport": "*" } } - \ No newline at end of file From e4ac6cf9a784e8feeeab580ec25f3132a43cc2b2 Mon Sep 17 00:00:00 2001 From: Oleksandr Hanzha Date: Sat, 25 Jul 2026 04:50:00 +0300 Subject: [PATCH 6/9] Fabric: drop the species registry debug dump from server start Left over from diagnosing add-on load order; it printed six INFO lines on every server start. Co-Authored-By: Claude Opus 5 --- .../com/dtteam/dynamictrees/DynamicTreesFabric.java | 1 - .../dynamictrees/platform/FabricMiscHelper.java | 13 ------------- 2 files changed, 14 deletions(-) diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabric.java b/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabric.java index dd336181b..4d2dc59dd 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabric.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabric.java @@ -57,7 +57,6 @@ public void onInitialize() { ServerLifecycleEvents.SERVER_STARTING.register(server -> { VanillaSaplingEventHandler.updateEnabled(); - FabricMiscHelper.debugSpeciesRegistry(); }); ServerLifecycleEvents.SERVER_STOPPING.register(server -> { diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricMiscHelper.java b/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricMiscHelper.java index c62d584e1..9172874dd 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricMiscHelper.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricMiscHelper.java @@ -1,8 +1,6 @@ package com.dtteam.dynamictrees.platform; -import com.dtteam.dynamictrees.DynamicTrees; import com.dtteam.dynamictrees.platform.services.IMiscHelper; -import com.dtteam.dynamictrees.tree.species.Species; import com.dtteam.dynamictrees.worldgen.IDTBiomeHolderSet; import com.dtteam.dynamictrees.worldgen.holderset.DTBiomeHolderSet; import net.minecraft.server.MinecraftServer; @@ -10,17 +8,6 @@ public class FabricMiscHelper implements IMiscHelper { - public static void debugSpeciesRegistry() { - DynamicTrees.LOG.info("=== Species Registry Debug ==="); - DynamicTrees.LOG.info("Total species count: {}", Species.REGISTRY.getAll().size()); - Species cherry = Species.REGISTRY.get(DynamicTrees.location("cherry")); - DynamicTrees.LOG.info("Cherry species: {} (valid: {})", cherry, cherry != null && cherry.isValid()); - Species oak = Species.REGISTRY.get(DynamicTrees.location("oak")); - DynamicTrees.LOG.info("Oak species: {} (valid: {})", oak, oak != null && oak.isValid()); - DynamicTrees.LOG.info("=== End Species Registry Debug ==="); - } - - @Override public boolean isLevelRestoringBlockSnapshots(Level level) { return false; From b63e10206cdd1b9fb06d3ab7b02ec5f43735d908 Mon Sep 17 00:00:00 2001 From: Oleksandr Hanzha Date: Fri, 7 Aug 2026 04:31:07 +0300 Subject: [PATCH 7/9] Fix invisible trunk on falling trees when a model-wrapping mod is present DT builds falling-tree geometry by asking a block's baked model for its parts through the DT-specific BlockStateModelWithConnectionData / ...WithRadius interfaces. That lookup went through the model manager, which hands back whatever the final baked model is -- and other mods are free to decorate it. Continuity wraps every model in its own EmissiveBlockStateModel, which implements neither DT interface, so QuadManipulator fell through to BlockStateModel#collectParts(RandomSource, List). For a level-context model that is deliberately a no-op, so the trunk contributed zero quads. Instrumenting a felled tree showed it exactly: 34 branches -> 0 quads, while 120 leaves (ordinary models) -> 720 quads. Trunk invisible, leaves fine, and nothing logged anywhere. In-world rendering was unaffected because that goes through the level-aware emitQuads path, where the wrapper delegates correctly and is exactly what we want. Only DT's own geometry passes needed the real model. So record DT's models as they are baked and consult that first, falling back to the model manager when DT did not supply one. This is mod-agnostic -- any current or future model decorator is handled. Leaves deliberately still go through the model manager so Continuity's treatment of them is preserved in the falling animation. NeoForge is unaffected: the registry is simply never populated there and getOrFallback degrades to the previous behaviour. Co-Authored-By: Claude Opus 5 --- .../model/DynamicModelRegistry.java | 66 +++++++++++++++++++ .../model/entity/FallingTreeEntityModel.java | 7 +- .../model/DTModelLoadingPlugin.java | 16 +++++ 3 files changed, 86 insertions(+), 3 deletions(-) create mode 100644 common/src/main/java/com/dtteam/dynamictrees/model/DynamicModelRegistry.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/model/DTModelLoadingPlugin.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/DTModelLoadingPlugin.java index 0cd0499d9..1513a0ecc 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/model/DTModelLoadingPlugin.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/DTModelLoadingPlugin.java @@ -78,10 +78,26 @@ public static void registerModelTypes() { @Override public void initialize(Context pluginContext) { FALLBACK_MODEL_CACHE.clear(); + DynamicModelRegistry.clear(); pluginContext.modifyBlockModelAfterBake().register(ModelModifier.WRAP_PHASE, DTModelLoadingPlugin::modifyModelAfterBake); } private static BlockStateModel modifyModelAfterBake(BlockStateModel model, ModelModifier.AfterBakeBlock.Context context) { + return record(context.state(), resolveModel(model, context)); + } + + /** + * 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; + } + + 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; From 03c2ac5fa43a69fbb783fc6e614d541d727d7ac2 Mon Sep 17 00:00:00 2001 From: Oleksandr Hanzha Date: Fri, 7 Aug 2026 04:31:22 +0300 Subject: [PATCH 8/9] Restore the Jade integration on Fabric DT registered nothing with Jade at all: WailaCompat -- the @WailaPlugin class that registers every provider -- was commented out in full during the port, so the branch, fruit, pod, rooty and rooty-water handlers were dead code even though five of the six files were otherwise intact. Uncommenting it is not enough. On Fabric, Jade discovers plugins through a "jade" entrypoint in fabric.mod.json, not by scanning for @WailaPlugin (cf. jade's own built-ins, mysticalagriculture, chopt, toms_storage, balm -- all declare it). DT had no such entrypoint. Add it. Jade is the only thing that resolves that entrypoint, so the class is never loaded when Jade is absent and Jade can stay a compileOnly dependency. The failure mode is completely silent: no log line, no error, and Jade writes no plugin_dynamictrees.* keys into config/jade/jade.json -- that empty config is what distinguishes "never registered" from "provider disabled". Also reimplement WailaBranchHandler's drops row, which was commented out because 26.2 removed Jade's IElement/IElementHelper API. Elements are now snownee.jade.api.ui.Element (a vanilla LayoutElement) built via ItemStackElement.of and SpacerElement; add() opens a line, append() extends it. Behaviour matches the original -- seed, fruits/pods deduped against the seed, then the logs and sticks the held tool would actually drop, via Species#getLogsAndSticks with Silk Touch and Fortune applied. Two hardenings over the original: read enchantments from the accessor's level registry and tolerate a null player (the old code dereferenced accessor.getPlayer() unguarded), and bail out before removeFirst() when there is nothing to show. Co-Authored-By: Claude Opus 5 --- .../compat/waila/WailaBranchHandler.java | 119 ++++++++++-------- .../compat/waila/WailaCompat.java | 64 +++++----- fabric/src/main/resources/fabric.mod.json | 3 + 3 files changed, 107 insertions(+), 79 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 a66504ff3..dbf9bc780 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 @@ -19,6 +19,7 @@ import net.minecraft.nbt.CompoundTag; import net.minecraft.network.chat.Component; import net.minecraft.resources.Identifier; +import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.enchantment.Enchantments; import net.minecraft.world.level.Level; @@ -28,6 +29,9 @@ import snownee.jade.api.IBlockComponentProvider; import snownee.jade.api.ITooltip; import snownee.jade.api.config.IPluginConfig; +import snownee.jade.api.ui.Element; +import snownee.jade.impl.ui.ItemStackElement; +import snownee.jade.impl.ui.SpacerElement; import java.util.LinkedList; import java.util.List; @@ -89,52 +93,73 @@ public void appendTooltip(ITooltip tooltip, BlockAccessor accessor, IPluginConfi tooltip.add(Component.literal(ChatFormatting.DARK_GRAY + species.getRegistryName().toString())); } - // TODO 26.2 port: Jade removed the IElement/ElementHelper API; the item icon row - // (seed/fruit/pod/log/stick stacks) needs to be reimplemented against the new Jade API. -// ItemStack seedStack = species.getSeedStack(1); -// -// List elements = new LinkedList<>(); -// elements.add(getElement(seedStack)); //adds seed; -// -// if (species.hasFruits()){ -// for (Fruit fruit : species.getFruits()){ -// ItemStack fruitStack = fruit.getItemStack(); -// if (fruitStack.getItem() != seedStack.getItem()) -// elements.add(getElement(fruitStack)); -// } -// } -// if (species.hasPods()){ -// for (Pod pod : species.getPods()){ -// ItemStack podStack = pod.getItemStack(); -// if (podStack.getItem() != seedStack.getItem()) -// elements.add(getElement(podStack)); -// } -// } -// -// int silkTouch = ItemUtils.getEnchantmentLevel(Enchantments.SILK_TOUCH, accessor.getPlayer().getMainHandItem(), accessor.getPlayer().registryAccess()); -// int fortune = ItemUtils.getEnchantmentLevel(Enchantments.FORTUNE, accessor.getPlayer().getMainHandItem(), accessor.getPlayer().registryAccess()); -// if (lastVolume.getVolume() > 0) { -// LogsAndSticks las = species.getLogsAndSticks(lastVolume, silkTouch > 0, fortune); -// List logStacks = las.logs; -// if (!logStacks.isEmpty()) { -// for (ItemStack logStack : logStacks) { -// elements.add(getElement(logStack)); -// } -// } -// if (las.sticks > 0) { -// ItemStack stickStack = species.getFamily().getStickStack(las.sticks); -// if (!stickStack.isEmpty()) { -// elements.add(getElement(stickStack)); -// } -// } -// } -// -// tooltip.add(elements.removeFirst()); -// elements.forEach(tooltip::append); -// tooltip.add(ElementHelper.INSTANCE.spacer(0, 2)); + appendDropsRow(tooltip, accessor, species); } } + /** + * Builds the single-line icon row showing what this tree yields: its seed, any fruits/pods, and + * — once the tree's volume is known — the logs and sticks the current tool would actually drop. + * + *

Since 26.2 Jade's {@code IElement}/{@code ElementHelper} API is gone; elements are now + * {@link Element}s (which are vanilla {@code LayoutElement}s) added via + * {@link ITooltip#add}/{@link ITooltip#append}. + */ + private void appendDropsRow(ITooltip tooltip, BlockAccessor accessor, Species species) { + final ItemStack seedStack = species.getSeedStack(1); + + final List elements = new LinkedList<>(); + if (!seedStack.isEmpty()) { + elements.add(ItemStackElement.of(seedStack)); + } + + if (species.hasFruits()) { + for (Fruit fruit : species.getFruits()) { + final ItemStack fruitStack = fruit.getItemStack(); + if (!fruitStack.isEmpty() && fruitStack.getItem() != seedStack.getItem()) { + elements.add(ItemStackElement.of(fruitStack)); + } + } + } + if (species.hasPods()) { + for (Pod pod : species.getPods()) { + final ItemStack podStack = pod.getItemStack(); + if (!podStack.isEmpty() && podStack.getItem() != seedStack.getItem()) { + elements.add(ItemStackElement.of(podStack)); + } + } + } + + if (lastVolume.getVolume() > 0) { + final Player player = accessor.getPlayer(); + final ItemStack heldStack = player == null ? ItemStack.EMPTY : player.getMainHandItem(); + final int silkTouch = ItemUtils.getEnchantmentLevel(Enchantments.SILK_TOUCH, heldStack, accessor.getLevel().registryAccess()); + final int fortune = ItemUtils.getEnchantmentLevel(Enchantments.FORTUNE, heldStack, accessor.getLevel().registryAccess()); + + final LogsAndSticks las = species.getLogsAndSticks(lastVolume, silkTouch > 0, fortune); + for (ItemStack logStack : las.logs) { + if (!logStack.isEmpty()) { + elements.add(ItemStackElement.of(logStack)); + } + } + if (las.sticks > 0) { + final ItemStack stickStack = species.getFamily().getStickStack(las.sticks); + if (!stickStack.isEmpty()) { + elements.add(ItemStackElement.of(stickStack)); + } + } + } + + if (elements.isEmpty()) { + return; + } + + // First element opens a new line, the rest are appended onto it. + tooltip.add(elements.removeFirst()); + elements.forEach(tooltip::append); + tooltip.add(new SpacerElement(0, 2)); + } + private NetVolumeNode.Volume getTreeVolume(Level level, BlockPos pos, Species species) { BlockState state = level.getBlockState(pos); Block block = state.getBlock(); @@ -167,14 +192,6 @@ private Species getWailaSpecies(Level level, BlockPos pos) { return TreeHelper.getBestGuessSpecies(level, pos); } -// private static IElement getElement(ItemStack stack) { -// if (!stack.isEmpty()) { -// return ElementHelper.INSTANCE.item(stack); -// } else { -// return ElementHelper.INSTANCE.spacer(0, 0); -// } -// } - @Override public Identifier getUid() { return ID; diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/compat/waila/WailaCompat.java b/fabric/src/main/java/com/dtteam/dynamictrees/compat/waila/WailaCompat.java index 91aa6c702..ffdac756b 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/compat/waila/WailaCompat.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/compat/waila/WailaCompat.java @@ -1,28 +1,36 @@ -//package com.dtteam.dynamictrees.compat.waila; -// -//import com.dtteam.dynamictrees.block.branch.BranchBlock; -//import com.dtteam.dynamictrees.block.branch.TrunkShellBlock; -//import com.dtteam.dynamictrees.block.fruit.FruitBlock; -//import com.dtteam.dynamictrees.block.pod.PodBlock; -//import com.dtteam.dynamictrees.block.soil.SoilBlock; -//import com.dtteam.dynamictrees.block.soil.WaterSoilProperties; -//import snownee.jade.api.IWailaClientRegistration; -//import snownee.jade.api.IWailaPlugin; -//import snownee.jade.api.WailaPlugin; -// -//@WailaPlugin -//public class WailaCompat implements IWailaPlugin { -// -// @Override -// public void registerClient(IWailaClientRegistration registration) { -// WailaBranchHandler branchHandler = new WailaBranchHandler(); -// registration.registerBlockComponent(branchHandler, BranchBlock.class); -// registration.registerBlockComponent(branchHandler, TrunkShellBlock.class); -// -// registration.registerBlockComponent(new WailaFruitHandler(), FruitBlock.class); -// registration.registerBlockComponent(new WailaPodHandler(), PodBlock.class); -// registration.registerBlockComponent(new WailaRootyHandler(), SoilBlock.class); -// registration.registerBlockComponent(new WailaRootyWaterHandler(), WaterSoilProperties.SoilWaterBlock.class); -// } -// -//} +package com.dtteam.dynamictrees.compat.waila; + +import com.dtteam.dynamictrees.block.branch.BranchBlock; +import com.dtteam.dynamictrees.block.branch.TrunkShellBlock; +import com.dtteam.dynamictrees.block.fruit.FruitBlock; +import com.dtteam.dynamictrees.block.pod.PodBlock; +import com.dtteam.dynamictrees.block.soil.SoilBlock; +import com.dtteam.dynamictrees.block.soil.WaterSoilProperties; +import snownee.jade.api.IWailaClientRegistration; +import snownee.jade.api.IWailaPlugin; +import snownee.jade.api.WailaPlugin; + +/** + * Registers DT's Jade tooltip providers. + * + *

On Fabric, Jade discovers plugins through the {@code "jade"} entrypoint in {@code fabric.mod.json} + * rather than by scanning for {@link WailaPlugin}, so this class must be listed there as well as + * annotated. The entrypoint is only ever resolved by Jade itself, so the Jade classes referenced here + * are never loaded when Jade is absent — which is why Jade can stay a {@code compileOnly} dependency. + */ +@WailaPlugin +public class WailaCompat implements IWailaPlugin { + + @Override + public void registerClient(IWailaClientRegistration registration) { + WailaBranchHandler branchHandler = new WailaBranchHandler(); + registration.registerBlockComponent(branchHandler, BranchBlock.class); + registration.registerBlockComponent(branchHandler, TrunkShellBlock.class); + + registration.registerBlockComponent(new WailaFruitHandler(), FruitBlock.class); + registration.registerBlockComponent(new WailaPodHandler(), PodBlock.class); + registration.registerBlockComponent(new WailaRootyHandler(), SoilBlock.class); + registration.registerBlockComponent(new WailaRootyWaterHandler(), WaterSoilProperties.SoilWaterBlock.class); + } + +} diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json index b60e965b3..1ce0ee641 100644 --- a/fabric/src/main/resources/fabric.mod.json +++ b/fabric/src/main/resources/fabric.mod.json @@ -25,6 +25,9 @@ "dynamictrees": [], "server": [ "com.dtteam.dynamictrees.DynamicTreesFabricServer" + ], + "jade": [ + "com.dtteam.dynamictrees.compat.waila.WailaCompat" ] }, "mixins": [ From 9c87e910a4d69a41e6ef01c4ce91aae61169ac85 Mon Sep 17 00:00:00 2001 From: Oleksandr Hanzha Date: Sat, 8 Aug 2026 01:49:45 +0300 Subject: [PATCH 9/9] Fabric: clear the cached server on SERVER_STOPPED, not SERVER_STOPPING Quitting a world could freeze the game with no crash report, leaving only this at the tail of latest.log: [Worker-Main-7/ERROR]: Caught exception in thread ... net.minecraft.ReportedException: Feature placement Caused by: java.lang.IllegalStateException: Queried biome registry too early; server does not exist yet! at BiomeListDeserializer.lambda$static$0 ... at CaveRootedTreePlacement.getPositions Fabric API injects SERVER_STOPPING at the HEAD of MinecraftServer#stopServer, before saveAllChunks drains the chunk generation queue, so nulling currentServer there pulls the biome registry out from under chunk gen tasks that are still in flight. Those run feature placement through CaveRootedTreePlacement -> BiomeDatabase -> DTBiomeHolderSet, which re-resolves the registry off currentServer on every call and has no cache, so the lookup throws, the chunk task dies, and the shutdown's chunk save never completes. NeoForge clears its own reference in ServerLifecycleHooks#handleServerStopped, not handleServerStopping, so this only ever affected the Fabric port. Move the clear to SERVER_STOPPED (TAIL of stopServer, after ServerLevel#close) to match, and guard it with an identity check so a newly constructed server cannot be clobbered. Also make currentServer volatile: it is written on the server thread and read from chunk generation workers, which had no visibility guarantee. Whether the exit survived was timing dependent - in one log a 42s session drained cleanly where a 24s session hung, since quitting sooner after opening leaves more chunk gen in flight. Co-Authored-By: Claude Opus 5 --- .../dtteam/dynamictrees/DynamicTreesFabric.java | 16 ++++++++++++++-- .../dynamictrees/platform/FabricMiscHelper.java | 4 +++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabric.java b/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabric.java index 4d2dc59dd..4b0547b2a 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabric.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabric.java @@ -59,8 +59,20 @@ public void onInitialize() { VanillaSaplingEventHandler.updateEnabled(); }); - ServerLifecycleEvents.SERVER_STOPPING.register(server -> { - FabricMiscHelper.currentServer = null; + // Must be SERVER_STOPPED, not SERVER_STOPPING. SERVER_STOPPING fires at the HEAD of + // MinecraftServer#stopServer, i.e. *before* saveAllChunks drains the chunk generation + // queue. Chunk gen tasks still in flight there run feature placement, which reaches + // CaveRootedTreePlacement -> BiomeDatabase -> DTBiomeHolderSet; that resolves the biome + // registry off currentServer on every call, so a null here throws + // "Queried biome registry too early" out of a worker thread, kills the chunk task, and + // hangs the shutdown (the game freezes on world exit). SERVER_STOPPED fires at the TAIL, + // after ServerLevel#close, which matches NeoForge clearing its reference in + // ServerLifecycleHooks#handleServerStopped. + ServerLifecycleEvents.SERVER_STOPPED.register(server -> { + // Only clear our own reference; never clobber a newer server's registration. + if (FabricMiscHelper.currentServer == server) { + FabricMiscHelper.currentServer = null; + } }); diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricMiscHelper.java b/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricMiscHelper.java index 9172874dd..8ce498395 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricMiscHelper.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricMiscHelper.java @@ -13,7 +13,9 @@ public boolean isLevelRestoringBlockSnapshots(Level level) { return false; } - public static MinecraftServer currentServer; + // Written on the server thread, read from chunk generation worker threads during worldgen. + // volatile so those workers cannot observe a stale reference across the start/stop boundary. + public static volatile MinecraftServer currentServer; @Override public MinecraftServer getCurrentServer() {