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 extends CustomUnbakedBlockStateModel> 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 extends CustomUnbakedBlockStateModel> 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 extends CustomUnbakedBlockStateModel> 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 extends CustomUnbakedBlockStateModel> 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 extends CustomUnbakedBlockStateModel> 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 extends CustomUnbakedBlockStateModel> 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 extends CustomUnbakedBlockStateModel> 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 extends T> 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 extends LootItemCondition> 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 extends LootPoolEntryContainer> 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());
}