2019-08-03 17:25:41 +00:00
/ *
* 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.debug ;
2019-10-25 02:33:24 +00:00
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 ;
2019-08-03 17:25:41 +00:00
import java.util.function.Consumer ;
2019-10-25 02:33:24 +00:00
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 ;
2019-08-03 17:25:41 +00:00
import net.minecraft.advancements.Advancement ;
import net.minecraft.advancements.AdvancementRewards ;
import net.minecraft.advancements.FrameType ;
2019-10-25 02:33:24 +00:00
import net.minecraft.block.BarrelBlock ;
import net.minecraft.block.Block ;
2019-08-03 17:25:41 +00:00
import net.minecraft.block.Blocks ;
2019-10-25 02:33:24 +00:00
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 ;
2019-08-03 17:25:41 +00:00
import net.minecraft.data.DataGenerator ;
2019-10-25 02:33:24 +00:00
import net.minecraft.data.DirectoryCache ;
2019-08-03 17:25:41 +00:00
import net.minecraft.data.IFinishedRecipe ;
import net.minecraft.data.RecipeProvider ;
import net.minecraft.data.ShapedRecipeBuilder ;
2019-10-25 02:33:24 +00:00
import net.minecraft.resources.IResource ;
import net.minecraft.resources.ResourcePackType ;
import net.minecraft.util.Direction ;
2019-08-03 17:25:41 +00:00
import net.minecraft.util.ResourceLocation ;
import net.minecraft.util.text.StringTextComponent ;
2019-10-25 02:33:24 +00:00
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 ;
2019-08-03 17:25:41 +00:00
import net.minecraftforge.common.crafting.ConditionalAdvancement ;
import net.minecraftforge.common.crafting.ConditionalRecipe ;
import net.minecraftforge.common.crafting.conditions.IConditionBuilder ;
import net.minecraftforge.eventbus.api.SubscribeEvent ;
import net.minecraftforge.fml.common.Mod ;
import net.minecraftforge.fml.common.Mod.EventBusSubscriber.Bus ;
import net.minecraftforge.fml.event.lifecycle.GatherDataEvent ;
2019-10-25 02:33:24 +00:00
@SuppressWarnings ( " deprecation " )
@Mod ( MODID )
2019-08-03 17:25:41 +00:00
@Mod.EventBusSubscriber ( bus = Bus . MOD )
public class DataGeneratorTest
{
2019-10-25 02:33:24 +00:00
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 ( ) ;
2019-08-03 17:25:41 +00:00
@SubscribeEvent
public static void gatherData ( GatherDataEvent event )
{
DataGenerator gen = event . getGenerator ( ) ;
2019-10-25 02:33:24 +00:00
if ( event . includeClient ( ) )
{
gen . addProvider ( new ItemModels ( gen , event . getExistingFileHelper ( ) ) ) ;
gen . addProvider ( new BlockStates ( gen , event . getExistingFileHelper ( ) ) ) ;
}
2019-08-03 17:25:41 +00:00
if ( event . includeServer ( ) )
{
gen . addProvider ( new Recipes ( gen ) ) ;
}
}
public static class Recipes extends RecipeProvider implements IConditionBuilder
{
public Recipes ( DataGenerator gen )
{
super ( gen ) ;
}
protected void registerRecipes ( Consumer < IFinishedRecipe > consumer )
{
ResourceLocation ID = new ResourceLocation ( " data_gen_test " , " conditional " ) ;
ConditionalRecipe . builder ( )
. addCondition (
and (
not ( modLoaded ( " minecraft " ) ) ,
itemExists ( " minecraft " , " dirt " ) ,
FALSE ( )
)
)
. addRecipe (
ShapedRecipeBuilder . shapedRecipe ( Blocks . DIAMOND_BLOCK , 64 )
. patternLine ( " XXX " )
. patternLine ( " XXX " )
. patternLine ( " XXX " )
. key ( 'X' , Blocks . DIRT )
. setGroup ( " " )
. addCriterion ( " has_dirt " , hasItem ( Blocks . DIRT ) ) //Doesn't actually print... TODO: nested/conditional advancements?
: : build
)
. setAdvancement ( ID ,
ConditionalAdvancement . builder ( )
. addCondition (
and (
not ( modLoaded ( " minecraft " ) ) ,
itemExists ( " minecraft " , " dirt " ) ,
FALSE ( )
)
)
. addAdvancement (
Advancement . Builder . builder ( )
. withParentId ( new ResourceLocation ( " minecraft " , " root " ) )
. withDisplay ( Blocks . DIAMOND_BLOCK ,
new StringTextComponent ( " Dirt2Diamonds " ) ,
new StringTextComponent ( " The BEST crafting recipe in the game! " ) ,
null , FrameType . TASK , false , false , false
)
. withRewards ( AdvancementRewards . Builder . recipe ( ID ) )
. withCriterion ( " has_dirt " , hasItem ( Blocks . DIRT ) )
)
)
. build ( consumer , ID ) ;
}
}
2019-10-25 02:33:24 +00:00
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+: " , " " ) ;
}
2019-08-03 17:25:41 +00:00
}