Quick Start Guide

This guide will help you get started with fabricpy quickly.

Installation

First, ensure you have Python 3.10+ installed, then install fabricpy:

pip install fabricpy

External Requirements

Before using fabricpy, you need to install these external dependencies:

1. Java Development Kit (JDK)

  • Version Required: JDK 17 or higher (recommended JDK 21)

  • Purpose: Compiles the generated Minecraft Fabric mod code

  • Installation:
    • macOS: brew install openjdk@21 or download from Oracle

    • Windows: Download from Oracle or use winget install Oracle.JDK.21

    • Linux: sudo apt install openjdk-21-jdk (Ubuntu/Debian) or sudo yum install java-21-openjdk-devel (CentOS/RHEL)

2. Git

  • Version Required: 2.0 or higher

  • Purpose: Version control and cloning Fabric mod templates

  • Installation:
    • macOS: brew install git or install Xcode Command Line Tools

    • Windows: Download from git-scm.com

    • Linux: sudo apt install git (Ubuntu/Debian) or sudo yum install git (CentOS/RHEL)

Creating Your First Mod

Here’s a complete example of creating a simple mod:

import fabricpy

# Create the mod configuration
mod = fabricpy.ModConfig(
    mod_id="tutorial_mod",
    name="Tutorial Mod",
    version="1.0.0",
    description="My first fabricpy mod",
    authors=["Your Name"]
)

# Create a simple item
ruby = fabricpy.Item(
    id="tutorial_mod:ruby",
    name="Ruby",
    item_group=fabricpy.item_group.INGREDIENTS
)

# Create a tool item
ruby_pickaxe = fabricpy.ToolItem(
    id="tutorial_mod:ruby_pickaxe",
    name="Ruby Pickaxe",
    durability=500,
    mining_speed_multiplier=8.0,
    attack_damage=3.0,
    mining_level=2,
    enchantability=22,
    repair_ingredient="tutorial_mod:ruby",
    item_group=fabricpy.item_group.TOOLS,
)

# Create a food item
ruby_apple = fabricpy.FoodItem(
    id="tutorial_mod:ruby_apple",
    name="Ruby Apple",
    nutrition=6,
    saturation=12.0,
    item_group=fabricpy.item_group.FOOD_AND_DRINK
)

# Create a block
ruby_block = fabricpy.Block(
    id="tutorial_mod:ruby_block",
    name="Ruby Block",
    item_group=fabricpy.item_group.BUILDING_BLOCKS,
    loot_table=fabricpy.LootTable.drops_self("tutorial_mod:ruby_block"),
)

# Create an ore block with mining configuration
ruby_ore = fabricpy.Block(
    id="tutorial_mod:ruby_ore",
    name="Ruby Ore",
    hardness=3.0,
    resistance=3.0,
    tool_type="pickaxe",
    mining_level="iron",
    item_group=fabricpy.item_group.NATURAL,
    loot_table=fabricpy.LootTable.drops_with_fortune(
        "tutorial_mod:ruby_ore", "tutorial_mod:ruby",
        min_count=1, max_count=2,
    ),
)

# Register all items and blocks
mod.registerItem(ruby)
mod.registerItem(ruby_pickaxe)
mod.registerFoodItem(ruby_apple)
mod.registerBlock(ruby_block)
mod.registerBlock(ruby_ore)

# Compile and run the mod
mod.compile()
mod.run()

Next Steps

  • Learn about creating recipes (see the RecipeJson class in the API reference)

  • Use the Crafting Recipe Generator to easily create crafting recipe JSON files with a visual interface

  • Configure block mining with hardness, tool types, mining levels, and per-tool speeds (see the Creating Blocks guide)

  • Define loot tables for your blocks (see the Loot Tables guide)

  • Understand custom creative tabs (see the ItemGroup class in the API reference)

  • Explore the complete API reference

Examples

Example scripts can be found in the examples directory.

Minimal mod — one item, one block
"""Beginner example — create a simple mod with one item and one block.

This is the smallest useful mod you can build with fabricpy.  It registers
a single item and a single block, then compiles the project.  Run this
script and you will have a fully buildable Fabric mod in the output
directory.
"""

