Introduction

Allay is a command-line application which can be used to create add-ons for Minecraft: Bedrock Edition. It’s goal is to take over the boring work you would do when manually developing an add-on such as generating UUIDs and handling localization.

When working with Allay you work on the source files and Allay takes them and transforms them in the way you need it so your add-on can be imported into Minecraft and published. This allows you to quickly build your next big project.

Throughout this manual you will read the terms pack and add-on frequently which are often used interchangeably and differently. Here, both add-on and pack refer to a “Behavior Pack”, “Resource Pack”, “Skin Pack” or “World Template” which can be exported to Minecraft. Allay bundles these together to a single “build” (which has the file extension mcaddon but may consist of multiple different add-ons).

Installation

Quick Installation (Unix only)

Enter the command below to quickly install Allay.

curl https://allay-mc.github.io/getallay/getallay.sh -sSf | sh

Manual Installation

Visit the release page and select the archive that matches your platform. Extract it and put the executable to some path of your choise (usually something like /usr/local/bin on Unix and C:\Program Files on Windows).

Via Cargo

If your platform is not supported or you just prefer to build Allay yourself, then you can do so by following the instructions below.

  1. Make sure you have Rust and Cargo installed.
  2. Install with cargo install --git https://github.com/allay-mc/allay.git or clone the repository with git clone https://github.com/allay-mc/allay.git and then run cargo install --path allay.

Configuration

The configuration file allay.toml contains metadata about your Allay project next to some additional configuration options. Most of the values are used to generate the manifest.json manifest file for each add-on whereas some of them control the build process.

[project]
name = "My Awesome Project"
description = { en-us = "This is my awesome project made with Allay" }
version = "0.1.0"
min-engine-version = "1.20.0" # consider updating this to the most recent version

[localization]
primary-language = "en-us"

[env]
# Here you can specify extra environment variables accessable by plugins.

The $schema field

Can be used for editor completions.

"$schema" = "https://allay.github.io/config-schema.json"

[project]
# ...

[localization]
# ...

The debug field

Defines whether builds run in debug mode. For example, the manifest.json file in debug mode is formatted with indention and compressed in release mode. Plugins may access the ALLAY_DEBUG variable for variable behavior.

The [project] section

The name field

Specify the name of the project.

[project]
name = "Hello World"

It’s also possible to localize the name:

[project.name]
de-de = "Hallo Welt"
en-us = "Hello World"

The description field

Defines the description of the project. This has the same structure as name.

The version field

The version of the project of the form <major>.<minor>.<patch>.

[project]
# ...
version = "1.3.0"

The authors field

Specify the authors of the project. This is an array with strings of the form <full name> <email (optional)>.

[project]
# ...
authors = ["John Doe <john.doe@example.org>", "Jane Doe"]

The license field

Specify the license of the project. This should but is not required to match a SPDX identifier.

[project]
# ...
license = "MIT"

The url field

Specify the URL of the project.

[project]
# ...
url = "https://github.com/allay-mc/allay"

The min-engine-version field

This is the minimum version of the game that this pack was written for. This is a required field for resource and behavior packs. This helps the game identify whether any backwards compatibility is needed for your pack. You should always use the highest version currently available when creating packs.

The [localization] section

The primary-language field

The groups field

The [env] section

This section can be used to provide arbitrary arguments for plugins.

[env]
FOO = "1"
BAR = "Hello"

See Also

The [build] section

The extra-watch-dirs field

Controls which directories should trigger a rebuild on changes when using the watch command.

[build]
extra-watch-dirs = ["plugins"]

The [[plugin]] sections

The name field

A plugin can optionally be granted a name which is useful as you can more easily identify what output came from which plugin during the build process.

[[plugin]]
name = "Greeter"
run = "plugins/greet.rb"
with = "ruby"

The run and with fields

The run and with fields are used to specify the program used to run the plugin.

[[plugin]]
run = "first-argument"
with = "program"
args = ["second-argument", "third-argument"]

Alternatively, you can use the following form:

[[plugin]]
run = "program"
args = ["first-argument", "second-argument", "third-argument"]

