Overview

Each Allay project resembles one or more coherent packs. For example adding a new block into the game requires both a behavior pack for the behavior of the block and a resource pack for the textures. If two or more packs are not coherent they should not belong to the same Allay project.

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.

Allay is a command-line tool which is controlled from a terminal. We expect that you know how to navigate between files in a terminal and how to edit files.

Tip

You can use an editor like Visual Studio Code which lets you use a terminal within the editor. This means you can easily navigate between files and edit them in a comfortable way while using the terminal to run simple commands like allay build.

Warning

This project is in a work-in-progress status. Many features as well as links might not work yet. Consider waiting for a stable release if you want to use this program.

Philosophy

  • allay build and double-click on the built add-on. It can be that simple.
  • We believe that the JSON format should be used by machines and not humans. Therefore TOML is used for configuartion and scripts can be used to easily convert formats like YAML into JSON which can be read by Minecraft. The manifest.json is generated automatically by Allay.
  • Generating UUIDs over and over again can be troublesome so Allay fully takes care of managing them in your project. For instance the allay uuid command can be used to get a nice overview of used UUIDs.
  • Built-in advanced handling of languages: Translate your project to mexican spanish (es-mx) and it will be available for spanish spanish (es-es) as well. Allay extends your existing .lang file (or creates new ones until each language is covered) by adding the appropiate name and description of the pack.
  • Written in the Rust programming language which is blazingly fast.1

Glossary

To avoid misconception between interchangably used terms, Allay strictly defines the following terms. They are used this way in the documentation and projects related to Allay.

TermMeaning
Add-onAn add-on might be either a built behavior pack, resource pack, skin pack or world template.
DirectoryWindows users might not be familar with this word. Throughout the documentation we will use this term which basically just means “folder”.
PackSame as an add-on but not the built version.

Scripts

Scripts enable a powerful way of automating several tasks. The script templating provides support for ERB enabling a way of flow control and generation.

allay add templating
<%# using erb %>
<% 10.times do |i| %>
scoreboard objectives add position<%= i %> dummy
<% end %>
[scripts]
base-path = "scripts"
pre = [
  { run = "templating.rb", with = "ruby" }
]

What’s next?

There is much more to discover and we hope this tool makes your Minecraft developing experience more confortable. You can continue reading through the documentation to learn how to use Allay.

Installation

Windows

TODO

Android

TODO

Linux / macOS

TODO

Example Project

In this chapter, we will cover how to create a world template which includes a behavior pack as well as an resource pack.

Step 1: Initialize the project

mkdir lost-sword
cd lost-sword
allay init
New-Item -Path . -Name "lost-sword" -ItemType "directory"
Set-Location -Path '.\lost-sword'
allay init

Step 2: Configure the World Template

First we move a Minecraft world into the src/WT/ directory.

TODO

Scripts

Each script is a program run either before building the add-on(s) (pre-built state) or after that (post-build state). Scripts can for instance be used to convert between file types such as JSON5 to JSON which then can be read by Minecraft.

Scripts are executed in order how they are specified in the configuration file.

[scripts]
base-path = "scripts/"
pre = [
    { run = "foo.rb", with = "ruby" },  # executed first
    { run = "bar.py", with = "python" },  # executed second
]

Always keep this in mind when adding new scripts.

Scripts are by convention located in the scripts/ directory but you can choose another directory if you want to. The base path should be specified in the base-path field.

Scripts can also be nested within that directory. Considering the following project structure:

project
├── allay.toml
├── build/
├── pack_icon.png
├── scripts/
│   ├── converters/
│   │   ├── yaml_to_json.rb
│   │   └── toml_to_json.rb
│   └── linters/
│       └── yaml.py
└── src/

Then you can refer to the scripts like this in your configuration file.

[scripts]
base-path = "scripts"
pre = [
    { run = "linters/yaml.py", with = "python" },
    { run = "converters/yaml_to_json.rb", with = "ruby" },
    { run = "converters/toml_to_json.rb", with = "ruby" },
]
post = []

There are some scripts provided in the allay-mc/scripts repository. They are entirely written in Ruby without any dependencies. Just make sure you have Ruby installed when using scripts from there.

Tip

You can use the allay add command to quickly download scripts from that repository. For more info, type allay add --help in the command-line.

