Added item models for facades.

This commit is contained in:
Sebastian Hartte 2016-08-28 12:10:40 +02:00
parent 77cb3d8b92
commit 5313d61490
9 changed files with 373 additions and 19 deletions

View file

@ -0,0 +1,99 @@
package appeng.client.render;
import java.util.Collections;
import java.util.List;
import javax.annotation.Nullable;
import net.minecraft.block.Block;
import net.minecraft.block.state.IBlockState;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.renderer.block.model.IBakedModel;
import net.minecraft.client.renderer.block.model.ItemCameraTransforms;
import net.minecraft.client.renderer.block.model.ItemOverrideList;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumFacing;
import net.minecraft.world.World;
import appeng.items.parts.ItemFacade;
/**
* This baked model class is used as a dispatcher to redirect the renderer to the *real* model that should be used based on the item stack.
* A custom Item Override List is used to accomplish this.
*/
public class FacadeDispatcherBakedModel implements IBakedModel
{
private final IBakedModel baseModel;
public FacadeDispatcherBakedModel( IBakedModel baseModel )
{
this.baseModel = baseModel;
}
// This is never used. See the item override list below.
@Override
public List<BakedQuad> getQuads( @Nullable IBlockState state, @Nullable EnumFacing side, long rand )
{
return Collections.emptyList();
}
@Override
public boolean isAmbientOcclusion()
{
return baseModel.isAmbientOcclusion();
}
@Override
public boolean isGui3d()
{
return baseModel.isGui3d();
}
@Override
public boolean isBuiltInRenderer()
{
return false;
}
@Override
public TextureAtlasSprite getParticleTexture()
{
return baseModel.getParticleTexture();
}
@Override
public ItemCameraTransforms getItemCameraTransforms()
{
return baseModel.getItemCameraTransforms();
}
@Override
public ItemOverrideList getOverrides()
{
return new ItemOverrideList( Collections.emptyList() )
{
@Override
public IBakedModel handleItemState( IBakedModel originalModel, ItemStack stack, World world, EntityLivingBase entity )
{
if( !( stack.getItem() instanceof ItemFacade ) )
{
return originalModel;
}
ItemFacade itemFacade = (ItemFacade) stack.getItem();
Block block = itemFacade.getBlock( stack );
int meta = itemFacade.getMeta( stack );
// This is kinda fascinating, how do we get the meta from the itemblock
IBlockState state = block.getStateFromMeta( meta );
return new FacadeWithBlockBakedModel( baseModel, state );
}
};
}
}

View file

@ -0,0 +1,66 @@
package appeng.client.render;
import java.util.Collection;
import java.util.Collections;
import com.google.common.base.Function;
import net.minecraft.client.renderer.block.model.IBakedModel;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.client.model.IModel;
import net.minecraftforge.client.model.ModelLoaderRegistry;
import net.minecraftforge.common.model.IModelState;
import net.minecraftforge.common.model.TRSRTransformation;
import appeng.core.AppEng;
/**
* The model class for facades. Since facades wrap existing models, they don't declare any dependencies here other
* than the cable anchor.
*/
public class FacadeItemModel implements IModel
{
// We use this to get the default item transforms and make our lives easier
private static final ResourceLocation MODEL_BASE = new ResourceLocation( AppEng.MOD_ID, "item/facade_base" );
@Override
public Collection<ResourceLocation> getDependencies()
{
return Collections.emptyList();
}
@Override
public Collection<ResourceLocation> getTextures()
{
return Collections.emptyList();
}
@Override
public IBakedModel bake( IModelState state, VertexFormat format, Function<ResourceLocation, TextureAtlasSprite> bakedTextureGetter )
{
IModel baseModel;
try
{
baseModel = ModelLoaderRegistry.getModel( MODEL_BASE );
}
catch( Exception e )
{
throw new RuntimeException( e );
}
IBakedModel bakedBaseModel = baseModel.bake( state, format, bakedTextureGetter );
return new FacadeDispatcherBakedModel( bakedBaseModel );
}
@Override
public IModelState getDefaultState()
{
return TRSRTransformation.identity();
}
}

View file

