agentsclimarketplace

Fabric blocks

Skill PSB1234/Fabric-Minecraft-Skill-1.20.1/fabric-blocks

Full reference for advanced block development in Fabric Minecraft 1.20.1 — block entities, server-side and client-side tickers, container blocks (inventories/slots), custom block states and properties, named screen handler factories, and block entity data persistence. Use this skill whenever the user asks about block entities, BlockEntityTicker, implementing Inventory on a block, custom BlockState properties, block entity NBT, container blocks, BlockEntityProvider, or anything beyond a simple static block. Triggers on: "block entity", "BlockEntity", "ticker", "BlockEntityTicker", "container block", "inventory block", "block state property", "custom block state", "block NBT", "ImplementedInventory", "chest-like block", or "block entity renderer". Part of the fabric-mc-modding skill family.From its SKILL.md

Install
npx -y skills add PSB1234/Fabric-Minecraft-Skill-1.20.1 --skill fabric-blocks

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

10.9 KB, ~2.4k tokens by cl100k_base, as published. Nobody here has run it

Fabric Blocks — 1.20.1 Reference

For simple blocks (no inventory, no custom state), see fabric-mc-modding for registration. This skill covers block entities, tickers, containers, and custom block states.


Block Entity Basics

1. The Block class (must implement BlockEntityProvider)

public class MyBlock extends Block implements BlockEntityProvider {

    public MyBlock(Settings settings) {
        super(settings);
    }

    @Override
    public BlockEntity createBlockEntity(BlockPos pos, BlockState state) {
        return new MyBlockEntity(pos, state);
    }

    // Add ticker for server and/or client ticking
    @Override
    public <T extends BlockEntity> BlockEntityTicker<T> getTicker(
            World world, BlockState state, BlockEntityType<T> type) {
        // Server-side ticker only:
        if (!world.isClient) {
            return checkType(type, ModBlockEntities.MY_BLOCK_ENTITY, MyBlockEntity::tick);
        }
        return null;
    }

    // Allow right-click to open screen
    @Override
    public ActionResult onUse(BlockState state, World world, BlockPos pos,
            PlayerEntity player, Hand hand, BlockHitResult hit) {
        if (!world.isClient) {
            BlockEntity be = world.getBlockEntity(pos);
            if (be instanceof MyBlockEntity myBe) {
                player.openHandledScreen(myBe);
            }
        }
        return ActionResult.SUCCESS;
    }
}

2. Register the block and block entity type

// Block registration
public class ModBlocks {
    public static final Block MY_BLOCK = new MyBlock(
        FabricBlockSettings.create().hardness(3.5f).requiresTool()
    );

    public static void register() {
        Registry.register(Registries.BLOCK, new Identifier("mymod", "my_block"), MY_BLOCK);
        Registry.register(Registries.ITEM,  new Identifier("mymod", "my_block"),
            new BlockItem(MY_BLOCK, new FabricItemSettings()));
    }
}

// Block entity type registration
public class ModBlockEntities {
    public static BlockEntityType<MyBlockEntity> MY_BLOCK_ENTITY;

    public static void register() {
        MY_BLOCK_ENTITY = FabricBlockEntityTypeBuilder
            .create(MyBlockEntity::new, ModBlocks.MY_BLOCK)
            .build();
        Registry.register(Registries.BLOCK_ENTITY_TYPE,
            new Identifier("mymod", "my_block_entity"), MY_BLOCK_ENTITY);
    }
}

Block Entity with Ticker

public class MyBlockEntity extends BlockEntity {

    private int tickCount = 0;

    public MyBlockEntity(BlockPos pos, BlockState state) {
        super(ModBlockEntities.MY_BLOCK_ENTITY, pos, state);
    }

    // Static ticker method — called every server tick when block is loaded
    public static void tick(World world, BlockPos pos, BlockState state, MyBlockEntity be) {
        be.tickCount++;
        if (be.tickCount % 20 == 0) { // every second
            // do periodic work
        }
    }

    // Persist data to NBT
    @Override
    protected void writeNbt(NbtCompound nbt) {
        super.writeNbt(nbt);
        nbt.putInt("tick_count", tickCount);
    }

    @Override
    public void readNbt(NbtCompound nbt) {
        super.readNbt(nbt);
        tickCount = nbt.getInt("tick_count");
    }
}

Container Block Entity (Inventory)

Implements ImplementedInventory from Fabric API for easy Inventory support:

public class MyContainerBlockEntity extends BlockEntity
        implements NamedScreenHandlerFactory, ImplementedInventory {

    private final DefaultedList<ItemStack> inventory =
        DefaultedList.ofSize(9, ItemStack.EMPTY);

    public MyContainerBlockEntity(BlockPos pos, BlockState state) {
        super(ModBlockEntities.MY_CONTAINER, pos, state);
    }

    // ImplementedInventory — provide your backing list
    @Override
    public DefaultedList<ItemStack> getItems() {
        return inventory;
    }

    // NamedScreenHandlerFactory — open GUI on right-click
    @Override
    public Text getDisplayName() {
        return Text.translatable("container.mymod.my_container");
    }

    @Override
    public ScreenHandler createMenu(int syncId, PlayerInventory inv, PlayerEntity player) {
        return new MyScreenHandler(syncId, inv, this);
    }

    // Sync to client on chunk load
    @Override
    public NbtCompound toInitialChunkDataNbt() {
        return createNbt();
    }

    @Override
    public Packet<ClientPlayPacketListener> toUpdatePacket() {
        return BlockEntityUpdateS2CPacket.create(this);
    }

    @Override
    protected void writeNbt(NbtCompound nbt) {
        super.writeNbt(nbt);
        Inventories.writeNbt(nbt, inventory);
    }

    @Override
    public void readNbt(NbtCompound nbt) {
        super.readNbt(nbt);
        Inventories.readNbt(nbt, inventory);
    }
}