Both snippets effectively do the same and if you use the combination of with + run or just run pretty much depends on your taste and the program you are invoking. Let’s look at an example which uses a Dart program as a plugin.

In the console you would write:

dart run plugins/hello.dart

To use it as a plugin, we add it to the configuration file:

[[plugin]]
run = "run"
with = "dart"
args = ["plugins/hello.dart"]

But we can use the alternative which would look better in this case:

run = "dart"
args = ["run", "plugins/hello.dart"]

The args and options fields

The args array passes each value to the program as an argument whereas the value used for options is serialized into JSON and passed as a single argument to the plugin.

The when field

See Also

The threaded field

Note

This field is unused but might be used in the future.

The panic field

Note

This field is unused but might be used in the future.

The [BP], [RP], [SP] and [WT] sections

These sections can be used to for pack-specific configurations.

The [BP] section also allows specifying the type of behavior pack by setting type to data or script. The [WT] section also allows specifying allow_random_seed and base_game_version.

The custom-manifest field

Whether to use the manifest.json file in the pack’s directory instead of generating one.

The custom-pack-icon field

Whether to use the pack_icon.png file in the pack’s directory instead of generating one. This field does not exist for world template configuration.

The name and description field

By default project.name and project.description are applied for all packs. You can override those with the name and description field. They both have the same structure as project.name/project.description.

The dependencies field

Plugins

Plugins are little programs that extend Allay by for instance transforming the source data.

Writing Plugins

To write your own plugin you should be comfortable with some programming language.

Environment Variables

When Allay executes a plugin, it provides a few environment variables that help you managing files for instance.

Caution

Never let a plugin modify source files (files within one of the BP, RP, SP or WT directories). Allay under the hood copies all source files to a temporary directory where files can be freely modified. After all plugins run, these files are zipped to one build file. The environments variables point to the paths in the temporary directory (“prebuilt directory”).

  • ALLAY_DEBUG — Whether the project is built in debug mode.
  • ALLAY_PREBUILD — The root of the prebuilt directory.
  • ALLAY_PROJECT_ROOT — The path to the root of the project (the directory with the allay.toml file).
  • ALLAY_VERSION — The version of Allay that is beeing used.

On top of that users may define extra environment variables in the [env] section of the configuration file.

Configuration

Plugins may be configured in three different ways:

  1. options field in [[plugin]] section — Tranforms the value into JSON and passes it as the first (or second when with is set) argument for the executable.
  2. args field in [[plugin]] section — Passes each string to the executable as arguments.
  3. env section — The plugin can access environment variables set in the configuration file.
  4. Code modification — The code itself can be modified.

Option 1 requires the program to parse JSON string. Some programming languages have this feature in their standard library like Python and JavaScript but it’s always possible to to use third-party dependencies. Option 2 can be used when option 1 cannot be satisfied. This option is also used commonly for command-line applications that are not specifically designed for Allay. Note that this approach may be limited as Allay passes value as is and don’t make use of things like filename expansion. Option 3 should only be used as a last resort when the plugin is not capable of accessing arguments or environment variables which is almost never the case.

Write and Use a Plugin

Below is an example of a plugin written in the Python programming language.

import os

version = os.environ["ALLAY_VERSION"]
project_root = os.environ["ALLAY_PROJECT_ROOT"]

print(f"Allay v{version}")
print(project_root)

By convention, plugins related to the Allay project are placed in a directory named plugins:

.
├── allay.toml
├── plugins/
│   └── info.py
└── src/

To tell Ally to run this plugin, we need to mention it in the allay.toml file.

# ...

[[plugin]]
run = "plugins/info.py"
with = "python3"

Running the info.py script yourself is likely going to yield unexpected results as the environment variables are not set. Plugins are intended to be executed by Allay and can therefore only be triggered by building the project (allay build).

Filters

Filters can be added to plugins to only run them when certain conditions are met. This can be achieved by adding the when field:

# ...

[[plugin]]
run = "plugins/info.py"
with = "python3"
when = 'env("ALLAY_DEBUG") == "1"'