Post scripts are less common because they work with the finalized built add-on. Ususally post-scripts only move the add-on to another location in the file system or upload the add-on.

Additional Options

Some scripts might accept additional options which can be set with the args field.

[scripts]
base-path = "scripts/"
pre = [
    {
        run = "templating.rb",
        with = "ruby",
        args = [
            "--trim-mode=%"  # enables Ruby code processing for lines beginning with `%`
        ],
    }
]

All arguments must be of type string even if the script expects a different type. In reality the string is converted by the script just like it would be provided in the command-line. Note that arguments are not pre-processed like shells do meaning you cannot use glob patterns or environment variables.

[scripts]
base-path = "scripts/"
pre = [
    {
        run = "templating.rb",
        with = "ruby",
        args = [
            "--trim-mode=$ERB_TRIM_MODE"  # `$ERB_TRIM_MODE` will be passed to the program as is
        ]
    }
]

A way to achive that is by modifying the source code or wrapping the program with another program which is discussed in the next chapter.

Conditional Scripts

There are often cases where one want to execute simple commands such as tsc to transpile TypeScript along with dynamic arguments such as environment variables.

[scripts]
base-path = "scripts/"
pre = [
    {
        run = "ts_to_js.sh",
        with = "sh",
    }
]
#!/usr/bin/env bash
shopt -s globstar         # allow recursive glob patterns
tsc $ALLAY_PREBUILD/*.ts  # transpile to JavaScript
rm **/*.ts                # remove TypeScript files (from prebuild)

However this cannot be used from operating systems other than Linux such as Windows as they do not have the sh shell. In order to make it compatible with Windows, the same script needs to be written in a supported language such as PowerShell:

tsc $Env:ALLAY_PREBUILD/*.ts                          # transpile to JavaScript
Get-ChildItem * -Include *.ts -Recurse | Remove-Item  # remove TypeScript files (from prebuild)

The configuration would then look like this:

[scripts]
base-path = "scripts/"
pre = [
    {
        run = "ts_to_js.sh",
        with = "bash",
        when = "os = 'linux'",
    },
    {
        run = "ts_to_js.ps1",
        with = "powershell",
        when = "os = 'windows'",
    }
]

Options

arch

Possible values:

  • 'x86'
  • 'x86_64'
  • 'mips'
  • 'powerpc'
  • 'powerpc64'
  • 'arm'
  • 'aarch64'

os

Possible values:

  • 'windows'
  • 'macos'
  • 'ios'
  • 'linux'
  • 'android'
  • 'freebsd'
  • 'dragonfly'
  • 'openbsd'
  • 'netbsd'

family

Possible values:

  • 'unix'
  • 'windows'
  • 'wasm'

allay_version

Possible values:

  • 0.1.0-alpha.1
  • 0.1.0

Operators and Grouping

In addition, conditions can be grouped with parenthesis meaning they are evaluated first or chained and modified with the following operators:

  • condition & condition — evaluates true when both conditions are met
  • condition | condition — evaluates true when at least one of the condition is met
  • condition ^ condition — evaluates true when exactly one of the condition is met
  • ! condition — evaluates true when the condition is not met (example: !os = 'linux')

Conditions can be wrapped in parentheses to group them:

(condition1 & condition2) | condition3

This would require both condition1 and condition2 to evaluate true or just condition3.

Running Executables

[scripts]
# ...
pre = [
    {
        run = "name-of-executable",
        args = ["arg1", "arg2"]
    }
]

This will invoke the name-of-executable executable. Note that the executable is not required to be in the base-path directory. If you want to supply dynamic values such as environment variables use a shell script (with conditions or some other scripting language as a wrapper instead.

Executables are usually installed through some package manager like pip or Cargo. They may be in text or binary format. Because executables in binary format are not cross platform, they are either used with a wrapper and conditions or are present somewhere outside the project’s directory. In that case it is good practise to describe how the executable can be installed.

Reference

The reference covers the details of various areas of Allay.

Configuration

An Allay project only exists if an allay.toml file is present. It contains several configuration options for the project and can be used to configure scripts as well.


The [project.name] section

The name of the project which will be used for the individual add-ons as well unless overridden with pack.*.name where * is one of bp, rp, sp or wt.

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

The [project.description] section

The description of the project which will be used for the individual add-ons as well unless overridden with pack.*.description where * is one of bp, rp, sp or wt.

[project.description]
de-de = "Das ist mein tolles Projekt."
en-us = "This is my amazing project."

The [project] section

The version field

The version of the project of the form n.n.n where n is any natural number or zero.

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

The development field

Specifies whether the project is currently in development. This has impact on the build process and maybe some scripts. This behavior can be overridden with allay build --release.

[project]
# ...
development = true

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.

— from https://learn.microsoft.com/en-us/minecraft/creator/reference/content/addonsreference/examples/addonmanifest

The authors field

An array of people that are considered the “authors” of the project. An optional email address may be included within angled brackets at the end of each author entry.

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

The license field

The name of the license(s). You might want to include a LICENSE.txt file in the root of your project which contains the content of the license.

[project]
# ...
license = "MIT OR Apache-2.0"

The url field

The URL to a site that is the home page for your project.

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

The [localization] section

The primary-language field

The primary language the add-on use. This is the language that untranslated languages will ultimatively fall back to.

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

The localization-groups field

Using this field allows you to override existing language groups or create new ones. This is explained in detail in chapter 4

[localization]
localization-groups = [
  ["ru-ru", "uk-ua"], 
  # puts russian and ukrainian in the same group meaning if a translation for
  # russian exists, ukrainian will fall back to that and vice versa

  ["de-de", "de-at"]
  # puts german german and austrian german, which is not supported natively but
  # could be implemented with a resource pack, in one group
]

The [scripts] section

The base-path field

This is the path relative to the project’s root directory (the directory that contains the allay.toml file) where scripts should be searched for. This is the scripts directory by convention but you are free to choose whatever directory you want.

The pre field

An array of scripts run before building the add-on. This is explained in detail in chapter 3

The post field

An array of scripts run afte the add-ons have been built. This is explained in detail in chapter 3

The [build] section

The include and exclude field

These fields allow you to specify glob patterns which affect the presence of specific files in the built add-on.

include = [
  "**/*.json",
  "**/*.md",
  "**/*.js",
  "**/*.png",
  "**/*.mcfunction",
  "**/*.mcstructure",
  "**/*.lang",
]