import fabricpy

# 1. Create the mod configuration ──────────────────────────────────────
mod = fabricpy.ModConfig(
    mod_id="basic_mod",
    name="Basic Mod",
    version="1.0.0",
    description="A minimal fabricpy example",
    authors=["Your Name"],
    project_dir="basic-mod-output",
)

# 2. Register a simple item ───────────────────────────────────────────
gem = fabricpy.Item(
    id="basic_mod:ruby",
    name="Ruby",
    max_stack_size=64,
    item_group=fabricpy.item_group.INGREDIENTS,
)
mod.registerItem(gem)

# 3. Register a simple block ──────────────────────────────────────────
block = fabricpy.Block(
    id="basic_mod:ruby_block",
    name="Ruby Block",
    item_group=fabricpy.item_group.BUILDING_BLOCKS,
    loot_table=fabricpy.LootTable.drops_self("basic_mod:ruby_block"),
)
mod.registerBlock(block)

# 4. Compile — generates a complete Fabric mod project ─────────────────
# mod.compile()
# mod.run()     # optional: launch a dev client
Food items with nutrition, smelting, and always_edible
"""Example demonstrating FoodItem creation with recipes.

Shows how to create food items with varying nutrition, saturation,
always-edible behaviour, and crafting recipes.
"""

import fabricpy

mod = fabricpy.ModConfig(
    mod_id="food_mod",
    name="Food Mod",
    version="1.0.0",
    description="Demonstrates FoodItem usage",
    authors=["Example Dev"],
)

# ── 1. Simple food ──────────────────────────────────────────────────── #

honey_bread = fabricpy.FoodItem(
    id="food_mod:honey_bread",
    name="Honey Bread",
    nutrition=7,
    saturation=8.0,
    item_group=fabricpy.item_group.FOOD_AND_DRINK,
)
mod.registerFoodItem(honey_bread)

# ── 2. Always-edible snack (can eat even when full) ─────────────────── #

magic_berry = fabricpy.FoodItem(
    id="food_mod:magic_berry",
    name="Magic Berry",
    nutrition=2,
    saturation=1.0,
    always_edible=True,
    item_group=fabricpy.item_group.FOOD_AND_DRINK,
)
mod.registerFoodItem(magic_berry)

# ── 3. Food with a crafting recipe ──────────────────────────────────── #

golden_carrot_recipe = fabricpy.RecipeJson(
    {
        "type": "minecraft:crafting_shaped",
        "pattern": ["GGG", "GCG", "GGG"],
        "key": {
            "G": "minecraft:gold_nugget",
            "C": "minecraft:carrot",
        },
        "result": {"id": "food_mod:super_golden_carrot", "count": 1},
    }
)

super_golden_carrot = fabricpy.FoodItem(
    id="food_mod:super_golden_carrot",
    name="Super Golden Carrot",
    nutrition=10,
    saturation=14.4,
    always_edible=True,
    recipe=golden_carrot_recipe,
    item_group=fabricpy.item_group.FOOD_AND_DRINK,
)
mod.registerFoodItem(super_golden_carrot)

# ── 4. Food with a smelting recipe (cooked item) ───────────────────── #

raw_venison = fabricpy.FoodItem(
    id="food_mod:raw_venison",
    name="Raw Venison",
    nutrition=3,
    saturation=1.8,
    item_group=fabricpy.item_group.FOOD_AND_DRINK,
)
mod.registerFoodItem(raw_venison)

cooked_venison_recipe = fabricpy.RecipeJson(
    {
        "type": "minecraft:smelting",
        "ingredient": "food_mod:raw_venison",
        "result": {"id": "food_mod:cooked_venison", "count": 1},
        "experience": 0.35,
        "cookingtime": 200,
    }
)

cooked_venison = fabricpy.FoodItem(
    id="food_mod:cooked_venison",
    name="Cooked Venison",
    nutrition=8,
    saturation=12.8,
    recipe=cooked_venison_recipe,
    item_group=fabricpy.item_group.FOOD_AND_DRINK,
)
mod.registerFoodItem(cooked_venison)

