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.
- Make sure you have Rust and Cargo installed.
- Install with
cargo install --git https://github.com/allay-mc/allay.git
or clone the repository withgit clone https://github.com/allay-mc/allay.git
and then runcargo 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"
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
The threaded
field
The panic
field
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.
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 theallay.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:
options
field in[[plugin]]
section — Tranforms the value into JSON and passes it as the first (or second whenwith
is set) argument for the executable.args
field in[[plugin]]
section — Passes each string to the executable as arguments.env
section — The plugin can access environment variables set in the configuration file.- 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 isso
.dll_prefix()
— Specifies the filename prefix used for shared libraries on this platform. Example value islib
.dll_suffix()
— Specifies the filename suffix used for shared libraries on this platform. Example value is.so
.env(key)
— Fetches the environment variablekey
from the current process or an empty string ifkey
is absent or the value is invalid unicode.env_present(key)
— Evaluatestrue
when the environment variable with the keykey
is present orfalse
otherwise.exe_extension()
— Specifies the file extension, if any, used for executable binaries on this platform. Example value isexe
.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 isunix
.os()
— A string describing the specific operating system in use. Example value islinux
.
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.
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.
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.
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.
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.
{
"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.