@ -0,0 +1,86 @@
package appeng.client.render;
import java.util.List;
import javax.annotation.Nullable;
import net.minecraft.block.state.IBlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.renderer.block.model.IBakedModel;
import net.minecraft.client.renderer.block.model.ItemCameraTransforms;
import net.minecraft.client.renderer.block.model.ItemOverrideList;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.util.EnumFacing;
/**
* This is the actual baked model that will combine the north face of a given block state
* with the base facade item model to achieve what is then actually rendered on screen.
*/
public class FacadeWithBlockBakedModel implements IBakedModel
{
private final IBakedModel baseModel;
private final IBlockState blockState;
private final IBakedModel textureModel;
public FacadeWithBlockBakedModel( IBakedModel baseModel, IBlockState blockState )
{
this.baseModel = baseModel;
this.blockState = blockState;
this.textureModel = Minecraft.getMinecraft().getBlockRendererDispatcher().getModelForState( blockState );
}
@Override
public List<BakedQuad> getQuads( @Nullable IBlockState state, @Nullable EnumFacing side, long rand )
{
// Only the north side is actually read from the base model for item models
if( side == EnumFacing.NORTH )
{
return textureModel.getQuads( blockState, side, rand );
}
else
{
return baseModel.getQuads( state, side, rand );
}
}
@Override
public boolean isAmbientOcclusion()
{
return baseModel.isAmbientOcclusion();
}
@Override
public boolean isGui3d()
{
return baseModel.isGui3d();
}
@Override
public boolean isBuiltInRenderer()
{
return false;
}
@Override
public TextureAtlasSprite getParticleTexture()
{
return baseModel.getParticleTexture();
}
@Override
public ItemCameraTransforms getItemCameraTransforms()
{
return baseModel.getItemCameraTransforms();
}
@Override
public ItemOverrideList getOverrides()
{
return ItemOverrideList.NONE;
}
}

View file

@ -65,6 +65,7 @@ import appeng.core.localization.PlayerMessages;
import appeng.core.stats.PlayerStatsRegistration;
import appeng.hooks.TickHandler;
import appeng.items.materials.ItemMultiItem;
import appeng.items.parts.ItemFacade;
import appeng.loot.ChestLoot;
import appeng.me.cache.CraftingGridCache;
import appeng.me.cache.EnergyGridCache;
@ -315,8 +316,10 @@ public final class Registration
if( AEConfig.instance.isFeatureEnabled( AEFeature.EnableFacadeCrafting ) )
{
GameRegistry.addRecipe( new FacadeRecipe() );
RecipeSorter.register( "appliedenergistics2:facade", FacadeRecipe.class, Category.SHAPED, "after:minecraft:shaped" );
definitions.items().facade().maybeItem().ifPresent( facadeItem -> {
GameRegistry.addRecipe( new FacadeRecipe( (ItemFacade) facadeItem ) );
RecipeSorter.register( "appliedenergistics2:facade", FacadeRecipe.class, Category.SHAPED, "after:minecraft:shaped" );
} );
}
}

View file

@ -37,6 +37,7 @@ import appeng.items.misc.ItemCrystalSeedRendering;
import appeng.items.misc.ItemEncodedPattern;
import appeng.items.misc.ItemPaintBall;
import appeng.items.misc.ItemPaintBallRendering;
import appeng.items.parts.FacadeRendering;
import appeng.items.parts.ItemFacade;
import appeng.items.storage.ItemBasicStorageCell;
import appeng.items.storage.ItemCreativeStorageCell;
@ -177,6 +178,7 @@ public final class ApiItems implements IItems
this.facade = registry.item( "facade", ItemFacade::new )
.features( AEFeature.Facades )
.creativeTab( CreativeTabFacade.instance )
.rendering( new FacadeRendering() )
.build();
this.crystalSeed = registry.item( "crystal_seed", ItemCrystalSeed::new )
.rendering( new ItemCrystalSeedRendering() )

View file

@ -0,0 +1,21 @@
package appeng.items.parts;
import appeng.bootstrap.IItemRendering;
import appeng.bootstrap.ItemRenderingCustomizer;
import appeng.client.render.FacadeItemModel;
/**
* Handles rendering customization for facade items. Please note that this works very differently
* from actually rendering a Facade in a cable bus.
*/
public class FacadeRendering extends ItemRenderingCustomizer
{
@Override
public void customize( IItemRendering rendering )
{
// This actually just uses the path it will look for by default, no custom model redirection needed
rendering.builtInModel( "models/item/facade", new FacadeItemModel() );
}
}

View file