# ── 5. Low-stack gourmet food ──────────────────────────────────────── #

feast_platter = fabricpy.FoodItem(
    id="food_mod:feast_platter",
    name="Feast Platter",
    nutrition=20,
    saturation=20.0,
    max_stack_size=1,
    always_edible=True,
    item_group=fabricpy.item_group.FOOD_AND_DRINK,
)
mod.registerFoodItem(feast_platter)

# Compile the mod
# mod.compile()
# mod.run()  # optional: launch a dev client
Blocks with recipes, textures, and click events
"""Example demonstrating blocks with recipes, textures, and click events.

Covers shaped/shapeless crafting recipes for blocks, separate block and
inventory textures, and blocks that react to player clicks using the
message helpers.
"""

import fabricpy
from fabricpy.message import send_action_bar_message, send_message

mod = fabricpy.ModConfig(
    mod_id="blocks_mod",
    name="Blocks Mod",
    version="1.0.0",
    description="Demonstrates Block features",
    authors=["Example Dev"],
)

# ── 1. Storage block with a shaped recipe ───────────────────────────── #

ruby_block_recipe = fabricpy.RecipeJson(
    {
        "type": "minecraft:crafting_shaped",
        "pattern": ["RRR", "RRR", "RRR"],
        "key": {"R": "blocks_mod:ruby"},
        "result": {"id": "blocks_mod:ruby_block", "count": 1},
    }
)

ruby_block = fabricpy.Block(
    id="blocks_mod:ruby_block",
    name="Block of Ruby",
    recipe=ruby_block_recipe,
    item_group=fabricpy.item_group.BUILDING_BLOCKS,
    loot_table=fabricpy.LootTable.drops_self("blocks_mod:ruby_block"),
)
mod.registerBlock(ruby_block)

# ── 2. Block with separate block and inventory textures ─────────────── #

magic_lamp = fabricpy.Block(
    id="blocks_mod:magic_lamp",
    name="Magic Lamp",
    block_texture_path="textures/blocks/magic_lamp.png",
    inventory_texture_path="textures/items/magic_lamp_item.png",
    item_group=fabricpy.item_group.FUNCTIONAL,
)
mod.registerBlock(magic_lamp)

# ── 3. Block with click events using message helpers ────────────────── #

info_block = fabricpy.Block(
    id="blocks_mod:info_block",
    name="Info Block",
    left_click_event=send_message("You punched the Info Block!"),
    right_click_event=send_action_bar_message("Right-clicked!"),
    item_group=fabricpy.item_group.REDSTONE,
)
mod.registerBlock(info_block)

# ── 4. Block with click events via subclass ─────────────────────────── #


class CounterBlock(fabricpy.Block):
    """A block that tells you when it is clicked."""

    def __init__(self):
        super().__init__(
            id="blocks_mod:counter_block",
            name="Counter Block",
            item_group=fabricpy.item_group.REDSTONE,
        )

    def on_left_click(self):
        return send_message("Counter: left click registered")

    def on_right_click(self):
        return send_action_bar_message("Counter: right click registered")


counter = CounterBlock()
mod.registerBlock(counter)

# ── 5. Block with a shapeless recipe (un-crafting) ──────────────────── #

ruby_decompose_recipe = fabricpy.RecipeJson(
    {
        "type": "minecraft:crafting_shapeless",
        "ingredients": ["blocks_mod:ruby_block"],
        "result": {"id": "blocks_mod:ruby", "count": 9},
    }
)

# The recipe is for the ruby item, but we attach it to a standalone item
ruby_item = fabricpy.Item(
    id="blocks_mod:ruby",
    name="Ruby",
    recipe=ruby_decompose_recipe,
    item_group=fabricpy.item_group.INGREDIENTS,
)
mod.registerItem(ruby_item)

# Compile the mod
# mod.compile()
# mod.run()     # optional: launch a dev client
Custom creative tabs
"""Example demonstrating custom creative tabs (ItemGroups).

Shows how to create your own creative inventory tabs and assign items
and blocks to them so players can find your content easily.
"""

import fabricpy

