diff --git a/src/main/java/appeng/client/render/FacadeDispatcherBakedModel.java b/src/main/java/appeng/client/render/FacadeDispatcherBakedModel.java new file mode 100644 index 00000000..8e2d8d81 --- /dev/null +++ b/src/main/java/appeng/client/render/FacadeDispatcherBakedModel.java @@ -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 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 ); + } + }; + } +} diff --git a/src/main/java/appeng/client/render/FacadeItemModel.java b/src/main/java/appeng/client/render/FacadeItemModel.java new file mode 100644 index 00000000..383c0888 --- /dev/null +++ b/src/main/java/appeng/client/render/FacadeItemModel.java @@ -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 getDependencies() + { + return Collections.emptyList(); + } + + @Override + public Collection getTextures() + { + return Collections.emptyList(); + } + + @Override + public IBakedModel bake( IModelState state, VertexFormat format, Function 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(); + } +} diff --git a/src/main/java/appeng/client/render/FacadeWithBlockBakedModel.java b/src/main/java/appeng/client/render/FacadeWithBlockBakedModel.java new file mode 100644 index 00000000..6a71db5e --- /dev/null +++ b/src/main/java/appeng/client/render/FacadeWithBlockBakedModel.java @@ -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 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; + } +} diff --git a/src/main/java/appeng/core/Registration.java b/src/main/java/appeng/core/Registration.java index 812fd899..2718d666 100644 --- a/src/main/java/appeng/core/Registration.java +++ b/src/main/java/appeng/core/Registration.java @@ -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" ); + } ); } } diff --git a/src/main/java/appeng/core/api/definitions/ApiItems.java b/src/main/java/appeng/core/api/definitions/ApiItems.java index b6cc87d7..44043170 100644 --- a/src/main/java/appeng/core/api/definitions/ApiItems.java +++ b/src/main/java/appeng/core/api/definitions/ApiItems.java @@ -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() ) diff --git a/src/main/java/appeng/items/parts/FacadeRendering.java b/src/main/java/appeng/items/parts/FacadeRendering.java new file mode 100644 index 00000000..bf95c00e --- /dev/null +++ b/src/main/java/appeng/items/parts/FacadeRendering.java @@ -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() ); + } +} diff --git a/src/main/java/appeng/items/parts/ItemFacade.java b/src/main/java/appeng/items/parts/ItemFacade.java index abde47a6..f79a972d 100644 --- a/src/main/java/appeng/items/parts/ItemFacade.java +++ b/src/main/java/appeng/items/parts/ItemFacade.java @@ -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 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 ) diff --git a/src/main/java/appeng/recipes/game/FacadeRecipe.java b/src/main/java/appeng/recipes/game/FacadeRecipe.java index f1cd171a..a7ab1d76 100644 --- a/src/main/java/appeng/recipes/game/FacadeRecipe.java +++ b/src/main/java/appeng/recipes/game/FacadeRecipe.java @@ -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 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; } } diff --git a/src/main/resources/assets/appliedenergistics2/models/item/facade_base.json b/src/main/resources/assets/appliedenergistics2/models/item/facade_base.json new file mode 100644 index 00000000..2b73cb0b --- /dev/null +++ b/src/main/resources/assets/appliedenergistics2/models/item/facade_base.json @@ -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" } + } + } + ] +} \ No newline at end of file