In this case the plugin would only run if the project is beeing built in debug mode. Allay uses rhai for evaluation. You can learn rhai by reading its documentation. Allay provides the following functions which are useful for writing filters:

  • arch() — A string describing the architecture of the CPU that is currently in use.
  • dll_extension() — Specifies the file extension used for shared libraries on this platform that goes after the dot. Example value is so.
  • dll_prefix() — Specifies the filename prefix used for shared libraries on this platform. Example value is lib.
  • dll_suffix() — Specifies the filename suffix used for shared libraries on this platform. Example value is .so.
  • env(key) — Fetches the environment variable key from the current process or an empty string if key is absent or the value is invalid unicode.
  • env_present(key) — Evaluates true when the environment variable with the key key is present or false otherwise.
  • exe_extension() — Specifies the file extension, if any, used for executable binaries on this platform. Example value is exe.
  • exe_suffix() — Specifies the filename suffix used for executable binaries on this platform. Example value is .exe.
  • family() — The family of the operating system. Example value is unix.
  • os() — A string describing the specific operating system in use. Example value is linux.

Using TypeScript

TypeScript is a commonly chosen alternative to JavaScript when working with Minecraft’s Script API. When working with the Script API, it’s a good idea to initialize the Allay project with npm or other package managers. This guide will use npm as the package manager.

First, initialize the project for npm:

npm init

Then edit the generated package.json to look similar to the following:

{
  "name": "myproject",
  "version": "0.1.0",
  "license": "MIT",
  "scripts": {
    "build": "tsc --outDir $ALLAY_PREBUILD"
  },
  "dependencies": {
    "@minecraft/server": "^1.13.0",
    "@minecraft/server-ui": "^1.3.0",
    "typescript": "next"
  }
}

We also create a tsconfig.json required to transpile our TypeScript files correctly:

{
  "compilerOptions": {
    "rootDir": "./src",
    "module": "ES2020",
    "moduleResolution": "Node",
    "target": "es6",
    "sourceMap": false
  },
  "include": ["src/**/*"]
}

This is the bare minimum TypeScript configuration required. You can further configure the file to your preferences.

We won’t use the npm CLI directly to run the build script like you usually would do. Instead, we invoke the build script by using an Allay plugin:

# ...

[[plugin]]
name = "transpile typescript"
run = "npm"
args = ["run", "build"]

This is neccessary as the package.json makes use of the environment variable ALLAY_PREBUILD which is only available when run by Allay.

The built add-on will still contain TypeScript files alongside the generated JavaScript files. Because we don’t need these files, we can use a plugin that removes the TypeScript files from the build.

# ...

[[plugin]]
name = "exclude typescript files"
run = "plugins/exclude.rb"
with = "ruby"
options = { patterns = ["**/*.ts"] }

You can find this plugin here.

Introduction To Resource Packs

This page explains how to reproduce the example from https://learn.microsoft.com/en-us/minecraft/creator/documents/resourcepack?view=minecraft-bedrock-stable with Allay.

Image showing a pig deeply confused by its environment containing green dirt blocks

Before building your first Add-On for Minecraft: Bedrock Edition, you need to create a pack to hold your custom content. There are two types of packs that a creator can make: resource packs and behavior packs. For this tutorial, we’re going to be focusing on resource packs.

For Minecraft to find and use your resource files, you must set up the folders and files in a specific way. This tutorial will guide you through creating this folder and file structure.

After running allay init create directories and files like shown below.

.
├── allay.toml
└── src/
    ├── BP/
    ├── RP/
+   │   └── textures/
+   │       └── blocks/
+   │           └── dirt.png
    ├── SP/
    └── WT/

The dirt.png file should be some texture of your choice. You can also download the green block below and use it instead.

Custom Dirt Texture

The image should have a quadratic size (e.g. 16x16 or 32x32).

Now allay build the project and export it to Minecraft.

Your custom texture will be used on every dirt block in the world, but it will not be used on blocks of dirt with grass on them because those blocks have a different name.

Todo

Add images explaining how to enable the add-on.

Introduction To Behavior Packs

