Developers
9 TOP-LEVEL ITEMSReading and Modifying Block Properties
If you run into needs like these during development:
- you want to save some
BlockStateproperties into JSON, packets, or anItemStack - you want to test whether a block state satisfies a set of property constraints
- you want to rebuild or modify a
BlockStatefrom string-keyed properties
then BlockProperties is the helper Croparia IF provides for exactly that kind of work.
It is best understood as:
- a serializable subset of block-state properties
You can extract it from a BlockState, move it through other systems, then later apply it back onto a real block state when needed.
Mental model
From the caller's point of view, the easiest way to read the relationship is:
BlockState- the vanilla runtime state object
BlockProperties- a property description that is easy to store, send, and match
StateHolderAccess.apply(...)- the bridge that applies
BlockPropertiesback onto aBlockState
- the bridge that applies
The most common lifecycle is:
- extract properties from one
BlockState - store or transport them
- later match or reapply them somewhere else
If you keep that main path in mind, most of the page falls into place.
Extract, apply, and match
The three most commonly used abilities are:
extract(BlockState state)isSubsetOf(BlockState state)- applying them back through
StateHolderAccess.apply(...)
Those three actions already cover most real-world use cases.
extract(...) is especially nice because:
- it stores differences relative to the block's default state
- it does not blindly copy every property
- the resulting structure is usually smaller and easier to persist
Meanwhile, isSubsetOf(...) and apply(...) correspond to:
- "does this world state satisfy my property requirements?"
- "rebuild or update a state using these saved requirements"
In practice, you can think of BlockProperties as:
- a storage format for partial block state
- a matching condition for block state
- a reconstruction parameter set for block state
A typical workflow
The most common usage path looks something like this:
BlockState state = level.getBlockState(pos);
BlockProperties properties = BlockProperties.extract(state);
// save, send, or attach properties
BlockState restored = StateHolderAccess.apply(state.getBlock().defaultBlockState(), properties);If your goal is not to rebuild a state but only to test it, then:
boolean matches = properties.isSubsetOf(otherState);That is exactly why BlockProperties shows up so often in recipes, display data, and cross-system transfer.
Why not just use Map<String, String>
Of course, you could store all of this in a plain Map<String, String>.
But the value of BlockProperties is that it already unifies the pieces that matter in actual development:
- JSON serialization
- network transport
- attaching the data to an
ItemStackas a Data Component - tooltip display
- recipe or structure matching
- rebuilding state through
StateHolderAccess.apply(...)
So you are not using "just a map." You are using a property format that the rest of Croparia IF already understands.
Where you will encounter it
Common places include:
BlockInputandBlockOutputin the Recipe API- display stacks that need to remember block-state details
- systems that need to persist
BlockStatedata across boundaries - code that later re-applies those properties back onto real world state
So if you are building a persistent data structure around block state, it is usually better to reuse this type than to invent another parallel format.
What StateHolderMixin contributes here
These abilities are possible because Croparia IF uses StateHolderMixin to attach the StateHolderAccess interface onto vanilla StateHolder.
From the user's point of view, you do not need to memorize the mixin details. The important part is:
- Croparia IF provides an extra bridge for reading and writing vanilla state by string property names
That bridge mainly includes:
cif$getValue(String key)cif$setValue(String key, String value)cif$getProperties()StateHolderAccess.apply(...)
So BlockProperties does not "magically know how to change block state." It works because this access layer connects it back into vanilla's state system.
Tips
- When you need to describe part of a block state, prefer
BlockProperties. - When you need to apply saved properties back to a real
BlockState, go throughStateHolderAccess.apply(...). - If you only need to inspect a
BlockStatetemporarily inside one function, vanilla state access is often enough. - If one system needs both persistence and matching or re-application,
BlockPropertiesis much more stable than a nakedMap<String, String>.