-
Notifications
You must be signed in to change notification settings - Fork 0
Home
The Vyn scripting language, embedded in Minecraft.
Write .vyn scripts inside resource packs and have them run in-game, reacting to what happens around the player β no Java required.
- What is VynAPI?
- What is Vyn?
- What can you do with it?
- Requirements
- Installation
- How it works
- Quick start: your first script
- Events
- How scripts are loaded
- Native API reference
- Vyn language quick tour
- For mod developers: VynAddon
- Building from source
- Project structure
- License & credits
VynAPI is a client-side Minecraft mod that ships the Vyn scripting engine inside the game and exposes Minecraft to it.
It turns every resource pack into a potential "script pack":
your_resource_pack/
βββ assets/
βββ yourmod/
βββ scripts/
βββ hello.vyn β plain-text Vyn script, loaded by VynAPI
Scripts are plain text files, hot-reloadable (F3 + T), and get a full view of the client world: the player, the world, blocks, sounds, biomes, game state, and more. They can draw text on the HUD, play sounds, send the player messages, react to arm swings, and respond to every sound that plays.
Because it is a mod (not a datapack), VynAPI works in singleplayer and multiplayer alike. Builds exist for Fabric, NeoForge, and Forge β Forge is supported on the 1.21.x builds, while the 26.x builds ship for Fabric and NeoForge.
Vyn is a statement-driven scripting language designed for readability, with first-class Blueprints (an OOP system) and concurrency support. It's a standalone Java project built by Abdelaziz_Mohamed β think "a friendlier, modern scripting language" rather than a Minecraft-specific thing. VynAPI is the Minecraft port of that language.
A small taste of the language:
~ variables & constants
make name "Steve"
lock MAX_HEALTH 20
~ conditionals
check name == "Steve" do
say "Hi, Steve!"
otherwise
say "Who are you?"
end
~ loops
cycle i from 1 to 5 do
say "Iteration: " + i
end
~ functions ("tasks")
task double takes x do
reply x * 2
end
~ object orientation ("blueprints")
blueprint Pet do
make name
build takes name do
make me.name name
end
task speak do
say me.name + " says hi!"
end
end
make dog new Pet("Rex")
dog.speak()
Vyn also has native concurrency (split do ... end), HTTP (fetch), JSON (pack/unpack), exception handling (attempt/recover), and hot-loadable modules (use).
Full language documentation lives in the Vyn repository. VynAPI embeds the same engine (
me.abdelaziz:Vyn-Script) and extends it with Minecraft-native bindings.
- No Java, no modding knowledge β write scripts in any text editor, drop them in your pack, and they run.
-
React to game events β every tick (
onTick), arm swings (onSwingHand), any sound that plays (onPlaySound). - Read the game state β player health/hunger/position/velocity, the block being looked at, nearby blocks, the biome, time of day, day count, grass/foliage/water colors...
-
Do things β show debug text on the HUD, send the player chat messages, play sounds at positions, trigger delayed logic with
wait. -
Ship libraries β split code into importable scripts and reuse them with
importScript/excludeScript. -
Hot reload β press
F3 + Twhile developing; your scripts reload instantly.
-
Extend the language β use the
VynAddonAPI to register custom events and bind custom native functions, types, and constants into every script environment. - Reuse the pipeline β script discovery, parsing, event dispatch, and per-tick scheduling are all handled for you.
-
One codebase, three loaders β the mod is built for Fabric, NeoForge, and Forge from shared
commonsources (Forge on the 1.21.x builds; 26.x builds target Fabric & NeoForge).
VynAPI supports Minecraft 1.21.5, 1.21.11, 26.1.x, and 26.2. Each Minecraft version has its own branch and build of the mod β download the one that matches your game version.
| Minecraft | Mod version | Java | Fabric | Forge | NeoForge |
|---|---|---|---|---|---|
| 1.21.5 | 1.21.5-1.0.0 |
21 | β Loader β₯ 0.16.14 + Fabric API 0.128.2+ | β β₯ 55.1.0 | β β₯ 21.5.96 |
| 1.21.11 | 1.21.11-1.0.0 |
21 | β Loader β₯ 0.17.3 + Fabric API 0.141.6+ | β β₯ 61.1.0 | β β₯ 21.11.45 |
| 26.1.x | 26.1-1.0.0 |
25 | β Loader β₯ 0.19.3 + Fabric API 0.145.1+ | β | β β₯ 26.1.2.94 |
| 26.2 | 26.2-1.0.0 |
25 | β Loader β₯ 0.19.3 + Fabric API 0.156.0+ | β | β β₯ 26.2.0.53 |
Note: Minecraft 26.1+ requires Java 25 (and so does the 26.x mod build). The 1.21.x builds use Java 21.
The mod is client-side only β install it in your client's mods folder. No server-side installation is needed (and scripts never run on the server).
- Download the VynAPI jar matching your Minecraft version and loader (e.g.
26.1-1.0.0for NeoForge on Minecraft 26.1) from the releases page (or build it yourself β see Building from source). - Drop the jar into your Minecraft
modsfolder. - Launch the game.
- Put any resource pack containing
.vynscripts in the resource pack folder and enable it β scripts load automatically.
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Minecraft (client) β
β β
β Resource packs βββΊ ScriptLoader (resource reload listener) β
β assets/*/scripts/*.vyn β β
β βΌ β
β ScriptHandler (Vyn engine) β
β ββββββββββββββββββββββββββββββ β
β Mixins βββΊ events: β Vyn environment β β
β onTick β β’ player, world, key, β β
β onSwingHand β modLoader constants β β
β onPlaySound β β’ Block/Position/Sound β β
β β β’ debugText/importScript/ β β
β β excludeScript β β
β β β’ wait statement β β
β ββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β BackgroundLoopHandler (tick scheduler) β
β DebugTextHandler / GuiMixin (HUD text) β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
-
Discovery β on every resource-pack reload,
ScriptLoaderscans every pack in the stack forassets/<any-namespace>/scripts/*.vyn. -
Parsing β
ScriptHandlerfeeds each file to the embedded Vyn engine (VynMain). Every event a script defines (task onTick ... end) is registered; library scripts without events are marked importable. -
Events β Mixins into the vanilla client translate game activity into script events:
-
MinecraftClientMixinβ firesonTickevery frame and drives the tick scheduler. -
ArmSwingMixinβ detects arm swings βonSwingHand. -
SoundListenerMixinβ intercepts every sound played βonPlaySound(with aSoundvalue). -
ClientPacketListenerMixinβ keeps theplayerbinding fresh on login. -
GuiMixinβ rendersdebugTextoutput on the HUD.
-
-
Execution β scripts run on the client thread through the Vyn runtime, with Minecraft types exposed as native bindings. Delays (
wait) and cooldowns are handled byBackgroundLoopHandler, a non-blocking per-tick scheduler.
Create a resource pack with this layout:
my_script_pack/
βββ pack.mcmeta
βββ assets/
βββ example/
βββ scripts/
βββ hello.vyn
hello.vyn:
task onTick do
debugText("Health: " + player.getHealth())
debugText("Looking at: " + player.getTargetBlock().getName())
end
Load the pack, press F3 + T, and two lines appear in the top-left corner of your screen, updating every tick.
React to sounds (the sound argument is a Sound value):
task onPlaySound takes sound do
check sound.getName() == "minecraft:block.note_block.pling" do
player.sendMessage("A note block played near " + sound.getPosition())
end
end
Play a sound when you swing your arm:
task onSwingHand do
player.playSound("minecraft:entity.experience_orb.pickup", 1.0, 1.0)
end
Delayed logic with wait (20 ticks = 1 second, non-blocking):
task onTick do
check player.getHealth() < 6 do
wait 40 do
player.sendMessage("Careful β you're low on health!")
end
end
end
Explore the world:
task onTick do
make pos player.getPosition()
make biome player.getWorld().getBiomeAt(pos)
check player.getWorld().getDayTime() > 13000 do
debugText("It's night in the " + biome + " β hello darkness!")
end
end
Scripts react to events by defining a task whose name matches the event. The mod ships with three built-in events:
Fired every client tick while in-game (not while paused, and only when the player and world exist).
task onTick do
~ runs 20 times per second
end
Fired when the local player swings their arm (attack / use). Throttled by a short swing cooldown that is shortened by Haste and Conduit Power effects.
task onSwingHand do
player.sendMessage("Swing! (health: " + player.getHealth() + ")")
end
Fired whenever a sound plays in the client's world (ambient sounds excluded). Receives one argument: a Sound value.
task onPlaySound takes sound do
player.sendMessage(sound.getName() + " @ vol " + sound.getVolume() + " pitch " + sound.getPitch())
end
Sound method |
Returns |
|---|---|
sound.getName() |
Sound id, e.g. minecraft:block.note_block.pling
|
sound.getVolume() |
Volume (double) |
sound.getPitch() |
Pitch (double) |
sound.getPosition() |
Position where the sound plays (block coords) |
Mod developers: you can register your own events β see VynAddon.
-
Location β
assets/<any-namespace>/scripts/*.vyn. The namespace doesn't matter; every pack in the stack is scanned. -
File name = script name β
hello.vynbecomes scripthello. - No shadowing β if two packs ship a script with the same name, both are loaded (each registered under its own pack id), so packs can't silently override each other.
-
Pack id β every script is tagged with the id of the pack that provided it (the id shown in the resource-pack screen). Pack ids are used by
importScript/excludeScript. -
Reload β scripts (re)load on any resource-pack reload:
F3 + T, or re-applying packs in the pack screen. A summary is printed to the log.
| State | Meaning |
|---|---|
LOADED |
Defines at least one event task β registered for event dispatch. |
IMPORTABLE |
Defines no event tasks β a library that other scripts can importScript. |
ERROR |
Failed to parse or threw during execution β the error is shown in chat and logged. |
EXCLUDED |
Disabled at runtime via excludeScript. |
Loads the code of an importable script into the current script's environment (a use-style module system). Typically called at the top of a script:
~ library.vyn (no event tasks)
task greet takes name do
player.sendMessage("Hello, " + name + "!")
end
~ main.vyn
importScript("file/MyPack.zip", "library")
task onTick do
greet("Steve")
end
If a script imports something that isn't importable (yet), loading is delayed and retried after the other scripts have loaded β so import order between files doesn't matter.
Disables another script at runtime β it stops receiving events.
task onTick do
excludeScript("file/OtherPack.zip", "annoying_beep")
end
Everything below is available inside every .vyn script.
| Constant | Type | Description |
|---|---|---|
player |
Player |
The local player (and their ridden entity). |
world |
World |
The client world. |
key |
Key |
Translation-key helpers. |
modLoader |
ModLoader |
Mod-loader / environment info. |
make pos new Position(100, 64, -200)
make snd new Sound("minecraft:block.note_block.pling", 1.0, 1.0, pos)
| Type | Constructor | Notable methods |
|---|---|---|
Position |
new Position(x, y, z) |
getX/getY/getZ, setX/setY/setZ, getDistanceTo(other | x, y, z)
|
Sound |
new Sound(id, volume, pitch, position) |
getName, getVolume, getPitch, getPosition, plus setters |
Identity & state
| Method | Returns |
|---|---|
player.getName() |
Player name |
player.getUUID() |
UUID string |
player.getGameMode() |
Game mode name (survival, creative, ...) |
player.isLocalPlayer() / isMainPlayer()
|
Whether this is the local player |
player.isSpectator() |
Spectator? |
player.getPermissionLevel() |
Permission level |
player.isCamera() |
Is the camera entity |
Position & rotation
| Method | Returns |
|---|---|
player.getPosition() |
Position (block coords) |
player.getX() / getY() / getZ() |
Exact coordinates |
player.getEyePosX() / getEyePosY() / getEyePosZ() |
Eye position |
player.getYaw() / getPitch() |
Rotation |
player.getHeadYaw() / getBodyYaw() |
Head / body yaw |
player.getWidth() / getHeight() |
Entity dimensions |
player.getFallDistance() |
Fall distance |
player.getVelocityX() / getVelocityY() / getVelocityZ() |
Current velocity |
player.getSpeed() |
Movement speed |
Vitals
| Method | Returns |
|---|---|
player.getHealth() |
Health |
player.getMaxHealth() |
Max health |
player.getAbsorptionAmount() / getMaxAbsorption()
|
Absorption |
player.getFoodLevel() / getSaturationLevel()
|
Hunger / saturation |
player.getAirSupply() / getMaxAirSupply()
|
Air |
player.getArmor() / getArmorCoverPercentage()
|
Armor |
player.isAlive() / isDead()
|
Life state |
player.getLuck() |
Luck |
player.getHurtTime() / getDeathTime()
|
Hurt / death timers |
player.getStuckArrowCount() / getStingerCount()
|
Arrows / stingers |
player.getMoodPercentage() |
Mood (muffled music) |
Blocks & world
| Method | Returns |
|---|---|
player.getTargetBlock() |
Block being looked at |
player.getSteppingBlock() |
Block the player is standing on |
player.getNearbyBlocks(radius) |
List of non-air Blocks in a cube around the player |
player.getWorld() |
World object |
Actions
| Method | Effect |
|---|---|
player.sendMessage(text) |
Shows a chat-style message to the local player |
player.playSound(id, volume, pitch) |
Plays a sound to the player |
player.playSound(sound) |
Plays a Sound value |
player.playSoundWorld(position, id, volume, pitch) |
Plays a sound at a world position |
player.playSoundWorld(position, sound) |
Same, with a Sound value |
| Method | Returns |
|---|---|
world.getBlock(x, y, z) |
Block at coordinates |
world.getDimension() |
Dimension id (e.g. minecraft:overworld) |
world.getDayTime() / getGameTime()
|
Time of day / total game time (ticks) |
world.isBrightOutside() |
Is it bright outside? |
world.getBiomeAt(position) |
Biome id (e.g. minecraft:plains) |
world.getGrassColor(position) / getFoliageColor(position) / getWaterColor(position)
|
Biome-tinted colors (int) |
world.calculateDistanceBetweenPositions(pos1, pos2) |
Distance between two positions |
| Method | Returns |
|---|---|
block.getName() |
Item id of the block (e.g. minecraft:stone) |
block.getPosition() |
Position of the block |
block.isAir() |
Is air? |
block.hasBlockTag(tagId) |
Has a block tag (e.g. minecraft:logs)? |
block.getLightBlock() / getLightEmission()
|
Light values |
block.getSkyDarken() |
Sky darkening |
block.canBeReplaced() |
Replaceable? |
block.hasBlockEntity() |
Has a block entity? |
block.requiresCorrectToolForDrops() |
Needs the right tool? |
block.ignitedByLava() / isRandomlyTicking() / isSolidRender() / canOcclude() / hasLargeCollisionShape()
|
Block-behavior flags |
block.instrument() |
Note-block instrument sound id |
| Method | Returns |
|---|---|
key.getTranslatedKey("key.attack") |
The translated, human-readable name for a key |
| Method | Returns |
|---|---|
modLoader.isModLoaded("fabric-api") |
Is a mod loaded? |
modLoader.isResourcePackLoaded("My Pack") |
Is a resource pack selected? |
modLoader.getRawGameVersion() |
Raw game version string |
| Function | Description |
|---|---|
debugText(text) |
Draws text at the top-left of the HUD for one tick (call every tick to keep it visible). |
importScript(packId, scriptName) |
Loads an importable script's code into the current script. |
excludeScript(packId, scriptName) |
Disables a loaded script (it stops receiving events). |
VynAPI adds a Minecraft-specific statement to the Vyn parser:
wait 20 do
~ runs once, 20 ticks (1 second) later, without blocking the game
player.sendMessage("Time's up!")
end
wait is powered by BackgroundLoopHandler, a tick-based scheduler that runs callbacks on the client thread.
Note: the wait duration is used as the internal slot id, so a second
waitwith the same duration that starts while the first is still pending is skipped (only one pendingwaitper duration at a time).
Full docs: github.com/Abdelaziz1586/Vyn
| Concept | Syntax |
|---|---|
| Variable | make x 10 |
| Constant | lock MAX 100 |
| If / else | check cond do ... otherwise ... end |
| Range loop | cycle i from 1 to 10 do ... end |
| While loop | cycle while cond do ... end |
| Break | escape |
| Function | task name takes a, b do ... reply value end |
| Class |
blueprint Name do ... end (with build, demolish, task methods, me self, mimics inheritance) |
| Concurrency | split do ... end |
| Sleep (blocking) |
hold 2000 (milliseconds) |
| Exceptions | attempt do ... recover err do ... end |
| HTTP / JSON |
fetch(url), pack(obj), unpack(json)
|
| Collections |
new List, new Map, size(obj), at(list, i), sort(list)
|
say "text" (console) |
In Minecraft, never block the client thread. Use VynAPI's
waitinstead ofholdfor delays, and keep per-tick work light (it runs 20Γ/second on the render thread's tick).
VynAddon is the extension point for mods. Subclass it to:
-
Register custom events (passed to the super constructor) β they become callable from scripts as
task <eventName> ... end. -
Bind native functions, types, and constants into every script environment (via
onEnable()).
public final class MyAddon extends VynAddon {
public MyAddon() {
super("onMyEvent"); // custom event scripts can listen to
}
@Override
public Consumer<Environment> onEnable() {
return env -> {
// expose a custom type so scripts can write `new MyType(...)`
NativeBinder.bind(env, MyType.class);
// expose a constant
NativeBinder.defineConstant(env, "myModVersion", "1.0.0");
// expose a native function
env.defineFunction("myFunction", new NativeFunction((e, args) -> {
// ... your logic ...
return null;
}));
};
}
}Then fire your event from anywhere in your mod:
ScriptHandler.fireEvent("onMyEvent", someValue, anotherValue);Any loaded script defining task onMyEvent takes a, b do ... end will receive it.
Useful ScriptHandler utilities:
| Method | Purpose |
|---|---|
ScriptHandler.fireEvent(name, args...) |
Fire an event to all loaded scripts |
ScriptHandler.getEventNames() / containsEvent(name)
|
Inspect registered events |
ScriptHandler.registerSTD(addon) / unregisterSTD(addon)
|
Register/unregister an addon |
ScriptHandler.addScript(packId, name, content) |
Inject a script programmatically |
Cross-loader code uses Java ServiceLoader:
-
Services.PLATFORMβIPlatformHelper(getPlatformName(),isModLoaded(),isDevelopmentEnvironment(), ...) -
ModLoaderinstances are backed by per-loaderModLoaderHandlerimplementations (Fabric/Forge/NeoForge).
The Vyn engine jar (me.abdelaziz:Vyn-Script) is bundled into the mod automatically β on Fabric via include, on Forge/NeoForge via Jar-in-Jar.
Requirements: JDK 21 (for the 1.21.x branches) or JDK 25 (for the 26.x branches), and the Vyn engine artifact available to Gradle.
The Vyn engine (me.abdelaziz:Vyn-Script:1.0-SNAPSHOT) is resolved from mavenLocal() β if you haven't published it, install it first:
git clone https://github.com/Abdelaziz1586/Vyn.git
cd Vyn
mvn clean installThen build VynAPI. Each supported Minecraft version lives on its own branch β check out the one you want before building:
# e.g. the 1.21.11 branch (also: 1.21.5, 26.1, 26.2)
git checkout 1.21.11
# everything
./gradlew build
# or per loader
./gradlew :fabric:build
./gradlew :neoforge:build
# Forge is available on the 1.21.x branches only:
./gradlew :forge:buildRun a dev client:
./gradlew :fabric:runClient
./gradlew :neoforge:runClient
./gradlew :forge:runClient # 1.21.x branches onlyBuilt jars land in <loader>/build/libs/ (e.g. fabric/build/libs/).
VynAPI/
βββ common/ # Shared code: engine integration, mixins, models, handlers
β βββ src/main/java/studio/meraki/vynapi/
β βββ handler/
β β βββ client/ # BackgroundLoopHandler (tick scheduler), InteractionHandler
β β βββ other/ # DebugTextHandler, ModLoader handlers
β β βββ script/ # ScriptHandler (core), ScriptLoader (pack scanning)
β βββ mixin/ # client/player/render/sound mixins (events & HUD)
β βββ model/ # VynAddon, Script/PackScripts, native bindings
β β βββ function/ # debugText, importScript, excludeScript
β β βββ statement/ # wait statement
β β βββ variable/ # Player, World, Block, Position, Sound, Key, ModLoader
β βββ platform/ # ServiceLoader plumbing (IPlatformHelper)
βββ fabric/ # Fabric entrypoints + loader handler + reload listener
βββ forge/ # Forge entrypoint + loader handler (Jar-in-Jar) β 1.21.x branches only
βββ neoforge/ # NeoForge entrypoint + loader handler (Jar-in-Jar)
βββ buildSrc/ # MultiLoader Gradle convention plugins
βββ gradle.properties
- License: All rights reserved β Β© 2026 Abdelaziz, Omar. See LICENSE.
- Vyn language: github.com/Abdelaziz1586/Vyn β MIT licensed.
- Authors: OmarDotContent & Abdelaziz_Mohamed β Vyn Port by Abdelaziz_Mohamed, Mod Port by OmarDotContent.