• Forums

Navigation

  • Home
  • Style Guide
  • Getting Started
    • Home
    • Structuring Your Mod
    • Forge Update Checker
    • Dependency Management
    • Debug Profiler
  • Concepts
    • Sides
    • Resources
    • Registries
    • The Fingerprint Violation Event
    • Internationalization and localization
  • Blocks
    • Home
    • Intro to Blockstates
    • Interaction
  • Animation API
    • Intro to the Animation API
    • Armatures
    • Animation State Machines
    • Using the API
  • Tile Entities
    • Home
    • Special Renderer
  • Items
    • Home
    • Loot Tables
  • Models
    • Intro to Models
    • Model Files
    • Blockstates
      • Intro to Blockstate JSONs
      • Forge Blockstate JSON
    • Connecting Blocks and Items to Models
    • Coloring Textures
    • Item Property Overrides
    • Advanced Models
      • Intro to Advanced Models
      • IModel
      • IModelState and IModelPart
      • IBakedModel
      • Extended Blockstates
        • Why Extended Blockstates
        • Declaring Unlisted Properties
        • Filling Extended States
        • Using Extended States
      • Perspective
      • ItemOverrideList
      • ICustomModelLoader
  • Rendering
    • TileEntityItemStackRenderer
  • Events
    • Basic Usage
  • Networking
    • Home
    • Overview
    • SimpleImpl
    • Entities
  • Data Storage
    • Capabilities
    • World Saved Data
    • Extended Entity Properties
    • Config Annotations
  • Utilities
    • Recipes
    • OreDictionary
    • PermissionAPI
  • Effects
    • Sounds
  • Conventions
    • Versioning
    • Locations
    • Loading Stages
  • Contributing to Forge
    • Getting Started
    • PR Guidelines

Extended Blockstates

Extended blockstates provide a way for blocks to pass arbitrary data to their models. Ordinary blockstates and properties occupy only a fixed set of possible states, while extended states can form infinite sets. This accomplished through the use of unlisted properties (IUnlistedProperty<V>), extended block states (IExtendedBlockState), and extended block state containers (ExtendedBlockState).

Ordinary block state containers (BlockStateContainer) take a set of listed properties (IProperty<V>) that define all possible values for themselves and use those to create all possible combinatons of values. These combinations are then turned into IBlockStates and stored in the finite set of all possible states. The properties are called “listed” as they appear on the F3 debug screen, all the way to the right.

Extended block state containers take a set of listed properties, and also a set of unlisted properties. Unlisted properties’ values can be anything that matches their type, and are not limited to a finite set. However, values are still required to satisfy the predicate IUnlistedProperty::isValid. The listed properties are again used to create the set of states, this time IExtendedBlockStates, but the unlisted properties remain unset. The unlisted properties are only set when IExtendedBlockState::withProperty is called to do so.

Most of the methods on IExtendedBlockState are self-explanatory, maybe with the exception of getClean. getClean returns the base IBlockState with none of the unlisted properties set.

Why Extended Blockstates

Why use extended blockstates at all? Why not simply pass an IBlockAccess and BlockPos into the rendering system directly and have the IBakedModel itself deal with it? Indeed, extended blockstates allow this to happen anyway! Why have the system at all when we could just do that? The reason is that it makes the system more flexible. External code can take an IExtendedBlockState and fill in some data by itself, overriding the block’s own data. This allows that code to alter the model in ways that would be impossible if it was just a black box that took a world and a position and spat out some geometry.

One such use case is advanced camouflaging blocks. The camouflaging block may have a tile entity that holds the IBlockState representative of the camouflagee, and an unlisted property to hold that state during rendering. This property can then be filled by getExtendedState. Finally, a custom IBakedModel can steal the model for that state and use it instead of using the uncamouflaged model.

Declaring Unlisted Properties

Unlisted properties are declared in Block.createBlockState, the same place as regular (“listed”) properties. Instead of returning a BlockStateContainer, one must return an ExtendedBlockState. Forge provides a builder BlockStateContainer.Builder, which will automatically handle returning an ExtendedBlockState for you.

Example:

@Override
public BlockStateContainer createBlockState() {
    return new BlockStateContainer.Builder(this).add(LISTED_PROP).add(UNLISTED_PROP).build();
}

Note that you do not need to set default values for your unlisted properties.

Filling Extended States

Before an IBlockState is passed to an IBakedModel, it will always have Block.getExtendedState called on it first. In this method, you will give all your unlisted properties values. Assuming you registered at least one unlisted property in the previous section, the IBlockState parameter can be safely casted to IExtendedBlockState, which has a withProperty method for unlisted properties analogous to its listed property cousin. Here, you can query whatever you want from the World, the Tile Entity, etc. (with appropriate safety checks, of course) and insert it into the extended blockstate.

Warning

It is highly recommended that your unlisted property values be immutable. Baked model implementations will use the extended state and unlisted values on multiple threads, so any value must be used in a threadsafe manner. The easiest way is to simply make that information an immutable snapshot. Anything you might possibly want to know in your custom IBakedModel, you should be passing an immutable snapshot of through Block.getExtendedState.

Example:

@Override
public IExtendedBlockState getExtendedState(IBlockState state, IBlockAccess world, BlockPos pos) {
    IExtendedBlockState ext = (IExtendedBlockState) state;
    TileEntity te = world.getTileEntity(pos);
    if (te instanceof MyTE) {
        ext = ext.withProperty(UNLISTED_PROP, ((MyTE) te).getSomeImmutableData());
    }
    return ext;
}

Using Extended States

In a custom IBakedModel, the IBlockState parameter passed to you will be exactly the object you returned in Block.getExtendedState, and you can pull data out of it and use it to affect your rendering as you wish.

Here is a basic example of using an unlisted property to determine which model to render, assuming UNLISTED_PROP is an IUnlistedProperty<String>:

// in custom IBakedModel
private final Map<String, IBakedModel> submodels = new HashMap<>(); // populated in a custom manner out of the scope of this article

@Override
public List<BakedQuad> getQuads(@Nullable IBlockState state, EnumFacing facing, long rand) {
    IBakedModel fallback = Minecraft.getMinecraft().getBlockRendererDispatcher().getBlockModelShapes().getModelManager().getMissingModel();
    if (state == null)
        return fallback.getQuads(state, facing, rand);

    IExtendedBlockState ex = (IExtendedBlockState) state;
    String id = ex.getValue(UNLISTED_PROP);
    return submodels.getOrDefault(id, fallback).getQuads(state, facing, rand);
}

Since there are no longer a fixed set of IExtendedBlockState generated at startup, comparisons involving an extended state must use getClean(), which is a method on IExtendedBlockState that returns the vanilla state (i.e. the fixed set of IBlockState with only the listed properties).

IExtendedBlockState state = ...;
IBlockState worldState = world.getBlockState(pos);
if(state.getClean() == worldState) {
    ...
}

Built with MkDocs using a custom theme. Hosted by Read the Docs.
Enable Dark Theme