Chapter 17, Facing the World
Two directional blocks: a log whose grain follows the face you place it on, and a freezer that turns to face you. Both are pure state plus a blockstate JSON, with one block-entity trap to step around.
A directional block is just a block with a single state property that records an orientation, plus a blockstate JSON that picks a rotated model per value of that property. No render code, no per-face texture juggling. Chapter 14 gave you the property machinery and Chapter 16 gave you the blockstate-to-model wiring; this chapter spends both at once on the two shapes you reach for most, a pillar that lies along an axis and a machine that fronts the player. The only genuinely surprising part is a one-line gotcha that only bites blocks with a block entity, and we save it for last.
An enum property
Both blocks here store their orientation in a RetroEnumProperty, the third kind from Chapter 14's table. You write a plain Java enum, and the property turns one of its constants into one of the block's states:
package com.example.example_mod;
/**
* The four horizontal directions a block can face, the beta equivalent of modern
* Minecraft's HorizontalFacingBlock property. Enum constants serialize as their
* lowercase names, which is what the freezer blockstate's "facing=north" keys match.
*/
public enum Facing {
NORTH,
SOUTH,
WEST,
EAST;Two details carry the whole pattern. First, the enum constant serializes to its lowercase name, so NORTH becomes the string "north", which is exactly the key the blockstate JSON uses ("facing=north"). That string is the contract between Java and the JSON; nothing else connects them. Second, the first-declared constant is the property's default value, so the block's default state has facing=north unless you say otherwise. (You can override the serialized name by implementing RetroNameable.getName(), but lowercase-of-the-constant is almost always what you want, so neither enum here bothers.)
The property is declared on the block as a static final field, the same way the lamp declared LIT and AGE in Chapter 14: public static final RetroEnumProperty<Facing> FACING = RetroEnumProperty.of("facing", Facing.class);. The string "facing" is the property name, the one that appears on the left of the = in the blockstate keys.
The directional log
The simplest case has no block entity and no player-aware logic: a pillar that runs along whichever axis you place it on, like an oak log. The state is one Axis property, three values:
public enum Axis {
Y,
X,
Z;
/**
* The axis a pillar should run along when placed against {@code side}. Beta side
* indices: 0/1 are the bottom/top faces (a vertical pillar), 4/5 are the x faces,
* 2/3 are the z faces. The pillar runs along the axis of the face's NORMAL, so a
* log placed on an x-facing wall runs along x.
*/
public static Axis fromSide(int side) {
switch (side) {
case 0: case 1: return Y;
case 2: case 3: return X; // z faces -> run along x (flipped from the face normal)
default: return Z; // sides 4/5, the x faces -> run along z
}
}
}Notice Y is listed first, on purpose. Because the first constant is the default, the log's default state is axis=y, an upright pillar, and that is the state the inventory icon and the in-hand model render. Nobody wants to see a log lying on its side in their hotbar, so the constant order is doing real work here, not just sorting alphabetically.
The block flips the grain at placement time. Beta hands onPlaced the side you clicked, an integer face index, and the log converts it to an axis and writes that into state:
public class ExampleLogBlock extends Block {
public static final RetroEnumProperty<Axis> AXIS = RetroEnumProperty.of("axis", Axis.class);
public ExampleLogBlock(int id) {
super(id, Material.WOOD);
}
@Override
public void onPlaced(World world, int x, int y, int z, int side) {
super.onPlaced(world, x, y, z, side);
RetroStates.set(world, x, y, z,
RetroStates.getDefault(this).with(AXIS, Axis.fromSide(side)));
}
}That is the entire directional behavior: start from RetroStates.getDefault(this), swap in the axis for the face that was clicked with one .with(...), and RetroStates.set commits it, re-rendering and syncing exactly as Chapter 14 described. There is no render code in this class because the look comes entirely from the blockstate JSON, which maps each axis value to a model plus the rotation that points the end-grain caps along the pillar:
{
"variants": {
"axis=x": { "model": "example_mod:block/packed_log_horizontal", "x": 90, "y": 90 },
"axis=y": { "model": "example_mod:block/packed_log" },
"axis=z": { "model": "example_mod:block/packed_log_horizontal", "x": 90 }
}
}The upright case (axis=y) uses an unrotated model whose parent is minecraft:block/cube_column, the modern pillar model that puts an end texture on the caps and a side texture around the trunk. The two horizontal cases reuse one cube_column_horizontal model and tip it 90 degrees on x to lay it down, with an extra 90 on y for the x axis so the caps point the right way. One state, three variants, the model rotations doing all the orienting. This is the same pattern modern Minecraft's logs use, working unchanged on beta.
The blockstate picks this horizontal model with its x and y rotations for logs on the X or Z axis, and the upright model for the Y axis.
{
"parent": "minecraft:block/cube_column_horizontal",
"textures": {
"end": "example_mod:block/packed_log_top",
"side": "example_mod:block/packed_log"
}
}The log needs two textures, the side grain wrapping the trunk and the end cap on the faces. These live at src/main/resources/assets/example_mod/textures/block/.
Facing the placer
The freezer is the other archetype: instead of following the face you click, it turns to face you. The state is a single Facing property (the four-direction enum above), and the work is in converting the placer's yaw into a direction. The enum's fromYaw does the arithmetic:
/**
* The direction a block should face when placed by an entity looking along {@code yaw}.
* A block faces the player, so this is the OPPOSITE of the way they look: the furnace
* and chest do exactly this. Yaw 0 is south in beta, so the quadrants land as below.
*/
public static Facing fromYaw(float yaw) {
int quadrant = Math.round(yaw / 90.0F) & 3;
switch (quadrant) {
case 0: return NORTH; // looking south, face north
case 1: return EAST;
case 2: return SOUTH;
default: return WEST;
}
}The key word is OPPOSITE. A furnace, a chest, and our freezer all show their front to the person who placed them, which means the stored facing is the reverse of where the player is looking. Round the yaw to the nearest quadrant, mask to four values with & 3, and the case 0 branch makes that concrete: when you look south (yaw 0 in beta), the block faces north, back at you. That is the whole orientation rule.
The block calls it in onPlaced, which for a block-entity block gets the LivingEntity placer rather than a face index:
@Override
public void onPlaced(World world, int x, int y, int z, LivingEntity placer) {
super.onPlaced(world, x, y, z, placer);
// State, not raw meta: store the facing the placer should see. RetroStates.set
// re-renders and notifies, so the door is correctly oriented the instant it lands.
RetroStates.set(world, x, y, z,
RetroStates.getDefault(this).with(FACING, Facing.fromYaw(placer.yaw)));
}Structurally it is identical to the log: default state, one .with(...), one set. Only the source of the direction differs, the placer's yaw instead of the clicked face. And the blockstate JSON is the same shape too, y-rotating one orientable model per facing so the frosted door always lands on the front:
{
"variants": {
"facing=north": { "model": "example_mod:block/freezer_block" },
"facing=south": { "model": "example_mod:block/freezer_block", "y": 180 },
"facing=west": { "model": "example_mod:block/freezer_block", "y": 270 },
"facing=east": { "model": "example_mod:block/freezer_block", "y": 90 }
}
}The model's parent is minecraft:block/orientable, the modern model that paints a distinct front texture on one side and side on the other three, with top on the cap. facing=north is the unrotated baseline; the other three values spin that same model around y by 90, 180, or 270 degrees. One model, four variants, the door pointed four ways. The freezer's actual machine, its block entity, container, and screen, is Chapter 6's subject; here we only care that it points the right way.
The orientable model uses all three of the freezer's textures, the frosted front door, the side wrapping the other three faces, and the top cap. They live alongside the log textures at src/main/resources/assets/example_mod/textures/block/.
The block-entity render gotcha
Here is the trap, and it is worth understanding because it produces a baffling symptom. A plain RetroAPI block gets the model render type wired up for it automatically the moment it has a blockstate JSON: RetroAPI sets that render type on the base Block during registration, and the JSON-driven look just appears. The packed log relies on exactly this and overrides nothing.
But the freezer is a BlockWithEntity, and BlockWithEntity overrides getRenderType() to return its own block-entity default. That override shadows the value RetroAPI set on the base class, so the automatic wiring is silently lost. The block-entity block has to ask for the model render type back, itself:
public class ExampleFreezerBlock extends BlockWithEntity {
public static final RetroEnumProperty<Facing> FACING = RetroEnumProperty.of("facing", Facing.class);
public ExampleFreezerBlock(int id) {
super(id, Material.STONE);
}
@Override
public int getRenderType() {
// Render the orientable JSON model (in the world AND the inventory icon), not the
// block-entity default. See the class note on why this override is required here.
return RenderType.resolve(RenderTypes.MODEL);
}Without that override the freezer falls back to a featureless cube: no front face, no orientation, in the world and in the inventory icon. The fix is the four lines above, return RenderType.resolve(RenderTypes.MODEL) from getRenderType(), putting the block back on the JSON-model render type that a non-entity block would have gotten for free.
The rule of thumb: if your block has a block entity and a blockstate JSON you want it to honor, override getRenderType() to return RenderType.resolve(RenderTypes.MODEL). A block without a block entity never needs this; the log doesn't have it. RenderTypes is the same constants class from Chapter 4's pipe note, here exposing the model render type rather than a vanilla cube look.
The inventory icon faces a chosen way
One more wrinkle, and it follows from a single fact: the inventory icon, the thing in your hotbar and the creative menu, renders the block's default state. A placed freezer computes its facing from your yaw in onPlaced, but the held item never runs onPlaced, so it just draws whatever the default state says. If you leave the default at facing=north, the icon points its front away from the visible faces of the little isometric cube, and the freezer looks blank in the menu.
So the registration overrides the default state, purely to aim the icon:
FREEZER_BLOCK = RetroBlockAccess.of(new ExampleFreezerBlock(RetroBlockAccess.allocateId()))
.sounds(Block.STONE_SOUND_GROUP)
.strength(3.5f)
.states(ExampleFreezerBlock.FACING)
// The inventory icon renders the DEFAULT state. Face it south so the front lands
// on the visible bottom-left face of the iso cube (where the furnace shows its
// front); placed freezers ignore this and face the player (see onPlaced).
.defaultState(s -> s.with(ExampleFreezerBlock.FACING, Facing.SOUTH))
.register(id("freezer_block"));The .defaultState(...) call, the same optional hook Chapter 14 introduced, points the default at facing=south so the front lands on a face the isometric icon actually shows, right where the vanilla furnace shows its front. This is a cosmetic choice for the held item alone: a placed freezer never reads the default, because onPlaced immediately overwrites the facing with the one computed from your yaw. The icon and the world block are decided by two completely separate code paths, and .defaultState(...) only touches the icon.
Contrast the log, which doesn't call .defaultState(...) at all (its registration is just .states(ExampleLogBlock.AXIS) plus the usual sounds, strength, and .mineable(RetroTool.AXE)). It gets the right icon for free, because Y being the first enum constant already makes the upright pillar the default, which is exactly what you want to see in hand. Choosing the enum order well can save you a .defaultState(...) call entirely.
What we built
Two directional blocks, one pattern. Each declares a single enum state property whose constants serialize to lowercase names; each writes that property in onPlaced with one .with(...) and one RetroStates.set; and each gets its rotated look from a blockstate JSON that maps property values to models plus rotations, with zero render code in the block class. The log follows the clicked face and leans on its enum order for a free upright icon. The freezer faces the placer via fromYaw, overrides getRenderType() to survive the BlockWithEntity shadowing trap, and uses .defaultState(...) to aim the inventory icon at a visible face. Orientation, on beta, is just state and data.
These blocks were things to place and look at. Next we make things to swing: Chapter 18, the Toolbelt, builds the tools and weapons that break them.




