Beet Minecraft Pack Development Kit

repository·main·Indexed 43 days ago

https://github.com/mcbeet/beet

A development kit for Minecraft that unifies resource pack and data pack tooling into a single pipeline. It includes a Python library for programmatic pack manipulation, a CLI toolchain for building and watching projects, and integrated tools like Lectern for converting markdown or plain text into packs, and Bolt for generating complex Minecraft commands using Python logic.

Tokens
23.4K
Snippets
95
Records
104
Agent score
54%

What's inside Beet

  1. What is Beet?

    main
    Beet is a Minecraft pack development kit designed to unify the tooling for resource packs and data packs into a single pipeline. It acts as an authoring layer that sits above the standard Minecraft distribution formats, allowing developers to manage complex file structures and integrate various pre-processors, frameworks, and generators seamlessly. This helps avoid the manual management of hundreds of files typically required in large-scale Minecraft pack development.
  2. Supercharge Minecraft commands with Python

    main

    Bolt allows you to define Minecraft NBT data structures as Python dictionaries and use Python logic (loops, functions, f-strings) to generate and execute complex Minecraft commands. This enables programmatic command generation, such as summoning armies of entities with specific attributes.

    infinite_invisibility = {
        Id: 14,
        Duration: 999999,
        Amplifier: 1,
        ShowParticles: false,
    }
    
    def summon_chicken_army(n):
        for i in range(n):
            summon chicken ~i ~ ~ {
                Tags: [f"quack{i}"],
                IsChickenJockey: true,
                Passengers: [{
                    id: zombie,
                    IsBaby: true,
                    ActiveEffects: [infinite_invisibility]
                }]
            }
    
    say Go forth, my minions!
    summon_chicken_army(16)
  3. Access files using namespaced locations (Proxies)

    main

    Instead of navigating the full hierarchy, you can use proxies on the DataPack or ResourcePack object to access files using their namespaced string identifiers (e.g., namespace:path). These proxies are always in sync with the underlying namespace objects.

    Proxy attributes are named after the file type (e.g., .functions, .textures).

    from beet import DataPack, Function
    
    pack = DataPack()
    
    # Using namespaced strings directly on the pack
    pack.functions["demo:foo"] = Function()
    
    # This is equivalent to:
    # pack["demo"].functions["foo"]
  4. Use the yield statement for plugin exit phases

    main

    Plugins in the beet pipeline operate in two phases: an entry phase and an exit phase. This is because plugins are conceptually wrapped around each other like layers.

    By using the yield statement, a plugin can pause its execution, allow all dependent plugins (those it require()d) to run, and then resume to perform cleanup or finalization tasks.

    Execution Flow:

    1. Plugin enters (code before yield).
    2. Plugin yields (pauses).
    3. Dependent plugins run to completion.
    4. Plugin resumes (code after yield).

    This is useful for aggregating data from multiple plugins before writing a final file (e.g., merging multiple language entries into one .json file).

    def add_translations_plugin(ctx: Context):
        # [Entry Phase] Initialize shared state
        ctx.meta["translations"] = {}
        
        yield  # <--- Wait for all other plugins to call 'set_translation'
    
        # [Exit Phase] Finalize and write files
        for code, data in ctx.meta["translations"].items():
            ctx.assets["minecraft:lang"].merge(data)
  5. Implement Service Injection with ctx.inject()

    main

    For complex logic, instead of using arbitrary metadata, you can package functionality into a Service. Services are instantiated via ctx.inject() and live for the duration of the pipeline.

    Pattern for creating a service:

    1. Define a class that accepts ctx: Context in its __init__.
    2. In __init__, use ctx.require() to register a plugin method that handles the service's finalization (using yield).
    3. Use ctx.inject(YourServiceClass) within plugins to access the service's API.

    This provides a strongly-typed, explicit way to share state and logic across plugins.

    from beet import Context
    
    class Internationalization:
        def __init__(self, ctx: Context):
            self.languages = {}
            # Register the cleanup plugin automatically
            ctx.require(self.add_translations)
    
        def add_translations(self, ctx: Context):
            yield
            # Finalize: merge all collected languages into assets
            ctx.assets["minecraft"].languages.merge(self.languages)
    
        def set(self, key: str, **kwargs: str):
            # API for other plugins to use
            for code, message in kwargs.items():
                self.languages[code][key] = message
    
    # Usage in a plugin:
    def my_plugin(ctx: Context):
        i18n = ctx.inject(Internationalization)
        i18n.set("hello", en_us="hello", fr_fr="bonjour")
  6. Use the beet pipeline for automated builds

    main

    The beet pipeline acts as a "conveyor belt" for your data packs and resource packs. You can write or use plugins to operate on the output of the pipeline. This allows you to:

    • Transform Data: Apply changes to the pack contents automatically.
    • Integrate Frameworks: Run external tools (like sandstone) as part of the build process to bundle generated content into your final package.
    • Automate Repetitive Tasks: Replace custom scripts with formal plugins that analyze or generate pack components.

    By using beet, you can achieve a unified build system where different tools (like mecha or lectern) interoperate by using the data pack format as a common language.

  7. Understand the Lectern font data structure

    main

    The Lectern snapshot format represents Minecraft font data using a JSON structure containing multiple font definitions. Each definition includes:

    • Unicode Pages: A list of character strings representing the glyphs on a specific unicode page.
    • Bitmaps: Objects defining a bitmap font, including:
      • type: Set to "bitmap".
      • file: The resource path to the image (e.g., "minecraft:font/ascii.png").
      • ascent: The vertical offset for the font.
      • chars: A 2D array of characters representing the pixel data or glyph mapping.
    • Legacy Unicode: A definition for legacy unicode support, specifying:
      • type: Set to "legacy_unicode".
      • sizes: The path to the binary file containing glyph sizes (e.g., "minecraft:font/glyph_sizes.bin").
      • template: A string template for locating unicode page images (e.g., "minecraft:font/unicode_page_%s.png").
    {
      "type": "bitmap",
      "file": "minecraft:font/ascii.png",
      "ascent": 7,
      "chars": [
        "\u0000\u0000...",
        "\u0020\u0021..."
      ]
    }
  8. Understand the beet development model

    main

    Unlike traditional Minecraft development where you edit files directly in your .minecraft folder, beet introduces a formal build step. You treat your project as a collection of source files (data packs and resource packs) that are processed through a pipeline and output to a destination (like a development world or a distribution folder).

    This model provides several advantages:

    • Version Control: Keep your data packs and resource packs side-by-side in a single repository.
    • Automated Releases: Separate your project sources from the final published assets.
    • Live Reloading: Use the beet watch --reload command to benefit from live data pack reloading during development.
    • Extensibility: Use a plugin-based pipeline to automate transformations, analysis, and generation.
  9. Understand the Data Pack and Resource Pack object hierarchy

    main

    In beet, DataPack and ResourcePack objects are dict-like primitives that map directly to the filesystem structure. The hierarchy follows this pattern:

    1. Pack: The top-level object (e.g., DataPack). It contains namespaces.
    2. Namespace: A dict-like object within the pack (e.g., pack["demo"]). Namespaces are created automatically.
    3. Namespace Container: A dict-like object within a namespace that groups files by type (e.g., namespace.functions).
    4. File Instance: The leaf nodes representing actual files (e.g., Function, Texture).

    You can access containers via attribute access (e.g., .functions) or type-based indexing (e.g., [Function]). Files can also be added directly to a namespace, and the namespace will automatically dispatch them to the correct container based on their type.

    from beet import DataPack, Function
    
    pack = DataPack()
    # Accessing via namespace and type
    demo_namespace = pack["demo"]
    demo_functions = demo_namespace[Function]
    
    # Adding a file (automatic dispatch to container)
    demo_functions["foo"] = Function(["say hello"])
    
    # Equivalent attribute access
    demo_namespace.functions["foo"]
  10. Understand the beet toolchain and plugin system

    main

    The beet toolchain is a build system for Minecraft data packs and resource packs. Instead of writing one-off scripts, you implement logic as plugins.

    Each plugin is a callable Python object that accepts a Context object. The toolchain initializes a Context with an empty data pack and an empty resource pack, then executes the plugins defined in your configuration sequentially (or as a dependency graph).

    Workflow:

    1. Define plugins as Python functions taking ctx: Context.
    2. Configure the build in a beet.json file.
    3. Run the beet command to execute the pipeline and output the generated packs.
    {
      "name": "greeting",
      "output": "out",
      "pipeline": ["my_plugins.add_greeting"]
    }
  11. Understand nested @script directives

    main

    The @script directive supports nesting. You can nest directives by increasing the number of @ symbols prefixing the directive name.

    • @script is the top-level directive.
    • @@script is a second-level nested directive.
    • @@@script is a third-level nested directive.

    Note: While nesting is supported, deep nesting (e.g., @@@@function) is possible but should generally be avoided for clarity.

    @script
    @@function demo:script_nested
    say wow
    
    @script
    @@script
    @@@script
    @@@@function demo:script_please_avoid_this
    say no
  12. Use modifiers in Lectern directives

    main

    Directives can be adjusted using modifiers specified in parentheses immediately after the directive name.

    Content Modification Modifiers

    • append: Concatenates the fragment content to existing content (available for all text-based namespaced resource directives).
    • prepend: Adds the fragment content before the existing content.
    • merge: Uses the beet merging strategy to combine the fragment with the existing file (useful for JSON files like minecraft:load).
    • base64: Decodes the content of the code fragment from base64.
    • download: Used with block fragments to download remote files from a URL.
    • strip_final_newline: Removes the final newline at the end of code block fragments.

    Syntax

    @<directive_name>(<modifier>) <arg1> <arg2> ...

    @function(append) tutorial:greeting
    say This is added afterwards.
    
    @function(prepend) tutorial:greeting
    say This is added before.
    
    @function_tag(merge) minecraft:load
    {
      "values": ["#tutorial:something_else"]
    }
    
    @function_tag(base64) tutorial:something_else
    ewogICJ2YWx1ZXMiOiBbInR1dG9yaWFsOnN0cmlwcGVkIl0KfQ==
    
    @function_tag(download) tutorial:from_github
    https://raw.githubusercontent.com/mcbeet/beet/main/examples/load_basic/src/data/demo/functions/foo.mcfunction
    
    @function(strip_final_newline) tutorial:stripped
    say This function doesn't have a final newline.