mod = fabricpy.ModConfig(
    mod_id="tabs_mod",
    name="Custom Tabs Mod",
    version="1.0.0",
    description="Demonstrates custom ItemGroup creation",
    authors=["Example Dev"],
)

# ── 1. Create custom creative tabs ──────────────────────────────────── #

gems_tab = fabricpy.ItemGroup(id="gems", name="Gems & Minerals")
magic_tab = fabricpy.ItemGroup(id="magic", name="Magic Items")

# ── 2. Items in the Gems tab ────────────────────────────────────────── #

ruby = fabricpy.Item(
    id="tabs_mod:ruby",
    name="Ruby",
    item_group=gems_tab,
)
mod.registerItem(ruby)

sapphire = fabricpy.Item(
    id="tabs_mod:sapphire",
    name="Sapphire",
    item_group=gems_tab,
)
mod.registerItem(sapphire)

topaz = fabricpy.Item(
    id="tabs_mod:topaz",
    name="Topaz",
    item_group=gems_tab,
)
mod.registerItem(topaz)

# Blocks also live in the Gems tab
ruby_block = fabricpy.Block(
    id="tabs_mod:ruby_block",
    name="Block of Ruby",
    item_group=gems_tab,
    loot_table=fabricpy.LootTable.drops_self("tabs_mod:ruby_block"),
)
mod.registerBlock(ruby_block)

# ── 3. Items in the Magic tab ───────────────────────────────────────── #

wand = fabricpy.Item(
    id="tabs_mod:wand",
    name="Magic Wand",
    max_stack_size=1,
    item_group=magic_tab,
)
mod.registerItem(wand)

enchanted_apple = fabricpy.FoodItem(
    id="tabs_mod:enchanted_apple",
    name="Enchanted Apple",
    nutrition=8,
    saturation=12.0,
    always_edible=True,
    item_group=magic_tab,
)
mod.registerFoodItem(enchanted_apple)

# ── 4. Mix custom and vanilla tabs ──────────────────────────────────── #

# Some items can still go into vanilla tabs
pickaxe = fabricpy.ToolItem(
    id="tabs_mod:ruby_pickaxe",
    name="Ruby Pickaxe",
    durability=750,
    mining_speed_multiplier=8.0,
    attack_damage=3.0,
    mining_level=2,
    enchantability=22,
    repair_ingredient="tabs_mod:ruby",
    item_group=fabricpy.item_group.TOOLS,  # vanilla tab
)
mod.registerItem(pickaxe)

# Compile the mod
# mod.compile()
# mod.run()  # optional: launch a dev client
Defining a custom ToolItem
"""Minimal example demonstrating a ToolItem."""

import fabricpy

# Configure the mod
mod = fabricpy.ModConfig(
    mod_id="example_tools",
    name="Example Tools",
    version="1.0.0",
    description="Demonstrates ToolItem usage",
    authors=["Example Dev"],
)

# Define a ruby pickaxe tool
ruby_pickaxe = fabricpy.ToolItem(
    id="example_tools:ruby_pickaxe",
    name="Ruby Pickaxe",
    durability=500,
    mining_speed_multiplier=8.0,
    attack_damage=3.0,
    mining_level=2,
    enchantability=22,
    repair_ingredient="minecraft:ruby",
    item_group=fabricpy.item_group.TOOLS,
)

# Register the tool with the mod
mod.registerItem(ruby_pickaxe)
Loot table patterns (self-drops, fortune, silk touch, entity & chest)
"""Example demonstrating loot table usage with blocks.

Shows common loot table patterns including self-drops, ore-style
fortune drops, silk-touch behaviour, and entity loot tables.
"""

import fabricpy

# Configure the mod
mod = fabricpy.ModConfig(
    mod_id="example_loot",
    name="Example Loot Tables",
    version="1.0.0",
    description="Demonstrates LootTable usage",
    authors=["Example Dev"],
)

# ── 1. Block that drops itself ──────────────────────────────────────── #

ruby_block = fabricpy.Block(
    id="example_loot:ruby_block",
    name="Ruby Block",
    item_group=fabricpy.item_group.BUILDING_BLOCKS,
    loot_table=fabricpy.LootTable.drops_self("example_loot:ruby_block"),
    tool_type="pickaxe",
)
mod.registerBlock(ruby_block)

