Syntax Reference
Complete syntax reference for RedScript.
File Extension
RedScript files use the .mcrs extension.
Comments
// Single-line comment
/*
Multi-line
comment
*/Variables
let name: type = value; // mutable
const NAME: type = value; // constant
let name = value; // type inferredTypes
| Type | Description | Example |
|---|---|---|
int | Integer (scoreboard, 32-bit signed) | 42 |
fixed | Fixed-point ×10000 (renamed from float in v2.5.0) | 10000 (= 1.0) |
double | IEEE 754 double, NBT-backed (new in v2.5.0) | x as double |
string | Text | "hello" |
bool | Boolean | true, false |
int[] | Array of int | [1, 2, 3] |
string[] | Array of string | ["a", "b"] |
selector | Entity selector | @a, @e[type=zombie] |
nbt | NBT data | {Health: 20f} |
v2.5.0:
floatis deprecated and renamed tofixed. Usex as fixed/x as doublefor explicit numeric conversions — implicit coercion is no longer allowed. The compiler will warn onfloatarithmetic used withoutmulfix.
Functions
fn name() {
// body
}
fn name(param: type) {
// body
}
fn name(param: type) -> return_type {
return value;
}
fn name(param: type, optional: type = default) {
// body
}Decorators
@decorator
fn name() { }
@decorator(key=value)
fn name() { }| Decorator | Description |
|---|---|
@load | Run on datapack load |
@tick | Run every game tick |
@tick(rate=N) | Run every N ticks |
@on_trigger("name") | Run when player activates trigger |
@on_death | Run on entity death |
@on_login | Run when player joins server |
@on_advancement("id") | Run when player earns advancement |
@on_craft("item") | Run when player crafts item |
@on_join_team("team") | Run when player joins a team |
@on(EventType) | Run on static event (PlayerDeath, PlayerJoin, BlockBreak, EntityKill, ItemUse) |
@keep | Prevent DCE from removing the function |
Control Flow
if / else
if (condition) {
// body
}
if (condition) {
// body
} else {
// body
}
if (condition) {
// body
} else if (condition) {
// body
} else {
// body
}match
Match supports both enum patterns and integer range patterns:
// Enum patterns
match value {
Pattern::A => { },
Pattern::B => { },
_ => { },
}
// Integer range patterns
let score: int = scoreboard_get(@s, #points);
match score {
90..100 => { say("A grade"); },
80..89 => { say("B grade"); },
70..79 => { say("C grade"); },
_ => { say("Below C"); },
}Range patterns use min..max (inclusive on both ends).
repeat
repeat(count) {
// body runs count times
}for i in range
Iterate over an integer range. The upper bound can be a literal or a variable:
for i in 0..10 {
say("${i}"); // 0 through 9
}
let count: int = get_score(@s, #rounds);
for i in 0..count {
// runs 'count' times
}The range is exclusive on the upper end (0..n → 0, 1, …, n-1).
for x in array
Iterate over every element of an array. The loop variable takes the value of each element in order:
let names: string[] = ["Alice", "Bob", "Carol"];
for name in names {
tell(@a, "Hello, ${name}!");
}
let scores: int[] = [10, 20, 30];
for s in scores {
// s is 10, then 20, then 30
}Works with any element type (int[], string[], struct arrays, etc.). Use for i in 0..arr.len when you also need the index.
break / continue
break exits the innermost loop early. continue skips to the next iteration:
while (true) {
if (score(@s, #lives) <= 0) {
break;
}
// ...
}
foreach (player in @a) {
if (score(player, #skip) == 1) {
continue;
}
give(player, "diamond", 1);
}Both break and continue work in while, foreach, and for i in range loops.
execute
The execute statement maps directly to Minecraft's execute command with a typed body block:
execute as @a at @s run {
setblock ~ ~-1 ~ "stone";
}
execute if block ~ ~-1 ~ "grass_block" run {
say("Standing on grass!");
}
execute positioned 0 64 0 run {
particle("heart", ~0, ~1, ~0);
}
execute store result score @s #points run {
// commands that produce a result
}Supported subcommands:
| Subcommand | Description |
|---|---|
as <selector> | Change executor |
at <selector> | Change position/rotation to entity |
positioned <x> <y> <z> | Set execution position |
positioned as <selector> | Set position to entity |
rotated <yaw> <pitch> | Override rotation |
rotated as <selector> | Copy entity rotation |
facing <x> <y> <z> | Face a position |
facing entity <selector> | Face an entity |
anchored eyes|feet | Set coordinate anchor |
align <axes> | Align to block grid |
in <dimension> | Change dimension |
on <relation> | Navigate entity relations |
if block <x> <y> <z> <block> | Condition on block type |
if score <target> <obj> matches <range> | Condition on score |
unless block ... | Negated block condition |
unless score ... | Negated score condition |
store result score <target> <obj> | Store result in score |
store success score <target> <obj> | Store success flag |
Operators
Arithmetic
| Operator | Description |
|---|---|
+ | Addition |
- | Subtraction |
* | Multiplication |
/ | Division |
% | Modulo |
Comparison
| Operator | Description |
|---|---|
== | Equal |
!= | Not equal |
< | Less than |
> | Greater than |
<= | Less or equal |
>= | Greater or equal |
Logical
| Operator | Description |
|---|---|
&& | And |
|| | Or |
! | Not |
Strings
let s: string = "hello";
let interpolated: string = "Hello, ${name}!";F-Strings (Interpolated Strings)
RedScript supports f-string interpolation using ${expr} inside any string literal. Any expression can appear inside the braces.
let player: string = "Steve";
let score: int = 42;
// Simple variable interpolation
let msg: string = "Hello, ${player}!";
// Expression interpolation
let info: string = "Score: ${score * 2} points";
// Nested computation
let desc: string = "Lives: ${max_lives - used_lives}";v2.6.0: Using
"string" + varfor concatenation is now a compile error. Use f-strings instead:rs// ❌ compile error let bad: string = "Hello " + name; // ✅ correct let good: string = "Hello ${name}";
F-Strings in Chat Commands
When f-strings are used inside tell, title, subtitle, actionbar, or announce, the compiler emits proper Minecraft JSON text components so dynamic values render correctly in-game:
tell(@a, "Your score is ${score(@s, #points)}!");
title(@s, "Round ${round} of ${max_rounds}");
actionbar(@a, "HP: ${score(@s, #hp)} / ${score(@s, #max_hp)}");These compile to MC JSON text like ["", "Your score is ", {"score": {"name": "@s", "objective": "points"}}, "!"].
Arrays
let arr: int[] = [1, 2, 3];
let first: int = arr[0];Structs
struct Name {
field: type,
field2: type,
}
let instance = Name {
field: value,
field2: value,
};
instance.field;Enums
enum Name {
Variant1,
Variant2,
Variant3,
}
let val: Name = Name::Variant1;Impl Blocks
impl blocks attach methods to a struct. Methods receive an implicit self parameter and are called with dot notation.
struct Vec2 {
x: int,
y: int,
}
impl Vec2 {
fn length_sq(self) -> int {
return self.x * self.x + self.y * self.y;
}
fn scale(self, factor: int) -> Vec2 {
return Vec2 { x: self.x * factor, y: self.y * factor };
}
fn add(self, other: Vec2) -> Vec2 {
return Vec2 { x: self.x + other.x, y: self.y + other.y };
}
}
let v = Vec2 { x: 3, y: 4 };
let len_sq: int = v.length_sq(); // 25
let scaled: Vec2 = v.scale(2); // Vec2 { x: 6, y: 8 }Methods defined in impl blocks follow the same visibility rules as top-level functions: names starting with _ are private and subject to dead-code elimination.
Option<T>
Option<T> represents a value that may or may not be present. It has two variants: Some(value) and None.
// Declare an optional value
let maybe: Option<int> = Some(42)
let empty: Option<int> = None
// Unwrap with if let (the only supported unwrap syntax)
if let Some(v) = maybe {
say("Got ${v}")
}Returning Option from functions
fn find_score(target: selector) -> Option<int> {
let s: int = score(target, #points)
if (s < 0) {
return None
}
return Some(s)
}
let result: Option<int> = find_score(@p)
if let Some(pts) = result {
tell(@s, "Points: ${pts}")
}Note: RedScript currently only supports
if let Some(x) = opt { ... }for unwrapping Option.match,.unwrap(),.is_some(),.is_none(), and.unwrap_or()are not yet implemented.
## Generics
Functions and structs can be parameterized with one or more type variables. The type variable is written in angle brackets after the name and can be used anywhere a type is expected in that definition.
```rs
// Generic function – returns the first element of any typed array
fn first<T>(arr: T[]): T {
return arr[0];
}
let n: int = first<int>([10, 20, 30]); // 10
let s: string = first<string>(["a", "b"]); // "a"
// Multiple type parameters
fn zip<A, B>(a: A, b: B): string {
return "${a} / ${b}";
}
// Generic struct
struct Pair<T> {
left: T,
right: T,
}
let p: Pair<int> = Pair { left: 1, right: 2 };Type inference is supported for function calls when the compiler can determine the type from the arguments. Explicit type parameters (
first<int>(...)) are always accepted.
Lambdas
let f = (x: int) => x * 2;
let g = (x: int) => {
// multi-line body
return x * 2;
};Selectors
@a // all players
@e // all entities
@p // nearest player
@s // self
@r // random player
@a[tag=x, distance=..10] // with argumentsNBT Literals
1b // byte
100s // short
1000L // long
1.5f // float
3.14d // double
42 // int
{key: value} // compound
[1, 2, 3] // list
[B; 1b, 0b] // byte array
[I; 1, 2, 3] // int array
[L; 100L, 200L] // long arrayforeach
// Run code as each entity matching the selector
foreach (z in @e[type=zombie]) {
kill(z); // z becomes @s in the compiled function
}
foreach (player in @a) {
give(player, "minecraft:diamond", 1);
}Compiles to:
execute as @e[type=minecraft:zombie] run function ns:fn/foreach_0Execute Context Modifiers
You can attach execute context modifiers directly to a foreach loop to control where and how the body runs. Modifiers are appended after the selector, before the block, and can be stacked in any order.
// Execute at each player's position
foreach (p in @a) at @s {
// body runs at each player's coordinates
}
// Execute 2 blocks above each zombie
foreach (z in @e[type=zombie]) at @s positioned ~ ~2 ~ {
// body runs 2 blocks above each zombie
}
// Execute at each player's position, facing the nearest zombie
foreach (p in @a) at @s rotated ~ 0 facing entity @e[type=zombie,limit=1,sort=nearest] {
// body runs at player position, rotated to face nearest zombie
}Supported Modifiers
| Modifier | Description |
|---|---|
at @s | Execute at the iterated entity's position and rotation |
positioned <x> <y> <z> | Offset the execution position (supports relative ~ and local ^ coordinates) |
rotated <yaw> <pitch> | Override the execution rotation (degrees; ~ keeps current axis) |
facing entity <selector> | Rotate execution to face the matched entity |
facing <x> <y> <z> | Rotate execution to face a fixed position |
anchored eyes|feet | Set the anchor point for ^-relative coordinates |
align <axes> | Align position to the block grid on the specified axes (e.g., xyz, xz) |
Modifiers compile directly to execute sub-commands in the generated .mcfunction file. For example:
# foreach (p in @a) at @s positioned ~ ~2 ~
execute as @a at @s positioned ~ ~2 ~ run function ns:fn/foreach_1Dead Code Elimination (DCE)
RedScript's optimizer automatically removes unreachable functions from the compiled output.
Visibility rules:
- Functions not starting with
_are public — always emitted (callable via/function namespace:name) - Functions starting with
_are private — only kept if called from reachable code - Decorated functions (
@tick,@load,@on_*,@on,@keep) are always kept
fn public_fn() { } // public, always emitted
fn _helper() { } // private, removed if not called
@keep
fn _kept_helper() { } // private name, but @keep forces retentionThis means you can write private utility functions freely — if they're never called, they won't bloat your datapack.