The configuration above makes Allay remove all files except the ones that have one of the above mentioned file extensions. Using this field is generally discouraged because an add-on uses several different files with different names and extensions. Minecraft will not complain if the add-on contains unrecognized files. It should be the job of scripts which convert files that are not used in the final build to remove those files.

Using the exclude file on the other hand is acceptable if a script for example does not remove unused files or if you just want to include notices in the source but remove them in the build.

exclude = ["**/LICENSE.txt", "**/README.md"]

The above example keeps all files except those named LICENSE.txt and README.md.

Warning

You cannot set both include and exclude at the same time.

Important

The manifest.json file is the only file that is kept no matter what patterns are specified in include and exclude.

The use-custom-manifest field

Because the allay.toml removes the need of a manifest.json file, Allay will display a warning if it found such file and ignores it. If you, for some reason need to use a custom manifest.json, then set this option to true.

Commands

Tip

You can use commands anywhere inside an Allay project meaning you can allay build it from within the src/WT/ directory for example.

Introduction

In this part of the book we will cover topics that are useful for “developers”. With developer, we mean people who extend the Allay code base or develop software that extends Allay. One example which will be our first topic is writing scripts. Earlier, we have discovered how to use scripts. Now the development of scripts is discussed. This requires another scope of knowledge because you have to know how to program. You can use your scripts for yourself or share them with others.

Scripts

While the official scripts are entirely written in Ruby doesn’t mean you cannot use some other programming language. In fact, you can use any programming language.

It’s generally preferred to use scripting languages such as Lua, Python or Ruby. They all don’t require a compilation step and can be run directly independet from the operating system and architecture.

Consider checking out the official scripts to see how scripts are structured and how they function.

Examples

TOML to JSON

#!/usr/bin/env python3

import sys
assert sys.version_info >= (3, 11), "tomllib is only available in Python 3.11 and above"

import json
import os
from pathlib import Path
import tomllib

BP = Path(os.environ["ALLAY_BP_PATH"])
RP = Path(os.environ["ALLAY_RP_PATH"])
SP = Path(os.environ["ALLAY_SP_PATH"])
WT = Path(os.environ["ALLAY_WT_PATH"])

