Add Blockstate and Model data providers (#6241)

This commit is contained in:
tterrag 2019-10-24 22:33:24 -04:00 committed by GitHub
parent 3bf6c17bb8
commit acaa470dea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
82 changed files with 4765 additions and 10 deletions

View File

@ -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]
}
}
}

View File

@ -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<Void> optionspec6 = optionparser.accepts("all", "Include all generators");
OptionSpec<String> optionspec7 = optionparser.accepts("output", "Output folder").withRequiredArg().defaultsTo("generated");
OptionSpec<String> optionspec8 = optionparser.accepts("input", "Input folder").withRequiredArg();
+ OptionSpec<String> existing = optionparser.accepts("existing", "Existing resource packs that generated resources can reference").withRequiredArg();
+ OptionSpec<File> gameDir = optionparser.accepts("gameDir").withRequiredArg().ofType(File.class).defaultsTo(new File(".")).required();
+ OptionSpec<String> 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<Path> inputs = optionset.valuesOf(optionspec8).stream().map(Paths::get).collect(Collectors.toList());
+ Collection<Path> existingPacks = optionset.valuesOf(existing).stream().map(Paths::get).collect(Collectors.toList());
+ java.util.Set<String> 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 {

View File

@ -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

View File

@ -0,0 +1,7 @@
{
"parent": "block/door_bottom",
"textures": {
"bottom": "block/acacia_door_bottom",
"top": "block/acacia_door_top"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "block/door_bottom_rh",
"textures": {
"bottom": "block/acacia_door_bottom",
"top": "block/acacia_door_top"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "block/door_top",
"textures": {
"bottom": "block/acacia_door_bottom",
"top": "block/acacia_door_top"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "block/door_top_rh",
"textures": {
"bottom": "block/acacia_door_bottom",
"top": "block/acacia_door_top"
}
}

View File

@ -0,0 +1,6 @@
{
"parent": "block/template_fence_gate",
"textures": {
"texture": "block/acacia_planks"
}
}

View File

@ -0,0 +1,6 @@
{
"parent": "block/template_fence_gate_open",
"textures": {
"texture": "block/acacia_planks"
}
}

View File

@ -0,0 +1,6 @@
{
"parent": "block/template_fence_gate_wall",
"textures": {
"texture": "block/acacia_planks"
}
}

View File

@ -0,0 +1,6 @@
{
"parent": "block/template_fence_gate_wall_open",
"textures": {
"texture": "block/acacia_planks"
}
}

View File

@ -0,0 +1,6 @@
{
"parent": "block/fence_post",
"textures": {
"texture": "block/acacia_planks"
}
}

View File

@ -0,0 +1,6 @@
{
"parent": "block/fence_side",
"textures": {
"texture": "block/acacia_planks"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "block/cube_column",
"textures": {
"side": "block/acacia_log",
"end": "block/acacia_log_top"
}
}

View File

@ -0,0 +1,8 @@
{
"parent": "block/slab",
"textures": {
"side": "block/acacia_planks",
"bottom": "block/acacia_planks",
"top": "block/acacia_planks"
}
}

View File

@ -0,0 +1,8 @@
{
"parent": "block/slab_top",
"textures": {
"side": "block/acacia_planks",
"bottom": "block/acacia_planks",
"top": "block/acacia_planks"
}
}

View File

@ -0,0 +1,8 @@
{
"parent": "block/stairs",
"textures": {
"side": "block/acacia_planks",
"bottom": "block/acacia_planks",
"top": "block/acacia_planks"
}
}

View File

@ -0,0 +1,8 @@
{
"parent": "block/inner_stairs",
"textures": {
"side": "block/acacia_planks",
"bottom": "block/acacia_planks",
"top": "block/acacia_planks"
}
}

View File

@ -0,0 +1,8 @@
{
"parent": "block/outer_stairs",
"textures": {
"side": "block/acacia_planks",
"bottom": "block/acacia_planks",
"top": "block/acacia_planks"
}
}

View File

@ -0,0 +1,6 @@
{
"parent": "block/template_orientable_trapdoor_bottom",
"textures": {
"texture": "block/acacia_trapdoor"
}
}

View File

@ -0,0 +1,6 @@
{
"parent": "block/template_orientable_trapdoor_open",
"textures": {
"texture": "block/acacia_trapdoor"
}
}

View File

@ -0,0 +1,6 @@
{
"parent": "block/template_orientable_trapdoor_top",
"textures": {
"texture": "block/acacia_trapdoor"
}
}

View File

@ -0,0 +1,8 @@
{
"parent": "block/cube_bottom_top",
"textures": {
"side": "block/barrel_side",
"bottom": "block/barrel_bottom",
"top": "block/barrel_top"
}
}

View File

@ -0,0 +1,8 @@
{
"parent": "block/cube_bottom_top",
"textures": {
"side": "block/barrel_side",
"bottom": "block/barrel_bottom",
"top": "block/barrel_top_open"
}
}

View File

@ -0,0 +1,6 @@
{
"parent": "block/template_fence_gate",
"textures": {
"texture": "block/birch_planks"
}
}

View File

@ -0,0 +1,6 @@
{
"parent": "block/template_fence_gate_open",
"textures": {
"texture": "block/birch_planks"
}
}

View File

@ -0,0 +1,6 @@
{
"parent": "block/template_fence_gate_wall",
"textures": {
"texture": "block/birch_planks"
}
}

View File

@ -0,0 +1,6 @@
{
"parent": "block/template_fence_gate_wall_open",
"textures": {
"texture": "block/birch_planks"
}
}

View File

@ -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
]
}
}
}

View File

@ -0,0 +1,6 @@
{
"parent": "block/template_wall_post",
"textures": {
"wall": "block/cobblestone"
}
}

View File

@ -0,0 +1,6 @@
{
"parent": "block/template_wall_side",
"textures": {
"wall": "block/cobblestone"
}
}

View File

@ -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"
}
}
}
]
}

