Components#

When developing a data pack with bolt, it's possible to define several nested resources (such as functions and tags) in a single bolt script. However, as the data pack grows in size, working with all of these resources can get pretty cumbersome, since most of the time we would be dealing with absolute and relative paths. That being said, integrity provides an elegant way of working with nested resources: through components.

Component#

A Component object is a container of nested resources, but it can also hold data and bolt functions. To create a component, simply import and call the Component factory method:

from integrity import Component

my_component = Component("demo")

The Component factory can be used in various ways depending on how we want our components to be handled.

The simplest method is by providing a unique name as first parameter. In the example above, my_component is called demo. Named components are children of the bolt module they were created on. When we start defining the resources of my_component, the output structure will be similar to this:

namespace:
    - module.mcfunction
    - module:
        - components:
            - demo:
                - function_1.mcfunction
                - function_2.mcfunction

Note that the name demo becomes a folder under the module/components directory. Because of that, a component's name needs to be unique in the module scope.

The Component factory also lets us create a component without specifying a name (i.e without any parameter):

other_component = Component()

In this case, other_component is known as a module-level component. By including it in our previous example, the updated file structure would look like this:

namespace:
    - module.mcfunction
    - module:
        - component:
            - function_1.mcfunction
            - function_2.mcfunction
        - components:
            - demo:
                - function_1.mcfunction
                - function_2.mcfunction

Module-level components are generated in the module/component directory, being directly associated with its parent module.

Obs: Each module can only have a single module-level component.

Alternatively, we can also create components at specific locations by specifying a path parameter when using the Component factory:

# here:module

# relative paths
my_component = Component(path=./my_component)

# absolute paths
another_component = Component(path=there:another_component)

# project root using generate_path helper function
root = Component(path=generate_path(""))

All of these components combined would generate the following structure:

here:
    - module.mcfunction
    - function_1.mcfunction
    - function_2.mcfunction
    - my_component:
        - function_1.mcfunction
        - function_2.mcfunction
there:
    - another_component:
        - function_1.mcfunction
        - function_2.mcfunction

This way, we're in full control of the generated structure. However, this method requires extra caution, since two components can't exist under the same path and it's easier to overwrite existing files.

Now that we have a Component object in hands, we can start defining its properties through the API.

API#

Component.on#

Component.on(event_name: str, path: str = None, tags: Iterable[str] = [])

Registers a new function listener to the event event_name. If the parameter path is not specified, a unique path is generated using Component.path. If the parameter tags is specified, the registered function is added to all function tags.

Returns the registered resource location. The returned value can be used to define a nested function.

# namespace:main

from integrity import Component

clock = Component("golden_clock")

init_path = clock.on("init")
function init_path:
    say "On clock init"

function clock.on("tick", tags=["minecraft:tick"]):
    say "On clock tick"

function clock.on("second"):
    say "On clock second"

function clock.on("second"):
    say "Also on clock second"

function clock.on("second", ./path_to/second):
    say "On clock second but explicit path"

clock.on("second", demo:existing_function/do_stuff)

This example would generate the following functions:

namespace:
    - main.mcfunction
    - main:
        - components:
            - golden_clock:
                - init.mcfunction
                - tick.mcfunction
                - second.mcfunction
                - second_1.mcfunction
    - path_to:
        - second.mcfunction
demo:
    - existing_function:
        - do_stuff.mcfunction

Events will also generate function tags if more than one listener is registered:

namespace:
    - main:
        - components:
            - golden_clock:
                - second.json
minecraft:
    - tick.json

Since both the init and tick events only have a single function listener, function tags are not generated and the events will refer to their listener paths directly when using Component.run.

Component.run#

Component.run(event_name: str)

Generates a function command that calls the event event_name. If the event only has a single function listener, the listener is called directly, otherwise the event function tag is called instead. If the provided event_name argument is prefixed by #, the event function tag is forcedly called.

The command is generated regardless if the function/function tag exists.

# demo:main

from integrity import Component

core = Component(path=./core)

core.on("tick", ./tick)

core.run("tick")
# function demo:tick

core.on("stuff") # generates path for us
core.on("stuff")

core.run("stuff")
# function #demo:core/stuff

core.run("next") # not implemented
# function demo:core/next

core.run("#prepare")
# function #demo:core/prepare

Component.data#

A dict object to store anything that might belong to a specific component. All components are created with an empty data field.

from integrity import Component

player = Component("player")
player.data["entity_id"] = "minecraft:player"

pig = Component("pig")
player.data["entity_id"] = "minecraft:pig"

player.data # { "entity_id": "minecraft:player" }
pig.data # { "entity_id": "minecraft:pig" }

Component.path#

Component.path(relative: str, tag: bool = False)

Generates a resource location based on the component root path and the provided relative argument. If tag is True, a tag resource location (prefixed by #) is generated instead.

from integrity import Component

player = Component(path=./player)

predicate player.path("is_sneaking") {
    ...
}

if predicate player.path("is_sneaking") say Hi!

loot spawn ~ ~ ~ loot player.path("weapons/iron_sword")