# ── 2. Ore with fortune-affected drops ──────────────────────────────── #

ruby_ore = fabricpy.Block(
    id="example_loot:ruby_ore",
    name="Ruby Ore",
    item_group=fabricpy.item_group.NATURAL,
    loot_table=fabricpy.LootTable.drops_with_fortune(
        "example_loot:ruby_ore",
        "minecraft:diamond",  # use a vanilla item that exists
        min_count=1,
        max_count=2,
    ),
    tool_type="pickaxe",
)
mod.registerBlock(ruby_ore)

# ── 3. Glass-style silk-touch-only block ────────────────────────────── #

crystal_glass = fabricpy.Block(
    id="example_loot:crystal_glass",
    name="Crystal Glass",
    item_group=fabricpy.item_group.BUILDING_BLOCKS,
    loot_table=fabricpy.LootTable.drops_with_silk_touch("example_loot:crystal_glass"),
    tool_type="pickaxe",
)
mod.registerBlock(crystal_glass)

# ── 4. Block that drops a different item ────────────────────────────── #

clay_block = fabricpy.Block(
    id="example_loot:clay_block",
    name="Clay Block",
    item_group=fabricpy.item_group.NATURAL,
    loot_table=fabricpy.LootTable.drops_item(
        "example_loot:clay_block",
        "minecraft:clay_ball",  # use the vanilla clay ball item
        min_count=2,
        max_count=4,
    ),
    tool_type="shovel",
)
mod.registerBlock(clay_block)

# ── 5. Block that drops nothing ─────────────────────────────────────── #

infested_block = fabricpy.Block(
    id="example_loot:infested_stone",
    name="Infested Stone",
    item_group=fabricpy.item_group.NATURAL,
    loot_table=fabricpy.LootTable.drops_nothing(),
)
mod.registerBlock(infested_block)

# ── 6. Entity loot table using the pool builder ─────────────────────── #

zombie_loot = fabricpy.LootTable.entity(
    [
        fabricpy.LootPool()
        .rolls(1)
        .entry("minecraft:bone", weight=3)
        .entry("minecraft:spider_eye", weight=1)
        .condition({"condition": "minecraft:survives_explosion"})
    ]
)
mod.registerLootTable("custom_zombie", zombie_loot)

# ── 7. Chest loot table with random rolls ───────────────────────────── #

dungeon_chest = fabricpy.LootTable.chest(
    [
        fabricpy.LootPool()
        .rolls({"type": "minecraft:uniform", "min": 2, "max": 5})
        .entry("minecraft:gold_ingot", weight=5)
        .entry("minecraft:diamond", weight=1)
        .entry("minecraft:iron_ingot", weight=10)
    ]
)
mod.registerLootTable("dungeon_chest", dungeon_chest)

# Compile the mod
# mod.compile()
# mod.run()  # optional: launch a dev client
Mining config u2014 hardness, tool types, mining levels, per-tool speeds
"""Example demonstrating block mining configuration.

Shows how to use the mining-related Block properties:

* ``hardness`` / ``resistance`` — control break time and blast durability
* ``tool_type`` — which tool mines the block efficiently
* ``mining_level`` — minimum tool tier (stone / iron / diamond)
* ``requires_tool`` — whether the correct tool is needed for drops
* ``mining_speeds`` — per-tool-type speed overrides
"""

import fabricpy

mod = fabricpy.ModConfig(
    mod_id="mining_demo",
    name="Mining Demo",
    version="1.0.0",
    description="Demonstrates mining configuration",
    authors=["Example Dev"],
)

# ── 0. Ruby item — the drop for ruby ore ────────────────────────────── #

ruby = fabricpy.Item(
    id="mining_demo:ruby",
    name="Ruby",
    item_group=fabricpy.item_group.INGREDIENTS,
)
mod.registerItem(ruby)

# ── 1. Simple ore — pickaxe required, iron tier ─────────────────────── #