View File

@ -0,0 +1,8 @@
{
"parent": "block/orientable",
"textures": {
"side": "block/furnace_side",
"front": "block/furnace_front",
"top": "block/furnace_top"
}
}

View File

@ -0,0 +1,8 @@
{
"parent": "block/orientable",
"textures": {
"side": "block/furnace_side",
"front": "block/furnace_front_on",
"top": "block/furnace_top"
}
}

View File

@ -0,0 +1,6 @@
{
"parent": "block/template_glass_pane_noside",
"textures": {
"pane": "block/glass"
}
}

View File

@ -0,0 +1,6 @@
{
"parent": "block/template_glass_pane_noside_alt",
"textures": {
"pane": "block/glass"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "block/template_glass_pane_post",
"textures": {
"pane": "block/glass",
"edge": "block/glass_pane_top"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "block/template_glass_pane_side",
"textures": {
"pane": "block/glass",
"edge": "block/glass_pane_top"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "block/template_glass_pane_side_alt",
"textures": {
"pane": "block/glass",
"edge": "block/glass_pane_top"
}
}

View File

@ -0,0 +1,6 @@
{
"parent": "block/template_trapdoor_bottom",
"textures": {
"texture": "block/oak_trapdoor"
}
}

View File

@ -0,0 +1,6 @@
{
"parent": "block/template_trapdoor_open",
"textures": {
"texture": "block/oak_trapdoor"
}
}

View File

@ -0,0 +1,6 @@
{
"parent": "block/template_trapdoor_top",
"textures": {
"texture": "block/oak_trapdoor"
}
}

View File

@ -0,0 +1,6 @@
{
"parent": "block/cube_all",
"textures": {
"all": "block/stone"
}
}

View File

@ -0,0 +1,6 @@
{
"parent": "block/template_torch",
"textures": {
"torch": "block/torch"
}
}

View File

@ -0,0 +1,6 @@
{
"parent": "block/torch_wall",
"textures": {
"torch": "block/torch"
}
}

View File

@ -0,0 +1,14 @@
{
"parent": "item/handheld_rod",
"textures": {
"layer0": "item/fishing_rod"
},
"overrides": [
{
"predicate": {
"cast": 1.0
},
"model": "item/fishing_rod_cast"
}
]
}

View File

@ -0,0 +1,6 @@
{
"parent": "data_gen_test:item/fishing_rod",
"textures": {
"layer0": "item/fishing_rod_cast"
}
}

View File

@ -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"
}
}
}
]
}

