diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/acacia_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/acacia_branch.json index 4e352b493..197c0f435 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/acacia_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/acacia_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:acacia", "textures": { "bark": "minecraft:block/acacia_log", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/birch_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/birch_branch.json index e195b2b56..940851ba8 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/birch_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/birch_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:birch", "textures": { "bark": "minecraft:block/birch_log", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/cherry_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/cherry_branch.json index 63a2e8b3b..5e6c550ef 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/cherry_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/cherry_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:cherry", "textures": { "bark": "minecraft:block/cherry_log", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/crimson_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/crimson_branch.json index f6d350f23..8cea8af81 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/crimson_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/crimson_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:crimson", "textures": { "bark": "minecraft:block/crimson_stem", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/dark_oak_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/dark_oak_branch.json index 8c7a4bee5..269baf8a6 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/dark_oak_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/dark_oak_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:dark_oak", "textures": { "bark": "minecraft:block/dark_oak_log", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/dark_oak_root.json b/common/src/generated/resources/assets/dynamictrees/blockstates/dark_oak_root.json index 7dd987b46..faab8d72a 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/dark_oak_root.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/dark_oak_root.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:surface_root", + "fabric:type": "dynamictrees:surface_root", "textures": { "bark": "minecraft:block/dark_oak_log" } diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/jungle_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/jungle_branch.json index 9a5f34a4c..81f776d7a 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/jungle_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/jungle_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:jungle", "textures": { "bark": "minecraft:block/jungle_log", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/jungle_root.json b/common/src/generated/resources/assets/dynamictrees/blockstates/jungle_root.json index c974dbf4a..386bbf09e 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/jungle_root.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/jungle_root.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:surface_root", + "fabric:type": "dynamictrees:surface_root", "textures": { "bark": "minecraft:block/jungle_log" } diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/mangrove_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/mangrove_branch.json index 8fc27ad61..d7322170c 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/mangrove_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/mangrove_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:mangrove", "textures": { "bark": "minecraft:block/mangrove_log", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/mangrove_roots.json b/common/src/generated/resources/assets/dynamictrees/blockstates/mangrove_roots.json index 5a8c51b14..650a54048 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/mangrove_roots.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/mangrove_roots.json @@ -5,6 +5,7 @@ }, "layer=exposed": { "type": "dynamictrees:roots", + "fabric:type": "dynamictrees:roots", "opaque": false, "textures": { "side": "minecraft:block/mangrove_roots_side", @@ -13,6 +14,7 @@ }, "layer=filled": { "type": "dynamictrees:roots", + "fabric:type": "dynamictrees:roots", "opaque": true, "textures": { "side": "minecraft:block/muddy_mangrove_roots_side", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/mossy_mangrove_roots.json b/common/src/generated/resources/assets/dynamictrees/blockstates/mossy_mangrove_roots.json index f127ee36d..5d707ddd8 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/mossy_mangrove_roots.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/mossy_mangrove_roots.json @@ -3,6 +3,7 @@ { "apply": { "type": "dynamictrees:roots", + "fabric:type": "dynamictrees:roots", "opaque": false, "textures": { "side": "minecraft:block/mangrove_roots_side", @@ -16,6 +17,7 @@ { "apply": { "type": "dynamictrees:roots", + "fabric:type": "dynamictrees:roots", "opaque": true, "textures": { "side": "minecraft:block/muddy_mangrove_roots_side", @@ -37,6 +39,7 @@ { "apply": { "type": "dynamictrees:roots_moss", + "fabric:type": "dynamictrees:roots_moss", "textures": { "moss": "minecraft:block/moss_block" } diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/oak_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/oak_branch.json index f3b48d6af..d7e8492c9 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/oak_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/oak_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:oak", "textures": { "bark": "minecraft:block/oak_log", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/pale_oak_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/pale_oak_branch.json index f7ede77a9..6aeac8918 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/pale_oak_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/pale_oak_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:pale_oak", "textures": { "bark": "minecraft:block/pale_oak_log", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/pale_oak_creaking_heart_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/pale_oak_creaking_heart_branch.json index 04a02c417..2792203a3 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/pale_oak_creaking_heart_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/pale_oak_creaking_heart_branch.json @@ -3,6 +3,7 @@ { "apply": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:pale_oak", "textures": { "bark": "minecraft:block/pale_oak_log", @@ -16,6 +17,7 @@ { "apply": { "type": "dynamictrees:creaking_heart", + "fabric:type": "dynamictrees:creaking_heart", "family": "dynamictrees:pale_oak", "textures": { "bark": "minecraft:block/pale_oak_log", @@ -31,6 +33,7 @@ { "apply": { "type": "dynamictrees:creaking_heart", + "fabric:type": "dynamictrees:creaking_heart", "family": "dynamictrees:pale_oak", "textures": { "bark": "minecraft:block/pale_oak_log", @@ -46,6 +49,7 @@ { "apply": { "type": "dynamictrees:creaking_heart", + "fabric:type": "dynamictrees:creaking_heart", "family": "dynamictrees:pale_oak", "textures": { "bark": "minecraft:block/pale_oak_log", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/pale_oak_root.json b/common/src/generated/resources/assets/dynamictrees/blockstates/pale_oak_root.json index 388444ac2..f37aa57c6 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/pale_oak_root.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/pale_oak_root.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:surface_root", + "fabric:type": "dynamictrees:surface_root", "textures": { "bark": "minecraft:block/pale_oak_log" } diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/potted_sapling.json b/common/src/generated/resources/assets/dynamictrees/blockstates/potted_sapling.json index 5030d937f..e8d6c9d8c 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/potted_sapling.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/potted_sapling.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:potted_dynamic_sapling", + "fabric:type": "dynamictrees:potted_dynamic_sapling", "model": "minecraft:block/flower_pot" } } diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/resin_pale_oak_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/resin_pale_oak_branch.json index 82d1566c8..a3de17fec 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/resin_pale_oak_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/resin_pale_oak_branch.json @@ -3,6 +3,7 @@ { "apply": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:pale_oak", "textures": { "bark": "minecraft:block/pale_oak_log", @@ -13,6 +14,7 @@ { "apply": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:pale_oak", "textures": { "bark": "minecraft:block/resin_clump", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/rooty_mangrove_aerial_roots.json b/common/src/generated/resources/assets/dynamictrees/blockstates/rooty_mangrove_aerial_roots.json index 91f364844..83b6a7f25 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/rooty_mangrove_aerial_roots.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/rooty_mangrove_aerial_roots.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:aerial_roots_soil", + "fabric:type": "dynamictrees:aerial_roots_soil", "family": "dynamictrees:mangrove", "textures": { "end": "minecraft:block/mangrove_log_top", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/spruce_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/spruce_branch.json index 64dc4384e..c9c796c56 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/spruce_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/spruce_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:spruce", "textures": { "bark": "minecraft:block/spruce_log", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_acacia_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_acacia_branch.json index a90a7ecac..0f6ba1645 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_acacia_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_acacia_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:acacia", "textures": { "bark": "minecraft:block/stripped_acacia_log", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_birch_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_birch_branch.json index 4c57c2fbb..f7608639d 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_birch_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_birch_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:birch", "textures": { "bark": "minecraft:block/stripped_birch_log", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_cherry_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_cherry_branch.json index ba7d0635f..abc08300e 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_cherry_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_cherry_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:cherry", "textures": { "bark": "minecraft:block/stripped_cherry_log", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_crimson_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_crimson_branch.json index 00c6b7049..a441d95f9 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_crimson_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_crimson_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:crimson", "textures": { "bark": "minecraft:block/stripped_crimson_stem", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_dark_oak_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_dark_oak_branch.json index bedb2a996..024eed2b1 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_dark_oak_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_dark_oak_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:dark_oak", "textures": { "bark": "minecraft:block/stripped_dark_oak_log", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_jungle_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_jungle_branch.json index cf4ba940a..1262144d0 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_jungle_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_jungle_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:jungle", "textures": { "bark": "minecraft:block/stripped_jungle_log", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_mangrove_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_mangrove_branch.json index 361f6ad11..b325ea15e 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_mangrove_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_mangrove_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:mangrove", "textures": { "bark": "minecraft:block/stripped_mangrove_log", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_oak_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_oak_branch.json index edfd98b36..380da7bb8 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_oak_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_oak_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:oak", "textures": { "bark": "minecraft:block/stripped_oak_log", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_pale_oak_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_pale_oak_branch.json index 742fad84a..a9c146ec9 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_pale_oak_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_pale_oak_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:pale_oak", "textures": { "bark": "minecraft:block/stripped_pale_oak_log", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_spruce_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_spruce_branch.json index e0b2f21e0..bbc7a8904 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_spruce_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_spruce_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:spruce", "textures": { "bark": "minecraft:block/stripped_spruce_log", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_warped_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_warped_branch.json index 4b4b50564..87c28a563 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_warped_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_warped_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:warped", "textures": { "bark": "minecraft:block/stripped_warped_stem", diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/warped_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/warped_branch.json index 6deee0565..1a55f1593 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/warped_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/warped_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:warped", "textures": { "bark": "minecraft:block/warped_stem", diff --git a/common/src/main/java/com/dtteam/dynamictrees/command/subcommand/SubCommand.java b/common/src/main/java/com/dtteam/dynamictrees/command/subcommand/SubCommand.java index 0382197c1..b98ff2536 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/command/subcommand/SubCommand.java +++ b/common/src/main/java/com/dtteam/dynamictrees/command/subcommand/SubCommand.java @@ -29,8 +29,8 @@ import net.minecraft.resources.Identifier; import net.minecraft.server.permissions.PermissionCheck; import net.minecraft.world.level.block.state.BlockState; +import org.jetbrains.annotations.Nullable; -import javax.annotation.Nullable; import java.util.Collection; import java.util.List; import java.util.function.Consumer; diff --git a/common/src/main/java/com/dtteam/dynamictrees/data/components/BranchDestructionDataComponent.java b/common/src/main/java/com/dtteam/dynamictrees/data/components/BranchDestructionDataComponent.java index f46bd847d..6f84eb130 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/data/components/BranchDestructionDataComponent.java +++ b/common/src/main/java/com/dtteam/dynamictrees/data/components/BranchDestructionDataComponent.java @@ -11,8 +11,8 @@ import net.minecraft.network.codec.StreamCodec; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.state.BlockState; +import org.jetbrains.annotations.Nullable; -import javax.annotation.Nullable; import java.util.Arrays; import java.util.Optional; diff --git a/common/src/main/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/common/src/main/resources/META-INF/dynamictrees.accesswidener b/common/src/main/resources/META-INF/dynamictrees.accesswidener index 39ea50edd..976f6d07e 100644 --- a/common/src/main/resources/META-INF/dynamictrees.accesswidener +++ b/common/src/main/resources/META-INF/dynamictrees.accesswidener @@ -7,23 +7,26 @@ 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 field net/minecraft/world/item/Item craftingRemainingItem Lnet/minecraft/world/item/ItemStackTemplate; +mutable field net/minecraft/world/item/Item craftingRemainingItem Lnet/minecraft/world/item/ItemStackTemplate; +extendable method net/minecraft/world/item/Item getCraftingRemainder ()Lnet/minecraft/world/item/ItemStackTemplate; accessible field net/minecraft/world/level/chunk/LevelChunk loaded Z -accessible field net/minecraft/world/level/levelgen/feature/stateproviders/WeightedStateProvider weightedList Lnet/minecraft/util/random/SimpleWeightedRandomList; accessible field net/minecraft/commands/synchronization/ArgumentTypeInfos BY_CLASS Ljava/util/Map; accessible class net/minecraft/world/inventory/BrewingStandMenu$PotionSlot -accessible class net/minecraft/data/tags/IntrinsicHolderTagsProvider$IntrinsicTagAppender -accessible method net/minecraft/client/renderer/texture/SpriteContents metadata ()Lnet/minecraft/server/packs/resources/ResourceMetadata; accessible field net/minecraft/client/renderer/texture/SpriteContents originalImage Lcom/mojang/blaze3d/platform/NativeImage; -accessible 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 field net/minecraft/client/renderer/block/dispatch/multipart/MultiPartModel models Ljava/util/List; +accessible field net/minecraft/world/level/block/entity/CreakingHeartBlockEntity ticksExisted J +extendable method net/minecraft/world/level/block/entity/CreakingHeartBlockEntity spreadResin (Lnet/minecraft/server/level/ServerLevel;)Ljava/util/Optional; +accessible method net/minecraft/world/level/block/entity/BlockEntityType (Lnet/minecraft/world/level/block/entity/BlockEntityType$BlockEntitySupplier;Ljava/util/Set;)V accessible method net/minecraft/client/data/models/model/TextureSlot create (Ljava/lang/String;)Lnet/minecraft/client/data/models/model/TextureSlot; +accessible field net/minecraft/world/level/block/entity/BlockEntity type Lnet/minecraft/world/level/block/entity/BlockEntityType; mutable field net/minecraft/world/level/block/entity/BlockEntity type Lnet/minecraft/world/level/block/entity/BlockEntityType; +accessible field net/minecraft/world/level/block/entity/BlockEntity blockState Lnet/minecraft/world/level/block/state/BlockState; mutable field net/minecraft/world/level/block/entity/BlockEntity blockState Lnet/minecraft/world/level/block/state/BlockState; \ No newline at end of file diff --git a/fabric/build.gradle b/fabric/build.gradle index 648ba5cc3..93cd1ec3e 100644 --- a/fabric/build.gradle +++ b/fabric/build.gradle @@ -5,11 +5,28 @@ plugins { //mod_version = "${mod_version}${fabricVersionAppend}" version = mod_version +repositories { + exclusiveContent { + forRepository { + maven { + name = 'Modrinth' + url = 'https://api.modrinth.com/maven' + } + } + filter { includeGroup 'maven.modrinth' } + } +} + dependencies { minecraft "com.mojang:minecraft:${minecraft_version}" implementation "net.fabricmc:fabric-loader:${fabric_loader_version}" implementation "net.fabricmc.fabric-api:fabric-api:${fabric_version}" implementation "fuzs.forgeconfigapiport:forgeconfigapiport-fabric:26.1.5" + + compileOnly "maven.modrinth:jade:26.1.9+fabric" + compileOnly "maven.modrinth:continuity:3.0.1-beta.2+26.1" + compileOnly "maven.modrinth:serene-seasons:26.1.2.0.2" + compileOnly "maven.modrinth:glitchcore:26.1.2.0.2" } loom { @@ -33,6 +50,8 @@ loom { } } +sourceSets.main.resources { srcDir project(':common').file('src/generated/resources') } + // Implement mcgradleconventions loader attribute def loaderAttribute = Attribute.of('io.github.mcgradleconventions.loader', String) ['apiElements', 'runtimeElements', 'sourcesElements', 'javadocElements', 'includeInternal', 'modCompileClasspath'].each { variant -> diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabric.java b/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabric.java index 2c5df41c7..5ce3bea74 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabric.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabric.java @@ -6,7 +6,7 @@ import com.dtteam.dynamictrees.platform.*; import com.dtteam.dynamictrees.registry.*; import com.dtteam.dynamictrees.worldgen.*; -import fuzs.forgeconfigapiport.fabric.impl.core.*; +import fuzs.forgeconfigapiport.fabric.api.v5.ConfigRegistry; import net.fabricmc.api.*; import net.fabricmc.fabric.api.event.lifecycle.v1.*; import net.fabricmc.loader.api.*; @@ -18,8 +18,8 @@ public class DynamicTreesFabric implements ModInitializer { @Override public void onInitialize() { - ConfigRegistryImpl.INSTANCE.register(DynamicTrees.MOD_ID,ModConfig.Type.SERVER, DTConfigs.SERVER_CONFIG); - ConfigRegistryImpl.INSTANCE.register(DynamicTrees.MOD_ID,ModConfig.Type.COMMON, DTConfigs.COMMON_CONFIG); + ConfigRegistry.INSTANCE.register(DynamicTrees.MOD_ID,ModConfig.Type.SERVER, DTConfigs.SERVER_CONFIG); + ConfigRegistry.INSTANCE.register(DynamicTrees.MOD_ID,ModConfig.Type.COMMON, DTConfigs.COMMON_CONFIG); FabricRegistryHandler.setup(DynamicTrees.MOD_ID); diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabricClient.java b/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabricClient.java index d9f1a0554..3f9acb612 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,16 +17,21 @@ import com.dtteam.dynamictrees.tree.*; import com.dtteam.dynamictrees.tree.family.*; import com.dtteam.dynamictrees.tree.species.*; -import fuzs.forgeconfigapiport.fabric.api.neoforge.v4.*; -import fuzs.forgeconfigapiport.fabric.impl.core.*; +import fuzs.forgeconfigapiport.fabric.api.v5.ConfigRegistry; import net.fabricmc.api.*; -import net.fabricmc.fabric.api.blockrenderlayer.v1.*; import net.fabricmc.fabric.api.client.event.lifecycle.v1.*; import net.fabricmc.fabric.api.client.item.v1.*; import net.fabricmc.fabric.api.client.model.loading.v1.*; import net.fabricmc.fabric.api.client.rendering.v1.*; import net.minecraft.client.*; +import net.minecraft.client.color.block.BlockColors; +import net.minecraft.client.color.block.BlockTintSource; +import net.minecraft.client.color.block.BlockTintSources; +import net.minecraft.client.color.item.ItemTintSources; +import net.minecraft.client.renderer.block.dispatch.BlockStateModel; +import net.minecraft.client.renderer.block.dispatch.BlockStateModelPart; import net.minecraft.client.renderer.texture.*; +import net.minecraft.client.resources.model.geometry.BakedQuad; import net.minecraft.core.*; import net.minecraft.resources.*; import net.minecraft.util.*; @@ -47,8 +53,8 @@ public class DynamicTreesFabricClient implements ClientModInitializer { @Override public void onInitializeClient() { - ConfigRegistryImpl.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(); @@ -62,15 +68,15 @@ private void registerClientWorldLoad() { ClientTickEvents.START_CLIENT_TICK.register(client -> { if (!initialized && client.level != null) { discoverWoodColors(); - LeavesProperties.postInitClient(); - BlockColorMultipliers.cleanUp(); registerBlockColors(); + BlockColorMultipliers.cleanUp(); initialized = true; } }); } private void registerModelLoaders() { + DTModelLoadingPlugin.registerModelTypes(); ModelLoadingPlugin.register(new DTModelLoadingPlugin()); } @@ -80,78 +86,60 @@ private void registerEntityRenderers() { } private void registerColorHandlers() { - BlockColorMultipliers.register("birch", (state, level, pos, tintIndex) -> FoliageColor.FOLIAGE_BIRCH); - BlockColorMultipliers.register("spruce", (state, level, pos, tintIndex) -> FoliageColor.FOLIAGE_EVERGREEN); - - ColorProviderRegistry.ITEM.register(DTRegistries.DENDRO_POTION.get()::getColor, DTRegistries.DENDRO_POTION.get()); - ColorProviderRegistry.ITEM.register(DTRegistries.STAFF.get()::getColor, DTRegistries.STAFF.get()); + BlockColorMultipliers.register("birch", BlockTintSources.constant(FoliageColor.FOLIAGE_BIRCH)); + BlockColorMultipliers.register("spruce", BlockTintSources.constant(FoliageColor.FOLIAGE_EVERGREEN)); + + // Item tint sources are now looked up by id from the item model JSON, mirroring + // NeoForge's RegisterColorHandlersEvent.ItemTintSources handling in ClientModEventHandler. + ItemTintSources.ID_MAPPER.put(DynamicTrees.location("dendro_potion"), DendroPotionItemTintSource.MAP_CODEC); + ItemTintSources.ID_MAPPER.put(DynamicTrees.location("staff_handle"), StaffHandleItemTintSource.MAP_CODEC); + ItemTintSources.ID_MAPPER.put(DynamicTrees.location("staff_crystal"), StaffCrystalItemTintSource.MAP_CODEC); } public static void registerBlockColors() { - final int white = 0xFFFFFFFF; - final int magenta = 0x00FF00FF; - final var blockColors = Minecraft.getInstance().getBlockColors(); - - for (SoilProperties soil : SoilProperties.REGISTRY) { - if (soil.getBlock().isEmpty()) continue; - SoilBlock roots = soil.getBlock().get(); - ColorProviderRegistry.BLOCK.register( - (state, level, pos, tintIndex) -> roots.colorMultiplier(blockColors, state, level, pos, tintIndex), - roots - ); - BlockRenderLayerMap.INSTANCE.putBlock(roots, RenderType.cutoutMipped()); - } - - for (Family family : Family.REGISTRY.getAll()) { - if (family instanceof AerialRootsFamily rootsFamily) { - rootsFamily.getRoots().ifPresent(roots -> - BlockRenderLayerMap.INSTANCE.putBlock(roots, RenderType.cutoutMipped()) - ); - } - } - - - ColorProviderRegistry.BLOCK.register( - (state, level, pos, tintIndex) -> isValidPos(level, pos) && (state.getBlock() instanceof PottedSaplingBlock) - ? DTRegistries.POTTED_SAPLING.get().getSpecies(level, pos).saplingColorMultiplier(state, level, pos, tintIndex) : white, - DTRegistries.POTTED_SAPLING.get() - ); - - for (Species species : Species.REGISTRY) { - if (species.getSapling().isPresent()) { - ColorProviderRegistry.BLOCK.register( - (state, level, pos, tintIndex) -> isValidPos(level, pos) - ? species.saplingColorMultiplier(state, level, pos, tintIndex) : white, - species.getSapling().get() - ); - } - species.getSapling().ifPresent(sapling -> BlockRenderLayerMap.INSTANCE.putBlock(sapling, RenderType.cutoutMipped())); - if(species.hasFruits()){ - species.getFruits().forEach(fruit -> - BlockRenderLayerMap.INSTANCE.putBlock(fruit.getBlock(), RenderType.cutoutMipped()) - ); - } - if(species.hasPods()){ - species.getPods().forEach(pod -> - BlockRenderLayerMap.INSTANCE.putBlock(pod.getBlock(), RenderType.cutoutMipped()) - ); - } - } + final BlockColors blockColors = Minecraft.getInstance().getBlockColors(); + + // Register Rooty Soils Tint Sources + SoilProperties.REGISTRY.getAll().stream().map(SoilProperties::getBlock).flatMap(Optional::stream) + .forEach(soilBlock -> { + SoilProperties properties = soilBlock.getSoilProperties(); + List sources = CloneTintSource.cloneAllSources( + blockColors, + () -> properties.getPrimitiveSoilState(soilBlock.defaultBlockState()), + properties.getFoliageTintLayerCount()); + sources.add(new SoilRootsTintSource(soilBlock)); + BlockColorRegistry.register(sources, soilBlock); + }); - for (DynamicLeavesBlock leaves : LeavesProperties.REGISTRY.getAll().stream() - .filter(lp -> lp.getDynamicLeavesBlock().isPresent()) - .map(lp -> lp.getDynamicLeavesBlock().get()) - .collect(Collectors.toSet())) { - ColorProviderRegistry.BLOCK.register( - (state, level, pos, tintIndex) -> isValidPos(level, pos) && TreeHelper.isLeaves(state.getBlock()) - ? ((DynamicLeavesBlock) state.getBlock()).getLeavesProperties().foliageColorMultiplier(state, level, pos) : magenta, - leaves - ); - } - } + // Register Leaves Tint Sources + LeavesProperties.REGISTRY.getAll().stream().map(LeavesProperties::getDynamicLeavesBlock).flatMap(Optional::stream) + .forEach(leaves -> { + LeavesProperties properties = leaves.getLeavesProperties(); + if (properties.hasCustomColor()) { + Integer customColor = properties.getCustomColor(); + if (customColor == null) //we use null as a way to default back to "biome" index source. + BlockColorRegistry.register(List.of(BlockTintSources.foliage()), leaves); + else + BlockColorRegistry.register(List.of(BlockTintSources.constant(customColor)), leaves); + } else { + BlockColorRegistry.register(CloneTintSource.cloneAllSources(blockColors, properties::getPrimitiveLeaves, properties.getFoliageTintLayerCount()), leaves); + } + }); - private static boolean isValidPos(BlockGetter level, BlockPos pos) { - return level != null && pos != null; + // Register Potted Sapling Tint Sources + BlockColorRegistry.register(List.of(new PottedSaplingTintSource(blockColors)), DTRegistries.POTTED_SAPLING.get()); + + // Register Sapling Tint Sources + Species.REGISTRY.getAll().stream().map(Species::getSapling).flatMap(Optional::stream) + .forEach(sapling -> { + Species species = sapling.getSpecies(); + BlockColorRegistry.register(List.of( + //Leaves for the leaves + new SaplingTintSource(blockColors, species), + //Mangrove saplings have roots so we provide a branch tint + new SuppliedConstantTintSource(() -> species.getFamily().woodBarkColor) + ), sapling); + }); } private void registerTooltipCallback() { @@ -183,43 +171,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); @@ -227,4 +202,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..912a86b1b 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/compat/SereneSeasonsSeasonProvider.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/compat/SereneSeasonsSeasonProvider.java @@ -26,7 +26,7 @@ public void updateTick(Level level, long dayTime) { @Override public boolean shouldSnowMelt(Level level, BlockPos pos) { if (ModConfig.seasons.generateSnowAndIce && seasonValue < SeasonHelper.WINTER_START) { - return level.getBiome(pos).value().warmEnoughToRain(pos); + return level.getBiome(pos).value().warmEnoughToRain(pos, level.getSeaLevel()); } return false; } diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/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..b99023334 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/compat/waila/WailaBranchHandler.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/compat/waila/WailaBranchHandler.java @@ -28,8 +28,8 @@ import snownee.jade.api.IBlockComponentProvider; import snownee.jade.api.ITooltip; import snownee.jade.api.config.IPluginConfig; -import snownee.jade.api.ui.IElement; -import snownee.jade.impl.ui.ElementHelper; +import snownee.jade.api.ui.Element; +import snownee.jade.api.ui.JadeUI; import java.util.LinkedList; import java.util.List; @@ -57,7 +57,7 @@ public void appendTooltip(ITooltip tooltip, BlockAccessor accessor, IPluginConfi //Attempt to get species from server via NBT data if (nbtData.contains("species")) { - species = Species.findSpecies(Identifier.parse(nbtData.getString("species"))); + species = Species.findSpecies(Identifier.parse(nbtData.getString("species").orElseThrow())); } //Attempt to get species by checking if we're still looking at the same block @@ -93,7 +93,7 @@ public void appendTooltip(ITooltip tooltip, BlockAccessor accessor, IPluginConfi ItemStack seedStack = species.getSeedStack(1); - List elements = new LinkedList<>(); + List elements = new LinkedList<>(); elements.add(getElement(seedStack)); //adds seed; if (species.hasFruits()){ @@ -131,7 +131,7 @@ public void appendTooltip(ITooltip tooltip, BlockAccessor accessor, IPluginConfi tooltip.add(elements.removeFirst()); elements.forEach(tooltip::append); - tooltip.add(ElementHelper.INSTANCE.spacer(0, 2)); + tooltip.add(JadeUI.spacer(0, 2)); } } @@ -167,11 +167,11 @@ private Species getWailaSpecies(Level level, BlockPos pos) { return TreeHelper.getBestGuessSpecies(level, pos); } - private static IElement getElement(ItemStack stack) { + private static Element getElement(ItemStack stack) { if (!stack.isEmpty()) { - return ElementHelper.INSTANCE.item(stack); + return JadeUI.item(stack); } else { - return ElementHelper.INSTANCE.spacer(0, 0); + return JadeUI.spacer(0, 0); } } diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/CommonEventHandler.java b/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/CommonEventHandler.java index 769c923d6..d5b8d83a5 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/CommonEventHandler.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/CommonEventHandler.java @@ -13,16 +13,15 @@ import com.dtteam.dynamictrees.worldgen.BiomeDatabases; import com.dtteam.dynamictrees.worldgen.feature.DynamicTreeFeature; import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLevelEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; -import net.fabricmc.fabric.api.event.lifecycle.v1.ServerWorldEvents; import net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents; import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener; import net.fabricmc.fabric.api.resource.ResourceManagerHelper; import net.minecraft.resources.Identifier; import net.minecraft.server.packs.PackType; -import net.minecraft.server.packs.resources.ResourceManager; -import net.minecraft.util.profiling.ProfilerFiller; +import net.minecraft.server.packs.resources.PreparableReloadListener; import net.minecraft.world.level.block.Block; import java.util.concurrent.CompletableFuture; @@ -32,16 +31,16 @@ public class CommonEventHandler { public static void RegisterEvents(){ - ServerTickEvents.START_WORLD_TICK.register((level)->{ + ServerTickEvents.START_LEVEL_TICK.register((level)->{ FutureBreak.process(level); - SeasonHelper.updateTick(level, level.getDayTime()); + SeasonHelper.updateTick(level, level.getDefaultClockTime()); }); - ServerWorldEvents.LOAD.register(((minecraftServer, serverLevel) -> { + ServerLevelEvents.LOAD.register(((minecraftServer, serverLevel) -> { BiomeDatabases.populateBlacklistFromConfig(); })); - ServerWorldEvents.UNLOAD.register(((minecraftServer, serverLevel) -> { + ServerLevelEvents.UNLOAD.register(((minecraftServer, serverLevel) -> { DynamicTreeFeature.DISC_PROVIDER.unloadWorld(serverLevel); })); @@ -57,13 +56,13 @@ public static void RegisterEvents(){ PlayerBlockBreakEvents.BEFORE.register((level, player, pos, state, blockEntity) -> { Block block = state.getBlock(); if (block instanceof BranchBlock branchBlock) { - return branchBlock.onDestroyedByPlayer(state, level, pos, player, true, level.getFluidState(pos)); + return branchBlock.onDestroyedByPlayer(state, level, pos, player, player.getMainHandItem(), true, level.getFluidState(pos)); } else if (block instanceof TrunkShellBlock trunkShellBlock) { - return trunkShellBlock.onDestroyedByPlayer(state, level, pos, player, true, level.getFluidState(pos)); + return trunkShellBlock.onDestroyedByPlayer(state, level, pos, player, player.getMainHandItem(), true, level.getFluidState(pos)); } else if (block instanceof SoilBlock soilBlock) { return soilBlock.onDestroyedByPlayer(state, level, pos, player, true, level.getFluidState(pos)); } else if (block instanceof PottedSaplingBlock pottedSaplingBlock) { - return pottedSaplingBlock.onDestroyedByPlayer(state, level, pos, player, true, level.getFluidState(pos)); + return pottedSaplingBlock.onDestroyedByPlayer(state, level, pos, player, player.getMainHandItem(), true, level.getFluidState(pos)); } return true; }); @@ -79,10 +78,9 @@ public FabricReloadListener() { } @Override - public CompletableFuture reload(PreparationBarrier stage, ResourceManager resourceManager, - ProfilerFiller preparationsProfiler, ProfilerFiller reloadProfiler, - Executor backgroundExecutor, Executor gameExecutor) { - return super.reload(stage, resourceManager, preparationsProfiler, reloadProfiler, backgroundExecutor, gameExecutor); + public CompletableFuture reload(SharedState sharedState, Executor backgroundExecutor, + PreparationBarrier preparationBarrier, Executor gameExecutor) { + return super.reload(sharedState, backgroundExecutor, preparationBarrier, gameExecutor); } @Override diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/VanillaSaplingEventHandler.java b/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/VanillaSaplingEventHandler.java index 52614ead8..b682aa7aa 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/VanillaSaplingEventHandler.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/VanillaSaplingEventHandler.java @@ -50,10 +50,6 @@ private static InteractionResult onUseBlock(Player player, Level level, Interact Species targetSpecies = DynamicSaplingBlock.SAPLING_REPLACERS.get(block); Species species = targetSpecies.selfOrLocationOverride(level, placePos); - if (species.overrideSaplingReplacementWhenCrouching() && player.isCrouching()){ - return InteractionResult.PASS; - } - if (!species.plantSapling(level, placePos, targetSpecies != species)) { if (!player.isCreative()) stack.grow(1); return InteractionResult.SUCCESS; diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinVegetationBlock.java b/fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinVegetationBlock.java index 390acb646..b81305029 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinVegetationBlock.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinVegetationBlock.java @@ -24,7 +24,7 @@ private void canSurvive(BlockState state, LevelReader level, BlockPos pos, Callb BlockState belowBlockState = level.getBlockState(blockpos); if (belowBlockState.getBlock() instanceof BasicRootsBlock roots){ if (belowBlockState.getValue(BasicRootsBlock.LAYER) == BasicRootsBlock.Layer.COVERED){ - Block block = BasicRootsBlock.Layer.COVERED.getPrimitive(roots.getFamily()).orElse(null); + Block block = BasicRootsBlock.Layer.COVERED.getPrimitive(roots.getAerialFamily()).orElse(null); if (block == null) return; cir.setReturnValue(mayPlaceOn(block.defaultBlockState(), level, blockpos)); } diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/BakedModelBlockPottedSapling.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/BakedModelBlockPottedSapling.java deleted file mode 100644 index 38e47de31..000000000 --- a/fabric/src/main/java/com/dtteam/dynamictrees/model/BakedModelBlockPottedSapling.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.dtteam.dynamictrees.model; - -import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel; -import net.minecraft.client.renderer.block.model.BakedQuad; -import net.minecraft.client.resources.model.BakedModel; -import net.minecraft.core.Direction; -import net.minecraft.util.RandomSource; -import net.minecraft.world.level.block.state.BlockState; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.List; - -public class BakedModelBlockPottedSapling extends ForwardingBakedModel { - - public BakedModelBlockPottedSapling(BakedModel basePotModel) { - this.wrapped = basePotModel; - } - - @Override - public boolean isVanillaAdapter() { - return true; - } - - @Override - @NotNull - public List getQuads(BlockState state, Direction face, RandomSource random) { - return new ArrayList<>(wrapped.getQuads(state, face, random)); - } -} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/BranchBlockUnbakedModel.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/BranchBlockUnbakedModel.java deleted file mode 100644 index 5217b2522..000000000 --- a/fabric/src/main/java/com/dtteam/dynamictrees/model/BranchBlockUnbakedModel.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.dtteam.dynamictrees.model; - -import com.dtteam.dynamictrees.model.baked.BasicBranchBlockBakedModel; -import com.dtteam.dynamictrees.model.baked.ThickBranchBlockBakedModel; -import com.dtteam.dynamictrees.tree.family.Family; -import net.minecraft.client.renderer.texture.TextureAtlasSprite; -import net.minecraft.client.resources.model.BakedModel; -import net.minecraft.client.resources.model.Material; -import net.minecraft.client.resources.model.ModelBaker; -import net.minecraft.client.resources.model.ModelState; -import net.minecraft.client.resources.model.UnbakedModel; -import net.minecraft.resources.Identifier; -import net.minecraft.world.inventory.InventoryMenu; -import org.jetbrains.annotations.Nullable; - -import java.util.Collection; -import java.util.Collections; -import java.util.function.Function; - -public class BranchBlockUnbakedModel implements UnbakedModel { - - protected final Identifier barkTextureLocation; - protected final Identifier ringsTextureLocation; - protected final Identifier familyName; - protected final boolean forceThickness; - - public BranchBlockUnbakedModel(Identifier barkTextureLocation, Identifier ringsTextureLocation, @Nullable Identifier familyName, boolean forceThickness) { - this.barkTextureLocation = barkTextureLocation; - this.ringsTextureLocation = ringsTextureLocation; - this.familyName = familyName; - this.forceThickness = forceThickness; - } - - @Override - public Collection getDependencies() { - return Collections.emptyList(); - } - - @Override - public void resolveParents(Function resolver) { - } - - @Override - public BakedModel bake(ModelBaker baker, Function spriteGetter, ModelState state) { - TextureAtlasSprite barkSprite = spriteGetter.apply(new Material(InventoryMenu.BLOCK_ATLAS, barkTextureLocation)); - TextureAtlasSprite ringsSprite = spriteGetter.apply(new Material(InventoryMenu.BLOCK_ATLAS, ringsTextureLocation)); - - Family family = familyName != null ? Family.REGISTRY.get(familyName) : null; - boolean useThickModel = forceThickness || (family != null && family.isThick()); - - if (useThickModel) { - Identifier thickRingsLocation = ringsTextureLocation.withSuffix("_thick"); - TextureAtlasSprite thickRingsSprite = spriteGetter.apply(new Material(InventoryMenu.BLOCK_ATLAS, thickRingsLocation)); - return new ThickBranchBlockBakedModel(barkSprite, ringsSprite, thickRingsSprite); - } - - return new BasicBranchBlockBakedModel(barkSprite, ringsSprite); - } -} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/DTModelLoadingPlugin.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/DTModelLoadingPlugin.java index ec4c14306..1513a0ecc 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/model/DTModelLoadingPlugin.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/DTModelLoadingPlugin.java @@ -8,239 +8,227 @@ import com.dtteam.dynamictrees.model.baked.BasicRootsBlockBakedModel; import com.dtteam.dynamictrees.model.baked.SurfaceRootBlockBakedModel; import com.dtteam.dynamictrees.model.baked.ThickBranchBlockBakedModel; -import com.dtteam.dynamictrees.tree.family.Family; +import com.dtteam.dynamictrees.model.blockstate.AerialRootsSoilBlockStateModel; +import com.dtteam.dynamictrees.model.blockstate.PottedSaplingBlockStateModel; +import com.dtteam.dynamictrees.model.blockstate.UnbakedBranchModel; +import com.dtteam.dynamictrees.model.blockstate.UnbakedCreakingHeartModel; +import com.dtteam.dynamictrees.model.blockstate.UnbakedRootsMossModel; +import com.dtteam.dynamictrees.model.blockstate.UnbakedRootsModel; +import com.dtteam.dynamictrees.model.blockstate.UnbakedSurfaceRootModel; +import com.dtteam.dynamictrees.model.parts.BranchModelPart; import com.dtteam.dynamictrees.tree.family.AerialRootsFamily; +import com.dtteam.dynamictrees.tree.family.Family; +import com.dtteam.dynamictrees.utility.IdentifierUtils; +import net.fabricmc.fabric.api.client.model.loading.v1.CustomUnbakedBlockStateModel; import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin; import net.fabricmc.fabric.api.client.model.loading.v1.ModelModifier; -import net.minecraft.client.renderer.texture.TextureAtlasSprite; -import net.minecraft.client.resources.model.BakedModel; -import net.minecraft.client.resources.model.Material; -import net.minecraft.client.resources.model.ModelIdentifier; +import net.minecraft.client.renderer.block.dispatch.BlockStateModel; +import net.minecraft.client.resources.model.ModelBaker; +import net.minecraft.client.resources.model.sprite.Material; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.Identifier; -import net.minecraft.world.inventory.InventoryMenu; import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import org.jetbrains.annotations.Nullable; -import java.util.HashMap; import java.util.Map; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Function; - +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Model loading hooks for DT's dynamic models on Fabric. + * + *

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

The secondary path is an after-bake fallback for dynamic blocks whose blockstate definitions + * are missing or unparseable (e.g. add-on tree packs still shipping pre-26.2 assets): any branch, + * surface root or underground roots block state that did not resolve to a DT model gets a + * procedurally built model based on its family's primitive log textures. + */ public class DTModelLoadingPlugin implements ModelLoadingPlugin { - public static final Identifier POTTED_SAPLING_MODEL = DynamicTrees.location("potted_sapling"); - private static final Map BRANCH_MODEL_CACHE = new HashMap<>(); - private static final Map ROOT_MODEL_CACHE = new HashMap<>(); - private static final Map UNDERGROUND_ROOTS_MODEL_CACHE = new HashMap<>(); - private static boolean modelsInitialized = false; + public static final Identifier BRANCH = DynamicTrees.location("branch"); + public static final Identifier SURFACE_ROOT = DynamicTrees.location("surface_root"); + public static final Identifier ROOTS = DynamicTrees.location("roots"); + public static final Identifier CREAKING_HEART = DynamicTrees.location("creaking_heart"); + public static final Identifier POTTED_DYNAMIC_SAPLING = DynamicTrees.location("potted_dynamic_sapling"); + public static final Identifier AERIAL_ROOTS_SOIL = DynamicTrees.location("aerial_roots_soil"); + public static final Identifier ROOTS_MOSS = DynamicTrees.location("roots_moss"); + + private static final Map FALLBACK_MODEL_CACHE = new ConcurrentHashMap<>(); + + /** + * Registers the custom blockstate model codecs. Must only be called once. + */ + public static void registerModelTypes() { + CustomUnbakedBlockStateModel.register(BRANCH, UnbakedBranchModel.CODEC); + CustomUnbakedBlockStateModel.register(ROOTS, UnbakedRootsModel.CODEC); + CustomUnbakedBlockStateModel.register(CREAKING_HEART, UnbakedCreakingHeartModel.CODEC); + CustomUnbakedBlockStateModel.register(SURFACE_ROOT, UnbakedSurfaceRootModel.CODEC); + CustomUnbakedBlockStateModel.register(POTTED_DYNAMIC_SAPLING, PottedSaplingBlockStateModel.Unbaked.CODEC); + CustomUnbakedBlockStateModel.register(AERIAL_ROOTS_SOIL, AerialRootsSoilBlockStateModel.Unbaked.CODEC); + CustomUnbakedBlockStateModel.register(ROOTS_MOSS, UnbakedRootsMossModel.CODEC); + } @Override - public void onInitializeModelLoader(Context pluginContext) { - modelsInitialized = false; - BRANCH_MODEL_CACHE.clear(); - ROOT_MODEL_CACHE.clear(); - UNDERGROUND_ROOTS_MODEL_CACHE.clear(); - pluginContext.modifyModelAfterBake().register(ModelModifier.WRAP_PHASE, this::modifyModelAfterBake); + public void initialize(Context pluginContext) { + FALLBACK_MODEL_CACHE.clear(); + DynamicModelRegistry.clear(); + pluginContext.modifyBlockModelAfterBake().register(ModelModifier.WRAP_PHASE, DTModelLoadingPlugin::modifyModelAfterBake); } - private void initBranchModels(Function spriteGetter) { - if (modelsInitialized) return; - modelsInitialized = true; - - for (Family family : Family.REGISTRY.getAll()) { - if (!family.isValid()) continue; + private static BlockStateModel modifyModelAfterBake(BlockStateModel model, ModelModifier.AfterBakeBlock.Context context) { + return record(context.state(), resolveModel(model, context)); + } - family.getPrimitiveLog().ifPresent(primitiveLog -> { - Identifier primitiveLogId = BuiltInRegistries.BLOCK.getKey(primitiveLog); - Identifier barkTexture = Identifier.fromNamespaceAndPath(primitiveLogId.getNamespace(), "block/" + primitiveLogId.getPath()); - Identifier ringsTexture = barkTexture.withSuffix("_top"); + /** + * Remembers DT's own models so DT can reach them even if another mod later decorates or replaces + * what the model manager hands out. See {@link DynamicModelRegistry}. + */ + private static BlockStateModel record(BlockState state, BlockStateModel model) { + if (model instanceof FabricDynamicBlockStateModel || model instanceof PottedSaplingBlockStateModel) { + DynamicModelRegistry.register(state, model); + } + return model; + } - AtomicReference barkRef = new AtomicReference<>(barkTexture); - AtomicReference ringsRef = new AtomicReference<>(ringsTexture); + private static BlockStateModel resolveModel(BlockStateModel model, ModelModifier.AfterBakeBlock.Context context) { + // Models already loaded through DT's codecs (or another DT path) are left untouched. + if (model instanceof FabricDynamicBlockStateModel || model instanceof PottedSaplingBlockStateModel) { + return model; + } - family.getTexturePath(Family.BRANCH).ifPresent(barkRef::set); - family.getTexturePath(Family.BRANCH_TOP).ifPresent(ringsRef::set); + BlockState state = context.state(); + Block block = state.getBlock(); - boolean isThick = family.isThick(); + if (block instanceof BasicRootsBlock rootsBlock) { + BlockStateModel rootsModel = getOrCreateRootsModel(rootsBlock, state, context.baker()); + return rootsModel != null ? rootsModel : model; + } - family.getBranch().ifPresent(branch -> { - Identifier blockId = BuiltInRegistries.BLOCK.getKey(branch); - BakedModel model = createBranchModel(barkRef.get(), ringsRef.get(), isThick, spriteGetter); - BRANCH_MODEL_CACHE.put(blockId, model); - }); - }); + if (block instanceof SurfaceRootBlock surfaceRootBlock) { + BlockStateModel rootModel = getOrCreateSurfaceRootModel(surfaceRootBlock, context.baker()); + return rootModel != null ? rootModel : model; + } - family.getPrimitiveStrippedLog().ifPresent(strippedLog -> { - Identifier strippedLogId = BuiltInRegistries.BLOCK.getKey(strippedLog); - Identifier strippedBarkTexture = Identifier.fromNamespaceAndPath(strippedLogId.getNamespace(), "block/" + strippedLogId.getPath()); - Identifier strippedRingsTexture = strippedBarkTexture.withSuffix("_top"); + if (block instanceof BranchBlock branchBlock) { + BlockStateModel branchModel = getOrCreateBranchModel(branchBlock, context.baker()); + return branchModel != null ? branchModel : model; + } - AtomicReference barkRef = new AtomicReference<>(strippedBarkTexture); - AtomicReference ringsRef = new AtomicReference<>(strippedRingsTexture); + return model; + } - family.getTexturePath(Family.STRIPPED_BRANCH).ifPresent(barkRef::set); - family.getTexturePath(Family.STRIPPED_BRANCH_TOP).ifPresent(ringsRef::set); + /////////////////////////////////////////// + // BRANCHES + /////////////////////////////////////////// - boolean isThick = family.isThick(); + @Nullable + private static BlockStateModel getOrCreateBranchModel(BranchBlock branchBlock, ModelBaker baker) { + Family family = branchBlock.getFamily(); + if (family == null || !family.isValid()) return null; - family.getStrippedBranch().ifPresent(strippedBranch -> { - Identifier blockId = BuiltInRegistries.BLOCK.getKey(strippedBranch); - BakedModel model = createBranchModel(barkRef.get(), ringsRef.get(), isThick, spriteGetter); - BRANCH_MODEL_CACHE.put(blockId, model); - }); - }); + Identifier blockId = BuiltInRegistries.BLOCK.getKey(branchBlock); - family.getSurfaceRoot().ifPresent(surfaceRoot -> { - family.getPrimitiveLog().ifPresent(primitiveLog -> { - Identifier primitiveLogId = BuiltInRegistries.BLOCK.getKey(primitiveLog); - Identifier barkTexture = Identifier.fromNamespaceAndPath(primitiveLogId.getNamespace(), "block/" + primitiveLogId.getPath()); - AtomicReference barkRef = new AtomicReference<>(barkTexture); - family.getTexturePath(Family.BRANCH).ifPresent(barkRef::set); + boolean stripped = family.getStrippedBranch().map(b -> b == branchBlock).orElse(false); + Optional primitiveLog = stripped ? family.getPrimitiveStrippedLog() : family.getPrimitiveLog(); + if (primitiveLog.isEmpty()) return null; - Identifier blockId = BuiltInRegistries.BLOCK.getKey(surfaceRoot); - BakedModel model = createRootModel(barkRef.get(), spriteGetter); - ROOT_MODEL_CACHE.put(blockId, model); - }); - }); + Identifier primitiveLogId = BuiltInRegistries.BLOCK.getKey(primitiveLog.get()); + Identifier barkTexture = Identifier.fromNamespaceAndPath(primitiveLogId.getNamespace(), "block/" + primitiveLogId.getPath()); + Identifier ringsTexture = IdentifierUtils.suffix(barkTexture, "_top"); - if (family instanceof AerialRootsFamily rootsFamily) { - rootsFamily.getRoots().ifPresent(roots -> { - Identifier blockId = BuiltInRegistries.BLOCK.getKey(roots); + Identifier barkOverride = family.getTexturePath(stripped ? Family.STRIPPED_BRANCH : Family.BRANCH).orElse(barkTexture); + Identifier ringsOverride = family.getTexturePath(stripped ? Family.STRIPPED_BRANCH_TOP : Family.BRANCH_TOP).orElse(ringsTexture); - rootsFamily.getPrimitiveRoots().ifPresent(primitiveRoots -> { - Identifier primitiveRootsId = BuiltInRegistries.BLOCK.getKey(primitiveRoots); - Identifier barkTexture = Identifier.fromNamespaceAndPath(primitiveRootsId.getNamespace(), "block/" + primitiveRootsId.getPath() + "_side"); - Identifier ringsTexture = Identifier.fromNamespaceAndPath(primitiveRootsId.getNamespace(), "block/" + primitiveRootsId.getPath() + "_top"); + boolean isThick = family.isThick(); - AtomicReference barkRef = new AtomicReference<>(barkTexture); - AtomicReference ringsRef = new AtomicReference<>(ringsTexture); + return FALLBACK_MODEL_CACHE.computeIfAbsent(blockId, id -> createBranchModel(barkOverride, ringsOverride, isThick, baker)); + } - family.getTexturePath(Family.ROOTS_SIDE).ifPresent(barkRef::set); - family.getTexturePath(Family.ROOTS_TOP).ifPresent(ringsRef::set); + private static BlockStateModel createBranchModel(Identifier barkTexture, Identifier ringsTexture, boolean isThick, ModelBaker baker) { + Material.Baked barkMat = bakeMaterial(baker, barkTexture); + Material.Baked ringsMat = bakeMaterial(baker, ringsTexture); - BakedModel model = createRootsBlockModel(barkRef.get(), ringsRef.get(), spriteGetter); - UNDERGROUND_ROOTS_MODEL_CACHE.put(blockId.withSuffix("_exposed"), model); - }); + BasicBranchBlockBakedModel regular = BasicBranchBlockBakedModel.bakeBasic(baker, + new BranchModelPart.UnbakedCore(barkMat), + new BranchModelPart.UnbakedSleeve(barkMat), + new BranchModelPart.UnbakedCore(ringsMat), + null); - rootsFamily.getPrimitiveFilledRoots().ifPresent(primitiveFilledRoots -> { - Identifier primitiveFilledRootsId = BuiltInRegistries.BLOCK.getKey(primitiveFilledRoots); - Identifier barkTexture = Identifier.fromNamespaceAndPath(primitiveFilledRootsId.getNamespace(), "block/" + primitiveFilledRootsId.getPath() + "_side"); - Identifier ringsTexture = Identifier.fromNamespaceAndPath(primitiveFilledRootsId.getNamespace(), "block/" + primitiveFilledRootsId.getPath() + "_top"); + if (isThick) { + Identifier thickRingsTexture = IdentifierUtils.suffix(ringsTexture, "_thick"); + Material.Baked thickRingsMat = bakeMaterial(baker, thickRingsTexture); + return ThickBranchBlockBakedModel.bakeThick(baker, regular, + new BranchModelPart.UnbakedThickTrunk(barkMat, false), + new BranchModelPart.UnbakedThickTrunk(thickRingsMat, true)); + } - BakedModel model = createRootsBlockModel(barkTexture, ringsTexture, spriteGetter); - UNDERGROUND_ROOTS_MODEL_CACHE.put(blockId.withSuffix("_filled"), model); - }); + return regular; + } + /////////////////////////////////////////// + // SURFACE ROOTS + /////////////////////////////////////////// - }); - } - } - } + @Nullable + private static BlockStateModel getOrCreateSurfaceRootModel(SurfaceRootBlock surfaceRootBlock, ModelBaker baker) { + Family family = surfaceRootBlock.getFamily(); + if (family == null || !family.isValid()) return null; - private BakedModel createBranchModel(Identifier barkTexture, Identifier ringsTexture, boolean isThick, Function spriteGetter) { - TextureAtlasSprite barkSprite = spriteGetter.apply(new Material(InventoryMenu.BLOCK_ATLAS, barkTexture)); - TextureAtlasSprite ringsSprite = spriteGetter.apply(new Material(InventoryMenu.BLOCK_ATLAS, ringsTexture)); + Optional primitiveLog = family.getPrimitiveLog(); + if (primitiveLog.isEmpty()) return null; - if (isThick) { - Identifier thickRingsTexture = ringsTexture.withSuffix("_thick"); - TextureAtlasSprite thickRingsSprite = spriteGetter.apply(new Material(InventoryMenu.BLOCK_ATLAS, thickRingsTexture)); - return new ThickBranchBlockBakedModel(barkSprite, ringsSprite, thickRingsSprite); - } + Identifier blockId = BuiltInRegistries.BLOCK.getKey(surfaceRootBlock); + Identifier primitiveLogId = BuiltInRegistries.BLOCK.getKey(primitiveLog.get()); + Identifier barkTexture = Identifier.fromNamespaceAndPath(primitiveLogId.getNamespace(), "block/" + primitiveLogId.getPath()); + Identifier barkOverride = family.getTexturePath(Family.BRANCH).orElse(barkTexture); - return new BasicBranchBlockBakedModel(barkSprite, ringsSprite); + return FALLBACK_MODEL_CACHE.computeIfAbsent(blockId, id -> SurfaceRootBlockBakedModel.bake(baker, barkOverride)); } - private BakedModel createRootModel(Identifier barkTexture, Function spriteGetter) { - TextureAtlasSprite barkSprite = spriteGetter.apply(new Material(InventoryMenu.BLOCK_ATLAS, barkTexture)); - return new SurfaceRootBlockBakedModel(barkSprite); - } + /////////////////////////////////////////// + // UNDERGROUND (AERIAL) ROOTS + /////////////////////////////////////////// - private BakedModel createRootsBlockModel(Identifier barkTexture, Identifier ringsTexture, Function spriteGetter) { - TextureAtlasSprite barkSprite = spriteGetter.apply(new Material(InventoryMenu.BLOCK_ATLAS, barkTexture)); - TextureAtlasSprite ringsSprite = spriteGetter.apply(new Material(InventoryMenu.BLOCK_ATLAS, ringsTexture)); - return new BasicRootsBlockBakedModel(barkSprite, ringsSprite); - } + @Nullable + private static BlockStateModel getOrCreateRootsModel(BasicRootsBlock rootsBlock, BlockState state, ModelBaker baker) { + if (!(rootsBlock.getFamily() instanceof AerialRootsFamily rootsFamily) || !rootsFamily.isValid()) return null; + if (!state.hasProperty(BasicRootsBlock.LAYER)) return null; + + BasicRootsBlock.Layer layer = state.getValue(BasicRootsBlock.LAYER); + Identifier blockId = BuiltInRegistries.BLOCK.getKey(rootsBlock); - private BakedModel createFallbackRootsModel(AerialRootsFamily family, String variant, Function spriteGetter) { - if (variant.contains("layer=exposed")) { - return family.getPrimitiveRoots().map(primitiveRoots -> { + return switch (layer) { + case EXPOSED -> rootsFamily.getPrimitiveRoots().map(primitiveRoots -> { Identifier primitiveRootsId = BuiltInRegistries.BLOCK.getKey(primitiveRoots); Identifier barkTexture = Identifier.fromNamespaceAndPath(primitiveRootsId.getNamespace(), "block/" + primitiveRootsId.getPath() + "_side"); Identifier ringsTexture = Identifier.fromNamespaceAndPath(primitiveRootsId.getNamespace(), "block/" + primitiveRootsId.getPath() + "_top"); - return createRootsBlockModel(barkTexture, ringsTexture, spriteGetter); + + Identifier barkOverride = rootsFamily.getTexturePath(Family.ROOTS_SIDE).orElse(barkTexture); + Identifier ringsOverride = rootsFamily.getTexturePath(Family.ROOTS_TOP).orElse(ringsTexture); + + return FALLBACK_MODEL_CACHE.computeIfAbsent(IdentifierUtils.suffix(blockId, "_exposed"), id -> + BasicRootsBlockBakedModel.bakeRoots(baker, bakeMaterial(baker, barkOverride), bakeMaterial(baker, ringsOverride), false)); }).orElse(null); - } else if (variant.contains("layer=filled")) { - return family.getPrimitiveFilledRoots().map(primitiveFilledRoots -> { + case FILLED -> rootsFamily.getPrimitiveFilledRoots().map(primitiveFilledRoots -> { Identifier primitiveFilledRootsId = BuiltInRegistries.BLOCK.getKey(primitiveFilledRoots); Identifier barkTexture = Identifier.fromNamespaceAndPath(primitiveFilledRootsId.getNamespace(), "block/" + primitiveFilledRootsId.getPath() + "_side"); Identifier ringsTexture = Identifier.fromNamespaceAndPath(primitiveFilledRootsId.getNamespace(), "block/" + primitiveFilledRootsId.getPath() + "_top"); - return createRootsBlockModel(barkTexture, ringsTexture, spriteGetter); + + return FALLBACK_MODEL_CACHE.computeIfAbsent(IdentifierUtils.suffix(blockId, "_filled"), id -> + BasicRootsBlockBakedModel.bakeRoots(baker, bakeMaterial(baker, barkTexture), bakeMaterial(baker, ringsTexture), true)); }).orElse(null); - } - return null; + case COVERED -> null; // Covered roots keep their regular (soil-like) model. + }; } - private BakedModel modifyModelAfterBake(BakedModel model, ModelModifier.AfterBake.Context context) { - ModelIdentifier modelId = context.topLevelId(); - if (modelId == null) return model; - - if (modelId.id().equals(POTTED_SAPLING_MODEL)) { - return new BakedModelBlockPottedSapling(model); - } - - Identifier blockId = modelId.id(); - Block block = BuiltInRegistries.BLOCK.get(blockId); - - if (block instanceof BasicRootsBlock rootsBlock) { - initBranchModels(context.textureGetter()); - - String variant = modelId.variant(); - Identifier cacheKey; - if (variant.contains("layer=filled")) { - cacheKey = blockId.withSuffix("_filled"); - } else if (variant.contains("layer=exposed")) { - cacheKey = blockId.withSuffix("_exposed"); - } else if (variant.contains("layer=covered")) { - return model; - } else { - return model; - } - - BakedModel rootsModel = UNDERGROUND_ROOTS_MODEL_CACHE.get(cacheKey); - if (rootsModel != null) { - return rootsModel; - } - - if (rootsBlock.getFamily() instanceof AerialRootsFamily undergroundFamily) { - BakedModel fallbackModel = createFallbackRootsModel(undergroundFamily, variant, context.textureGetter()); - if (fallbackModel != null) { - UNDERGROUND_ROOTS_MODEL_CACHE.put(cacheKey, fallbackModel); - return fallbackModel; - } - } - return model; - } - - if (block instanceof SurfaceRootBlock) { - initBranchModels(context.textureGetter()); - - BakedModel rootModel = ROOT_MODEL_CACHE.get(blockId); - if (rootModel != null) { - return rootModel; - } - return model; - } - - if (block instanceof BranchBlock) { - initBranchModels(context.textureGetter()); - - BakedModel branchModel = BRANCH_MODEL_CACHE.get(blockId); - if (branchModel != null) { - return branchModel; - } - } - - - return model; + private static Material.Baked bakeMaterial(ModelBaker baker, Identifier texture) { + return baker.materials().get(new Material(texture), texture::toDebugFileName); } } diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/FabricDynamicBlockStateModel.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/FabricDynamicBlockStateModel.java new file mode 100644 index 000000000..df75da7d5 --- /dev/null +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/FabricDynamicBlockStateModel.java @@ -0,0 +1,43 @@ +package com.dtteam.dynamictrees.model; + +import net.fabricmc.fabric.api.client.renderer.v1.mesh.QuadEmitter; +import net.fabricmc.fabric.api.client.renderer.v1.model.FabricBlockStateModel; +import net.fabricmc.fabric.api.client.renderer.v1.model.FabricBlockStateModelPart; +import net.minecraft.client.renderer.block.BlockAndTintGetter; +import net.minecraft.client.renderer.block.dispatch.BlockStateModel; +import net.minecraft.client.renderer.block.dispatch.BlockStateModelPart; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.block.state.BlockState; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +/** + * Fabric mirror of NeoForge's {@code DynamicBlockStateModel}: a {@link BlockStateModel} whose + * geometry depends on level context. Implementors provide the level-aware + * {@link #collectParts(BlockAndTintGetter, BlockPos, BlockState, RandomSource, List)}; quads are + * emitted through the Fabric Renderer API's {@link FabricBlockStateModel#emitQuads} path. + */ +public interface FabricDynamicBlockStateModel extends BlockStateModel, FabricBlockStateModel { + + void collectParts(BlockAndTintGetter level, BlockPos pos, BlockState state, RandomSource random, List parts); + + @Override + default void collectParts(RandomSource random, List parts) { + // Geometry is dynamic; without level context there is nothing meaningful to collect. + } + + @Override + default void emitQuads(QuadEmitter emitter, BlockAndTintGetter level, BlockPos pos, BlockState state, + RandomSource random, Predicate<@Nullable Direction> cullTest) { + List parts = new ArrayList<>(); + collectParts(level, pos, state, random, parts); + for (BlockStateModelPart part : parts) { + ((FabricBlockStateModelPart) part).emitQuads(emitter, cullTest); + } + } +} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/FallingTreeEntityModelFabric.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/FallingTreeEntityModelFabric.java index d1baafeb4..9f5f52a73 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/model/FallingTreeEntityModelFabric.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/FallingTreeEntityModelFabric.java @@ -1,181 +1,17 @@ package com.dtteam.dynamictrees.model; -import com.dtteam.dynamictrees.api.network.BranchDestructionData; -import com.dtteam.dynamictrees.block.branch.BranchBlock; -import com.dtteam.dynamictrees.block.soil.SoilBlock; -import com.dtteam.dynamictrees.compat.continuity.WrappedModelHandler; import com.dtteam.dynamictrees.entity.FallingTreeEntity; -import com.dtteam.dynamictrees.model.baked.BasicBranchBlockBakedModel; import com.dtteam.dynamictrees.model.entity.FallingTreeEntityModel; -import com.dtteam.dynamictrees.tree.TreeHelper; -import com.dtteam.dynamictrees.tree.species.Species; -import net.minecraft.client.Minecraft; -import net.minecraft.client.renderer.block.BlockRenderDispatcher; -import net.minecraft.client.renderer.block.model.BakedQuad; -import net.minecraft.client.resources.model.BakedModel; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.util.RandomSource; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.phys.Vec3; -import org.apache.commons.lang3.tuple.Pair; - -import java.util.ArrayList; -import java.util.List; +/** + * Fabric falling tree entity model. Since the 26.2 port, quad generation is fully handled by the + * common {@link FallingTreeEntityModel} (via {@code QuadManipulator} and the + * {@code BlockStateModelWithConnectionData} interface implemented by DT's Fabric block state models), + * so no Fabric-specific behavior remains. + */ public class FallingTreeEntityModelFabric extends FallingTreeEntityModel { public FallingTreeEntityModelFabric(FallingTreeEntity entity) { super(entity); } - - @Override - public List generateTreeQuads(FallingTreeEntity entity) { - BlockRenderDispatcher dispatcher = Minecraft.getInstance().getBlockRenderer(); - BranchDestructionData destructionData = entity.getDestroyData(); - Direction cutDir = destructionData.cutDir; - - ArrayList treeQuads = new ArrayList<>(); - - int[] connectionArray = new int[6]; - - if (destructionData.getNumBranches() > 0) { - BlockState exState = destructionData.getBranchBlockState(0); - BlockPos cutPos = destructionData.cutPos; - if (exState != null) { - Species species = destructionData.species; - RandomSource random = entity.getRandom(); - - boolean rootyBlockAdded = false; - if (destructionData.soilState != null) { - BlockState soilState = destructionData.soilState; - if (soilState != null) { - BlockState soilBlock = soilState.getBlock(); - BakedModel rootyModel = dispatcher.getBlockModel(soilState); - BlockPos cutOffset = destructionData.getRelativeCutPos(); - treeQuads.addAll(toTreeQuadData( - getQuadsWithOffset(rootyModel, soilState, new Vec3(cutOffset.getX(), cutOffset.getY() - 1, cutOffset.getZ()), random), - destructionData.species.getFamily().getRootColor(soilState, soilBlock.getColorFromBark()), - soilState)); - rootyBlockAdded = true; - } - } - - BakedModel branchModel = dispatcher.getBlockModel(exState); - destructionData.getConnections(0, connectionArray); - boolean bottomRingsAdded = false; - if (!rootyBlockAdded && connectionArray[cutDir.get3DDataValue()] > 0) { - BlockPos offsetPos = destructionData.getRelativeCutPos().relative(cutDir); - float offset = (8 - Math.min(((BranchBlock) exState.getBlock()).getRadius(exState), BranchBlock.MAX_RADIUS)) / 16f; - int coreRadius = ((BranchBlock) exState.getBlock()).getRadius(exState); - treeQuads.addAll(toTreeQuadData( - getBottomRingQuads(branchModel, new Vec3(offsetPos.getX(), offsetPos.getY(), offsetPos.getZ()).scale(offset), coreRadius, cutDir), - exState)); - bottomRingsAdded = true; - } - - for (int index = 0; index < destructionData.getNumBranches(); index++) { - Block previousBranch = exState.getBlock(); - exState = destructionData.getBranchBlockState(index); - if (!previousBranch.equals(exState.getBlock())) { - branchModel = dispatcher.getBlockModel(exState); - } - BlockPos relPos = destructionData.getBranchRelPos(index); - destructionData.getConnections(index, connectionArray); - int coreRadius = ((BranchBlock) exState.getBlock()).getRadius(exState); - Direction forceRingDir = (index == 0 && bottomRingsAdded) ? cutDir : null; - treeQuads.addAll(toTreeQuadData( - getBranchQuadsWithConnections(branchModel, exState, new Vec3(relPos.getX(), relPos.getY(), relPos.getZ()), random, connectionArray, coreRadius, forceRingDir), - exState)); - } - - for (Pair leafLoc : destructionData.getAllLeavesWithPos()) { - BlockState leafState = leafLoc.getValue(); - List bakedQuads = getQuadsWithOffset(dispatcher.getBlockModel(leafState), leafState, - new Vec3(leafLoc.getKey().getX(), leafLoc.getKey().getY(), leafLoc.getKey().getZ()), random); - - treeQuads.addAll(toTreeQuadData(bakedQuads, species.leafColorMultiplier(entity.level(), - cutPos.offset(leafLoc.getKey())), leafState)); - } - } - } - - return treeQuads; - } - - private List getBottomRingQuads(BakedModel model, Vec3 offset, int coreRadius, Direction cutDir) { - List allQuads = new ArrayList<>(); - - if (model instanceof BasicBranchBlockBakedModel branchModel) { - for (BakedQuad quad : branchModel.getRingQuads(coreRadius)) { - if (quad.getDirection() == cutDir) { - allQuads.add(quad); - } - } - } - - return offsetAllQuads(offset, allQuads); - } - - private List getBranchQuadsWithConnections(BakedModel model, BlockState state, Vec3 offset, RandomSource random, int[] connections, int coreRadius, Direction forceRingDir) { - List allQuads = new ArrayList<>(); - - BasicBranchBlockBakedModel branchModel = WrappedModelHandler.getInstance().unwrapBranchModel(model); - if (branchModel != null) { - int twigRadius = state.getBlock() instanceof BranchBlock branchBlock - ? branchBlock.getFamily().getPrimaryThickness() - : 1; - - branchModel.collectQuads(coreRadius, connections, twigRadius, forceRingDir) - .values().forEach(allQuads::addAll); - } else { - for (Direction direction : Direction.values()) { - allQuads.addAll(model.getQuads(state, direction, random)); - } - allQuads.addAll(model.getQuads(state, null, random)); - } - - return offsetAllQuads(offset, allQuads); - } - - private List getQuadsWithOffset(BakedModel model, BlockState state, Vec3 offset, RandomSource random) { - List allQuads = new ArrayList<>(); - - for (Direction direction : Direction.values()) { - allQuads.addAll(model.getQuads(state, direction, random)); - } - allQuads.addAll(model.getQuads(state, null, random)); - - return offsetAllQuads(offset, allQuads); - } - - private List offsetAllQuads(Vec3 offset, List allQuads) { - if (offset.x() != 0 || offset.y() != 0 || offset.z() != 0) { - List offsetQuads = new ArrayList<>(); - for (BakedQuad quad : allQuads) { - offsetQuads.add(offsetQuad(quad, offset)); - } - return offsetQuads; - } - - return allQuads; - } - - private BakedQuad offsetQuad(BakedQuad quad, Vec3 offset) { - int[] vertexData = quad.getVertices().clone(); - - for (int i = 0; i < 4; i++) { - int baseIndex = i * 8; - float x = Float.intBitsToFloat(vertexData[baseIndex]) + (float) offset.x(); - float y = Float.intBitsToFloat(vertexData[baseIndex + 1]) + (float) offset.y(); - float z = Float.intBitsToFloat(vertexData[baseIndex + 2]) + (float) offset.z(); - vertexData[baseIndex] = Float.floatToRawIntBits(x); - vertexData[baseIndex + 1] = Float.floatToRawIntBits(y); - vertexData[baseIndex + 2] = Float.floatToRawIntBits(z); - } - - return new BakedQuad(vertexData, quad.getTintIndex(), quad.getDirection(), quad.getSprite(), quad.isShade()); - } } diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/BasicBranchBlockBakedModel.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/BasicBranchBlockBakedModel.java index eeed78a92..08057a83e 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/BasicBranchBlockBakedModel.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/BasicBranchBlockBakedModel.java @@ -1,270 +1,168 @@ package com.dtteam.dynamictrees.model.baked; +import com.dtteam.dynamictrees.api.network.Connections; import com.dtteam.dynamictrees.block.branch.BranchBlock; -import com.dtteam.dynamictrees.block.branch.ThickBranchBlock; -import com.google.common.collect.Maps; -import net.fabricmc.fabric.api.renderer.v1.mesh.*; -import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel; -import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; -import net.fabricmc.fabric.api.renderer.v1.material.MaterialFinder; -import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial; -import net.fabricmc.fabric.api.renderer.v1.RendererAccess; -import net.minecraft.client.renderer.block.model.BakedQuad; -import net.minecraft.client.renderer.block.model.BlockElement; -import net.minecraft.client.renderer.block.model.BlockElementFace; -import net.minecraft.client.renderer.block.model.BlockFaceUV; -import net.minecraft.client.renderer.block.model.FaceBakery; -import net.minecraft.client.renderer.block.model.ItemOverrides; -import net.minecraft.client.renderer.block.model.ItemTransforms; -import net.minecraft.client.renderer.texture.TextureAtlasSprite; -import net.minecraft.client.resources.model.BakedModel; -import net.minecraft.client.resources.model.BlockModelRotation; +import com.dtteam.dynamictrees.model.BlockStateModelWithConnectionData; +import com.dtteam.dynamictrees.model.BranchMultiPartHolder; +import com.dtteam.dynamictrees.model.FabricDynamicBlockStateModel; +import com.dtteam.dynamictrees.model.ModelConnections; +import com.dtteam.dynamictrees.model.ModelHelper; +import com.dtteam.dynamictrees.model.parts.BranchModelPart; +import com.dtteam.dynamictrees.tree.TreeHelper; +import net.minecraft.client.renderer.block.BlockAndTintGetter; +import net.minecraft.client.renderer.block.dispatch.BlockStateModelPart; +import net.minecraft.client.resources.model.ModelBaker; +import net.minecraft.client.resources.model.geometry.BakedQuad; +import net.minecraft.client.resources.model.sprite.Material; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; -import net.minecraft.core.Direction.Axis; -import net.minecraft.core.Direction.AxisDirection; import net.minecraft.util.RandomSource; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.BlockAndTintGetter; import net.minecraft.world.level.block.state.BlockState; import org.jetbrains.annotations.Nullable; -import org.joml.Vector3f; -import java.util.*; -import java.util.function.Supplier; - -@SuppressWarnings("unchecked") -public class BasicBranchBlockBakedModel implements BakedModel, FabricBakedModel { - - protected final TextureAtlasSprite barkTexture; - protected final TextureAtlasSprite ringsTexture; - - public final List[][] sleevesQuads = new List[6][8]; - public final List[][] coresQuads = new List[3][8]; - public final List[] ringsQuads = new List[8]; - - public BasicBranchBlockBakedModel(TextureAtlasSprite barkTexture, TextureAtlasSprite ringsTexture) { - this.barkTexture = barkTexture; - this.ringsTexture = ringsTexture; - initModels(); - } - - private void initModels() { - for (int i = 0; i < 8; i++) { - int radius = i + 1; - if (radius < 8) { - for (Direction dir : Direction.values()) { - sleevesQuads[dir.get3DDataValue()][i] = bakeSleeve(radius, dir, barkTexture); - } +import java.util.List; + +/** + * Dynamic branch block model for Fabric. Mirrors the NeoForge {@code BranchBlockStateModel}, + * emitting level-aware geometry (branch connections) at chunk-build time via the Fabric + * Renderer API. + */ +public class BasicBranchBlockBakedModel implements FabricDynamicBlockStateModel, BlockStateModelWithConnectionData { + + protected final BranchMultiPartHolder cores; + protected final BranchMultiPartHolder sleeves; + protected final BranchMultiPartHolder rings; + protected final BranchMultiPartHolder sleeveRings; + + public BasicBranchBlockBakedModel(BranchMultiPartHolder cores, BranchMultiPartHolder sleeves, + BranchMultiPartHolder rings, BranchMultiPartHolder sleeveRings) { + this.cores = cores; + this.sleeves = sleeves; + this.rings = rings; + this.sleeveRings = sleeveRings; + } + + /** + * Bakes a basic branch model out of the given unbaked parts. Mirrors the NeoForge + * {@code UnbakedBranchModel#bakeBasic}. + */ + public static BasicBranchBlockBakedModel bakeBasic( + ModelBaker baker, BranchModelPart.UnbakedCore unbakedCores, BranchModelPart.UnbakedSleeve unbakedSleeves, + BranchModelPart.UnbakedCore unbakedRings, @Nullable BranchModelPart.UnbakedSleeve unbakedSleeveRings) { + BranchMultiPartHolder sleeves = new BranchMultiPartHolder(); + BranchMultiPartHolder cores = new BranchMultiPartHolder(); + BranchMultiPartHolder rings = new BranchMultiPartHolder(); + BranchMultiPartHolder sleeveRings = new BranchMultiPartHolder(); + + for (int radius = 1; radius <= BranchBlock.MAX_RADIUS; radius++) { + if (radius < BranchBlock.MAX_RADIUS) { + sleeves.putAllParts(radius, unbakedSleeves.bakeAllSides(baker, radius)); } - coresQuads[0][i] = bakeCore(radius, Axis.Y, barkTexture); - coresQuads[1][i] = bakeCore(radius, Axis.Z, barkTexture); - coresQuads[2][i] = bakeCore(radius, Axis.X, barkTexture); - - ringsQuads[i] = bakeCore(radius, Axis.Y, ringsTexture); - } - } - - public BlockElement generateSleevePart(int radius, Direction dir) { - int dradius = radius * 2; - int halfSize = (16 - dradius) / 2; - int halfSizeX = dir.getStepX() != 0 ? halfSize : dradius; - int halfSizeY = dir.getStepY() != 0 ? halfSize : dradius; - int halfSizeZ = dir.getStepZ() != 0 ? halfSize : dradius; - int move = 16 - halfSize; - int centerX = 16 + (dir.getStepX() * move); - int centerY = 16 + (dir.getStepY() * move); - int centerZ = 16 + (dir.getStepZ() * move); - - Vector3f posFrom = new Vector3f((centerX - halfSizeX) / 2f, (centerY - halfSizeY) / 2f, (centerZ - halfSizeZ) / 2f); - Vector3f posTo = new Vector3f((centerX + halfSizeX) / 2f, (centerY + halfSizeY) / 2f, (centerZ + halfSizeZ) / 2f); - - boolean negative = dir.getAxisDirection() == AxisDirection.NEGATIVE; - if (dir.getAxis() == Axis.Z) { - negative = !negative; - } - - Map mapFacesIn = Maps.newEnumMap(Direction.class); - - for (Direction face : Direction.values()) { - if (dir.getOpposite() != face) { - BlockFaceUV uvface = null; - if (dir == face) { - if (radius == 1) { - uvface = new BlockFaceUV(new float[]{8 - radius, 8 - radius, 8 + radius, 8 + radius}, 0); - } - } else { - uvface = new BlockFaceUV(new float[]{8 - radius, negative ? 16 - halfSize : 0, 8 + radius, negative ? 16 : halfSize}, getFaceAngle(dir.getAxis(), face)); - } - if (uvface != null) { - mapFacesIn.put(face, new BlockElementFace(null, -1, null, uvface)); - } + if (unbakedSleeveRings != null) { + sleeveRings.putAllParts(radius, unbakedSleeveRings.bakeAllSides(baker, radius)); } - } - return new BlockElement(posFrom, posTo, mapFacesIn, null, true); - } + cores.putAllParts(Direction.Axis.Y, radius, unbakedCores.bakeAllSides(baker, radius, Direction.Axis.Y)); //DOWN<->UP + cores.putAllParts(Direction.Axis.Z, radius, unbakedCores.bakeAllSides(baker, radius, Direction.Axis.Z)); //NORTH<->SOUTH + cores.putAllParts(Direction.Axis.X, radius, unbakedCores.bakeAllSides(baker, radius, Direction.Axis.X)); //WEST<->EAST - public List bakeSleeve(int radius, Direction dir, TextureAtlasSprite bark) { - BlockElement part = generateSleevePart(radius, dir); - List quads = new ArrayList<>(); - FaceBakery faceBakery = new FaceBakery(); - - for (Map.Entry e : part.faces.entrySet()) { - Direction face = e.getKey(); - quads.add(faceBakery.bakeQuad(part.from, part.to, e.getValue(), bark, face, BlockModelRotation.X0_Y0, part.rotation, true)); + rings.putAllParts(radius, unbakedRings.bakeAllSides(baker, radius, Direction.Axis.Y)); } - return quads; + return new BasicBranchBlockBakedModel(cores, sleeves, rings, sleeveRings); } - protected BlockElement generateCorePart(int radius, Axis axis) { - Vector3f posFrom = new Vector3f(8 - radius, 8 - radius, 8 - radius); - Vector3f posTo = new Vector3f(8 + radius, 8 + radius, 8 + radius); - - Map mapFacesIn = Maps.newEnumMap(Direction.class); - - for (Direction face : Direction.values()) { - BlockFaceUV uvface = new BlockFaceUV(new float[]{8 - radius, 8 - radius, 8 + radius, 8 + radius}, getFaceAngle(axis, face)); - mapFacesIn.put(face, new BlockElementFace(null, -1, null, uvface)); - } - - return new BlockElement(posFrom, posTo, mapFacesIn, null, true); - } - - public List bakeCore(int radius, Axis axis, TextureAtlasSprite icon) { - BlockElement part = generateCorePart(radius, axis); - List quads = new ArrayList<>(); - FaceBakery faceBakery = new FaceBakery(); - - for (Map.Entry e : part.faces.entrySet()) { - Direction face = e.getKey(); - quads.add(faceBakery.bakeQuad(part.from, part.to, e.getValue(), icon, face, BlockModelRotation.X0_Y0, part.rotation, true)); - } - - return quads; - } - - public int getFaceAngle(Axis axis, Direction face) { - if (axis == Axis.Y) { - return 0; - } else if (axis == Axis.Z) { - return switch (face) { - case UP -> 0; - case WEST -> 270; - case DOWN -> 180; - default -> 90; - }; - } else { - return (face == Direction.NORTH) ? 270 : 90; - } - } + /////////////////////////////////////////// + // FABRIC DYNAMIC GEOMETRY + /////////////////////////////////////////// @Override - public boolean isVanillaAdapter() { - return false; + public void collectParts(BlockAndTintGetter level, BlockPos pos, BlockState state, RandomSource random, List parts) { + collectParts(state, parts, ModelHelper.getModelConnections(level, pos, state)); } @Override - public void emitBlockQuads(BlockAndTintGetter blockView, BlockState state, BlockPos pos, Supplier randomSupplier, RenderContext context) { - EnumMap> bakedQuads = collectQuads(blockView, state, pos); - if (bakedQuads == null) return; - - QuadEmitter emitter = context.getEmitter(); - RenderMaterial material = getRenderMaterial(); - if (material == null) return; - - bakedQuads.forEach((dir, quads) -> - emitQuads(dir, emitter, material, quads)); - } - - protected @Nullable EnumMap> collectQuads(BlockAndTintGetter getter, BlockState state, BlockPos pos) { - if (state == null) return null; - - final int coreRadius = getRadius(state); - if (coreRadius <= 0 || coreRadius > maxBranchRadius()) return null; - - int[] connections = new int[]{0, 0, 0, 0, 0, 0}; - int twigRadius = 1; - - if (state.getBlock() instanceof BranchBlock branchBlock) { - connections = branchBlock.getConnectionData(getter, pos, state).getAllRadii(); - twigRadius = branchBlock.getFamily().getPrimaryThickness(); - } - - return collectQuads(coreRadius, connections, twigRadius, null); + public Object createGeometryKey(BlockAndTintGetter level, BlockPos pos, BlockState state, RandomSource random) { + return ModelHelper.getModelConnections(level, pos, state); } - protected int maxBranchRadius() { - return ThickBranchBlock.MAX_RADIUS; - } + /////////////////////////////////////////// + // PART COLLECTION + /////////////////////////////////////////// - public EnumMap> collectQuads(int coreRadius, int[] connections, int twigRadius, Direction forceRingDir) { - int numConnections = 0; - for (int i : connections) { - numConnections += (i != 0) ? 1 : 0; + @Override + public void collectParts(BlockState state, List parts, Connections connectionsData) { + final int coreRadius = TreeHelper.getRadius(state); + if (coreRadius > 8 || coreRadius == 0) return; + if (!(connectionsData instanceof ModelConnections modelConnections)) return; + + final int[] connections = connectionsData.getAllRadii(); + final Direction forceRingDir = modelConnections.getRingOnly(); + final int twigRadius = modelConnections.getFamily().getPrimaryThickness(); + final int numConnections = countConnections(connections); + + final Direction sourceDir = get3DSourceDir(coreRadius, connections); + final Direction.Axis coreDir = sourceDir == null ? Direction.Axis.Y : sourceDir.getAxis(); + final Direction coreRingDir = (numConnections == 1 && sourceDir != null) ? sourceDir.getOpposite() : null; + + if (forceRings(numConnections, forceRingDir)) { + addPart(parts, rings.getPart(forceRingDir, coreRadius)); + } else { + for (Direction face : Direction.values()) { + gatherCoreParts(parts, face, coreRadius, connections, coreRingDir, coreDir); + gatherSleeveParts(parts, face, coreRadius, connections, twigRadius); + } } - - Direction sourceDir = getSourceDir(coreRadius, connections); - int coreDir = resolveCoreDir(sourceDir); - Direction coreRingDir = forceRingDir != null ? forceRingDir : - ((numConnections == 1 && sourceDir != null) ? sourceDir.getOpposite() : null); - - EnumMap> bakedQuads = new EnumMap<>(Direction.class); - - for (Direction face : Direction.values()) { - List quads = bakedQuads.computeIfAbsent(face, dir->new ArrayList<>()); - if (coreRadius != connections[face.get3DDataValue()]) { - if (coreRingDir == null || coreRingDir != face) { - quads.addAll(coresQuads[coreDir][coreRadius - 1]); - } else { - quads.addAll(ringsQuads[coreRadius - 1]); - } + //The null side is usually empty, but roots have the cross. + addPart(parts, cores.getPart(coreDir, null, coreRadius)); + } + + private void gatherSleeveParts(List parts, Direction face, int coreRadius, int[] connections, int twigRadius) { + // Get quads for sleeves models. + for (Direction connDir : Direction.values()) { + final int idx = connDir.get3DDataValue(); + final int connRadius = connections[idx]; + if (connRadius == 0) continue; + // If the connection side matches the quadpull side then cull the sleeve face. + // Don't cull radius-1 connections for leaves (which are partly transparent). + if (coreRadius < 8 && connRadius <= twigRadius || face != connDir) { + addPart(parts, sleeves.getPart(connDir, connRadius)); } - - if (coreRadius != 8) { - for (Direction connDir : Direction.values()) { - int idx = connDir.get3DDataValue(); - int connRadius = connections[idx]; - if (connRadius > 0 && connRadius < 8 && (connRadius <= twigRadius || face != connDir)) { - quads.addAll(sleevesQuads[idx][connRadius - 1]); - } - } + if (face == connDir && !sleeveRings.isEmpty()) { + addPart(parts, sleeveRings.getPart(connDir, connRadius)); } } - return bakedQuads; } - public List getRingQuads(int radius){ - return ringsQuads[radius - 1]; - } + private void gatherCoreParts(List parts, Direction face, int coreRadius, int[] connections, Direction coreRingDir, Direction.Axis coreDir) { + if (coreRadius == connections[face.get3DDataValue()]) return; - protected static RenderMaterial getRenderMaterial() { - var renderer = RendererAccess.INSTANCE.getRenderer(); - if (renderer == null) return null; - MaterialFinder finder = renderer.materialFinder(); -// finder.disableAo(0, true); -// finder.disableDiffuse(0, true); + if (coreRingDir != null && coreRingDir == face) { + addPart(parts, rings.getPart(face, coreRadius)); + } else { + addPart(parts, cores.getPart(coreDir, face, coreRadius)); + } + } - return finder.find(); + protected static void addPart(List parts, @Nullable BlockStateModelPart part) { + if (part == null) return; + parts.add(part); } - protected void emitQuads(Direction face, QuadEmitter emitter, RenderMaterial material, List quads) { - if (quads == null) return; - for (BakedQuad quad : quads) { - if (quad.getDirection() == face) { - emitter.fromVanilla(quad, material, face); - emitter.emit(); - } - } + private static boolean forceRings(int numConnections, Direction forceRingDir) { + return numConnections == 0 && forceRingDir != null; } - @Override - public void emitItemQuads(ItemStack stack, Supplier randomSupplier, RenderContext context) { + public static int countConnections(int[] connections) { + int numConnections = 0; + for (int i : connections) { + numConnections += (i != 0) ? 1 : 0; + } + return numConnections; } @Nullable - protected Direction getSourceDir(int coreRadius, int[] connections) { + public static Direction get3DSourceDir(int coreRadius, int[] connections) { int largestConnection = 0; Direction sourceDir = null; @@ -277,59 +175,22 @@ protected Direction getSourceDir(int coreRadius, int[] connections) { } if (largestConnection < coreRadius) { - sourceDir = null; + sourceDir = null; //Has no source node } return sourceDir; } - protected int resolveCoreDir(@Nullable Direction dir) { - if (dir == null) { - return 0; - } - return dir.get3DDataValue() >> 1; - } - - protected int getRadius(BlockState blockState) { - return ((BranchBlock) blockState.getBlock()).getRadius(blockState); - } - - @Override - public List getQuads(@Nullable BlockState state, @Nullable Direction direction, RandomSource random) { - return Collections.emptyList(); - } - - @Override - public boolean useAmbientOcclusion() { - return true; - } - - @Override - public boolean isGui3d() { - return false; - } - - @Override - public boolean usesBlockLight() { - return false; - } - - @Override - public boolean isCustomRenderer() { - return false; - } - - @Override - public TextureAtlasSprite getParticleIcon() { - return barkTexture; - } + /////////////////////////////////////////// + // VANILLA MODEL METHODS + /////////////////////////////////////////// @Override - public ItemTransforms getTransforms() { - return ItemTransforms.NO_TRANSFORMS; + public Material.Baked particleMaterial() { + return cores.getFirstMaterial(); } @Override - public ItemOverrides getOverrides() { - return ItemOverrides.EMPTY; + public @BakedQuad.MaterialFlags int materialFlags() { + return cores.materialFlags(); } } diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/BasicRootsBlockBakedModel.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/BasicRootsBlockBakedModel.java index 37ab0a461..196a17ade 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/BasicRootsBlockBakedModel.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/BasicRootsBlockBakedModel.java @@ -1,132 +1,36 @@ package com.dtteam.dynamictrees.model.baked; -import com.dtteam.dynamictrees.block.branch.BasicRootsBlock; -import com.dtteam.dynamictrees.block.branch.BranchBlock; -import com.google.common.collect.Maps; -import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial; -import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter; -import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; -import net.minecraft.client.renderer.block.model.BakedQuad; -import net.minecraft.client.renderer.block.model.BlockElement; -import net.minecraft.client.renderer.block.model.BlockElementFace; -import net.minecraft.client.renderer.block.model.BlockFaceUV; -import net.minecraft.client.renderer.block.model.FaceBakery; -import net.minecraft.client.renderer.texture.TextureAtlasSprite; -import net.minecraft.client.resources.model.BlockModelRotation; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.util.RandomSource; -import net.minecraft.world.level.BlockAndTintGetter; -import net.minecraft.world.level.block.state.BlockState; -import org.jetbrains.annotations.Nullable; -import org.joml.Vector3f; - -import java.util.ArrayList; -import java.util.EnumMap; -import java.util.List; -import java.util.Map; -import java.util.function.Supplier; - -@SuppressWarnings("unchecked") +import com.dtteam.dynamictrees.model.BranchMultiPartHolder; +import com.dtteam.dynamictrees.model.parts.BranchModelPart; +import net.minecraft.client.resources.model.ModelBaker; +import net.minecraft.client.resources.model.sprite.Material; + +/** + * Dynamic underground roots model for Fabric. Baked out of root-specific branch parts; + * the part collection logic is shared with {@link BasicBranchBlockBakedModel}. + * Mirrors the NeoForge {@code UnbakedRootsModel} baking. + */ public class BasicRootsBlockBakedModel extends BasicBranchBlockBakedModel { - final static float Z_FIGHTING_OFFSET = 0.001f; - - private final List[][] sleeveEndFaces = new List[6][8]; - - public BasicRootsBlockBakedModel(TextureAtlasSprite barkTexture, TextureAtlasSprite ringsTexture) { - super(barkTexture, ringsTexture); - initRootsModels(); + public BasicRootsBlockBakedModel(BasicBranchBlockBakedModel baked) { + super(baked.cores, baked.sleeves, baked.rings, baked.sleeveRings); } - private void initRootsModels() { - for (int i = 0; i < 8; i++) { - int radius = i + 1; - for (Direction dir : Direction.values()) { - sleeveEndFaces[dir.get3DDataValue()][i] = bakeSleeveFace(radius, dir, ringsTexture); - } + public static BasicRootsBlockBakedModel bakeRoots(ModelBaker baker, Material.Baked barkMat, Material.Baked ringsMat, boolean opaque) { + BasicBranchBlockBakedModel model; + if (opaque) { + model = bakeBasic(baker, + new BranchModelPart.UnbakedCore(barkMat), + new BranchModelPart.UnbakedSleeve(barkMat), + new BranchModelPart.UnbakedCore(ringsMat), + new BranchModelPart.UnbakedRootSleeveEnds(ringsMat)); + } else { + model = bakeBasic(baker, + new BranchModelPart.UnbakedRootCore(barkMat, true), + new BranchModelPart.UnbakedRootSleeve(barkMat), + new BranchModelPart.UnbakedRootCore(ringsMat, false), + null); } - } - - public List bakeSleeveFace(int radius, Direction dir, TextureAtlasSprite rings) { - int dradius = radius * 2; - int halfSize = (16 - dradius) / 2; - float halfSizeX = dir.getStepX() != 0 ? halfSize + Z_FIGHTING_OFFSET : dradius; - float halfSizeY = dir.getStepY() != 0 ? halfSize + Z_FIGHTING_OFFSET : dradius; - float halfSizeZ = dir.getStepZ() != 0 ? halfSize + Z_FIGHTING_OFFSET : dradius; - int move = 16 - halfSize; - int centerX = 16 + (dir.getStepX() * move); - int centerY = 16 + (dir.getStepY() * move); - int centerZ = 16 + (dir.getStepZ() * move); - - Vector3f posFrom = new Vector3f((centerX - halfSizeX) / 2f, (centerY - halfSizeY) / 2f, (centerZ - halfSizeZ) / 2f); - Vector3f posTo = new Vector3f((centerX + halfSizeX) / 2f, (centerY + halfSizeY) / 2f, (centerZ + halfSizeZ) / 2f); - - Map mapFacesIn = Maps.newEnumMap(Direction.class); - BlockFaceUV uvface = new BlockFaceUV(new float[]{8 - radius, 8 - radius, 8 + radius, 8 + radius}, 0); - mapFacesIn.put(dir, new BlockElementFace(dir, -1, "", uvface)); - - BlockElement part = new BlockElement(posFrom, posTo, mapFacesIn, null, true); - List quads = new ArrayList<>(); - FaceBakery faceBakery = new FaceBakery(); - - for (Map.Entry e : part.faces.entrySet()) { - Direction face = e.getKey(); - quads.add(faceBakery.bakeQuad(part.from, part.to, e.getValue(), rings, face, BlockModelRotation.X0_Y0, part.rotation, true)); - } - - return quads; - } - - @Override - protected int getRadius(BlockState blockState) { - if (blockState.getBlock() instanceof BasicRootsBlock) { - if (blockState.hasProperty(BasicRootsBlock.RADIUS)) { - return blockState.getValue(BasicRootsBlock.RADIUS); - } - } - return super.getRadius(blockState); - } - - @Override - public EnumMap> collectQuads(int coreRadius, int[] connections, int twigRadius, Direction forceRingDir) { - int numConnections = 0; - for (int i : connections) { - numConnections += (i != 0) ? 1 : 0; - } - - Direction sourceDir = getSourceDir(coreRadius, connections); - int coreDir = resolveCoreDir(sourceDir); - Direction coreRingDir = (numConnections == 1 && sourceDir != null) ? sourceDir.getOpposite() : null; - - EnumMap> bakedQuads = new EnumMap<>(Direction.class); - - for (Direction face : Direction.values()) { - List quads = bakedQuads.computeIfAbsent(face, dir->new ArrayList<>()); - int connectionOnFace = connections[face.get3DDataValue()]; - if (coreRadius != connectionOnFace) { - if (coreRingDir == null || coreRingDir != face) { - quads.addAll(coresQuads[coreDir][coreRadius - 1]); - } else { - quads.addAll(ringsQuads[coreRadius - 1]); - } - } - - if (coreRadius != 8) { - for (Direction connDir : Direction.values()) { - int idx = connDir.get3DDataValue(); - int connRadius = connections[idx]; - if (connRadius > 0 && connRadius < 8 && (connRadius <= twigRadius || face != connDir)) { - quads.addAll(sleevesQuads[idx][connRadius - 1]); - } - } - } - - if (connectionOnFace > 0 && connectionOnFace <= coreRadius) { - quads.addAll(sleeveEndFaces[face.get3DDataValue()][connectionOnFace - 1]); - } - } - - return bakedQuads; + return new BasicRootsBlockBakedModel(model); } } diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/SurfaceRootBlockBakedModel.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/SurfaceRootBlockBakedModel.java index e7ad85fbf..1659e2f86 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/SurfaceRootBlockBakedModel.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/SurfaceRootBlockBakedModel.java @@ -1,323 +1,158 @@ package com.dtteam.dynamictrees.model.baked; +import com.dtteam.dynamictrees.api.network.Connections; import com.dtteam.dynamictrees.api.network.RootConnections; import com.dtteam.dynamictrees.block.branch.SurfaceRootBlock; +import com.dtteam.dynamictrees.model.BlockStateModelWithConnectionData; +import com.dtteam.dynamictrees.model.FabricDynamicBlockStateModel; +import com.dtteam.dynamictrees.model.ModelHelper; +import com.dtteam.dynamictrees.model.parts.SurfaceRootModelPart; import com.dtteam.dynamictrees.utility.CoordUtils; -import com.google.common.collect.Maps; -import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter; -import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel; -import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; -import net.fabricmc.fabric.api.renderer.v1.material.MaterialFinder; -import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial; -import net.fabricmc.fabric.api.renderer.v1.RendererAccess; -import net.minecraft.client.renderer.block.model.BakedQuad; -import net.minecraft.client.renderer.block.model.BlockElement; -import net.minecraft.client.renderer.block.model.BlockElementFace; -import net.minecraft.client.renderer.block.model.BlockFaceUV; -import net.minecraft.client.renderer.block.model.FaceBakery; -import net.minecraft.client.renderer.block.model.ItemOverrides; -import net.minecraft.client.renderer.block.model.ItemTransforms; -import net.minecraft.client.renderer.texture.TextureAtlasSprite; -import net.minecraft.client.resources.model.BakedModel; -import net.minecraft.client.resources.model.BlockModelRotation; +import net.minecraft.client.renderer.block.BlockAndTintGetter; +import net.minecraft.client.renderer.block.dispatch.BlockStateModelPart; +import net.minecraft.client.resources.model.ModelBaker; +import net.minecraft.client.resources.model.geometry.BakedQuad; +import net.minecraft.client.resources.model.sprite.Material; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; +import net.minecraft.resources.Identifier; import net.minecraft.util.Mth; import net.minecraft.util.RandomSource; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.BlockAndTintGetter; import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.phys.AABB; -import org.apache.commons.lang3.tuple.Pair; import org.jetbrains.annotations.Nullable; -import org.joml.Vector3f; -import java.util.*; -import java.util.function.Supplier; +import java.util.List; -@SuppressWarnings("unchecked") -public class SurfaceRootBlockBakedModel implements BakedModel, FabricBakedModel { +/** + * Dynamic surface root model for Fabric. Mirrors the NeoForge {@code SurfaceRootBlockStateModel}. + */ +public class SurfaceRootBlockBakedModel implements FabricDynamicBlockStateModel, BlockStateModelWithConnectionData { - protected final TextureAtlasSprite barkTexture; + protected final SurfaceRootModelPart[][] cores; + protected final SurfaceRootModelPart[][] sleeves; + protected final SurfaceRootModelPart[][] verts; + protected final Material.Baked particleMaterial; - public final List[][] sleevesQuads = new List[4][7]; - public final List[][] coresQuads = new List[2][8]; - public final List[][] vertsQuads = new List[4][8]; - - public SurfaceRootBlockBakedModel(TextureAtlasSprite barkTexture) { - this.barkTexture = barkTexture; - initModels(); + public SurfaceRootBlockBakedModel(SurfaceRootModelPart[][] cores, SurfaceRootModelPart[][] sleeves, + SurfaceRootModelPart[][] verts, Material.Baked particleMaterial) { + this.cores = cores; + this.sleeves = sleeves; + this.verts = verts; + this.particleMaterial = particleMaterial; } - private void initModels() { + /** + * Bakes a surface root model for the given bark texture. Mirrors the NeoForge + * {@code SurfaceRootBlockStateModel.Unbaked#bake}. + */ + public static SurfaceRootBlockBakedModel bake(ModelBaker baker, Identifier barkTexture) { + SurfaceRootModelPart[][] sleeves = new SurfaceRootModelPart[4][7]; + SurfaceRootModelPart[][] cores = new SurfaceRootModelPart[2][8]; //8 Cores for 2 axis(X, Z) with the bark texture on all 6 sides rotated appropriately. + SurfaceRootModelPart[][] verts = new SurfaceRootModelPart[4][8]; + + Material.Baked barkMat = baker.materials().get(new Material(barkTexture, false), barkTexture::toDebugFileName); + + SurfaceRootModelPart.UnbakedCore unbakedCores = new SurfaceRootModelPart.UnbakedCore(barkMat); + SurfaceRootModelPart.UnbakedSleeve unbakedSleeves = new SurfaceRootModelPart.UnbakedSleeve(barkMat); + SurfaceRootModelPart.UnbakedVert unbakedVerts = new SurfaceRootModelPart.UnbakedVert(barkMat); + for (int r = 0; r < 8; r++) { int radius = r + 1; if (radius < 8) { for (Direction dir : CoordUtils.HORIZONTALS) { int horIndex = dir.get2DDataValue(); - sleevesQuads[horIndex][r] = bakeSleeve(radius, dir); - vertsQuads[horIndex][r] = bakeVert(radius, dir); - } - } - coresQuads[0][r] = bakeCore(radius, Direction.Axis.Z); - coresQuads[1][r] = bakeCore(radius, Direction.Axis.X); - } - } - - public int getRadialHeight(int radius) { - return radius * 2; - } - - public List bakeSleeve(int radius, Direction dir) { - int radialHeight = getRadialHeight(radius); - - int dradius = radius * 2; - int halfSize = (16 - dradius) / 2; - int halfSizeX = dir.getStepX() != 0 ? halfSize : dradius; - int halfSizeZ = dir.getStepZ() != 0 ? halfSize : dradius; - int move = 16 - halfSize; - int centerX = 16 + (dir.getStepX() * move); - int centerZ = 16 + (dir.getStepZ() * move); - - Vector3f posFrom = new Vector3f((centerX - halfSizeX) / 2f, 0, (centerZ - halfSizeZ) / 2f); - Vector3f posTo = new Vector3f((centerX + halfSizeX) / 2f, radialHeight, (centerZ + halfSizeZ) / 2f); - - boolean sleeveNegative = dir.getAxisDirection() == Direction.AxisDirection.NEGATIVE; - if (dir.getAxis() == Direction.Axis.Z) { - sleeveNegative = !sleeveNegative; - } - - Map mapFacesIn = Maps.newEnumMap(Direction.class); - - for (Direction face : Direction.values()) { - if (dir.getOpposite() != face) { - BlockFaceUV uvface; - if (face.getAxis().isHorizontal()) { - boolean facePositive = face.getAxisDirection() == Direction.AxisDirection.POSITIVE; - uvface = new BlockFaceUV(new float[]{facePositive ? 16 - radialHeight : 0, (sleeveNegative ? 16 - halfSize : 0), facePositive ? 16 : radialHeight, (sleeveNegative ? 16 : halfSize)}, getFaceAngle(dir.getAxis(), face)); - } else { - uvface = new BlockFaceUV(new float[]{8 - radius, sleeveNegative ? 16 - halfSize : 0, 8 + radius, sleeveNegative ? 16 : halfSize}, getFaceAngle(dir.getAxis(), face)); + sleeves[horIndex][r] = unbakedSleeves.bake(baker, radius, dir); + verts[horIndex][r] = unbakedVerts.bake(baker, radius, dir); } - mapFacesIn.put(face, new BlockElementFace(null, -1, null, uvface)); - } - } - - BlockElement part = new BlockElement(posFrom, posTo, mapFacesIn, null, true); - List quads = new ArrayList<>(); - FaceBakery faceBakery = new FaceBakery(); - - for (Map.Entry e : part.faces.entrySet()) { - Direction face = e.getKey(); - quads.add(faceBakery.bakeQuad(part.from, part.to, e.getValue(), barkTexture, face, BlockModelRotation.X0_Y0, part.rotation, true)); - } - - return quads; - } - - private List bakeVert(int radius, Direction dir) { - int radialHeight = getRadialHeight(radius); - List quads = new ArrayList<>(); - FaceBakery faceBakery = new FaceBakery(); - - AABB partBoundary = new AABB(8 - radius, radialHeight, 8 - radius, 8 + radius, 16 + radialHeight, 8 + radius) - .move(dir.getStepX() * 7, 0, dir.getStepZ() * 7); - - for (int i = 0; i < 2; i++) { - AABB pieceBoundary = partBoundary.intersect(new AABB(0, 0, 0, 16, 16, 16).move(0, 16 * i, 0)); - - for (Direction face : Direction.values()) { - Map mapFacesIn = Maps.newEnumMap(Direction.class); - - BlockFaceUV uvface = new BlockFaceUV(modUV(getUVs(pieceBoundary, face)), getFaceAngle(Direction.Axis.Y, face)); - mapFacesIn.put(face, new BlockElementFace(null, -1, null, uvface)); - - Vector3f[] limits = AABBLimits(pieceBoundary); - - BlockElement part = new BlockElement(limits[0], limits[1], mapFacesIn, null, true); - quads.add(faceBakery.bakeQuad(part.from, part.to, part.faces.get(face), barkTexture, face, BlockModelRotation.X0_Y0, part.rotation, true)); - } - } - - return quads; - } - - public List bakeCore(int radius, Direction.Axis axis) { - int radialHeight = getRadialHeight(radius); - - Vector3f posFrom = new Vector3f(8 - radius, 0, 8 - radius); - Vector3f posTo = new Vector3f(8 + radius, radialHeight, 8 + radius); - - Map mapFacesIn = Maps.newEnumMap(Direction.class); - - for (Direction face : Direction.values()) { - BlockFaceUV uvface; - if (face.getAxis().isHorizontal()) { - boolean positive = face.getAxisDirection() == Direction.AxisDirection.POSITIVE; - uvface = new BlockFaceUV(new float[]{positive ? 16 - radialHeight : 0, 8 - radius, positive ? 16 : radialHeight, 8 + radius}, getFaceAngle(axis, face)); - } else { - uvface = new BlockFaceUV(new float[]{8 - radius, 8 - radius, 8 + radius, 8 + radius}, getFaceAngle(axis, face)); } - - mapFacesIn.put(face, new BlockElementFace(null, -1, null, uvface)); - } - - BlockElement part = new BlockElement(posFrom, posTo, mapFacesIn, null, true); - List quads = new ArrayList<>(); - FaceBakery faceBakery = new FaceBakery(); - - for (Map.Entry e : part.faces.entrySet()) { - Direction face = e.getKey(); - quads.add(faceBakery.bakeQuad(part.from, part.to, e.getValue(), barkTexture, face, BlockModelRotation.X0_Y0, part.rotation, true)); - } - - return quads; - } - - public int getFaceAngle(Direction.Axis axis, Direction face) { - if (axis == Direction.Axis.Y) { - return 0; - } else if (axis == Direction.Axis.Z) { - return switch (face) { - case UP -> 0; - case WEST, NORTH -> 270; - case DOWN -> 180; - default -> 90; - }; - } else { - return (face == Direction.NORTH) ? 270 : 90; + cores[0][r] = unbakedCores.bake(baker, radius, Direction.Axis.Z); //NORTH<->SOUTH + cores[1][r] = unbakedCores.bake(baker, radius, Direction.Axis.X); //WEST<->EAST } - } - public float[] getUVs(AABB box, Direction face) { - return switch (face) { - case UP -> new float[]{(float) box.minX, (float) box.minZ, (float) box.maxX, (float) box.maxZ}; - case NORTH -> new float[]{16f - (float) box.maxX, (float) box.minY, 16f - (float) box.minX, (float) box.maxY}; - case SOUTH -> new float[]{(float) box.minX, (float) box.minY, (float) box.maxX, (float) box.maxY}; - case WEST -> new float[]{(float) box.minZ, (float) box.minY, (float) box.maxZ, (float) box.maxY}; - case EAST -> new float[]{16f - (float) box.maxZ, (float) box.minY, 16f - (float) box.minZ, (float) box.maxY}; - default -> new float[]{(float) box.minX, 16f - (float) box.minZ, (float) box.maxX, 16f - (float) box.maxZ}; - }; + return new SurfaceRootBlockBakedModel(cores, sleeves, verts, barkMat); } - public float[] modUV(float[] uvs) { - uvs[0] = (int) uvs[0] & 0xf; - uvs[1] = (int) uvs[1] & 0xf; - uvs[2] = (((int) uvs[2] - 1) & 0xf) + 1; - uvs[3] = (((int) uvs[3] - 1) & 0xf) + 1; - return uvs; - } - - public Vector3f[] AABBLimits(AABB aabb) { - return new Vector3f[]{ - new Vector3f((float) aabb.minX, (float) aabb.minY, (float) aabb.minZ), - new Vector3f((float) aabb.maxX, (float) aabb.maxY, (float) aabb.maxZ), - }; - } + /////////////////////////////////////////// + // FABRIC DYNAMIC GEOMETRY + /////////////////////////////////////////// @Override - public boolean isVanillaAdapter() { - return false; + public void collectParts(BlockAndTintGetter level, BlockPos pos, BlockState state, RandomSource random, List parts) { + collectParts(state, parts, ModelHelper.getRootConnections(level, pos, state)); } @Override - public void emitBlockQuads(BlockAndTintGetter blockView, BlockState state, BlockPos pos, Supplier randomSupplier, RenderContext context) { - EnumMap> bakedQuads = collectQuads(blockView, state, pos); - if (bakedQuads == null) return; - - QuadEmitter emitter = context.getEmitter(); - RenderMaterial material = getRenderMaterial(); - if (material == null) return; - - bakedQuads.forEach((dir, quads) -> - emitQuads(dir, emitter, material, quads)); + public Object createGeometryKey(BlockAndTintGetter level, BlockPos pos, BlockState state, RandomSource random) { + return ModelHelper.getRootConnections(level, pos, state); } - @Nullable - public EnumMap> collectQuads(BlockAndTintGetter getter, BlockState state, BlockPos pos) { - if (state == null) return null; + /////////////////////////////////////////// + // PART COLLECTION + /////////////////////////////////////////// - int coreRadius = getRadius(state); - if (coreRadius <= 0 || coreRadius > 8) return null; + @Override + public void collectParts(BlockState state, List parts, Connections connectionsData) { + int coreRadius = 0; + if (state.getBlock() instanceof SurfaceRootBlock root) { + coreRadius = root.getRadius(state); + } + if (coreRadius == 0) return; int[] connections = new int[]{0, 0, 0, 0}; RootConnections.ConnectionLevel[] connectionLevels = RootConnections.PLACEHOLDER_CONNECTION_LEVELS.clone(); - if (state.getBlock() instanceof SurfaceRootBlock surfaceRootBlock) { - RootConnections connectionData = surfaceRootBlock.getConnectionData(getter, pos); - connections = connectionData.getAllRadii(); - connectionLevels = connectionData.getConnectionLevels(); + if (connectionsData instanceof RootConnections rootConnections) { + connections = rootConnections.getAllRadii(); + connectionLevels = rootConnections.getConnectionLevels(); } for (int i = 0; i < connections.length; i++) { connections[i] = Mth.clamp(connections[i], 0, coreRadius); } - Direction sourceDir = getSourceDir(coreRadius, connections); + //The source direction is the biggest connection from one of the horizontal directions + Direction sourceDir = get2DSourceDir(coreRadius, connections); if (sourceDir == null) { sourceDir = Direction.DOWN; } int coreDir = resolveCoreDir(sourceDir); boolean isGrounded = state.getValue(SurfaceRootBlock.GROUNDED); + if (isGrounded) { + parts.add(cores[coreDir][coreRadius - 1]); + } - EnumMap> bakedQuads = new EnumMap<>(Direction.class); - - for (Direction face : Direction.values()) { - List quads = bakedQuads.computeIfAbsent(face, dir->new ArrayList<>()); - if (isGrounded) { - quads.addAll(coresQuads[coreDir][coreRadius - 1]); - } - - if (coreRadius != 8) { - for (Direction connDir : CoordUtils.HORIZONTALS) { - int idx = connDir.get2DDataValue(); - int connRadius = connections[idx]; - if (connRadius > 0) { - if (isGrounded && sleevesQuads[idx][connRadius - 1] != null) { - quads.addAll(sleevesQuads[idx][connRadius - 1]); - } - if (connectionLevels[idx] == RootConnections.ConnectionLevel.HIGH && vertsQuads[idx][connRadius - 1] != null) { - quads.addAll(vertsQuads[idx][connRadius - 1]); - } + //Get quads for sleeves models + if (coreRadius != 8) { //Special case for r!=8.. If it's a solid block so it has no sleeves + for (Direction connDir : CoordUtils.HORIZONTALS) { + int idx = connDir.get2DDataValue(); + int connRadius = connections[idx]; + if (connRadius > 0) { + if (isGrounded && sleeves[idx][connRadius - 1] != null) { + parts.add(sleeves[idx][connRadius - 1]); + } + if (connectionLevels[idx] == RootConnections.ConnectionLevel.HIGH && verts[idx][connRadius - 1] != null) { + parts.add(verts[idx][connRadius - 1]); } } } } - return bakedQuads; - } - - protected static void emitQuads(Direction face, QuadEmitter emitter, RenderMaterial material, List quads) { - if (quads == null) return; - for (BakedQuad quad : quads) { - if (quad.getDirection() == face) { - emitter.fromVanilla(quad, material, null); - emitter.emit(); - } - } - } - - protected static RenderMaterial getRenderMaterial() { - var renderer = RendererAccess.INSTANCE.getRenderer(); - if (renderer == null) return null; - MaterialFinder finder = renderer.materialFinder(); - -// finder.disableAo(0, true); -// finder.disableDiffuse(0, true); - - return finder.find(); } - @Override - public void emitItemQuads(ItemStack stack, Supplier randomSupplier, RenderContext context) { + /** + * Converts direction DUNSWE to 2 axis numbers for Z,X + */ + protected int resolveCoreDir(Direction dir) { + return dir.getAxis() == Direction.Axis.X ? 1 : 0; } - protected Direction getSourceDir(int coreRadius, int[] connections) { + @Nullable + public static Direction get2DSourceDir(int coreRadius, int[] connections) { int largestConnection = 0; Direction sourceDir = null; for (Direction dir : CoordUtils.HORIZONTALS) { - int horIndex = dir.get2DDataValue(); - int connRadius = connections[horIndex]; + int connRadius = connections[dir.get2DDataValue()]; if (connRadius > largestConnection) { largestConnection = connRadius; sourceDir = dir; @@ -325,56 +160,22 @@ protected Direction getSourceDir(int coreRadius, int[] connections) { } if (largestConnection < coreRadius) { - sourceDir = null; + sourceDir = null; //Has no source node } return sourceDir; } - protected int resolveCoreDir(Direction dir) { - return dir.getAxis() == Direction.Axis.X ? 1 : 0; - } - - protected int getRadius(BlockState blockState) { - return ((SurfaceRootBlock) blockState.getBlock()).getRadius(blockState); - } - - @Override - public List getQuads(@Nullable BlockState state, @Nullable Direction direction, RandomSource random) { - return Collections.emptyList(); - } - - @Override - public boolean useAmbientOcclusion() { - return true; - } - - @Override - public boolean isGui3d() { - return false; - } - - @Override - public boolean usesBlockLight() { - return false; - } - - @Override - public boolean isCustomRenderer() { - return true; - } - - @Override - public TextureAtlasSprite getParticleIcon() { - return barkTexture; - } + /////////////////////////////////////////// + // VANILLA MODEL METHODS + /////////////////////////////////////////// @Override - public ItemTransforms getTransforms() { - return ItemTransforms.NO_TRANSFORMS; + public Material.Baked particleMaterial() { + return particleMaterial; } @Override - public ItemOverrides getOverrides() { - return ItemOverrides.EMPTY; + public @BakedQuad.MaterialFlags int materialFlags() { + return this.cores[0][0].materialFlags(); } } diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/ThickBranchBlockBakedModel.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/ThickBranchBlockBakedModel.java index 8616a2817..a53f60c29 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/ThickBranchBlockBakedModel.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/ThickBranchBlockBakedModel.java @@ -1,217 +1,97 @@ package com.dtteam.dynamictrees.model.baked; +import com.dtteam.dynamictrees.api.network.Connections; import com.dtteam.dynamictrees.block.branch.BranchBlock; import com.dtteam.dynamictrees.block.branch.ThickBranchBlock; -import com.dtteam.dynamictrees.utility.CoordUtils; -import com.dtteam.dynamictrees.utility.CoordUtils.Surround; -import com.google.common.collect.Maps; -import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter; -import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; -import net.fabricmc.fabric.api.renderer.v1.material.MaterialFinder; -import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial; -import net.fabricmc.fabric.api.renderer.v1.RendererAccess; -import net.minecraft.client.renderer.block.model.BakedQuad; -import net.minecraft.client.renderer.block.model.BlockElement; -import net.minecraft.client.renderer.block.model.BlockElementFace; -import net.minecraft.client.renderer.block.model.BlockFaceUV; -import net.minecraft.client.renderer.block.model.FaceBakery; -import net.minecraft.client.renderer.texture.TextureAtlasSprite; -import net.minecraft.client.resources.model.BlockModelRotation; -import net.minecraft.core.BlockPos; +import com.dtteam.dynamictrees.model.BranchMultiPartHolder; +import com.dtteam.dynamictrees.model.ModelConnections; +import com.dtteam.dynamictrees.model.parts.BranchModelPart; +import com.dtteam.dynamictrees.tree.TreeHelper; +import net.minecraft.client.renderer.block.dispatch.BlockStateModelPart; +import net.minecraft.client.resources.model.ModelBaker; import net.minecraft.core.Direction; -import net.minecraft.core.Direction.Axis; -import net.minecraft.core.Vec3i; import net.minecraft.util.Mth; -import net.minecraft.util.RandomSource; -import net.minecraft.world.level.BlockAndTintGetter; import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.phys.AABB; -import net.minecraft.world.phys.Vec3; -import org.joml.Vector3f; -import java.util.ArrayList; -import java.util.EnumMap; +import java.util.EnumSet; import java.util.List; -import java.util.Map; -import java.util.function.Supplier; -@SuppressWarnings("unchecked") +/** + * Dynamic thick branch (trunk) model for Fabric. Mirrors the NeoForge {@code ThickBranchBlockStateModel}. + */ public class ThickBranchBlockBakedModel extends BasicBranchBlockBakedModel { - private final TextureAtlasSprite thickRingsTexture; - private final List[] trunksBarkQuads = new List[16]; - private final List[] trunksTopBarkQuads = new List[16]; - private final List[] trunksTopRingsQuads = new List[16]; - public final List[] trunksBotRingsQuads = new List[16]; + private final BranchMultiPartHolder trunkBark; // The trunk will always feature bark on its sides. + private final BranchMultiPartHolder trunkRings; // The trunk will feature rings on its top and bottom. - public ThickBranchBlockBakedModel(TextureAtlasSprite barkTexture, TextureAtlasSprite ringsTexture, TextureAtlasSprite thickRingsTexture) { - super(barkTexture, ringsTexture); - this.thickRingsTexture = thickRingsTexture; - initThickModels(); + public ThickBranchBlockBakedModel(BasicBranchBlockBakedModel fallback, + BranchMultiPartHolder trunkBark, BranchMultiPartHolder trunkRings) { + super(fallback.cores, fallback.sleeves, fallback.rings, fallback.sleeveRings); + this.trunkBark = trunkBark; + this.trunkRings = trunkRings; } - private void initThickModels() { - for (int i = 0; i < ThickBranchBlock.MAX_RADIUS_THICK - ThickBranchBlock.MAX_RADIUS; i++) { - int radius = i + ThickBranchBlock.MAX_RADIUS + 1; - trunksBarkQuads[i] = bakeTrunkBark(radius, this.barkTexture, true); - trunksTopBarkQuads[i] = bakeTrunkBark(radius, this.barkTexture, false); - trunksTopRingsQuads[i] = bakeTrunkRings(radius, thickRingsTexture, Direction.UP); - trunksBotRingsQuads[i] = bakeTrunkRings(radius, thickRingsTexture, Direction.DOWN); - } - } - - public List bakeTrunkBark(int radius, TextureAtlasSprite bark, boolean side) { - List quads = new ArrayList<>(); - FaceBakery faceBakery = new FaceBakery(); - AABB wholeVolume = new AABB(8 - radius, 0, 8 - radius, 8 + radius, 16, 8 + radius); - - final Direction[] run = side ? CoordUtils.HORIZONTALS : new Direction[]{Direction.UP, Direction.DOWN}; - ArrayList offsets = new ArrayList<>(); - - for (Surround dir : Surround.values()) { - offsets.add(dir.getOffset()); - } - offsets.add(new Vec3i(0, 0, 0)); - - for (Direction face : run) { - final Vec3i dirVector = face.getNormal(); - - for (Vec3i offset : offsets) { - if (face.getAxis() == Axis.Y || new Vec3(dirVector.getX(), dirVector.getY(), dirVector.getZ()).add(new Vec3(offset.getX(), offset.getY(), offset.getZ())).lengthSqr() > 2.25) { - Vec3 scaledOffset = new Vec3(offset.getX() * 16, offset.getY() * 16, offset.getZ() * 16); - AABB partBoundary = new AABB(0, 0, 0, 16, 16, 16).move(scaledOffset).intersect(wholeVolume); - - Vector3f[] limits = aabbLimits(partBoundary); - - Map mapFacesIn = Maps.newEnumMap(Direction.class); - - BlockFaceUV uvface = new BlockFaceUV(modUV(getUVs(partBoundary, face)), getFaceAngle(Axis.Y, face)); - mapFacesIn.put(face, new BlockElementFace(null, -1, null, uvface)); - - BlockElement part = new BlockElement(limits[0], limits[1], mapFacesIn, null, true); - quads.add(faceBakery.bakeQuad(part.from, part.to, part.faces.get(face), bark, face, BlockModelRotation.X0_Y0, part.rotation, true)); - } - } + /** + * Bakes a thick branch model out of the given unbaked trunk parts and a regular branch fallback. + * Mirrors the NeoForge {@code UnbakedBranchModel#bakeThick}. + */ + public static ThickBranchBlockBakedModel bakeThick( + ModelBaker baker, BasicBranchBlockBakedModel fallback, + BranchModelPart.UnbakedThickTrunk unbakedBark, BranchModelPart.UnbakedThickTrunk unbakedRings) { + BranchMultiPartHolder trunksBark = new BranchMultiPartHolder(); + BranchMultiPartHolder trunksRings = new BranchMultiPartHolder(); + + for (int radius = BranchBlock.MAX_RADIUS + 1; radius <= ThickBranchBlock.MAX_RADIUS_THICK; radius++) { + trunksBark.putAllParts(radius, unbakedBark.bakeAllSides(baker, radius)); + trunksRings.putAllParts(radius, unbakedRings.bakeSides(baker, radius, EnumSet.of(Direction.UP, Direction.DOWN))); } - return quads; + return new ThickBranchBlockBakedModel(fallback, trunksBark, trunksRings); } - public List bakeTrunkRings(int radius, TextureAtlasSprite ring, Direction face) { - List quads = new ArrayList<>(); - FaceBakery faceBakery = new FaceBakery(); - AABB wholeVolume = new AABB(8 - radius, 0, 8 - radius, 8 + radius, 16, 8 + radius); - int wholeVolumeWidth = 48; - - ArrayList offsets = new ArrayList<>(); - - for (Surround dir : Surround.values()) { - offsets.add(dir.getOffset()); + @Override + public void collectParts(BlockState state, List parts, Connections connectionsData) { + int coreRadius = TreeHelper.getRadius(state); + if (coreRadius <= BranchBlock.MAX_RADIUS) { + super.collectParts(state, parts, connectionsData); + return; } - offsets.add(new Vec3i(0, 0, 0)); + coreRadius = Mth.clamp(coreRadius, BranchBlock.MAX_RADIUS + 1, ThickBranchBlock.MAX_RADIUS_THICK); + if (!(connectionsData instanceof ModelConnections modelConnections)) return; - for (Vec3i offset : offsets) { - Vec3 scaledOffset = new Vec3(offset.getX() * 16, offset.getY() * 16, offset.getZ() * 16); - AABB partBoundary = new AABB(0, 0, 0, 16, 16, 16).move(scaledOffset).intersect(wholeVolume); + final int[] connections = connectionsData.getAllRadii(); + final Direction forceRingDir = modelConnections.getRingOnly(); + final int twigRadius = modelConnections.getFamily().getPrimaryThickness(); - Vector3f posFrom = new Vector3f((float) partBoundary.minX, (float) partBoundary.minY, (float) partBoundary.minZ); - Vector3f posTo = new Vector3f((float) partBoundary.maxX, (float) partBoundary.maxY, (float) partBoundary.maxZ); + int numConnections = countConnections(connections); - Map mapFacesIn = Maps.newEnumMap(Direction.class); - float[] uvs = getRingsUvs(face, partBoundary, wholeVolumeWidth); + if (numConnections == 0 && forceRingDir != null) return; - BlockFaceUV uvFace = new BlockFaceUV(uvs, getFaceAngle(Axis.Y, face)); - mapFacesIn.put(face, new BlockElementFace(null, -1, null, uvFace)); - - BlockElement part = new BlockElement(posFrom, posTo, mapFacesIn, null, true); - quads.add(faceBakery.bakeQuad(part.from, part.to, part.faces.get(face), ring, face, BlockModelRotation.X0_Y0, part.rotation, true)); + if (forceRingDir != null) { + connections[forceRingDir.get3DDataValue()] = 0; + addPart(parts, trunkRings.getPart(forceRingDir, coreRadius)); } - return quads; - } - - private static float[] getRingsUvs(Direction face, AABB partBoundary, int wholeVolumeWidth) { - float textureOffsetX = -16f; - float textureOffsetZ = -16f; + boolean branchesAround = areBranchesAround(connections); - float minX = ((float) ((partBoundary.minX - textureOffsetX) / wholeVolumeWidth)) * 16f; - float maxX = ((float) ((partBoundary.maxX - textureOffsetX) / wholeVolumeWidth)) * 16f; - float minZ = ((float) ((partBoundary.minZ - textureOffsetZ) / wholeVolumeWidth)) * 16f; - float maxZ = ((float) ((partBoundary.maxZ - textureOffsetZ) / wholeVolumeWidth)) * 16f; - - if (face == Direction.DOWN) { - minZ = ((float) ((partBoundary.maxZ - textureOffsetZ) / wholeVolumeWidth)) * 16f; - maxZ = ((float) ((partBoundary.minZ - textureOffsetZ) / wholeVolumeWidth)) * 16f; + for (Direction face : Direction.values()) { + gatherTrunkParts(parts, face, connections, twigRadius, branchesAround, coreRadius); } - - return new float[]{minX, minZ, maxX, maxZ}; - } - - public static float[] getUVs(AABB box, Direction face) { - return switch (face) { - case UP -> new float[]{(float) box.minX, (float) box.minZ, (float) box.maxX, (float) box.maxZ}; - case NORTH -> new float[]{16f - (float) box.maxX, (float) box.minY, 16f - (float) box.minX, (float) box.maxY}; - case SOUTH -> new float[]{(float) box.minX, (float) box.minY, (float) box.maxX, (float) box.maxY}; - case WEST -> new float[]{(float) box.minZ, (float) box.minY, (float) box.maxZ, (float) box.maxY}; - case EAST -> new float[]{16f - (float) box.maxZ, (float) box.minY, 16f - (float) box.minZ, (float) box.maxY}; - default -> new float[]{(float) box.minX, 16f - (float) box.minZ, (float) box.maxX, 16f - (float) box.maxZ}; - }; } - public static float[] modUV(float[] uvs) { - uvs[0] = (int) uvs[0] & 0xf; - uvs[1] = (int) uvs[1] & 0xf; - uvs[2] = (((int) uvs[2] - 1) & 0xf) + 1; - uvs[3] = (((int) uvs[3] - 1) & 0xf) + 1; - return uvs; + private static boolean areBranchesAround(int[] connections) { + return connections[2] + connections[3] + connections[4] + connections[5] != 0; } - public static Vector3f[] aabbLimits(AABB aabb) { - return new Vector3f[]{ - new Vector3f((float) aabb.minX, (float) aabb.minY, (float) aabb.minZ), - new Vector3f((float) aabb.maxX, (float) aabb.maxY, (float) aabb.maxZ), - }; - } - - @Override - protected int maxBranchRadius() { - return ThickBranchBlock.MAX_RADIUS_THICK; - } - - @Override - public EnumMap> collectQuads(int coreRadius, int[] connections, int twigRadius, Direction forceRingDir) { - if (coreRadius <= BranchBlock.MAX_RADIUS) { - return super.collectQuads(coreRadius, connections, twigRadius, forceRingDir); - } - - boolean branchesAround = connections[2] + connections[3] + connections[4] + connections[5] != 0; - - int radiusIndex = coreRadius - BranchBlock.MAX_RADIUS - 1; - if (radiusIndex >= trunksBarkQuads.length) return null; - - EnumMap> bakedQuads = new EnumMap<>(Direction.class); - - for (Direction face : Direction.values()) { - List quads = bakedQuads.computeIfAbsent(face, dir->new ArrayList<>()); - quads.addAll(trunksBarkQuads[radiusIndex]); - - if (face == Direction.UP || face == Direction.DOWN) { - if (connections[face.get3DDataValue()] < twigRadius && !branchesAround) { - quads.addAll(trunksTopRingsQuads[radiusIndex]); - } else if (connections[face.get3DDataValue()] < coreRadius) { - quads.addAll(trunksTopBarkQuads[radiusIndex]); - } + private void gatherTrunkParts(List parts, Direction face, int[] connections, int twigRadius, boolean branchesAround, int coreRadius) { + if (face == Direction.UP || face == Direction.DOWN) { + if (connections[face.get3DDataValue()] < twigRadius && !branchesAround) { + addPart(parts, this.trunkRings.getPart(face, coreRadius)); + } else if (connections[face.get3DDataValue()] < coreRadius) { + addPart(parts, this.trunkBark.getPart(face, coreRadius)); } + } else { + addPart(parts, trunkBark.getPart(face, coreRadius)); } - - return bakedQuads; - } - - @Override - public List getRingQuads(int radius) { - if (radius <= BranchBlock.MAX_RADIUS){ - return super.getRingQuads(radius); - } - return trunksBotRingsQuads[radius - BranchBlock.MAX_RADIUS - 1]; } } diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/AerialRootsSoilBlockStateModel.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/AerialRootsSoilBlockStateModel.java new file mode 100644 index 000000000..fe8493ca4 --- /dev/null +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/AerialRootsSoilBlockStateModel.java @@ -0,0 +1,117 @@ +package com.dtteam.dynamictrees.model.blockstate; + +import com.dtteam.dynamictrees.model.BlockStateModelWithRadius; +import com.dtteam.dynamictrees.model.FabricDynamicBlockStateModel; +import com.dtteam.dynamictrees.model.parts.AerialRootSoilModelPart; +import com.dtteam.dynamictrees.tree.TreeHelper; +import com.dtteam.dynamictrees.tree.family.Family; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.fabricmc.fabric.api.client.model.loading.v1.CustomUnbakedBlockStateModel; +import net.minecraft.client.renderer.block.BlockAndTintGetter; +import net.minecraft.client.renderer.block.dispatch.BlockStateModel; +import net.minecraft.client.renderer.block.dispatch.BlockStateModelPart; +import net.minecraft.client.resources.model.ModelBaker; +import net.minecraft.client.resources.model.ResolvableModel; +import net.minecraft.client.resources.model.geometry.BakedQuad; +import net.minecraft.client.resources.model.sprite.Material; +import net.minecraft.core.BlockPos; +import net.minecraft.resources.Identifier; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.block.state.BlockState; + +import java.util.List; +import java.util.Optional; + +/** + * Fabric port of the NeoForge {@code AerialRootsSoilBlockStateModel}; deserialized from + * blockstate JSONs with type {@code dynamictrees:aerial_roots_soil}. + */ +public record AerialRootsSoilBlockStateModel( + AerialRootSoilModelPart[] soilParts +) implements FabricDynamicBlockStateModel, BlockStateModelWithRadius { + + private record RadiusGeometryKey(AerialRootsSoilBlockStateModel model, int radius) {} + + @Override + public Object createGeometryKey(BlockAndTintGetter level, BlockPos pos, BlockState state, RandomSource random) { + return new RadiusGeometryKey(this, TreeHelper.getRadius(state)); + } + + @Override + public void collectParts(BlockState state, List parts, int radius) { + if (radius == 0 || radius > 8) return; + parts.add(soilParts[radius - 1]); + } + + @Override + public void collectParts(BlockAndTintGetter level, BlockPos pos, BlockState state, RandomSource random, List parts) { + collectParts(state, parts, TreeHelper.getRadius(state)); + } + + public record Unbaked(Identifier end, Identifier overlay, Identifier overlay_end, Identifier side, Optional family) implements CustomUnbakedBlockStateModel { + + public static final String END_TEXTURE = "end"; + public static final String OVERLAY_TEXTURE = "overlay"; + public static final String OVERLAY_END_TEXTURE = "overlay_end"; + public static final String SIDE_TEXTURE = "side"; + public static final String TEXTURES = "textures"; + public static final String FAMILY = "family"; + + private record RootsSoilTextures(Identifier end, Identifier overlay, Identifier overlay_end, Identifier side) { + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(i -> i.group( + Identifier.CODEC.fieldOf(END_TEXTURE).forGetter(RootsSoilTextures::end), + Identifier.CODEC.fieldOf(OVERLAY_TEXTURE).forGetter(RootsSoilTextures::overlay), + Identifier.CODEC.fieldOf(OVERLAY_END_TEXTURE).forGetter(RootsSoilTextures::overlay_end), + Identifier.CODEC.fieldOf(SIDE_TEXTURE).forGetter(RootsSoilTextures::side) + ).apply(i, RootsSoilTextures::new)); + } + + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(i -> i.group( + RootsSoilTextures.CODEC.codec().fieldOf(TEXTURES).forGetter(m -> + new RootsSoilTextures(m.end(), m.overlay(), m.overlay_end(), m.side())), + Family.CODEC.optionalFieldOf(FAMILY).forGetter(Unbaked::family) + ).apply(i, (textures, family) -> + new Unbaked(textures.end(), textures.overlay(), textures.overlay_end(), textures.side(), family))); + + @Override + public MapCodec codec() { + return CODEC; + } + + @Override + public BlockStateModel bake(ModelBaker baker) { + AerialRootSoilModelPart[] soilParts = new AerialRootSoilModelPart[8]; + + Material.Baked endMat = bakeMaterial(baker, end); + Material.Baked overlayMat = bakeMaterial(baker, overlay); + Material.Baked overlayEndMat = bakeMaterial(baker, overlay_end); + Material.Baked sideMat = bakeMaterial(baker, side); + + AerialRootSoilModelPart.UnbakedPart unbakedPart = new AerialRootSoilModelPart.UnbakedPart(endMat, overlayMat, overlayEndMat, sideMat); + + for (int i = 0; i < 8; i++) { + soilParts[i] = unbakedPart.bake(baker, i + 1); + } + + return new AerialRootsSoilBlockStateModel(soilParts); + } + + private Material.Baked bakeMaterial(ModelBaker baker, Identifier texture) { + return baker.materials().get(new Material(texture, false), texture::toDebugFileName); + } + + @Override + public void resolveDependencies(ResolvableModel.Resolver resolver) {} + } + + @Override + public @BakedQuad.MaterialFlags int materialFlags() { + return soilParts[0].materialFlags(); + } + + @Override + public Material.Baked particleMaterial() { + return soilParts[0].particleMaterial(); + } +} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/PottedSaplingBlockStateModel.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/PottedSaplingBlockStateModel.java new file mode 100644 index 000000000..4b9680855 --- /dev/null +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/PottedSaplingBlockStateModel.java @@ -0,0 +1,138 @@ +package com.dtteam.dynamictrees.model.blockstate; + +import com.dtteam.dynamictrees.block.sapling.PottedSaplingBlockEntity; +import com.dtteam.dynamictrees.model.FabricDynamicBlockStateModel; +import com.dtteam.dynamictrees.model.ModelHelper; +import com.dtteam.dynamictrees.tree.species.Species; +import com.mojang.math.Transformation; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.fabricmc.fabric.api.client.model.loading.v1.CustomUnbakedBlockStateModel; +import net.minecraft.client.renderer.block.BlockAndTintGetter; +import net.minecraft.client.renderer.block.dispatch.BlockStateModel; +import net.minecraft.client.renderer.block.dispatch.BlockStateModelPart; +import net.minecraft.client.renderer.block.dispatch.ModelState; +import net.minecraft.client.resources.model.ModelBaker; +import net.minecraft.client.resources.model.ResolvableModel; +import net.minecraft.client.resources.model.ResolvedModel; +import net.minecraft.client.resources.model.SimpleModelWrapper; +import net.minecraft.client.resources.model.geometry.BakedQuad; +import net.minecraft.client.resources.model.sprite.Material; +import net.minecraft.client.resources.model.sprite.TextureSlots; +import net.minecraft.core.BlockPos; +import net.minecraft.resources.Identifier; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.block.state.BlockState; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.joml.Vector3f; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Fabric port of the NeoForge {@code PottedSaplingBlockStateModel}; deserialized from blockstate + * JSONs with type {@code dynamictrees:potted_dynamic_sapling}. Retrieves the potted species from + * the block entity (NeoForge uses model data instead). + */ +public record PottedSaplingBlockStateModel( + BlockStateModelPart pot, + Map saplings, + Material.Baked particleMaterial +) implements FabricDynamicBlockStateModel { + + @Override + public @BakedQuad.MaterialFlags int materialFlags() { + return this.pot.materialFlags(); + } + + @Nullable + private static Species getSpecies(BlockAndTintGetter level, BlockPos pos) { + return level.getBlockEntity(pos) instanceof PottedSaplingBlockEntity pottedSapling + ? pottedSapling.getSpecies() : null; + } + + @Override + public Object createGeometryKey(BlockAndTintGetter level, BlockPos pos, BlockState state, RandomSource random) { + return getSpecies(level, pos); + } + + @Override + public void collectParts(BlockAndTintGetter level, BlockPos pos, BlockState state, RandomSource random, List parts) { + Species species = getSpecies(level, pos); + if (species == null || !species.isValid() || species.getSapling().isEmpty()) return; + + SimpleModelWrapper sapling = saplings.get(species); + if (sapling == null) return; + + parts.add(pot); + parts.add(sapling); + } + + public record Unbaked(Identifier modelLocation) implements CustomUnbakedBlockStateModel { + + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(i -> i.group( + Identifier.CODEC.fieldOf("model").forGetter(Unbaked::modelLocation) + ).apply(i, Unbaked::new)); + + @Override + public MapCodec codec() { + return CODEC; + } + + @Override + public BlockStateModel bake(ModelBaker modelBaker) { + ResolvedModel pot = modelBaker.getModel(modelLocation); + TextureSlots potSlots = pot.getTopTextureSlots(); + Material.Baked material = pot.resolveParticleMaterial(potSlots, modelBaker); + + SimpleModelWrapper bakedPot = new SimpleModelWrapper( + pot.bakeTopGeometry(potSlots, modelBaker, ModelHelper.noState()), + pot.getTopAmbientOcclusion(), + material + ); + + Map saplings = new HashMap<>(); + + for (Species species : Species.REGISTRY) { + if (species.getSapling().isPresent()) { + Identifier saplingModelLocation = species.getSaplingModelLocation(); + ResolvedModel resolved = modelBaker.getModel(saplingModelLocation); + + TextureSlots saplingSlots = resolved.getTopTextureSlots(); + saplings.put(species, new SimpleModelWrapper( + resolved.bakeTopGeometry(saplingSlots, modelBaker, OFFSET_UP), + resolved.getTopAmbientOcclusion(), + resolved.resolveParticleMaterial(saplingSlots, modelBaker))); + } + } + + return new PottedSaplingBlockStateModel(bakedPot, saplings, material); + } + + @Override + public void resolveDependencies(ResolvableModel.Resolver resolver) { + resolver.markDependency(this.modelLocation); + // Also mark the sapling models so bake() can safely resolve them. + for (Species species : Species.REGISTRY) { + if (species.getSapling().isPresent()) { + resolver.markDependency(species.getSaplingModelLocation()); + } + } + } + } + + public static final ModelState OFFSET_UP = new ModelState() { + private static final Transformation TRANSFORM = new Transformation( + new Vector3f(0f, 0.25f, 0f), + null, null, null + ); + + @Override + @NotNull + public Transformation transformation() { + return TRANSFORM; + } + }; +} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/UnbakedBranchModel.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/UnbakedBranchModel.java new file mode 100644 index 000000000..d17f9a023 --- /dev/null +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/UnbakedBranchModel.java @@ -0,0 +1,79 @@ +package com.dtteam.dynamictrees.model.blockstate; + +import com.dtteam.dynamictrees.DynamicTrees; +import com.dtteam.dynamictrees.model.baked.BasicBranchBlockBakedModel; +import com.dtteam.dynamictrees.model.baked.ThickBranchBlockBakedModel; +import com.dtteam.dynamictrees.model.parts.BranchModelPart; +import com.dtteam.dynamictrees.tree.family.Family; +import com.dtteam.dynamictrees.utility.IdentifierUtils; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.fabricmc.fabric.api.client.model.loading.v1.CustomUnbakedBlockStateModel; +import net.minecraft.client.renderer.block.dispatch.BlockStateModel; +import net.minecraft.client.resources.model.ModelBaker; +import net.minecraft.client.resources.model.ResolvableModel; +import net.minecraft.client.resources.model.sprite.Material; +import net.minecraft.resources.Identifier; +import org.jetbrains.annotations.NotNull; + +import java.util.Optional; + +/** + * Fabric port of the NeoForge {@code UnbakedBranchModel}; deserialized from blockstate JSONs + * with type {@code dynamictrees:branch}. + */ +public record UnbakedBranchModel(Identifier barkTexture, Identifier ringsTexture, Optional family) implements CustomUnbakedBlockStateModel { + + public static final String BARK_TEXTURE = "bark"; + public static final String RINGS_TEXTURE = "rings"; + public static final String TEXTURES = "textures"; + public static final String FAMILY = "family"; + + private record BranchTextures(Identifier bark, Identifier rings) { + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(i -> i.group( + Identifier.CODEC.fieldOf(BARK_TEXTURE).forGetter(BranchTextures::bark), + Identifier.CODEC.fieldOf(RINGS_TEXTURE).forGetter(BranchTextures::rings) + ).apply(i, BranchTextures::new)); + } + + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(i -> i.group( + BranchTextures.CODEC.codec().fieldOf(TEXTURES).forGetter(m -> new BranchTextures(m.barkTexture(), m.ringsTexture())), + Family.CODEC.optionalFieldOf(FAMILY).forGetter(UnbakedBranchModel::family) + ).apply(i, (textures, family) -> new UnbakedBranchModel(textures.bark(), textures.rings(), family))); + + @Override + public MapCodec codec() { + return CODEC; + } + + @Override + public void resolveDependencies(ResolvableModel.Resolver resolver) {} + + @Override + public BlockStateModel bake(ModelBaker baker) { + Material.Baked barkMat = baker.materials().get(new Material(barkTexture), barkTexture::toDebugFileName); + Material.Baked ringsMat = baker.materials().get(new Material(ringsTexture), ringsTexture::toDebugFileName); + + BasicBranchBlockBakedModel regular = BasicBranchBlockBakedModel.bakeBasic(baker, + new BranchModelPart.UnbakedCore(barkMat), + new BranchModelPart.UnbakedSleeve(barkMat), + new BranchModelPart.UnbakedCore(ringsMat), + null); + + if (family.isPresent() && family.get().isThick()) { + Identifier thickRings = getThickRingsTexture(ringsTexture); + Material.Baked thickRingsMat = baker.materials().get(new Material(thickRings), thickRings::toDebugFileName); + + return ThickBranchBlockBakedModel.bakeThick(baker, regular, + new BranchModelPart.UnbakedThickTrunk(barkMat, false), + new BranchModelPart.UnbakedThickTrunk(thickRingsMat, true)); + } + return regular; + } + + private @NotNull Identifier getThickRingsTexture(Identifier ringsTexture) { + if (ringsTexture.equals(DynamicTrees.location("block/air"))) + return ringsTexture; + return IdentifierUtils.suffix(ringsTexture, "_thick"); + } +} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/UnbakedCreakingHeartModel.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/UnbakedCreakingHeartModel.java new file mode 100644 index 000000000..6dea5956d --- /dev/null +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/UnbakedCreakingHeartModel.java @@ -0,0 +1,62 @@ +package com.dtteam.dynamictrees.model.blockstate; + +import com.dtteam.dynamictrees.model.baked.BasicBranchBlockBakedModel; +import com.dtteam.dynamictrees.model.parts.BranchModelPart; +import com.dtteam.dynamictrees.tree.family.Family; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.fabricmc.fabric.api.client.model.loading.v1.CustomUnbakedBlockStateModel; +import net.minecraft.client.renderer.block.dispatch.BlockStateModel; +import net.minecraft.client.resources.model.ModelBaker; +import net.minecraft.client.resources.model.ResolvableModel; +import net.minecraft.client.resources.model.sprite.Material; +import net.minecraft.resources.Identifier; + +import java.util.Optional; + +/** + * Fabric port of the NeoForge {@code UnbakedCreakingHeartModel}; deserialized from blockstate + * JSONs with type {@code dynamictrees:creaking_heart}. + */ +public record UnbakedCreakingHeartModel(Identifier heartBark, Identifier heartRings, Identifier bark, Optional family) implements CustomUnbakedBlockStateModel { + + public static final String BARK_TEXTURE = "bark"; + public static final String HEART_BARK_TEXTURE = "heart_bark"; + public static final String HEART_RINGS_TEXTURE = "heart_rings"; + public static final String TEXTURES = "textures"; + public static final String FAMILY = "family"; + + private record HeartTextures(Identifier bark, Identifier heartBark, Identifier heartRings) { + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(i -> i.group( + Identifier.CODEC.fieldOf(BARK_TEXTURE).forGetter(HeartTextures::bark), + Identifier.CODEC.fieldOf(HEART_BARK_TEXTURE).forGetter(HeartTextures::heartBark), + Identifier.CODEC.fieldOf(HEART_RINGS_TEXTURE).forGetter(HeartTextures::heartRings) + ).apply(i, HeartTextures::new)); + } + + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(i -> i.group( + HeartTextures.CODEC.codec().fieldOf(TEXTURES).forGetter(m -> new HeartTextures(m.bark, m.heartBark, m.heartRings)), + Family.CODEC.optionalFieldOf(FAMILY).forGetter(UnbakedCreakingHeartModel::family) + ).apply(i, (textures, family) -> new UnbakedCreakingHeartModel(textures.heartBark, textures.heartRings, textures.bark, family))); + + @Override + public MapCodec codec() { + return CODEC; + } + + @Override + public void resolveDependencies(ResolvableModel.Resolver resolver) {} + + @Override + public BlockStateModel bake(ModelBaker baker) { + Material.Baked heartMat = baker.materials().get(new Material(heartBark), heartBark::toDebugFileName); + Material.Baked ringsMat = baker.materials().get(new Material(heartRings), heartRings::toDebugFileName); + Material.Baked barkMat = baker.materials().get(new Material(bark), bark::toDebugFileName); + + return BasicBranchBlockBakedModel.bakeBasic(baker, + new BranchModelPart.UnbakedHeartCore(heartMat, barkMat), + new BranchModelPart.UnbakedSleeve(heartMat), + new BranchModelPart.UnbakedCore(ringsMat), + null); + } +} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/UnbakedRootsModel.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/UnbakedRootsModel.java new file mode 100644 index 000000000..f729d08d8 --- /dev/null +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/UnbakedRootsModel.java @@ -0,0 +1,54 @@ +package com.dtteam.dynamictrees.model.blockstate; + +import com.dtteam.dynamictrees.model.baked.BasicRootsBlockBakedModel; +import com.mojang.serialization.Codec; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.fabricmc.fabric.api.client.model.loading.v1.CustomUnbakedBlockStateModel; +import net.minecraft.client.renderer.block.dispatch.BlockStateModel; +import net.minecraft.client.resources.model.ModelBaker; +import net.minecraft.client.resources.model.ResolvableModel; +import net.minecraft.client.resources.model.sprite.Material; +import net.minecraft.resources.Identifier; + +import java.util.Optional; + +/** + * Fabric port of the NeoForge {@code UnbakedRootsModel}; deserialized from blockstate JSONs + * with type {@code dynamictrees:roots}. + */ +public record UnbakedRootsModel(Identifier side, Identifier top, boolean opaque) implements CustomUnbakedBlockStateModel { + + public static final String SIDE = "side"; + public static final String TOP = "top"; + public static final String OPAQUE = "opaque"; + public static final String TEXTURES = "textures"; + + private record RootsTextures(Identifier exposedSide, Identifier exposedTop) { + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(i -> i.group( + Identifier.CODEC.fieldOf(SIDE).forGetter(RootsTextures::exposedSide), + Identifier.CODEC.fieldOf(TOP).forGetter(RootsTextures::exposedTop) + ).apply(i, RootsTextures::new)); + } + + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(i -> i.group( + RootsTextures.CODEC.codec().fieldOf(TEXTURES).forGetter(m -> new RootsTextures(m.side, m.top)), + Codec.BOOL.optionalFieldOf(OPAQUE).forGetter(o -> Optional.of(o.opaque)) + ).apply(i, (textures, opaque) -> new UnbakedRootsModel(textures.exposedSide, textures.exposedTop, opaque.orElse(false)))); + + @Override + public MapCodec codec() { + return CODEC; + } + + @Override + public void resolveDependencies(ResolvableModel.Resolver resolver) {} + + @Override + public BlockStateModel bake(ModelBaker baker) { + Material.Baked barkMat = baker.materials().get(new Material(side), side::toDebugFileName); + Material.Baked ringsMat = baker.materials().get(new Material(top), top::toDebugFileName); + + return BasicRootsBlockBakedModel.bakeRoots(baker, barkMat, ringsMat, opaque); + } +} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/UnbakedRootsMossModel.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/UnbakedRootsMossModel.java new file mode 100644 index 000000000..dd41d537c --- /dev/null +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/UnbakedRootsMossModel.java @@ -0,0 +1,51 @@ +package com.dtteam.dynamictrees.model.blockstate; + +import com.dtteam.dynamictrees.model.baked.BasicBranchBlockBakedModel; +import com.dtteam.dynamictrees.model.parts.BranchModelPart; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.fabricmc.fabric.api.client.model.loading.v1.CustomUnbakedBlockStateModel; +import net.minecraft.client.renderer.block.dispatch.BlockStateModel; +import net.minecraft.client.resources.model.ModelBaker; +import net.minecraft.client.resources.model.ResolvableModel; +import net.minecraft.client.resources.model.sprite.Material; +import net.minecraft.resources.Identifier; + +/** + * Fabric port of the NeoForge {@code UnbakedRootsMossModel}; deserialized from blockstate JSONs + * with type {@code dynamictrees:roots_moss}. + */ +public record UnbakedRootsMossModel(Identifier moss) implements CustomUnbakedBlockStateModel { + + public static final String MOSS = "moss"; + public static final String TEXTURES = "textures"; + + private record RootsTextures(Identifier moss) { + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(i -> i.group( + Identifier.CODEC.fieldOf(MOSS).forGetter(RootsTextures::moss) + ).apply(i, RootsTextures::new)); + } + + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(i -> i.group( + RootsTextures.CODEC.codec().fieldOf(TEXTURES).forGetter(m -> new RootsTextures(m.moss)) + ).apply(i, textures -> new UnbakedRootsMossModel(textures.moss))); + + @Override + public MapCodec codec() { + return CODEC; + } + + @Override + public void resolveDependencies(ResolvableModel.Resolver resolver) {} + + @Override + public BlockStateModel bake(ModelBaker baker) { + Material.Baked mossMat = baker.materials().get(new Material(moss), moss::toDebugFileName); + + return BasicBranchBlockBakedModel.bakeBasic(baker, + new BranchModelPart.UnbakedMossCore(mossMat), + new BranchModelPart.UnbakedMossSleeve(mossMat), + new BranchModelPart.UnbakedMossCore(mossMat), + null); + } +} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/UnbakedSurfaceRootModel.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/UnbakedSurfaceRootModel.java new file mode 100644 index 000000000..6b780e46c --- /dev/null +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/blockstate/UnbakedSurfaceRootModel.java @@ -0,0 +1,43 @@ +package com.dtteam.dynamictrees.model.blockstate; + +import com.dtteam.dynamictrees.model.baked.SurfaceRootBlockBakedModel; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.fabricmc.fabric.api.client.model.loading.v1.CustomUnbakedBlockStateModel; +import net.minecraft.client.renderer.block.dispatch.BlockStateModel; +import net.minecraft.client.resources.model.ModelBaker; +import net.minecraft.client.resources.model.ResolvableModel; +import net.minecraft.resources.Identifier; + +/** + * Fabric port of the NeoForge {@code SurfaceRootBlockStateModel.Unbaked}; deserialized from + * blockstate JSONs with type {@code dynamictrees:surface_root}. + */ +public record UnbakedSurfaceRootModel(Identifier barkTexture) implements CustomUnbakedBlockStateModel { + + public static final String TEXTURES = "textures"; + public static final String BARK_TEXTURE = "bark"; + + private record Textures(Identifier bark) { + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(i -> i.group( + Identifier.CODEC.fieldOf(BARK_TEXTURE).forGetter(Textures::bark) + ).apply(i, Textures::new)); + } + + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(i -> i.group( + Textures.CODEC.codec().fieldOf(TEXTURES).forGetter(m -> new Textures(m.barkTexture())) + ).apply(i, textures -> new UnbakedSurfaceRootModel(textures.bark()))); + + @Override + public MapCodec codec() { + return CODEC; + } + + @Override + public void resolveDependencies(ResolvableModel.Resolver resolver) {} + + @Override + public BlockStateModel bake(ModelBaker baker) { + return SurfaceRootBlockBakedModel.bake(baker, barkTexture); + } +} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricClientHelper.java b/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricClientHelper.java index 49e245526..ddbc17b2e 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricClientHelper.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricClientHelper.java @@ -17,7 +17,7 @@ public int getPixelRGBA(TextureAtlasSprite sprite, int x, int y) { SpriteContents contents = sprite.contents(); NativeImage image = contents.originalImage; if (image != null) { - return image.getPixelRGBA(x, y); + return image.getPixel(x, y); } return 0; } catch (Exception e) { diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricCompatHelper.java b/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricCompatHelper.java index 5ac8c6ef9..f9157aa6f 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricCompatHelper.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricCompatHelper.java @@ -1,13 +1,18 @@ package com.dtteam.dynamictrees.platform; +import com.dtteam.dynamictrees.DynamicTrees; import com.dtteam.dynamictrees.compat.SereneSeasonsSeasonProvider; import com.dtteam.dynamictrees.platform.services.ICompatHelper; public class FabricCompatHelper implements ICompatHelper { @Override - public void registerSeasonProvider() { - SereneSeasonsSeasonProvider.registerSereneSeasonsProvider(); + public void registerSeasonProvider(String modId) { + // Only Serene Seasons has a Fabric compat dependency wired up (see fabric/build.gradle); + // Ecliptic Seasons (NeoForge's other branch here) has no Fabric port pinned yet. + if (DynamicTrees.SERENE_SEASONS.equals(modId)) { + SereneSeasonsSeasonProvider.registerSereneSeasonsProvider(); + } } } \ No newline at end of file diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricInteractionHelper.java b/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricInteractionHelper.java index ed3a3a0a7..ab9c0fa5b 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricInteractionHelper.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricInteractionHelper.java @@ -25,7 +25,6 @@ public boolean canToolAxeStrip(ItemStack stack) { return stack.is(ItemTags.AXES); } - @Override public boolean canToolAxeDig(ItemStack stack) { return stack.is(ItemTags.AXES); } @@ -39,13 +38,13 @@ public int setSeedItemEntityLifespan(ItemEntity entityItem, Seed seed) { public boolean blockDestroyByPlayer(BlockState state, Level level, BlockPos pos, Player player, boolean willHarvest, FluidState fluidState) { Block block = state.getBlock(); if (block instanceof BranchBlock branchBlock) { - return branchBlock.onDestroyedByPlayer(state, level, pos, player, willHarvest, fluidState); + return branchBlock.onDestroyedByPlayer(state, level, pos, player, player.getMainHandItem(), willHarvest, fluidState); } else if (block instanceof TrunkShellBlock trunkShellBlock) { - return trunkShellBlock.onDestroyedByPlayer(state, level, pos, player, willHarvest, fluidState); + return trunkShellBlock.onDestroyedByPlayer(state, level, pos, player, player.getMainHandItem(), willHarvest, fluidState); } else if (block instanceof SoilBlock soilBlock) { return soilBlock.onDestroyedByPlayer(state, level, pos, player, willHarvest, fluidState); } else if (block instanceof PottedSaplingBlock pottedSaplingBlock) { - return pottedSaplingBlock.onDestroyedByPlayer(state, level, pos, player, willHarvest, fluidState); + return pottedSaplingBlock.onDestroyedByPlayer(state, level, pos, player, player.getMainHandItem(), willHarvest, fluidState); } return true; } diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/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..e0b234d30 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/registry/FabricRegistryLoader.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/registry/FabricRegistryLoader.java @@ -1,17 +1,19 @@ package com.dtteam.dynamictrees.registry; import com.dtteam.dynamictrees.DynamicTrees; -import com.dtteam.dynamictrees.recipe.DendroPotionRecipeHandler; import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.serialization.MapCodec; -import net.fabricmc.fabric.api.itemgroup.v1.FabricItemGroup; +import net.fabricmc.fabric.api.object.builder.v1.entity.FabricEntityDataRegistry; import net.minecraft.commands.synchronization.ArgumentTypeInfo; import net.minecraft.commands.synchronization.ArgumentTypeInfos; import net.minecraft.core.Registry; import net.minecraft.core.component.DataComponentType; import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.syncher.EntityDataSerializer; import net.minecraft.resources.Identifier; +import net.minecraft.resources.ResourceKey; import net.minecraft.sounds.SoundEvent; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; @@ -30,11 +32,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; @@ -45,19 +44,24 @@ public class FabricRegistryLoader extends RegistryLoader { public static void setup (){ DTRegistries.setup(); - DendroPotionRecipeHandler.getAllDendroRecipes(); + // DendroPotionRecipeHandler.getAllDendroRecipes() is intentionally not called eagerly + // here: it builds ItemStacks from vanilla item Holders that aren't bound yet this early + // in mod init ("Components not bound yet" NPE). It's lazily populated and cached on + // first real use instead, via MixinPotionBrewing's injections into PotionBrewing. } @Override 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 @@ -77,13 +81,14 @@ public Supplier registerCreativeTab(String name, Supplier Supplier> registerEntity(String name, EntityType.Builder builder, boolean isTree) { // if (isTree) // builder.setShouldReceiveVelocityUpdates(true).setTrackingRange(512).setUpdateInterval(Integer.MAX_VALUE); - EntityType entityType = Registry.register(BuiltInRegistries.ENTITY_TYPE, DynamicTrees.location(name), builder.build(name)); + EntityType entityType = Registry.register(BuiltInRegistries.ENTITY_TYPE, DynamicTrees.location(name), + builder.build(ResourceKey.create(Registries.ENTITY_TYPE, DynamicTrees.location(name)))); return ()-> entityType; } @Override public Supplier> registerBlockEntity(String name, BlockEntityType.BlockEntitySupplier newBlockEntity, Supplier> validBlocks) { - BlockEntityType entityType = Registry.register(BuiltInRegistries.BLOCK_ENTITY_TYPE, DynamicTrees.location(name), new BlockEntityType<>(newBlockEntity, validBlocks.get(), null)); + BlockEntityType entityType = Registry.register(BuiltInRegistries.BLOCK_ENTITY_TYPE, DynamicTrees.location(name), new BlockEntityType<>(newBlockEntity, validBlocks.get())); return ()-> entityType; } @@ -100,6 +105,15 @@ public Supplier> registerDataComponentType(String name, return ()-> type; } + @Override + public Supplier> registerEntityDataSerializer(String name, Supplier> operator) { + EntityDataSerializer serializer = operator.get(); + // Fabric API forbids vanilla's EntityDataSerializers.registerSerializer at runtime + // ("use FabricEntityDataRegistry.register instead"), so register through its API. + FabricEntityDataRegistry.register(DynamicTrees.location(name), serializer); + return ()-> serializer; + } + @Override public , T extends ArgumentTypeInfo.Template, I extends ArgumentTypeInfo> Supplier registerCommandArgumentType(String name, Class infoClass, I argumentTypeInfo) { ArgumentTypeInfos.BY_CLASS.put(infoClass, argumentTypeInfo); @@ -108,20 +122,20 @@ public , T extends ArgumentTypeInfo.Template, I ext } @Override - public Supplier registerLootConditionType(String name, MapCodec serializerFactory) { - LootItemConditionType type = Registry.register(BuiltInRegistries.LOOT_CONDITION_TYPE, DynamicTrees.location(name), new LootItemConditionType(serializerFactory)); + public Supplier> registerLootConditionType(String name, MapCodec serializerFactory) { + MapCodec type = Registry.register(BuiltInRegistries.LOOT_CONDITION_TYPE, DynamicTrees.location(name), serializerFactory); return ()-> type; } @Override - public Supplier registerLootPoolEntryType(String name, MapCodec serializerFactory) { - LootPoolEntryType type = Registry.register(BuiltInRegistries.LOOT_POOL_ENTRY_TYPE, DynamicTrees.location(name), new LootPoolEntryType(serializerFactory)); + public Supplier> registerLootPoolEntryType(String name, MapCodec serializerFactory) { + MapCodec type = Registry.register(BuiltInRegistries.LOOT_POOL_ENTRY_TYPE, DynamicTrees.location(name), serializerFactory); return ()-> type; } @Override - public Supplier> registerLootFunctionType(String name, MapCodec serializerFactory) { - LootItemFunctionType type = Registry.register(BuiltInRegistries.LOOT_FUNCTION_TYPE, DynamicTrees.location(name), new LootItemFunctionType<>(serializerFactory)); + public Supplier> registerLootFunctionType(String name, MapCodec serializerFactory) { + MapCodec type = Registry.register(BuiltInRegistries.LOOT_FUNCTION_TYPE, DynamicTrees.location(name), serializerFactory); return ()-> type; } diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/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..04a2b3f8a 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/DTBiomeHolderSet.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/DTBiomeHolderSet.java @@ -102,6 +102,11 @@ public boolean canSerializeIn(HolderOwner owner) { return true; } + @Override + public boolean isBound() { + return true; + } + @Override public Optional> unwrapKey() { return Optional.empty(); diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/DelayedHolderSet.java b/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/DelayedHolderSet.java index c70ab944e..c4b7c9406 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/DelayedHolderSet.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/DelayedHolderSet.java @@ -58,6 +58,11 @@ public boolean canSerializeIn(HolderOwner owner) { return this.holderSetSupplier.get().canSerializeIn(owner); } + @Override + public boolean isBound() { + return this.holderSetSupplier.get().isBound(); + } + @Override public Optional> unwrapKey() { return this.holderSetSupplier.get().unwrapKey(); diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/NameRegexMatchHolderSet.java b/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/NameRegexMatchHolderSet.java index f0c897065..74d19bb46 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/NameRegexMatchHolderSet.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/NameRegexMatchHolderSet.java @@ -13,6 +13,6 @@ public NameRegexMatchHolderSet(HolderLookup.RegistryLookup registryLookup, St @Override protected Stream getInput(Holder holder) { - return holder.unwrapKey().stream().map(key -> key.location().toString()); + return holder.unwrapKey().stream().map(key -> key.identifier().toString()); } } diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/OrHolderSet.java b/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/OrHolderSet.java index 279f2d5db..320bc7434 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/OrHolderSet.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/OrHolderSet.java @@ -24,4 +24,9 @@ public Stream> stream() { public boolean canSerializeIn(HolderOwner owner) { return this.values.stream().allMatch(set -> set.canSerializeIn(owner)); } + + @Override + public boolean isBound() { + return this.values.stream().allMatch(HolderSet::isBound); + } } diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/StreamBackedHolderSet.java b/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/StreamBackedHolderSet.java index a4b7ad1e0..1af57a690 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/StreamBackedHolderSet.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/StreamBackedHolderSet.java @@ -1,11 +1,11 @@ package com.dtteam.dynamictrees.worldgen.holderset; import com.mojang.datafixers.util.Either; -import net.minecraft.Util; import net.minecraft.core.Holder; import net.minecraft.core.HolderSet; import net.minecraft.tags.TagKey; import net.minecraft.util.RandomSource; +import net.minecraft.util.Util; import java.util.*; import java.util.stream.Collectors; @@ -15,6 +15,15 @@ public List> contents() { return this.stream().collect(Collectors.toList()); } + // These sets are eagerly computed from their backing source (a registry lookup, a + // supplier, etc.) rather than lazily resolved against a tag registry, so they're always + // "bound" once constructible - mirrors vanilla HolderSet.Direct's isBound(), not + // HolderSet.Named's (which tracks whether a deferred tag binding has resolved yet). + @Override + public boolean isBound() { + return true; + } + public Set> contentsSet() { return this.stream().collect(Collectors.toSet()); }