paths = [BP, RP, SP, WT]
for path in paths:
    for p in path.rglob("*.toml"):
        if p.is_file():
            # read TOML file
            with p.open("rb") as f:
                data = tomllib.load(f)

            # remove TOML file
            p.unlink()

            # write JSON file
            with p.with_suffix("json") as f:
                json.dump(f, data)

auto-import add-on

This example requires the environment variable MINECRAFT_FOLDER to set to the location of the Minecraft data:

  • Windows: %localappdata%\Packages\Microsoft.MinecraftUWP_8wekyb3d8bbwe\LocalState\games\com.mojang
  • Android: storage/emulated/0/Android/data/com.mojang.minecraftpe/files/games/com.mojang
  • iOS & iPadOS: On My iPhone/Minecraft/games/com.mojang
#!/usr/bin/env python3

import os
from pathlib import Path
import shutil

TODO

Structure

Environment Variables

When Allay runs a script, it will also provide several environment variables that can be used within the script. Note that these are only set when Allay executes scripts meaning they are not accessible normally.

Example

# FILE: myscript.py
import os
os.environ["ALLAY_BP_PATH"]
  • ALLAY_BP_PATH — where the prebuilt behavior pack is located
  • ALLAY_BUILD — where the built add-ons are located
  • ALLAY_CONFIG — where the configuration file is located
  • ALLAY_PREBUILD — where the prebuilt add-ons are located1
  • ALLAY_RP_PATH — where the prebuilt resource pack is located
  • ALLAY_RELEASE — whether the add-ons are beeing built in release mode
  • ALLAY_SP_PATH — where the prebuilt skin pack is located
  • ALLAY_VERSION — the version of Allay that is beeing used
  • ALLAY_WT_PATH — where the prebuilt world template is located
  • ALLAY_ROOT — where the project is located

Attention

Allay overrides existing environment variables during execution.

Attention

Always use paths provided by Allay’s environment variables instead of specifying them on your own unless the programming languages doesn’t support accessing environment variables.

Never modify the original source. Allay copies the directories in src into a seperate directory (.allay/prebuild/) which is allowed to be modified. The environment variables refer to that location.

The reason for this is, that some scripts may be nested inside a directory containig all scripts. Now if a script tries to access some path relatively it might break for some users due to their different structure.

# BAD:
from pathlib import Path
path = Path("../.allay/prebuild/BP/")
...
# WORSE:
from pathlib import Path
path = Path("../src/BP/")
...
# GOOD:
import os
from pathlib import Path
path = Path(os.environ["ALLAY_BP_PATH"])
...
1

Usually the ALLAY_*_PATH variables should be used instead of this.

Configuration

Some scripts might rely on options that can be configured by the user. Instead of manually editing constants in the script, one can use the configuartion file.

Using command-line options

Most programming languages a capable of reading arguments provided in the command-line. Rust uses these method to allow users to provide options specific to a script in the configuration file.

from argparse import ArgumentParser

parser = ArgumentParser()
parser.add_argument("name")
parser.add_argument("age", type=int)
args = parser.parse_args()
print(f"You are {args.name} and you are {args.age} years old.")

One can then provide arguments in their configuration file like this:

[scripts]
base-path = "scripts"
pre = [
  { run = "helloworld.py", with = "python", args = ["John Doe", "42"] }
]

Remember to document the arguments somewhere.

Parsing Libraries

LanguageLibrary
Luaargparse
NodeJSparseArg (std)
Pythonargparse (std)
Rubyoptparse (std)

Using [metadata]

Another less recommended approach is the usage of the [metadata] section in the configuration file. It is designed to be used by external programs but parsing TOML is less supported than parsing command-line arguments in many programming languages.

import os
from pathlib import Path
import tomllib

config_file = Path(os.environ["ALLAY_CONFIG"])
with config_file.open("rb") as f:
    data = tomllib.load(f)["metadata"]["helloworld"]

print(f"You are {data['name']} and you are {data['age']} years old.")

One can then provide arguments in their configuration file like this:

[metadata.helloworld]
name = "John Doe"
age = 42

Acknowledgement

The base idea originated from the wonderful Regolith project by Bedrock OSS. Features like scripts (filters) and the project structure are inspired by Regolith. The Bedrock Wiki helped a lot as well for references like localization and the manifest file format.

mdBook is used for the documentation website. Its pretty design and customization support allows us to document Allay in the best possible way.