View File

@ -0,0 +1,6 @@
{
"parent": "item/generated",
"textures": {
"layer0": "block/stone"
}
}

View File

@ -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
}
}
}

View File

@ -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
}
}
]
}

View File

@ -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
}
}
}

View File

@ -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
}
}
}

View File

@ -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"
}
}
}

View File

@ -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"
}
}
}

View File

@ -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
}
}
}

View File

@ -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
}
}
}

View File

@ -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
}
}
}

View File

@ -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
}
}
]
}

View File

@ -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
}
}
}

View File

@ -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"
}
}
]
}

View File

@ -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
}
}
}

View File

@ -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
}
]
}
}

View File

@ -0,0 +1,7 @@
{
"variants": {
"": {
"model": "data_gen_test:block/torch"
}
}
}

View File

@ -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
}
}
}

View File

@ -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<BlockModelBuilder> {
public BlockModelBuilder(ResourceLocation outputLocation, ExistingFileHelper existingFileHelper) {
super(outputLocation, existingFileHelper);
}
}

View File

@ -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<BlockModelBuilder> {
public BlockModelProvider(DataGenerator generator, String modid, ExistingFileHelper existingFileHelper) {
super(generator, modid, BLOCK_FOLDER, BlockModelBuilder::new, existingFileHelper);
}
}

View File

@ -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<Block, IGeneratedBlockstate> 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<Block, IGeneratedBlockstate> 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<ModelFile, ConfiguredModel[]> 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<BlockState, ModelFile> modelFunc) {
horizontalBlock(block, modelFunc, DEFAULT_ANGLE_OFFSET);
}
protected void horizontalBlock(Block block, Function<BlockState, ModelFile> 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<BlockState, ModelFile> modelFunc) {
horizontalBlock(block, modelFunc, DEFAULT_ANGLE_OFFSET);
}
protected void horizontalFaceBlock(Block block, Function<BlockState, ModelFile> 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<BlockState, ModelFile> modelFunc) {
directionalBlock(block, modelFunc, DEFAULT_ANGLE_OFFSET);
}
protected void directionalBlock(Block block, Function<BlockState, ModelFile> 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<ConfiguredModel> models;
private ConfiguredModelList(List<ConfiguredModel> 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.<ConfiguredModel>builder().addAll(this.models).add(models).build());
}
}
}

View File

