Documentation · python-like scripting
AdvancedScriptsMod
A full Python-like scripting language embedded directly into your Minecraft Forge 1.20.1. Write scripts with variables, loops, functions, imports, and deep access to live server data, note: this is a BETA so all the features here may not work or not yet implemented.
📄Overview
Scripts are plain-text .script files placed inside the scripts/ folder at the root of your server directory. Each script runs on its own background thread, so blocking calls like wait() never stall the server. Subdirectories are supported and are used for import paths.
Script Folder
server/scripts/
Subfolders map to import paths. e.g. utils/math.script → import utils.math
Threading Model
Every script runs in its own daemon thread. Commands and queries are dispatched to the server thread via a CountDownLatch, keeping both threads safe.
🏷Annotations
Annotations must appear as the very first non-blank, non-comment line of the file.
| Annotation | Behavior |
@PREPARE | Auto-executed at server start and on /scripts reload. |
@LIBRARY | When imported, only def definitions and top-level variable assignments are exported. Commands and print() calls are skipped. |
@RUN | When imported, the entire top-level code runs. Overrides the @LIBRARY default. |
@PREPARE
# This script runs automatically at server start
print("Server ready!")
global_var = 42
Note: A file with no annotation behaves like @LIBRARY.
🔣Types & Variables
| Type | Literal Example | Notes |
int | 42, -7 | Java Integer or Long |
float | 3.14 | Java Double |
str | "hello", triple-quoted | Escape sequences \n \t \r \\ \" \' |
bool | True, False | Case-sensitive |
None | None | Null value |
list | [1, "a", True] | Mixed types, mutable, 0-indexed, negative indexing |
dict | {"key": value} | String keys only |
x = 10
x += 5
items = [1, 2, 3]
items[0] = 99
data = {"hp": 20}
data["hp"] = 18
➕Operators
| Category | Operators | Notes |
| Arithmetic | + - * / // % ** | // floor div, ** power. + concatenates lists and strings. |
| Comparison | == != < > <= >= | Double precision |
| Logical | and or not | Short-circuit evaluation |
| Membership | in not in | list, dict (by key), str |
| Unary | - + | |
🔀Control Flow
if / elif / else
if hp < 5:
print("critical!")
elif hp < 10:
print("low health")
else:
print("fine")
for
for i in range(5):
print(i)
for name, score in [["Alice", 10], ["Bob", 5]]:
print(name, score)
while / break / continue / pass
count = 0
while count < 3:
count += 1
wait(20)
📦Functions
def greet(player, msg="Welcome!"):
/say {player}: {msg}
def add(a, b):
return a + b
result = add(3, 4) # 7
First-pass scan: All def statements are registered before any code executes.
🪄Macros (@macro)
A macro runs inside the caller's scope. Variables assigned inside a macro are set directly on the caller.
@macro
def GIVE_DIAMOND(player):
/give {player} minecraft:diamond 64
gave_diamond = True
GIVE_DIAMOND("Steve")
print(gave_diamond) # True
📥Import System
| Syntax | Result |
import utils | Bind module to utils. Call via utils.greet() |
import utils as u | Alias as u |
import path.to.lib | Loads scripts/path/to/lib.script |
from utils import * | Inject all into current scope |
from utils import greet as hi, MAX as LIMIT | Named aliases |
🔧Preprocessor (#define)
#define MAX_HP 20
#define SPAWN_MSG "Spawning entity now"
hp = MAX_HP # becomes: hp = 20
print(SPAWN_MSG)
💬F-Strings
name = "Steve"
hp = 18
msg = f"Player {name} has {hp} HP ({hp / 20 * 100}%)"
/say Hello, {name}!
/give {name} minecraft:apple {hp * 2}
🌐Scope & global
counter = 0
def increment():
global counter
counter += 1
increment()
print(counter) # 1
🔩Built-in Functions
| Function | Signature | Returns |
print | print(*args) | Logs to server console with [Script] prefix |
wait | wait(ticks) | Pauses script. 20 ticks = 1 second. Interruptible. |
len | len(obj) | int |
str | str(val) | Convert to string |
int | int(val) | Convert to integer |
float | float(val) | Convert to float |
bool | bool(val) | Truthiness as boolean |
abs | abs(n) | Absolute value |
min | min(*args) or min(list) | Minimum |
max | max(*args) or max(list) | Maximum |
round | round(n, digits=0) | Rounded number |
sum | sum(list) | Sum of all numeric items |
range | range(stop) / range(start, stop, step) | list of integers |
list | list(iterable?) | New list |
dict | dict() | New empty dict |
type | type(val) | "int", "float", "bool", "str", "list", "dict", "module" |
sorted | sorted(list) | New sorted list |
reversed | reversed(list) | New reversed list |
enumerate | enumerate(list, start=0) | List of [index, item] |
zip | zip(list1, list2) | List of [a, b] pairs |
hasattr | hasattr(module, name) | bool |
getattr | getattr(module, name, default) | Module member or default |
input | input() | Always "" |
📋List Methods
| Method | Description |
list.append(val) | Add item to end |
list.extend(other) | Add all items from another list |
list.insert(i, val) | Insert at index |
list.remove(val) | Remove first occurrence |
list.pop(i=-1) | Remove and return item at index |
list.clear() | Remove all items |
list.sort() | In-place numeric sort |
list.reverse() | In-place reverse |
list.copy() | Shallow copy |
list.count(val) | Count occurrences |
list.index(val) | Index of first occurrence or -1 |
📂Dict Methods
| Method | Description |
dict.get(key, default) | Get value or default |
dict.keys() | List of all keys |
dict.values() | List of all values |
dict.items() | List of [key, value] pairs |
dict.pop(key) | Remove and return value |
dict.update(other) | Merge another dict |
dict.clear() | Remove all entries |
dict.copy() | Shallow copy |
dict.setdefault(key, val) | Set and return if key absent |
dict.has_key(key) | Boolean key existence check |
🔤String Methods
| Method | Description |
str.upper() / str.lower() | Case conversion |
str.strip() / str.lstrip() / str.rstrip() | Trim whitespace |
str.split(sep) | Split on separator. Returns list. |
str.join(list) | Join list items with separator |
str.replace(old, new) | Replace all occurrences |
str.startswith(prefix) | bool |
str.endswith(suffix) | bool |
str.contains(sub) | bool |
str.find(sub) | First index of substring, or -1 |
str.count(sub) | Number of non-overlapping occurrences |
str.format(*args) | Replace {} placeholders in order |
str.zfill(width) | Zero-pad to width |
str.isdigit() | bool |
str.isalpha() | bool |
str.isalnum() | bool |
🗃Database Functions
The script engine includes a lightweight JSON-based key-value store. Each table is a separate file saved at scripts/data/<table>.json. No SQL, no setup — just read and write values by key.
Persistence: Data survives server restarts. Files are created automatically on first write. Supported value types: int, float, str, bool, dict, list, None.
| Function | Signature | Returns |
db_set | db_set(table, key, value) | None — write or overwrite a key |
db_get | db_get(table, key, default?) | Stored value, or default (None) if key absent |
db_exist | db_exist(table) | bool — table file exists on disk |
db_has | db_has(table, key) | bool — key exists in table |
db_keys | db_keys(table) | list[str] — all keys in the table |
db_all | db_all(table) | dict — full table as a dict |
db_delete | db_delete(table, key) | None — remove a single key |
db_clear | db_clear(table) | None — empty the table (file kept) |
db_tables | db_tables() | list[str] — names of all existing tables |
db_drop | db_drop(table) | bool — delete the table file from disk |
Basic usage
# Write values
db_set("players", "Steve_kills", 42)
db_set("players", "Steve_coins", 100)
# Read values (with optional default)
kills = db_get("players", "Steve_kills") # 42
coins = db_get("players", "Steve_money", 0) # 0 (default)
# Existence checks
db_exist("players") # True
db_has("players", "Steve_kills") # True
# Enumerate
print(db_keys("players")) # ["Steve_kills", "Steve_coins"]
print(db_all("players")) # {"Steve_kills": 42, "Steve_coins": 100}
print(db_tables()) # ["players"]
# Cleanup
db_delete("players", "Steve_kills")
db_clear("players") # empties the table
db_drop("players") # deletes the file
Example — coin economy
@PREPARE
while True:
for name in player_list():
if not db_has("economy", name + "_coins"):
db_set("economy", name + "_coins", 0)
coins = db_get("economy", name + "_coins", 0)
db_set("economy", name + "_coins", coins + 1)
/tell {name} §6+1 coin! Total: {coins + 1}
wait(6000) # 5 minutes
Concurrency note: Each db_set / db_get reads and writes the JSON file synchronously. If two scripts write the same table simultaneously, last write wins. Keep writes coarse-grained for high-frequency updates.
⚔Server Commands
Any line starting with / is executed as a server command (permission level 4). Expressions inside {} are evaluated before execution.
player = "Steve"
/gamemode creative {player}
/give {player} minecraft:diamond_sword 1
/tp {player} 0 64 0
Thread-safe: Commands are dispatched to the server thread. Timeout is 10 seconds.
🧍Player Functions
Presence & Count
| Function | Returns |
player_online(name) | bool |
player_list() | list[str] — all online player names |
player_count() | int |
Stats
| Function | Returns |
player_health(name) | float |
player_max_health(name) | float |
player_hunger(name) | int (0–20) |
player_saturation(name) | float |
player_level(name) | int |
player_xp(name) | float (0.0–1.0) |
player_total_xp(name) | int |
player_gamemode(name) | "survival" / "creative" / "adventure" / "spectator" |
Position & Rotation
| Function | Returns |
player_x(name) / player_y(name) / player_z(name) | float |
player_pos(name) | [x, y, z] |
player_yaw(name) / player_pitch(name) | float |
player_dimension(name) | "overworld" / "the_nether" / "the_end" |
State Flags
| Function | Returns |
player_flying(name) | bool |
player_sneaking(name) | bool |
player_sprinting(name) | bool |
player_on_ground(name) | bool |
player_in_water(name) | bool |
player_is_op(name) | bool |
Inventory
| Function | Returns |
player_inventory(name) | List of {"slot", "id", "count", "name"} dicts |
player_hand(name) | {"id", "count", "name"} |
🌍World Functions
| Function | Returns |
world_time() | int — total game ticks |
world_day_time() | int — ticks within day (0–23999) |
world_day() | int |
world_weather() | "clear" / "rain" / "thunder" |
world_difficulty() | "peaceful" / "easy" / "normal" / "hard" |
world_spawn() | [x, y, z] |
🖥Server Functions
| Function | Returns |
server_max_players() | int |
server_tps() | float (max 20.0) |
server_mspt() | float |
🧱Block Functions
| Function | Returns |
block_at(x, y, z) | str block ID, e.g. "minecraft:stone" |
block_at(x, y, z, dim) | Same in specified dimension |
block_is_air(x, y, z) | bool |
block_light(x, y, z) | int 0–15 |
🐉Entity Functions
All entity functions accept a standard Minecraft entity selector: "@e", "@e[type=zombie]", "@e[limit=1,sort=nearest]", etc.
| Function | Returns |
entity_count(sel) | int |
entity_list(sel) | List of entity dicts |
entity_type(sel) | str e.g. "minecraft:zombie" |
entity_name(sel) | str |
entity_uuid(sel) | str |
entity_x/y/z(sel) | float |
entity_pos(sel) | [x, y, z] |
entity_yaw/pitch(sel) | float |
entity_dimension(sel) | str |
entity_velocity(sel) | [vx, vy, vz] |
entity_health(sel) / entity_max_health(sel) | float |
entity_alive(sel) | bool |
entity_on_fire/on_ground/in_water/silent/no_gravity/is_baby(sel) | bool |
entity_tags(sel) | list[str] |
entity_has_tag(sel, tag) | bool |
entity_nbt(sel) | dict |
entity_nbt_all(sel) | list[dict] |
entity_nbt_tag(sel, tag) | any |
🗄NBT Snapshots
player_nbt(name) fields
| Key | Type | Description |
Health / MaxHealth | float | HP |
FoodLevel | int | 0–20 |
XpLevel / XpProgress / TotalXp | int/float/int | |
GameMode | str | |
PosX / PosY / PosZ | float | Position |
Yaw / Pitch | float | Rotation |
Dimension | str | |
Flying / Sneaking / Sprinting / OnGround / InWater / IsOp | bool | |
Name / UUID | str | |
nbt = player_nbt("Steve")
print(nbt["Health"], nbt["XpLevel"])
tag = player_nbt_tag("Steve", "Dimension")
🏆Scoreboard Functions
These functions read and write Minecraft scoreboard objectives and scores directly, without needing to dispatch /scoreboard commands. All target parameters accept either a player name ("Steve") or any entity selector ("@a[tag=active]").
One display slot at a time: Minecraft supports only one objective per display slot. Calling score_display("sidebar", obj) replaces whatever is currently shown. Pass an empty string as the objective to hide the display completely.
Read & Write
| Function | Signature | Returns | Description |
score_get |
score_get(target, objective) |
int |
Returns the score of the first entity matching target. Returns 0 if the player is not tracked in this objective. |
score_set |
score_set(target, objective, value) |
None |
Set score to an exact integer. Applies to all entities matching the selector. |
score_add |
score_add(target, objective, amount) |
None |
Add amount to the current score. Use a negative value to subtract. Applies to all matches. |
score_reset |
score_reset(target, objective) |
None |
Remove the score entry entirely — the player will no longer appear in score_get_all until a new score is set. |
score_test |
score_test(target, objective, min, max) |
bool |
Returns True if the score of the first matching entity is within [min, max] inclusive. Ideal for exact milestone detection without external state. |
score_get_all |
score_get_all(objective) |
dict |
Returns a {"playerName": score} dict of every tracked holder for this objective, including offline players. Useful for leaderboard logic. |
Objective Management
| Function | Signature | Returns | Description |
score_exists |
score_exists(objective) |
bool |
Check whether an objective with this name is registered. Use before score_create to avoid errors on reload. |
score_objectives |
score_objectives() |
list[str] |
Returns the names of all currently registered objectives on the scoreboard. |
score_create |
score_create(name, criteria?, displayName?) |
None |
Register a new objective. criteria defaults to "dummy" if omitted. displayName defaults to name. No-op if the objective already exists — safe to call on every server start. |
score_remove |
score_remove(name) |
None |
Permanently delete an objective and all associated scores from the scoreboard. |
score_display |
score_display(slot, objective) |
None |
Assign an objective to a display slot. Pass "" as objective to clear the slot. Valid slots: "sidebar", "list", "belowName". |
Criteria reference
| Criteria string | Increments when… | Manual control |
dummy | Never — fully manual | Yes — use score_set / score_add |
playerKillCount | Player kills another player | Also writable |
deathCount | Player dies | Also writable |
totalKillCount | Player kills any entity | Also writable |
health | Mirrors current HP (live, read-only in practice) | No |
level | Mirrors XP level (live) | No |
food | Mirrors hunger bar (live) | No |
xp | Mirrors total XP points (live) | No |
Example — Setup on server start
@PREPARE
if not score_exists("kills"):
score_create("kills", "playerKillCount", "☠ Kill Tracker")
score_display("sidebar", "kills")
Example — Milestone title with score_test
while True:
for p in player_list():
if score_test(p, "kills", 5, 5):
/title {p} actionbar [{"text":"5 Kill Streak!","color":"gold","bold":true}]
if score_test(p, "kills", 25, 25):
/title {p} title [{"text":"CHAMPION","color":"red","bold":true}]
/say {p} ha raggiunto 25 kill!
wait(60)
Example — Leaderboard announce with score_get_all
scores = score_get_all("kills")
best = None
top = -1
for p in scores:
if scores[p] > top:
top = scores[p]
best = p
if best != None:
/say Leader: {best} with {top} kill!
Example — Reset all scores
for p in player_list():
score_set(p, "kills", 0)
/say All scores resetted!
Example — Switch sidebar objective
# Hide current sidebar, show deaths instead
score_display("sidebar", "") # clear
score_display("sidebar", "deaths") # show deaths
Auto-criteria: Objectives with criteria like playerKillCount or deathCount are incremented automatically by the server — you don't need to call score_add. Use dummy for scores you manage entirely from scripts.
🧵Threading & Lifecycle
| Behavior | Details |
| Thread interrupt | /scripts stop sets the interrupt flag; the interpreter checks it at every statement boundary and inside wait(). |
| Timeout | Every server query and command has a 10-second timeout. |
| Duplicate prevention | Cannot start a script that is already running by name. |
| Daemon threads | Do not prevent JVM shutdown. |
# /scripts reload — clears module cache and runs all @PREPARE scripts
# /scripts run events/greet
# /scripts stop events/greet
# /scripts stopall
⚡Module Cache
Imported modules are parsed once and stored in a thread-safe cache. Subsequent import statements reuse the cached module without re-reading the file.
Hot-reload: Run /scripts reload after editing a library file to clear the cache.
📚Examples
Auto-broadcast & loop
@PREPARE
messages = ["Welcome!", "Visit our Discord!", "Type /help"]
idx = 0
while True:
/say {messages[idx]}
idx = (idx + 1) % len(messages)
wait(1200)
Low-health warning system
def check_players():
for name in player_list():
hp = player_health(name)
if hp < 4:
/title {name} actionbar {"§cCritical HP: " + str(int(hp)) + "♥"}
while True:
check_players()
wait(10)
Entity janitor
MAX_ZOMBIES = 20
while True:
cnt = entity_count("@e[type=zombie]")
if cnt > MAX_ZOMBIES:
/kill @e[type=zombie,sort=furthest,limit={cnt - MAX_ZOMBIES}]
/say Cleared {cnt - MAX_ZOMBIES} excess zombies.
wait(600)
Library + importer pattern
# lib/rewards.script
@LIBRARY
def give_starter_kit(player):
/give {player} minecraft:iron_sword 1
/give {player} minecraft:bread 16
# welcome.script
from lib.rewards import give_starter_kit
for name in player_list():
give_starter_kit(name)
Note: List comprehensions are not supported. Use a for loop and list.append() instead.