Dropping inventory on block break

In your Block class:

@Override
public void onStateReplaced(BlockState state, World world, BlockPos pos,
        BlockState newState, boolean moved) {
    if (!state.isOf(newState.getBlock())) {
        BlockEntity be = world.getBlockEntity(pos);
        if (be instanceof MyContainerBlockEntity container) {
            ItemScatterer.scatter(world, pos, container);
            world.updateComparators(pos, this);
        }
        super.onStateReplaced(state, world, pos, newState, moved);
    }
}

Custom Block State Properties

Defining properties

public class MyDirectionalBlock extends Block implements BlockEntityProvider {

    // Boolean property
    public static final BooleanProperty POWERED =
        BooleanProperty.of("powered");

    // Integer property (0–3)
    public static final IntProperty LEVEL =
        IntProperty.of("level", 0, 3);

    // Direction facing (horizontal)
    public static final DirectionProperty FACING =
        Properties.HORIZONTAL_FACING; // north/south/east/west

    // Add properties to the state definition
    @Override
    protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
        builder.add(POWERED, LEVEL, FACING);
    }

    // Set default state in constructor
    public MyDirectionalBlock(Settings settings) {
        super(settings);
        setDefaultState(getStateManager().getDefaultState()
            .with(POWERED, false)
            .with(LEVEL, 0)
            .with(FACING, Direction.NORTH));
    }

    // Set facing from player placement
    @Override
    public BlockState getPlacementState(ItemPlacementContext ctx) {
        return getDefaultState()
            .with(FACING, ctx.getHorizontalPlayerFacing().getOpposite());
    }
}

Updating state

world.setBlockState(pos,
    world.getBlockState(pos)
        .with(MyDirectionalBlock.POWERED, true)
        .with(MyDirectionalBlock.LEVEL, 2),
    Block.NOTIFY_ALL
);

Blockstate JSON for Custom Properties

assets/mymod/blockstates/my_directional_block.json:

{
  "variants": {
    "facing=north,powered=false": { "model": "mymod:block/my_block" },
    "facing=south,powered=false": { "model": "mymod:block/my_block", "y": 180 },
    "facing=east,powered=false":  { "model": "mymod:block/my_block", "y": 90 },
    "facing=west,powered=false":  { "model": "mymod:block/my_block", "y": 270 },
    "facing=north,powered=true":  { "model": "mymod:block/my_block_on" },
    "facing=south,powered=true":  { "model": "mymod:block/my_block_on", "y": 180 },
    "facing=east,powered=true":   { "model": "mymod:block/my_block_on", "y": 90 },
    "facing=west,powered=true":   { "model": "mymod:block/my_block_on", "y": 270 }
  }
}

Comparator Output

// In your block class:
@Override
public boolean hasComparatorOutput(BlockState state) { return true; }

@Override
public int getComparatorOutput(BlockState state, World world, BlockPos pos) {
    return ScreenHandler.calculateComparatorOutput(world.getBlockEntity(pos));
}

Block Entity Energy / Fluid (via Fabric API transfer)

For blocks that store energy or fluid, use the Fabric Transfer API:

// In block entity:
private final SingleVariantStorage<FluidVariant> fluidStorage =
    new SingleVariantStorage<>() {
        @Override protected FluidVariant getBlankVariant() { return FluidVariant.blank(); }
        @Override protected long getCapacity(FluidVariant v) { return FluidConstants.BUCKET * 4; }
        @Override protected void onFinalCommit() { markDirty(); }
    };

// Expose via API (in block entity):
// Implement Storage<FluidVariant> and register with FluidStorage.SIDED

See the Fabric Transfer API docs for full patterns.


Common Mistakes

  • Block entity type registered with wrong blockFabricBlockEntityTypeBuilder.create(..., MY_BLOCK) must list every block that uses this entity type.
  • Ticker returns null on client — if you want client ticking (e.g. for animations), check world.isClient and return a client ticker separately.
  • NBT not savingwriteNbt and readNbt must call super and all data must be written/read symmetrically.
  • Screen not opening — block must return ActionResult.SUCCESS from onUse, and block entity must implement NamedScreenHandlerFactory.
  • Inventory not persisting — use Inventories.writeNbt/readNbt helpers; don't write slots manually.

Scripts

ScriptWhat it does
scripts/add_block_entity.pyScaffold a BlockEntity + its host Block class
# Basic block entity
python scripts/add_block_entity.py --mod-id mymod --group com.example --id my_machine

# With server tick and 9-slot inventory
python scripts/add_block_entity.py --mod-id mymod --group com.example --id my_furnace --ticker --inventory 9

Assets (Templates)

FileUse for
assets/blockstate_simple.jsonSingle-variant blockstate (cube block)
assets/model_block_entity.jsonBlock model with particle texture (for BlockWithEntity)

Related Skills

  • fabric-mc-modding — basic block/item registration
  • fabric-rendering — BlockEntityRenderer for visual effects
  • fabric-networking — syncing block entity data to clients
  • fabric-datagen — generating loot tables, models, tags for blocks
  • minecraft-json-assets — writing blockstate/model JSON by hand

What ships with it: 3 files

6.1 KB alongside SKILL.md, 1 of them executable

scripts/

Keep looking

Skills are one crate of 326,861. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.