@ -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.
* <p>
* 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<VariantBlockStateBuilder> builder(VariantBlockStateBuilder outer, VariantBlockStateBuilder.PartialBlockstate state) {
return new Builder<>(models -> outer.setModels(state, models), ImmutableList.of());
}
static Builder<PartBuilder> builder(MultiPartBlockStateBuilder outer) {
return new Builder<PartBuilder>(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.
* <p>
* Multiple models can be configured at once through the use of
* {@link #nextModel()}.
*
* @param <T> the type of the owning builder, which supplied the callback, and
* will be returned upon completion.
*/
public static class Builder<T> {
private ModelFile model;
@Nullable
private final Function<ConfiguredModel[], T> callback;
private final List<ConfiguredModel> otherModels;
private int rotationX;
private int rotationY;
private boolean uvLock;
private int weight = DEFAULT_WEIGHT;
Builder() {
this(null, ImmutableList.of());
}
Builder(@Nullable Function<ConfiguredModel[], T> callback, List<ConfiguredModel> 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<T> 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<T> 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<T> rotationY(int value) {
checkRotation(rotationX, value);
rotationY = value;
return this;
}
public Builder<T> 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<T> 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.
* <p>
* Known callbacks include:
* <ul>
* <li>{@link PartialBlockstate#modelForState()}</li>
* <li>{@link MultiPartBlockStateBuilder#part()}</li>
* </ul>
*
* @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<T> nextModel() {
return new Builder<>(callback, Arrays.asList(build()));
}
}
}

View File

@ -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<Path> 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;
}
}

View File

@ -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();
}

View File

@ -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<ItemModelBuilder> {
protected List<OverrideBuilder> 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<ResourceLocation, Float> 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;
}
}
}

View File

@ -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<ItemModelBuilder> {
public ItemModelProvider(DataGenerator generator, String modid, ExistingFileHelper existingFileHelper) {
super(generator, modid, ITEM_FOLDER, ItemModelBuilder::new, existingFileHelper);
}
}

View File