ruby_ore = fabricpy.Block(
    id="mining_demo:ruby_ore",
    name="Ruby Ore",
    hardness=3.0,
    resistance=3.0,
    tool_type="pickaxe",
    mining_level="iron",  # needs at least an iron pickaxe
    item_group=fabricpy.item_group.NATURAL,
    loot_table=fabricpy.LootTable.drops_with_fortune(
        "mining_demo:ruby_ore",
        "mining_demo:ruby",
        min_count=1,
        max_count=3,
    ),
)
mod.registerBlock(ruby_ore)

# ── 2. Tough block — high hardness, diamond tier ────────────────────── #

reinforced_block = fabricpy.Block(
    id="mining_demo:reinforced_block",
    name="Reinforced Block",
    hardness=25.0,  # very slow to mine
    resistance=600.0,  # survives most explosions
    tool_type="pickaxe",
    mining_level="diamond",
    item_group=fabricpy.item_group.BUILDING_BLOCKS,
    loot_table=fabricpy.LootTable.drops_self("mining_demo:reinforced_block"),
)
mod.registerBlock(reinforced_block)

# ── 3. Multi-tool block — mineable efficiently by multiple tools ─────── #

mixed_ore = fabricpy.Block(
    id="mining_demo:mixed_ore",
    name="Mixed Ore",
    hardness=4.0,
    resistance=4.0,
    mining_level="stone",
    requires_tool=True,
    mining_speeds={  # pickaxe is fastest, shovel also works
        "pickaxe": 8.0,
        "shovel": 3.0,
    },
    item_group=fabricpy.item_group.NATURAL,
    loot_table=fabricpy.LootTable.drops_self("mining_demo:mixed_ore"),
)
mod.registerBlock(mixed_ore)

# ── 4. Soft block — no tool required, breaks quickly ────────────────── #

soft_block = fabricpy.Block(
    id="mining_demo:soft_block",
    name="Soft Block",
    hardness=0.5,
    resistance=0.5,
    # No tool_type → any tool works, drops always
    item_group=fabricpy.item_group.BUILDING_BLOCKS,
    loot_table=fabricpy.LootTable.drops_self("mining_demo:soft_block"),
)
mod.registerBlock(soft_block)

# ── 5. Shovel block with per-tool overrides ──────────────────────────── #

gravel_ore = fabricpy.Block(
    id="mining_demo:gravel_ore",
    name="Gravel Ore",
    hardness=1.0,
    resistance=1.0,
    tool_type="shovel",
    mining_speeds={
        "shovel": 6.0,
        "pickaxe": 1.5,  # pickaxe marginally helps
    },
    item_group=fabricpy.item_group.NATURAL,
)
mod.registerBlock(gravel_ore)

# ── Compile ──────────────────────────────────────────────────────────── #

# Uncomment to generate the mod project:
mod.compile()
mod.run()  # optional: launch a dev client with the mod loaded
Complete mod — all features combined
"""Full mod example — a complete "Ruby" content mod.

Ties together every fabricpy feature into one cohesive mod:

* Custom creative tab
* Raw material item + gem item
* Tool items (pickaxe and sword)
* Food items (raw + cooked via smelting recipe)
* Ore block with fortune-affected loot and mining configuration
* Deepslate ore variant (harder, same mining level)
* Multi-tool ore with per-tool mining speed overrides
* Storage block with shaped crafting recipe and self-drop loot
* Glass-style decorative block with silk-touch loot
* Interactive block with click events
* Entity loot table registered standalone

This script is intended as a reference for real mods.
"""

import fabricpy
from fabricpy.message import send_message

# ── Mod setup ────────────────────────────────────────────────────────── #

mod = fabricpy.ModConfig(
    mod_id="ruby_mod",
    name="Ruby Mod",
    version="1.0.0",
    description="A complete content mod adding ruby items, tools, and blocks",
    authors=["Example Dev"],
    project_dir="ruby-mod-output",
)

# ── Custom creative tab ─────────────────────────────────────────────── #

ruby_tab = fabricpy.ItemGroup(id="ruby_items", name="Ruby")

# ── Items ────────────────────────────────────────────────────────────── #

