Language Reference
MDL (Minecraft Datapack Language) is a simple language that compiles to Minecraft datapack .mcfunction
files.
Basic Syntax
Pack Declaration
pack "pack_name" "description" pack_format;
Namespace Declaration
namespace "namespace_name";
Variables
// Declare variables with scope
var num counter scope<global> = 0;
var num playerScore = 0; // Defaults to player-specific scope
var num teamScore scope<@a[team=red]> = 0;
// Assignment with explicit scope
counter<global> = 42;
playerScore<@s> = playerScore<@s> + 10;
teamScore<@a[team=red]> = 5;
Variable Substitution
// In strings and commands - variables are automatically resolved to their declared scopes
say Counter: $counter$;
tellraw @a {"text":"Score: $playerScore$","color":"gold"};
// In conditions
if "$playerScore$ > 100" {
say High score!;
}
Functions
function "function_name" {
say Hello World;
tellraw @a {"text":"Welcome!","color":"green"};
}
// Call functions
function "namespace:function_name";
// Call functions with scope
function "namespace:function_name<@a>";
Control Structures
If Statements
if "$counter$ > 5" {
say Counter is high!;
}
if "$health$ < 10" {
say Health is low!;
} else {
say Health is good;
}
While Loops
while "$counter$ < 10" {
counter = counter + 1;
say Counter: $counter$;
}
Raw Command Blocks
// Raw blocks allow direct Minecraft commands without MDL processing
$!raw
scoreboard players set @s player_timer_enabled 1
execute as @a run function mypack:increase_tick_per_player
say "Raw commands bypass MDL syntax checking"
raw!$
// Single-line raw commands
$!raw scoreboard players add @s player_tick_counter 1 raw!$
// Mix MDL and raw commands
say "This is MDL syntax";
$!raw scoreboard players set @s player_timer_enabled 1 raw!$
say "Back to MDL syntax";
Use cases for raw blocks:
- Complex
execute
commands that MDL doesn’t support - Direct scoreboard operations
- Commands with special syntax that conflicts with MDL
- Legacy Minecraft commands not yet supported by MDL
Hooks
on_load "namespace:function_name"; // Runs when datapack loads
on_tick "namespace:function_name"; // Runs every tick
Scope System
MDL uses an explicit scope system where each variable access must specify its scope. This makes code more readable and eliminates hidden state.
Benefits of Explicit Scopes:
- No hidden state - You always see exactly what scope you’re using
- No scope tracking bugs - Each access is self-contained
- More readable -
variable<@s>
is clear and explicit - Easier to debug - No need to trace back where a variable was declared
- Consistent behavior - Variables always behave the same way regardless of context
Variable Declaration Scopes
- No scope specified - Player-specific (defaults to
@s
in execution context) scope<global>
- Server-wide (stored on server armor stand)scope<@a>
- All playersscope<@a[team=red]>
- Team-specificscope<@e[type=armor_stand,tag=something,limit=1]>
- Custom entity
Variable Access with Explicit Scopes
// Each variable access must specify the scope
counter<global> = 42; // Access global counter
playerScore<@s> = playerScore<@s> + 10; // Access player-specific score
teamScore<@a[team=red]> = 5; // Access team-specific score
// Variables in strings automatically use their declared scopes
say Global: $counter$, Player: $playerScore$, Team: $teamScore$;
Function Call Scopes
// Execute as specific selector
function "namespace:function_name<@a>"; // All players
function "namespace:function_name<@s>"; // Current player
function "namespace:function_name<@a[team=red]>"; // Red team players
Explicit Scope System Details
How It Works
The explicit scope system ensures that every variable access is clear and unambiguous:
- Declaration: Variables declare their scope when defined
var num globalVar scope<global> = 0; // Global scope var num playerVar = 0; // Player scope (defaults to @s) var num teamVar scope<@a[team=red]> = 0; // Team scope
- Access: Every variable access must specify the scope
globalVar<global> = 42; // Access global variable playerVar<@s> = playerVar<@s> + 10; // Access player variable teamVar<@a[team=red]> = 5; // Access team variable
- String Substitution: Variables in strings automatically use their declared scopes
say Global: $globalVar$, Player: $playerVar$; // Automatic scope resolution
- Explicit Scopes in Conditions: Override declared scopes in if/while conditions
if "$playerVar<@s>$ > 10" { // Explicit scope in condition say "Player score is high!"; } if "$globalVar<global>$ > 100" { // Global scope in condition say "Global counter reached!"; } if "$teamVar<@a[team=red]>$ > 50" { // Team scope in condition say "Red team is winning!"; }
Scope Mapping
MDL maps scopes to Minecraft selectors:
MDL Scope | Minecraft Selector | Description |
---|---|---|
scope<global> |
@e[type=armor_stand,tag=mdl_server,limit=1] |
Server-wide storage |
scope<@s> |
@s |
Current player |
scope<@a> |
@a |
All players |
scope<@a[team=red]> |
@a[team=red] |
Red team players |
scope<@e[type=armor_stand,tag=something,limit=1]> |
@e[type=armor_stand,tag=something,limit=1] |
Custom entity |
Best Practices
- Be explicit: Always specify the scope when accessing variables
- Use meaningful names:
playerScore<@s>
is clearer thanscore<@s>
- Group related variables: Use consistent scopes for related data
- Document complex scopes: Add comments for non-standard selectors
Examples
Basic Hello World
pack "hello" "A simple hello world datapack" 82;
namespace "hello";
function "main" {
say Hello, Minecraft!;
tellraw @a {"text":"Welcome to my datapack!","color":"green"};
}
on_load "hello:main";
Counter with Explicit Scoped Variables
pack "counter" "Counter example" 82;
namespace "counter";
var num globalCounter scope<global> = 0;
var num playerCounter = 0; // Defaults to player-specific scope
function "increment" {
globalCounter<global> = globalCounter<global> + 1;
playerCounter<@s> = playerCounter<@s> + 1;
say Global: $globalCounter$, Player: $playerCounter$;
}
function "show_all" {
function "counter:increment<@a>";
}
While Loop Example
pack "loops" "Loop example" 82;
namespace "loops";
var num counter scope<global> = 0;
function "countdown" {
counter<global> = 5;
while "$counter$ > 0" {
say Countdown: $counter$;
counter<global> = counter<global> - 1;
}
say Blast off!;
}
Raw Commands
pack "raw" "Raw command example" 82;
namespace "raw";
function "custom" {
// Use raw Minecraft commands
effect give @s minecraft:speed 10 1;
particle minecraft:explosion ~ ~ ~ 1 1 1 0 10;
playsound minecraft:entity.player.levelup player @s ~ ~ ~ 1 1;
}
function "raw_example" {
// Raw blocks allow direct Minecraft commands without MDL processing
$!raw
scoreboard players set @s player_timer_enabled 1
execute as @a run function raw:increase_tick_per_player
say "Raw commands bypass MDL syntax checking"
raw!$
// You can mix MDL and raw commands
say "This is MDL syntax";
$!raw scoreboard players add @s player_tick_counter 1 raw!$
say "Back to MDL syntax";
}
Complete Example
pack "game" "Complete game example" 82;
namespace "game";
// Variables
var num score = 0; // Defaults to player-specific scope
var num level = 1; // Defaults to player-specific scope
var num globalTimer scope<global> = 0;
// Main game function
function "start_game" {
score<@s> = 0;
level<@s> = 1;
say Game started! Level: $level$, Score: $score$;
}
// Level up function
function "level_up" {
if "$score$ >= 100" {
level<@s> = level<@s> + 1;
score<@s> = score<@s> - 100;
say Level up! New level: $level$;
tellraw @a {"text":"Player leveled up!","color":"gold"};
}
}
// Timer function
function "update_timer" {
globalTimer<global> = globalTimer<global> + 1;
if "$globalTimer$ >= 1200" { // 60 seconds
globalTimer<global> = 0;
say Time's up! Final score: $score$;
}
}
// Hooks
on_load "game:start_game";
on_tick "game:update_timer";