@ -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 <T> Self type, for simpler chaining of methods.
*/
@SuppressWarnings("deprecation")
public class ModelBuilder<T extends ModelBuilder<T>> extends ModelFile {
@Nullable
protected ModelFile parent;
protected final Map<String, String> textures = new LinkedHashMap<>();
protected final TransformsBuilder transforms = new TransformsBuilder();
protected final ExistingFileHelper existingFileHelper;
protected boolean ambientOcclusion = true;
protected boolean gui3d = false;
protected final List<ElementBuilder> 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<Perspective, ItemTransformVec3f> transforms = this.transforms.build();
if (!transforms.isEmpty()) {
JsonObject display = new JsonObject();
for (Entry<Perspective, ItemTransformVec3f> 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<String, String> 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<Direction, FaceBuilder> 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 <em>possible</em> 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<Direction, FaceBuilder> action) {
Arrays.stream(Direction.values())
.forEach(d -> action.accept(d, face(d)));
return this;
}
/**
* Modify all <em>existing</em> 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<Direction, FaceBuilder> action) {
faces.entrySet().stream()
.forEach(e -> action.accept(e.getKey(), e.getValue()));
return this;
}
/**
* Texture all <em>possible</em> 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 <em>existing</em> 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<Direction, FaceBuilder> addTexture(String texture) {
return ($, f) -> f.texture(texture);
}
BlockPart build() {
Map<Direction, BlockPartFace> 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<Perspective, TransformVecBuilder> 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<Perspective, ItemTransformVec3f> 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; }
}
}
}

View File

@ -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");
}
}
}

View File

@ -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<T extends ModelBuilder<T>> 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<ResourceLocation, T> factory;
@VisibleForTesting
protected final Map<ResourceLocation, T> generatedModels = new HashMap<>();
protected final ExistingFileHelper existingFileHelper;
protected DirectoryCache cache;
protected abstract void registerModels();
public ModelProvider(DataGenerator generator, String modid, String folder, Function<ResourceLocation, T> 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<ResourceLocation, ExistingFileHelper, T> 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");
}
}

View File

@ -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<PartBuilder> 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<PartBuilder> 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<IProperty<?>, 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 <T> 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 <T extends Comparable<T>> PartBuilder condition(IProperty<T> 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<IProperty<?>, Collection<Comparable<?>>> 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());
}
}
}

View File

@ -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)}.
* <p>
* 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.
* <p>
* 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<PartialBlockstate, ConfiguredModelList> models = new LinkedHashMap<>();
private final Set<BlockState> coveredStates = new HashSet<>();
VariantBlockStateBuilder(Block owner) {
this.owner = owner;
}
public Map<PartialBlockstate, ConfiguredModelList> getModels() {
return models;
}
public Block getOwner() {
return owner;
}
@Override
public JsonObject toJson() {
List<BlockState> 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<BlockState, ConfiguredModel[]> mapper) {
return forAllStatesExcept(mapper);
}
public VariantBlockStateBuilder forAllStatesExcept(Function<BlockState, ConfiguredModel[]> mapper, IProperty<?>... ignored) {
Set<PartialBlockstate> seen = new HashSet<>();
for (BlockState fullState : owner.getStateContainer().getValidStates()) {
Map<IProperty<?>, 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<BlockState> {
private final Block owner;
private final SortedMap<IProperty<?>, Comparable<?>> setStates;
@Nullable
private final VariantBlockStateBuilder outerBuilder;
PartialBlockstate(Block owner, @Nullable VariantBlockStateBuilder outerBuilder) {
this(owner, ImmutableMap.of(), outerBuilder);
}
PartialBlockstate(Block owner, Map<IProperty<?>, Comparable<?>> setStates, @Nullable VariantBlockStateBuilder outerBuilder) {
this.owner = owner;
this.outerBuilder = outerBuilder;
for (Map.Entry<IProperty<?>, 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 <T extends Comparable<T>> PartialBlockstate with(IProperty<T> prop, T value) {
Preconditions.checkArgument(!setStates.containsKey(prop), "Property %s has already been set", prop);
Map<IProperty<?>, 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<VariantBlockStateBuilder> 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<IProperty<?>, Comparable<?>> getSetStates() {
return setStates;
}
@Override
public boolean test(BlockState blockState) {
if (blockState.getBlock() != getOwner()) {
return false;
}
for (Map.Entry<IProperty<?>, 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<IProperty<?>, 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<PartialBlockstate> comparingByProperties() {
// Sort variants inversely by property values, to approximate vanilla style
return (s1, s2) -> {
SortedSet<IProperty<?>> 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;
};
}
}
}

View File

@ -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<ModLoadingException> loadingExceptions;
private final List<ModLoadingWarning> loadingWarnings;
private GatherDataEvent.DataGeneratorConfig dataGeneratorConfig;
private ExistingFileHelper existingFileHelper;
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private final Optional<Consumer<String>> statusConsumer = StartupMessageManager.modLoaderConsumer();
@ -265,17 +267,18 @@ public class ModLoader
this.loadingWarnings.add(warning);
}
public void runDataGenerator(final Set<String> mods, final Path path, final Collection<Path> inputs, final boolean serverGenerators, final boolean clientGenerators, final boolean devToolGenerators, final boolean reportsGenerator, final boolean structureValidator) {
public void runDataGenerator(final Set<String> mods, final Path path, final Collection<Path> inputs, Collection<Path> 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<ModContainer, ModLifecycleEvent> 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);
}
}

View File

@ -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; }

View File

@ -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 <init>()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

View File

@ -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<String> IGNORED_MODELS = ImmutableSet.of("test_generated_model", "test_block_model");
@Override
public void act(DirectoryCache cache) throws IOException
{
super.act(cache);
List<String> 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<Block> IGNORED_BLOCKS = ImmutableSet.of(Blocks.BIRCH_FENCE_GATE, Blocks.STONE);
private static final Set<ResourceLocation> IGNORED_MODELS = ImmutableSet.of();
private List<String> 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<String, String> generatedPartsByCondition = HashMultimap.create();
Multimap<String, String> 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<String> generatedVariants = generatedPartsByCondition.get(cond);
Collection<String> 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<String> extra = new ArrayList<>(generatedVariants);
extra.removeAll(vanillaVariants);
List<String> 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 <T extends ModelBuilder<T>> List<String> testModelResults(Map<ResourceLocation, T> models, ExistingFileHelper existingFileHelper, Set<ResourceLocation> toIgnore) {
List<String> 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+:", "");
}
}