@ -25,6 +25,10 @@ import java.util.List;
import net.minecraft.block.Block;
import net.minecraft.block.BlockGlass;
import net.minecraft.block.BlockStainedGlass;
import net.minecraft.block.state.IBlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.renderer.block.model.IBakedModel;
import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Blocks;
@ -34,6 +38,7 @@ import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.BlockRenderLayer;
import net.minecraft.util.EnumActionResult;
import net.minecraft.util.EnumBlockRenderType;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.ResourceLocation;
@ -45,7 +50,9 @@ import appeng.api.AEApi;
import appeng.api.exceptions.MissingDefinition;
import appeng.api.parts.IAlphaPassItem;
import appeng.api.util.AEPartLocation;
import appeng.core.AELog;
import appeng.core.FacadeConfig;
import appeng.decorative.solid.BlockQuartzOre;
import appeng.facade.FacadePart;
import appeng.facade.IFacadeItem;
import appeng.items.AEBaseItem;
@ -129,6 +136,35 @@ public class ItemFacade extends AEBaseItem implements IFacadeItem, IAlphaPassIte
}
}
private static boolean hasSimpleModel( Block b, IBlockState blockState )
{
if( b.getRenderType( blockState ) != EnumBlockRenderType.MODEL )
{
return false;
}
IBakedModel model = Minecraft.getMinecraft().getBlockRendererDispatcher().getBlockModelShapes().getModelForState( blockState );
for( EnumFacing facing : EnumFacing.values() )
{
List<BakedQuad> quads = model.getQuads( blockState, facing, 0 );
if( quads.size() != 1 )
{
return false;
}
BakedQuad q = quads.get( 0 );
if( q.getFace() != facing )
{
return false;
}
// TODO We could also check that the quad is fully encompassing the side
}
return true;
}
public ItemStack createFacadeForItem( final ItemStack l, final boolean returnItem )
{
if( l == null )
@ -144,8 +180,24 @@ public class ItemFacade extends AEBaseItem implements IFacadeItem, IAlphaPassIte
final int metadata = l.getItem().getMetadata( l.getItemDamage() );
// TODO 1.10.2-R - XD
final boolean defaultValue = true || b instanceof BlockGlass || b instanceof BlockStainedGlass;
final boolean hasTile = b.hasTileEntity( b.getDefaultState() );
final boolean enableGlass = b instanceof BlockGlass || b instanceof BlockStainedGlass;
final boolean disableOre = b instanceof BlockQuartzOre;
// Try to get the block state based on the item stack's meta. If this fails, don't consider it for a facade
// This for example fails for Pistons because they hardcoded an invalid meta value in vanilla
IBlockState blockState;
try
{
blockState = b.getStateFromMeta( metadata );
}
catch( Exception e )
{
AELog.debug( e, "Cannot create a facade for " + b.getRegistryName() );
return null;
}
final boolean defaultValue = ( b.isFullyOpaque( blockState ) && hasSimpleModel( b, blockState ) && !b.getTickRandomly() && !hasTile && !disableOre ) || enableGlass;
if( FacadeConfig.instance.checkEnabled( b, metadata, defaultValue ) )
{
if( returnItem )

View file

@ -19,12 +19,10 @@
package appeng.recipes.game;
import java.util.Optional;
import javax.annotation.Nullable;
import net.minecraft.inventory.IInventory;
import net.minecraft.inventory.InventoryCrafting;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.crafting.IRecipe;
import net.minecraft.world.World;
@ -39,13 +37,13 @@ import appeng.items.parts.ItemFacade;
public final class FacadeRecipe implements IRecipe
{
private final IComparableDefinition anchor;
private final Optional<Item> maybeFacade;
private final ItemFacade facade;
public FacadeRecipe()
public FacadeRecipe( ItemFacade facade )
{
this.facade = facade;
final IDefinitions definitions = AEApi.instance().definitions();
this.maybeFacade = definitions.items().facade().maybeItem();
this.anchor = definitions.parts().cableAnchor();
}
@ -62,17 +60,12 @@ public final class FacadeRecipe implements IRecipe
{
if( this.anchor.isSameAs( inv.getStackInSlot( 1 ) ) && this.anchor.isSameAs( inv.getStackInSlot( 3 ) ) && this.anchor.isSameAs( inv.getStackInSlot( 5 ) ) && this.anchor.isSameAs( inv.getStackInSlot( 7 ) ) )
{
return this.maybeFacade.map( facadeItemDefinition ->
final ItemStack facades = facade.createFacadeForItem( inv.getStackInSlot( 4 ), !createFacade );
if( facades != null && createFacade )
{
final ItemFacade facade = (ItemFacade) facadeItemDefinition;
final ItemStack facades = facade.createFacadeForItem( inv.getStackInSlot( 4 ), !createFacade );
if( facades != null && createFacade )
{
facades.stackSize = 4;
}
return facades;
} ).orElse( null );
facades.stackSize = 4;
}
return facades;
}
}

View file

@ -0,0 +1,32 @@
{
"parent": "block/block",
"textures": {
"anchor": "appliedenergistics2:parts/cable_anchor",
"particle": "appliedenergistics2:parts/cable_anchor"
},
"elements": [
{
"from": [ 7.0, 7.0, 2.0 ],
"to": [ 9.0, 9.0, 10.0 ],
"faces": {
"north": { "texture": "#anchor" },
"east": { "texture": "#anchor" },
"south": { "texture": "#anchor" },
"west": { "texture": "#anchor" },
"up": { "texture": "#anchor" },
"down": { "texture": "#anchor" }
}
},
{
"from": [ 0, 0, 0 ],
"to": [ 16, 16, 2 ],
"faces": {
"east": { "texture": "#anchor" },
"south": { "texture": "#anchor" },
"west": { "texture": "#anchor" },
"up": { "texture": "#anchor" },
"down": { "texture": "#anchor" }
}
}
]
}