Changes

7,598 bytes added ,  05:49, 6 December 2021
Copy BlockStates to MC1.17 archive
== Introduction of States ==

The block state system abstracts out the details of the block's properties from the other behaviors of the block.

Each <code>property</code> of a block is described by an instance of <code><nowiki>Property<?></nowiki></code>. Examples of block properties include instruments (<code><nowiki>Property<NoteBlockInstrument></nowiki></code>), direction (<code><nowiki>Property<Direction></nowiki></code>), poweredness (<code><nowiki>Property<Boolean></nowiki></code>), etc. Each property has the value of the type <code><nowiki>T</nowiki></code> parametrized by <code><nowiki>Property<T></nowiki></code>.

A unique triple can be constructed from the <code><nowiki>Block</nowiki></code>, the set of <code><nowiki>Property<?></nowiki></code>, and the set of values for those properties. This unique triple is called a <code><nowiki>BlockState</nowiki></code>. For example, a stone button which is facing east and is powered or held down is represented by <code><nowiki>minecraft:stone_button[facing=east,powered=true]</nowiki></code>.

== Proper Usage of Block States ==

<code><nowiki>BlockState</nowiki></code> is a flexible and powerful system, but it also has limitations. <code><nowiki>BlockState</nowiki></code>s are immutable, and all combinations of their properties are generated on startup of the game. This means that having a <code><nowiki>BlockState</nowiki></code> with many properties and possible values will slow down the loading of the game, and befuddle anyone trying to make sense of your block logic.

Not all blocks and situations require the usage of <code><nowiki>BlockState</nowiki></code>; only the most basic properties of a block should be put into one, and any other situation is better off with having a <code><nowiki>BlockEntity</nowiki></code> or being a separate <code><nowiki>Block</nowiki></code>. Always consider if you actually need to use a state for your purposes.

{{Colored box|title=Tip|content=A good rule of thumb is: '''if it has a different name, it should be a separate block'''.}}

An example is making chair blocks: the ''direction'' of the chair should be a ''property'', while the different ''types of wood'' should be separated into different blocks.
An "Oak Chair" facing east (<code><nowiki>oak_chair[facing=east]</nowiki></code>) is different from a "Spruce Chair" facing west (<code><nowiki>spruce_chair[facing=west]</nowiki></code>).

== Implementing Block States ==

In your Block class, create or reference <code><nowiki>static final</nowiki></code> <code><nowiki>Property<?></nowiki></code> objects for every property that your Block has. You are free to make your own <code><nowiki>Property<?></nowiki></code> implementations, but the means to do that are left as an exercise to the reader. The vanilla code provides several convenience implementations:

* <code><nowiki>IntegerProperty</nowiki></code>
** Implements <code><nowiki>Property<Integer></nowiki></code>. Defines a property that holds an integer value.
** Created by calling <code><nowiki>IntegerProperty.create(String propertyName, int minimum, int maximum)</nowiki></code>.
* <code><nowiki>BooleanProperty</nowiki></code>
** Implements <code><nowiki>Property<Boolean></nowiki></code>. Defines a property that holds a <code><nowiki>true</nowiki></code> or <code><nowiki>false</nowiki></code> value.
** Created by calling <code><nowiki>BooleanProperty.create(String propertyName)</nowiki></code>.
* <code><nowiki>EnumProperty<E extends Enum<E>></nowiki></code>
** Implements <code><nowiki>Property<E></nowiki></code>. Defines a property that can take on the values of an Enum class.
** Created by calling <code><nowiki>EnumProperty.create(String propertyName, Class<E> enumClass)</nowiki></code>.
** It is also possible to use only a subset of the Enum values (e.g. <code><nowiki>RailShape</nowiki></code>s that can only ascend and not turn). See the overloads of <code><nowiki>EnumProperty.create</nowiki></code>.
* <code><nowiki>DirectionProperty</nowiki></code>
** This is a convenience implementation of <code><nowiki>EnumProperty<Direction></nowiki></code>
** Several convenience predicates are also provided. For example, to get a property that represents the cardinal directions, call <code><nowiki>DirectionProperty.create("<name>", Direction.Plane.HORIZONTAL)</nowiki></code>; to get the X directions, <code><nowiki>DirectionProperty.create("<name>", Direction.Axis.X)</nowiki></code>

The class <code><nowiki>BlockStateProperties</nowiki></code> contains shared vanilla properties which should be used or referenced whenever possible, in place of creating your own properties.

When you have your desired <code><nowiki>Property<></nowiki></code> objects, override <code><nowiki>Block#createBlockStateDefinition(StateDefinition$Builder)</nowiki></code> in your ''Block'' class. In that method, call <code><nowiki>StateDefinition$Builder#add(...);</nowiki></code> with the parameters as every <code><nowiki>Property<?></nowiki></code> you wish the block to have.

Every block will also have a "default" state that is automatically chosen for you. You can change this "default" state by calling the <code><nowiki>Block#registerDefaultState(BlockState)</nowiki></code> method from your constructor. When your block is placed it will become this "default" state. An example from <code><nowiki>DoorBlock</nowiki></code>:

<syntaxhighlight lang="java">
this.registerDefaultState(
this.stateDefinition.any()
.setValue(FACING, Direction.NORTH)
.setValue(OPEN, false)
.setValue(HINGE, DoorHingeSide.LEFT)
.setValue(POWERED, false)
.setValue(HALF, DoubleBlockHalf.LOWER)
);
</syntaxhighlight>

If you wish to change what <code><nowiki>BlockState</nowiki></code> is used when placing your block, you can overwrite <code><nowiki>Block#getStateForPlacement(BlockPlaceContext)</nowiki></code>. This can be used to, for example, set the direction of your block depending on where the player is standing when they place it.

Because <code><nowiki>BlockState</nowiki></code>s are immutable, and all combinations of their properties are generated on startup of the game, calling <code><nowiki>BlockState#setValue(Property<T>, T)</nowiki></code> will simply go to the <code><nowiki>Block</nowiki></code>'s <code><nowiki>StateHolder</nowiki></code> and request the <code><nowiki>BlockState</nowiki></code> with the set of values you want.

Because all possible <code><nowiki>BlockState</nowiki></code>s are generated at startup, you are free and encouraged to use the reference equality operator (<code><nowiki>==</nowiki></code>) to check if two <code><nowiki>BlockState</nowiki></code>s are equal.

== Using <tt>BlockState</tt>s ==

You can get the value of a property by calling <code><nowiki>BlockState#getValue(Property<?>)</nowiki></code>, passing it the property you want to get the value of.
If you want to get a <code><nowiki>BlockState</nowiki></code> with a different set of values, simply call <code><nowiki>BlockState#setValue(Property<T>, T)</nowiki></code> with the property and its value.

You can get and place <code><nowiki>BlockState</nowiki></code>s in the level using <code><nowiki>Level#setBlockAndUpdate(BlockPos, BlockState)</nowiki></code> and <code><nowiki>Level#getBlockState(BlockState)</nowiki></code>. If you are placing a <code><nowiki>Block</nowiki></code>, call <code><nowiki>Block#defaultBlockState()</nowiki></code> to get the "default" state, and use subsequent calls to <code><nowiki>BlockState#setValue(Property<T>, T)</nowiki></code> as stated above to achieve the desired state.
372

edits