ruby = fabricpy.Item(
    id="ruby_mod:ruby",
    name="Ruby",
    item_group=ruby_tab,
)
mod.registerItem(ruby)

raw_ruby = fabricpy.Item(
    id="ruby_mod:raw_ruby",
    name="Raw Ruby",
    item_group=ruby_tab,
)
mod.registerItem(raw_ruby)

# ── Tools ────────────────────────────────────────────────────────────── #

ruby_pickaxe = fabricpy.ToolItem(
    id="ruby_mod:ruby_pickaxe",
    name="Ruby Pickaxe",
    durability=750,
    mining_speed_multiplier=8.0,
    attack_damage=3.0,
    mining_level=2,
    enchantability=22,
    repair_ingredient="ruby_mod:ruby",
    recipe=fabricpy.RecipeJson(
        {
            "type": "minecraft:crafting_shaped",
            "pattern": ["RRR", " S ", " S "],
            "key": {
                "R": "ruby_mod:ruby",
                "S": "minecraft:stick",
            },
            "result": {"id": "ruby_mod:ruby_pickaxe", "count": 1},
        }
    ),
    item_group=fabricpy.item_group.TOOLS,
)
mod.registerItem(ruby_pickaxe)

ruby_sword = fabricpy.ToolItem(
    id="ruby_mod:ruby_sword",
    name="Ruby Sword",
    durability=500,
    attack_damage=7.0,
    enchantability=22,
    repair_ingredient="ruby_mod:ruby",
    recipe=fabricpy.RecipeJson(
        {
            "type": "minecraft:crafting_shaped",
            "pattern": [" R ", " R ", " S "],
            "key": {
                "R": "ruby_mod:ruby",
                "S": "minecraft:stick",
            },
            "result": {"id": "ruby_mod:ruby_sword", "count": 1},
        }
    ),
    item_group=fabricpy.item_group.COMBAT,
)
mod.registerItem(ruby_sword)

# ── Food ─────────────────────────────────────────────────────────────── #

raw_ruby_apple = fabricpy.FoodItem(
    id="ruby_mod:raw_ruby_apple",
    name="Raw Ruby Apple",
    nutrition=3,
    saturation=1.5,
    item_group=fabricpy.item_group.FOOD_AND_DRINK,
)
mod.registerFoodItem(raw_ruby_apple)

cooked_ruby_apple = fabricpy.FoodItem(
    id="ruby_mod:cooked_ruby_apple",
    name="Cooked Ruby Apple",
    nutrition=8,
    saturation=12.0,
    always_edible=True,
    recipe=fabricpy.RecipeJson(
        {
            "type": "minecraft:smelting",
            "ingredient": "ruby_mod:raw_ruby_apple",
            "result": {"id": "ruby_mod:cooked_ruby_apple", "count": 1},
            "experience": 0.35,
            "cookingtime": 200,
        }
    ),
    item_group=fabricpy.item_group.FOOD_AND_DRINK,
)
mod.registerFoodItem(cooked_ruby_apple)

# ── Blocks ───────────────────────────────────────────────────────────── #

# Ore — drops raw ruby with fortune, or itself with silk touch
ruby_ore = fabricpy.Block(
    id="ruby_mod:ruby_ore",
    name="Ruby Ore",
    hardness=3.0,
    resistance=3.0,
    tool_type="pickaxe",
    mining_level="iron",
    item_group=fabricpy.item_group.NATURAL,
    loot_table=fabricpy.LootTable.drops_with_fortune(
        "ruby_mod:ruby_ore",
        "ruby_mod:raw_ruby",
        min_count=1,
        max_count=3,
    ),
)
mod.registerBlock(ruby_ore)

# Deepslate ore variant — harder and requires iron
deepslate_ruby_ore = fabricpy.Block(
    id="ruby_mod:deepslate_ruby_ore",
    name="Deepslate Ruby Ore",
    hardness=4.5,
    resistance=3.0,
    tool_type="pickaxe",
    mining_level="iron",
    item_group=fabricpy.item_group.NATURAL,
    loot_table=fabricpy.LootTable.drops_with_fortune(
        "ruby_mod:deepslate_ruby_ore",
        "ruby_mod:raw_ruby",
        min_count=1,
        max_count=3,
    ),
)
mod.registerBlock(deepslate_ruby_ore)

