diff --git a/build.gradle b/build.gradle index 27e333ade..5531051b7 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ plugins { // see https://fabricmc.net/develop/ for new versions - id 'net.fabricmc.fabric-loom' version '1.15.5' apply false + id 'net.fabricmc.fabric-loom' version '1.17.17' apply false // see https://projects.neoforged.net/neoforged/moddevgradle for new versions - id 'net.neoforged.moddev' version '2.0.141' apply false + id 'net.neoforged.moddev' version '2.0.142' apply false id "me.modmuss50.mod-publish-plugin" version "1.1.0" apply false } \ No newline at end of file diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/acacia_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/acacia_branch.json index 4e352b493..6ed41cacd 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/acacia_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/acacia_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:acacia", "textures": { "bark": "minecraft:block/acacia_log", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/birch_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/birch_branch.json index e195b2b56..95fd5b705 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/birch_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/birch_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:birch", "textures": { "bark": "minecraft:block/birch_log", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/cherry_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/cherry_branch.json index 63a2e8b3b..bebb223f8 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/cherry_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/cherry_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:cherry", "textures": { "bark": "minecraft:block/cherry_log", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/crimson_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/crimson_branch.json index f6d350f23..75f25dbbc 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/crimson_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/crimson_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:crimson", "textures": { "bark": "minecraft:block/crimson_stem", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/dark_oak_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/dark_oak_branch.json index 8c7a4bee5..ccb326b6d 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/dark_oak_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/dark_oak_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:dark_oak", "textures": { "bark": "minecraft:block/dark_oak_log", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/dark_oak_root.json b/common/src/generated/resources/assets/dynamictrees/blockstates/dark_oak_root.json index 7dd987b46..48a75cb95 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/dark_oak_root.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/dark_oak_root.json @@ -2,9 +2,10 @@ "variants": { "": { "type": "dynamictrees:surface_root", + "fabric:type": "dynamictrees:surface_root", "textures": { "bark": "minecraft:block/dark_oak_log" } } } -} \ No newline at end of file +} diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/jungle_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/jungle_branch.json index 9a5f34a4c..9c160cb08 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/jungle_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/jungle_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:jungle", "textures": { "bark": "minecraft:block/jungle_log", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/jungle_root.json b/common/src/generated/resources/assets/dynamictrees/blockstates/jungle_root.json index c974dbf4a..10a0f2451 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/jungle_root.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/jungle_root.json @@ -2,9 +2,10 @@ "variants": { "": { "type": "dynamictrees:surface_root", + "fabric:type": "dynamictrees:surface_root", "textures": { "bark": "minecraft:block/jungle_log" } } } -} \ No newline at end of file +} diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/mangrove_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/mangrove_branch.json index 8fc27ad61..189cb16f0 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/mangrove_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/mangrove_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:mangrove", "textures": { "bark": "minecraft:block/mangrove_log", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/mangrove_roots.json b/common/src/generated/resources/assets/dynamictrees/blockstates/mangrove_roots.json index 5a8c51b14..51d249f72 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/mangrove_roots.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/mangrove_roots.json @@ -5,6 +5,7 @@ }, "layer=exposed": { "type": "dynamictrees:roots", + "fabric:type": "dynamictrees:roots", "opaque": false, "textures": { "side": "minecraft:block/mangrove_roots_side", @@ -13,6 +14,7 @@ }, "layer=filled": { "type": "dynamictrees:roots", + "fabric:type": "dynamictrees:roots", "opaque": true, "textures": { "side": "minecraft:block/muddy_mangrove_roots_side", @@ -20,4 +22,4 @@ } } } -} \ No newline at end of file +} diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/mossy_mangrove_roots.json b/common/src/generated/resources/assets/dynamictrees/blockstates/mossy_mangrove_roots.json index f127ee36d..e36541a23 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/mossy_mangrove_roots.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/mossy_mangrove_roots.json @@ -3,6 +3,7 @@ { "apply": { "type": "dynamictrees:roots", + "fabric:type": "dynamictrees:roots", "opaque": false, "textures": { "side": "minecraft:block/mangrove_roots_side", @@ -16,6 +17,7 @@ { "apply": { "type": "dynamictrees:roots", + "fabric:type": "dynamictrees:roots", "opaque": true, "textures": { "side": "minecraft:block/muddy_mangrove_roots_side", @@ -37,6 +39,7 @@ { "apply": { "type": "dynamictrees:roots_moss", + "fabric:type": "dynamictrees:roots_moss", "textures": { "moss": "minecraft:block/moss_block" } @@ -46,4 +49,4 @@ } } ] -} \ No newline at end of file +} diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/oak_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/oak_branch.json index f3b48d6af..2595332e1 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/oak_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/oak_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:oak", "textures": { "bark": "minecraft:block/oak_log", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/pale_oak_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/pale_oak_branch.json index f7ede77a9..eb4a7bb43 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/pale_oak_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/pale_oak_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:pale_oak", "textures": { "bark": "minecraft:block/pale_oak_log", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/pale_oak_creaking_heart_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/pale_oak_creaking_heart_branch.json index 04a02c417..d53334578 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/pale_oak_creaking_heart_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/pale_oak_creaking_heart_branch.json @@ -3,6 +3,7 @@ { "apply": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:pale_oak", "textures": { "bark": "minecraft:block/pale_oak_log", @@ -16,6 +17,7 @@ { "apply": { "type": "dynamictrees:creaking_heart", + "fabric:type": "dynamictrees:creaking_heart", "family": "dynamictrees:pale_oak", "textures": { "bark": "minecraft:block/pale_oak_log", @@ -31,6 +33,7 @@ { "apply": { "type": "dynamictrees:creaking_heart", + "fabric:type": "dynamictrees:creaking_heart", "family": "dynamictrees:pale_oak", "textures": { "bark": "minecraft:block/pale_oak_log", @@ -46,6 +49,7 @@ { "apply": { "type": "dynamictrees:creaking_heart", + "fabric:type": "dynamictrees:creaking_heart", "family": "dynamictrees:pale_oak", "textures": { "bark": "minecraft:block/pale_oak_log", @@ -59,4 +63,4 @@ } } ] -} \ No newline at end of file +} diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/pale_oak_root.json b/common/src/generated/resources/assets/dynamictrees/blockstates/pale_oak_root.json index 388444ac2..84624835c 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/pale_oak_root.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/pale_oak_root.json @@ -2,9 +2,10 @@ "variants": { "": { "type": "dynamictrees:surface_root", + "fabric:type": "dynamictrees:surface_root", "textures": { "bark": "minecraft:block/pale_oak_log" } } } -} \ No newline at end of file +} diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/potted_sapling.json b/common/src/generated/resources/assets/dynamictrees/blockstates/potted_sapling.json index 5030d937f..fb9d8d810 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/potted_sapling.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/potted_sapling.json @@ -2,7 +2,8 @@ "variants": { "": { "type": "dynamictrees:potted_dynamic_sapling", + "fabric:type": "dynamictrees:potted_dynamic_sapling", "model": "minecraft:block/flower_pot" } } -} \ No newline at end of file +} diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/resin_pale_oak_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/resin_pale_oak_branch.json index 82d1566c8..06d4bbd8a 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/resin_pale_oak_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/resin_pale_oak_branch.json @@ -3,6 +3,7 @@ { "apply": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:pale_oak", "textures": { "bark": "minecraft:block/pale_oak_log", @@ -13,6 +14,7 @@ { "apply": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:pale_oak", "textures": { "bark": "minecraft:block/resin_clump", @@ -21,4 +23,4 @@ } } ] -} \ No newline at end of file +} diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/rooty_mangrove_aerial_roots.json b/common/src/generated/resources/assets/dynamictrees/blockstates/rooty_mangrove_aerial_roots.json index 91f364844..308102979 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/rooty_mangrove_aerial_roots.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/rooty_mangrove_aerial_roots.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:aerial_roots_soil", + "fabric:type": "dynamictrees:aerial_roots_soil", "family": "dynamictrees:mangrove", "textures": { "end": "minecraft:block/mangrove_log_top", @@ -11,4 +12,4 @@ } } } -} \ No newline at end of file +} diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/spruce_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/spruce_branch.json index 64dc4384e..f3a51ca47 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/spruce_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/spruce_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:spruce", "textures": { "bark": "minecraft:block/spruce_log", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_acacia_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_acacia_branch.json index a90a7ecac..54b2f1412 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_acacia_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_acacia_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:acacia", "textures": { "bark": "minecraft:block/stripped_acacia_log", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_birch_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_birch_branch.json index 4c57c2fbb..ea1d9b71d 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_birch_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_birch_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:birch", "textures": { "bark": "minecraft:block/stripped_birch_log", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_cherry_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_cherry_branch.json index ba7d0635f..5459a017f 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_cherry_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_cherry_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:cherry", "textures": { "bark": "minecraft:block/stripped_cherry_log", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_crimson_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_crimson_branch.json index 00c6b7049..de8ee8678 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_crimson_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_crimson_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:crimson", "textures": { "bark": "minecraft:block/stripped_crimson_stem", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_dark_oak_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_dark_oak_branch.json index bedb2a996..837e35f50 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_dark_oak_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_dark_oak_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:dark_oak", "textures": { "bark": "minecraft:block/stripped_dark_oak_log", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_jungle_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_jungle_branch.json index cf4ba940a..af9e713c6 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_jungle_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_jungle_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:jungle", "textures": { "bark": "minecraft:block/stripped_jungle_log", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_mangrove_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_mangrove_branch.json index 361f6ad11..18e818361 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_mangrove_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_mangrove_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:mangrove", "textures": { "bark": "minecraft:block/stripped_mangrove_log", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_oak_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_oak_branch.json index edfd98b36..285515531 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_oak_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_oak_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:oak", "textures": { "bark": "minecraft:block/stripped_oak_log", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_pale_oak_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_pale_oak_branch.json index 742fad84a..101658146 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_pale_oak_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_pale_oak_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:pale_oak", "textures": { "bark": "minecraft:block/stripped_pale_oak_log", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_spruce_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_spruce_branch.json index e0b2f21e0..e24d078dc 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_spruce_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_spruce_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:spruce", "textures": { "bark": "minecraft:block/stripped_spruce_log", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_warped_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_warped_branch.json index 4b4b50564..be59b71bb 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_warped_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/stripped_warped_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:warped", "textures": { "bark": "minecraft:block/stripped_warped_stem", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} diff --git a/common/src/generated/resources/assets/dynamictrees/blockstates/warped_branch.json b/common/src/generated/resources/assets/dynamictrees/blockstates/warped_branch.json index 6deee0565..660300534 100644 --- a/common/src/generated/resources/assets/dynamictrees/blockstates/warped_branch.json +++ b/common/src/generated/resources/assets/dynamictrees/blockstates/warped_branch.json @@ -2,6 +2,7 @@ "variants": { "": { "type": "dynamictrees:branch", + "fabric:type": "dynamictrees:branch", "family": "dynamictrees:warped", "textures": { "bark": "minecraft:block/warped_stem", @@ -9,4 +10,4 @@ } } } -} \ No newline at end of file +} diff --git a/common/src/main/java/com/dtteam/dynamictrees/api/configuration/package-info.java b/common/src/main/java/com/dtteam/dynamictrees/api/configuration/package-info.java deleted file mode 100644 index 1abc681aa..000000000 --- a/common/src/main/java/com/dtteam/dynamictrees/api/configuration/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@MethodsReturnNonnullByDefault -package com.dtteam.dynamictrees.api.configuration; - -import com.mojang.logging.annotations.MethodsReturnNonnullByDefault; \ No newline at end of file diff --git a/common/src/main/java/com/dtteam/dynamictrees/block/branch/package-info.java b/common/src/main/java/com/dtteam/dynamictrees/block/branch/package-info.java deleted file mode 100644 index f9d4c45f8..000000000 --- a/common/src/main/java/com/dtteam/dynamictrees/block/branch/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@MethodsReturnNonnullByDefault -package com.dtteam.dynamictrees.block.branch; - -import com.mojang.logging.annotations.MethodsReturnNonnullByDefault; \ No newline at end of file diff --git a/common/src/main/java/com/dtteam/dynamictrees/block/leaves/DynamicLeavesBlock.java b/common/src/main/java/com/dtteam/dynamictrees/block/leaves/DynamicLeavesBlock.java index d3078af63..562b515ef 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/block/leaves/DynamicLeavesBlock.java +++ b/common/src/main/java/com/dtteam/dynamictrees/block/leaves/DynamicLeavesBlock.java @@ -31,7 +31,7 @@ import net.minecraft.util.Mth; import net.minecraft.util.ParticleUtils; import net.minecraft.util.RandomSource; -import net.minecraft.util.Tuple; +import com.mojang.datafixers.util.Pair; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.InsideBlockEffectApplier; import net.minecraft.world.entity.LivingEntity; @@ -175,15 +175,15 @@ public void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource */ public boolean updateAllLeaves(LevelAccessor level, BlockPos startPos, BlockState startState, RandomSource rand, boolean worldGen){ //We store the position and hydro of the next block. - Queue> toProcess = new ArrayDeque<>(); + Queue> toProcess = new ArrayDeque<>(); Set processedPositions = new HashSet<>(); int firstHydro = updateHydro(level, startPos, startState, worldGen); - toProcess.add(new Tuple<>(startPos, firstHydro)); + toProcess.add(new Pair<>(startPos, firstHydro)); if (firstHydro == 0) return false; while (!toProcess.isEmpty() && processedPositions.size() <= getLeavesProperties().maxLeavesRecursion()){ - Tuple tup = toProcess.remove(); - BlockPos pos = tup.getA(); - int hydro = tup.getB(); + Pair tup = toProcess.remove(); + BlockPos pos = tup.getFirst(); + int hydro = tup.getSecond(); processedPositions.add(pos); for (Direction dir : Direction.values()) { // Go on all 6 sides of this block if (hydro > 1 || rand.nextInt(4) == 0) { // we'll give it a 1 in 4 chance to grow leaves if hydro is low to help performance @@ -201,7 +201,7 @@ public boolean updateAllLeaves(LevelAccessor level, BlockPos startPos, BlockStat //Do not iterate back through bigger hydro values //or if the leaves failed to grow if (sideHydro == 0 || sideHydro <= hydro){ - toProcess.add(new Tuple<>(sidePos, sideHydro)); + toProcess.add(new Pair<>(sidePos, sideHydro)); } } } diff --git a/common/src/main/java/com/dtteam/dynamictrees/block/leaves/LeavesProperties.java b/common/src/main/java/com/dtteam/dynamictrees/block/leaves/LeavesProperties.java index 4e6184a70..5dc35ffb2 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/block/leaves/LeavesProperties.java +++ b/common/src/main/java/com/dtteam/dynamictrees/block/leaves/LeavesProperties.java @@ -1,5 +1,9 @@ package com.dtteam.dynamictrees.block.leaves; +import net.minecraft.world.entity.EntityTypes; + +import com.dtteam.dynamictrees.utility.TagUtils; + import com.dtteam.dynamictrees.DynamicTrees; import com.dtteam.dynamictrees.api.cell.CellKit; import com.dtteam.dynamictrees.api.registry.RegistryEntry; @@ -573,7 +577,7 @@ public BlockBehaviour.Properties getDefaultBlockProperties() { .randomTicks() .sound(SoundType.GRASS) .noOcclusion() - .isValidSpawn((s, r, p, e) -> e == EntityType.OCELOT || e == EntityType.PARROT) + .isValidSpawn((s, r, p, e) -> e == EntityTypes.OCELOT || e == EntityTypes.PARROT) .isSuffocating((s, r, p) -> false) .isViewBlocking((s, r, p) -> false) .isRedstoneConductor((s, r, p) -> false) @@ -710,13 +714,13 @@ public String toReloadDataString() { Pair.of("connectAnyRadius", this.connectAnyRadius)); } - public void addGeneratedBlockTags (Function, TagAppender> tagAppender){ + public void addGeneratedBlockTags (Function, TagAppender> tagAppender){ getDynamicLeavesBlock().ifPresent(leaves -> defaultLeavesTags().forEach(tag -> { if (isOnlyIfLoaded()) { - tagAppender.apply(tag).addOptional(leaves); + tagAppender.apply(tag).addOptional(TagUtils.key(leaves)); } else { - tagAppender.apply(tag).add(leaves); + tagAppender.apply(tag).add(TagUtils.key(leaves)); } }) ); diff --git a/common/src/main/java/com/dtteam/dynamictrees/block/leaves/package-info.java b/common/src/main/java/com/dtteam/dynamictrees/block/leaves/package-info.java deleted file mode 100644 index 8a2058796..000000000 --- a/common/src/main/java/com/dtteam/dynamictrees/block/leaves/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@MethodsReturnNonnullByDefault -package com.dtteam.dynamictrees.block.leaves; - -import com.mojang.logging.annotations.MethodsReturnNonnullByDefault; \ No newline at end of file diff --git a/common/src/main/java/com/dtteam/dynamictrees/block/package-info.java b/common/src/main/java/com/dtteam/dynamictrees/block/package-info.java deleted file mode 100644 index 2242cf487..000000000 --- a/common/src/main/java/com/dtteam/dynamictrees/block/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@MethodsReturnNonnullByDefault -package com.dtteam.dynamictrees.block; - -import com.mojang.logging.annotations.MethodsReturnNonnullByDefault; \ No newline at end of file diff --git a/common/src/main/java/com/dtteam/dynamictrees/block/soil/SoilProperties.java b/common/src/main/java/com/dtteam/dynamictrees/block/soil/SoilProperties.java index 9c321b702..f8567d1fa 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/block/soil/SoilProperties.java +++ b/common/src/main/java/com/dtteam/dynamictrees/block/soil/SoilProperties.java @@ -1,5 +1,7 @@ package com.dtteam.dynamictrees.block.soil; +import com.dtteam.dynamictrees.utility.TagUtils; + import com.dtteam.dynamictrees.DynamicTrees; import com.dtteam.dynamictrees.api.registry.RegistryEntry; import com.dtteam.dynamictrees.api.registry.RegistryHandler; @@ -294,14 +296,14 @@ public void setOnlyIfLoaded(String onlyIfLoaded) { this.onlyIfLoaded.add(onlyIfLoaded); } - public void addGeneratedBlockTags (Function, TagAppender> tagAppender){ + public void addGeneratedBlockTags (Function, TagAppender> tagAppender){ // add rooty blocks to the rooty soil tag. getBlock().ifPresent(rootyBlock -> defaultSoilBlockTags().forEach(tag -> { if (!isOnlyIfLoaded()) { - tagAppender.apply(tag).add(rootyBlock); + tagAppender.apply(tag).add(TagUtils.key(rootyBlock)); } else { - tagAppender.apply(tag).addOptional(rootyBlock); + tagAppender.apply(tag).addOptional(TagUtils.key(rootyBlock)); } })); } diff --git a/common/src/main/java/com/dtteam/dynamictrees/block/soil/package-info.java b/common/src/main/java/com/dtteam/dynamictrees/block/soil/package-info.java deleted file mode 100644 index 9dd2b758a..000000000 --- a/common/src/main/java/com/dtteam/dynamictrees/block/soil/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@MethodsReturnNonnullByDefault -package com.dtteam.dynamictrees.block.soil; - -import com.mojang.logging.annotations.MethodsReturnNonnullByDefault; \ No newline at end of file diff --git a/common/src/main/java/com/dtteam/dynamictrees/command/package-info.java b/common/src/main/java/com/dtteam/dynamictrees/command/package-info.java deleted file mode 100644 index 200973484..000000000 --- a/common/src/main/java/com/dtteam/dynamictrees/command/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@MethodsReturnNonnullByDefault -package com.dtteam.dynamictrees.command; - -import com.mojang.logging.annotations.MethodsReturnNonnullByDefault; \ No newline at end of file diff --git a/common/src/main/java/com/dtteam/dynamictrees/data/BiGenerator.java b/common/src/main/java/com/dtteam/dynamictrees/data/BiGenerator.java index ff6dde83e..cfab352bf 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/data/BiGenerator.java +++ b/common/src/main/java/com/dtteam/dynamictrees/data/BiGenerator.java @@ -1,6 +1,6 @@ package com.dtteam.dynamictrees.data; -import net.minecraft.util.Tuple; +import com.mojang.datafixers.util.Pair; /** * A generator for a resource, providing the means to construct a Json file using provided {@link Dependencies} and @@ -10,11 +10,11 @@ * @param the type of the second generator * @param the type of the input to get the dependencies from */ -public interface BiGenerator extends Generator, I> { +public interface BiGenerator extends Generator, I> { @Override - default void generate(Tuple providers, I input, Dependencies dependencies){ - generate(providers.getA(), providers.getB(), input, dependencies); + default void generate(Pair providers, I input, Dependencies dependencies){ + generate(providers.getFirst(), providers.getSecond(), input, dependencies); } void generate(P1 provider1, P2 provider2, I input, Dependencies dependencies); diff --git a/common/src/main/java/com/dtteam/dynamictrees/data/DTLootTableBuilder.java b/common/src/main/java/com/dtteam/dynamictrees/data/DTLootTableBuilder.java index 63ba67c19..c633c7cd4 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/data/DTLootTableBuilder.java +++ b/common/src/main/java/com/dtteam/dynamictrees/data/DTLootTableBuilder.java @@ -8,7 +8,7 @@ import com.dtteam.dynamictrees.loot.function.MultiplyBySticksCount; import com.dtteam.dynamictrees.loot.function.MultiplyByTotalVolume; import com.dtteam.dynamictrees.utility.ItemUtils; -import net.minecraft.advancements.criterion.*; +import net.minecraft.advancements.predicates.*; import net.minecraft.core.HolderLookup; import net.minecraft.core.component.predicates.DataComponentPredicates; import net.minecraft.core.component.predicates.EnchantmentsPredicate; diff --git a/common/src/main/java/com/dtteam/dynamictrees/deserialization/deserializer/worldgen/package-info.java b/common/src/main/java/com/dtteam/dynamictrees/deserialization/deserializer/worldgen/package-info.java deleted file mode 100644 index 2a15654db..000000000 --- a/common/src/main/java/com/dtteam/dynamictrees/deserialization/deserializer/worldgen/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@MethodsReturnNonnullByDefault -package com.dtteam.dynamictrees.deserialization.deserializer.worldgen; - -import com.mojang.logging.annotations.MethodsReturnNonnullByDefault; \ No newline at end of file diff --git a/common/src/main/java/com/dtteam/dynamictrees/deserialization/package-info.java b/common/src/main/java/com/dtteam/dynamictrees/deserialization/package-info.java deleted file mode 100644 index cbec1edac..000000000 --- a/common/src/main/java/com/dtteam/dynamictrees/deserialization/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@MethodsReturnNonnullByDefault -package com.dtteam.dynamictrees.deserialization; - -import com.mojang.logging.annotations.MethodsReturnNonnullByDefault; \ No newline at end of file diff --git a/common/src/main/java/com/dtteam/dynamictrees/entity/FallingTreeEntity.java b/common/src/main/java/com/dtteam/dynamictrees/entity/FallingTreeEntity.java index 2d96262b6..8d6311716 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/entity/FallingTreeEntity.java +++ b/common/src/main/java/com/dtteam/dynamictrees/entity/FallingTreeEntity.java @@ -185,7 +185,7 @@ public void buildClient() { cleanupShellBlocks(destroyData); - Minecraft.getInstance().levelRenderer.setBlocksDirty(renderBounds.getMin().getX(), renderBounds.getMin().getY(), renderBounds.getMin().getZ(), renderBounds.getMax().getX(), renderBounds.getMax().getY(), renderBounds.getMax().getZ());//This forces the client to rerender the chunks + Minecraft.getInstance().levelExtractor.setBlocksDirty(renderBounds.getMin().getX(), renderBounds.getMin().getY(), renderBounds.getMin().getZ(), renderBounds.getMax().getX(), renderBounds.getMax().getY(), renderBounds.getMax().getZ());//This forces the client to rerender the chunks } protected void cleanupShellBlocks(BranchDestructionData destroyData) { diff --git a/common/src/main/java/com/dtteam/dynamictrees/entity/animation/FalloverAnimationHandler.java b/common/src/main/java/com/dtteam/dynamictrees/entity/animation/FalloverAnimationHandler.java index dbf41d0f0..be22de373 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/entity/animation/FalloverAnimationHandler.java +++ b/common/src/main/java/com/dtteam/dynamictrees/entity/animation/FalloverAnimationHandler.java @@ -191,7 +191,7 @@ private ParticleOptions getParticle(FallingTreeEntity entity, BlockState leavesS } protected void spawnParticlesAtLeaves(FallingTreeEntity entity, BlockPos leavesPos, ParticleOptions particle, Vec3 velocity, RandomSource rand, int particleCount, double limitChance){ - Vec3 newPos = getRelativeLeavesPosition(entity, leavesPos.getCenter()); + Vec3 newPos = getRelativeLeavesPosition(entity, Vec3.atCenterOf(leavesPos)); for (int j=0; jDT'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..c468fa8b8 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); @@ -157,7 +158,7 @@ public void renderToBuffer(PoseStack.Pose pose, VertexConsumer buffer, int packe g *= diffuse; b *= diffuse; } - int newColor = ((int)(r * 255F) & 255) << 16 | ((int)(g * 255F) & 255) << 8 | ((int)(b * 255F) & 255); + int newColor = 0xFF000000 | ((int)(r * 255F) & 255) << 16 | ((int)(g * 255F) & 255) << 8 | ((int)(b * 255F) & 255); QuadInstance instance = new QuadInstance(); instance.setColor(newColor); instance.setLightCoords(packedLight); diff --git a/common/src/main/java/com/dtteam/dynamictrees/systems/genfeature/BeeNestGenFeature.java b/common/src/main/java/com/dtteam/dynamictrees/systems/genfeature/BeeNestGenFeature.java index 8ea3b63f6..53957cf80 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/systems/genfeature/BeeNestGenFeature.java +++ b/common/src/main/java/com/dtteam/dynamictrees/systems/genfeature/BeeNestGenFeature.java @@ -1,5 +1,7 @@ package com.dtteam.dynamictrees.systems.genfeature; +import net.minecraft.world.level.block.entity.BlockEntityTypes; + import com.dtteam.dynamictrees.api.configuration.ConfigurationProperty; import com.dtteam.dynamictrees.api.function.TetraFunction; import com.dtteam.dynamictrees.data.tags.DTBiomeTags; @@ -145,7 +147,7 @@ protected boolean placeBeeNestWithBees(LevelAccessor world, Block nestBlock, Blo nestState = nestState.setValue(BeehiveBlock.FACING, faceDir); } world.setBlock(pos, nestState, 3); - world.getBlockEntity(pos, BlockEntityType.BEEHIVE).ifPresent((blockEntity) -> { + world.getBlockEntity(pos, BlockEntityTypes.BEEHIVE).ifPresent((blockEntity) -> { int j = 2 + random.nextInt(2); for(int k = 0; k < j; ++k) { diff --git a/common/src/main/java/com/dtteam/dynamictrees/systems/genfeature/package-info.java b/common/src/main/java/com/dtteam/dynamictrees/systems/genfeature/package-info.java deleted file mode 100644 index 11080a1e3..000000000 --- a/common/src/main/java/com/dtteam/dynamictrees/systems/genfeature/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@MethodsReturnNonnullByDefault -package com.dtteam.dynamictrees.systems.genfeature; - -import com.mojang.logging.annotations.MethodsReturnNonnullByDefault; \ No newline at end of file diff --git a/common/src/main/java/com/dtteam/dynamictrees/systems/season/NormalSeasonManager.java b/common/src/main/java/com/dtteam/dynamictrees/systems/season/NormalSeasonManager.java index 560a68cfd..490cdefec 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/systems/season/NormalSeasonManager.java +++ b/common/src/main/java/com/dtteam/dynamictrees/systems/season/NormalSeasonManager.java @@ -6,7 +6,7 @@ import com.dtteam.dynamictrees.api.season.SeasonProvider; import net.minecraft.core.BlockPos; import net.minecraft.resources.Identifier; -import net.minecraft.util.Tuple; +import com.mojang.datafixers.util.Pair; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.biome.Biome; @@ -22,23 +22,23 @@ public class NormalSeasonManager implements SeasonManager { public static final Supplier NULL = NormalSeasonManager::new; private final Map seasonContextMap = new HashMap<>(); - private Function> seasonMapper = w -> new Tuple<>(new NullSeasonProvider(), new NullSeasonGrowthCalculator()); + private Function> seasonMapper = w -> new Pair<>(new NullSeasonProvider(), new NullSeasonGrowthCalculator()); public NormalSeasonManager() { } - public NormalSeasonManager(Function> seasonMapper) { + public NormalSeasonManager(Function> seasonMapper) { this.seasonMapper = seasonMapper; } - private Tuple createProvider(Level level) { + private Pair createProvider(Level level) { return seasonMapper.apply(level); } private SeasonContext getContext(Level level) { return seasonContextMap.computeIfAbsent(level.dimension().identifier(), d -> { - Tuple tuple = createProvider(level); - return new SeasonContext(tuple.getA(), tuple.getB()); + Pair tuple = createProvider(level); + return new SeasonContext(tuple.getFirst(), tuple.getSecond()); }); } diff --git a/common/src/main/java/com/dtteam/dynamictrees/tree/family/AerialRootsFamily.java b/common/src/main/java/com/dtteam/dynamictrees/tree/family/AerialRootsFamily.java index 3e31f75e4..cdbe15304 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/tree/family/AerialRootsFamily.java +++ b/common/src/main/java/com/dtteam/dynamictrees/tree/family/AerialRootsFamily.java @@ -1,5 +1,7 @@ package com.dtteam.dynamictrees.tree.family; +import com.dtteam.dynamictrees.utility.TagUtils; + import com.dtteam.dynamictrees.DynamicTrees; import com.dtteam.dynamictrees.api.registry.TypedRegistry; import com.dtteam.dynamictrees.block.branch.BasicRootsBlock; @@ -214,16 +216,16 @@ public Optional getPrimitiveCoveredRoots() { } @Override - public void addGeneratedBlockTags (Function, TagAppender> tagAppender){ + public void addGeneratedBlockTags (Function, TagAppender> tagAppender){ super.addGeneratedBlockTags(tagAppender); //Create roots tag and root harvest tag if the family is mangrove-like. getRoots().ifPresent(roots -> { - this.tierTag(getDefaultRootsHarvestTier(), tagAppender).ifPresent(tagBuilder -> tagBuilder.add(roots)); + this.tierTag(getDefaultRootsHarvestTier(), tagAppender).ifPresent(tagBuilder -> tagBuilder.add(TagUtils.key(roots))); defaultRootsTags().forEach(tag -> { if (!isOnlyIfLoaded()) { - tagAppender.apply(tag).add(roots); + tagAppender.apply(tag).add(TagUtils.key(roots)); } else { - tagAppender.apply(tag).addOptional(roots); + tagAppender.apply(tag).addOptional(TagUtils.key(roots)); } }); }); diff --git a/common/src/main/java/com/dtteam/dynamictrees/tree/family/AltBranchFamily.java b/common/src/main/java/com/dtteam/dynamictrees/tree/family/AltBranchFamily.java index 870c72d2d..6353aa36a 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/tree/family/AltBranchFamily.java +++ b/common/src/main/java/com/dtteam/dynamictrees/tree/family/AltBranchFamily.java @@ -1,5 +1,7 @@ package com.dtteam.dynamictrees.tree.family; +import com.dtteam.dynamictrees.utility.TagUtils; + import com.dtteam.dynamictrees.DynamicTrees; import com.dtteam.dynamictrees.api.registry.TypedRegistry; import com.dtteam.dynamictrees.block.branch.BasicBranchBlock; @@ -110,15 +112,15 @@ public void addBranchTextures(BiConsumer textureConsumer, Id super.addBranchTextures(textureConsumer, primitiveLogLocation, sourceBlock); } - public void addGeneratedBlockTags (Function, TagAppender> tagAppender){ + public void addGeneratedBlockTags (Function, TagAppender> tagAppender){ super.addGeneratedBlockTags(tagAppender); getAltBranch().ifPresent(branch -> { - tierTag(getDefaultBranchHarvestTier(), tagAppender).ifPresent(tagBuilder -> tagBuilder.add(branch)); + tierTag(getDefaultBranchHarvestTier(), tagAppender).ifPresent(tagBuilder -> tagBuilder.add(TagUtils.key(branch))); defaultBranchTags().forEach(tag -> { if (!isOnlyIfLoaded()) { - tagAppender.apply(tag).add(branch); + tagAppender.apply(tag).add(TagUtils.key(branch)); } else { - tagAppender.apply(tag).addOptional(branch); + tagAppender.apply(tag).addOptional(TagUtils.key(branch)); } }); }); diff --git a/common/src/main/java/com/dtteam/dynamictrees/tree/family/CreakingHeartFamily.java b/common/src/main/java/com/dtteam/dynamictrees/tree/family/CreakingHeartFamily.java index 2c07153a0..3b7ebf3bc 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/tree/family/CreakingHeartFamily.java +++ b/common/src/main/java/com/dtteam/dynamictrees/tree/family/CreakingHeartFamily.java @@ -1,5 +1,7 @@ package com.dtteam.dynamictrees.tree.family; +import com.dtteam.dynamictrees.utility.TagUtils; + import com.dtteam.dynamictrees.DynamicTrees; import com.dtteam.dynamictrees.api.registry.TypedRegistry; import com.dtteam.dynamictrees.block.branch.*; @@ -113,15 +115,15 @@ public void addHeartTextures(BiConsumer textureConsumer, Ide } } - public void addGeneratedBlockTags (Function, TagAppender> tagAppender){ + public void addGeneratedBlockTags (Function, TagAppender> tagAppender){ super.addGeneratedBlockTags(tagAppender); getHeartBranch().ifPresent(branch -> { - tierTag(getDefaultBranchHarvestTier(), tagAppender).ifPresent(tagBuilder -> tagBuilder.add(branch)); + tierTag(getDefaultBranchHarvestTier(), tagAppender).ifPresent(tagBuilder -> tagBuilder.add(TagUtils.key(branch))); defaultBranchTags().forEach(tag -> { if (!isOnlyIfLoaded()) { - tagAppender.apply(tag).add(branch); + tagAppender.apply(tag).add(TagUtils.key(branch)); } else { - tagAppender.apply(tag).addOptional(branch); + tagAppender.apply(tag).addOptional(TagUtils.key(branch)); } }); }); diff --git a/common/src/main/java/com/dtteam/dynamictrees/tree/family/Family.java b/common/src/main/java/com/dtteam/dynamictrees/tree/family/Family.java index a478eb678..def313158 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/tree/family/Family.java +++ b/common/src/main/java/com/dtteam/dynamictrees/tree/family/Family.java @@ -1,5 +1,7 @@ package com.dtteam.dynamictrees.tree.family; +import com.dtteam.dynamictrees.utility.TagUtils; + import com.dtteam.dynamictrees.DynamicTrees; import com.dtteam.dynamictrees.api.registry.RegistryEntry; import com.dtteam.dynamictrees.api.registry.RegistryHandler; @@ -311,9 +313,11 @@ public boolean isWood() { } protected Optional getBranchBlock(int index) { + if (index < 0 || index >= branches.size()) return Optional.empty(); // Some families (e.g. from add-on tree packs) have no stripped branch entry. return Optionals.ofBlock(branches.get(index).getBlock()); } protected Optional getBranchItem(int index) { + if (index < 0 || index >= branches.size()) return Optional.empty(); return branches.get(index).getItem(); } @@ -715,33 +719,33 @@ public List> defaultStrippedBranchTags() { Collections.singletonList(DTBlockTags.STRIPPED_BRANCHES_THAT_BURN); } - public void addGeneratedBlockTags (Function, TagAppender> tagAppender){ + public void addGeneratedBlockTags (Function, TagAppender> tagAppender){ getBranch().ifPresent(branch -> { - tierTag(getDefaultBranchHarvestTier(), tagAppender).ifPresent(tagBuilder -> tagBuilder.add(branch)); + tierTag(getDefaultBranchHarvestTier(), tagAppender).ifPresent(tagBuilder -> tagBuilder.add(TagUtils.key(branch))); defaultBranchTags().forEach(tag -> { if (!isOnlyIfLoaded()) { - tagAppender.apply(tag).add(branch); + tagAppender.apply(tag).add(TagUtils.key(branch)); } else { - tagAppender.apply(tag).addOptional(branch); + tagAppender.apply(tag).addOptional(TagUtils.key(branch)); } }); }); // Create stripped branch tag and harvest tag if the family has a stripped branch. getStrippedBranch().ifPresent(strippedBranch -> { - tierTag(getDefaultStrippedBranchHarvestTier(), tagAppender).ifPresent(tagBuilder -> tagBuilder.add(strippedBranch)); + tierTag(getDefaultStrippedBranchHarvestTier(), tagAppender).ifPresent(tagBuilder -> tagBuilder.add(TagUtils.key(strippedBranch))); defaultStrippedBranchTags().forEach(tag -> { if (!isOnlyIfLoaded()) { - tagAppender.apply(tag).add(strippedBranch); + tagAppender.apply(tag).add(TagUtils.key(strippedBranch)); } else { - tagAppender.apply(tag).addOptional(strippedBranch); + tagAppender.apply(tag).addOptional(TagUtils.key(strippedBranch)); } }); }); } - protected Optional> tierTag(@Nullable ToolMaterial tier, Function, TagAppender> tagAppender) { + protected Optional> tierTag(@Nullable ToolMaterial tier, Function, TagAppender> tagAppender) { if (tier == null) return Optional.empty(); @@ -750,12 +754,12 @@ protected Optional> tierTag(@Nullable ToolMaterial tie return Optional.of(tagAppender.apply(tag)); } - public void addGeneratedItemTags (Function, TagAppender> tagAppender){ + public void addGeneratedItemTags (Function, TagAppender> tagAppender){ getBranchItem().ifPresent(item -> { if (!isOnlyIfLoaded()) { - defaultBranchItemTags().forEach(tag -> tagAppender.apply(tag).add(item)); + defaultBranchItemTags().forEach(tag -> tagAppender.apply(tag).add(TagUtils.key(item))); } else { - defaultBranchItemTags().forEach(tag -> tagAppender.apply(tag).addOptional(item)); + defaultBranchItemTags().forEach(tag -> tagAppender.apply(tag).addOptional(TagUtils.key(item))); } } ); diff --git a/common/src/main/java/com/dtteam/dynamictrees/tree/family/MossyAerialRootsFamily.java b/common/src/main/java/com/dtteam/dynamictrees/tree/family/MossyAerialRootsFamily.java index 9ecbcdcf5..4a2c5366c 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/tree/family/MossyAerialRootsFamily.java +++ b/common/src/main/java/com/dtteam/dynamictrees/tree/family/MossyAerialRootsFamily.java @@ -1,5 +1,7 @@ package com.dtteam.dynamictrees.tree.family; +import com.dtteam.dynamictrees.utility.TagUtils; + import com.dtteam.dynamictrees.DynamicTrees; import com.dtteam.dynamictrees.api.registry.TypedRegistry; import com.dtteam.dynamictrees.block.branch.BasicRootsBlock; @@ -107,16 +109,16 @@ public List getBlockModelGenerators() { } @Override - public void addGeneratedBlockTags (Function, TagAppender> tagAppender){ + public void addGeneratedBlockTags (Function, TagAppender> tagAppender){ super.addGeneratedBlockTags(tagAppender); //Create roots tag and root harvest tag if the family is mangrove-like. getMossyRoots().ifPresent(roots -> { - this.tierTag(getDefaultRootsHarvestTier(), tagAppender).ifPresent(tagBuilder -> tagBuilder.add(roots)); + this.tierTag(getDefaultRootsHarvestTier(), tagAppender).ifPresent(tagBuilder -> tagBuilder.add(TagUtils.key(roots))); defaultRootsTags().forEach(tag -> { if (!isOnlyIfLoaded()) { - tagAppender.apply(tag).add(roots); + tagAppender.apply(tag).add(TagUtils.key(roots)); } else { - tagAppender.apply(tag).addOptional(roots); + tagAppender.apply(tag).addOptional(TagUtils.key(roots)); } }); }); diff --git a/common/src/main/java/com/dtteam/dynamictrees/tree/species/Species.java b/common/src/main/java/com/dtteam/dynamictrees/tree/species/Species.java index adb8bc4be..0a059d87b 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/tree/species/Species.java +++ b/common/src/main/java/com/dtteam/dynamictrees/tree/species/Species.java @@ -1,5 +1,7 @@ package com.dtteam.dynamictrees.tree.species; +import com.dtteam.dynamictrees.utility.TagUtils; + import com.dtteam.dynamictrees.DynamicTrees; import com.dtteam.dynamictrees.api.lazyvalue.LazyValue; import com.dtteam.dynamictrees.api.network.BranchDestructionData; @@ -2387,20 +2389,20 @@ public Identifier getSeedParentModelLocation() { return DynamicTrees.location("item/standard_seed"); } - public void addGeneratedBlockTags (Function, TagAppender> tagAppender){ + public void addGeneratedBlockTags (Function, TagAppender> tagAppender){ // Create dynamic sapling block tags. getSapling().ifPresent(sapling -> defaultSaplingTags().forEach(tag -> { if (isOnlyIfLoaded()) { - tagAppender.apply(tag).addOptional(sapling); + tagAppender.apply(tag).addOptional(TagUtils.key(sapling)); } else { - tagAppender.apply(tag).add(sapling); + tagAppender.apply(tag).add(TagUtils.key(sapling)); } }) ); } - public void addGeneratedItemTags (Function, TagAppender> tagAppender){ + public void addGeneratedItemTags (Function, TagAppender> tagAppender){ // Some species return the common seed, so only return if the species has its own seed. if (!hasSeed()) { return; @@ -2409,9 +2411,9 @@ public void addGeneratedItemTags (Function, TagAppender getSeed().ifPresent(seed -> defaultSeedTags().forEach(tag ->{ if (isOnlyIfLoaded()) { - tagAppender.apply(tag).addOptional(seed); + tagAppender.apply(tag).addOptional(TagUtils.key(seed)); } else { - tagAppender.apply(tag).add(seed); + tagAppender.apply(tag).add(TagUtils.key(seed)); } }) ); diff --git a/common/src/main/java/com/dtteam/dynamictrees/treepack/package-info.java b/common/src/main/java/com/dtteam/dynamictrees/treepack/package-info.java deleted file mode 100644 index 0836ea062..000000000 --- a/common/src/main/java/com/dtteam/dynamictrees/treepack/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@MethodsReturnNonnullByDefault -package com.dtteam.dynamictrees.treepack; - -import com.mojang.logging.annotations.MethodsReturnNonnullByDefault; \ No newline at end of file diff --git a/common/src/main/java/com/dtteam/dynamictrees/utility/TagUtils.java b/common/src/main/java/com/dtteam/dynamictrees/utility/TagUtils.java new file mode 100644 index 000000000..5ecd81d71 --- /dev/null +++ b/common/src/main/java/com/dtteam/dynamictrees/utility/TagUtils.java @@ -0,0 +1,23 @@ +package com.dtteam.dynamictrees.utility; + +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; + +/** + * Helpers for converting registered objects to the {@link ResourceKey}s required by + * {@link net.minecraft.data.tags.TagAppender} since 26.2. + */ +public final class TagUtils { + + private TagUtils() {} + + public static ResourceKey key(Block block) { + return BuiltInRegistries.BLOCK.getResourceKey(block).orElseThrow(); + } + + public static ResourceKey key(Item item) { + return BuiltInRegistries.ITEM.getResourceKey(item).orElseThrow(); + } +} diff --git a/common/src/main/java/com/dtteam/dynamictrees/worldgen/featurecancellation/TreeFeatureCanceller.java b/common/src/main/java/com/dtteam/dynamictrees/worldgen/featurecancellation/TreeFeatureCanceller.java index 8c1e0b6b4..0c161117d 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/worldgen/featurecancellation/TreeFeatureCanceller.java +++ b/common/src/main/java/com/dtteam/dynamictrees/worldgen/featurecancellation/TreeFeatureCanceller.java @@ -47,8 +47,8 @@ but one (unclean) solution may be to add the non-tree features back to the gener private boolean doesContainTrees(RandomFeatureConfiguration featureConfig, BiomePropertySelectors.NormalFeatureCancellation featureCancellations) { - for (WeightedPlacedFeature feature : featureConfig.features) { - final PlacedFeature currentConfiguredFeature = feature.feature.value(); + for (WeightedPlacedFeature feature : featureConfig.features()) { + final PlacedFeature currentConfiguredFeature = feature.feature().value(); ConfiguredFeature cf = currentConfiguredFeature.getFeatures() .findFirst().map(Holder::value).orElse(null); if (cf == null) return false; diff --git a/common/src/main/java/com/dtteam/dynamictrees/worldgen/structure/TreePoolElement.java b/common/src/main/java/com/dtteam/dynamictrees/worldgen/structure/TreePoolElement.java index 132ef0d24..690d3de71 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/worldgen/structure/TreePoolElement.java +++ b/common/src/main/java/com/dtteam/dynamictrees/worldgen/structure/TreePoolElement.java @@ -2,7 +2,6 @@ import com.dtteam.dynamictrees.registry.DTRegistries; import com.dtteam.dynamictrees.tree.species.Species; -import com.mojang.logging.annotations.MethodsReturnNonnullByDefault; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; import net.minecraft.core.BlockPos; @@ -30,7 +29,6 @@ /** * @author Harley O'Connor */ -@MethodsReturnNonnullByDefault public final class TreePoolElement extends StructurePoolElement { public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance diff --git a/common/src/main/resources/META-INF/dynamictrees.accesswidener b/common/src/main/resources/META-INF/dynamictrees.accesswidener index 39ea50edd..e80d66106 100644 --- a/common/src/main/resources/META-INF/dynamictrees.accesswidener +++ b/common/src/main/resources/META-INF/dynamictrees.accesswidener @@ -7,23 +7,31 @@ accessible field net/minecraft/world/level/storage/loot/parameters/LootContextPa accessible method net/minecraft/world/level/levelgen/feature/stateproviders/BlockStateProviderType (Lcom/mojang/serialization/MapCodec;)V accessible field net/minecraft/world/level/levelgen/structure/pools/StructureTemplatePool rawTemplates Ljava/util/List; accessible field net/minecraft/world/level/levelgen/structure/pools/StructureTemplatePool templates Lit/unimi/dsi/fastutil/objects/ObjectArrayList; -accessible field net/minecraft/world/item/crafting/RecipeManager byType Lcom/google/common/collect/Multimap; -accessible field net/minecraft/world/item/Item craftingRemainingItem Lnet/minecraft/world/item/Item; -mutable field net/minecraft/world/item/Item craftingRemainingItem Lnet/minecraft/world/item/Item; +accessible method net/minecraft/world/item/Item getCraftingRemainder ()Lnet/minecraft/world/item/ItemStackTemplate; +extendable method net/minecraft/world/item/Item getCraftingRemainder ()Lnet/minecraft/world/item/ItemStackTemplate; +accessible field net/minecraft/world/item/Item craftingRemainingItem Lnet/minecraft/world/item/ItemStackTemplate; +mutable field net/minecraft/world/item/Item craftingRemainingItem Lnet/minecraft/world/item/ItemStackTemplate; accessible field net/minecraft/world/level/chunk/LevelChunk loaded Z -accessible field net/minecraft/world/level/levelgen/feature/stateproviders/WeightedStateProvider weightedList Lnet/minecraft/util/random/SimpleWeightedRandomList; +accessible field net/minecraft/world/level/levelgen/feature/stateproviders/WeightedStateProvider weightedList Lnet/minecraft/util/random/WeightedList; accessible field net/minecraft/commands/synchronization/ArgumentTypeInfos BY_CLASS Ljava/util/Map; accessible class net/minecraft/world/inventory/BrewingStandMenu$PotionSlot -accessible class net/minecraft/data/tags/IntrinsicHolderTagsProvider$IntrinsicTagAppender -accessible method net/minecraft/client/renderer/texture/SpriteContents metadata ()Lnet/minecraft/server/packs/resources/ResourceMetadata; accessible field net/minecraft/client/renderer/texture/SpriteContents originalImage Lcom/mojang/blaze3d/platform/NativeImage; -accessible method net/minecraft/client/renderer/texture/atlas/SpriteSources register (Ljava/lang/String;Lcom/mojang/serialization/MapCodec;)Lnet/minecraft/client/renderer/texture/atlas/SpriteSourceType; +accessible field net/minecraft/client/renderer/texture/SpriteContents additionalMetadata Ljava/util/List; accessible method net/minecraft/client/data/models/model/TextureSlot create (Ljava/lang/String;)Lnet/minecraft/client/data/models/model/TextureSlot; +accessible field net/minecraft/world/level/block/entity/BlockEntity type Lnet/minecraft/world/level/block/entity/BlockEntityType; mutable field net/minecraft/world/level/block/entity/BlockEntity type Lnet/minecraft/world/level/block/entity/BlockEntityType; -mutable field net/minecraft/world/level/block/entity/BlockEntity blockState Lnet/minecraft/world/level/block/state/BlockState; \ No newline at end of file +accessible field net/minecraft/world/level/block/entity/BlockEntity blockState Lnet/minecraft/world/level/block/state/BlockState; +mutable field net/minecraft/world/level/block/entity/BlockEntity blockState Lnet/minecraft/world/level/block/state/BlockState; + +extendable method net/minecraft/world/level/block/entity/CreakingHeartBlockEntity spreadResin (Lnet/minecraft/server/level/ServerLevel;)Ljava/util/Optional; +accessible field net/minecraft/world/level/block/entity/CreakingHeartBlockEntity ticksExisted J + +accessible field net/minecraft/client/renderer/block/dispatch/multipart/MultiPartModel models Ljava/util/List; + +accessible field net/minecraft/client/color/item/ItemTintSources ID_MAPPER Lnet/minecraft/util/ExtraCodecs$LateBoundIdMapper; diff --git a/docs/superpowers/plans/2026-08-09-fabric-creaking-heart-and-config-sync.md b/docs/superpowers/plans/2026-08-09-fabric-creaking-heart-and-config-sync.md new file mode 100644 index 000000000..f6bdd2bb9 --- /dev/null +++ b/docs/superpowers/plans/2026-08-09-fabric-creaking-heart-and-config-sync.md @@ -0,0 +1,281 @@ +# Fabric Creaking Heart and Config Sync Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Prevent Fabric creaking-heart block entities from invoking vanilla's incompatible axis lookup, then ship the corrected jar and canonical Ashfall configs to the live Prism instance. + +**Architecture:** Add Fabric's missing copy of the already-proven NeoForge mixin and register it in the Fabric mixin configuration. The mixin intercepts only Dynamic Trees creaking-heart branch states and delegates to their branch-aware log check. Ashfall remains the artifact and config source of truth, and its existing bounded deployment path replaces the live jar and both comment/format-drifted configs with automatic backups. + +**Tech Stack:** Java 25, Minecraft 26.2, Fabric Loader 0.19.3, Fabric Loom 1.17.17, Sponge Mixin, Gradle, Python 3 Ashfall tooling, Prism Launcher. + +## Global Constraints + +- Do not add `AXIS` to Dynamic Trees branch states. +- Do not change creaking-heart activation, resin, loot, or connectivity behavior. +- Keep the NeoForge mixin unchanged. +- Do not change any Dynamic Trees config key or value. +- Ashfall's committed `dynamictrees-common.toml` and `dynamictrees-server.toml` are canonical. +- Do not edit DTBOP or any save data. +- Preserve Dynamic Trees' untracked `.vscode/` directory. +- Deploy only through Ashfall's bounded deployment script so every replaced live file is backed up. + +--- + +### Task 1: Restore the Creaking-Heart Redirect on Fabric + +**Files:** +- Create: `fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinCreakingHeartBlock.java` +- Modify: `fabric/src/main/resources/dynamictrees.fabric.mixins.json:10` + +**Interfaces:** +- Consumes: vanilla static `CreakingHeartBlock.hasRequiredLogs(BlockState, LevelReader, BlockPos)` and Dynamic Trees static `CreakingHeartBranchBlock.hasRequiredLogs(BlockState, LevelReader, BlockPos)`. +- Produces: a cancellable head injection that returns the branch-aware result only for `CreakingHeartBranchBlock` states. + +- [ ] **Step 1: Run the Fabric mixin artifact contract and verify RED** + +Run from `/Volumes/Dev/Projects/krona/minecraft-mods/DynamicTrees`: + +```bash +python3 -c 'import json; from pathlib import Path; source = Path("fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinCreakingHeartBlock.java"); config = json.loads(Path("fabric/src/main/resources/dynamictrees.fabric.mixins.json").read_text()); missing = []; missing += [] if source.is_file() else ["Fabric mixin source"]; missing += [] if "MixinCreakingHeartBlock" in config["mixins"] else ["Fabric mixin registration"]; assert not missing, f"missing creaking-heart compatibility: {missing}"' +``` + +Expected: exit 1 listing both `Fabric mixin source` and `Fabric mixin registration`. + +- [ ] **Step 2: Create the focused Fabric mixin** + +Create `MixinCreakingHeartBlock.java` with exactly this implementation, matching the working NeoForge source: + +```java +package com.dtteam.dynamictrees.mixin; + +import com.dtteam.dynamictrees.block.branch.CreakingHeartBranchBlock; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.CreakingHeartBlock; +import net.minecraft.world.level.block.state.BlockState; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(CreakingHeartBlock.class) +public class MixinCreakingHeartBlock { + + @Inject(method = "hasRequiredLogs", at = @At("HEAD"), cancellable = true) + private static void hasRequiredLogs(BlockState state, LevelReader level, BlockPos pos, CallbackInfoReturnable cir) { + if (state.getBlock() instanceof CreakingHeartBranchBlock) { + cir.setReturnValue(CreakingHeartBranchBlock.hasRequiredLogs(state, level, pos)); + } + } + +} +``` + +- [ ] **Step 3: Register the mixin in Fabric resources** + +Add `"MixinCreakingHeartBlock",` to the `mixins` array in `dynamictrees.fabric.mixins.json`, immediately before `"MixinMinecraftServer"`. + +- [ ] **Step 4: Re-run the source contract and verify GREEN** + +Run the command from Step 1 again. + +Expected: exit 0 with no output. + +- [ ] **Step 5: Clean-build the Fabric jar** + +Run: + +```bash +./gradlew clean :fabric:build +``` + +Expected: `BUILD SUCCESSFUL` and `fabric/build/libs/dynamictrees-fabric-26.2-1.8.0-BETA03.jar` exists. + +- [ ] **Step 6: Verify the packaged class and mixin registration** + +Run: + +```bash +jar tf fabric/build/libs/dynamictrees-fabric-26.2-1.8.0-BETA03.jar | rg '^com/dtteam/dynamictrees/mixin/MixinCreakingHeartBlock.class$' +unzip -p fabric/build/libs/dynamictrees-fabric-26.2-1.8.0-BETA03.jar dynamictrees.fabric.mixins.json | python3 -c 'import json, sys; config = json.load(sys.stdin); assert "MixinCreakingHeartBlock" in config["mixins"]' +``` + +Expected: the first command prints the class path and the second exits 0. + +- [ ] **Step 7: Review and commit the Fabric fix** + +Run: + +```bash +git diff --check +git diff -- fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinCreakingHeartBlock.java fabric/src/main/resources/dynamictrees.fabric.mixins.json +git add fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinCreakingHeartBlock.java fabric/src/main/resources/dynamictrees.fabric.mixins.json +git commit -m "fix: redirect creaking heart log checks on Fabric" +``` + +Expected: exactly the new mixin and Fabric mixin JSON are committed; `.vscode/` remains unstaged. + +--- + +### Task 2: Pin and Deploy the Fixed Jar and Canonical Configs + +**Files:** +- Replace: `/Volumes/Dev/Projects/krona/minecraft-mods/Ashfall/mods/dist/dynamictrees-fabric-26.2-1.8.0-BETA03.jar` +- Modify: `/Volumes/Dev/Projects/krona/minecraft-mods/Ashfall/manifest/dynamic-trees.local.toml` +- Modify: `/Volumes/Dev/Projects/krona/minecraft-mods/Ashfall/baseline/mods-sha256.txt` +- Regenerate: `/Volumes/Dev/Projects/krona/minecraft-mods/Ashfall/docs/audits/release-file-inventory.json` +- Deploy unchanged canonical sources: `/Volumes/Dev/Projects/krona/minecraft-mods/Ashfall/defaultconfigs/dynamictrees-common.toml` +- Deploy unchanged canonical sources: `/Volumes/Dev/Projects/krona/minecraft-mods/Ashfall/defaultconfigs/dynamictrees-server.toml` + +**Interfaces:** +- Consumes: the Task 1 Fabric jar and source commit. +- Produces: an Ashfall-pinned artifact, three backed-up live replacements, byte-identical live configs, and a refreshed release audit. + +- [ ] **Step 1: Run the Ashfall/live integration assertions and verify RED** + +Run from `/Volumes/Dev/Projects/krona/minecraft-mods/Ashfall`: + +```bash +python3 -c 'import hashlib; from pathlib import Path; built = Path("../DynamicTrees/fabric/build/libs/dynamictrees-fabric-26.2-1.8.0-BETA03.jar"); pinned = Path("mods/dist/dynamictrees-fabric-26.2-1.8.0-BETA03.jar"); live_root = Path("/Users/krona/Library/Application Support/PrismLauncher/instances/26.2/minecraft"); failures = []; failures += [] if hashlib.sha256(built.read_bytes()).digest() == hashlib.sha256(pinned.read_bytes()).digest() else ["Ashfall jar"]; failures += [] if (Path("defaultconfigs/dynamictrees-common.toml").read_bytes() == (live_root / "config/dynamictrees-common.toml").read_bytes()) else ["common config"]; failures += [] if (Path("defaultconfigs/dynamictrees-server.toml").read_bytes() == (live_root / "config/dynamictrees-server.toml").read_bytes()) else ["server config"]; assert not failures, f"out-of-sync Dynamic Trees assets: {failures}"' +``` + +Expected: exit 1 listing `Ashfall jar`, `common config`, and `server config`. + +- [ ] **Step 2: Copy the exact built jar into Ashfall** + +Run: + +```bash +cp ../DynamicTrees/fabric/build/libs/dynamictrees-fabric-26.2-1.8.0-BETA03.jar mods/dist/dynamictrees-fabric-26.2-1.8.0-BETA03.jar +shasum -a 256 ../DynamicTrees/fabric/build/libs/dynamictrees-fabric-26.2-1.8.0-BETA03.jar mods/dist/dynamictrees-fabric-26.2-1.8.0-BETA03.jar +``` + +Expected: both printed SHA-256 values are identical. + +- [ ] **Step 3: Update artifact provenance and the expected live hash** + +Run `git -C ../DynamicTrees rev-parse HEAD` and `shasum -a 256 mods/dist/dynamictrees-fabric-26.2-1.8.0-BETA03.jar`. + +Using `apply_patch`, set `manifest/dynamic-trees.local.toml`'s `source-revision` to the literal 40-character Task 1 commit and `sha256` to the literal 64-character artifact hash. Replace the Dynamic Trees line in `baseline/mods-sha256.txt` with that same hash. Do not alter DTBOP's manifest or hash. + +- [ ] **Step 4: Run Ashfall tests and inspect the full deployment dry run** + +Run: + +```bash +python3 -m unittest discover -s tests -v +python3 scripts/deploy.py --manifest deploy/ashfall-files.json --dry-run +``` + +Expected: all 63 tracked tests pass. The dry run identifies the Dynamic Trees jar plus `dynamictrees-common.toml` and `dynamictrees-server.toml` as changes. If it identifies any other changed non-Dynamic-Trees target, inspect that difference before applying. + +- [ ] **Step 5: Apply the bounded deployment** + +After confirming Minecraft is closed, run: + +```bash +python3 scripts/deploy.py --manifest deploy/ashfall-files.json --apply +``` + +Expected: Ashfall prints a timestamped backup path containing the previous live jar and both previous live config files. + +- [ ] **Step 6: Re-run the integration assertions and verify GREEN** + +Run the command from Step 1 again. + +Expected: exit 0 with no output. + +- [ ] **Step 7: Verify live artifact equality and pack validity** + +Run: + +```bash +python3 scripts/validate-pack.py --repo "$PWD" --instance "/Users/krona/Library/Application Support/PrismLauncher/instances/26.2" +shasum -a 256 ../DynamicTrees/fabric/build/libs/dynamictrees-fabric-26.2-1.8.0-BETA03.jar mods/dist/dynamictrees-fabric-26.2-1.8.0-BETA03.jar "/Users/krona/Library/Application Support/PrismLauncher/instances/26.2/minecraft/mods/dynamictrees-fabric-26.2-1.8.0-BETA03.jar" +cmp -s defaultconfigs/dynamictrees-common.toml "/Users/krona/Library/Application Support/PrismLauncher/instances/26.2/minecraft/config/dynamictrees-common.toml" +cmp -s defaultconfigs/dynamictrees-server.toml "/Users/krona/Library/Application Support/PrismLauncher/instances/26.2/minecraft/config/dynamictrees-server.toml" +``` + +Expected: validation passes, all three jar hashes match, and both `cmp` commands exit 0. + +- [ ] **Step 8: Commit Ashfall's rebuilt artifact metadata** + +Run: + +```bash +git diff --check +git status --short +git add mods/dist/dynamictrees-fabric-26.2-1.8.0-BETA03.jar manifest/dynamic-trees.local.toml baseline/mods-sha256.txt +git commit -m "fix: update Dynamic Trees creaking heart port" +``` + +Expected: the commit contains the jar, its local manifest, and the baseline hash only. The canonical config files remain unchanged in Git because they were already the source of truth. + +- [ ] **Step 9: Regenerate and commit the now-verifiable release inventory** + +Run: + +```bash +ASHFALL_PORT_COMMIT="$(git rev-parse HEAD)" +python3 scripts/release_inventory.py --repo "$PWD" --manifest deploy/ashfall-files.json --commit "$ASHFALL_PORT_COMMIT" --output docs/audits/release-file-inventory.json +git add docs/audits/release-file-inventory.json +git commit -m "docs: refresh Ashfall release inventory" +``` + +Expected: inventory generation succeeds now that the canonical configs and every full-manifest live target match. The inventory includes the Dynamic Trees and DTBOP local artifacts. + +--- + +### Task 3: Final Verification, Push, and Crash-World Acceptance + +**Files:** +- Verify only; no new source files. + +**Interfaces:** +- Consumes: Task 1's pushed candidate and Task 2's live deployment. +- Produces: a pushed `port/26.2` branch, clean Ashfall commits, and runtime confirmation that the existing heart block no longer crashes the server. + +- [ ] **Step 1: Run fresh source, package, build, and pack checks** + +Run: + +```bash +cd /Volumes/Dev/Projects/krona/minecraft-mods/DynamicTrees +python3 -c 'import json; from pathlib import Path; source = Path("fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinCreakingHeartBlock.java"); config = json.loads(Path("fabric/src/main/resources/dynamictrees.fabric.mixins.json").read_text()); assert source.is_file() and "MixinCreakingHeartBlock" in config["mixins"]' +./gradlew :fabric:build +jar tf fabric/build/libs/dynamictrees-fabric-26.2-1.8.0-BETA03.jar | rg '^com/dtteam/dynamictrees/mixin/MixinCreakingHeartBlock.class$' +unzip -p fabric/build/libs/dynamictrees-fabric-26.2-1.8.0-BETA03.jar dynamictrees.fabric.mixins.json | python3 -c 'import json, sys; assert "MixinCreakingHeartBlock" in json.load(sys.stdin)["mixins"]' +git diff --check +git status --short --branch + +cd /Volumes/Dev/Projects/krona/minecraft-mods/Ashfall +python3 -m unittest discover -s tests -v +python3 -c 'import hashlib; from pathlib import Path; built = Path("../DynamicTrees/fabric/build/libs/dynamictrees-fabric-26.2-1.8.0-BETA03.jar"); pinned = Path("mods/dist/dynamictrees-fabric-26.2-1.8.0-BETA03.jar"); live_root = Path("/Users/krona/Library/Application Support/PrismLauncher/instances/26.2/minecraft"); assert hashlib.sha256(built.read_bytes()).digest() == hashlib.sha256(pinned.read_bytes()).digest(); assert Path("defaultconfigs/dynamictrees-common.toml").read_bytes() == (live_root / "config/dynamictrees-common.toml").read_bytes(); assert Path("defaultconfigs/dynamictrees-server.toml").read_bytes() == (live_root / "config/dynamictrees-server.toml").read_bytes()' +python3 scripts/validate-pack.py --repo "$PWD" --instance "/Users/krona/Library/Application Support/PrismLauncher/instances/26.2" +git diff --check +git status --short --branch +``` + +Expected: every command exits 0; Dynamic Trees shows only `.vscode/` as untracked and Ashfall is clean. + +- [ ] **Step 2: Push the Dynamic Trees port branch** + +Run: + +```bash +git -C /Volumes/Dev/Projects/krona/minecraft-mods/DynamicTrees push fork port/26.2 +``` + +Expected: `fork/port/26.2` advances through the design, plan, and creaking-heart source-fix commits. Ashfall is not pushed because it has no configured remote. + +- [ ] **Step 3: Request the runtime crash-world check** + +Ask the user to restart the Prism `26.2` instance and load the same affected world. The existing block entity at `(-252,79,-182)` must tick without `Cannot get property ... axis ... pale_oak_creaking_heart_branch`, and no save editing or block removal should be needed. + +After the game exits, run: + +```bash +rg -n "Cannot get property.*axis|pale_oak_creaking_heart_branch|Ticking block entity" "/Users/krona/Library/Application Support/PrismLauncher/instances/26.2/minecraft/logs/latest.log" +``` + +Expected: no new match from the post-fix session. Historical crash reports remain untouched. diff --git a/docs/superpowers/plans/2026-08-09-falling-tree-shader-and-dtbop-translations.md b/docs/superpowers/plans/2026-08-09-falling-tree-shader-and-dtbop-translations.md new file mode 100644 index 000000000..354d4afb1 --- /dev/null +++ b/docs/superpowers/plans/2026-08-09-falling-tree-shader-and-dtbop-translations.md @@ -0,0 +1,353 @@ +# Falling Tree Shader and DTBOP Translations Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Make falling Dynamic Trees entities render with shaders and localize every DTBOP branch item on Minecraft 26.2 Fabric, then ship both rebuilt jars through Ashfall. + +**Architecture:** Correct the missing alpha byte at the single point where Dynamic Trees constructs falling-tree quad colors. Correct DTBOP's resource data by mirroring its twelve existing branch-block names under the corresponding item keys. Keep both source changes independently buildable and commit them in their own repositories before Ashfall records their exact revisions and artifacts. + +**Tech Stack:** Java 25, Minecraft 26.2, Fabric Loader 0.19.3, Fabric Loom 1.17.17, Gradle, JSON language resources, Python 3 Ashfall validation, Prism Launcher. + +## Global Constraints + +- Patch the downstream 26.2 ports; do not modify Iris, Sodium, Complementary Reimagined, or their configuration. +- Keep every existing `*_branch` registry ID unchanged. +- Preserve existing tint and diffuse-light calculations. +- Add item translations for all twelve DTBOP branch families, not only Redwood. +- Do not add a new unit-test framework to either upstream-owned mod. +- Preserve unrelated working-tree files, including Dynamic Trees' untracked `.vscode/` directory. +- Deploy only through Ashfall's explicit manifest so the live files receive automatic backups. + +--- + +### Task 1: Make Falling-Tree Quad Colors Opaque + +**Files:** +- Modify: `common/src/main/java/com/dtteam/dynamictrees/model/entity/FallingTreeEntityModel.java:161` + +**Interfaces:** +- Consumes: `float r`, `float g`, and `float b` values already clamped by the existing 8-bit channel packing. +- Produces: `int newColor` in packed ARGB form, with alpha fixed to `0xFF`, for `QuadInstance.setColor(int)`. + +- [ ] **Step 1: Run the focused source-contract check and verify RED** + +Run from `/Volumes/Dev/Projects/krona/minecraft-mods/DynamicTrees`: + +```bash +python3 -c 'from pathlib import Path; source = Path("common/src/main/java/com/dtteam/dynamictrees/model/entity/FallingTreeEntityModel.java").read_text(); assert "int newColor = 0xFF000000 |" in source, "falling-tree color has no opaque alpha byte"' +``` + +Expected: exit 1 with `AssertionError: falling-tree color has no opaque alpha byte`. + +- [ ] **Step 2: Add the opaque alpha byte without changing RGB calculations** + +Replace the current `newColor` assignment with: + +```java +int newColor = 0xFF000000 | ((int)(r * 255F) & 255) << 16 | ((int)(g * 255F) & 255) << 8 | ((int)(b * 255F) & 255); +``` + +Do not alter the tint, diffuse shading, light coordinates, overlay coordinates, render type, or quad submission path. + +- [ ] **Step 3: Re-run the focused check and verify GREEN** + +Run the command from Step 1 again. + +Expected: exit 0 with no output. + +- [ ] **Step 4: Build the complete Dynamic Trees Fabric artifact** + +Run: + +```bash +./gradlew clean :fabric:build +``` + +Expected: `BUILD SUCCESSFUL` and `fabric/build/libs/dynamictrees-fabric-26.2-1.8.0-BETA03.jar` exists. + +- [ ] **Step 5: Inspect the packaged bytecode** + +Run: + +```bash +javap -classpath fabric/build/libs/dynamictrees-fabric-26.2-1.8.0-BETA03.jar -c -p com.dtteam.dynamictrees.model.entity.FallingTreeEntityModel +``` + +Expected: `renderToBuffer` contains an integer OR operation combining the calculated RGB channels with the opaque `-16777216` (`0xFF000000`) constant before the call to `QuadInstance.setColor`. + +- [ ] **Step 6: Review and commit the renderer patch** + +Run: + +```bash +git diff --check +git diff -- common/src/main/java/com/dtteam/dynamictrees/model/entity/FallingTreeEntityModel.java +git add common/src/main/java/com/dtteam/dynamictrees/model/entity/FallingTreeEntityModel.java +git commit -m "fix: preserve falling tree opacity with shaders" +``` + +Expected: one source file in the commit; `.vscode/` remains untracked and unstaged. + +--- + +### Task 2: Localize Every DTBOP Branch Item + +**Files:** +- Modify: `/Volumes/Dev/Projects/krona/minecraft-mods/DynamicTrees-BOP/src/main/resources/assets/dtbop/lang/en_us.json:2` + +**Interfaces:** +- Consumes: the existing twelve `block.dtbop.*_branch` display values. +- Produces: matching `item.dtbop.*_branch` keys used by Minecraft 26.2 item names. + +- [ ] **Step 1: Run the language invariant and verify RED** + +Run from `/Volumes/Dev/Projects/krona/minecraft-mods/DynamicTrees-BOP`: + +```bash +python3 -c 'import json; from pathlib import Path; data = json.loads(Path("src/main/resources/assets/dtbop/lang/en_us.json").read_text()); branches = {key: value for key, value in data.items() if key.startswith("block.dtbop.") and key.endswith("_branch")}; missing = [key.replace("block.", "item.", 1) for key, value in branches.items() if data.get(key.replace("block.", "item.", 1)) != value]; assert len(branches) == 12 and not missing, f"missing or mismatched branch item translations: {missing}"' +``` + +Expected: exit 1 listing all twelve missing item keys, including `item.dtbop.redwood_branch`. + +- [ ] **Step 2: Add the twelve item translations** + +Add these entries after the existing block branch translations: + +```json + "item.dtbop.dead_branch": "Dead Tree", + "item.dtbop.fir_branch": "Fir Tree", + "item.dtbop.hellbark_branch": "Hellbark Tree", + "item.dtbop.jacaranda_branch": "Jacaranda Tree", + "item.dtbop.magic_branch": "Magic Tree", + "item.dtbop.mahogany_branch": "Mahogany Tree", + "item.dtbop.maple_branch": "Maple Tree", + "item.dtbop.palm_branch": "Palm Tree", + "item.dtbop.pine_branch": "Pine Tree", + "item.dtbop.redwood_branch": "Redwood Tree", + "item.dtbop.umbran_branch": "Umbran Tree", + "item.dtbop.willow_branch": "Willow Tree", +``` + +- [ ] **Step 3: Re-run the language invariant and verify GREEN** + +Run the command from Step 1 again. + +Expected: exit 0 with no output. + +- [ ] **Step 4: Build the DTBOP Fabric artifact** + +Run: + +```bash +./gradlew clean build +``` + +Expected: `BUILD SUCCESSFUL` and `build/libs/dtbop-3.5.0-fabric-alpha.jar` exists. + +- [ ] **Step 5: Verify the packaged language file** + +Run: + +```bash +unzip -p build/libs/dtbop-3.5.0-fabric-alpha.jar assets/dtbop/lang/en_us.json | python3 -c 'import json, sys; data = json.load(sys.stdin); assert data["item.dtbop.redwood_branch"] == "Redwood Tree"; assert sum(key.startswith("item.dtbop.") and key.endswith("_branch") for key in data) == 12' +``` + +Expected: exit 0 with no output. + +- [ ] **Step 6: Review and commit the localization patch** + +Run: + +```bash +git diff --check +git diff -- src/main/resources/assets/dtbop/lang/en_us.json +git add src/main/resources/assets/dtbop/lang/en_us.json +git commit -m "fix: localize branch items on 26.2" +``` + +Expected: only `en_us.json` is committed. + +--- + +### Task 3: Pin Both Rebuilt Ports in Ashfall + +**Files:** +- Replace: `/Volumes/Dev/Projects/krona/minecraft-mods/Ashfall/mods/dist/dynamictrees-fabric-26.2-1.8.0-BETA03.jar` +- Create: `/Volumes/Dev/Projects/krona/minecraft-mods/Ashfall/mods/dist/dtbop-3.5.0-fabric-alpha.jar` +- Modify: `/Volumes/Dev/Projects/krona/minecraft-mods/Ashfall/manifest/dynamic-trees.local.toml` +- Create: `/Volumes/Dev/Projects/krona/minecraft-mods/Ashfall/manifest/dynamic-trees-bop.local.toml` +- Modify: `/Volumes/Dev/Projects/krona/minecraft-mods/Ashfall/deploy/ashfall-files.json` +- Modify: `/Volumes/Dev/Projects/krona/minecraft-mods/Ashfall/baseline/mods-sha256.txt` +- Regenerate: `/Volumes/Dev/Projects/krona/minecraft-mods/Ashfall/docs/audits/release-file-inventory.json` + +**Interfaces:** +- Consumes: the exact jars and commit revisions produced by Tasks 1 and 2. +- Produces: reproducible Ashfall-owned artifacts and a bounded deployment mapping to the live Prism instance. + +- [ ] **Step 1: Run the artifact integration check and verify RED** + +Run from `/Volumes/Dev/Projects/krona/minecraft-mods/Ashfall`: + +```bash +python3 -c 'import hashlib, json; from pathlib import Path; root = Path.cwd(); built = {"dynamictrees-fabric-26.2-1.8.0-BETA03.jar": root.parent / "DynamicTrees/fabric/build/libs/dynamictrees-fabric-26.2-1.8.0-BETA03.jar", "dtbop-3.5.0-fabric-alpha.jar": root.parent / "DynamicTrees-BOP/build/libs/dtbop-3.5.0-fabric-alpha.jar"}; copies = {entry["target"]: entry["source"] for entry in json.loads((root / "deploy/ashfall-files.json").read_text())["copy"]}; failures = [name for name, source in built.items() if not (root / "mods/dist" / name).is_file() or hashlib.sha256(source.read_bytes()).digest() != hashlib.sha256((root / "mods/dist" / name).read_bytes()).digest() or copies.get("mods/" + name) != "mods/dist/" + name]; assert not failures, f"unintegrated port artifacts: {failures}"' +``` + +Expected: exit 1 because both rebuilt artifacts are not yet pinned, and DTBOP has no deployment entry. + +- [ ] **Step 2: Copy the exact built jars into Ashfall** + +Run: + +```bash +cp ../DynamicTrees/fabric/build/libs/dynamictrees-fabric-26.2-1.8.0-BETA03.jar mods/dist/dynamictrees-fabric-26.2-1.8.0-BETA03.jar +cp ../DynamicTrees-BOP/build/libs/dtbop-3.5.0-fabric-alpha.jar mods/dist/dtbop-3.5.0-fabric-alpha.jar +``` + +Expected: each Ashfall jar has the same SHA-256 as its source build artifact. + +- [ ] **Step 3: Update the local source manifests** + +Read the exact source revisions with `git -C ../DynamicTrees rev-parse HEAD` and `git -C ../DynamicTrees-BOP rev-parse HEAD`, and read each artifact hash with `shasum -a 256`. + +Update `manifest/dynamic-trees.local.toml` so `sha256` matches the rebuilt Dynamic Trees jar and `source-revision` is the Task 1 commit. + +Create `manifest/dynamic-trees-bop.local.toml` with these fixed fields: + +```toml +filename = 'dtbop-3.5.0-fabric-alpha.jar' +name = "Dynamic Trees for Biomes O' Plenty" +side = 'both' +source-repository = 'https://github.com/Arilas/DynamicTrees-BOP' +x-prismlauncher-loaders = [ 'fabric' ] +x-prismlauncher-mc-versions = [ '26.2' ] +x-prismlauncher-version-number = '3.5.0-fabric-alpha' + +[[x-prismlauncher-dependencies]] +addonId = 'P7dR8mSH' +type = 'REQUIRED' +``` + +Using `apply_patch`, add `sha256` after `side` with the literal 64-character value printed by `shasum`, and add `source-revision` after `source-repository` with the literal 40-character value printed by `git rev-parse`. Validate the result with `python3 -c 'import tomllib; tomllib.load(open("manifest/dynamic-trees-bop.local.toml", "rb"))'`. + +- [ ] **Step 4: Add the DTBOP artifact to the bounded deployment manifest** + +Add this object immediately after the existing Dynamic Trees jar mapping in `deploy/ashfall-files.json`: + +```json +{ + "source": "mods/dist/dtbop-3.5.0-fabric-alpha.jar", + "root": "minecraft", + "target": "mods/dtbop-3.5.0-fabric-alpha.jar" +} +``` + +Preserve valid JSON and do not add either local source manifest to the Prism `.index` directory; the manifests document Ashfall provenance and are consumed by release inventory generation. + +- [ ] **Step 5: Update the expected mod hashes** + +Replace the Dynamic Trees line in `baseline/mods-sha256.txt` with its new exact SHA-256. Replace the DTBOP line with its new exact SHA-256, preserving the file's existing lexical path order. + +- [ ] **Step 6: Re-run the artifact integration check and verify GREEN** + +Run the command from Step 1 again. + +Expected: exit 0 with no output. + +- [ ] **Step 7: Run repository tests and inspect a dry-run deployment** + +Run: + +```bash +python3 -m unittest discover -s tests -v +python3 scripts/deploy.py --manifest deploy/ashfall-files.json --dry-run +``` + +Expected: all tracked tests pass. The dry run lists both changed mod jars and no unrelated save, account, screenshot, or server-list targets. It must not report an active matching Java process before applying. + +- [ ] **Step 8: Apply the bounded deployment and validate live equality** + +Run only after confirming Minecraft is closed: + +```bash +python3 scripts/deploy.py --manifest deploy/ashfall-files.json --apply +python3 scripts/validate-pack.py --repo "$PWD" --instance "/Users/krona/Library/Application Support/PrismLauncher/instances/26.2" +``` + +Expected: deployment creates a timestamped backup under `/Users/krona/Library/Application Support/PrismLauncher/instances/26.2/ashfall-backups/`; validation passes with both live jars hash-identical to Ashfall's copies. + +- [ ] **Step 9: Commit the Ashfall artifact and manifest update** + +Review `git status --short`, then stage exactly: + +```bash +git add mods/dist/dynamictrees-fabric-26.2-1.8.0-BETA03.jar mods/dist/dtbop-3.5.0-fabric-alpha.jar manifest/dynamic-trees.local.toml manifest/dynamic-trees-bop.local.toml deploy/ashfall-files.json baseline/mods-sha256.txt +git commit -m "fix: update Dynamic Trees port artifacts" +``` + +Expected: one local Ashfall commit containing only both accepted port artifacts and their reproducibility metadata. + +- [ ] **Step 10: Regenerate and commit the release inventory** + +Run: + +```bash +ASHFALL_ARTIFACT_COMMIT="$(git rev-parse HEAD)" +python3 scripts/release_inventory.py --repo "$PWD" --manifest deploy/ashfall-files.json --commit "$ASHFALL_ARTIFACT_COMMIT" --output docs/audits/release-file-inventory.json +git add docs/audits/release-file-inventory.json +git commit -m "docs: refresh Ashfall release inventory" +``` + +Expected: the inventory describes both local jars, records live hash matches, and points to the artifact integration commit from Step 9. + +--- + +### Task 4: Final Verification and Delivery + +**Files:** +- Verify only; no new files. + +**Interfaces:** +- Consumes: all three repository commits and the deployed Prism artifacts. +- Produces: pushed Dynamic Trees and DTBOP port branches plus a clean, locally committed Ashfall handoff. + +- [ ] **Step 1: Re-run source and package checks** + +Run the focused GREEN checks from Tasks 1 and 2, then: + +```bash +git -C /Volumes/Dev/Projects/krona/minecraft-mods/DynamicTrees diff --check +git -C /Volumes/Dev/Projects/krona/minecraft-mods/DynamicTrees-BOP diff --check +python3 -m unittest discover -s /Volumes/Dev/Projects/krona/minecraft-mods/Ashfall/tests -v +python3 /Volumes/Dev/Projects/krona/minecraft-mods/Ashfall/scripts/validate-pack.py --repo /Volumes/Dev/Projects/krona/minecraft-mods/Ashfall --instance "/Users/krona/Library/Application Support/PrismLauncher/instances/26.2" +``` + +Expected: every command passes. Dynamic Trees may show only the preserved untracked `.vscode/` directory; the other scoped source changes are committed. + +- [ ] **Step 2: Confirm built, pinned, and live hashes are identical** + +Run `shasum -a 256` on each of these triples: + +- Dynamic Trees build, `Ashfall/mods/dist` copy, and live Prism jar. +- DTBOP build, `Ashfall/mods/dist` copy, and live Prism jar. + +Expected: all three Dynamic Trees hashes match each other, and all three DTBOP hashes match each other. + +- [ ] **Step 3: Push both port branches** + +Run: + +```bash +git -C /Volumes/Dev/Projects/krona/minecraft-mods/DynamicTrees push fork port/26.2 +git -C /Volumes/Dev/Projects/krona/minecraft-mods/DynamicTrees-BOP push fork port/26.2-fabric +``` + +Expected: both `fork` branches advance to the new commits. `Ashfall` remains locally committed because `git -C /Volumes/Dev/Projects/krona/minecraft-mods/Ashfall remote -v` returns no configured remote. + +- [ ] **Step 4: Request the runtime acceptance check** + +Ask the user to fully restart the Prism Launcher `26.2` instance with Complementary Reimagined enabled and verify both behaviors: + +1. Fell a Dynamic Dark Oak tree and confirm the animated trunk and leaves are visible, not only its shadow. +2. Inspect the DTBOP Redwood trunk item and confirm its display name is `Redwood Tree` rather than `item.dtbop.redwood_branch`. + +After the game exits, run `Ashfall/scripts/check-log.sh`. Expected: no new Dynamic Trees, DTBOP, Iris, or renderer errors beyond the recorded baseline. diff --git a/docs/superpowers/specs/2026-08-09-fabric-creaking-heart-and-config-sync-design.md b/docs/superpowers/specs/2026-08-09-fabric-creaking-heart-and-config-sync-design.md new file mode 100644 index 000000000..6ac810c01 --- /dev/null +++ b/docs/superpowers/specs/2026-08-09-fabric-creaking-heart-and-config-sync-design.md @@ -0,0 +1,45 @@ +# Fabric Creaking Heart and Ashfall Config Sync Design + +## Scope + +Fix the Minecraft 26.2 Fabric server crash caused when a Dynamic Trees pale-oak creaking-heart branch is ticked. Rebuild and ship the corrected Dynamic Trees jar through Ashfall, then restore byte-for-byte equality between Ashfall's canonical Dynamic Trees configs and the live Prism instance. + +## Root Cause + +Vanilla `CreakingHeartBlock.hasRequiredLogs` reads `RotatedPillarBlock.AXIS` from the heart state. Dynamic Trees creaking-heart branches represent variable-radius tree networks and intentionally do not have an axis property. + +Dynamic Trees already intercepts the vanilla method on NeoForge and delegates dynamic heart states to `CreakingHeartBranchBlock.hasRequiredLogs`, which checks neighboring dynamic branches. The Fabric port omitted both the mixin class and its mixin-configuration entry. Consequently, the inherited vanilla creaking-heart block-entity tick reaches the incompatible axis lookup and crashes the integrated server. + +## Fabric Fix + +Add the same focused mixin used by NeoForge to the Fabric source set and register it in `dynamictrees.fabric.mixins.json`. At the head of vanilla `hasRequiredLogs`, the mixin returns Dynamic Trees' branch-aware result only when the state belongs to `CreakingHeartBranchBlock`. Vanilla heart states continue through the unmodified vanilla implementation. + +Keep the platform copies separate for this downstream port. Moving the mixin into common sources would require broader loader/resource restructuring, while adding an axis property to dynamic branches would impose incorrect single-axis semantics on a multi-directional branch network. + +## Ashfall Config Synchronization + +Ashfall is the source of truth for `dynamictrees-common.toml` and `dynamictrees-server.toml`. Semantic parsing confirms that the repository and live instance currently contain identical gameplay keys and values; their differences are comments and formatting only. + +Use Ashfall's bounded deployment manifest to replace the live configs with the committed repository copies while deploying the rebuilt jar. Do not copy generated live formatting back into Ashfall. The deployment must back up the current live jar and both live configs before replacement. + +## Verification and Delivery + +Verification must prove: + +- The pre-fix Fabric artifact lacks the creaking-heart mixin class/configuration entry. +- The rebuilt Fabric artifact contains the mixin class and lists it in `dynamictrees.fabric.mixins.json`. +- The Dynamic Trees Fabric build succeeds. +- Ashfall's tracked tests and pack validation pass. +- The build, Ashfall, and live Dynamic Trees jars have identical SHA-256 hashes. +- Both live Dynamic Trees config files are byte-identical to Ashfall after deployment. +- Loading the affected world no longer produces the `axis`-property exception at the existing heart block. + +Commit the Dynamic Trees source fix and push `port/26.2`. Commit the updated jar, hash, provenance, baseline, and refreshed release inventory in Ashfall. Ashfall remains local because it has no configured remote. + +## Non-goals + +- Do not add `AXIS` to Dynamic Trees branch states. +- Do not change creaking-heart activation, resin, loot, or branch-connectivity behavior. +- Do not alter any Dynamic Trees config value. +- Do not edit DTBOP for this crash. +- Do not delete or modify the affected world block or save data. diff --git a/docs/superpowers/specs/2026-08-09-falling-tree-shader-and-dtbop-translation-design.md b/docs/superpowers/specs/2026-08-09-falling-tree-shader-and-dtbop-translation-design.md new file mode 100644 index 000000000..12743fc4f --- /dev/null +++ b/docs/superpowers/specs/2026-08-09-falling-tree-shader-and-dtbop-translation-design.md @@ -0,0 +1,42 @@ +# Falling Tree Shader and DTBOP Translation Fix Design + +## Scope + +Apply two minimal compatibility fixes to the Minecraft 26.2 Fabric ports: + +- Make Dynamic Trees falling-tree geometry visible when shaders honor vertex alpha. +- Give every Dynamic Trees for Biomes O' Plenty branch item a translated display name. + +Build both mods, update the Ashfall modpack and active Prism Launcher instance, verify the result, and commit and push each repository separately. + +## Dynamic Trees Rendering Fix + +`FallingTreeEntityModel` reconstructs branch and leaf quads for the falling-tree entity. It currently sends colors as `0xRRGGBB` to `QuadInstance.setColor`, whose 26.2 color contract is packed ARGB. The omitted high byte becomes alpha zero. Shader pipelines that consume vertex alpha therefore make both trunks and leaves invisible even though the entity still casts a shadow. + +Change the reconstructed color to `0xFFRRGGBB`. Keep the existing tint and diffuse-light calculations unchanged. This correction belongs in the common renderer because it is valid for both vanilla and shader rendering and affects every falling tree using that path. + +## DTBOP Translation Fix + +Dynamic Trees registers branch blocks and corresponding branch items under IDs such as `dtbop:redwood_branch`. The `_branch` suffix is an intentional internal representation for variable-radius trunk segments and must not be renamed. + +Minecraft 26.2 derives the branch item's display key from the item registry ID, producing `item.dtbop.redwood_branch`. DTBOP currently supplies only matching `block.dtbop.*_branch` entries. Add item translations for all twelve DTBOP branch families, mirroring the existing localized block display names. This avoids a Redwood-only special case and fixes every item registered through the same path. + +## Verification and Delivery + +For each repository: + +1. Run the relevant clean Fabric build and tests. +2. Inspect the output jar to confirm the changed class or language resource is packaged. +3. Update the Ashfall modpack artifact and metadata without disturbing unrelated working-tree changes. +4. Back up and replace the corresponding jar in the active Prism instance. +5. Run the modpack's available validation checks. +6. Commit only files belonging to this work and push the current port branch. + +Runtime acceptance criteria are that a falling Dark Oak or other dynamic tree renders its trunk and leaves with Complementary Reimagined enabled, and the DTBOP Redwood trunk item displays its localized tree name instead of `item.dtbop.redwood_branch`. + +## Non-goals + +- Do not modify Complementary Reimagined, Iris, or Sodium configuration. +- Do not rename branch registry IDs or alter saved-world data. +- Do not refactor the falling-tree rendering pipeline beyond the missing alpha correction. +- Do not modify unrelated files already present in either repository or modpack. diff --git a/fabric/build.gradle b/fabric/build.gradle index 55794b978..ecae446b8 100644 --- a/fabric/build.gradle +++ b/fabric/build.gradle @@ -5,10 +5,45 @@ plugins { //mod_version = "${mod_version}${fabricVersionAppend}" version = mod_version +repositories { + exclusiveContent { + forRepository { + maven { + name = 'Modrinth' + url = 'https://api.modrinth.com/maven' + } + } + filter { + includeGroup 'maven.modrinth' + } + } +} + dependencies { minecraft "com.mojang:minecraft:${minecraft_version}" implementation "net.fabricmc:fabric-loader:${fabric_loader_version}" implementation "net.fabricmc.fabric-api:fabric-api:${fabric_version}" + compileOnly "maven.modrinth:jade:26.2.9+fabric" + compileOnly "maven.modrinth:continuity:3.0.1+26.2" + compileOnly "maven.modrinth:serene-seasons:26.1.2.0.4" + compileOnly "maven.modrinth:glitchcore:26.2.0.0.0" // transitive API of serene-seasons, needed at compile time + implementation "fuzs.forgeconfigapiport:forgeconfigapiport-fabric:${project.forge_config_api_port_version}" + compileOnly group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.2' // javax.annotation used by common sources +} + +// Include the datagenned assets/data (blockstates, item/block models, loot, ...) like the neoforge jar does. +sourceSets.main.resources { srcDir project(':common').file('src/generated/resources') } +// The generated directory can be seen twice by the copy spec (identical files); first one wins. +tasks.named('processResources', ProcessResources) { duplicatesStrategy = DuplicatesStrategy.EXCLUDE } +tasks.named('sourcesJar', Jar) { duplicatesStrategy = DuplicatesStrategy.EXCLUDE } + +// Ship the generated data (tags, loot tables) in the fabric jar too. The generated assets +// (blockstates using the custom "dynamictrees:branch" model type) stay neoforge-only — +// the fabric model layer replaces branch models after bake and cannot parse them. +processResources { + from(project(':common').file('src/generated/resources')) { + include 'data/**' + } } loom { diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabric.java b/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabric.java index 7023e005b..4b0547b2a 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabric.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabric.java @@ -9,7 +9,7 @@ import com.dtteam.dynamictrees.registry.FabricRegistryHandler; import com.dtteam.dynamictrees.registry.FabricRegistryLoader; import com.dtteam.dynamictrees.worldgen.FabricBiomeModifications; -import fuzs.forgeconfigapiport.fabric.api.neoforge.v4.*; +import fuzs.forgeconfigapiport.fabric.api.v5.ConfigRegistry; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.loader.api.FabricLoader; @@ -21,8 +21,8 @@ public class DynamicTreesFabric implements ModInitializer { @Override public void onInitialize() { - NeoForgeConfigRegistry.INSTANCE.register(DynamicTrees.MOD_ID,ModConfig.Type.SERVER, DTConfigs.SERVER_CONFIG); - NeoForgeConfigRegistry.INSTANCE.register(DynamicTrees.MOD_ID,ModConfig.Type.COMMON, DTConfigs.COMMON_CONFIG); + ConfigRegistry.INSTANCE.register(DynamicTrees.MOD_ID, ModConfig.Type.SERVER, DTConfigs.SERVER_CONFIG); + ConfigRegistry.INSTANCE.register(DynamicTrees.MOD_ID, ModConfig.Type.COMMON, DTConfigs.COMMON_CONFIG); FabricRegistryHandler.setup(DynamicTrees.MOD_ID); @@ -44,17 +44,35 @@ public void onInitialize() { FabricRegistryLoader.setup(); - DynamicTrees.commonSetup(); + // Note: DynamicTrees.commonSetup() is deliberately NOT called here. It applies the + // treepack "setup" stage (primitive log/leaves/stripped log block lookups etc.), which + // must run after ALL mods have registered their blocks. Fabric invokes ModInitializers + // in an order that may put this mod before content mods (e.g. Biomes O' Plenty), which + // previously made every add-on treepack primitive lookup fail on the first pass. + // It is instead called from the client/dedicated-server entrypoints, which Fabric runs + // after the whole main entrypoint phase (mirroring NeoForge's FMLCommonSetupEvent + // timing). See DynamicTreesFabricClient and DynamicTreesFabricServer. FabricBiomeModifications.register(); ServerLifecycleEvents.SERVER_STARTING.register(server -> { VanillaSaplingEventHandler.updateEnabled(); - FabricMiscHelper.debugSpeciesRegistry(); }); - ServerLifecycleEvents.SERVER_STOPPING.register(server -> { - FabricMiscHelper.currentServer = null; + // Must be SERVER_STOPPED, not SERVER_STOPPING. SERVER_STOPPING fires at the HEAD of + // MinecraftServer#stopServer, i.e. *before* saveAllChunks drains the chunk generation + // queue. Chunk gen tasks still in flight there run feature placement, which reaches + // CaveRootedTreePlacement -> BiomeDatabase -> DTBiomeHolderSet; that resolves the biome + // registry off currentServer on every call, so a null here throws + // "Queried biome registry too early" out of a worker thread, kills the chunk task, and + // hangs the shutdown (the game freezes on world exit). SERVER_STOPPED fires at the TAIL, + // after ServerLevel#close, which matches NeoForge clearing its reference in + // ServerLifecycleHooks#handleServerStopped. + ServerLifecycleEvents.SERVER_STOPPED.register(server -> { + // Only clear our own reference; never clobber a newer server's registration. + if (FabricMiscHelper.currentServer == server) { + FabricMiscHelper.currentServer = null; + } }); diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabricClient.java b/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabricClient.java index 4587c2371..70cfd83ba 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabricClient.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabricClient.java @@ -6,6 +6,7 @@ import com.dtteam.dynamictrees.block.sapling.*; import com.dtteam.dynamictrees.block.soil.*; import com.dtteam.dynamictrees.client.*; +import com.dtteam.dynamictrees.client.TintSources.*; import com.dtteam.dynamictrees.config.*; import com.dtteam.dynamictrees.item.*; import com.dtteam.dynamictrees.model.*; @@ -16,15 +17,20 @@ import com.dtteam.dynamictrees.tree.*; import com.dtteam.dynamictrees.tree.family.*; import com.dtteam.dynamictrees.tree.species.*; -import fuzs.forgeconfigapiport.fabric.api.neoforge.v4.*; +import fuzs.forgeconfigapiport.fabric.api.v5.ConfigRegistry; import net.fabricmc.api.*; -import net.fabricmc.fabric.api.blockrenderlayer.v1.*; import net.fabricmc.fabric.api.client.event.lifecycle.v1.*; import net.fabricmc.fabric.api.client.item.v1.*; import net.fabricmc.fabric.api.client.model.loading.v1.*; import net.fabricmc.fabric.api.client.rendering.v1.*; import net.minecraft.client.*; +import net.minecraft.client.color.block.BlockTintSource; +import net.minecraft.client.color.block.BlockTintSources; +import net.minecraft.client.color.item.ItemTintSources; +import net.minecraft.client.renderer.block.dispatch.BlockStateModel; +import net.minecraft.client.renderer.block.dispatch.BlockStateModelPart; import net.minecraft.client.renderer.texture.*; +import net.minecraft.client.resources.model.geometry.BakedQuad; import net.minecraft.core.*; import net.minecraft.resources.*; import net.minecraft.util.*; @@ -46,11 +52,17 @@ public class DynamicTreesFabricClient implements ClientModInitializer { @Override public void onInitializeClient() { - NeoForgeConfigRegistry.INSTANCE.register(DynamicTrees.MOD_ID, ModConfig.Type.CLIENT, DTConfigs.CLIENT_CONFIG); - AtlasSourceTypeRegistryImpl.register(ThickBranchRingsSource.ID, ThickBranchRingsSource.setType(ThickBranchRingsSource.CODEC)); + // Runs after ALL main entrypoints, so every mod's blocks are registered and the + // treepack setup stage (primitive log/leaves lookups) resolves add-on blocks. + // This must happen before the first resource load so models bake with correct data. + DynamicTrees.commonSetup(); + + ConfigRegistry.INSTANCE.register(DynamicTrees.MOD_ID, ModConfig.Type.CLIENT, DTConfigs.CLIENT_CONFIG); + SpriteSourceRegistry.register(ThickBranchRingsSource.ID, ThickBranchRingsSource.CODEC); registerModelLoaders(); registerEntityRenderers(); registerColorHandlers(); + registerItemTintSources(); registerTooltipCallback(); registerClientTick(); registerClientWorldLoad(); @@ -61,15 +73,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()); } @@ -79,78 +91,62 @@ private void registerEntityRenderers() { } private void registerColorHandlers() { - BlockColorMultipliers.register("birch", (state, level, pos, tintIndex) -> FoliageColor.getBirchColor()); - BlockColorMultipliers.register("spruce", (state, level, pos, tintIndex) -> FoliageColor.getEvergreenColor()); + BlockColorMultipliers.register("birch", BlockTintSources.constant(FoliageColor.FOLIAGE_BIRCH)); + BlockColorMultipliers.register("spruce", BlockTintSources.constant(FoliageColor.FOLIAGE_EVERGREEN)); + } - ColorProviderRegistry.ITEM.register(DTRegistries.DENDRO_POTION.get()::getColor, DTRegistries.DENDRO_POTION.get()); - ColorProviderRegistry.ITEM.register(DTRegistries.STAFF.get()::getColor, DTRegistries.STAFF.get()); + private void registerItemTintSources() { + // Mirrors the NeoForge RegisterColorHandlersEvent.ItemTintSources registrations; the + // item model JSONs reference these tint source types by id. + ItemTintSources.ID_MAPPER.put(DynamicTrees.location("dendro_potion"), DendroPotionItemTintSource.MAP_CODEC); + ItemTintSources.ID_MAPPER.put(DynamicTrees.location("staff_handle"), StaffHandleItemTintSource.MAP_CODEC); + ItemTintSources.ID_MAPPER.put(DynamicTrees.location("staff_crystal"), StaffCrystalItemTintSource.MAP_CODEC); } public static void registerBlockColors() { - final int white = 0xFFFFFFFF; - final int magenta = 0x00FF00FF; final var blockColors = Minecraft.getInstance().getBlockColors(); - for (SoilProperties soil : SoilProperties.REGISTRY) { - if (soil.getBlock().isEmpty()) continue; - SoilBlock roots = soil.getBlock().get(); - ColorProviderRegistry.BLOCK.register( - (state, level, pos, tintIndex) -> roots.colorMultiplier(blockColors, state, level, pos, tintIndex), - roots - ); - BlockRenderLayerMap.INSTANCE.putBlock(roots, RenderType.cutoutMipped()); - } - - for (Family family : Family.REGISTRY.getAll()) { - if (family instanceof AerialRootsFamily rootsFamily) { - rootsFamily.getRoots().ifPresent(roots -> - BlockRenderLayerMap.INSTANCE.putBlock(roots, RenderType.cutoutMipped()) - ); - } - } - - - ColorProviderRegistry.BLOCK.register( - (state, level, pos, tintIndex) -> isValidPos(level, pos) && (state.getBlock() instanceof PottedSaplingBlock) - ? DTRegistries.POTTED_SAPLING.get().getSpecies(level, pos).saplingColorMultiplier(state, level, pos, tintIndex) : white, - DTRegistries.POTTED_SAPLING.get() - ); - - for (Species species : Species.REGISTRY) { - if (species.getSapling().isPresent()) { - ColorProviderRegistry.BLOCK.register( - (state, level, pos, tintIndex) -> isValidPos(level, pos) - ? species.saplingColorMultiplier(state, level, pos, tintIndex) : white, - species.getSapling().get() - ); - } - species.getSapling().ifPresent(sapling -> BlockRenderLayerMap.INSTANCE.putBlock(sapling, RenderType.cutoutMipped())); - if(species.hasFruits()){ - species.getFruits().forEach(fruit -> - BlockRenderLayerMap.INSTANCE.putBlock(fruit.getBlock(), RenderType.cutoutMipped()) - ); - } - if(species.hasPods()){ - species.getPods().forEach(pod -> - BlockRenderLayerMap.INSTANCE.putBlock(pod.getBlock(), RenderType.cutoutMipped()) - ); - } - } + // Register Rooty Soils Tint Sources + SoilProperties.REGISTRY.getAll().stream().map(SoilProperties::getBlock).flatMap(Optional::stream) + .forEach(soilBlock -> { + SoilProperties properties = soilBlock.getSoilProperties(); + List sources = CloneTintSource.cloneAllSources( + blockColors, + () -> properties.getPrimitiveSoilState(soilBlock.defaultBlockState()), + properties.getFoliageTintLayerCount()); + sources.add(new SoilRootsTintSource(soilBlock)); + BlockColorRegistry.register(sources, soilBlock); + }); - for (DynamicLeavesBlock leaves : LeavesProperties.REGISTRY.getAll().stream() - .filter(lp -> lp.getDynamicLeavesBlock().isPresent()) - .map(lp -> lp.getDynamicLeavesBlock().get()) - .collect(Collectors.toSet())) { - ColorProviderRegistry.BLOCK.register( - (state, level, pos, tintIndex) -> isValidPos(level, pos) && TreeHelper.isLeaves(state.getBlock()) - ? ((DynamicLeavesBlock) state.getBlock()).getLeavesProperties().foliageColorMultiplier(state, level, pos) : magenta, - leaves - ); - } - } + // Register Leaves Tint Sources + LeavesProperties.REGISTRY.getAll().stream().map(LeavesProperties::getDynamicLeavesBlock).flatMap(Optional::stream) + .forEach(leaves -> { + LeavesProperties properties = leaves.getLeavesProperties(); + if (properties.hasCustomColor()) { + Integer customColor = properties.getCustomColor(); + if (customColor == null) //we use null as a way to default back to "biome" index source. + BlockColorRegistry.register(List.of(BlockTintSources.foliage()), leaves); + else + BlockColorRegistry.register(List.of(BlockTintSources.constant(customColor)), leaves); + } else { + BlockColorRegistry.register(CloneTintSource.cloneAllSources(blockColors, properties::getPrimitiveLeaves, properties.getFoliageTintLayerCount()), leaves); + } + }); - private static boolean isValidPos(BlockGetter level, BlockPos pos) { - return level != null && pos != null; + // Register Potted Sapling Tint Sources + BlockColorRegistry.register(List.of(new PottedSaplingTintSource(blockColors)), DTRegistries.POTTED_SAPLING.get()); + + // Register Sapling Tint Sources + Species.REGISTRY.getAll().stream().map(Species::getSapling).flatMap(Optional::stream) + .forEach(sapling -> { + Species species = sapling.getSpecies(); + BlockColorRegistry.register(List.of( + //Leaves for the leaves + new SaplingTintSource(blockColors, species), + //Mangrove saplings have roots so we provide a branch tint + new SuppliedConstantTintSource(() -> species.getFamily().woodBarkColor) + ), sapling); + }); } private void registerTooltipCallback() { @@ -182,43 +178,30 @@ private void registerTooltipCallback() { } private void registerClientTick() { - ClientTickEvents.START_WORLD_TICK.register(level -> { - SeasonHelper.updateTick(level, level.getDayTime()); + ClientTickEvents.START_LEVEL_TICK.register(level -> { + SeasonHelper.updateTick(level, level.getDefaultClockTime()); }); } public static void discoverWoodColors() { - final Function bakedTextureGetter = Minecraft.getInstance() - .getTextureAtlas(InventoryMenu.BLOCK_ATLAS); - for (Family family : Family.REGISTRY.getAll()) { family.woodRingColor = 0xFFF1AE; family.woodBarkColor = 0xB3A979; if (family != Family.NULL_FAMILY) { family.getPrimitiveLog().ifPresent(branch -> { BlockState state = branch.defaultBlockState(); - family.woodRingColor = getFaceColor(state, Direction.DOWN, bakedTextureGetter); - family.woodBarkColor = getFaceColor(state, Direction.NORTH, bakedTextureGetter); + family.woodRingColor = getFaceColor(state, Direction.DOWN); + family.woodBarkColor = getFaceColor(state, Direction.NORTH); }); } } } - private static int getFaceColor(BlockState state, Direction face, Function textureGetter) { - final BakedModel model = Minecraft.getInstance().getBlockRenderer().getBlockModel(state); - if (model == null) { - DynamicTrees.LOG.warn("Could not get model for {}! Branch needs to be handled manually!", state.getBlock()); - return 0; - } - List quads = model.getQuads(state, face, RandomSource.create()); - if (quads.isEmpty()) { - quads = model.getQuads(state, null, RandomSource.create()); - } - if (quads.isEmpty()) { - DynamicTrees.LOG.warn("Could not get color of {} side for {}! Branch needs to be handled manually!", face, state.getBlock()); - return 0; - } - TextureAtlasSprite sprite = quads.getFirst().getSprite(); + private static int getFaceColor(BlockState state, Direction face) { + final BlockStateModel model = Minecraft.getInstance().getModelManager().getBlockStateModelSet().get(state); + List quads = getQuads(state, face, model); + if (quads == null) return 0; + TextureAtlasSprite sprite = quads.getFirst().materialInfo().sprite(); final TextureHelper.PixelBuffer pixelBuffer = new TextureHelper.PixelBuffer(sprite); final int u = pixelBuffer.w / 16; final TextureHelper.PixelBuffer center = new TextureHelper.PixelBuffer(u * 8, u * 8); @@ -226,4 +209,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/DynamicTreesFabricServer.java b/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabricServer.java new file mode 100644 index 000000000..d280ee3a7 --- /dev/null +++ b/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabricServer.java @@ -0,0 +1,17 @@ +package com.dtteam.dynamictrees; + +import net.fabricmc.api.DedicatedServerModInitializer; + +/** + * Dedicated server entrypoint. Fabric invokes this after the whole main entrypoint phase, + * so every mod's blocks are registered by the time the treepack setup stage (primitive + * log/leaves lookups etc.) runs — mirroring NeoForge's FMLCommonSetupEvent timing. + * The client counterpart is {@link DynamicTreesFabricClient}. + */ +public class DynamicTreesFabricServer implements DedicatedServerModInitializer { + + @Override + public void onInitializeServer() { + DynamicTrees.commonSetup(); + } +} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/compat/SereneSeasonsSeasonProvider.java b/fabric/src/main/java/com/dtteam/dynamictrees/compat/SereneSeasonsSeasonProvider.java index 58a05eb44..1519d6894 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/compat/SereneSeasonsSeasonProvider.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/compat/SereneSeasonsSeasonProvider.java @@ -3,8 +3,8 @@ import com.dtteam.dynamictrees.DynamicTrees; import com.dtteam.dynamictrees.api.season.SeasonProvider; import com.dtteam.dynamictrees.systems.season.*; +import com.mojang.datafixers.util.Pair; import net.minecraft.core.BlockPos; -import net.minecraft.util.Tuple; import net.minecraft.world.level.Level; import sereneseasons.api.season.Season; import sereneseasons.init.ModConfig; @@ -26,7 +26,7 @@ public void updateTick(Level level, long dayTime) { @Override public boolean shouldSnowMelt(Level level, BlockPos pos) { if (ModConfig.seasons.generateSnowAndIce && seasonValue < SeasonHelper.WINTER_START) { - return level.getBiome(pos).value().warmEnoughToRain(pos); + return level.getBiome(pos).value().warmEnoughToRain(pos, level.getSeaLevel()); } return false; } @@ -35,8 +35,8 @@ public static void registerSereneSeasonsProvider (){ SeasonCompatibilityHandler.registerSeasonManager(DynamicTrees.SERENE_SEASONS, () -> { NormalSeasonManager seasonManager = new NormalSeasonManager( world -> ModConfig.seasons.isDimensionWhitelisted(world.dimension()) ? - new Tuple<>(new SereneSeasonsSeasonProvider(), new ActiveSeasonGrowthCalculator()) : - new Tuple<>(new NullSeasonProvider(), new NullSeasonGrowthCalculator()) + new Pair<>(new SereneSeasonsSeasonProvider(), new ActiveSeasonGrowthCalculator()) : + new Pair<>(new NullSeasonProvider(), new NullSeasonGrowthCalculator()) ); seasonManager.setTropicalPredicate((world, pos) -> sereneseasons.api.season.SeasonHelper.usesTropicalSeasons(world.getBiome(pos))); return seasonManager; diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/compat/continuity/ContinuityWrappedModelHandler.java b/fabric/src/main/java/com/dtteam/dynamictrees/compat/continuity/ContinuityWrappedModelHandler.java index d357eeba8..47310c4b1 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/compat/continuity/ContinuityWrappedModelHandler.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/compat/continuity/ContinuityWrappedModelHandler.java @@ -1,16 +1,35 @@ package com.dtteam.dynamictrees.compat.continuity; import com.dtteam.dynamictrees.model.baked.BasicBranchBlockBakedModel; -import me.pepperbell.continuity.client.model.CtmBakedModel; -import net.minecraft.client.resources.model.BakedModel; +import me.pepperbell.continuity.client.model.CtmBlockStateModel; +import net.fabricmc.fabric.api.client.model.loading.v1.wrapper.WrapperBlockStateModel; +import net.minecraft.client.renderer.block.dispatch.BlockStateModel; import org.jetbrains.annotations.Nullable; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; + public class ContinuityWrappedModelHandler extends WrappedModelHandler { + // Continuity's CtmBlockStateModel extends Fabric's WrapperBlockStateModel, whose + // wrapped model field is protected with no public accessor. + @Nullable + private static final VarHandle WRAPPED_FIELD = findWrappedField(); + + @Nullable + private static VarHandle findWrappedField() { + try { + return MethodHandles.privateLookupIn(WrapperBlockStateModel.class, MethodHandles.lookup()) + .findVarHandle(WrapperBlockStateModel.class, "wrapped", BlockStateModel.class); + } catch (ReflectiveOperationException e) { + return null; + } + } + @Override - public @Nullable BasicBranchBlockBakedModel unwrapBranchModel(BakedModel model) { - if (model instanceof CtmBakedModel ctmModel){ - return super.unwrapBranchModel(ctmModel.getWrappedModel()); + public @Nullable BasicBranchBlockBakedModel unwrapBranchModel(BlockStateModel model) { + if (model instanceof CtmBlockStateModel ctmModel && WRAPPED_FIELD != null) { + return super.unwrapBranchModel((BlockStateModel) WRAPPED_FIELD.get(ctmModel)); } return super.unwrapBranchModel(model); } diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/compat/continuity/WrappedModelHandler.java b/fabric/src/main/java/com/dtteam/dynamictrees/compat/continuity/WrappedModelHandler.java index 36ac0d73d..0ec9d895c 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/compat/continuity/WrappedModelHandler.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/compat/continuity/WrappedModelHandler.java @@ -2,16 +2,16 @@ import com.dtteam.dynamictrees.model.baked.BasicBranchBlockBakedModel; import com.dtteam.dynamictrees.platform.Services; -import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.client.renderer.block.dispatch.BlockStateModel; import org.jetbrains.annotations.Nullable; public abstract class WrappedModelHandler { private static WrappedModelHandler INSTANCE = null; - public static WrappedModelHandler getInstance(){ + public static WrappedModelHandler getInstance() { if (INSTANCE == null) { - if (Services.PLATFORM.isModLoaded("continuity")){ + if (Services.PLATFORM.isModLoaded("continuity")) { INSTANCE = new ContinuityWrappedModelHandler(); } else { INSTANCE = new WrappedModelHandler() {}; @@ -21,9 +21,10 @@ public static WrappedModelHandler getInstance(){ } @Nullable - public BasicBranchBlockBakedModel unwrapBranchModel(BakedModel model){ - if (model instanceof BasicBranchBlockBakedModel branchModel) + public BasicBranchBlockBakedModel unwrapBranchModel(BlockStateModel model) { + if (model instanceof BasicBranchBlockBakedModel branchModel) { return branchModel; + } return null; } diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/compat/waila/WailaBranchHandler.java b/fabric/src/main/java/com/dtteam/dynamictrees/compat/waila/WailaBranchHandler.java index 89556b3fe..dbf9bc780 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/compat/waila/WailaBranchHandler.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/compat/waila/WailaBranchHandler.java @@ -19,6 +19,7 @@ import net.minecraft.nbt.CompoundTag; import net.minecraft.network.chat.Component; import net.minecraft.resources.Identifier; +import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.enchantment.Enchantments; import net.minecraft.world.level.Level; @@ -28,8 +29,9 @@ 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.impl.ui.ItemStackElement; +import snownee.jade.impl.ui.SpacerElement; import java.util.LinkedList; import java.util.List; @@ -57,7 +59,7 @@ public void appendTooltip(ITooltip tooltip, BlockAccessor accessor, IPluginConfi //Attempt to get species from server via NBT data if (nbtData.contains("species")) { - species = Species.findSpecies(Identifier.parse(nbtData.getString("species"))); + species = Species.findSpecies(Identifier.parse(nbtData.getString("species").get())); } //Attempt to get species by checking if we're still looking at the same block @@ -91,48 +93,71 @@ public void appendTooltip(ITooltip tooltip, BlockAccessor accessor, IPluginConfi tooltip.add(Component.literal(ChatFormatting.DARK_GRAY + species.getRegistryName().toString())); } - ItemStack seedStack = species.getSeedStack(1); + appendDropsRow(tooltip, accessor, species); + } + } - List elements = new LinkedList<>(); - elements.add(getElement(seedStack)); //adds seed; + /** + * Builds the single-line icon row showing what this tree yields: its seed, any fruits/pods, and + * — once the tree's volume is known — the logs and sticks the current tool would actually drop. + * + *

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

On Fabric, Jade discovers plugins through the {@code "jade"} entrypoint in {@code fabric.mod.json} + * rather than by scanning for {@link WailaPlugin}, so this class must be listed there as well as + * annotated. The entrypoint is only ever resolved by Jade itself, so the Jade classes referenced here + * are never loaded when Jade is absent — which is why Jade can stay a {@code compileOnly} dependency. + */ +@WailaPlugin +public class WailaCompat implements IWailaPlugin { + + @Override + public void registerClient(IWailaClientRegistration registration) { + WailaBranchHandler branchHandler = new WailaBranchHandler(); + registration.registerBlockComponent(branchHandler, BranchBlock.class); + registration.registerBlockComponent(branchHandler, TrunkShellBlock.class); + + registration.registerBlockComponent(new WailaFruitHandler(), FruitBlock.class); + registration.registerBlockComponent(new WailaPodHandler(), PodBlock.class); + registration.registerBlockComponent(new WailaRootyHandler(), SoilBlock.class); + registration.registerBlockComponent(new WailaRootyWaterHandler(), WaterSoilProperties.SoilWaterBlock.class); + } + +} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/CommonEventHandler.java b/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/CommonEventHandler.java index 769c923d6..e1f4feb05 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/CommonEventHandler.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/CommonEventHandler.java @@ -15,7 +15,7 @@ import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; -import net.fabricmc.fabric.api.event.lifecycle.v1.ServerWorldEvents; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLevelEvents; import net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents; import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener; import net.fabricmc.fabric.api.resource.ResourceManagerHelper; @@ -32,16 +32,16 @@ public class CommonEventHandler { public static void RegisterEvents(){ - ServerTickEvents.START_WORLD_TICK.register((level)->{ + ServerTickEvents.START_LEVEL_TICK.register((level)->{ FutureBreak.process(level); - SeasonHelper.updateTick(level, level.getDayTime()); + SeasonHelper.updateTick(level, level.getDefaultClockTime()); }); - ServerWorldEvents.LOAD.register(((minecraftServer, serverLevel) -> { + ServerLevelEvents.LOAD.register(((minecraftServer, serverLevel) -> { BiomeDatabases.populateBlacklistFromConfig(); })); - ServerWorldEvents.UNLOAD.register(((minecraftServer, serverLevel) -> { + ServerLevelEvents.UNLOAD.register(((minecraftServer, serverLevel) -> { DynamicTreeFeature.DISC_PROVIDER.unloadWorld(serverLevel); })); @@ -57,13 +57,13 @@ public static void RegisterEvents(){ PlayerBlockBreakEvents.BEFORE.register((level, player, pos, state, blockEntity) -> { Block block = state.getBlock(); if (block instanceof BranchBlock branchBlock) { - return branchBlock.onDestroyedByPlayer(state, level, pos, player, true, level.getFluidState(pos)); + return branchBlock.onDestroyedByPlayer(state, level, pos, player, player.getMainHandItem(), true, level.getFluidState(pos)); } else if (block instanceof TrunkShellBlock trunkShellBlock) { - return trunkShellBlock.onDestroyedByPlayer(state, level, pos, player, true, level.getFluidState(pos)); + return trunkShellBlock.onDestroyedByPlayer(state, level, pos, player, player.getMainHandItem(), true, level.getFluidState(pos)); } else if (block instanceof SoilBlock soilBlock) { return soilBlock.onDestroyedByPlayer(state, level, pos, player, true, level.getFluidState(pos)); } else if (block instanceof PottedSaplingBlock pottedSaplingBlock) { - return pottedSaplingBlock.onDestroyedByPlayer(state, level, pos, player, true, level.getFluidState(pos)); + return pottedSaplingBlock.onDestroyedByPlayer(state, level, pos, player, player.getMainHandItem(), true, level.getFluidState(pos)); } return true; }); @@ -79,10 +79,9 @@ public FabricReloadListener() { } @Override - public CompletableFuture reload(PreparationBarrier stage, ResourceManager resourceManager, - ProfilerFiller preparationsProfiler, ProfilerFiller reloadProfiler, - Executor backgroundExecutor, Executor gameExecutor) { - return super.reload(stage, resourceManager, preparationsProfiler, reloadProfiler, backgroundExecutor, gameExecutor); + public CompletableFuture reload(SharedState sharedState, Executor backgroundExecutor, + PreparationBarrier preparationBarrier, Executor gameExecutor) { + return super.reload(sharedState, backgroundExecutor, preparationBarrier, gameExecutor); } @Override diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/VanillaSaplingEventHandler.java b/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/VanillaSaplingEventHandler.java index 52614ead8..fd205b6df 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/VanillaSaplingEventHandler.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/VanillaSaplingEventHandler.java @@ -47,15 +47,17 @@ private static InteractionResult onUseBlock(Player player, Level level, Interact return InteractionResult.PASS; } - Species targetSpecies = DynamicSaplingBlock.SAPLING_REPLACERS.get(block); - Species species = targetSpecies.selfOrLocationOverride(level, placePos); - - if (species.overrideSaplingReplacementWhenCrouching() && player.isCrouching()){ + if (!DynamicSaplingBlock.shouldReplaceSaplingWhenPlaced(block.defaultBlockState())) { return InteractionResult.PASS; } - if (!species.plantSapling(level, placePos, targetSpecies != species)) { - if (!player.isCreative()) stack.grow(1); + Species targetSpecies = DynamicSaplingBlock.SAPLING_REPLACERS.get(block); + Species species = targetSpecies.selfOrLocationOverride(level, placePos); + + if (species.plantSapling(level, placePos, targetSpecies != species)) { + if (!player.isCreative()) { + stack.shrink(1); + } return InteractionResult.SUCCESS; } diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinCreakingHeartBlock.java b/fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinCreakingHeartBlock.java new file mode 100644 index 000000000..d5a713fad --- /dev/null +++ b/fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinCreakingHeartBlock.java @@ -0,0 +1,23 @@ +package com.dtteam.dynamictrees.mixin; + +import com.dtteam.dynamictrees.block.branch.CreakingHeartBranchBlock; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.CreakingHeartBlock; +import net.minecraft.world.level.block.state.BlockState; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(CreakingHeartBlock.class) +public class MixinCreakingHeartBlock { + + @Inject(method = "hasRequiredLogs", at = @At("HEAD"), cancellable = true) + private static void hasRequiredLogs(BlockState state, LevelReader level, BlockPos pos, CallbackInfoReturnable cir) { + if (state.getBlock() instanceof CreakingHeartBranchBlock) { + cir.setReturnValue(CreakingHeartBranchBlock.hasRequiredLogs(state, level, pos)); + } + } + +} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinVegetationBlock.java b/fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinVegetationBlock.java index 390acb646..d2cd47652 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinVegetationBlock.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinVegetationBlock.java @@ -24,7 +24,8 @@ private void canSurvive(BlockState state, LevelReader level, BlockPos pos, Callb BlockState belowBlockState = level.getBlockState(blockpos); if (belowBlockState.getBlock() instanceof BasicRootsBlock roots){ if (belowBlockState.getValue(BasicRootsBlock.LAYER) == BasicRootsBlock.Layer.COVERED){ - Block block = BasicRootsBlock.Layer.COVERED.getPrimitive(roots.getFamily()).orElse(null); + if (!(roots.getFamily() instanceof com.dtteam.dynamictrees.tree.family.AerialRootsFamily rootsFamily)) return; + Block block = BasicRootsBlock.Layer.COVERED.getPrimitive(rootsFamily).orElse(null); if (block == null) return; cir.setReturnValue(mayPlaceOn(block.defaultBlockState(), level, blockpos)); } diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/BakedModelBlockPottedSapling.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/BakedModelBlockPottedSapling.java deleted file mode 100644 index 38e47de31..000000000 --- a/fabric/src/main/java/com/dtteam/dynamictrees/model/BakedModelBlockPottedSapling.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.dtteam.dynamictrees.model; - -import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel; -import net.minecraft.client.renderer.block.model.BakedQuad; -import net.minecraft.client.resources.model.BakedModel; -import net.minecraft.core.Direction; -import net.minecraft.util.RandomSource; -import net.minecraft.world.level.block.state.BlockState; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.List; - -public class BakedModelBlockPottedSapling extends ForwardingBakedModel { - - public BakedModelBlockPottedSapling(BakedModel basePotModel) { - this.wrapped = basePotModel; - } - - @Override - public boolean isVanillaAdapter() { - return true; - } - - @Override - @NotNull - public List getQuads(BlockState state, Direction face, RandomSource random) { - return new ArrayList<>(wrapped.getQuads(state, face, random)); - } -} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/BranchBlockUnbakedModel.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/BranchBlockUnbakedModel.java deleted file mode 100644 index 5217b2522..000000000 --- a/fabric/src/main/java/com/dtteam/dynamictrees/model/BranchBlockUnbakedModel.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.dtteam.dynamictrees.model; - -import com.dtteam.dynamictrees.model.baked.BasicBranchBlockBakedModel; -import com.dtteam.dynamictrees.model.baked.ThickBranchBlockBakedModel; -import com.dtteam.dynamictrees.tree.family.Family; -import net.minecraft.client.renderer.texture.TextureAtlasSprite; -import net.minecraft.client.resources.model.BakedModel; -import net.minecraft.client.resources.model.Material; -import net.minecraft.client.resources.model.ModelBaker; -import net.minecraft.client.resources.model.ModelState; -import net.minecraft.client.resources.model.UnbakedModel; -import net.minecraft.resources.Identifier; -import net.minecraft.world.inventory.InventoryMenu; -import org.jetbrains.annotations.Nullable; - -import java.util.Collection; -import java.util.Collections; -import java.util.function.Function; - -public class BranchBlockUnbakedModel implements UnbakedModel { - - protected final Identifier barkTextureLocation; - protected final Identifier ringsTextureLocation; - protected final Identifier familyName; - protected final boolean forceThickness; - - public BranchBlockUnbakedModel(Identifier barkTextureLocation, Identifier ringsTextureLocation, @Nullable Identifier familyName, boolean forceThickness) { - this.barkTextureLocation = barkTextureLocation; - this.ringsTextureLocation = ringsTextureLocation; - this.familyName = familyName; - this.forceThickness = forceThickness; - } - - @Override - public Collection getDependencies() { - return Collections.emptyList(); - } - - @Override - public void resolveParents(Function resolver) { - } - - @Override - public BakedModel bake(ModelBaker baker, Function spriteGetter, ModelState state) { - TextureAtlasSprite barkSprite = spriteGetter.apply(new Material(InventoryMenu.BLOCK_ATLAS, barkTextureLocation)); - TextureAtlasSprite ringsSprite = spriteGetter.apply(new Material(InventoryMenu.BLOCK_ATLAS, ringsTextureLocation)); - - Family family = familyName != null ? Family.REGISTRY.get(familyName) : null; - boolean useThickModel = forceThickness || (family != null && family.isThick()); - - if (useThickModel) { - Identifier thickRingsLocation = ringsTextureLocation.withSuffix("_thick"); - TextureAtlasSprite thickRingsSprite = spriteGetter.apply(new Material(InventoryMenu.BLOCK_ATLAS, thickRingsLocation)); - return new ThickBranchBlockBakedModel(barkSprite, ringsSprite, thickRingsSprite); - } - - return new BasicBranchBlockBakedModel(barkSprite, ringsSprite); - } -} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/DTModelLoadingPlugin.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/DTModelLoadingPlugin.java index ec4c14306..1513a0ecc 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/model/DTModelLoadingPlugin.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/DTModelLoadingPlugin.java @@ -8,239 +8,227 @@ import com.dtteam.dynamictrees.model.baked.BasicRootsBlockBakedModel; import com.dtteam.dynamictrees.model.baked.SurfaceRootBlockBakedModel; import com.dtteam.dynamictrees.model.baked.ThickBranchBlockBakedModel; -import com.dtteam.dynamictrees.tree.family.Family; +import com.dtteam.dynamictrees.model.blockstate.AerialRootsSoilBlockStateModel; +import com.dtteam.dynamictrees.model.blockstate.PottedSaplingBlockStateModel; +import com.dtteam.dynamictrees.model.blockstate.UnbakedBranchModel; +import com.dtteam.dynamictrees.model.blockstate.UnbakedCreakingHeartModel; +import com.dtteam.dynamictrees.model.blockstate.UnbakedRootsMossModel; +import com.dtteam.dynamictrees.model.blockstate.UnbakedRootsModel; +import com.dtteam.dynamictrees.model.blockstate.UnbakedSurfaceRootModel; +import com.dtteam.dynamictrees.model.parts.BranchModelPart; import com.dtteam.dynamictrees.tree.family.AerialRootsFamily; +import com.dtteam.dynamictrees.tree.family.Family; +import com.dtteam.dynamictrees.utility.IdentifierUtils; +import net.fabricmc.fabric.api.client.model.loading.v1.CustomUnbakedBlockStateModel; import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin; import net.fabricmc.fabric.api.client.model.loading.v1.ModelModifier; -import net.minecraft.client.renderer.texture.TextureAtlasSprite; -import net.minecraft.client.resources.model.BakedModel; -import net.minecraft.client.resources.model.Material; -import net.minecraft.client.resources.model.ModelIdentifier; +import net.minecraft.client.renderer.block.dispatch.BlockStateModel; +import net.minecraft.client.resources.model.ModelBaker; +import net.minecraft.client.resources.model.sprite.Material; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.Identifier; -import net.minecraft.world.inventory.InventoryMenu; import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import org.jetbrains.annotations.Nullable; -import java.util.HashMap; import java.util.Map; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Function; - +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Model loading hooks for DT's dynamic models on Fabric. + * + *

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

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