diff --git a/build.gradle b/build.gradle index 98360aaff..aad6837f5 100644 --- a/build.gradle +++ b/build.gradle @@ -159,7 +159,10 @@ project(':forge') { ] } resources { - srcDirs = ["$rootDir/src/test/resources"] + srcDirs = [ + "$rootDir/src/test/resources", + "$rootDir/src/generated_test/resources" + ] } } userdev { @@ -308,7 +311,8 @@ project(':forge') { source sourceSets.main source sourceSets.userdev - args '--mod', 'forge', '--all', '--output', rootProject.file('src/generated/resources/') + args '--mod', 'forge', '--all', '--output', rootProject.file('src/generated/resources/'), '--validate', + '--existing', sourceSets.main.resources.srcDirs[0] } forge_test_data { @@ -321,7 +325,8 @@ project(':forge') { tests { sources sourceSets.test } } - args '--mod', 'data_gen_test', '--all', '--output', rootProject.file('src/generated_test/resources/') + args '--mod', 'data_gen_test', '--all', '--output', rootProject.file('src/generated_test/resources/'), '--validate', + '--existing', sourceSets.main.resources.srcDirs[0] } } } diff --git a/patches/minecraft/net/minecraft/data/Main.java.patch b/patches/minecraft/net/minecraft/data/Main.java.patch index 912e2d88f..8cf8910bf 100644 --- a/patches/minecraft/net/minecraft/data/Main.java.patch +++ b/patches/minecraft/net/minecraft/data/Main.java.patch @@ -7,10 +7,11 @@ import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; -@@ -21,8 +22,10 @@ +@@ -21,8 +22,11 @@ OptionSpec optionspec6 = optionparser.accepts("all", "Include all generators"); OptionSpec optionspec7 = optionparser.accepts("output", "Output folder").withRequiredArg().defaultsTo("generated"); OptionSpec optionspec8 = optionparser.accepts("input", "Input folder").withRequiredArg(); ++ OptionSpec existing = optionparser.accepts("existing", "Existing resource packs that generated resources can reference").withRequiredArg(); + OptionSpec gameDir = optionparser.accepts("gameDir").withRequiredArg().ofType(File.class).defaultsTo(new File(".")).required(); + OptionSpec mod = optionparser.accepts("mod", "A modid to dump").withRequiredArg().withValuesSeparatedBy(","); OptionSet optionset = optionparser.parse(p_main_0_); @@ -19,7 +20,7 @@ Path path = Paths.get(optionspec7.value(optionset)); boolean flag = optionset.has(optionspec6); boolean flag1 = flag || optionset.has(optionspec2); -@@ -30,10 +33,11 @@ +@@ -30,10 +34,12 @@ boolean flag3 = flag || optionset.has(optionspec3); boolean flag4 = flag || optionset.has(optionspec4); boolean flag5 = flag || optionset.has(optionspec5); @@ -28,8 +29,9 @@ - }).collect(Collectors.toList()), flag1, flag2, flag3, flag4, flag5); - datagenerator.func_200392_c(); + Collection inputs = optionset.valuesOf(optionspec8).stream().map(Paths::get).collect(Collectors.toList()); ++ Collection existingPacks = optionset.valuesOf(existing).stream().map(Paths::get).collect(Collectors.toList()); + java.util.Set mods = new java.util.HashSet<>(optionset.valuesOf(mod)); -+ net.minecraftforge.fml.ModLoader.get().runDataGenerator(mods, path, inputs, flag2, flag1, flag3, flag4, flag5); ++ net.minecraftforge.fml.ModLoader.get().runDataGenerator(mods, path, inputs, existingPacks, flag2, flag1, flag3, flag4, flag5); + if (mods.contains("minecraft") || mods.isEmpty()) + func_200264_a(mods.isEmpty() ? path : path.resolve("minecraft"), inputs, flag1, flag2, flag3, flag4, flag5).func_200392_c(); } else { diff --git a/src/generated_test/resources/.cache/cache b/src/generated_test/resources/.cache/cache index 508842c77..58f1a4909 100644 --- a/src/generated_test/resources/.cache/cache +++ b/src/generated_test/resources/.cache/cache @@ -1,2 +1,64 @@ +dc2deb0c2da07695855bbb88882455a80787ccac assets\data_gen_test\models\block\acacia_door_bottom.json +e987df4921fc71322984556e9617915d4df2bdff assets\data_gen_test\models\block\acacia_door_bottom_hinge.json +5103910559b21ce74f315358935a3e582b0c45cd assets\data_gen_test\models\block\acacia_door_top.json +590e2cc0ba335ffc618441d6fcc582401ee646cc assets\data_gen_test\models\block\acacia_door_top_hinge.json +9c4e371aace89c681b4ea367975f9b4569acaada assets\data_gen_test\models\block\acacia_fence_gate.json +1e2e4ace5adef04a3cee3d528c33382a18f40513 assets\data_gen_test\models\block\acacia_fence_gate_open.json +557312487766c0801958d72d13b0ac16d8d5d9fd assets\data_gen_test\models\block\acacia_fence_gate_wall.json +dd8d2e76831b2a445737f378458db5c505a9b03d assets\data_gen_test\models\block\acacia_fence_gate_wall_open.json +4d5b8be3e004e34e75c0f7d415595e14f8985d6c assets\data_gen_test\models\block\acacia_fence_post.json +90491fcfa42ee4d8ec7958655866f5a3b55811ad assets\data_gen_test\models\block\acacia_fence_side.json +9bf079dec64b632446a867744149d0fccff6fc70 assets\data_gen_test\models\block\acacia_log.json +462367a50d9ab2306ca642a72b5fd20c217dc451 assets\data_gen_test\models\block\acacia_slab.json +3032bec1f17684ceef914e3763b9be773d17b02f assets\data_gen_test\models\block\acacia_slab_top.json +3611756373823dced8f5db5237a6ee0a5f3cbb7a assets\data_gen_test\models\block\acacia_stairs.json +72a7c67a73c6217ad117d5583a329e89fd34f246 assets\data_gen_test\models\block\acacia_stairs_inner.json +34f85d11d44a198433f530bd54c2c172d7699872 assets\data_gen_test\models\block\acacia_stairs_outer.json +31c3b7793cb61870bd74e8e85af7597be57827c6 assets\data_gen_test\models\block\acacia_trapdoor_bottom.json +8be9644fe5227cb2c3af52af01b02e9c2c4f9a38 assets\data_gen_test\models\block\acacia_trapdoor_open.json +8978e4a1d2902029738eac82749ffc2c30088fe9 assets\data_gen_test\models\block\acacia_trapdoor_top.json +88c7bca952fda50e5287ddb5c2e74ff179d08eb2 assets\data_gen_test\models\block\barrel.json +ce5d10ee7ab7afed216f3159bdcd5b1deb114104 assets\data_gen_test\models\block\barrel_open.json +c3ecb601615c300cafbdd9dc28f9fc42b6835dcd assets\data_gen_test\models\block\birch_fence_gate.json +3bcea12fba8714c7ad7c00f440f3e7546a8958ce assets\data_gen_test\models\block\birch_fence_gate_open.json +13b47687fb80334b3e51781e691f53f8073843a7 assets\data_gen_test\models\block\birch_fence_gate_wall.json +dd0a89461e5f72c08ccf6f3d07bce9c290019022 assets\data_gen_test\models\block\birch_fence_gate_wall_open.json +1d0f4dceda264528ae1b1f5f971ae84202405e51 assets\data_gen_test\models\block\block.json +42fcd2c027f204708f423808f22f6e4c0d5f586b assets\data_gen_test\models\block\cobblestone_wall_post.json +21d8ec1410152c67c4b7a06184be545ef1e1d0cf assets\data_gen_test\models\block\cobblestone_wall_side.json +26c80ee9a99336b434c9984445bfcf01b25f2cd0 assets\data_gen_test\models\block\cube.json +428f68585535789c809eb40d8151a5baa2b0045b assets\data_gen_test\models\block\furnace.json +51a161348cf0c51252dbdbad035b7945e02a8763 assets\data_gen_test\models\block\furnace_on.json +eb911d39c848d09bc5910aef1a86af63b4fbb6c6 assets\data_gen_test\models\block\glass_pane_noside.json +e5df8cc0b7dd9f79fe930711bcf8e60385b8b7ab assets\data_gen_test\models\block\glass_pane_noside_alt.json +b178beb42e1db7d177ceb28f0703876943c22a90 assets\data_gen_test\models\block\glass_pane_post.json +17cd1168c6c32e1fbd870a9e61716aed6ba7c8a2 assets\data_gen_test\models\block\glass_pane_side.json +85326250de254ad1bba0ad183c78e876afa3da51 assets\data_gen_test\models\block\glass_pane_side_alt.json +cebd38234491e0882901230538b4205bcf816597 assets\data_gen_test\models\block\oak_trapdoor_bottom.json +d8842f8f1a63ee4ac0bcc52e83a375de983b8bb0 assets\data_gen_test\models\block\oak_trapdoor_open.json +091f1cf54c828d8c8315cedeb28a3f864a72456e assets\data_gen_test\models\block\oak_trapdoor_top.json +a5ace137c931b71acf2599184832187a241acc9c assets\data_gen_test\models\block\stone.json +78f9176f15f3a728ee20b5a734e89a32c8108395 assets\data_gen_test\models\block\torch.json +e1820400e7b8e4011121eae3f7c873d73f301cd7 assets\data_gen_test\models\block\wall_torch.json +d96ff395b92ed2419c8c4aa6e61a610ef78b3216 assets\data_gen_test\models\item\fishing_rod.json +abe69efd05a6349acca7e39f3353d7c928a55a72 assets\data_gen_test\models\item\fishing_rod_cast.json +e1a3a09af48181e868c26741053e36eafe8bead6 assets\data_gen_test\models\item\test_block_model.json +9a27e0d9a3f3d2a834eff92661e090eeda1c59fe assets\data_gen_test\models\item\test_generated_model.json +1767c874758bd7c3235db446b6219e6e1edd25b4 assets\minecraft\blockstates\acacia_door.json +e41383b4c13ec922c0f5cfa2cc313d88fd294685 assets\minecraft\blockstates\acacia_fence.json +967d268bfcf32474b61578bef16e853b6360dd73 assets\minecraft\blockstates\acacia_fence_gate.json +e9af65f877e6d012bce27ae35eef3822dda98675 assets\minecraft\blockstates\acacia_log.json +2fc1c2cff82aaebdb4ecf298e6de278c62cce3b3 assets\minecraft\blockstates\acacia_slab.json +075938fd82340c895eff226d11898abae9a5e178 assets\minecraft\blockstates\acacia_stairs.json +9384d458574395650a976b3ad1fe5905b100ddbf assets\minecraft\blockstates\acacia_trapdoor.json +42a10a132337264c8821426e17d0c09b92ad42f9 assets\minecraft\blockstates\barrel.json +9bc8ba4b8e4ad3cc7074838cf1bf8dad89e2b339 assets\minecraft\blockstates\birch_fence_gate.json +acbf9f921b05785d49d8526870ba13aee1a322f7 assets\minecraft\blockstates\cobblestone_wall.json +6a3c8e2691b6b7f39f1236a17bb6aa145a9db53f assets\minecraft\blockstates\furnace.json +1b2a1344020237ab877e27c43934b042d259bf36 assets\minecraft\blockstates\glass_pane.json +bf2e445b48b024354a69138b20a4a4a8aa5d15d1 assets\minecraft\blockstates\oak_trapdoor.json +9c4cc92efb78811e8d70a383a1a89eb75b69db04 assets\minecraft\blockstates\stone.json +570fdd86046df75a073b759ff7aaf4c4caf43115 assets\minecraft\blockstates\torch.json +16f12628d0c23a4ca66961b0a1c837c3b1e4457d assets\minecraft\blockstates\wall_torch.json 4fbaf6f4a3ea05cc071076e27f44ac81f9cc50e3 data\data_gen_test\advancements\conditional.json ed4cbf1a3a2f5d8969f6346fdc9acdbe81d0c919 data\data_gen_test\recipes\conditional.json diff --git a/src/generated_test/resources/assets/data_gen_test/models/block/acacia_door_bottom.json b/src/generated_test/resources/assets/data_gen_test/models/block/acacia_door_bottom.json new file mode 100644 index 000000000..9f605d574 --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/block/acacia_door_bottom.json @@ -0,0 +1,7 @@ +{ + "parent": "block/door_bottom", + "textures": { + "bottom": "block/acacia_door_bottom", + "top": "block/acacia_door_top" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/block/acacia_door_bottom_hinge.json b/src/generated_test/resources/assets/data_gen_test/models/block/acacia_door_bottom_hinge.json new file mode 100644 index 000000000..82df44a19 --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/block/acacia_door_bottom_hinge.json @@ -0,0 +1,7 @@ +{ + "parent": "block/door_bottom_rh", + "textures": { + "bottom": "block/acacia_door_bottom", + "top": "block/acacia_door_top" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/block/acacia_door_top.json b/src/generated_test/resources/assets/data_gen_test/models/block/acacia_door_top.json new file mode 100644 index 000000000..4acc6bb1a --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/block/acacia_door_top.json @@ -0,0 +1,7 @@ +{ + "parent": "block/door_top", + "textures": { + "bottom": "block/acacia_door_bottom", + "top": "block/acacia_door_top" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/block/acacia_door_top_hinge.json b/src/generated_test/resources/assets/data_gen_test/models/block/acacia_door_top_hinge.json new file mode 100644 index 000000000..c07f4ec6a --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/block/acacia_door_top_hinge.json @@ -0,0 +1,7 @@ +{ + "parent": "block/door_top_rh", + "textures": { + "bottom": "block/acacia_door_bottom", + "top": "block/acacia_door_top" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/block/acacia_fence_gate.json b/src/generated_test/resources/assets/data_gen_test/models/block/acacia_fence_gate.json new file mode 100644 index 000000000..cec4f1f74 --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/block/acacia_fence_gate.json @@ -0,0 +1,6 @@ +{ + "parent": "block/template_fence_gate", + "textures": { + "texture": "block/acacia_planks" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/block/acacia_fence_gate_open.json b/src/generated_test/resources/assets/data_gen_test/models/block/acacia_fence_gate_open.json new file mode 100644 index 000000000..a0a850678 --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/block/acacia_fence_gate_open.json @@ -0,0 +1,6 @@ +{ + "parent": "block/template_fence_gate_open", + "textures": { + "texture": "block/acacia_planks" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/block/acacia_fence_gate_wall.json b/src/generated_test/resources/assets/data_gen_test/models/block/acacia_fence_gate_wall.json new file mode 100644 index 000000000..eebfb4dd1 --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/block/acacia_fence_gate_wall.json @@ -0,0 +1,6 @@ +{ + "parent": "block/template_fence_gate_wall", + "textures": { + "texture": "block/acacia_planks" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/block/acacia_fence_gate_wall_open.json b/src/generated_test/resources/assets/data_gen_test/models/block/acacia_fence_gate_wall_open.json new file mode 100644 index 000000000..d4c00c0aa --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/block/acacia_fence_gate_wall_open.json @@ -0,0 +1,6 @@ +{ + "parent": "block/template_fence_gate_wall_open", + "textures": { + "texture": "block/acacia_planks" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/block/acacia_fence_post.json b/src/generated_test/resources/assets/data_gen_test/models/block/acacia_fence_post.json new file mode 100644 index 000000000..d14fec081 --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/block/acacia_fence_post.json @@ -0,0 +1,6 @@ +{ + "parent": "block/fence_post", + "textures": { + "texture": "block/acacia_planks" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/block/acacia_fence_side.json b/src/generated_test/resources/assets/data_gen_test/models/block/acacia_fence_side.json new file mode 100644 index 000000000..8acc49bc3 --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/block/acacia_fence_side.json @@ -0,0 +1,6 @@ +{ + "parent": "block/fence_side", + "textures": { + "texture": "block/acacia_planks" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/block/acacia_log.json b/src/generated_test/resources/assets/data_gen_test/models/block/acacia_log.json new file mode 100644 index 000000000..30ecc29c7 --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/block/acacia_log.json @@ -0,0 +1,7 @@ +{ + "parent": "block/cube_column", + "textures": { + "side": "block/acacia_log", + "end": "block/acacia_log_top" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/block/acacia_slab.json b/src/generated_test/resources/assets/data_gen_test/models/block/acacia_slab.json new file mode 100644 index 000000000..5541feb7f --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/block/acacia_slab.json @@ -0,0 +1,8 @@ +{ + "parent": "block/slab", + "textures": { + "side": "block/acacia_planks", + "bottom": "block/acacia_planks", + "top": "block/acacia_planks" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/block/acacia_slab_top.json b/src/generated_test/resources/assets/data_gen_test/models/block/acacia_slab_top.json new file mode 100644 index 000000000..7281e9250 --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/block/acacia_slab_top.json @@ -0,0 +1,8 @@ +{ + "parent": "block/slab_top", + "textures": { + "side": "block/acacia_planks", + "bottom": "block/acacia_planks", + "top": "block/acacia_planks" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/block/acacia_stairs.json b/src/generated_test/resources/assets/data_gen_test/models/block/acacia_stairs.json new file mode 100644 index 000000000..fd0afbdc1 --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/block/acacia_stairs.json @@ -0,0 +1,8 @@ +{ + "parent": "block/stairs", + "textures": { + "side": "block/acacia_planks", + "bottom": "block/acacia_planks", + "top": "block/acacia_planks" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/block/acacia_stairs_inner.json b/src/generated_test/resources/assets/data_gen_test/models/block/acacia_stairs_inner.json new file mode 100644 index 000000000..8fdeaf14a --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/block/acacia_stairs_inner.json @@ -0,0 +1,8 @@ +{ + "parent": "block/inner_stairs", + "textures": { + "side": "block/acacia_planks", + "bottom": "block/acacia_planks", + "top": "block/acacia_planks" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/block/acacia_stairs_outer.json b/src/generated_test/resources/assets/data_gen_test/models/block/acacia_stairs_outer.json new file mode 100644 index 000000000..de26ea593 --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/block/acacia_stairs_outer.json @@ -0,0 +1,8 @@ +{ + "parent": "block/outer_stairs", + "textures": { + "side": "block/acacia_planks", + "bottom": "block/acacia_planks", + "top": "block/acacia_planks" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/block/acacia_trapdoor_bottom.json b/src/generated_test/resources/assets/data_gen_test/models/block/acacia_trapdoor_bottom.json new file mode 100644 index 000000000..24b50301a --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/block/acacia_trapdoor_bottom.json @@ -0,0 +1,6 @@ +{ + "parent": "block/template_orientable_trapdoor_bottom", + "textures": { + "texture": "block/acacia_trapdoor" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/block/acacia_trapdoor_open.json b/src/generated_test/resources/assets/data_gen_test/models/block/acacia_trapdoor_open.json new file mode 100644 index 000000000..5bf0f4922 --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/block/acacia_trapdoor_open.json @@ -0,0 +1,6 @@ +{ + "parent": "block/template_orientable_trapdoor_open", + "textures": { + "texture": "block/acacia_trapdoor" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/block/acacia_trapdoor_top.json b/src/generated_test/resources/assets/data_gen_test/models/block/acacia_trapdoor_top.json new file mode 100644 index 000000000..7eb25f700 --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/block/acacia_trapdoor_top.json @@ -0,0 +1,6 @@ +{ + "parent": "block/template_orientable_trapdoor_top", + "textures": { + "texture": "block/acacia_trapdoor" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/block/barrel.json b/src/generated_test/resources/assets/data_gen_test/models/block/barrel.json new file mode 100644 index 000000000..c36cc35c2 --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/block/barrel.json @@ -0,0 +1,8 @@ +{ + "parent": "block/cube_bottom_top", + "textures": { + "side": "block/barrel_side", + "bottom": "block/barrel_bottom", + "top": "block/barrel_top" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/block/barrel_open.json b/src/generated_test/resources/assets/data_gen_test/models/block/barrel_open.json new file mode 100644 index 000000000..0ee476b52 --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/block/barrel_open.json @@ -0,0 +1,8 @@ +{ + "parent": "block/cube_bottom_top", + "textures": { + "side": "block/barrel_side", + "bottom": "block/barrel_bottom", + "top": "block/barrel_top_open" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/block/birch_fence_gate.json b/src/generated_test/resources/assets/data_gen_test/models/block/birch_fence_gate.json new file mode 100644 index 000000000..d1581f83a --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/block/birch_fence_gate.json @@ -0,0 +1,6 @@ +{ + "parent": "block/template_fence_gate", + "textures": { + "texture": "block/birch_planks" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/block/birch_fence_gate_open.json b/src/generated_test/resources/assets/data_gen_test/models/block/birch_fence_gate_open.json new file mode 100644 index 000000000..d2a06fbe1 --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/block/birch_fence_gate_open.json @@ -0,0 +1,6 @@ +{ + "parent": "block/template_fence_gate_open", + "textures": { + "texture": "block/birch_planks" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/block/birch_fence_gate_wall.json b/src/generated_test/resources/assets/data_gen_test/models/block/birch_fence_gate_wall.json new file mode 100644 index 000000000..0fa8d0443 --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/block/birch_fence_gate_wall.json @@ -0,0 +1,6 @@ +{ + "parent": "block/template_fence_gate_wall", + "textures": { + "texture": "block/birch_planks" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/block/birch_fence_gate_wall_open.json b/src/generated_test/resources/assets/data_gen_test/models/block/birch_fence_gate_wall_open.json new file mode 100644 index 000000000..61d8c6732 --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/block/birch_fence_gate_wall_open.json @@ -0,0 +1,6 @@ +{ + "parent": "block/template_fence_gate_wall_open", + "textures": { + "texture": "block/birch_planks" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/block/block.json b/src/generated_test/resources/assets/data_gen_test/models/block/block.json new file mode 100644 index 000000000..7ad9a5c7d --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/block/block.json @@ -0,0 +1,76 @@ +{ + "display": { + "gui": { + "rotation": [ + 30, + 225, + 0 + ], + "scale": [ + 0.625, + 0.625, + 0.625 + ] + }, + "ground": { + "translation": [ + 0, + 3, + 0 + ], + "scale": [ + 0.25, + 0.25, + 0.25 + ] + }, + "fixed": { + "scale": [ + 0.5, + 0.5, + 0.5 + ] + }, + "thirdperson_righthand": { + "rotation": [ + 75, + 45, + 0 + ], + "translation": [ + 0, + 2.5, + 0 + ], + "scale": [ + 0.375, + 0.375, + 0.375 + ] + }, + "firstperson_righthand": { + "rotation": [ + 0, + 45, + 0 + ], + "scale": [ + 0.4, + 0.4, + 0.4 + ] + }, + "firstperson_lefthand": { + "rotation": [ + 0, + 225, + 0 + ], + "scale": [ + 0.4, + 0.4, + 0.4 + ] + } + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/block/cobblestone_wall_post.json b/src/generated_test/resources/assets/data_gen_test/models/block/cobblestone_wall_post.json new file mode 100644 index 000000000..6913a1239 --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/block/cobblestone_wall_post.json @@ -0,0 +1,6 @@ +{ + "parent": "block/template_wall_post", + "textures": { + "wall": "block/cobblestone" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/block/cobblestone_wall_side.json b/src/generated_test/resources/assets/data_gen_test/models/block/cobblestone_wall_side.json new file mode 100644 index 000000000..14681a16b --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/block/cobblestone_wall_side.json @@ -0,0 +1,6 @@ +{ + "parent": "block/template_wall_side", + "textures": { + "wall": "block/cobblestone" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/block/cube.json b/src/generated_test/resources/assets/data_gen_test/models/block/cube.json new file mode 100644 index 000000000..6916e6421 --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/block/cube.json @@ -0,0 +1,43 @@ +{ + "parent": "data_gen_test:block/block", + "elements": [ + { + "from": [ + 0, + 0, + 0 + ], + "to": [ + 16, + 16, + 16 + ], + "faces": { + "down": { + "texture": "#down", + "cullface": "down" + }, + "up": { + "texture": "#up", + "cullface": "up" + }, + "north": { + "texture": "#north", + "cullface": "north" + }, + "south": { + "texture": "#south", + "cullface": "south" + }, + "west": { + "texture": "#west", + "cullface": "west" + }, + "east": { + "texture": "#east", + "cullface": "east" + } + } + } + ] +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/block/furnace.json b/src/generated_test/resources/assets/data_gen_test/models/block/furnace.json new file mode 100644 index 000000000..7935d9f83 --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/block/furnace.json @@ -0,0 +1,8 @@ +{ + "parent": "block/orientable", + "textures": { + "side": "block/furnace_side", + "front": "block/furnace_front", + "top": "block/furnace_top" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/block/furnace_on.json b/src/generated_test/resources/assets/data_gen_test/models/block/furnace_on.json new file mode 100644 index 000000000..5fc3aeffa --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/block/furnace_on.json @@ -0,0 +1,8 @@ +{ + "parent": "block/orientable", + "textures": { + "side": "block/furnace_side", + "front": "block/furnace_front_on", + "top": "block/furnace_top" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/block/glass_pane_noside.json b/src/generated_test/resources/assets/data_gen_test/models/block/glass_pane_noside.json new file mode 100644 index 000000000..f83a00bb1 --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/block/glass_pane_noside.json @@ -0,0 +1,6 @@ +{ + "parent": "block/template_glass_pane_noside", + "textures": { + "pane": "block/glass" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/block/glass_pane_noside_alt.json b/src/generated_test/resources/assets/data_gen_test/models/block/glass_pane_noside_alt.json new file mode 100644 index 000000000..63abc8d88 --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/block/glass_pane_noside_alt.json @@ -0,0 +1,6 @@ +{ + "parent": "block/template_glass_pane_noside_alt", + "textures": { + "pane": "block/glass" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/block/glass_pane_post.json b/src/generated_test/resources/assets/data_gen_test/models/block/glass_pane_post.json new file mode 100644 index 000000000..0c53deb7c --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/block/glass_pane_post.json @@ -0,0 +1,7 @@ +{ + "parent": "block/template_glass_pane_post", + "textures": { + "pane": "block/glass", + "edge": "block/glass_pane_top" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/block/glass_pane_side.json b/src/generated_test/resources/assets/data_gen_test/models/block/glass_pane_side.json new file mode 100644 index 000000000..3fbec203d --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/block/glass_pane_side.json @@ -0,0 +1,7 @@ +{ + "parent": "block/template_glass_pane_side", + "textures": { + "pane": "block/glass", + "edge": "block/glass_pane_top" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/block/glass_pane_side_alt.json b/src/generated_test/resources/assets/data_gen_test/models/block/glass_pane_side_alt.json new file mode 100644 index 000000000..eadcfe1e4 --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/block/glass_pane_side_alt.json @@ -0,0 +1,7 @@ +{ + "parent": "block/template_glass_pane_side_alt", + "textures": { + "pane": "block/glass", + "edge": "block/glass_pane_top" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/block/oak_trapdoor_bottom.json b/src/generated_test/resources/assets/data_gen_test/models/block/oak_trapdoor_bottom.json new file mode 100644 index 000000000..5fe3bc491 --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/block/oak_trapdoor_bottom.json @@ -0,0 +1,6 @@ +{ + "parent": "block/template_trapdoor_bottom", + "textures": { + "texture": "block/oak_trapdoor" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/block/oak_trapdoor_open.json b/src/generated_test/resources/assets/data_gen_test/models/block/oak_trapdoor_open.json new file mode 100644 index 000000000..3a282b078 --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/block/oak_trapdoor_open.json @@ -0,0 +1,6 @@ +{ + "parent": "block/template_trapdoor_open", + "textures": { + "texture": "block/oak_trapdoor" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/block/oak_trapdoor_top.json b/src/generated_test/resources/assets/data_gen_test/models/block/oak_trapdoor_top.json new file mode 100644 index 000000000..cce7c6409 --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/block/oak_trapdoor_top.json @@ -0,0 +1,6 @@ +{ + "parent": "block/template_trapdoor_top", + "textures": { + "texture": "block/oak_trapdoor" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/block/stone.json b/src/generated_test/resources/assets/data_gen_test/models/block/stone.json new file mode 100644 index 000000000..a165a4948 --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/block/stone.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cube_all", + "textures": { + "all": "block/stone" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/block/torch.json b/src/generated_test/resources/assets/data_gen_test/models/block/torch.json new file mode 100644 index 000000000..0b731a767 --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/block/torch.json @@ -0,0 +1,6 @@ +{ + "parent": "block/template_torch", + "textures": { + "torch": "block/torch" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/block/wall_torch.json b/src/generated_test/resources/assets/data_gen_test/models/block/wall_torch.json new file mode 100644 index 000000000..de8fb2415 --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/block/wall_torch.json @@ -0,0 +1,6 @@ +{ + "parent": "block/torch_wall", + "textures": { + "torch": "block/torch" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/item/fishing_rod.json b/src/generated_test/resources/assets/data_gen_test/models/item/fishing_rod.json new file mode 100644 index 000000000..c8d5fb65b --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/item/fishing_rod.json @@ -0,0 +1,14 @@ +{ + "parent": "item/handheld_rod", + "textures": { + "layer0": "item/fishing_rod" + }, + "overrides": [ + { + "predicate": { + "cast": 1.0 + }, + "model": "item/fishing_rod_cast" + } + ] +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/item/fishing_rod_cast.json b/src/generated_test/resources/assets/data_gen_test/models/item/fishing_rod_cast.json new file mode 100644 index 000000000..b541b7f3a --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/item/fishing_rod_cast.json @@ -0,0 +1,6 @@ +{ + "parent": "data_gen_test:item/fishing_rod", + "textures": { + "layer0": "item/fishing_rod_cast" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/item/test_block_model.json b/src/generated_test/resources/assets/data_gen_test/models/item/test_block_model.json new file mode 100644 index 000000000..db3290bff --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/item/test_block_model.json @@ -0,0 +1,48 @@ +{ + "parent": "block/block", + "textures": { + "all": "block/dirt", + "top": "block/stone" + }, + "elements": [ + { + "from": [ + 0, + 0, + 0 + ], + "to": [ + 16, + 16, + 16 + ], + "faces": { + "down": { + "texture": "#all", + "cullface": "down" + }, + "up": { + "texture": "#top", + "cullface": "up", + "tintindex": 0 + }, + "north": { + "texture": "#all", + "cullface": "north" + }, + "south": { + "texture": "#all", + "cullface": "south" + }, + "west": { + "texture": "#all", + "cullface": "west" + }, + "east": { + "texture": "#all", + "cullface": "east" + } + } + } + ] +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/data_gen_test/models/item/test_generated_model.json b/src/generated_test/resources/assets/data_gen_test/models/item/test_generated_model.json new file mode 100644 index 000000000..d2e4716f2 --- /dev/null +++ b/src/generated_test/resources/assets/data_gen_test/models/item/test_generated_model.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "block/stone" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/minecraft/blockstates/acacia_door.json b/src/generated_test/resources/assets/minecraft/blockstates/acacia_door.json new file mode 100644 index 000000000..2e1ac6526 --- /dev/null +++ b/src/generated_test/resources/assets/minecraft/blockstates/acacia_door.json @@ -0,0 +1,124 @@ +{ + "variants": { + "facing=north,half=upper,hinge=left,open=false": { + "model": "data_gen_test:block/acacia_door_top", + "y": 270 + }, + "facing=south,half=upper,hinge=left,open=false": { + "model": "data_gen_test:block/acacia_door_top", + "y": 90 + }, + "facing=west,half=upper,hinge=left,open=false": { + "model": "data_gen_test:block/acacia_door_top", + "y": 180 + }, + "facing=east,half=upper,hinge=left,open=false": { + "model": "data_gen_test:block/acacia_door_top" + }, + "facing=north,half=lower,hinge=left,open=false": { + "model": "data_gen_test:block/acacia_door_bottom", + "y": 270 + }, + "facing=south,half=lower,hinge=left,open=false": { + "model": "data_gen_test:block/acacia_door_bottom", + "y": 90 + }, + "facing=west,half=lower,hinge=left,open=false": { + "model": "data_gen_test:block/acacia_door_bottom", + "y": 180 + }, + "facing=east,half=lower,hinge=left,open=false": { + "model": "data_gen_test:block/acacia_door_bottom" + }, + "facing=north,half=upper,hinge=right,open=false": { + "model": "data_gen_test:block/acacia_door_top_hinge", + "y": 270 + }, + "facing=south,half=upper,hinge=right,open=false": { + "model": "data_gen_test:block/acacia_door_top_hinge", + "y": 90 + }, + "facing=west,half=upper,hinge=right,open=false": { + "model": "data_gen_test:block/acacia_door_top_hinge", + "y": 180 + }, + "facing=east,half=upper,hinge=right,open=false": { + "model": "data_gen_test:block/acacia_door_top_hinge" + }, + "facing=north,half=lower,hinge=right,open=false": { + "model": "data_gen_test:block/acacia_door_bottom_hinge", + "y": 270 + }, + "facing=south,half=lower,hinge=right,open=false": { + "model": "data_gen_test:block/acacia_door_bottom_hinge", + "y": 90 + }, + "facing=west,half=lower,hinge=right,open=false": { + "model": "data_gen_test:block/acacia_door_bottom_hinge", + "y": 180 + }, + "facing=east,half=lower,hinge=right,open=false": { + "model": "data_gen_test:block/acacia_door_bottom_hinge" + }, + "facing=north,half=upper,hinge=left,open=true": { + "model": "data_gen_test:block/acacia_door_top_hinge" + }, + "facing=south,half=upper,hinge=left,open=true": { + "model": "data_gen_test:block/acacia_door_top_hinge", + "y": 180 + }, + "facing=west,half=upper,hinge=left,open=true": { + "model": "data_gen_test:block/acacia_door_top_hinge", + "y": 270 + }, + "facing=east,half=upper,hinge=left,open=true": { + "model": "data_gen_test:block/acacia_door_top_hinge", + "y": 90 + }, + "facing=north,half=lower,hinge=left,open=true": { + "model": "data_gen_test:block/acacia_door_bottom_hinge" + }, + "facing=south,half=lower,hinge=left,open=true": { + "model": "data_gen_test:block/acacia_door_bottom_hinge", + "y": 180 + }, + "facing=west,half=lower,hinge=left,open=true": { + "model": "data_gen_test:block/acacia_door_bottom_hinge", + "y": 270 + }, + "facing=east,half=lower,hinge=left,open=true": { + "model": "data_gen_test:block/acacia_door_bottom_hinge", + "y": 90 + }, + "facing=north,half=upper,hinge=right,open=true": { + "model": "data_gen_test:block/acacia_door_top", + "y": 180 + }, + "facing=south,half=upper,hinge=right,open=true": { + "model": "data_gen_test:block/acacia_door_top" + }, + "facing=west,half=upper,hinge=right,open=true": { + "model": "data_gen_test:block/acacia_door_top", + "y": 90 + }, + "facing=east,half=upper,hinge=right,open=true": { + "model": "data_gen_test:block/acacia_door_top", + "y": 270 + }, + "facing=north,half=lower,hinge=right,open=true": { + "model": "data_gen_test:block/acacia_door_bottom", + "y": 180 + }, + "facing=south,half=lower,hinge=right,open=true": { + "model": "data_gen_test:block/acacia_door_bottom" + }, + "facing=west,half=lower,hinge=right,open=true": { + "model": "data_gen_test:block/acacia_door_bottom", + "y": 90 + }, + "facing=east,half=lower,hinge=right,open=true": { + "model": "data_gen_test:block/acacia_door_bottom", + "y": 270 + } + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/minecraft/blockstates/acacia_fence.json b/src/generated_test/resources/assets/minecraft/blockstates/acacia_fence.json new file mode 100644 index 000000000..f51a6d3d2 --- /dev/null +++ b/src/generated_test/resources/assets/minecraft/blockstates/acacia_fence.json @@ -0,0 +1,48 @@ +{ + "multipart": [ + { + "apply": { + "model": "data_gen_test:block/acacia_fence_post" + } + }, + { + "when": { + "north": "true" + }, + "apply": { + "model": "data_gen_test:block/acacia_fence_side", + "uvlock": true + } + }, + { + "when": { + "south": "true" + }, + "apply": { + "model": "data_gen_test:block/acacia_fence_side", + "y": 180, + "uvlock": true + } + }, + { + "when": { + "west": "true" + }, + "apply": { + "model": "data_gen_test:block/acacia_fence_side", + "y": 270, + "uvlock": true + } + }, + { + "when": { + "east": "true" + }, + "apply": { + "model": "data_gen_test:block/acacia_fence_side", + "y": 90, + "uvlock": true + } + } + ] +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/minecraft/blockstates/acacia_fence_gate.json b/src/generated_test/resources/assets/minecraft/blockstates/acacia_fence_gate.json new file mode 100644 index 000000000..637165670 --- /dev/null +++ b/src/generated_test/resources/assets/minecraft/blockstates/acacia_fence_gate.json @@ -0,0 +1,80 @@ +{ + "variants": { + "facing=north,in_wall=false,open=false": { + "model": "data_gen_test:block/acacia_fence_gate", + "y": 180, + "uvlock": true + }, + "facing=south,in_wall=false,open=false": { + "model": "data_gen_test:block/acacia_fence_gate", + "uvlock": true + }, + "facing=west,in_wall=false,open=false": { + "model": "data_gen_test:block/acacia_fence_gate", + "y": 90, + "uvlock": true + }, + "facing=east,in_wall=false,open=false": { + "model": "data_gen_test:block/acacia_fence_gate", + "y": 270, + "uvlock": true + }, + "facing=north,in_wall=true,open=false": { + "model": "data_gen_test:block/acacia_fence_gate_wall", + "y": 180, + "uvlock": true + }, + "facing=south,in_wall=true,open=false": { + "model": "data_gen_test:block/acacia_fence_gate_wall", + "uvlock": true + }, + "facing=west,in_wall=true,open=false": { + "model": "data_gen_test:block/acacia_fence_gate_wall", + "y": 90, + "uvlock": true + }, + "facing=east,in_wall=true,open=false": { + "model": "data_gen_test:block/acacia_fence_gate_wall", + "y": 270, + "uvlock": true + }, + "facing=north,in_wall=false,open=true": { + "model": "data_gen_test:block/acacia_fence_gate_open", + "y": 180, + "uvlock": true + }, + "facing=south,in_wall=false,open=true": { + "model": "data_gen_test:block/acacia_fence_gate_open", + "uvlock": true + }, + "facing=west,in_wall=false,open=true": { + "model": "data_gen_test:block/acacia_fence_gate_open", + "y": 90, + "uvlock": true + }, + "facing=east,in_wall=false,open=true": { + "model": "data_gen_test:block/acacia_fence_gate_open", + "y": 270, + "uvlock": true + }, + "facing=north,in_wall=true,open=true": { + "model": "data_gen_test:block/acacia_fence_gate_wall_open", + "y": 180, + "uvlock": true + }, + "facing=south,in_wall=true,open=true": { + "model": "data_gen_test:block/acacia_fence_gate_wall_open", + "uvlock": true + }, + "facing=west,in_wall=true,open=true": { + "model": "data_gen_test:block/acacia_fence_gate_wall_open", + "y": 90, + "uvlock": true + }, + "facing=east,in_wall=true,open=true": { + "model": "data_gen_test:block/acacia_fence_gate_wall_open", + "y": 270, + "uvlock": true + } + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/minecraft/blockstates/acacia_log.json b/src/generated_test/resources/assets/minecraft/blockstates/acacia_log.json new file mode 100644 index 000000000..1c2762bef --- /dev/null +++ b/src/generated_test/resources/assets/minecraft/blockstates/acacia_log.json @@ -0,0 +1,16 @@ +{ + "variants": { + "axis=x": { + "model": "data_gen_test:block/acacia_log", + "x": 90, + "y": 90 + }, + "axis=y": { + "model": "data_gen_test:block/acacia_log" + }, + "axis=z": { + "model": "data_gen_test:block/acacia_log", + "x": 90 + } + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/minecraft/blockstates/acacia_slab.json b/src/generated_test/resources/assets/minecraft/blockstates/acacia_slab.json new file mode 100644 index 000000000..5d28d6bad --- /dev/null +++ b/src/generated_test/resources/assets/minecraft/blockstates/acacia_slab.json @@ -0,0 +1,13 @@ +{ + "variants": { + "type=top": { + "model": "data_gen_test:block/acacia_slab_top" + }, + "type=bottom": { + "model": "data_gen_test:block/acacia_slab" + }, + "type=double": { + "model": "minecraft:block/acacia_planks" + } + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/minecraft/blockstates/acacia_stairs.json b/src/generated_test/resources/assets/minecraft/blockstates/acacia_stairs.json new file mode 100644 index 000000000..1c5ac6f2e --- /dev/null +++ b/src/generated_test/resources/assets/minecraft/blockstates/acacia_stairs.json @@ -0,0 +1,209 @@ +{ + "variants": { + "facing=north,half=top,shape=straight": { + "model": "data_gen_test:block/acacia_stairs", + "x": 180, + "y": 270, + "uvlock": true + }, + "facing=south,half=top,shape=straight": { + "model": "data_gen_test:block/acacia_stairs", + "x": 180, + "y": 90, + "uvlock": true + }, + "facing=west,half=top,shape=straight": { + "model": "data_gen_test:block/acacia_stairs", + "x": 180, + "y": 180, + "uvlock": true + }, + "facing=east,half=top,shape=straight": { + "model": "data_gen_test:block/acacia_stairs", + "x": 180, + "uvlock": true + }, + "facing=north,half=bottom,shape=straight": { + "model": "data_gen_test:block/acacia_stairs", + "y": 270, + "uvlock": true + }, + "facing=south,half=bottom,shape=straight": { + "model": "data_gen_test:block/acacia_stairs", + "y": 90, + "uvlock": true + }, + "facing=west,half=bottom,shape=straight": { + "model": "data_gen_test:block/acacia_stairs", + "y": 180, + "uvlock": true + }, + "facing=east,half=bottom,shape=straight": { + "model": "data_gen_test:block/acacia_stairs" + }, + "facing=north,half=top,shape=inner_left": { + "model": "data_gen_test:block/acacia_stairs_inner", + "x": 180, + "y": 270, + "uvlock": true + }, + "facing=south,half=top,shape=inner_left": { + "model": "data_gen_test:block/acacia_stairs_inner", + "x": 180, + "y": 90, + "uvlock": true + }, + "facing=west,half=top,shape=inner_left": { + "model": "data_gen_test:block/acacia_stairs_inner", + "x": 180, + "y": 180, + "uvlock": true + }, + "facing=east,half=top,shape=inner_left": { + "model": "data_gen_test:block/acacia_stairs_inner", + "x": 180, + "uvlock": true + }, + "facing=north,half=bottom,shape=inner_left": { + "model": "data_gen_test:block/acacia_stairs_inner", + "y": 180, + "uvlock": true + }, + "facing=south,half=bottom,shape=inner_left": { + "model": "data_gen_test:block/acacia_stairs_inner" + }, + "facing=west,half=bottom,shape=inner_left": { + "model": "data_gen_test:block/acacia_stairs_inner", + "y": 90, + "uvlock": true + }, + "facing=east,half=bottom,shape=inner_left": { + "model": "data_gen_test:block/acacia_stairs_inner", + "y": 270, + "uvlock": true + }, + "facing=north,half=top,shape=inner_right": { + "model": "data_gen_test:block/acacia_stairs_inner", + "x": 180, + "uvlock": true + }, + "facing=south,half=top,shape=inner_right": { + "model": "data_gen_test:block/acacia_stairs_inner", + "x": 180, + "y": 180, + "uvlock": true + }, + "facing=west,half=top,shape=inner_right": { + "model": "data_gen_test:block/acacia_stairs_inner", + "x": 180, + "y": 270, + "uvlock": true + }, + "facing=east,half=top,shape=inner_right": { + "model": "data_gen_test:block/acacia_stairs_inner", + "x": 180, + "y": 90, + "uvlock": true + }, + "facing=north,half=bottom,shape=inner_right": { + "model": "data_gen_test:block/acacia_stairs_inner", + "y": 270, + "uvlock": true + }, + "facing=south,half=bottom,shape=inner_right": { + "model": "data_gen_test:block/acacia_stairs_inner", + "y": 90, + "uvlock": true + }, + "facing=west,half=bottom,shape=inner_right": { + "model": "data_gen_test:block/acacia_stairs_inner", + "y": 180, + "uvlock": true + }, + "facing=east,half=bottom,shape=inner_right": { + "model": "data_gen_test:block/acacia_stairs_inner" + }, + "facing=north,half=top,shape=outer_left": { + "model": "data_gen_test:block/acacia_stairs_outer", + "x": 180, + "y": 270, + "uvlock": true + }, + "facing=south,half=top,shape=outer_left": { + "model": "data_gen_test:block/acacia_stairs_outer", + "x": 180, + "y": 90, + "uvlock": true + }, + "facing=west,half=top,shape=outer_left": { + "model": "data_gen_test:block/acacia_stairs_outer", + "x": 180, + "y": 180, + "uvlock": true + }, + "facing=east,half=top,shape=outer_left": { + "model": "data_gen_test:block/acacia_stairs_outer", + "x": 180, + "uvlock": true + }, + "facing=north,half=bottom,shape=outer_left": { + "model": "data_gen_test:block/acacia_stairs_outer", + "y": 180, + "uvlock": true + }, + "facing=south,half=bottom,shape=outer_left": { + "model": "data_gen_test:block/acacia_stairs_outer" + }, + "facing=west,half=bottom,shape=outer_left": { + "model": "data_gen_test:block/acacia_stairs_outer", + "y": 90, + "uvlock": true + }, + "facing=east,half=bottom,shape=outer_left": { + "model": "data_gen_test:block/acacia_stairs_outer", + "y": 270, + "uvlock": true + }, + "facing=north,half=top,shape=outer_right": { + "model": "data_gen_test:block/acacia_stairs_outer", + "x": 180, + "uvlock": true + }, + "facing=south,half=top,shape=outer_right": { + "model": "data_gen_test:block/acacia_stairs_outer", + "x": 180, + "y": 180, + "uvlock": true + }, + "facing=west,half=top,shape=outer_right": { + "model": "data_gen_test:block/acacia_stairs_outer", + "x": 180, + "y": 270, + "uvlock": true + }, + "facing=east,half=top,shape=outer_right": { + "model": "data_gen_test:block/acacia_stairs_outer", + "x": 180, + "y": 90, + "uvlock": true + }, + "facing=north,half=bottom,shape=outer_right": { + "model": "data_gen_test:block/acacia_stairs_outer", + "y": 270, + "uvlock": true + }, + "facing=south,half=bottom,shape=outer_right": { + "model": "data_gen_test:block/acacia_stairs_outer", + "y": 90, + "uvlock": true + }, + "facing=west,half=bottom,shape=outer_right": { + "model": "data_gen_test:block/acacia_stairs_outer", + "y": 180, + "uvlock": true + }, + "facing=east,half=bottom,shape=outer_right": { + "model": "data_gen_test:block/acacia_stairs_outer" + } + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/minecraft/blockstates/acacia_trapdoor.json b/src/generated_test/resources/assets/minecraft/blockstates/acacia_trapdoor.json new file mode 100644 index 000000000..f3a766046 --- /dev/null +++ b/src/generated_test/resources/assets/minecraft/blockstates/acacia_trapdoor.json @@ -0,0 +1,68 @@ +{ + "variants": { + "facing=north,half=top,open=false": { + "model": "data_gen_test:block/acacia_trapdoor_top" + }, + "facing=south,half=top,open=false": { + "model": "data_gen_test:block/acacia_trapdoor_top", + "y": 180 + }, + "facing=west,half=top,open=false": { + "model": "data_gen_test:block/acacia_trapdoor_top", + "y": 270 + }, + "facing=east,half=top,open=false": { + "model": "data_gen_test:block/acacia_trapdoor_top", + "y": 90 + }, + "facing=north,half=bottom,open=false": { + "model": "data_gen_test:block/acacia_trapdoor_bottom" + }, + "facing=south,half=bottom,open=false": { + "model": "data_gen_test:block/acacia_trapdoor_bottom", + "y": 180 + }, + "facing=west,half=bottom,open=false": { + "model": "data_gen_test:block/acacia_trapdoor_bottom", + "y": 270 + }, + "facing=east,half=bottom,open=false": { + "model": "data_gen_test:block/acacia_trapdoor_bottom", + "y": 90 + }, + "facing=north,half=top,open=true": { + "model": "data_gen_test:block/acacia_trapdoor_open", + "x": 180, + "y": 180 + }, + "facing=south,half=top,open=true": { + "model": "data_gen_test:block/acacia_trapdoor_open", + "x": 180 + }, + "facing=west,half=top,open=true": { + "model": "data_gen_test:block/acacia_trapdoor_open", + "x": 180, + "y": 90 + }, + "facing=east,half=top,open=true": { + "model": "data_gen_test:block/acacia_trapdoor_open", + "x": 180, + "y": 270 + }, + "facing=north,half=bottom,open=true": { + "model": "data_gen_test:block/acacia_trapdoor_open" + }, + "facing=south,half=bottom,open=true": { + "model": "data_gen_test:block/acacia_trapdoor_open", + "y": 180 + }, + "facing=west,half=bottom,open=true": { + "model": "data_gen_test:block/acacia_trapdoor_open", + "y": 270 + }, + "facing=east,half=bottom,open=true": { + "model": "data_gen_test:block/acacia_trapdoor_open", + "y": 90 + } + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/minecraft/blockstates/barrel.json b/src/generated_test/resources/assets/minecraft/blockstates/barrel.json new file mode 100644 index 000000000..a7da394f8 --- /dev/null +++ b/src/generated_test/resources/assets/minecraft/blockstates/barrel.json @@ -0,0 +1,56 @@ +{ + "variants": { + "facing=down,open=false": { + "model": "data_gen_test:block/barrel", + "x": 180 + }, + "facing=up,open=false": { + "model": "data_gen_test:block/barrel" + }, + "facing=north,open=false": { + "model": "data_gen_test:block/barrel", + "x": 90 + }, + "facing=south,open=false": { + "model": "data_gen_test:block/barrel", + "x": 90, + "y": 180 + }, + "facing=west,open=false": { + "model": "data_gen_test:block/barrel", + "x": 90, + "y": 270 + }, + "facing=east,open=false": { + "model": "data_gen_test:block/barrel", + "x": 90, + "y": 90 + }, + "facing=down,open=true": { + "model": "data_gen_test:block/barrel_open", + "x": 180 + }, + "facing=up,open=true": { + "model": "data_gen_test:block/barrel_open" + }, + "facing=north,open=true": { + "model": "data_gen_test:block/barrel_open", + "x": 90 + }, + "facing=south,open=true": { + "model": "data_gen_test:block/barrel_open", + "x": 90, + "y": 180 + }, + "facing=west,open=true": { + "model": "data_gen_test:block/barrel_open", + "x": 90, + "y": 270 + }, + "facing=east,open=true": { + "model": "data_gen_test:block/barrel_open", + "x": 90, + "y": 90 + } + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/minecraft/blockstates/birch_fence_gate.json b/src/generated_test/resources/assets/minecraft/blockstates/birch_fence_gate.json new file mode 100644 index 000000000..db89c4384 --- /dev/null +++ b/src/generated_test/resources/assets/minecraft/blockstates/birch_fence_gate.json @@ -0,0 +1,104 @@ +{ + "variants": { + "facing=north,in_wall=false,open=false": [ + { + "model": "minecraft:builtin/generated" + }, + { + "model": "data_gen_test:block/birch_fence_gate", + "y": 180, + "uvlock": true, + "weight": 100 + } + ], + "facing=south,in_wall=false,open=false": [ + { + "model": "minecraft:builtin/generated" + }, + { + "model": "data_gen_test:block/birch_fence_gate", + "uvlock": true, + "weight": 100 + } + ], + "facing=west,in_wall=false,open=false": [ + { + "model": "minecraft:builtin/generated" + }, + { + "model": "data_gen_test:block/birch_fence_gate", + "y": 90, + "uvlock": true, + "weight": 100 + } + ], + "facing=east,in_wall=false,open=false": [ + { + "model": "minecraft:builtin/generated" + }, + { + "model": "data_gen_test:block/birch_fence_gate", + "y": 270, + "uvlock": true, + "weight": 100 + } + ], + "facing=north,in_wall=true,open=false": { + "model": "data_gen_test:block/birch_fence_gate_wall", + "y": 180, + "uvlock": true + }, + "facing=south,in_wall=true,open=false": { + "model": "data_gen_test:block/birch_fence_gate_wall", + "uvlock": true + }, + "facing=west,in_wall=true,open=false": { + "model": "data_gen_test:block/birch_fence_gate_wall", + "y": 90, + "uvlock": true + }, + "facing=east,in_wall=true,open=false": { + "model": "data_gen_test:block/birch_fence_gate_wall", + "y": 270, + "uvlock": true + }, + "facing=north,in_wall=false,open=true": { + "model": "data_gen_test:block/birch_fence_gate_open", + "y": 180, + "uvlock": true + }, + "facing=south,in_wall=false,open=true": { + "model": "data_gen_test:block/birch_fence_gate_open", + "uvlock": true + }, + "facing=west,in_wall=false,open=true": { + "model": "data_gen_test:block/birch_fence_gate_open", + "y": 90, + "uvlock": true + }, + "facing=east,in_wall=false,open=true": { + "model": "data_gen_test:block/birch_fence_gate_open", + "y": 270, + "uvlock": true + }, + "facing=north,in_wall=true,open=true": { + "model": "data_gen_test:block/birch_fence_gate_wall_open", + "y": 180, + "uvlock": true + }, + "facing=south,in_wall=true,open=true": { + "model": "data_gen_test:block/birch_fence_gate_wall_open", + "uvlock": true + }, + "facing=west,in_wall=true,open=true": { + "model": "data_gen_test:block/birch_fence_gate_wall_open", + "y": 90, + "uvlock": true + }, + "facing=east,in_wall=true,open=true": { + "model": "data_gen_test:block/birch_fence_gate_wall_open", + "y": 270, + "uvlock": true + } + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/minecraft/blockstates/cobblestone_wall.json b/src/generated_test/resources/assets/minecraft/blockstates/cobblestone_wall.json new file mode 100644 index 000000000..335c9f3ed --- /dev/null +++ b/src/generated_test/resources/assets/minecraft/blockstates/cobblestone_wall.json @@ -0,0 +1,51 @@ +{ + "multipart": [ + { + "when": { + "up": "true" + }, + "apply": { + "model": "data_gen_test:block/cobblestone_wall_post" + } + }, + { + "when": { + "north": "true" + }, + "apply": { + "model": "data_gen_test:block/cobblestone_wall_side", + "uvlock": true + } + }, + { + "when": { + "south": "true" + }, + "apply": { + "model": "data_gen_test:block/cobblestone_wall_side", + "y": 180, + "uvlock": true + } + }, + { + "when": { + "west": "true" + }, + "apply": { + "model": "data_gen_test:block/cobblestone_wall_side", + "y": 270, + "uvlock": true + } + }, + { + "when": { + "east": "true" + }, + "apply": { + "model": "data_gen_test:block/cobblestone_wall_side", + "y": 90, + "uvlock": true + } + } + ] +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/minecraft/blockstates/furnace.json b/src/generated_test/resources/assets/minecraft/blockstates/furnace.json new file mode 100644 index 000000000..ea7fe45c3 --- /dev/null +++ b/src/generated_test/resources/assets/minecraft/blockstates/furnace.json @@ -0,0 +1,34 @@ +{ + "variants": { + "facing=north,lit=false": { + "model": "data_gen_test:block/furnace" + }, + "facing=south,lit=false": { + "model": "data_gen_test:block/furnace", + "y": 180 + }, + "facing=west,lit=false": { + "model": "data_gen_test:block/furnace", + "y": 270 + }, + "facing=east,lit=false": { + "model": "data_gen_test:block/furnace", + "y": 90 + }, + "facing=north,lit=true": { + "model": "data_gen_test:block/furnace_on" + }, + "facing=south,lit=true": { + "model": "data_gen_test:block/furnace_on", + "y": 180 + }, + "facing=west,lit=true": { + "model": "data_gen_test:block/furnace_on", + "y": 270 + }, + "facing=east,lit=true": { + "model": "data_gen_test:block/furnace_on", + "y": 90 + } + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/minecraft/blockstates/glass_pane.json b/src/generated_test/resources/assets/minecraft/blockstates/glass_pane.json new file mode 100644 index 000000000..40fcd41e8 --- /dev/null +++ b/src/generated_test/resources/assets/minecraft/blockstates/glass_pane.json @@ -0,0 +1,77 @@ +{ + "multipart": [ + { + "apply": { + "model": "data_gen_test:block/glass_pane_post" + } + }, + { + "when": { + "north": "true" + }, + "apply": { + "model": "data_gen_test:block/glass_pane_side" + } + }, + { + "when": { + "north": "false" + }, + "apply": { + "model": "data_gen_test:block/glass_pane_noside" + } + }, + { + "when": { + "south": "true" + }, + "apply": { + "model": "data_gen_test:block/glass_pane_side_alt" + } + }, + { + "when": { + "south": "false" + }, + "apply": { + "model": "data_gen_test:block/glass_pane_noside_alt", + "y": 90 + } + }, + { + "when": { + "west": "true" + }, + "apply": { + "model": "data_gen_test:block/glass_pane_side_alt", + "y": 90 + } + }, + { + "when": { + "west": "false" + }, + "apply": { + "model": "data_gen_test:block/glass_pane_noside", + "y": 270 + } + }, + { + "when": { + "east": "true" + }, + "apply": { + "model": "data_gen_test:block/glass_pane_side", + "y": 90 + } + }, + { + "when": { + "east": "false" + }, + "apply": { + "model": "data_gen_test:block/glass_pane_noside_alt" + } + } + ] +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/minecraft/blockstates/oak_trapdoor.json b/src/generated_test/resources/assets/minecraft/blockstates/oak_trapdoor.json new file mode 100644 index 000000000..b6f8afcf1 --- /dev/null +++ b/src/generated_test/resources/assets/minecraft/blockstates/oak_trapdoor.json @@ -0,0 +1,58 @@ +{ + "variants": { + "facing=north,half=top,open=false": { + "model": "data_gen_test:block/oak_trapdoor_top" + }, + "facing=south,half=top,open=false": { + "model": "data_gen_test:block/oak_trapdoor_top" + }, + "facing=west,half=top,open=false": { + "model": "data_gen_test:block/oak_trapdoor_top" + }, + "facing=east,half=top,open=false": { + "model": "data_gen_test:block/oak_trapdoor_top" + }, + "facing=north,half=bottom,open=false": { + "model": "data_gen_test:block/oak_trapdoor_bottom" + }, + "facing=south,half=bottom,open=false": { + "model": "data_gen_test:block/oak_trapdoor_bottom" + }, + "facing=west,half=bottom,open=false": { + "model": "data_gen_test:block/oak_trapdoor_bottom" + }, + "facing=east,half=bottom,open=false": { + "model": "data_gen_test:block/oak_trapdoor_bottom" + }, + "facing=north,half=top,open=true": { + "model": "data_gen_test:block/oak_trapdoor_open" + }, + "facing=south,half=top,open=true": { + "model": "data_gen_test:block/oak_trapdoor_open", + "y": 180 + }, + "facing=west,half=top,open=true": { + "model": "data_gen_test:block/oak_trapdoor_open", + "y": 270 + }, + "facing=east,half=top,open=true": { + "model": "data_gen_test:block/oak_trapdoor_open", + "y": 90 + }, + "facing=north,half=bottom,open=true": { + "model": "data_gen_test:block/oak_trapdoor_open" + }, + "facing=south,half=bottom,open=true": { + "model": "data_gen_test:block/oak_trapdoor_open", + "y": 180 + }, + "facing=west,half=bottom,open=true": { + "model": "data_gen_test:block/oak_trapdoor_open", + "y": 270 + }, + "facing=east,half=bottom,open=true": { + "model": "data_gen_test:block/oak_trapdoor_open", + "y": 90 + } + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/minecraft/blockstates/stone.json b/src/generated_test/resources/assets/minecraft/blockstates/stone.json new file mode 100644 index 000000000..0d7bd5d1b --- /dev/null +++ b/src/generated_test/resources/assets/minecraft/blockstates/stone.json @@ -0,0 +1,40 @@ +{ + "variants": { + "": [ + { + "model": "data_gen_test:block/stone" + }, + { + "model": "data_gen_test:block/stone", + "y": 90 + }, + { + "model": "data_gen_test:block/stone", + "y": 180 + }, + { + "model": "data_gen_test:block/stone", + "y": 270 + }, + { + "model": "data_gen_test:block/stone", + "x": 180 + }, + { + "model": "data_gen_test:block/stone", + "x": 180, + "y": 90 + }, + { + "model": "data_gen_test:block/stone", + "x": 180, + "y": 180 + }, + { + "model": "data_gen_test:block/stone", + "x": 180, + "y": 270 + } + ] + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/minecraft/blockstates/torch.json b/src/generated_test/resources/assets/minecraft/blockstates/torch.json new file mode 100644 index 000000000..5352f6963 --- /dev/null +++ b/src/generated_test/resources/assets/minecraft/blockstates/torch.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "data_gen_test:block/torch" + } + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/minecraft/blockstates/wall_torch.json b/src/generated_test/resources/assets/minecraft/blockstates/wall_torch.json new file mode 100644 index 000000000..895edc102 --- /dev/null +++ b/src/generated_test/resources/assets/minecraft/blockstates/wall_torch.json @@ -0,0 +1,20 @@ +{ + "variants": { + "facing=north": { + "model": "data_gen_test:block/wall_torch", + "y": 270 + }, + "facing=south": { + "model": "data_gen_test:block/wall_torch", + "y": 90 + }, + "facing=west": { + "model": "data_gen_test:block/wall_torch", + "y": 180 + }, + "facing=east": { + "model": "data_gen_test:block/wall_torch", + "y": 360 + } + } +} \ No newline at end of file diff --git a/src/main/java/net/minecraftforge/client/model/generators/BlockModelBuilder.java b/src/main/java/net/minecraftforge/client/model/generators/BlockModelBuilder.java new file mode 100644 index 000000000..fe1b8df39 --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/generators/BlockModelBuilder.java @@ -0,0 +1,37 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2019. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.client.model.generators; + +import net.minecraft.util.ResourceLocation; + +/** + * Builder for block models, does not currently provide any additional + * functionality over {@link ModelBuilder}, purely a stub class with a concrete + * generic. + * + * @see ModelProvider + * @see ModelBuilder + */ +public class BlockModelBuilder extends ModelBuilder { + + public BlockModelBuilder(ResourceLocation outputLocation, ExistingFileHelper existingFileHelper) { + super(outputLocation, existingFileHelper); + } +} diff --git a/src/main/java/net/minecraftforge/client/model/generators/BlockModelProvider.java b/src/main/java/net/minecraftforge/client/model/generators/BlockModelProvider.java new file mode 100644 index 000000000..bbb964eaa --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/generators/BlockModelProvider.java @@ -0,0 +1,14 @@ +package net.minecraftforge.client.model.generators; + +import net.minecraft.data.DataGenerator; + +/** + * Stub class to extend for block model data providers, eliminates some + * boilerplate constructor parameters. + */ +public abstract class BlockModelProvider extends ModelProvider { + + public BlockModelProvider(DataGenerator generator, String modid, ExistingFileHelper existingFileHelper) { + super(generator, modid, BLOCK_FOLDER, BlockModelBuilder::new, existingFileHelper); + } +} diff --git a/src/main/java/net/minecraftforge/client/model/generators/BlockStateProvider.java b/src/main/java/net/minecraftforge/client/model/generators/BlockStateProvider.java new file mode 100644 index 000000000..e1d28e9f6 --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/generators/BlockStateProvider.java @@ -0,0 +1,546 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2019. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.client.model.generators; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import javax.annotation.Nonnull; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.DoorBlock; +import net.minecraft.block.FenceBlock; +import net.minecraft.block.FenceGateBlock; +import net.minecraft.block.FourWayBlock; +import net.minecraft.block.LogBlock; +import net.minecraft.block.PaneBlock; +import net.minecraft.block.RotatedPillarBlock; +import net.minecraft.block.SixWayBlock; +import net.minecraft.block.SlabBlock; +import net.minecraft.block.StairsBlock; +import net.minecraft.block.TrapDoorBlock; +import net.minecraft.block.WallBlock; +import net.minecraft.data.DataGenerator; +import net.minecraft.data.IDataProvider; +import net.minecraft.state.properties.AttachFace; +import net.minecraft.state.properties.BlockStateProperties; +import net.minecraft.state.properties.DoorHingeSide; +import net.minecraft.state.properties.DoubleBlockHalf; +import net.minecraft.state.properties.Half; +import net.minecraft.state.properties.SlabType; +import net.minecraft.state.properties.StairsShape; +import net.minecraft.util.Direction; +import net.minecraft.util.Direction.Axis; +import net.minecraft.util.ResourceLocation; + +/** + * Data provider for blockstate files. Extends {@link BlockModelProvider} so that + * blockstates and their referenced models can be provided in tandem. + */ +public abstract class BlockStateProvider extends BlockModelProvider { + + private static final Logger LOGGER = LogManager.getLogger(); + private static final Gson GSON = (new GsonBuilder()).setPrettyPrinting().disableHtmlEscaping().create(); + + @VisibleForTesting + protected final Map registeredBlocks = new LinkedHashMap<>(); + + public BlockStateProvider(DataGenerator gen, String modid, ExistingFileHelper exFileHelper) { + super(gen, modid, exFileHelper); + } + + @Override + protected final void registerModels() { + registeredBlocks.clear(); + registerStatesAndModels(); + for (Map.Entry entry : registeredBlocks.entrySet()) { + saveBlockState(entry.getValue().toJson(), entry.getKey()); + } + } + + protected abstract void registerStatesAndModels(); + + protected VariantBlockStateBuilder getVariantBuilder(Block b) { + if (registeredBlocks.containsKey(b)) { + IGeneratedBlockstate old = registeredBlocks.get(b); + Preconditions.checkState(old instanceof VariantBlockStateBuilder); + return (VariantBlockStateBuilder) old; + } else { + VariantBlockStateBuilder ret = new VariantBlockStateBuilder(b); + registeredBlocks.put(b, ret); + return ret; + } + } + + protected MultiPartBlockStateBuilder getMultipartBuilder(Block b) { + if (registeredBlocks.containsKey(b)) { + IGeneratedBlockstate old = registeredBlocks.get(b); + Preconditions.checkState(old instanceof MultiPartBlockStateBuilder); + return (MultiPartBlockStateBuilder) old; + } else { + MultiPartBlockStateBuilder ret = new MultiPartBlockStateBuilder(b); + registeredBlocks.put(b, ret); + return ret; + } + } + + private String name(Block block) { + return block.getRegistryName().getPath(); + } + + protected ResourceLocation blockTexture(Block block) { + ResourceLocation name = block.getRegistryName(); + return new ResourceLocation(name.getNamespace(), BLOCK_FOLDER + "/" + name.getPath()); + } + + private ResourceLocation extend(ResourceLocation rl, String suffix) { + return new ResourceLocation(rl.getNamespace(), rl.getPath() + suffix); + } + + protected ModelFile cubeAll(Block block) { + return cubeAll(name(block), blockTexture(block)); + } + + protected void simpleBlock(Block block) { + simpleBlock(block, cubeAll(block)); + } + + protected void simpleBlock(Block block, Function expander) { + simpleBlock(block, expander.apply(cubeAll(block))); + } + + protected void simpleBlock(Block block, ModelFile model) { + simpleBlock(block, new ConfiguredModel(model)); + } + + protected void simpleBlock(Block block, ConfiguredModel... models) { + getVariantBuilder(block) + .partialState().setModels(models); + } + + protected void axisBlock(RotatedPillarBlock block) { + axisBlock(block, blockTexture(block)); + } + + protected void logBlock(LogBlock block) { + axisBlock(block, blockTexture(block), extend(blockTexture(block), "_top")); + } + + protected void axisBlock(RotatedPillarBlock block, ResourceLocation baseName) { + axisBlock(block, extend(baseName, "_side"), extend(baseName, "_end")); + } + + protected void axisBlock(RotatedPillarBlock block, ResourceLocation side, ResourceLocation end) { + axisBlock(block, cubeColumn(name(block), side, end)); + } + + protected void axisBlock(RotatedPillarBlock block, ModelFile model) { + getVariantBuilder(block) + .partialState().with(RotatedPillarBlock.AXIS, Axis.Y) + .modelForState().modelFile(model).addModel() + .partialState().with(RotatedPillarBlock.AXIS, Axis.Z) + .modelForState().modelFile(model).rotationX(90).addModel() + .partialState().with(RotatedPillarBlock.AXIS, Axis.X) + .modelForState().modelFile(model).rotationX(90).rotationY(90).addModel(); + } + + private static final int DEFAULT_ANGLE_OFFSET = 180; + + protected void horizontalBlock(Block block, ResourceLocation side, ResourceLocation front, ResourceLocation top) { + horizontalBlock(block, orientable(name(block), side, front, top)); + } + + protected void horizontalBlock(Block block, ModelFile model) { + horizontalBlock(block, model, DEFAULT_ANGLE_OFFSET); + } + + protected void horizontalBlock(Block block, ModelFile model, int angleOffset) { + horizontalBlock(block, $ -> model, angleOffset); + } + + protected void horizontalBlock(Block block, Function modelFunc) { + horizontalBlock(block, modelFunc, DEFAULT_ANGLE_OFFSET); + } + + protected void horizontalBlock(Block block, Function modelFunc, int angleOffset) { + getVariantBuilder(block) + .forAllStates(state -> ConfiguredModel.builder() + .modelFile(modelFunc.apply(state)) + .rotationY((int) state.get(BlockStateProperties.HORIZONTAL_FACING).getHorizontalAngle() + angleOffset) + .build() + ); + } + + protected void horizontalFaceBlock(Block block, ModelFile model) { + horizontalFaceBlock(block, model, DEFAULT_ANGLE_OFFSET); + } + + protected void horizontalFaceBlock(Block block, ModelFile model, int angleOffset) { + horizontalFaceBlock(block, $ -> model, angleOffset); + } + + protected void horizontalFaceBlock(Block block, Function modelFunc) { + horizontalBlock(block, modelFunc, DEFAULT_ANGLE_OFFSET); + } + + protected void horizontalFaceBlock(Block block, Function modelFunc, int angleOffset) { + getVariantBuilder(block) + .forAllStates(state -> ConfiguredModel.builder() + .modelFile(modelFunc.apply(state)) + .rotationX(state.get(BlockStateProperties.FACE).ordinal() * 90) + .rotationY((((int) state.get(BlockStateProperties.HORIZONTAL_FACING).getHorizontalAngle() + angleOffset) + (state.get(BlockStateProperties.FACE) == AttachFace.CEILING ? 180 : 0)) % 360) + .build() + ); + } + + protected void directionalBlock(Block block, ModelFile model) { + directionalBlock(block, model, DEFAULT_ANGLE_OFFSET); + } + + protected void directionalBlock(Block block, ModelFile model, int angleOffset) { + directionalBlock(block, $ -> model, angleOffset); + } + + protected void directionalBlock(Block block, Function modelFunc) { + directionalBlock(block, modelFunc, DEFAULT_ANGLE_OFFSET); + } + + protected void directionalBlock(Block block, Function modelFunc, int angleOffset) { + getVariantBuilder(block) + .forAllStates(state -> { + Direction dir = state.get(BlockStateProperties.FACING); + return ConfiguredModel.builder() + .modelFile(modelFunc.apply(state)) + .rotationX(dir == Direction.DOWN ? 180 : dir.getAxis().isHorizontal() ? 90 : 0) + .rotationY(dir.getAxis().isVertical() ? 0 : (((int) dir.getHorizontalAngle()) + angleOffset) % 360) + .build(); + }); + } + + protected void stairsBlock(StairsBlock block, ResourceLocation texture) { + stairsBlock(block, texture, texture, texture); + } + + protected void stairsBlock(StairsBlock block, String name, ResourceLocation texture) { + stairsBlock(block, name, texture, texture, texture); + } + + protected void stairsBlock(StairsBlock block, ResourceLocation side, ResourceLocation bottom, ResourceLocation top) { + stairsBlockInternal(block, block.getRegistryName().toString(), side, bottom, top); + } + + protected void stairsBlock(StairsBlock block, String name, ResourceLocation side, ResourceLocation bottom, ResourceLocation top) { + stairsBlockInternal(block, name + "_stairs", side, bottom, top); + } + + private void stairsBlockInternal(StairsBlock block, String baseName, ResourceLocation side, ResourceLocation bottom, ResourceLocation top) { + ModelFile stairs = stairs(baseName, side, bottom, top); + ModelFile stairsInner = stairsInner(baseName + "_inner", side, bottom, top); + ModelFile stairsOuter = stairsOuter(baseName + "_outer", side, bottom, top); + stairsBlock(block, stairs, stairsInner, stairsOuter); + } + + protected void stairsBlock(StairsBlock block, ModelFile stairs, ModelFile stairsInner, ModelFile stairsOuter) { + getVariantBuilder(block) + .forAllStatesExcept(state -> { + Direction facing = state.get(StairsBlock.FACING); + Half half = state.get(StairsBlock.HALF); + StairsShape shape = state.get(StairsBlock.SHAPE); + int yRot = (int) facing.rotateY().getHorizontalAngle(); // Stairs model is rotated 90 degrees clockwise for some reason + if (shape == StairsShape.INNER_LEFT || shape == StairsShape.OUTER_LEFT) { + yRot += 270; // Left facing stairs are rotated 90 degrees clockwise + } + if (shape != StairsShape.STRAIGHT && half == Half.TOP) { + yRot += 90; // Top stairs are rotated 90 degrees clockwise + } + yRot %= 360; + boolean uvlock = yRot != 0 || half == Half.TOP; // Don't set uvlock for states that have no rotation + return ConfiguredModel.builder() + .modelFile(shape == StairsShape.STRAIGHT ? stairs : shape == StairsShape.INNER_LEFT || shape == StairsShape.INNER_RIGHT ? stairsInner : stairsOuter) + .rotationX(half == Half.BOTTOM ? 0 : 180) + .rotationY(yRot) + .uvLock(uvlock) + .build(); + }, StairsBlock.WATERLOGGED); + } + + protected void slabBlock(SlabBlock block, ResourceLocation doubleslab, ResourceLocation texture) { + slabBlock(block, doubleslab, texture, texture, texture); + } + + protected void slabBlock(SlabBlock block, ResourceLocation doubleslab, ResourceLocation side, ResourceLocation bottom, ResourceLocation top) { + slabBlock(block, slab(name(block), side, bottom, top), slabTop(name(block) + "_top", side, bottom, top), getExistingFile(doubleslab)); + } + + protected void slabBlock(SlabBlock block, ModelFile bottom, ModelFile top, ModelFile doubleslab) { + getVariantBuilder(block) + .partialState().with(SlabBlock.TYPE, SlabType.BOTTOM).addModels(new ConfiguredModel(bottom)) + .partialState().with(SlabBlock.TYPE, SlabType.TOP).addModels(new ConfiguredModel(top)) + .partialState().with(SlabBlock.TYPE, SlabType.DOUBLE).addModels(new ConfiguredModel(doubleslab)); + } + + protected void fourWayBlock(FourWayBlock block, ModelFile post, ModelFile side) { + MultiPartBlockStateBuilder builder = getMultipartBuilder(block) + .part().modelFile(post).addModel().end(); + fourWayMultipart(builder, side); + } + + protected void fourWayMultipart(MultiPartBlockStateBuilder builder, ModelFile side) { + SixWayBlock.FACING_TO_PROPERTY_MAP.entrySet().forEach(e -> { + Direction dir = e.getKey(); + if (dir.getAxis().isHorizontal()) { + builder.part().modelFile(side).rotationY((((int) dir.getHorizontalAngle()) + 180) % 360).uvLock(true).addModel() + .condition(e.getValue(), true); + } + }); + } + + protected void fenceBlock(FenceBlock block, ResourceLocation texture) { + String baseName = block.getRegistryName().toString(); + fourWayBlock(block, fencePost(baseName + "_post", texture), fenceSide(baseName + "_side", texture)); + } + + protected void fenceBlock(FenceBlock block, String name, ResourceLocation texture) { + fourWayBlock(block, fencePost(name + "_fence_post", texture), fenceSide(name + "_fence_side", texture)); + } + + protected void fenceGateBlock(FenceGateBlock block, ResourceLocation texture) { + fenceGateBlockInternal(block, block.getRegistryName().toString(), texture); + } + + protected void fenceGateBlock(FenceGateBlock block, String name, ResourceLocation texture) { + fenceGateBlockInternal(block, name + "_fence_gate", texture); + } + + private void fenceGateBlockInternal(FenceGateBlock block, String baseName, ResourceLocation texture) { + ModelFile gate = fenceGate(baseName, texture); + ModelFile gateOpen = fenceGateOpen(baseName + "_open", texture); + ModelFile gateWall = fenceGateWall(baseName + "_wall", texture); + ModelFile gateWallOpen = fenceGateWallOpen(baseName + "_wall_open", texture); + fenceGateBlock(block, gate, gateOpen, gateWall, gateWallOpen); + } + + protected void fenceGateBlock(FenceGateBlock block, ModelFile gate, ModelFile gateOpen, ModelFile gateWall, ModelFile gateWallOpen) { + getVariantBuilder(block).forAllStatesExcept(state -> { + ModelFile model = gate; + if (state.get(FenceGateBlock.IN_WALL)) { + model = gateWall; + } + if (state.get(FenceGateBlock.OPEN)) { + model = model == gateWall ? gateWallOpen : gateOpen; + } + return ConfiguredModel.builder() + .modelFile(model) + .rotationY((int) state.get(FenceGateBlock.HORIZONTAL_FACING).getHorizontalAngle()) + .uvLock(true) + .build(); + }, FenceGateBlock.POWERED); + } + + protected void wallBlock(WallBlock block, ResourceLocation texture) { + wallBlockInternal(block, block.getRegistryName().toString(), texture); + } + + protected void wallBlock(WallBlock block, String name, ResourceLocation texture) { + wallBlockInternal(block, name + "_wall", texture); + } + + private void wallBlockInternal(WallBlock block, String baseName, ResourceLocation texture) { + wallBlock(block, wallPost(baseName + "_post", texture), wallSide(baseName + "_side", texture)); + } + + protected void wallBlock(WallBlock block, ModelFile post, ModelFile side) { + MultiPartBlockStateBuilder builder = getMultipartBuilder(block) + .part().modelFile(post).addModel() + .condition(WallBlock.UP, true).end(); + fourWayMultipart(builder, side); + } + + protected void paneBlock(PaneBlock block, ResourceLocation pane, ResourceLocation edge) { + paneBlockInternal(block, block.getRegistryName().toString(), pane, edge); + } + + protected void paneBlock(PaneBlock block, String name, ResourceLocation pane, ResourceLocation edge) { + paneBlockInternal(block, name + "_pane", pane, edge); + } + + private void paneBlockInternal(PaneBlock block, String baseName, ResourceLocation pane, ResourceLocation edge) { + ModelFile post = panePost(baseName + "_post", pane, edge); + ModelFile side = paneSide(baseName + "_side", pane, edge); + ModelFile sideAlt = paneSideAlt(baseName + "_side_alt", pane, edge); + ModelFile noSide = paneNoSide(baseName + "_noside", pane); + ModelFile noSideAlt = paneNoSideAlt(baseName + "_noside_alt", pane); + paneBlock(block, post, side, sideAlt, noSide, noSideAlt); + } + + protected void paneBlock(PaneBlock block, ModelFile post, ModelFile side, ModelFile sideAlt, ModelFile noSide, ModelFile noSideAlt) { + MultiPartBlockStateBuilder builder = getMultipartBuilder(block) + .part().modelFile(post).addModel().end(); + SixWayBlock.FACING_TO_PROPERTY_MAP.entrySet().forEach(e -> { + Direction dir = e.getKey(); + if (dir.getAxis().isHorizontal()) { + boolean alt = dir == Direction.SOUTH; + builder.part().modelFile(alt || dir == Direction.WEST ? sideAlt : side).rotationY(dir.getAxis() == Axis.X ? 90 : 0).addModel() + .condition(e.getValue(), true).end() + .part().modelFile(alt || dir == Direction.EAST ? noSideAlt : noSide).rotationY(dir == Direction.WEST ? 270 : dir == Direction.SOUTH ? 90 : 0).addModel() + .condition(e.getValue(), false); + } + }); + } + + protected void doorBlock(DoorBlock block, ResourceLocation bottom, ResourceLocation top) { + doorBlockInternal(block, block.getRegistryName().toString(), bottom, top); + } + + protected void doorBlock(DoorBlock block, String name, ResourceLocation bottom, ResourceLocation top) { + doorBlockInternal(block, name + "_door", bottom, top); + } + + private void doorBlockInternal(DoorBlock block, String baseName, ResourceLocation bottom, ResourceLocation top) { + ModelFile bottomLeft = doorBottomLeft(baseName + "_bottom", bottom, top); + ModelFile bottomRight = doorBottomRight(baseName + "_bottom_hinge", bottom, top); + ModelFile topLeft = doorTopLeft(baseName + "_top", bottom, top); + ModelFile topRight = doorTopRight(baseName + "_top_hinge", bottom, top); + doorBlock(block, bottomLeft, bottomRight, topLeft, topRight); + } + + protected void doorBlock(DoorBlock block, ModelFile bottomLeft, ModelFile bottomRight, ModelFile topLeft, ModelFile topRight) { + getVariantBuilder(block).forAllStatesExcept(state -> { + int yRot = ((int) state.get(DoorBlock.FACING).getHorizontalAngle()) + 90; + boolean rh = state.get(DoorBlock.HINGE) == DoorHingeSide.RIGHT; + boolean open = state.get(DoorBlock.OPEN); + boolean right = rh ^ open; + if (open) { + yRot += 90; + } + if (rh && open) { + yRot += 180; + } + yRot %= 360; + return ConfiguredModel.builder().modelFile(state.get(DoorBlock.HALF) == DoubleBlockHalf.LOWER ? (right ? bottomRight : bottomLeft) : (right ? topRight : topLeft)) + .rotationY(yRot) + .build(); + }, DoorBlock.POWERED); + } + + protected void trapdoorBlock(TrapDoorBlock block, ResourceLocation texture, boolean orientable) { + trapdoorBlockInternal(block, block.getRegistryName().toString(), texture, orientable); + } + + protected void trapdoorBlock(TrapDoorBlock block, String name, ResourceLocation texture, boolean orientable) { + trapdoorBlockInternal(block, name + "_trapdoor", texture, orientable); + } + + private void trapdoorBlockInternal(TrapDoorBlock block, String baseName, ResourceLocation texture, boolean orientable) { + ModelFile bottom = orientable ? trapdoorOrientableBottom(baseName + "_bottom", texture) : trapdoorBottom(baseName + "_bottom", texture); + ModelFile top = orientable ? trapdoorOrientableTop(baseName + "_top", texture) : trapdoorTop(baseName + "_top", texture); + ModelFile open = orientable ? trapdoorOrientableOpen(baseName + "_open", texture) : trapdoorOpen(baseName + "_open", texture); + trapdoorBlock(block, bottom, top, open, orientable); + } + + protected void trapdoorBlock(TrapDoorBlock block, ModelFile bottom, ModelFile top, ModelFile open, boolean orientable) { + getVariantBuilder(block).forAllStatesExcept(state -> { + int xRot = 0; + int yRot = ((int) state.get(TrapDoorBlock.HORIZONTAL_FACING).getHorizontalAngle()) + 180; + boolean isOpen = state.get(TrapDoorBlock.OPEN); + if (orientable && isOpen && state.get(TrapDoorBlock.HALF) == Half.TOP) { + xRot += 180; + yRot += 180; + } + if (!orientable && !isOpen) { + yRot = 0; + } + yRot %= 360; + return ConfiguredModel.builder().modelFile(isOpen ? open : state.get(TrapDoorBlock.HALF) == Half.TOP ? top : bottom) + .rotationX(xRot) + .rotationY(yRot) + .build(); + }, TrapDoorBlock.POWERED, TrapDoorBlock.WATERLOGGED); + } + + private void saveBlockState(JsonObject stateJson, Block owner) { + ResourceLocation blockName = Preconditions.checkNotNull(owner.getRegistryName()); + Path mainOutput = generator.getOutputFolder(); + String pathSuffix = "assets/" + blockName.getNamespace() + "/blockstates/" + blockName.getPath() + ".json"; + Path outputPath = mainOutput.resolve(pathSuffix); + try { + IDataProvider.save(GSON, cache, stateJson, outputPath); + } catch (IOException e) { + LOGGER.error("Couldn't save blockstate to {}", outputPath, e); + } + } + + @Nonnull + @Override + public String getName() { + return "Block States"; + } + + public static class ConfiguredModelList { + private final List models; + + private ConfiguredModelList(List models) { + Preconditions.checkArgument(!models.isEmpty()); + this.models = models; + } + + public ConfiguredModelList(ConfiguredModel model) { + this(ImmutableList.of(model)); + } + + public ConfiguredModelList(ConfiguredModel... models) { + this(Arrays.asList(models)); + } + + public JsonElement toJSON() { + if (models.size()==1) { + return models.get(0).toJSON(false); + } else { + JsonArray ret = new JsonArray(); + for (ConfiguredModel m:models) { + ret.add(m.toJSON(true)); + } + return ret; + } + } + + public ConfiguredModelList append(ConfiguredModel... models) { + return new ConfiguredModelList(ImmutableList.builder().addAll(this.models).add(models).build()); + } + } +} diff --git a/src/main/java/net/minecraftforge/client/model/generators/ConfiguredModel.java b/src/main/java/net/minecraftforge/client/model/generators/ConfiguredModel.java new file mode 100644 index 000000000..a90ea96ee --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/generators/ConfiguredModel.java @@ -0,0 +1,301 @@ +package net.minecraftforge.client.model.generators; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Function; +import java.util.stream.IntStream; + +import javax.annotation.Nullable; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ObjectArrays; +import com.google.gson.JsonObject; + +import net.minecraft.client.renderer.model.ModelRotation; +import net.minecraftforge.client.model.generators.MultiPartBlockStateBuilder.PartBuilder; +import net.minecraftforge.client.model.generators.VariantBlockStateBuilder.PartialBlockstate; + +/** + * Represents a model with blockstate configurations, e.g. rotation, uvlock, and + * random weight. + *

+ * Can be manually constructed, created by static factory such as + * {@link #allYRotations(ModelFile, int, boolean)}, or created by builder via + * {@link #builder()}. + */ +public final class ConfiguredModel { + + /** + * The default random weight of configured models, used by convenience + * overloads. + */ + public static final int DEFAULT_WEIGHT = 1; + + public final ModelFile model; + public final int rotationX; + public final int rotationY; + public final boolean uvLock; + public final int weight; + + private static IntStream validRotations() { + return IntStream.range(0, 4).map(i -> i * 90); + } + + public static ConfiguredModel[] allYRotations(ModelFile model, int x, boolean uvlock) { + return allYRotations(model, x, uvlock, DEFAULT_WEIGHT); + } + + public static ConfiguredModel[] allYRotations(ModelFile model, int x, boolean uvlock, int weight) { + return validRotations() + .mapToObj(y -> new ConfiguredModel(model, x, y, uvlock, weight)) + .toArray(ConfiguredModel[]::new); + } + + public static ConfiguredModel[] allRotations(ModelFile model, boolean uvlock) { + return allRotations(model, uvlock, DEFAULT_WEIGHT); + } + + public static ConfiguredModel[] allRotations(ModelFile model, boolean uvlock, int weight) { + return validRotations() + .mapToObj(x -> allYRotations(model, x, uvlock, weight)) + .flatMap(Arrays::stream) + .toArray(ConfiguredModel[]::new); + } + + /** + * Construct a new {@link ConfiguredModel}. + * + * @param model the underlying model + * @param rotationX x-rotation to apply to the model + * @param rotationY y-rotation to apply to the model + * @param uvLock if uvlock should be enabled + * @param weight the random weight of the model + * + * @throws NullPointerException if {@code model} is {@code null} + * @throws IllegalArgumentException if x and/or y rotation are not valid (see + * {@link ModelRotation}) + * @throws IllegalArgumentException if weight is less than or equal to zero + */ + public ConfiguredModel(ModelFile model, int rotationX, int rotationY, boolean uvLock, int weight) { + Preconditions.checkNotNull(model); + this.model = model; + checkRotation(rotationX, rotationY); + this.rotationX = rotationX; + this.rotationY = rotationY; + this.uvLock = uvLock; + checkWeight(weight); + this.weight = weight; + } + + /** + * Construct a new {@link ConfiguredModel} with the {@link #DEFAULT_WEIGHT + * default random weight}. + * + * @param model the underlying model + * @param rotationX x-rotation to apply to the model + * @param rotationY y-rotation to apply to the model + * @param uvLock if uvlock should be enabled + * + * @throws NullPointerException if {@code model} is {@code null} + * @throws IllegalArgumentException if x and/or y rotation are not valid (see + * {@link ModelRotation}) + */ + public ConfiguredModel(ModelFile model, int rotationX, int rotationY, boolean uvLock) { + this(model, rotationX, rotationY, uvLock, DEFAULT_WEIGHT); + } + + /** + * Construct a new {@link ConfiguredModel} with the default rotation (0, 0), + * uvlock (false), and {@link #DEFAULT_WEIGHT default random weight}. + * + * @throws NullPointerException if {@code model} is {@code null} + */ + public ConfiguredModel(ModelFile model) { + this(model, 0, 0, false); + } + + static void checkRotation(int rotationX, int rotationY) { + Preconditions.checkArgument(ModelRotation.getModelRotation(rotationX, rotationY) != null, "Invalid model rotation x=%d, y=%d", rotationX, rotationY); + } + + static void checkWeight(int weight) { + Preconditions.checkArgument(weight >= 1, "Model weight must be greater than or equal to 1. Found: %d", weight); + } + + JsonObject toJSON(boolean includeWeight) { + JsonObject modelJson = new JsonObject(); + modelJson.addProperty("model", model.getLocation().toString()); + if (rotationX != 0) + modelJson.addProperty("x", rotationX); + if (rotationY != 0) + modelJson.addProperty("y", rotationY); + if (uvLock) + modelJson.addProperty("uvlock", uvLock); + if (includeWeight && weight != DEFAULT_WEIGHT) + modelJson.addProperty("weight", weight); + return modelJson; + } + + /** + * Create a new unowned {@link Builder}. + * + * @return the builder + * @see Builder + */ + public static Builder builder() { + return new Builder<>(); + } + + static Builder builder(VariantBlockStateBuilder outer, VariantBlockStateBuilder.PartialBlockstate state) { + return new Builder<>(models -> outer.setModels(state, models), ImmutableList.of()); + } + + static Builder builder(MultiPartBlockStateBuilder outer) { + return new Builder(models -> { + PartBuilder ret = outer.new PartBuilder(new BlockStateProvider.ConfiguredModelList(models)); + outer.addPart(ret); + return ret; + }, ImmutableList.of()); + } + + /** + * A builder for {@link ConfiguredModel}s, which can contain a callback for + * processing the finished result. If no callback is available (e.g. in the case + * of {@link ConfiguredModel#builder()}), some methods will not be available. + *

+ * Multiple models can be configured at once through the use of + * {@link #nextModel()}. + * + * @param the type of the owning builder, which supplied the callback, and + * will be returned upon completion. + */ + public static class Builder { + + private ModelFile model; + @Nullable + private final Function callback; + private final List otherModels; + private int rotationX; + private int rotationY; + private boolean uvLock; + private int weight = DEFAULT_WEIGHT; + + Builder() { + this(null, ImmutableList.of()); + } + + Builder(@Nullable Function callback, List otherModels) { + this.callback = callback; + this.otherModels = otherModels; + } + + /** + * Set the underlying model object for this configured model. + * + * @param model the model + * @return this builder + * @throws NullPointerException if {@code model} is {@code null} + */ + public Builder modelFile(ModelFile model) { + Preconditions.checkNotNull(model, "Model must not be null"); + this.model = model; + return this; + } + + /** + * Set the x-rotation for this model. + * + * @param value the x-rotation value + * @return this builder + * @throws IllegalArgumentException if {@code value} is not a valid x-rotation + * (see {@link ModelRotation}) + */ + public Builder rotationX(int value) { + checkRotation(value, rotationY); + rotationX = value; + return this; + } + + /** + * Set the y-rotation for this model. + * + * @param value the y-rotation value + * @return this builder + * @throws IllegalArgumentException if {@code value} is not a valid y-rotation + * (see {@link ModelRotation}) + */ + public Builder rotationY(int value) { + checkRotation(rotationX, value); + rotationY = value; + return this; + } + + public Builder uvLock(boolean value) { + uvLock = value; + return this; + } + + /** + * Set the random weight for this model. + * + * @param value the weight value + * @return this builder + * @throws IllegalArgumentException if {@code value} is less than or equal to + * zero + */ + public Builder weight(int value) { + checkWeight(value); + weight = value; + return this; + } + + /** + * Build the most recent model, as if {@link #nextModel()} was never called. + * Useful for single-model builders. + * + * @return the most recently configured model + */ + public ConfiguredModel buildLast() { + return new ConfiguredModel(model, rotationX, rotationY, uvLock, weight); + } + + /** + * Build all configured models and return them as an array. + * + * @return the array of built models. + */ + public ConfiguredModel[] build() { + return ObjectArrays.concat(otherModels.toArray(new ConfiguredModel[0]), buildLast()); + } + + /** + * Apply the contained callback and return the owning builder object. What the + * callback does is not defined by this class, but most likely it adds the built + * models to the current variant being configured. + *

+ * Known callbacks include: + *

    + *
  • {@link PartialBlockstate#modelForState()}
  • + *
  • {@link MultiPartBlockStateBuilder#part()}
  • + *
+ * + * @return the owning builder object + * @throws NullPointerException if there is no owning builder (and thus no callback) + */ + public T addModel() { + Preconditions.checkNotNull(callback, "Cannot use addModel() without an owning builder present"); + return callback.apply(build()); + } + + /** + * Complete the current model and return a new builder instance with the same + * callback, and storing all previously built models. + * + * @return a new builder for configuring the next model + */ + public Builder nextModel() { + return new Builder<>(callback, Arrays.asList(build())); + } + } +} diff --git a/src/main/java/net/minecraftforge/client/model/generators/ExistingFileHelper.java b/src/main/java/net/minecraftforge/client/model/generators/ExistingFileHelper.java new file mode 100644 index 000000000..13f152613 --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/generators/ExistingFileHelper.java @@ -0,0 +1,84 @@ +package net.minecraftforge.client.model.generators; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Collection; + +import com.google.common.annotations.VisibleForTesting; + +import net.minecraft.resources.FilePack; +import net.minecraft.resources.FolderPack; +import net.minecraft.resources.IResource; +import net.minecraft.resources.IResourceManager; +import net.minecraft.resources.IResourcePack; +import net.minecraft.resources.ResourcePackType; +import net.minecraft.resources.SimpleReloadableResourceManager; +import net.minecraft.resources.VanillaPack; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.fml.event.lifecycle.GatherDataEvent; + +/** + * Enables data providers to check if other data files currently exist. The + * instance provided in the {@link GatherDataEvent} utilizes the standard + * resources (via {@link VanillaPack}), as well as any extra resource packs + * passed in via the {@code --existing} argument. + */ +public class ExistingFileHelper { + + private final IResourceManager clientResources, serverData; + private final boolean enable; + + public ExistingFileHelper(Collection existingPacks, boolean enable) { + this.clientResources = new SimpleReloadableResourceManager(ResourcePackType.CLIENT_RESOURCES, Thread.currentThread()); + this.serverData = new SimpleReloadableResourceManager(ResourcePackType.SERVER_DATA, Thread.currentThread()); + this.clientResources.addResourcePack(new VanillaPack("minecraft", "realms")); + this.serverData.addResourcePack(new VanillaPack("minecraft")); + for (Path existing : existingPacks) { + File file = existing.toFile(); + IResourcePack pack = file.isDirectory() ? new FolderPack(file) : new FilePack(file); + this.clientResources.addResourcePack(pack); + this.serverData.addResourcePack(pack); + }; + this.enable = enable; + } + + private IResourceManager getManager(ResourcePackType type) { + return type == ResourcePackType.CLIENT_RESOURCES ? clientResources : serverData; + } + + private ResourceLocation getLocation(ResourceLocation base, String suffix, String prefix) { + return new ResourceLocation(base.getNamespace(), prefix + "/" + base.getPath() + suffix); + } + + /** + * Check if a given resource exists in the known resource packs. + * + * @param loc the base location of the resource, e.g. + * {@code "minecraft:block/stone"} + * @param type the type of resources to check + * @param pathSuffix a string to append after the path, e.g. {@code ".json"} + * @param pathPrefix a string to append before the path, before a slash, e.g. + * {@code "models"} + * @return {@code true} if the resource exists in any pack, {@code false} + * otherwise + */ + public boolean exists(ResourceLocation loc, ResourcePackType type, String pathSuffix, String pathPrefix) { + if (!enable) { + return true; + } + return getManager(type).hasResource(getLocation(loc, pathSuffix, pathPrefix)); + } + + @VisibleForTesting + public IResource getResource(ResourceLocation loc, ResourcePackType type, String pathSuffix, String pathPrefix) throws IOException { + return getManager(type).getResource(getLocation(loc, pathSuffix, pathPrefix)); + } + + /** + * @return {@code true} if validation is enabled, {@code false} otherwise + */ + public boolean isEnabled() { + return enable; + } +} diff --git a/src/main/java/net/minecraftforge/client/model/generators/IGeneratedBlockstate.java b/src/main/java/net/minecraftforge/client/model/generators/IGeneratedBlockstate.java new file mode 100644 index 000000000..3fb2389dd --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/generators/IGeneratedBlockstate.java @@ -0,0 +1,10 @@ +package net.minecraftforge.client.model.generators; + +import com.google.common.annotations.VisibleForTesting; +import com.google.gson.JsonObject; + +@VisibleForTesting +public interface IGeneratedBlockstate { + + JsonObject toJson(); +} diff --git a/src/main/java/net/minecraftforge/client/model/generators/ItemModelBuilder.java b/src/main/java/net/minecraftforge/client/model/generators/ItemModelBuilder.java new file mode 100644 index 000000000..04a9f6159 --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/generators/ItemModelBuilder.java @@ -0,0 +1,102 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2019. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.client.model.generators; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import com.google.common.base.Preconditions; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + +import net.minecraft.util.ResourceLocation; + +/** + * Builder for item models, adds the ability to build overrides via + * {@link #override()}. + */ +public class ItemModelBuilder extends ModelBuilder { + + protected List overrides = new ArrayList<>(); + + public ItemModelBuilder(ResourceLocation outputLocation, ExistingFileHelper existingFileHelper) { + super(outputLocation, existingFileHelper); + } + + public OverrideBuilder override() { + OverrideBuilder ret = new OverrideBuilder(); + overrides.add(ret); + return ret; + } + + /** + * Get an existing override builder + * + * @param index the index of the existing override builder + * @return the override builder + * @throws IndexOutOfBoundsException if {@code} index is out of bounds + */ + public OverrideBuilder override(int index) { + Preconditions.checkElementIndex(index, overrides.size(), "override"); + return overrides.get(index); + } + + @Override + public JsonObject toJson() { + JsonObject root = super.toJson(); + if (!overrides.isEmpty()) { + JsonArray overridesJson = new JsonArray(); + overrides.stream().map(OverrideBuilder::toJson).forEach(overridesJson::add); + root.add("overrides", overridesJson); + } + return root; + } + + public class OverrideBuilder { + + private ModelFile model; + private final Map predicates = new LinkedHashMap<>(); + + public OverrideBuilder model(ModelFile model) { + this.model = model; + model.assertExistence(); + return this; + } + + public OverrideBuilder predicate(ResourceLocation key, float value) { + this.predicates.put(key, value); + return this; + } + + public ItemModelBuilder end() { return ItemModelBuilder.this; } + + JsonObject toJson() { + JsonObject ret = new JsonObject(); + JsonObject predicatesJson = new JsonObject(); + predicates.forEach((key, val) -> predicatesJson.addProperty(serializeLoc(key), val)); + ret.add("predicate", predicatesJson); + ret.addProperty("model", serializeLoc(model.getLocation())); + return ret; + } + } + +} diff --git a/src/main/java/net/minecraftforge/client/model/generators/ItemModelProvider.java b/src/main/java/net/minecraftforge/client/model/generators/ItemModelProvider.java new file mode 100644 index 000000000..1c9e8f5e5 --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/generators/ItemModelProvider.java @@ -0,0 +1,14 @@ +package net.minecraftforge.client.model.generators; + +import net.minecraft.data.DataGenerator; + +/** + * Stub class to extend for item model data providers, eliminates some + * boilerplate constructor parameters. + */ +public abstract class ItemModelProvider extends ModelProvider { + + public ItemModelProvider(DataGenerator generator, String modid, ExistingFileHelper existingFileHelper) { + super(generator, modid, ITEM_FOLDER, ItemModelBuilder::new, existingFileHelper); + } +} diff --git a/src/main/java/net/minecraftforge/client/model/generators/ModelBuilder.java b/src/main/java/net/minecraftforge/client/model/generators/ModelBuilder.java new file mode 100644 index 000000000..a4ee1fb87 --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/generators/ModelBuilder.java @@ -0,0 +1,666 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2019. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.client.model.generators; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; + +import javax.annotation.Nullable; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + +import net.minecraft.client.renderer.Vector3f; +import net.minecraft.client.renderer.model.BlockFaceUV; +import net.minecraft.client.renderer.model.BlockPart; +import net.minecraft.client.renderer.model.BlockPartFace; +import net.minecraft.client.renderer.model.BlockPartRotation; +import net.minecraft.client.renderer.model.ItemCameraTransforms.TransformType; +import net.minecraft.client.renderer.model.ItemTransformVec3f; +import net.minecraft.client.renderer.texture.MissingTextureSprite; +import net.minecraft.resources.ResourcePackType; +import net.minecraft.util.Direction; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.MathHelper; + +/** + * General purpose model builder, contains all the commonalities between item + * and block models. + * + * @see ModelProvider + * @see BlockModelBuilder + * @see ItemModelBuilder + * + * @param Self type, for simpler chaining of methods. + */ +@SuppressWarnings("deprecation") +public class ModelBuilder> extends ModelFile { + + @Nullable + protected ModelFile parent; + protected final Map textures = new LinkedHashMap<>(); + protected final TransformsBuilder transforms = new TransformsBuilder(); + protected final ExistingFileHelper existingFileHelper; + + protected boolean ambientOcclusion = true; + protected boolean gui3d = false; + + protected final List elements = new ArrayList<>(); + + protected ModelBuilder(ResourceLocation outputLocation, ExistingFileHelper existingFileHelper) { + super(outputLocation); + this.existingFileHelper = existingFileHelper; + } + + @SuppressWarnings("unchecked") + private T self() { return (T) this; } + + @Override + protected boolean exists() { + return true; + } + + /** + * Set the parent model for the current model. + * + * @param parent the parent model + * @return this builder + * @throws NullPointerException if {@code parent} is {@code null} + * @throws IllegalStateException if {@code parent} does not {@link ModelFile#assertExistence() exist} + */ + public T parent(ModelFile parent) { + Preconditions.checkNotNull(parent, "Parent must not be null"); + parent.assertExistence(); + this.parent = parent; + return self(); + } + + /** + * Set the texture for a given dictionary key. + * + * @param key the texture key + * @param texture the texture, can be another key e.g. {@code "#all"} + * @return this builder + * @throws NullPointerException if {@code key} is {@code null} + * @throws NullPointerException if {@code texture} is {@code null} + * @throws IllegalStateException if {@code texture} is not a key (does not start + * with {@code '#'}) and does not exist in any + * known resource pack + */ + public T texture(String key, String texture) { + Preconditions.checkNotNull(key, "Key must not be null"); + Preconditions.checkNotNull(texture, "Texture must not be null"); + if (texture.charAt(0) == '#') { + this.textures.put(key, texture); + return self(); + } else { + ResourceLocation asLoc; + if (texture.contains(":")) { + asLoc = new ResourceLocation(texture); + } else { + asLoc = new ResourceLocation(getLocation().getNamespace(), texture); + } + return texture(key, asLoc); + } + } + + /** + * Set the texture for a given dictionary key. + * + * @param key the texture key + * @param texture the texture + * @return this builder + * @throws NullPointerException if {@code key} is {@code null} + * @throws NullPointerException if {@code texture} is {@code null} + * @throws IllegalStateException if {@code texture} is not a key (does not start + * with {@code '#'}) and does not exist in any + * known resource pack + */ + public T texture(String key, ResourceLocation texture) { + Preconditions.checkNotNull(key, "Key must not be null"); + Preconditions.checkNotNull(texture, "Texture must not be null"); + Preconditions.checkArgument(existingFileHelper.exists(texture, ResourcePackType.CLIENT_RESOURCES, ".png", "textures"), + "Texture %s does not exist in any known resource pack", texture); + this.textures.put(key, texture.toString()); + return self(); + } + + public TransformsBuilder transforms() { + return transforms; + } + + public T ao(boolean ao) { + this.ambientOcclusion = ao; + return self(); + } + + public T gui3d(boolean gui3d) { + this.gui3d = gui3d; + return self(); + } + + public ElementBuilder element() { + ElementBuilder ret = new ElementBuilder(); + elements.add(ret); + return ret; + } + + /** + * Get an existing element builder + * + * @param index the index of the existing element builder + * @return the element builder + * @throws IndexOutOfBoundsException if {@code} index is out of bounds + */ + public ElementBuilder element(int index) { + Preconditions.checkElementIndex(index, elements.size(), "Element index"); + return elements.get(index); + } + + @VisibleForTesting + public JsonObject toJson() { + JsonObject root = new JsonObject(); + if (this.parent != null) { + root.addProperty("parent", serializeLoc(this.parent.getLocation())); + } + + if (!this.ambientOcclusion) { + root.addProperty("ambientocclusion", this.ambientOcclusion); + } + + Map transforms = this.transforms.build(); + if (!transforms.isEmpty()) { + JsonObject display = new JsonObject(); + for (Entry e : transforms.entrySet()) { + JsonObject transform = new JsonObject(); + ItemTransformVec3f vec = e.getValue(); + if (vec.equals(ItemTransformVec3f.DEFAULT)) continue; + if (!vec.rotation.equals(ItemTransformVec3f.Deserializer.ROTATION_DEFAULT)) { + transform.add("rotation", serializeVector3f(vec.rotation)); + } + if (!vec.translation.equals(ItemTransformVec3f.Deserializer.TRANSLATION_DEFAULT)) { + transform.add("translation", serializeVector3f(e.getValue().translation)); + } + if (!vec.scale.equals(ItemTransformVec3f.Deserializer.SCALE_DEFAULT)) { + transform.add("scale", serializeVector3f(e.getValue().scale)); + } + display.add(e.getKey().name, transform); + } + root.add("display", display); + } + + if (!this.textures.isEmpty()) { + JsonObject textures = new JsonObject(); + for (Entry e : this.textures.entrySet()) { + textures.addProperty(e.getKey(), serializeLocOrKey(e.getValue())); + } + root.add("textures", textures); + } + + if (!this.elements.isEmpty()) { + JsonArray elements = new JsonArray(); + this.elements.stream().map(ElementBuilder::build).forEach(part -> { + JsonObject partObj = new JsonObject(); + partObj.add("from", serializeVector3f(part.positionFrom)); + partObj.add("to", serializeVector3f(part.positionTo)); + + if (part.partRotation != null) { + JsonObject rotation = new JsonObject(); + rotation.add("origin", serializeVector3f(part.partRotation.origin)); + rotation.addProperty("axis", part.partRotation.axis.getName()); + rotation.addProperty("angle", part.partRotation.angle); + if (part.partRotation.rescale) { + rotation.addProperty("rescale", part.partRotation.rescale); + } + partObj.add("rotation", rotation); + } + + if (!part.shade) { + partObj.addProperty("shade", part.shade); + } + + JsonObject faces = new JsonObject(); + for (Direction dir : Direction.values()) { + BlockPartFace face = part.mapFaces.get(dir); + if (face == null) continue; + + JsonObject faceObj = new JsonObject(); + faceObj.addProperty("texture", serializeLocOrKey(face.texture)); + if (!Arrays.equals(face.blockFaceUV.uvs, part.getFaceUvs(dir))) { + faceObj.add("uv", new Gson().toJsonTree(face.blockFaceUV.uvs)); + } + if (face.cullFace != null) { + faceObj.addProperty("cullface", face.cullFace.getName()); + } + if (face.blockFaceUV.rotation != 0) { + faceObj.addProperty("rotation", face.blockFaceUV.rotation); + } + if (face.tintIndex != -1) { + faceObj.addProperty("tintindex", face.tintIndex); + } + faces.add(dir.getName(), faceObj); + } + if (!part.mapFaces.isEmpty()) { + partObj.add("faces", faces); + } + elements.add(partObj); + }); + root.add("elements", elements); + } + + return root; + } + + private String serializeLocOrKey(String tex) { + if (tex.charAt(0) == '#') { + return tex; + } + return serializeLoc(new ResourceLocation(tex)); + } + + String serializeLoc(ResourceLocation loc) { + if (loc.getNamespace().equals("minecraft")) { + return loc.getPath(); + } + return loc.toString(); + } + + private JsonArray serializeVector3f(Vector3f vec) { + JsonArray ret = new JsonArray(); + ret.add(serializeFloat(vec.getX())); + ret.add(serializeFloat(vec.getY())); + ret.add(serializeFloat(vec.getZ())); + return ret; + } + + private Number serializeFloat(float f) { + if ((int) f == f) { + return (int) f; + } + return f; + } + + public class ElementBuilder { + + private Vector3f from = new Vector3f(); + private Vector3f to = new Vector3f(16, 16, 16); + private final Map faces = new LinkedHashMap<>(); + private RotationBuilder rotation; + private boolean shade = true; + + private void validateCoordinate(float coord, char name) { + Preconditions.checkArgument(!(coord < -16.0F) && !(coord > 32.0F), "Position " + name + " out of range, must be within [-16, 32]. Found: %d", coord); + } + + private void validatePosition(Vector3f pos) { + validateCoordinate(pos.getX(), 'x'); + validateCoordinate(pos.getY(), 'y'); + validateCoordinate(pos.getZ(), 'z'); + } + + /** + * Set the "from" position for this element. + * + * @param x x-position for this vector + * @param y y-position for this vector + * @param z z-position for this vector + * @return this builder + * @throws IllegalArgumentException if the vector is out of bounds (any + * coordinate not between -16 and 32, + * inclusive) + */ + public ElementBuilder from(float x, float y, float z) { + this.from = new Vector3f(x, y, z); + validatePosition(this.from); + return this; + } + + /** + * Set the "to" position for this element. + * + * @param x x-position for this vector + * @param y y-position for this vector + * @param z z-position for this vector + * @return this builder + * @throws IllegalArgumentException if the vector is out of bounds (any + * coordinate not between -16 and 32, + * inclusive) + */ + public ElementBuilder to(float x, float y, float z) { + this.to = new Vector3f(x, y, z); + validatePosition(this.to); + return this; + } + + /** + * Return or create the face builder for the given direction. + * + * @param dir the direction + * @return the face builder for the given direction + * @throws NullPointerException if {@code dir} is {@code null} + */ + public FaceBuilder face(Direction dir) { + Preconditions.checkNotNull(dir, "Direction must not be null"); + return faces.computeIfAbsent(dir, FaceBuilder::new); + } + + public RotationBuilder rotation(BlockPartRotation rotation) { + if (this.rotation == null) { + this.rotation = new RotationBuilder(); + } + return this.rotation; + } + + public ElementBuilder shade(boolean shade) { + this.shade = shade; + return this; + } + + /** + * Modify all possible faces dynamically using a function, creating new + * faces as necessary. + * + * @param action the function to apply to each direction + * @return this builder + * @throws NullPointerException if {@code action} is {@code null} + */ + public ElementBuilder allFaces(BiConsumer action) { + Arrays.stream(Direction.values()) + .forEach(d -> action.accept(d, face(d))); + return this; + } + + /** + * Modify all existing faces dynamically using a function. + * + * @param action the function to apply to each direction + * @return this builder + * @throws NullPointerException if {@code action} is {@code null} + */ + public ElementBuilder faces(BiConsumer action) { + faces.entrySet().stream() + .forEach(e -> action.accept(e.getKey(), e.getValue())); + return this; + } + + /** + * Texture all possible faces in the current element with the given + * texture, creating new faces where necessary. + * + * @param texture the texture + * @return this builder + * @throws NullPointerException if {@code texture} is {@code null} + */ + public ElementBuilder textureAll(String texture) { + return allFaces(addTexture(texture)); + } + + /** + * Texture all existing faces in the current element with the given + * texture. + * + * @param texture the texture + * @return this builder + * @throws NullPointerException if {@code texture} is {@code null} + */ + public ElementBuilder texture(String texture) { + return faces(addTexture(texture)); + } + + /** + * Create a typical cube element, creating new faces as needed, applying the + * given texture, and setting the cullface. + * + * @param texture the texture + * @return this builder + * @throws NullPointerException if {@code texture} is {@code null} + */ + public ElementBuilder cube(String texture) { + return allFaces(addTexture(texture).andThen((dir, f) -> f.cullface(dir))); + } + + private BiConsumer addTexture(String texture) { + return ($, f) -> f.texture(texture); + } + + BlockPart build() { + Map faces = this.faces.entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().build(), (k1, k2) -> { throw new IllegalArgumentException(); }, LinkedHashMap::new)); + return new BlockPart(from, to, faces, rotation == null ? null : rotation.build(), shade); + } + + public T end() { return self(); } + + public class FaceBuilder { + + private Direction cullface; + private int tintindex = -1; + private String texture = MissingTextureSprite.getLocation().toString(); + private float[] uvs; + private FaceRotation rotation = FaceRotation.ZERO; + + FaceBuilder(Direction dir) { + // param unused for functional match + } + + public FaceBuilder cullface(@Nullable Direction dir) { + this.cullface = dir; + return this; + } + + public FaceBuilder tintindex(int index) { + this.tintindex = index; + return this; + } + + /** + * Set the texture for the current face. + * + * @param texture the texture + * @return this builder + * @throws NullPointerException if {@code texture} is {@code null} + */ + public FaceBuilder texture(String texture) { + Preconditions.checkNotNull(texture, "Texture must not be null"); + this.texture = texture; + return this; + } + + public FaceBuilder uvs(float u1, float v1, float u2, float v2) { + this.uvs = new float[] { u1, v1, u2, v2 }; + return this; + } + + /** + * Set the texture rotation for the current face. + * + * @param rot the rotation + * @return this builder + * @throws NullPointerException if {@code rot} is {@code null} + */ + public FaceBuilder rotation(FaceRotation rot) { + Preconditions.checkNotNull(rot, "Rotation must not be null"); + this.rotation = rot; + return this; + } + + BlockPartFace build() { + if (this.texture == null) { + throw new IllegalStateException("A model face must have a texture"); + } + return new BlockPartFace(cullface, tintindex, texture, new BlockFaceUV(uvs, rotation.rotation)); + } + + public ElementBuilder end() { return ElementBuilder.this; } + } + + public class RotationBuilder { + + private Vector3f origin; + private Direction.Axis axis; + private float angle; + private boolean rescale; + + public RotationBuilder origin(float x, float y, float z) { + this.origin = new Vector3f(x, y, z); + return this; + } + + /** + * @param axis the axis of rotation + * @return this builder + * @throws NullPointerException if {@code axis} is {@code null} + */ + public RotationBuilder axis(Direction.Axis axis) { + Preconditions.checkNotNull(axis, "Axis must not be null"); + this.axis = axis; + return this; + } + + /** + * @param angle the rotation angle + * @return this builder + * @throws IllegalArgumentException if {@code angle} is invalid (not one of 0, +/-22.5, +/-45) + */ + public RotationBuilder angle(float angle) { + // Same logic from BlockPart.Deserializer#parseAngle + Preconditions.checkArgument(angle != 0.0F && MathHelper.abs(angle) != 22.5F && MathHelper.abs(angle) != 45.0F, "Invalid rotation %f found, only -45/-22.5/0/22.5/45 allowed", angle); + this.angle = angle; + return this; + } + + public RotationBuilder rescale(boolean rescale) { + this.rescale = rescale; + return this; + } + + BlockPartRotation build() { + return new BlockPartRotation(origin, axis, angle, rescale); + } + + public ElementBuilder end() { return ElementBuilder.this; } + } + } + + public enum FaceRotation { + ZERO(0), + CLOCKWISE_90(90), + UPSIDE_DOWN(180), + COUNTERCLOCKWISE_90(270), + ; + + final int rotation; + + private FaceRotation(int rotation) { + this.rotation = rotation; + } + } + + // Since vanilla doesn't keep the name in TransformType... + public enum Perspective { + + THIRDPERSON_RIGHT(TransformType.THIRD_PERSON_RIGHT_HAND, "thirdperson_righthand"), + THIRDPERSON_LEFT(TransformType.THIRD_PERSON_LEFT_HAND, "thirdperson_lefthand"), + FIRSTPERSON_RIGHT(TransformType.FIRST_PERSON_RIGHT_HAND, "firstperson_righthand"), + FIRSTPERSON_LEFT(TransformType.FIRST_PERSON_LEFT_HAND, "firstperson_lefthand"), + HEAD(TransformType.HEAD, "head"), + GUI(TransformType.GUI, "gui"), + GROUND(TransformType.GROUND, "ground"), + FIXED(TransformType.FIXED, "fixed"), + ; + + public final TransformType vanillaType; + final String name; + + private Perspective(TransformType vanillaType, String name) { + this.vanillaType = vanillaType; + this.name = name; + } + } + + public class TransformsBuilder { + + private final Map transforms = new LinkedHashMap<>(); + + /** + * Begin building a new transform for the given perspective. + * + * @param type the perspective to create or return the builder for + * @return the builder for the given perspective + * @throws NullPointerException if {@code type} is {@code null} + */ + public TransformVecBuilder transform(Perspective type) { + Preconditions.checkNotNull(type, "Perspective cannot be null"); + return transforms.computeIfAbsent(type, TransformVecBuilder::new); + } + + Map build() { + return this.transforms.entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().build(), (k1, k2) -> { throw new IllegalArgumentException(); }, LinkedHashMap::new)); + } + + public T end() { return self(); } + + public class TransformVecBuilder { + + private Vector3f rotation = new Vector3f(); + private Vector3f translation = new Vector3f(); + private Vector3f scale = new Vector3f(); + + TransformVecBuilder(Perspective type) { + // param unused for functional match + } + + public TransformVecBuilder rotation(float x, float y, float z) { + this.rotation = new Vector3f(x, y, z); + return this; + } + + public TransformVecBuilder translation(float x, float y, float z) { + this.translation = new Vector3f(x, y, z); + return this; + } + + public TransformVecBuilder scale(float sc) { + return scale(sc, sc, sc); + } + + public TransformVecBuilder scale(float x, float y, float z) { + this.scale = new Vector3f(x, y, z); + return this; + } + + ItemTransformVec3f build() { + return new ItemTransformVec3f(rotation, translation, scale); + } + + public TransformsBuilder end() { return TransformsBuilder.this; } + } + } +} diff --git a/src/main/java/net/minecraftforge/client/model/generators/ModelFile.java b/src/main/java/net/minecraftforge/client/model/generators/ModelFile.java new file mode 100644 index 000000000..f2a7444dc --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/generators/ModelFile.java @@ -0,0 +1,90 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2019. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.client.model.generators; + +import com.google.common.base.Preconditions; + +import net.minecraft.resources.ResourcePackType; +import net.minecraft.util.ResourceLocation; + +public abstract class ModelFile { + + protected ResourceLocation location; + + protected ModelFile(ResourceLocation location) { + this.location = location; + } + + protected abstract boolean exists(); + + public ResourceLocation getLocation() { + assertExistence(); + return location; + } + + /** + * Assert that this model exists. + * @throws IllegalStateException if this model does not exist + */ + public void assertExistence() { + Preconditions.checkState(exists(), "Model at %s does not exist", location); + } + + public ResourceLocation getUncheckedLocation() { + return location; + } + + public static class UncheckedModelFile extends ModelFile { + + public UncheckedModelFile(String location) { + this(new ResourceLocation(location)); + } + public UncheckedModelFile(ResourceLocation location) { + super(location); + } + + @Override + protected boolean exists() { + return true; + } + } + + public static class ExistingModelFile extends ModelFile { + private final ExistingFileHelper existingHelper; + + @Deprecated + public ExistingModelFile(String location, ExistingFileHelper existingHelper) { + this(new ResourceLocation(location), existingHelper); + } + + public ExistingModelFile(ResourceLocation location, ExistingFileHelper existingHelper) { + super(location); + this.existingHelper = existingHelper; + } + + @Override + protected boolean exists() { + if (getUncheckedLocation().getPath().contains(".")) + return existingHelper.exists(getUncheckedLocation(), ResourcePackType.CLIENT_RESOURCES, "", "models"); + else + return existingHelper.exists(getUncheckedLocation(), ResourcePackType.CLIENT_RESOURCES, ".json", "models"); + } + } +} diff --git a/src/main/java/net/minecraftforge/client/model/generators/ModelProvider.java b/src/main/java/net/minecraftforge/client/model/generators/ModelProvider.java new file mode 100644 index 000000000..6652ad239 --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/generators/ModelProvider.java @@ -0,0 +1,380 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2019. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.client.model.generators; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiFunction; +import java.util.function.Function; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import net.minecraft.data.DataGenerator; +import net.minecraft.data.DirectoryCache; +import net.minecraft.data.IDataProvider; +import net.minecraft.resources.ResourcePackType; +import net.minecraft.util.ResourceLocation; + +public abstract class ModelProvider> implements IDataProvider { + + private class ExistingFileHelperIncludingGenerated extends ExistingFileHelper { + + private final ExistingFileHelper delegate; + + public ExistingFileHelperIncludingGenerated(ExistingFileHelper delegate) { + super(Collections.emptyList(), true); + this.delegate = delegate; + } + + @Override + public boolean exists(ResourceLocation loc, ResourcePackType type, String pathSuffix, String pathPrefix) { + if (generatedModels.containsKey(loc)) { + return true; + } + return delegate.exists(loc, type, pathSuffix, pathPrefix); + } + } + + public static final String BLOCK_FOLDER = "block"; + public static final String ITEM_FOLDER = "item"; + + private static final Gson GSON = (new GsonBuilder()).setPrettyPrinting().create(); + protected final DataGenerator generator; + protected final String modid; + protected final String folder; + protected final Function factory; + @VisibleForTesting + protected final Map generatedModels = new HashMap<>(); + protected final ExistingFileHelper existingFileHelper; + + protected DirectoryCache cache; + + protected abstract void registerModels(); + + public ModelProvider(DataGenerator generator, String modid, String folder, Function factory, ExistingFileHelper existingFileHelper) { + Preconditions.checkNotNull(generator); + this.generator = generator; + Preconditions.checkNotNull(modid); + this.modid = modid; + Preconditions.checkNotNull(folder); + this.folder = folder; + Preconditions.checkNotNull(factory); + this.factory = factory; + Preconditions.checkNotNull(existingFileHelper); + this.existingFileHelper = new ExistingFileHelperIncludingGenerated(existingFileHelper); + } + + public ModelProvider(DataGenerator generator, String modid, String folder, BiFunction builderFromModId, ExistingFileHelper existingFileHelper) { + this(generator, modid, folder, loc->builderFromModId.apply(loc, existingFileHelper), existingFileHelper); + } + + protected T getBuilder(String path) { + Preconditions.checkNotNull(path, "Path must not be null"); + ResourceLocation outputLoc = extendWithFolder(path.contains(":") ? new ResourceLocation(path) : new ResourceLocation(modid, path)); + return generatedModels.computeIfAbsent(outputLoc, factory); + } + + private ResourceLocation extendWithFolder(ResourceLocation rl) { + if (rl.getPath().contains("/")) { + return rl; + } + return new ResourceLocation(rl.getNamespace(), folder + "/" + rl.getPath()); + } + + protected ResourceLocation modLoc(String name) { + return new ResourceLocation(modid, name); + } + + protected ResourceLocation mcLoc(String name) { + return new ResourceLocation(name); + } + + protected T withExistingParent(String name, String parent) { + return withExistingParent(name, mcLoc(parent)); + } + + protected T withExistingParent(String name, ResourceLocation parent) { + return getBuilder(name).parent(getExistingFile(parent)); + } + + protected T cube(String name, ResourceLocation down, ResourceLocation up, ResourceLocation north, ResourceLocation south, ResourceLocation east, ResourceLocation west) { + return withExistingParent(name, "cube") + .texture("down", down) + .texture("up", up) + .texture("north", north) + .texture("south", south) + .texture("east", east) + .texture("west", west); + } + + private T singleTexture(String name, String parent, ResourceLocation texture) { + return singleTexture(name, mcLoc(parent), texture); + } + + protected T singleTexture(String name, ResourceLocation parent, ResourceLocation texture) { + return singleTexture(name, parent, "texture", texture); + } + + private T singleTexture(String name, String parent, String textureKey, ResourceLocation texture) { + return singleTexture(name, mcLoc(parent), textureKey, texture); + } + + protected T singleTexture(String name, ResourceLocation parent, String textureKey, ResourceLocation texture) { + return withExistingParent(name, parent) + .texture(textureKey, texture); + } + + protected T cubeAll(String name, ResourceLocation texture) { + return singleTexture(name, BLOCK_FOLDER + "/cube_all", "all", texture); + } + + protected T cubeTop(String name, ResourceLocation side, ResourceLocation top) { + return withExistingParent(name, BLOCK_FOLDER + "/cube_top") + .texture("side", side) + .texture("top", top); + } + + private T sideBottomTop(String name, String parent, ResourceLocation side, ResourceLocation bottom, ResourceLocation top) { + return withExistingParent(name, parent) + .texture("side", side) + .texture("bottom", bottom) + .texture("top", top); + } + + protected T cubeBottomTop(String name, ResourceLocation side, ResourceLocation bottom, ResourceLocation top) { + return sideBottomTop(name, BLOCK_FOLDER + "/cube_bottom_top", side, bottom, top); + } + + protected T cubeColumn(String name, ResourceLocation side, ResourceLocation end) { + return withExistingParent(name, BLOCK_FOLDER + "/cube_column") + .texture("side", side) + .texture("end", end); + } + + protected T orientableVertical(String name, ResourceLocation side, ResourceLocation front) { + return withExistingParent(name, BLOCK_FOLDER + "/orientable_vertical") + .texture("side", side) + .texture("front", front); + } + + protected T orientableWithBottom(String name, ResourceLocation side, ResourceLocation front, ResourceLocation bottom, ResourceLocation top) { + return withExistingParent(name, BLOCK_FOLDER + "/orientable_with_bottom") + .texture("side", side) + .texture("front", front) + .texture("bottom", bottom) + .texture("top", top); + } + + protected T orientable(String name, ResourceLocation side, ResourceLocation front, ResourceLocation top) { + return withExistingParent(name, BLOCK_FOLDER + "/orientable") + .texture("side", side) + .texture("front", front) + .texture("top", top); + } + + protected T crop(String name, ResourceLocation crop) { + return singleTexture(name, BLOCK_FOLDER + "/crop", "crop", crop); + } + + protected T cross(String name, ResourceLocation cross) { + return singleTexture(name, BLOCK_FOLDER + "/cross", "cross", cross); + } + + protected T stairs(String name, ResourceLocation side, ResourceLocation bottom, ResourceLocation top) { + return sideBottomTop(name, BLOCK_FOLDER + "/stairs", side, bottom, top); + } + + protected T stairsOuter(String name, ResourceLocation side, ResourceLocation bottom, ResourceLocation top) { + return sideBottomTop(name, BLOCK_FOLDER + "/outer_stairs", side, bottom, top); + } + + protected T stairsInner(String name, ResourceLocation side, ResourceLocation bottom, ResourceLocation top) { + return sideBottomTop(name, BLOCK_FOLDER + "/inner_stairs", side, bottom, top); + } + + protected T slab(String name, ResourceLocation side, ResourceLocation bottom, ResourceLocation top) { + return sideBottomTop(name, BLOCK_FOLDER + "/slab", side, bottom, top); + } + + protected T slabTop(String name, ResourceLocation side, ResourceLocation bottom, ResourceLocation top) { + return sideBottomTop(name, BLOCK_FOLDER + "/slab_top", side, bottom, top); + } + + protected T fencePost(String name, ResourceLocation texture) { + return singleTexture(name, BLOCK_FOLDER + "/fence_post", texture); + } + + protected T fenceSide(String name, ResourceLocation texture) { + return singleTexture(name, BLOCK_FOLDER + "/fence_side", texture); + } + + protected T fenceInventory(String name, ResourceLocation texture) { + return singleTexture(name, BLOCK_FOLDER + "/fence_inventory", texture); + } + + protected T fenceGate(String name, ResourceLocation texture) { + return singleTexture(name, BLOCK_FOLDER + "/template_fence_gate", texture); + } + + protected T fenceGateOpen(String name, ResourceLocation texture) { + return singleTexture(name, BLOCK_FOLDER + "/template_fence_gate_open", texture); + } + + protected T fenceGateWall(String name, ResourceLocation texture) { + return singleTexture(name, BLOCK_FOLDER + "/template_fence_gate_wall", texture); + } + + protected T fenceGateWallOpen(String name, ResourceLocation texture) { + return singleTexture(name, BLOCK_FOLDER + "/template_fence_gate_wall_open", texture); + } + + protected T wallPost(String name, ResourceLocation wall) { + return singleTexture(name, BLOCK_FOLDER + "/template_wall_post", "wall", wall); + } + + protected T wallSide(String name, ResourceLocation wall) { + return singleTexture(name, BLOCK_FOLDER + "/template_wall_side", "wall", wall); + } + + protected T wallInventory(String name, ResourceLocation wall) { + return singleTexture(name, BLOCK_FOLDER + "/wall_inventory", "wall", wall); + } + + private T pane(String name, String parent, ResourceLocation pane, ResourceLocation edge) { + return withExistingParent(name, BLOCK_FOLDER + "/" + parent) + .texture("pane", pane) + .texture("edge", edge); + } + + protected T panePost(String name, ResourceLocation pane, ResourceLocation edge) { + return pane(name, "template_glass_pane_post", pane, edge); + } + + protected T paneSide(String name, ResourceLocation pane, ResourceLocation edge) { + return pane(name, "template_glass_pane_side", pane, edge); + } + + protected T paneSideAlt(String name, ResourceLocation pane, ResourceLocation edge) { + return pane(name, "template_glass_pane_side_alt", pane, edge); + } + + protected T paneNoSide(String name, ResourceLocation pane) { + return singleTexture(name, BLOCK_FOLDER + "/template_glass_pane_noside", "pane", pane); + } + + protected T paneNoSideAlt(String name, ResourceLocation pane) { + return singleTexture(name, BLOCK_FOLDER + "/template_glass_pane_noside_alt", "pane", pane); + } + + private T door(String name, String model, ResourceLocation bottom, ResourceLocation top) { + return withExistingParent(name, BLOCK_FOLDER + "/" + model) + .texture("bottom", bottom) + .texture("top", top); + } + + protected T doorBottomLeft(String name, ResourceLocation bottom, ResourceLocation top) { + return door(name, "door_bottom", bottom, top); + } + + protected T doorBottomRight(String name, ResourceLocation bottom, ResourceLocation top) { + return door(name, "door_bottom_rh", bottom, top); + } + + protected T doorTopLeft(String name, ResourceLocation bottom, ResourceLocation top) { + return door(name, "door_top", bottom, top); + } + + protected T doorTopRight(String name, ResourceLocation bottom, ResourceLocation top) { + return door(name, "door_top_rh", bottom, top); + } + + protected T trapdoorBottom(String name, ResourceLocation texture) { + return singleTexture(name, BLOCK_FOLDER + "/template_trapdoor_bottom", texture); + } + + protected T trapdoorTop(String name, ResourceLocation texture) { + return singleTexture(name, BLOCK_FOLDER + "/template_trapdoor_top", texture); + } + + protected T trapdoorOpen(String name, ResourceLocation texture) { + return singleTexture(name, BLOCK_FOLDER + "/template_trapdoor_open", texture); + } + + protected T trapdoorOrientableBottom(String name, ResourceLocation texture) { + return singleTexture(name, BLOCK_FOLDER + "/template_orientable_trapdoor_bottom", texture); + } + + protected T trapdoorOrientableTop(String name, ResourceLocation texture) { + return singleTexture(name, BLOCK_FOLDER + "/template_orientable_trapdoor_top", texture); + } + + protected T trapdoorOrientableOpen(String name, ResourceLocation texture) { + return singleTexture(name, BLOCK_FOLDER + "/template_orientable_trapdoor_open", texture); + } + + protected T torch(String name, ResourceLocation torch) { + return singleTexture(name, BLOCK_FOLDER + "/template_torch", "torch", torch); + } + + protected T torchWall(String name, ResourceLocation torch) { + return singleTexture(name, BLOCK_FOLDER + "/torch_wall", "torch", torch); + } + + protected T carpet(String name, ResourceLocation wool) { + return singleTexture(name, BLOCK_FOLDER + "/carpet", "wool", wool); + } + + protected ModelFile.ExistingModelFile getExistingFile(ResourceLocation path) { + ModelFile.ExistingModelFile ret = new ModelFile.ExistingModelFile(extendWithFolder(path), existingFileHelper); + ret.assertExistence(); + return ret; + } + + @Override + public void act(DirectoryCache cache) throws IOException { + this.cache = cache; + generatedModels.clear(); + registerModels(); + generateAll(); + this.cache = null; + } + + private void generateAll() { + for (T model : generatedModels.values()) { + Path target = getPath(model); + try { + IDataProvider.save(GSON, cache, model.toJson(), target); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + private Path getPath(T model) { + ResourceLocation loc = model.getLocation(); + return generator.getOutputFolder().resolve("assets/" + loc.getNamespace() + "/models/" + loc.getPath() + ".json"); + } +} diff --git a/src/main/java/net/minecraftforge/client/model/generators/MultiPartBlockStateBuilder.java b/src/main/java/net/minecraftforge/client/model/generators/MultiPartBlockStateBuilder.java new file mode 100644 index 000000000..7a7801320 --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/generators/MultiPartBlockStateBuilder.java @@ -0,0 +1,125 @@ +package net.minecraftforge.client.model.generators; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map.Entry; + +import com.google.common.base.Preconditions; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + +import net.minecraft.block.Block; +import net.minecraft.state.IProperty; + +public final class MultiPartBlockStateBuilder implements IGeneratedBlockstate { + + private final List parts = new ArrayList<>(); + private final Block owner; + + public MultiPartBlockStateBuilder(Block owner) { + this.owner = owner; + } + + /** + * Creates a builder for models to assign to a {@link PartBuilder}, which when + * completed via {@link ConfiguredModel.Builder#addModel()} will assign the + * resultant set of models to the part and return it for further processing. + * + * @return the model builder + * @see ConfiguredModel.Builder + */ + public ConfiguredModel.Builder part() { + return ConfiguredModel.builder(this); + } + + MultiPartBlockStateBuilder addPart(PartBuilder part) { + this.parts.add(part); + return this; + } + + @Override + public JsonObject toJson() { + JsonArray variants = new JsonArray(); + for (PartBuilder part : parts) { + variants.add(part.toJson()); + } + JsonObject main = new JsonObject(); + main.add("multipart", variants); + return main; + } + + public class PartBuilder { + public BlockStateProvider.ConfiguredModelList models; + public boolean useOr; + public final Multimap, Comparable> conditions = HashMultimap.create(); + + PartBuilder(BlockStateProvider.ConfiguredModelList models) { + this.models = models; + } + + public PartBuilder useOr() { + this.useOr = true; + return this; + } + + /** + * Set a condition for this part, which consists of a property and a set of + * valid values. Can be called multiple times for multiple different properties. + * + * @param the type of the property value + * @param prop the property + * @param values a set of valid values + * @return this builder + * @throws NullPointerException if {@code prop} is {@code null} + * @throws NullPointerException if {@code values} is {@code null} + * @throws IllegalArgumentException if {@code values} is empty + * @throws IllegalArgumentException if {@code prop} has already been configured + * @throws IllegalArgumentException if {@code prop} is not applicable to the + * current block's state + */ + @SafeVarargs + public final > PartBuilder condition(IProperty prop, T... values) { + Preconditions.checkNotNull(prop, "Property must not be null"); + Preconditions.checkNotNull(values, "Value list must not be null"); + Preconditions.checkArgument(values.length > 0, "Value list must not be empty"); + Preconditions.checkArgument(!conditions.containsKey(prop), "Cannot set condition for property \"%s\" more than once", prop.getName()); + Preconditions.checkArgument(canApplyTo(owner), "IProperty %s is not valid for the block %s", prop, owner); + this.conditions.putAll(prop, Arrays.asList(values)); + return this; + } + + public MultiPartBlockStateBuilder end() { return MultiPartBlockStateBuilder.this; } + + JsonObject toJson() { + JsonObject out = new JsonObject(); + if (!conditions.isEmpty()) { + JsonObject when = new JsonObject(); + for (Entry, Collection>> e : conditions.asMap().entrySet()) { + StringBuilder activeString = new StringBuilder(); + for (Object val : e.getValue()) { + if (activeString.length() > 0) + activeString.append("|"); + activeString.append(val.toString()); + } + when.addProperty(e.getKey().getName(), activeString.toString()); + } + if (useOr) { + JsonObject innerWhen = when; + when = new JsonObject(); + when.add("OR", innerWhen); + } + out.add("when", when); + } + out.add("apply", models.toJSON()); + return out; + } + + public boolean canApplyTo(Block b) { + return b.getStateContainer().getProperties().containsAll(conditions.keySet()); + } + } +} diff --git a/src/main/java/net/minecraftforge/client/model/generators/VariantBlockStateBuilder.java b/src/main/java/net/minecraftforge/client/model/generators/VariantBlockStateBuilder.java new file mode 100644 index 000000000..70507a35a --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/generators/VariantBlockStateBuilder.java @@ -0,0 +1,340 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2019. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.client.model.generators; + +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.function.Function; +import java.util.function.Predicate; + +import javax.annotation.Nullable; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.gson.JsonObject; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.state.IProperty; +import net.minecraftforge.client.model.generators.BlockStateProvider.ConfiguredModelList; + +/** + * Builder for variant-type blockstates, i.e. non-multipart blockstates. Should + * not be manually instantiated, instead use + * {@link BlockStateProvider#getVariantBuilder(Block)}. + *

+ * Variants can either be set via + * {@link #setModels(PartialBlockstate, ConfiguredModel...)} or + * {@link #addModels(PartialBlockstate, ConfiguredModel...)}, where model(s) can + * be assigned directly to {@link PartialBlockstate partial states}, or builder + * style via {@link #partialState()} and its subsequent methods. + *

+ * This class also provides the convenience methods + * {@link #forAllStates(Function)} and + * {@link #forAllStatesExcept(Function, IProperty...)} for cases where the model + * for each variant can be decided dynamically based on the state's property + * values. + * + * @see BlockStateProvider + */ +public class VariantBlockStateBuilder implements IGeneratedBlockstate { + + private final Block owner; + private final Map models = new LinkedHashMap<>(); + private final Set coveredStates = new HashSet<>(); + + VariantBlockStateBuilder(Block owner) { + this.owner = owner; + } + + public Map getModels() { + return models; + } + + public Block getOwner() { + return owner; + } + + @Override + public JsonObject toJson() { + List missingStates = Lists.newArrayList(owner.getStateContainer().getValidStates()); + missingStates.removeAll(coveredStates); + Preconditions.checkState(missingStates.isEmpty(), "Blockstate for block %s does not cover all states. Missing: %s", owner, missingStates); + JsonObject variants = new JsonObject(); + getModels().entrySet().stream() + .sorted(Entry.comparingByKey(PartialBlockstate.comparingByProperties())) + .forEach(entry -> variants.add(entry.getKey().toString(), entry.getValue().toJSON())); + JsonObject main = new JsonObject(); + main.add("variants", variants); + return main; + } + + /** + * Assign some models to a given {@link PartialBlockstate partial state}. + * + * @param state The {@link PartialBlockstate partial state} for which to add + * the models + * @param models A set of models to add to this state + * @return this builder + * @throws NullPointerException if {@code state} is {@code null} + * @throws IllegalArgumentException if {@code models} is empty + * @throws IllegalArgumentException if {@code state}'s owning block differs from + * the builder's + * @throws IllegalArgumentException if {@code state} partially matches another + * state which has already been configured + */ + public VariantBlockStateBuilder addModels(PartialBlockstate state, ConfiguredModel... models) { + Preconditions.checkNotNull(state, "state must not be null"); + Preconditions.checkArgument(models.length > 0, "Cannot set models to empty array"); + Preconditions.checkArgument(state.getOwner() == owner, "Cannot set models for a different block. Found: %s, Current: %s", state.getOwner(), owner); + if (!this.models.containsKey(state)) { + Preconditions.checkArgument(disjointToAll(state), "Cannot set models for a state for which a partial match has already been configured"); + this.models.put(state, new ConfiguredModelList(models)); + for (BlockState fullState : owner.getStateContainer().getValidStates()) { + if (state.test(fullState)) { + coveredStates.add(fullState); + } + } + } else { + this.models.compute(state, ($, cml) -> cml.append(models)); + } + return this; + } + + /** + * Assign some models to a given {@link PartialBlockstate partial state}, + * throwing an exception if the state has already been configured. Otherwise, + * simply calls {@link #addModels(PartialBlockstate, ConfiguredModel...)}. + * + * @param state The {@link PartialBlockstate partial state} for which to set + * the models + * @param models A set of models to assign to this state + * @return this builder + * @throws IllegalArgumentException if {@code state} has already been configured + * @see #addModels(PartialBlockstate, ConfiguredModel...) + */ + public VariantBlockStateBuilder setModels(PartialBlockstate state, ConfiguredModel... model) { + Preconditions.checkArgument(!models.containsKey(state), "Cannot set models for a state that has already been configured: %s", state); + addModels(state, model); + return this; + } + + private boolean disjointToAll(PartialBlockstate newState) { + return coveredStates.stream().noneMatch(newState); + } + + public PartialBlockstate partialState() { + return new PartialBlockstate(owner, this); + } + + public VariantBlockStateBuilder forAllStates(Function mapper) { + return forAllStatesExcept(mapper); + } + + public VariantBlockStateBuilder forAllStatesExcept(Function mapper, IProperty... ignored) { + Set seen = new HashSet<>(); + for (BlockState fullState : owner.getStateContainer().getValidStates()) { + Map, Comparable> propertyValues = Maps.newLinkedHashMap(fullState.getValues()); + for (IProperty p : ignored) { + propertyValues.remove(p); + } + PartialBlockstate partialState = new PartialBlockstate(owner, propertyValues, this); + if (seen.add(partialState)) { + setModels(partialState, mapper.apply(fullState)); + } + } + return this; + } + + public static class PartialBlockstate implements Predicate { + private final Block owner; + private final SortedMap, Comparable> setStates; + @Nullable + private final VariantBlockStateBuilder outerBuilder; + + PartialBlockstate(Block owner, @Nullable VariantBlockStateBuilder outerBuilder) { + this(owner, ImmutableMap.of(), outerBuilder); + } + + PartialBlockstate(Block owner, Map, Comparable> setStates, @Nullable VariantBlockStateBuilder outerBuilder) { + this.owner = owner; + this.outerBuilder = outerBuilder; + for (Map.Entry, Comparable> entry : setStates.entrySet()) { + IProperty prop = entry.getKey(); + Comparable value = entry.getValue(); + Preconditions.checkArgument(owner.getStateContainer().getProperties().contains(prop), "Property %s not found on block %s", entry, this.owner); + Preconditions.checkArgument(prop.getAllowedValues().contains(value), "%s is not a valid value for %s", value, prop); + } + this.setStates = Maps.newTreeMap(Comparator.comparing(IProperty::getName)); + this.setStates.putAll(setStates); + } + + public > PartialBlockstate with(IProperty prop, T value) { + Preconditions.checkArgument(!setStates.containsKey(prop), "Property %s has already been set", prop); + Map, Comparable> newState = new HashMap<>(setStates); + newState.put(prop, value); + return new PartialBlockstate(owner, newState, outerBuilder); + } + + private void checkValidOwner() { + Preconditions.checkNotNull(outerBuilder, "Partial blockstate must have a valid owner to perform this action"); + } + + /** + * Creates a builder for models to assign to this state, which when completed + * via {@link ConfiguredModel.Builder#addModel()} will assign the resultant set + * of models to this state. + * + * @return the model builder + * @see ConfiguredModel.Builder + */ + public ConfiguredModel.Builder modelForState() { + checkValidOwner(); + return ConfiguredModel.builder(outerBuilder, this); + } + + /** + * Add models to the current state's variant. For use when it is more convenient + * to add multiple sets of models, as a replacement for + * {@link #setModels(ConfiguredModel...)}. + * + * @param models The models to add. + * @return {@code this} + * @throws NullPointerException If the parent builder is {@code null} + */ + public PartialBlockstate addModels(ConfiguredModel... models) { + checkValidOwner(); + outerBuilder.addModels(this, models); + return this; + } + + /** + * Set this variant's models, and return the parent builder. + * + * @param models The models to set + * @return The parent builder instance + * @throws NullPointerException If the parent builder is {@code null} + */ + public VariantBlockStateBuilder setModels(ConfiguredModel... models) { + checkValidOwner(); + return outerBuilder.setModels(this, models); + } + + /** + * Complete this state without adding any new models, and return a new partial + * state via the parent builder. For use after calling + * {@link #addModels(ConfiguredModel...)}. + * + * @return A fresh partial state as specified by + * {@link VariantBlockStateBuilder#partialState()}. + * @throws NullPointerException If the parent builder is {@code null} + */ + public PartialBlockstate partialState() { + checkValidOwner(); + return outerBuilder.partialState(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PartialBlockstate that = (PartialBlockstate) o; + return owner.equals(that.owner) && + setStates.equals(that.setStates); + } + + @Override + public int hashCode() { + return Objects.hash(owner, setStates); + } + + public Block getOwner() { + return owner; + } + + public SortedMap, Comparable> getSetStates() { + return setStates; + } + + @Override + public boolean test(BlockState blockState) { + if (blockState.getBlock() != getOwner()) { + return false; + } + for (Map.Entry, Comparable> entry : setStates.entrySet()) { + if (blockState.get(entry.getKey()) != entry.getValue()) { + return false; + } + } + return true; + } + + @Override + public String toString() { + StringBuilder ret = new StringBuilder(); + for (Map.Entry, Comparable> entry : setStates.entrySet()) { + if (ret.length() > 0) { + ret.append(','); + } + ret.append(entry.getKey().getName()) + .append('=') + .append(entry.getValue()); + } + return ret.toString(); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static Comparator comparingByProperties() { + // Sort variants inversely by property values, to approximate vanilla style + return (s1, s2) -> { + SortedSet> propUniverse = new TreeSet<>(s1.getSetStates().comparator().reversed()); + propUniverse.addAll(s1.getSetStates().keySet()); + propUniverse.addAll(s2.getSetStates().keySet()); + for (IProperty prop : propUniverse) { + Comparable val1 = s1.getSetStates().get(prop); + Comparable val2 = s2.getSetStates().get(prop); + if (val1 == null && val2 != null) { + return -1; + } else if (val2 == null && val1 != null) { + return 1; + } else if (val1 != null && val2 != null){ + int cmp = val1.compareTo(val2); + if (cmp != 0) { + return cmp; + } + } + } + return 0; + }; + } + } +} \ No newline at end of file diff --git a/src/main/java/net/minecraftforge/fml/ModLoader.java b/src/main/java/net/minecraftforge/fml/ModLoader.java index 812d8b23d..838ec49e0 100644 --- a/src/main/java/net/minecraftforge/fml/ModLoader.java +++ b/src/main/java/net/minecraftforge/fml/ModLoader.java @@ -23,6 +23,7 @@ import com.google.common.collect.ImmutableList; import cpw.mods.modlauncher.TransformingClassLoader; import net.minecraft.util.registry.Bootstrap; import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.client.model.generators.ExistingFileHelper; import net.minecraftforge.common.capabilities.CapabilityManager; import net.minecraftforge.eventbus.api.Event; import net.minecraftforge.fml.config.ConfigTracker; @@ -103,6 +104,7 @@ public class ModLoader private final List loadingExceptions; private final List loadingWarnings; private GatherDataEvent.DataGeneratorConfig dataGeneratorConfig; + private ExistingFileHelper existingFileHelper; @SuppressWarnings("OptionalUsedAsFieldOrParameterType") private final Optional> statusConsumer = StartupMessageManager.modLoaderConsumer(); @@ -265,17 +267,18 @@ public class ModLoader this.loadingWarnings.add(warning); } - public void runDataGenerator(final Set mods, final Path path, final Collection inputs, final boolean serverGenerators, final boolean clientGenerators, final boolean devToolGenerators, final boolean reportsGenerator, final boolean structureValidator) { + public void runDataGenerator(final Set mods, final Path path, final Collection inputs, Collection existingPacks, final boolean serverGenerators, final boolean clientGenerators, final boolean devToolGenerators, final boolean reportsGenerator, final boolean structureValidator) { if (mods.contains("minecraft") && mods.size() == 1) return; LOGGER.info("Initializing Data Gatherer for mods {}", mods); Bootstrap.register(); dataGeneratorConfig = new GatherDataEvent.DataGeneratorConfig(mods, path, inputs, serverGenerators, clientGenerators, devToolGenerators, reportsGenerator, structureValidator); + existingFileHelper = new ExistingFileHelper(existingPacks, structureValidator); gatherAndInitializeMods(null); dispatchAndHandleError(LifecycleEventProvider.GATHERDATA, Runnable::run, null); dataGeneratorConfig.runAll(); } public Function getDataGeneratorEvent() { - return mc -> new GatherDataEvent(mc, dataGeneratorConfig.makeGenerator(p->dataGeneratorConfig.getMods().size() == 1 ? p : p.resolve(mc.getModId()), dataGeneratorConfig.getMods().contains(mc.getModId())), dataGeneratorConfig); + return mc -> new GatherDataEvent(mc, dataGeneratorConfig.makeGenerator(p->dataGeneratorConfig.getMods().size() == 1 ? p : p.resolve(mc.getModId()), dataGeneratorConfig.getMods().contains(mc.getModId())), dataGeneratorConfig, existingFileHelper); } } diff --git a/src/main/java/net/minecraftforge/fml/event/lifecycle/GatherDataEvent.java b/src/main/java/net/minecraftforge/fml/event/lifecycle/GatherDataEvent.java index f1c15a0d1..6a684eebd 100644 --- a/src/main/java/net/minecraftforge/fml/event/lifecycle/GatherDataEvent.java +++ b/src/main/java/net/minecraftforge/fml/event/lifecycle/GatherDataEvent.java @@ -21,6 +21,7 @@ package net.minecraftforge.fml.event.lifecycle; import cpw.mods.modlauncher.api.LamdbaExceptionUtils; import net.minecraft.data.DataGenerator; +import net.minecraftforge.client.model.generators.ExistingFileHelper; import net.minecraftforge.fml.ModContainer; import java.nio.file.Path; @@ -34,14 +35,17 @@ public class GatherDataEvent extends ModLifecycleEvent { private final DataGenerator dataGenerator; private final DataGeneratorConfig config; - public GatherDataEvent(final ModContainer modContainer, final DataGenerator dataGenerator, final DataGeneratorConfig dataGeneratorConfig) + private final ExistingFileHelper existingFileHelper; + public GatherDataEvent(final ModContainer modContainer, final DataGenerator dataGenerator, final DataGeneratorConfig dataGeneratorConfig, ExistingFileHelper existingFileHelper) { super(modContainer); this.dataGenerator = dataGenerator; this.config = dataGeneratorConfig; + this.existingFileHelper = existingFileHelper; } public DataGenerator getGenerator() { return this.dataGenerator; } + public ExistingFileHelper getExistingFileHelper() { return existingFileHelper; } public boolean includeServer() { return this.config.server; } public boolean includeClient() { return this.config.client; } public boolean includeDev() { return this.config.dev; } diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index 073a68f9f..05506af47 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -37,7 +37,11 @@ public net.minecraft.client.renderer.model.BlockModel field_178315_d # parent public net.minecraft.client.renderer.model.BlockModel field_178318_c # textures public net.minecraft.client.renderer.model.BlockModel field_178322_i # ambientOcclusion public net.minecraft.client.renderer.model.BlockModel func_187966_f()Ljava/util/List; # getOverrides +public net.minecraft.client.renderer.model.BlockPart func_178236_a(Lnet/minecraft/util/Direction;)[F # getFaceUvs protected net.minecraft.client.renderer.model.ItemOverrideList ()V +public net.minecraft.client.renderer.model.ItemTransformVec3f$Deserializer field_178362_a # ROTATION_DEFAULT +public net.minecraft.client.renderer.model.ItemTransformVec3f$Deserializer field_178360_b # TRANSLATION_DEFAULT +public net.minecraft.client.renderer.model.ItemTransformVec3f$Deserializer field_178361_c # SCALE_DEFAULT protected net.minecraft.client.renderer.model.ModelBakery field_177598_f # resourceManager protected net.minecraft.client.renderer.model.ModelBakery field_177602_b # LOCATIONS_BUILTIN_TEXTURES public net.minecraft.client.renderer.model.ModelBakery field_177604_a # MODEL_MISSING diff --git a/src/test/java/net/minecraftforge/debug/DataGeneratorTest.java b/src/test/java/net/minecraftforge/debug/DataGeneratorTest.java index 96c796ad5..78993ac8b 100644 --- a/src/test/java/net/minecraftforge/debug/DataGeneratorTest.java +++ b/src/test/java/net/minecraftforge/debug/DataGeneratorTest.java @@ -19,18 +19,72 @@ package net.minecraftforge.debug; +import static net.minecraftforge.debug.DataGeneratorTest.MODID; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.jline.utils.InputStreamReader; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Multimap; +import com.google.common.collect.ObjectArrays; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import net.minecraft.advancements.Advancement; import net.minecraft.advancements.AdvancementRewards; import net.minecraft.advancements.FrameType; +import net.minecraft.block.BarrelBlock; +import net.minecraft.block.Block; import net.minecraft.block.Blocks; +import net.minecraft.block.DoorBlock; +import net.minecraft.block.FenceBlock; +import net.minecraft.block.FenceGateBlock; +import net.minecraft.block.FurnaceBlock; +import net.minecraft.block.LogBlock; +import net.minecraft.block.PaneBlock; +import net.minecraft.block.SlabBlock; +import net.minecraft.block.StairsBlock; +import net.minecraft.block.TrapDoorBlock; +import net.minecraft.block.WallBlock; +import net.minecraft.client.renderer.model.ItemCameraTransforms; +import net.minecraft.client.renderer.model.ItemTransformVec3f; +import net.minecraft.client.renderer.model.Variant; import net.minecraft.data.DataGenerator; +import net.minecraft.data.DirectoryCache; import net.minecraft.data.IFinishedRecipe; import net.minecraft.data.RecipeProvider; import net.minecraft.data.ShapedRecipeBuilder; +import net.minecraft.resources.IResource; +import net.minecraft.resources.ResourcePackType; +import net.minecraft.util.Direction; import net.minecraft.util.ResourceLocation; import net.minecraft.util.text.StringTextComponent; +import net.minecraftforge.client.model.generators.BlockStateProvider; +import net.minecraftforge.client.model.generators.ConfiguredModel; +import net.minecraftforge.client.model.generators.ExistingFileHelper; +import net.minecraftforge.client.model.generators.ItemModelProvider; +import net.minecraftforge.client.model.generators.ModelBuilder; +import net.minecraftforge.client.model.generators.ModelBuilder.Perspective; +import net.minecraftforge.client.model.generators.ModelFile; +import net.minecraftforge.client.model.generators.ModelFile.UncheckedModelFile; +import net.minecraftforge.client.model.generators.MultiPartBlockStateBuilder; +import net.minecraftforge.client.model.generators.VariantBlockStateBuilder; import net.minecraftforge.common.crafting.ConditionalAdvancement; import net.minecraftforge.common.crafting.ConditionalRecipe; import net.minecraftforge.common.crafting.conditions.IConditionBuilder; @@ -39,15 +93,29 @@ import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod.EventBusSubscriber.Bus; import net.minecraftforge.fml.event.lifecycle.GatherDataEvent; -@Mod("data_gen_test") +@SuppressWarnings("deprecation") +@Mod(MODID) @Mod.EventBusSubscriber(bus = Bus.MOD) public class DataGeneratorTest { + static final String MODID = "data_gen_test"; + + private static final Gson GSON = new GsonBuilder() + .registerTypeAdapter(Variant.class, new Variant.Deserializer()) + .registerTypeAdapter(ItemCameraTransforms.class, new ItemCameraTransforms.Deserializer()) + .registerTypeAdapter(ItemTransformVec3f.class, new ItemTransformVec3f.Deserializer()) + .create(); + @SubscribeEvent public static void gatherData(GatherDataEvent event) { DataGenerator gen = event.getGenerator(); + if (event.includeClient()) + { + gen.addProvider(new ItemModels(gen, event.getExistingFileHelper())); + gen.addProvider(new BlockStates(gen, event.getExistingFileHelper())); + } if (event.includeServer()) { gen.addProvider(new Recipes(gen)); @@ -107,4 +175,438 @@ public class DataGeneratorTest .build(consumer, ID); } } + + public static class ItemModels extends ItemModelProvider + { + private static final Logger LOGGER = LogManager.getLogger(); + + public ItemModels(DataGenerator generator, ExistingFileHelper existingFileHelper) + { + super(generator, MODID, existingFileHelper); + } + + @Override + protected void registerModels() + { + getBuilder("test_generated_model") + .parent(new UncheckedModelFile("item/generated")) + .texture("layer0", mcLoc("block/stone")); + + getBuilder("test_block_model") + .parent(getExistingFile(mcLoc("block/block"))) + .texture("all", mcLoc("block/dirt")) + .texture("top", mcLoc("block/stone")) + .element() + .cube("#all") + .face(Direction.UP) + .texture("#top") + .tintindex(0) + .end() + .end(); + + // Testing consistency + + // Test overrides + ModelFile fishingRod = withExistingParent("fishing_rod", "handheld_rod") + .texture("layer0", mcLoc("item/fishing_rod")) + .override() + .predicate(mcLoc("cast"), 1) + .model(getExistingFile(mcLoc("item/fishing_rod_cast"))) // Use the vanilla model for validation + .end(); + + withExistingParent("fishing_rod_cast", modLoc("fishing_rod")) + .parent(fishingRod) + .texture("layer0", mcLoc("item/fishing_rod_cast")); + } + + private static final Set IGNORED_MODELS = ImmutableSet.of("test_generated_model", "test_block_model"); + + @Override + public void act(DirectoryCache cache) throws IOException + { + super.act(cache); + List errors = testModelResults(this.generatedModels, existingFileHelper, IGNORED_MODELS.stream().map(s -> new ResourceLocation(MODID, folder + "/" + s)).collect(Collectors.toSet())); + if (!errors.isEmpty()) { + LOGGER.error("Found {} discrepancies between generated and vanilla item models: ", errors.size()); + for (String s : errors) { + LOGGER.error(" {}", s); + } + throw new AssertionError("Generated item models differed from vanilla equivalents, check above errors."); + } + } + + @Override + public String getName() + { + return "Forge Test Item Models"; + } + } + + public static class BlockStates extends BlockStateProvider + { + private static final Logger LOGGER = LogManager.getLogger(); + + public BlockStates(DataGenerator gen, ExistingFileHelper exFileHelper) + { + super(gen, MODID, exFileHelper); + } + + @Override + protected void registerStatesAndModels() + { + // Unnecessarily complicated example to showcase how manual building works + ModelFile birchFenceGate = fenceGate("birch_fence_gate", mcLoc("block/birch_planks")); + ModelFile birchFenceGateOpen = fenceGateOpen("birch_fence_gate_open", mcLoc("block/birch_planks")); + ModelFile birchFenceGateWall = fenceGateWall("birch_fence_gate_wall", mcLoc("block/birch_planks")); + ModelFile birchFenceGateWallOpen = fenceGateWallOpen("birch_fence_gate_wall_open", mcLoc("block/birch_planks")); + ModelFile invisbleModel = new UncheckedModelFile(new ResourceLocation("builtin/generated")); + VariantBlockStateBuilder builder = getVariantBuilder(Blocks.BIRCH_FENCE_GATE); + for (Direction dir : FenceGateBlock.HORIZONTAL_FACING.getAllowedValues()) { + int angle = (int) dir.getHorizontalAngle(); + builder + .partialState() + .with(FenceGateBlock.HORIZONTAL_FACING, dir) + .with(FenceGateBlock.IN_WALL, false) + .with(FenceGateBlock.OPEN, false) + .modelForState() + .modelFile(invisbleModel) + .nextModel() + .modelFile(birchFenceGate) + .rotationY(angle) + .uvLock(true) + .weight(100) + .addModel() + .partialState() + .with(FenceGateBlock.HORIZONTAL_FACING, dir) + .with(FenceGateBlock.IN_WALL, false) + .with(FenceGateBlock.OPEN, true) + .modelForState() + .modelFile(birchFenceGateOpen) + .rotationY(angle) + .uvLock(true) + .addModel() + .partialState() + .with(FenceGateBlock.HORIZONTAL_FACING, dir) + .with(FenceGateBlock.IN_WALL, true) + .with(FenceGateBlock.OPEN, false) + .modelForState() + .modelFile(birchFenceGateWall) + .rotationY(angle) + .uvLock(true) + .addModel() + .partialState() + .with(FenceGateBlock.HORIZONTAL_FACING, dir) + .with(FenceGateBlock.IN_WALL, true) + .with(FenceGateBlock.OPEN, true) + .modelForState() + .modelFile(birchFenceGateWallOpen) + .rotationY(angle) + .uvLock(true) + .addModel(); + } + + // Realistic examples using helpers + simpleBlock(Blocks.STONE, model -> ObjectArrays.concat( + ConfiguredModel.allYRotations(model, 0, false), + ConfiguredModel.allYRotations(model, 180, false), + ConfiguredModel.class)); + + // From here on, models are 1-to-1 copies of vanilla (except for model locations) and will be tested as such below + ModelFile block = getBuilder("block").transforms() + .transform(Perspective.GUI) + .rotation(30, 225, 0) + .scale(0.625f) + .end() + .transform(Perspective.GROUND) + .translation(0, 3, 0) + .scale(0.25f) + .end() + .transform(Perspective.FIXED) + .scale(0.5f) + .end() + .transform(Perspective.THIRDPERSON_RIGHT) + .rotation(75, 45, 0) + .translation(0, 2.5f, 0) + .scale(0.375f) + .end() + .transform(Perspective.FIRSTPERSON_RIGHT) + .rotation(0, 45, 0) + .scale(0.4f) + .end() + .transform(Perspective.FIRSTPERSON_LEFT) + .rotation(0, 225, 0) + .scale(0.4f) + .end() + .end(); + + getBuilder("cube") + .parent(block) + .element() + .allFaces((dir, face) -> face.texture("#" + dir.getName()).cullface(dir)); + + ModelFile furnace = orientable("furnace", mcLoc("block/furnace_side"), mcLoc("block/furnace_front"), mcLoc("block/furnace_top")); + ModelFile furnaceLit = orientable("furnace_on", mcLoc("block/furnace_side"), mcLoc("block/furnace_front_on"), mcLoc("block/furnace_top")); + + getVariantBuilder(Blocks.FURNACE) + .forAllStates(state -> ConfiguredModel.builder() + .modelFile(state.get(FurnaceBlock.LIT) ? furnaceLit : furnace) + .rotationY((int) state.get(FurnaceBlock.FACING).getOpposite().getHorizontalAngle()) + .build() + ); + + ModelFile barrel = cubeBottomTop("barrel", mcLoc("block/barrel_side"), mcLoc("block/barrel_bottom"), mcLoc("block/barrel_top")); + ModelFile barrelOpen = cubeBottomTop("barrel_open", mcLoc("block/barrel_side"), mcLoc("block/barrel_bottom"), mcLoc("block/barrel_top_open")); + directionalBlock(Blocks.BARREL, state -> state.get(BarrelBlock.PROPERTY_OPEN) ? barrelOpen : barrel); // Testing custom state interpreter + + logBlock((LogBlock) Blocks.ACACIA_LOG); + + stairsBlock((StairsBlock) Blocks.ACACIA_STAIRS, "acacia", mcLoc("block/acacia_planks")); + slabBlock((SlabBlock) Blocks.ACACIA_SLAB, Blocks.ACACIA_PLANKS.getRegistryName(), mcLoc("block/acacia_planks")); + + fenceBlock((FenceBlock) Blocks.ACACIA_FENCE, "acacia", mcLoc("block/acacia_planks")); + fenceGateBlock((FenceGateBlock) Blocks.ACACIA_FENCE_GATE, "acacia", mcLoc("block/acacia_planks")); + + wallBlock((WallBlock) Blocks.COBBLESTONE_WALL, "cobblestone", mcLoc("block/cobblestone")); + + paneBlock((PaneBlock) Blocks.GLASS_PANE, "glass", mcLoc("block/glass"), mcLoc("block/glass_pane_top")); + + doorBlock((DoorBlock) Blocks.ACACIA_DOOR, "acacia", mcLoc("block/acacia_door_bottom"), mcLoc("block/acacia_door_top")); + trapdoorBlock((TrapDoorBlock) Blocks.ACACIA_TRAPDOOR, "acacia", mcLoc("block/acacia_trapdoor"), true); + trapdoorBlock((TrapDoorBlock) Blocks.OAK_TRAPDOOR, "oak", mcLoc("block/oak_trapdoor"), false); // Test a non-orientable trapdoor + + simpleBlock(Blocks.TORCH, torch("torch", mcLoc("block/torch"))); + horizontalBlock(Blocks.WALL_TORCH, torchWall("wall_torch", mcLoc("block/torch")), 90); + } + + // Testing the outputs + + private static final Set IGNORED_BLOCKS = ImmutableSet.of(Blocks.BIRCH_FENCE_GATE, Blocks.STONE); + private static final Set IGNORED_MODELS = ImmutableSet.of(); + + private List errors = new ArrayList<>(); + + @Override + public void act(DirectoryCache cache) throws IOException + { + super.act(cache); + this.errors.addAll(testModelResults(this.generatedModels, existingFileHelper, IGNORED_MODELS)); + this.registeredBlocks.forEach((block, state) -> { + if (IGNORED_BLOCKS.contains(block)) return; + JsonObject generated = state.toJson(); + try { + IResource vanillaResource = existingFileHelper.getResource(block.getRegistryName(), ResourcePackType.CLIENT_RESOURCES, ".json", "blockstates"); + JsonObject existing = GSON.fromJson(new InputStreamReader(vanillaResource.getInputStream()), JsonObject.class); + if (state instanceof VariantBlockStateBuilder) { + compareVariantBlockstates(block, generated, existing); + } else if (state instanceof MultiPartBlockStateBuilder) { + compareMultipartBlockstates(block, generated, existing); + } else { + throw new IllegalStateException("Unknown blockstate type: " + state.getClass()); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + + if (!errors.isEmpty()) { + LOGGER.error("Found {} discrepancies between generated and vanilla models/blockstates: ", errors.size()); + for (String s : errors) { + LOGGER.error(" {}", s); + } + throw new AssertionError("Generated blockstates/models differed from vanilla equivalents, check above errors."); + } + } + + private void compareVariantBlockstates(Block block, JsonObject generated, JsonObject vanilla) { + JsonObject generatedVariants = generated.getAsJsonObject("variants"); + JsonObject vanillaVariants = vanilla.getAsJsonObject("variants"); + Stream.concat(generatedVariants.entrySet().stream(), vanillaVariants.entrySet().stream()) + .map(e -> e.getKey()) + .distinct() + .forEach(key -> { + JsonElement generatedVariant = generatedVariants.get(key); + JsonElement vanillaVariant = vanillaVariants.get(key); + if (generatedVariant.isJsonArray()) { + compareArrays(block, "key " + key, "random variants", generatedVariant, vanillaVariant); + for (int i = 0; i < generatedVariant.getAsJsonArray().size(); i++) { + compareVariant(block, key + "[" + i + "]", generatedVariant.getAsJsonArray().get(i).getAsJsonObject(), vanillaVariant.getAsJsonArray().get(i).getAsJsonObject()); + } + } + if (generatedVariant.isJsonObject()) { + if (!vanillaVariant.isJsonObject()) { + blockstateError(block, "incorrectly does not have an array of variants for key %s", key); + return; + } + compareVariant(block, key, generatedVariant.getAsJsonObject(), vanillaVariant.getAsJsonObject()); + } + }); + } + + private void compareVariant(Block block, String key, JsonObject generatedVariant, JsonObject vanillaVariant) { + if (generatedVariant == null) { + blockstateError(block, "missing variant for %s", key); + return; + } + if (vanillaVariant == null) { + blockstateError(block, "has extra variant %s", key); + return; + } + String generatedModel = toVanillaModel(generatedVariant.get("model").getAsString()); + String vanillaModel = vanillaVariant.get("model").getAsString(); + if (!generatedModel.equals(vanillaModel)) { + blockstateError(block, "has incorrect model \"%s\" for variant %s. Expecting: %s", generatedModel, key, vanillaModel); + return; + } + generatedVariant.addProperty("model", generatedModel); + // Parse variants to objects to handle default values in vanilla jsons + Variant parsedGeneratedVariant = GSON.fromJson(generatedVariant, Variant.class); + Variant parsedVanillaVariant = GSON.fromJson(vanillaVariant, Variant.class); + if (!parsedGeneratedVariant.equals(parsedVanillaVariant)) { + blockstateError(block, "has incorrect variant %s. Expecting: %s, Found: %s", key, vanillaVariant, generatedVariant); + return; + } + } + + private void compareMultipartBlockstates(Block block, JsonObject generated, JsonObject vanilla) { + JsonElement generatedPartsElement = generated.get("multipart"); + JsonElement vanillaPartsElement = vanilla.getAsJsonArray("multipart"); + compareArrays(block, "parts", "multipart", generatedPartsElement, vanillaPartsElement); + // String instead of JSON types due to inconsistent hashing + Multimap generatedPartsByCondition = HashMultimap.create(); + Multimap vanillaPartsByCondition = HashMultimap.create(); + + JsonArray generatedParts = generatedPartsElement.getAsJsonArray(); + JsonArray vanillaParts = vanillaPartsElement.getAsJsonArray(); + for (int i = 0; i < generatedParts.size(); i++) { + JsonObject generatedPart = generatedParts.get(i).getAsJsonObject(); + String generatedCondition = toEquivalentString(generatedPart.get("when")); + JsonElement generatedVariants = generatedPart.get("apply"); + if (generatedVariants.isJsonObject()) { + correctVariant(generatedVariants.getAsJsonObject()); + } else if (generatedVariants.isJsonArray()) { + for (int j = 0; j < generatedVariants.getAsJsonArray().size(); j++) { + correctVariant(generatedVariants.getAsJsonArray().get(i).getAsJsonObject()); + } + } + generatedPartsByCondition.put(generatedCondition, toEquivalentString(generatedVariants)); + + JsonObject vanillaPart = vanillaParts.get(i).getAsJsonObject(); + String vanillaCondition = toEquivalentString(vanillaPart.get("when")); + String vanillaVariants = toEquivalentString(vanillaPart.get("apply")); + + vanillaPartsByCondition.put(vanillaCondition, vanillaVariants); + } + + Stream.concat(generatedPartsByCondition.keySet().stream(), vanillaPartsByCondition.keySet().stream()) + .distinct() + .forEach(cond -> { + Collection generatedVariants = generatedPartsByCondition.get(cond); + Collection vanillaVariants = vanillaPartsByCondition.get(cond); + if (generatedVariants.size() != vanillaVariants.size()) { + if (vanillaVariants.isEmpty()) { + blockstateError(block, " has extra condition %s", cond); + } else if (generatedVariants.isEmpty()) { + blockstateError(block, " is missing condition %s", cond); + } else { + blockstateError(block, " has differing amounts of variant lists matching condition %s. Expected: %d, Found: %d", cond, vanillaVariants.size(), generatedVariants.size()); + } + return; + } + + if (!vanillaVariants.containsAll(generatedVariants) || !generatedVariants.containsAll(vanillaVariants)) { + List extra = new ArrayList<>(generatedVariants); + extra.removeAll(vanillaVariants); + List missing = new ArrayList<>(vanillaVariants); + missing.removeAll(generatedVariants); + if (!extra.isEmpty()) { + blockstateError(block, " has extra variants for condition %s: %s", cond, extra); + } + if (!missing.isEmpty()) { + blockstateError(block, " has missing variants for condition %s: %s", cond, missing); + } + } + }); + } + + // Eliminate some formatting differences that are not meaningful + private String toEquivalentString(JsonElement element) { + return Objects.toString(element) + .replaceAll("\"(true|false)\"", "$1") // Unwrap booleans in strings + .replaceAll("\"(-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?)\"", "$1"); // Unwrap numbers in strings, regex from https://stackoverflow.com/questions/13340717/json-numbers-regular-expression + } + + private void correctVariant(JsonObject variant) { + variant.addProperty("model", toVanillaModel(variant.get("model").getAsString())); + } + + private boolean compareArrays(Block block, String key, String name, JsonElement generated, JsonElement vanilla) { + if (!vanilla.isJsonArray()) { + blockstateError(block, "incorrectly has an array of %s for %s", name, key); + return false; + } + JsonArray generatedArray = generated.getAsJsonArray(); + JsonArray vanillaArray = vanilla.getAsJsonArray(); + if (generatedArray.size() != vanillaArray.size()) { + blockstateError(block, "has incorrect number of %s for %s. Expecting: %s, Found: %s", name, key, vanillaArray.size(), generatedArray.size()); + return false; + } + return true; + } + + private void blockstateError(Block block, String fmt, Object... args) { + errors.add("Generated blockstate for block " + block + " " + String.format(fmt, args)); + } + + @Override + public String getName() { + return "Forge Test Blockstates"; + } + } + + private static > List testModelResults(Map models, ExistingFileHelper existingFileHelper, Set toIgnore) { + List ret = new ArrayList<>(); + models.forEach((loc, model) -> { + if (toIgnore.contains(loc)) return; + JsonObject generated = model.toJson(); + if (generated.has("parent")) { + generated.addProperty("parent", toVanillaModel(generated.get("parent").getAsString())); + } + try { + IResource vanillaResource = existingFileHelper.getResource(new ResourceLocation(loc.getPath()), ResourcePackType.CLIENT_RESOURCES, ".json", "models"); + JsonObject existing = GSON.fromJson(new InputStreamReader(vanillaResource.getInputStream()), JsonObject.class); + + JsonElement generatedDisplay = generated.remove("display"); + JsonElement vanillaDisplay = existing.remove("display"); + if (generatedDisplay == null && vanillaDisplay != null) { + ret.add("Model " + loc + " is missing transforms"); + return; + } else if (generatedDisplay != null && vanillaDisplay == null) { + ret.add("Model " + loc + " has transforms when vanilla equivalent does not"); + return; + } else if (generatedDisplay != null) { // Both must be non-null + ItemCameraTransforms generatedTransforms = GSON.fromJson(generatedDisplay, ItemCameraTransforms.class); + ItemCameraTransforms vanillaTransforms = GSON.fromJson(vanillaDisplay, ItemCameraTransforms.class); + for (Perspective type : Perspective.values()) { + if (!generatedTransforms.getTransform(type.vanillaType).equals(vanillaTransforms.getTransform(type.vanillaType))) { + ret.add("Model " + loc + " has transforms that differ from vanilla equivalent for perspective " + type.name()); + return; + } + } + } + + if (!existing.equals(generated)) { + ret.add("Model " + loc + " does not match vanilla equivalent"); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + return ret; + } + + private static String toVanillaModel(String model) { + // We generate our own model jsons to test model building, but otherwise our blockstates should be identical + // So remove modid to match + return model.replaceAll("^\\w+:", ""); + } }