Skip to main content

Render Block TESR / OBJ

This is a more advanced tutorial in which various new concepts are introduced. First you will see how to make a tile entity that has to sync data from server to client. You will also see how we can use both a static OBJ model combined with an animating dynamic OBJ model. And finally you will see how to render any item directly in the world. To do this we need to implement a TESR (TileEntitySpecialRenderer). The end results looks like this. You can insert and remove items by right-clicking on this block:

image

Make sure you added our domain to the object loader like you did in the static OBJ tutorial:

public static class ClientProxy extends CommonProxy {
@Override
public void preInit(FMLPreInitializationEvent e) {
super.preInit(e);

OBJLoader.INSTANCE.addDomain(MODID);

...
}
}

Then we have our block:

public class PedestalBlock extends Block implements ITileEntityProvider {

// Used for visuals only
public static final IProperty<Boolean> IS_HANDLES = PropertyBool.create("is_handles");

public PedestalBlock() {
super(Material.ROCK);
setUnlocalizedName(ModTut.MODID + ".pedestalblock");
setRegistryName("pedestalblock");
setDefaultState(blockState.getBaseState().withProperty(IS_HANDLES, false));
}

@Override
public IBlockState getActualState(IBlockState state, IBlockAccess worldIn, BlockPos pos) {
return state.withProperty(IS_HANDLES, false);
}

@Override
protected BlockStateContainer createBlockState() {
return new BlockStateContainer(this, IS_HANDLES);
}

@Override
public IBlockState getStateFromMeta(int meta) {
return getDefaultState();
}

@Override
public int getMetaFromState(IBlockState state) {
return 0;
}

@SideOnly(Side.CLIENT)
public void initModel() {
ModelLoader.setCustomModelResourceLocation(Item.getItemFromBlock(this), 0, new ModelResourceLocation(getRegistryName(), "inventory"));
// Bind our TESR to our tile entity
ClientRegistry.bindTileEntitySpecialRenderer(PedestalTileEntity.class, new PedestalTESR());
}

@Override
public TileEntity createNewTileEntity(World worldIn, int meta) {
return new PedestalTileEntity();
}

@Override
@SideOnly(Side.CLIENT)
public boolean shouldSideBeRendered(IBlockState blockState, IBlockAccess worldIn, BlockPos pos, EnumFacing side) {
return false;
}

@Override
public boolean isBlockNormalCube(IBlockState blockState) {
return false;
}

@Override
public boolean isOpaqueCube(IBlockState blockState) {
return false;
}

private PedestalTileEntity getTE(World world, BlockPos pos) {
return (PedestalTileEntity) world.getTileEntity(pos);
}

@Override
public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player,
EnumHand hand, EnumFacing side, float hitX, float hitY, float hitZ) {
if (!world.isRemote) {
PedestalTileEntity te = getTE(world, pos);
if (te.getStack().isEmpty()) {
if (!player.getHeldItem(hand).isEmpty()) {
// There is no item in the pedestal and the player is holding an item. We move that item
// to the pedestal
te.setStack(player.getHeldItem(hand));
player.inventory.setInventorySlotContents(player.inventory.currentItem, ItemStack.EMPTY);
// Make sure the client knows about the changes in the player inventory
player.openContainer.detectAndSendChanges();
}
} else {
// There is a stack in the pedestal. In this case we remove it and try to put it in the
// players inventory if there is room
ItemStack stack = te.getStack();
te.setStack(ItemStack.EMPTY);
if (!player.inventory.addItemStackToInventory(stack)) {
// Not possible. Throw item in the world
EntityItem entityItem = new EntityItem(world, pos.getX(), pos.getY()+1, pos.getZ(), stack);
world.spawnEntity(entityItem);
} else {
player.openContainer.detectAndSendChanges();
}
}
}

// Return true also on the client to make sure that MC knows we handled this and will not try to place
// a block on the client
return true;
}
}

Then we have the tile entity. Notice the extra code we use to synchronize our tile entity to the client when needed:

public class PedestalTileEntity extends TileEntity {

private ItemStack stack = ItemStack.EMPTY;

public ItemStack getStack() {
return stack;
}

public void setStack(ItemStack stack) {
this.stack = stack;
markDirty();
if (world != null) {
IBlockState state = worldObj.getBlockState(getPos());
world.notifyBlockUpdate(getPos(), state, state, 3);
}
}

@Override
public NBTTagCompound getUpdateTag() {
// getUpdateTag() is called whenever the chunkdata is sent to the
// client. In contrast getUpdatePacket() is called when the tile entity
// itself wants to sync to the client. In many cases you want to send
// over the same information in getUpdateTag() as in getUpdatePacket().
return writeToNBT(new NBTTagCompound());
}

@Override
public SPacketUpdateTileEntity getUpdatePacket() {
// Prepare a packet for syncing our TE to the client. Since we only have to sync the stack
// and that's all we have we just write our entire NBT here. If you have a complex
// tile entity that doesn't need to have all information on the client you can write
// a more optimal NBT here.
NBTTagCompound nbtTag = new NBTTagCompound();
this.writeToNBT(nbtTag);
return new SPacketUpdateTileEntity(getPos(), 1, nbtTag);
}

@Override
public void onDataPacket(NetworkManager net, SPacketUpdateTileEntity packet) {
// Here we get the packet from the server and read it into our client side tile entity
this.readFromNBT(packet.getNbtCompound());
}

@Override
public void readFromNBT(NBTTagCompound compound) {
super.readFromNBT(compound);
if (compound.hasKey("item")) {
stack = new ItemStack(compound.getCompoundTag("item"));
} else {
stack = ItemStack.EMPTY;
}
}

@Override
public NBTTagCompound writeToNBT(NBTTagCompound compound) {
super.writeToNBT(compound);
if (!stack.isEmpty()) {
NBTTagCompound tagCompound = new NBTTagCompound();
stack.writeToNBT(tagCompound);
compound.setTag("item", tagCompound);
}
return compound;
}
}

Finally, we have the actual TESR. Note that the static model is not rendered here. This is handled automatically. We only have to do the animating handles and the item:

@SideOnly(Side.CLIENT)
public class PedestalTESR extends TileEntitySpecialRenderer<PedestalTileEntity> {

@Override
public void render(PedestalTileEntity te, double x, double y, double z, float partialTicks, int destroyStage, float alpha) {
GlStateManager.pushAttrib();
GlStateManager.pushMatrix();

// Translate to the location of our tile entity
GlStateManager.translate(x, y, z);
GlStateManager.disableRescaleNormal();

// Render the rotating handles
renderHandles(te);

// Render our item
renderItem(te);

GlStateManager.popMatrix();
GlStateManager.popAttrib();
}

private void renderHandles(PedestalTileEntity te) {
GlStateManager.pushMatrix();

GlStateManager.translate(.5, 0, .5);
long angle = (System.currentTimeMillis() / 10) % 360;
GlStateManager.rotate(angle, 0, 1, 0);

RenderHelper.disableStandardItemLighting();
this.bindTexture(TextureMap.LOCATION_BLOCKS_TEXTURE);
if (Minecraft.isAmbientOcclusionEnabled()) {
GlStateManager.shadeModel(GL11.GL_SMOOTH);
} else {
GlStateManager.shadeModel(GL11.GL_FLAT);
}

World world = te.getWorld();
// Translate back to local view coordinates so that we can do the acual rendering here
GlStateManager.translate(-te.getPos().getX(), -te.getPos().getY(), -te.getPos().getZ());

Tessellator tessellator = Tessellator.getInstance();
BufferBuilder bufferBuilder = tessellator.getBuffer();
bufferBuilder.begin(GL11.GL_QUADS, DefaultVertexFormats.BLOCK);

IBlockState state = ModBlocks.pedestalBlock.getDefaultState().withProperty(PedestalBlock.IS_HANDLES, true);
BlockRendererDispatcher dispatcher = Minecraft.getMinecraft().getBlockRendererDispatcher();
IBakedModel model = dispatcher.getModelForState(state);
dispatcher.getBlockModelRenderer().renderModel(world, model, state, te.getPos(), bufferBuilder, true);
tessellator.draw();

RenderHelper.enableStandardItemLighting();
GlStateManager.popMatrix();
}

private void renderItem(PedestalTileEntity te) {
ItemStack stack = te.getStack();
if (!stack.isEmpty()) {
RenderHelper.enableStandardItemLighting();
GlStateManager.enableLighting();
GlStateManager.pushMatrix();
// Translate to the center of the block and .9 points higher
GlStateManager.translate(.5, .9, .5);
GlStateManager.scale(.4f, .4f, .4f);

Minecraft.getMinecraft().getRenderItem().renderItem(stack, ItemCameraTransforms.TransformType.NONE);

GlStateManager.popMatrix();
}
}

}

And finally we need our blockstate json:

{
"forge_marker": 1,
"defaults": {
"custom": { "flip-v": true }
},
"variants": {
"is_handles=false": { "model": "modtut:pedestal.obj" },
"is_handles=true": { "model": "modtut:pedestalhandles.obj" },
"inventory": { "model": "modtut:pedestal.obj" }
}
}