This page explains how to reproduce the example from https://learn.microsoft.com/en-us/minecraft/creator/documents/behaviorpack?view=minecraft-bedrock-stable with Allay.

Image of a cow chasing Steve

Before building your first Add-On for Minecraft: Bedrock Edition, you will need to create a pack to hold your custom content. There are two types of packs that a creator can make: resource packs and behavior packs. A behavior pack is a folder structure that contains files that drive entity behaviors, loot drops, spawn rules, items, recipes, and trade tables. This tutorial covers how behavior packs are created and how to add behaviors to an in-game cow entity to make it aggressive.

.
├── allay.toml
└── src/
    ├── BP/
+   │   └── entities/
+   │       └── cow.json
    ├── RP/
    ├── SP/
    └── WT/

Every entity’s behaviors are defined in its JSON file that lives inside the code that makes Minecraft work. You’re going to create a new cow behavior file that Minecraft will use instead of its usual “vanilla” one.

Note

This is the entire edited cow.json file. It’s big because cows do a lot!

{
    "format_version": "1.16.0",
    "minecraft:entity": {
        "description": {
            "identifier": "minecraft:cow",
            "is_spawnable": true,
            "is_summonable": true,
            "is_experimental": false
        },
        "component_groups": {
            "minecraft:cow_baby": {
                "minecraft:is_baby": {},
                "minecraft:scale": {
                    "value": 0.5
                },
                "minecraft:ageable": {
                    "duration": 1200,
                    "feed_items": "wheat",
                    "grow_up": {
                        "event": "minecraft:ageable_grow_up",
                        "target": "self"
                    }
                },
                "minecraft:behavior.follow_parent": {
                    "priority": 6,
                    "speed_multiplier": 1.1
                }
            },
            "minecraft:cow_adult": {
                "minecraft:experience_reward": {
                    "on_bred": "Math.Random(1,7)",
                    "on_death": "query.last_hit_by_player ? Math.Random(1,3) : 0"
                },
                "minecraft:loot": {
                    "table": "loot_tables/entities/cow.json"
                },
                "minecraft:behavior.breed": {
                    "priority": 3,
                    "speed_multiplier": 1.0
                },
                "minecraft:breedable": {
                    "require_tame": false,
                    "breed_items": "wheat",
                    "breeds_with": {
                        "mate_type": "minecraft:cow",
                        "baby_type": "minecraft:cow",
                        "breed_event": {
                            "event": "minecraft:entity_born",
                            "target": "baby"
                        }
                    }
                },
                "minecraft:interact": {
                    "interactions": [
                        {
                            "on_interact": {
                                "filters": {
                                    "all_of": [
                                        {
                                            "test": "is_family",
                                            "subject": "other",
                                            "value": "player"
                                        },
                                        {
                                            "test": "has_equipment",
                                            "domain": "hand",
                                            "subject": "other",
                                            "value": "bucket:0"
                                        }
                                    ]
                                }
                            },
                            "use_item": true,
                            "transform_to_item": "bucket:1",
                            "play_sounds": "milk",
                            "interact_text": "action.interact.milk"
                        }
                    ]
                }
            }
        },
        "components": {
            "minecraft:is_hidden_when_invisible": {},
            "minecraft:type_family": {
                "family": [
                    "cow",
                    "mob"
                ]
            },
            "minecraft:breathable": {
                "total_supply": 15,
                "suffocate_time": 0
            },
            "minecraft:navigation.walk": {
                "can_path_over_water": true,
                "avoid_water": true,
                "avoid_damage_blocks": true
            },
            "minecraft:movement.basic": {},
            "minecraft:jump.static": {},
            "minecraft:can_climb": {},
            "minecraft:collision_box": {
                "width": 0.9,
                "height": 1.3
            },
            "minecraft:nameable": {},
            "minecraft:health": {
                "value": 10,
                "max": 10
            },
            "minecraft:hurt_on_condition": {
                "damage_conditions": [
                    {
                        "filters": {
                            "test": "in_lava",
                            "subject": "self",
                            "operator": "==",
                            "value": true
                        },
                        "cause": "lava",
                        "damage_per_tick": 4
                    }
                ]
            },
            "minecraft:movement": {
                "value": 0.25
            },
            "minecraft:despawn": {
                "despawn_from_distance": {}
            },
            "minecraft:behavior.float": {
                "priority": 0
            },
            "minecraft:behavior.panic": {
                "priority": 1,
                "speed_multiplier": 1.25
            },
            "minecraft:behavior.mount_pathing": {
                "priority": 2,
                "speed_multiplier": 1.5,
                "target_dist": 0.0,
                "track_target": true
            },
            "minecraft:behavior.breed": {
                "priority": 3,
                "speed_multiplier": 1.0
            },
            "minecraft:behavior.tempt": {
                "priority": 4,
                "speed_multiplier": 1.25,
                "items": [
                    "wheat"
                ]
            },
            "minecraft:behavior.follow_parent": {
                "priority": 5,
                "speed_multiplier": 1.1
            },
            "minecraft:behavior.random_stroll": {
                "priority": 6,
                "speed_multiplier": 0.8
            },
            "minecraft:behavior.look_at_player": {
                "priority": 7,
                "look_distance": 6.0,
                "probability": 0.02
            },
            "minecraft:behavior.random_look_around": {
                "priority": 9
            },
            "minecraft:leashable": {
                "soft_distance": 4.0,
                "hard_distance": 6.0,
                "max_distance": 10.0
            },
            "minecraft:balloonable": {},
            "minecraft:rideable": {
                "seat_count": 1,
                "family_types": [
                    "zombie"
                ],
                "seats": {
                    "position": [
                        0.0,
                        1.105,
                        0.0
                    ]
                }
            },
            "minecraft:physics": {},
            "minecraft:pushable": {
                "is_pushable": true,
                "is_pushable_by_piston": true
            },
            "minecraft:conditional_bandwidth_optimization": {},
            "minecraft:behavior.nearest_attackable_target": {
                "priority": 2,
                "must_see": true,
                "reselect_targets": true,
                "within_radius": 25.0,
                "entity_types": [
                    {
                        "filters": {
                            "test": "is_family",
                            "subject": "other",
                            "value": "player"
                        },
                        "max_dist": 32
                    }
                ]
            },
            "minecraft:behavior.melee_attack": {
                "priority": 3
            },
            "minecraft:attack": {
                "damage": 3
            }
        },
        "events": {
            "minecraft:entity_spawned": {
                "randomize": [
                    {
                        "weight": 95,
                        "trigger": "minecraft:spawn_adult"
                    },
                    {
                        "weight": 5,
                        "add": {
                            "component_groups": [
                                "minecraft:cow_baby"
                            ]
                        }
                    }
                ]
            },
            "minecraft:entity_born": {
                "add": {
                    "component_groups": [
                        "minecraft:cow_baby"
                    ]
                }
            },
            "minecraft:entity_transformed": {
                "remove": {},
                "add": {
                    "component_groups": [
                        "minecraft:cow_adult"
                    ]
                }
            },
            "minecraft:ageable_grow_up": {
                "remove": {
                    "component_groups": [
                        "minecraft:cow_baby"
                    ]
                },
                "add": {
                    "component_groups": [
                        "minecraft:cow_adult"
                    ]
                }
            },
            "minecraft:spawn_adult": {
                "add": {
                    "component_groups": [
                        "minecraft:cow_adult"
                    ]
                }
            }
        }
    }
}

This is that code that, when added to the components section of a cow.json file, turns cows into aggressive killing machines:

            "minecraft:behavior.nearest_attackable_target": {
                "priority": 2,
                "must_see": true,
                "reselect_targets": true,
                "within_radius": 25.0,
                "entity_types": [
                    {
                        "filters": {
                            "test": "is_family",
                            "subject": "other",
                            "value": "player"
                        },
                        "max_dist": 32
                    }
                ]
            },
            "minecraft:behavior.melee_attack": {
                "priority": 3
            },
            "minecraft:attack": {
                "damage": 3
            }

Now allay build the project and export it to Minecraft.