# Multi-tool ore — per-tool speed overrides, stone tier
mixed_ruby_ore = fabricpy.Block(
    id="ruby_mod:mixed_ruby_ore",
    name="Mixed Ruby Ore",
    hardness=4.0,
    resistance=4.0,
    requires_tool=True,
    mining_level="stone",
    mining_speeds={
        "pickaxe": 8.0,  # fastest
        "shovel": 3.0,  # slower but still works
    },
    item_group=fabricpy.item_group.NATURAL,
    loot_table=fabricpy.LootTable.drops_self("ruby_mod:mixed_ruby_ore"),
)
mod.registerBlock(mixed_ruby_ore)

# Storage block — craft 9 rubies into a block, drops itself
ruby_block = fabricpy.Block(
    id="ruby_mod:ruby_block",
    name="Block of Ruby",
    recipe=fabricpy.RecipeJson(
        {
            "type": "minecraft:crafting_shaped",
            "pattern": ["RRR", "RRR", "RRR"],
            "key": {"R": "ruby_mod:ruby"},
            "result": {"id": "ruby_mod:ruby_block", "count": 1},
        }
    ),
    item_group=ruby_tab,
    loot_table=fabricpy.LootTable.drops_self("ruby_mod:ruby_block"),
)
mod.registerBlock(ruby_block)

# Decorative glass — only drops with silk touch
ruby_glass = fabricpy.Block(
    id="ruby_mod:ruby_glass",
    name="Ruby Glass",
    item_group=ruby_tab,
    loot_table=fabricpy.LootTable.drops_with_silk_touch("ruby_mod:ruby_glass"),
)
mod.registerBlock(ruby_glass)


# Interactive altar block
class RubyAltar(fabricpy.Block):
    """An altar that reacts to player interaction and destruction."""

    def __init__(self):
        super().__init__(
            id="ruby_mod:ruby_altar",
            name="Ruby Altar",
            item_group=ruby_tab,
            loot_table=fabricpy.LootTable.drops_self("ruby_mod:ruby_altar"),
        )

    def on_right_click(self):
        return send_message("The altar hums with energy...")

    def on_left_click(self):
        return send_message("The altar resists your blow!")

    def on_break(self):
        return send_message("The altar crumbles and its energy fades...")


altar = RubyAltar()
mod.registerBlock(altar)

# ── Standalone loot tables ──────────────────────────────────────────── #

# Ruby golem entity drops
ruby_golem_loot = fabricpy.LootTable.entity(
    [
        fabricpy.LootPool()
        .rolls(1)
        .entry("ruby_mod:ruby", weight=5)
        .entry("ruby_mod:raw_ruby", weight=3),
        fabricpy.LootPool()
        .rolls({"type": "minecraft:uniform", "min": 0, "max": 2})
        .entry("ruby_mod:ruby_block", weight=1),
    ]
)
mod.registerLootTable("ruby_golem", ruby_golem_loot)

# Decompose block back into rubies (shapeless)
ruby_decompose = fabricpy.Item(
    id="ruby_mod:ruby_from_block",
    name="Ruby (from block)",
    recipe=fabricpy.RecipeJson(
        {
            "type": "minecraft:crafting_shapeless",
            "ingredients": ["ruby_mod:ruby_block"],
            "result": {"id": "ruby_mod:ruby", "count": 9},
        }
    ),
    item_group=ruby_tab,
)
mod.registerItem(ruby_decompose)

# Smelt raw ruby into gem
raw_ruby_smelt = fabricpy.RecipeJson(
    {
        "type": "minecraft:smelting",
        "ingredient": "ruby_mod:raw_ruby",
        "result": {"id": "ruby_mod:ruby", "count": 1},
        "experience": 1.0,
        "cookingtime": 200,
    }
)
# Attach smelting recipe to the raw ruby item
raw_ruby.recipe = raw_ruby_smelt

# ── Compile ──────────────────────────────────────────────────────────── #

# Uncomment to generate the mod project:
# mod.compile()
# mod.run()