๐ฃ Block States
Introductionโ
In Minecraft's block system, each block has one or more block states. For example, wood has a facing direction, and leaves have different distances. These states determine how the block behaves and appears in the game.
Single-State Blockโ
Most blocks only require a single state to function properly. The example below shows how to configure a single-state block.
blocks:
default:chinese_lantern:
state:
id: 15
state: note_block:15
model:
path: "minecraft:block/custom/chinese_lantern"
generation:
parent: "minecraft:block/cube_column"
textures:
"end": "minecraft:block/custom/chinese_lantern_top"
"side": "minecraft:block/custom/chinese_lantern"
Internal Idโ
blocks:
default:chinese_lantern:
state:
id: 15
The id represents a unique identifier for blocks. For example, 15
in this case would correspond to craftengine:note_block_15
.
The 15 in id: 15
is completely unrelated to the 15 in state: note_block:15
. I will explain it later.
The maximum number of id is determined by the sum of two factors:
- Available Block Appearance States: These are defined in the
mappings.yml
file. For a single block type, the more free block states you "release," the higher the number of available appearance states. - Additionally Registered Real States: These are added via the
additional-real-blocks.yml
file. This configuration allows you to manually register extra real serverside states for specific blocks, further increasing the total pool of internal IDs.
Visual Stateโ
blocks:
default:chinese_lantern:
state: note_block:15
The state
refers to the visual appearance seen by clients. These states are the vanilla block states we freed in the mappings.yml file. The maximum number of available states is determined by the Minecraft version and is strictly limited. note_block:15
refers to the 16th released note_block block state (zero-indexed).
You can configure state like this if you want to precisely control the appearance state in use.
state: minecraft:note_block[instrument=hat,note=0,powered=false]
Modelโ
blocks:
default:chinese_lantern:
state:
model:
path: "minecraft:block/custom/chinese_lantern"
generation:
parent: "minecraft:block/cube_column"
textures:
"end": "minecraft:block/custom/chinese_lantern_top"
"side": "minecraft:block/custom/chinese_lantern"
The model
determines which 3D model will replace the released vanilla block state. In addition to standard block models, CraftEngine allows further configuration of randomized models and model rotation.
If you are unsure how to configure properties like path
and generation
, you should complete Tutorial firstly.
Weighted Modelsโ
To achieve randomized models, just provide model paths in a list format:
state:
models: # model(s)
- path: "minecraft:block/custom/fairy_flower_1"
weight: 8
- path: "minecraft:block/custom/fairy_flower_2"
weight: 5
- path: "minecraft:block/custom/fairy_flower_3"
weight: 2
weight: Sets the probability of the model for being used in the game, defaults to 1 (=100%). If more than one model is used for the same variant, the probability is calculated by dividing the individual model's weight by the sum of the weights of all models. (For example, if three models are used with weights 1, 1, and 2, then their combined weight would be 4 (1+1+2). The probability of each model being used would then be determined by dividing each weight by 4: 1/4, 1/4 and 2/4, or 25%, 25% and 50%, respectively.)
Rotationโ
If you need to create a piece of wood with three different orientations, all you need to do is specify the x and y properties.
model:
path: "minecraft:block/custom/chinese_lantern"
x: 90
y: 180
uvlock: false
x: Rotation of the model on the x-axis in increments of 90 degrees.
y: Rotation of the model on the y-axis in increments of 90 degrees.
uvlock: Can be true or false (default). Locks the rotation of the texture of a block, if set to true. This way the texture does not rotate with the block when using the x and y-tags above.
Multi-State Blockโ
The following content may become difficult to understand. I will explain it in as much detail as possible. Please make sure to read it word by word.
The first step in creating a multi-state block is to define the type of state property for that block. In this example, I've set an axis
property.
blocks:
default:palm_log:
states: # state(s)
properties:
axis: # Property name
type: axis # Data type
default: y # Default value
Propertyโ
The property name is almost arbitrary, but I recommend using lowercase letters. However, the plugin hardcodes certain names for special placement behaviors. For example, when a property is named axis
, the plugin will automatically align its orientation during placement. This entire process is fully automated.
In the following example, since the property is named custom_axis
, the plugin will NOT apply rotation during placement. No matter how you place it, the block will always use the state custom_axis=y
when placed.
custom_axis:
type: axis
default: y
You can find detailed information about property types and the plugin's hardcoded property names on โน๏ธ Properties.
Appearanceโ
In the second step, we need to configure the possible visual appearances for the custom block. For example, a log block requires three orientations, so we need to assign three vanilla states as its visual representations.
blocks:
default:palm_log:
states:
appearances:
axisY:
state: "note_block:0"
model:
path: "minecraft:block/custom/stripped_palm_log"
generation:
parent: "minecraft:block/cube_column"
textures:
"end": "minecraft:block/custom/palm_log_top"
"side": "minecraft:block/custom/palm_log"
axisX:
state: "note_block:1"
model:
x: 90
y: 90
path: "minecraft:block/custom/stripped_palm_log_horizontal"
generation:
parent: "minecraft:block/cube_column_horizontal"
textures:
"end": "minecraft:block/custom/palm_log_top"
"side": "minecraft:block/custom/palm_log"
axisZ:
state: "note_block:2"
model:
x: 90
path: "minecraft:block/custom/stripped_palm_log_horizontal"
generation:
parent: "minecraft:block/cube_column_horizontal"
textures:
"end": "minecraft:block/custom/palm_log_top"
"side": "minecraft:block/custom/palm_log"
In the configuration above, axisX
, axisZ
, and axisY
are arbitrary names. You can use any descriptive terms that clearly represent the visual states, as long as they are unique.
The configuration of state
and model(s)
follows the same rules as single-state blocks. If you're unsure about any details, please refer back to the relevant documentation.
Variantโ
Now for the final step: We need to combine all custom properties, list all possible combinations, and assign internal block IDs to each. If you're unfamiliar with internal ID, please review the relevant documentation.
For properties and their values, use =
to connect them (e.g., axis=y
). When multiple properties appear together, separate them with commas (e.g., axis=y,age=7
).
blocks:
default:palm_log:
states:
variants:
axis=x:
appearance: axisX # refers to the appearance name we just defined in the "appearances"
id: 0
axis=y:
appearance: axisY
id: 1
axis=z:
appearance: axisZ
id: 2
Full Config
blocks:
default:palm_log:
states:
properties:
axis:
type: axis
default: y
appearances:
axisY:
state: "note_block:0"
model:
path: "minecraft:block/custom/stripped_palm_log"
generation:
parent: "minecraft:block/cube_column"
textures:
"end": "minecraft:block/custom/palm_log_top"
"side": "minecraft:block/custom/palm_log"
axisX:
state: "note_block:1"
model:
x: 90
y: 90
path: "minecraft:block/custom/stripped_palm_log_horizontal"
generation:
parent: "minecraft:block/cube_column_horizontal"
textures:
"end": "minecraft:block/custom/palm_log_top"
"side": "minecraft:block/custom/palm_log"
axisZ:
state: "note_block:2"
model:
x: 90
path: "minecraft:block/custom/stripped_palm_log_horizontal"
generation:
parent: "minecraft:block/cube_column_horizontal"
textures:
"end": "minecraft:block/custom/palm_log_top"
"side": "minecraft:block/custom/palm_log"
variants:
axis=x:
appearance: axisX
id: 0
axis=y:
appearance: axisY
id: 1
axis=z:
appearance: axisZ
id: 2
To customize block settings for specific states, add configuration like this:
variants:
"distance=7,persistent=false,waterlogged=false":
appearance: "default"
id: 6
settings:
is-randomly-ticking: true
Feeling confused?โ
However, you might also be confused about the relationship between id
and state
โ why do I need to configure two parameters? Aside from making the configuration more complicated, whatโs the actual purpose of this?
To understand why, you need to look at how CraftEngine implements custom blocks at a fundamental level. Letโs start by discussing the principles behind the plugin.
Unused Statesโ
First, let me ask you a simple question: How many different appearances does a vanilla leaves block have? I believe youโll quickly figure out the answer: two states โ waterlogged and non-waterlogged.
But do you know how many block states a single type of leaves block needs to maintain on the server side?
Answer
The correct number is 28. Did you get it right?
But how is this number calculated? Let's enable debug mode (F3) and hover the cursor over a leaves block. You'll notice several key-value pairs listed under the block ID - these represent the block's properties. Alternatively, you could use a debug stick to examine these.
- waterlogged: 2 states (true/false).
- persistent: 2 states (true/false).
- distance: 7 states (values 1 to 7).
Therefore, the total number of possible leaves block states is 2 ร 2 ร 7 = 28
.
Then you'll realize - wow! With so many leaves block states but only two visual variants, couldn't we repurpose those extra states for custom block appearances? Exactly! This is where the mappings.yml
file comes into play. Through mappings.yml
, we can map seemingly identical block states to share the same visual representation. This clever approach frees up those redundant states for our custom block implementations.
mappings.ymlโ
Below is an excerpt from the default mappings.yml configuration. It maps all oak leaves with distance=1-7 to distance=7 and persistent=true. This means every vanilla leaves block in the world is technically converted to distance=7, persistent=true leaves by the plugin - yet you can't visually tell the difference because they appear identical.
"minecraft:oak_leaves[distance=1,persistent=false,waterlogged=false]": minecraft:oak_leaves[distance=7,persistent=true,waterlogged=false]
"minecraft:oak_leaves[distance=2,persistent=false,waterlogged=false]": minecraft:oak_leaves[distance=7,persistent=true,waterlogged=false]
"minecraft:oak_leaves[distance=3,persistent=false,waterlogged=false]": minecraft:oak_leaves[distance=7,persistent=true,waterlogged=false]
"minecraft:oak_leaves[distance=4,persistent=false,waterlogged=false]": minecraft:oak_leaves[distance=7,persistent=true,waterlogged=false]
"minecraft:oak_leaves[distance=5,persistent=false,waterlogged=false]": minecraft:oak_leaves[distance=7,persistent=true,waterlogged=false]
"minecraft:oak_leaves[distance=6,persistent=false,waterlogged=false]": minecraft:oak_leaves[distance=7,persistent=true,waterlogged=false]
"minecraft:oak_leaves[distance=7,persistent=false,waterlogged=false]": minecraft:oak_leaves[distance=7,persistent=true,waterlogged=false]
"minecraft:oak_leaves[distance=1,persistent=true,waterlogged=false]": minecraft:oak_leaves[distance=7,persistent=true,waterlogged=false]
"minecraft:oak_leaves[distance=2,persistent=true,waterlogged=false]": minecraft:oak_leaves[distance=7,persistent=true,waterlogged=false]
"minecraft:oak_leaves[distance=3,persistent=true,waterlogged=false]": minecraft:oak_leaves[distance=7,persistent=true,waterlogged=false]
"minecraft:oak_leaves[distance=4,persistent=true,waterlogged=false]": minecraft:oak_leaves[distance=7,persistent=true,waterlogged=false]
"minecraft:oak_leaves[distance=5,persistent=true,waterlogged=false]": minecraft:oak_leaves[distance=7,persistent=true,waterlogged=false]
"minecraft:oak_leaves[distance=6,persistent=true,waterlogged=false]": minecraft:oak_leaves[distance=7,persistent=true,waterlogged=false]
Id & Stateโ
Now, let's return to the id and state we just discussed. The state refers to the "unused" block states we released in mappings.yml, while the id represents the actual block states existing on the server side. We need to establish a bridge between these two parameters with packet magics, so that the new blocks actually existing on the server will be mapped to those freed vanilla states, thereby appearing as custom blocks.
Not Enough Real Block Statesโ
By default, the plugin generates and releases a number of real block states equal to the available freed visual states. However, in some cases, we may need special mechanics that require more real block states but fewer visual states. In such scenarios, multiple real states will be mapped to the same visual state, resulting in the issue mentioned in the title: "Not Enough Real Blocks".
To resolve this, you will need to use the additional-real-blocks.yml
file to register additional real blocks.