Advanced techniques & tricks
The crown-jewel patterns that turn vanilla data packs into full custom systems β event hooks, raycasting, GUIs, custom crafting, bosses, state machines and the math that powers them, each with a copy-pasteable sketch and the primitives it leans on.
- Event detection via advancements
- Raycasting in mcfunction
- Custom GUIs
- Player motion & custom movement
- Custom crafting beyond vanilla
- Custom mobs & bosses
- Scheduling & self-ticking clocks
- Hitbox detection with interaction entities
- State machines via storage & macros
- Fixed-point math & trig
- Best practices
- Pitfalls & workarounds
- Sources
Every sketch below targets 1.21+ (singular resource folders: function, advancement, recipe, predicate). Since 1.20.5+ item and block-entity data live in the components system (minecraft:custom_data, minecraft:food, β¦), not the old {tag:{β¦}} compound. Entities still use raw NBT (Motion, Health, Tags) β don't conflate the two. Where a sketch shows old-style NBT on an item, translate it to a component.
Event detection via advancements
Vanilla has no "on right-click" hook. The community synthesises events by attaching a reward function to an advancement whose criteria fire on a game action, then revoking the advancement immediately so it can fire again. This event loop is the backbone of almost every interaction pack. The full advancement schema lives on the advancements page; this is the event-loop recap.
Common triggers pressed into service as events:
| Event | Trigger | Notes |
|---|---|---|
| Right-click item in air | minecraft:used_totem-style stat, or a carrot_on_a_stick used stat | Classic method: give a carrot_on_a_stick/warped_fungus_on_a_stick, watch the minecraft.used:minecraft.carrot_on_a_stick custom criterion. |
| Right-click item on block | minecraft:item_used_on_block | Location + item predicate. |
| Right-click an entity 1.19.4+ | minecraft:player_interacted_with_entity | Fires when clicking with a matching item on a matching entity. |
| Consume food | minecraft:consume_item / minecraft:using_item | Any custom item with a minecraft:food/consumable component 1.20.5+. |
| Kill mob | minecraft:player_killed_entity | Entity predicate on the victim. |
| Place block | minecraft:placed_block (older) or scan for the block | Some versions removed placed_block; fall back to an area scan. |
data/example/advancement/right_click.json
{
"criteria": {
"click": {
"trigger": "minecraft:item_used_on_block",
"conditions": {
"item": { "items": ["minecraft:stick"] }
}
}
},
"rewards": { "function": "example:on_click" }
}
data/example/function/on_click.mcfunction
# runs as the player who triggered it
advancement revoke @s only example:right_click
# ... your event logic here ...
say I was clicked!
For "right-click in air with a specific custom item," give the item a carrot_on_a_stick base, or 1.20.5+ a consumable/food component and detect using_item β the food route needs no resource pack at all.
Always put advancement revoke @s only <id> at the very top of the reward function, or it fires exactly once ever. Some triggers were renamed or removed across versions (e.g. placed_block); when a trigger doesn't exist, detect by scanning β compare block states before/after, or use a marker plus an area check. Keep reward functions tiny: they run as the player.
Raycasting in mcfunction
A ray is simulated by a recursive function that steps forward in small increments using local coordinates (^ ^ ^<step>), checking a stop condition (block β air, or an entity in range) at each step, guarded by a decrementing counter so it can't loop forever.
data/example/function/ray.mcfunction (hand-rolled block raycast)
# start: run as @s at @s anchored eyes, then:
# scoreboard players set @s ray_steps 50
# scoreboard players set @s ray_hit 0
# function example:ray
# function example:ray
execute unless block ~ ~ ~ minecraft:air run function example:hit
scoreboard players remove @s ray_steps 1
execute if score @s ray_steps matches 1.. if score @s ray_hit matches 0 \
positioned ^ ^ ^0.1 run function example:ray
# function example:hit
scoreboard players set @s ray_hit 1
setblock ~ ~ ~ minecraft:glowstone
Key mechanics: the step size (0.1) trades precision against command count; the counter caps range (50 Γ 0.1 = 5 blocks); the hit flag short-circuits recursion. For an entity raycast, at each step test execute if entity @e[type=β¦,distance=..0.3] instead of a block.
Hand-rolled step-marching over- and under-shoots thin hitboxes and ignores real collision shapes (slabs, stairs, fences). Unless you're learning, prefer a library. Iris (Aeldrion) is a precision raycasting library 1.20.3+ that walks real block AABBs:
data/example/function/aim.mcfunction (Iris entry point)
execute as <player> at @s anchored eyes positioned ^ ^ ^ run function iris:get_target
# results land in storage iris:output β target type, coords,
# hit block face, and distance in micrometers
Iris accounts for real block AABBs (ray/plane intersection + back-face culling), supports entity targeting, a configurable MaxRecursionDepth (default 16), block whitelist/blacklist, and a Callback function run at the endpoint β all set through iris:settings storage.
Store the endpoint coordinates back into scores/storage for a follow-up action (place a block, spawn a particle, teleport). anchored eyes matters β omit it and the ray starts at foot level. Use large steps for coarse detection, then a second refining pass with a small step.
Small step = accurate but expensive: a 32-block ray at 0.1 is 320 recursive calls β watch maxCommandChainLength. Cap the counter, and run raycasts on demand (a keypress/interaction event), never every tick for every player. Libraries such as Iris or Bookshelf's raycast step by real block boundaries and sidestep the trade-off.
Custom GUIs
Three families of "menu" exist in vanilla-only packs:
- Container menus (chest/hopper/shulker/dropper): open a block or entity inventory, populate slots with clickable custom items, and detect clicks by scanning which slot changed. AjjGUI is the mature take on this.
- Display-entity menus:
text_display+item_displayentities placed in the world (or, with 1.20.2+ eye-anchored positioning, as a HUD-like overlay), with interaction entities as invisible hitboxes over each "button." - Book / actionbar / title text as lightweight menus β clickable
run_command/suggest_commandtext components in written books.
AjjGUI lets a mapmaker drag widgets into a shulker/chest boat in-game and compile them β no file editing. Widget types: placeholder, button, counter, switch, radiobutton, itembin, itemslot, scrollbutton. API convention: ajjgui:__<name> is verbose (feedback), ajjgui:_<name> is silent. Install by dropping the pack, running /reload, then /function ajjgui:__install. Menus open per-player, are multiplayer-safe, and don't disturb inventories.
In-game (AjjGUI)
/function ajjgui:__compile # build a GUI from the staged widgets
/function ajjgui:_openself {id:"my_menu"} # open a personalized instance for @s
data/example/function/scan_click.mcfunction (roll-your-own chest menu)
# Give buttons a namespaced custom_data id; while the menu is open,
# scan the container each tick and read whether a tagged button is in slot 0:
execute as @a[tag=menu_open] run \
execute store result score @s clicked run \
data get entity @s Inventory[{Slot:0b,components:{"minecraft:custom_data":{btn:1b}}}] 0
item_display/text_display with billboard:"center" always faces the player β perfect for floating labels and buttons. Pair each with an interaction entity for a true per-button hitbox (see hitbox detection).
Players can steal items from an open container GUI. Either use read-only display menus, or continuously re-set the container contents and cancel item removal each tick. For anything non-trivial (shift-click, drag, hopper-suck exploits) prefer a library over hand-rolling.
Player motion & custom movement
Player velocity can't be written directly with /data the way a mob's Motion can β the client owns player physics. Two approaches: for non-player entities, write Motion:[x,y,z] NBT; for players, use the 1.20.5+ data-driven enchantment impulse / knockback, or a momentary spectator-swap β which is exactly what the player_motion library (MulverineX) automates.
player_motion applies additive velocity to players via a data-driven impulse enchantment (implemented with a saddle-slot / spectator swap so there are no side effects):
data/example/function/dash.mcfunction (player_motion API)
# Local (relative to facing): x = left/right, y = up/down, z = forward.
# 10000 β 1 block/tick. Additive β stacks with current motion.
function player_motion:api/launch_local_xyz {x:0, y:8000, z:15000}
# Global (absolute axes):
function player_motion:api/launch_global_xyz {x:0, y:5000, z:0}
Limits: MSPT/ping can desync continuous forces; it's experimental on Realms and can't be used on mounted or spectator players. Available on smithed.net/packs/player_motion, MIT, pure mcfunction. For a mob, write Motion directly (doubles, applied for one tick):
data/example/function/mob_dash.mcfunction
# Launch a mob straight up; for a directional launch compute
# the components from rotation via trig (see below) or a library:
execute as @e[type=zombie,tag=dash] at @s run \
data modify entity @s Motion set value [0.0d, 0.4d, 0.0d]
Combine a raycast endpoint with player_motion to build grappling hooks and blink-dashes. For sustained changes, prefer the minecraft:generic.movement_speed and jump_strength attributes over writing Motion every tick.
Writing Motion on a player usually does nothing β the client resyncs immediately. Use knockback events, explosion knockback, minecraft:apply_impulse, or vehicle tricks instead. Remember mob Motion is doubles (the d suffix) and lasts a single tick.
Custom crafting beyond vanilla
Vanilla recipes match custom-component items poorly, so the community uses four patterns:
recipe_craftedadvancement 1.20+: a dummy shaped/shapeless recipe crafts a placeholder; theminecraft:recipe_craftedtrigger (with aningredientspredicate array that can match components) fires a function that swaps the output for the real item. Shows in the recipe book. On 1.21+ results can carry components directly in the recipe JSON.- Floor / drop crafting: scan for dropped item entities in a small radius, verify all ingredients are present, despawn them (
Age:6000s), summon the result. - Container-based crafter: a dropper/dispenser or barrel whose
ItemsNBT is compared against recipe patterns each tick. - Smithing / anvil: intercept via
minecraft:crafter, or detect smithing-table use with a recipe + trigger.
data/example/advancement/custom_sword.json (recipe_crafted route)
{ "trigger": "minecraft:recipe_crafted",
"conditions": { "recipe_id": "example:custom_sword",
"ingredients": [ { "items": ["minecraft:emerald"] },
{ "items": ["minecraft:diamond"] } ] } }
data/example/function/crafted_sword.mcfunction (reward)
advancement revoke @s only example:crafted_sword
clear @s example:placeholder_result 1
give @s minecraft:netherite_sword[custom_data={custom_sword:true}]
data/example/function/floor_craft.mcfunction (drop crafting, concept)
execute at @a as @e[type=item,distance=..6,nbt={Item:{id:"minecraft:emerald"}}] at @s \
if block ~ ~ ~ minecraft:air \
store success entity @s Age short 6000 \
store success entity @e[type=item,distance=..0.5,limit=1,nbt={Item:{id:"minecraft:diamond"}}] Age short 6000 \
run summon item ~ ~ ~ {Item:{id:"minecraft:diamond_block",count:1}}
Libraries: Smithed Crafter is the community-standard shared 3Γ3 so multiple packs coexist; Bookshelf has helpers too.
Age:6000s on an item makes it despawn instantly β used to "consume" ingredients atomically. Chaining store success verifies several ingredients in one command: every link must succeed for the final run to fire.
Vanilla shapeless recipes ignore NBT/components in older versions β that's the whole reason these patterns exist. Post-1.20.5, recipe JSON can specify components on results, but ingredient matching is still limited, so the recipe_crafted trigger route remains common. Always revoke the advancement.
Custom mobs & bosses
A "custom mob" is a vanilla entity β often armor_stand, marker, interaction, or a re-skinned real mob β driven by functions for AI, attributes for stats, and a bossbar for HP display. Marker/interaction entities are cheap anchors; a visible mob or item_display provides the body.
- Body/AI anchor:
armor_stand(equipment/pose),interaction(a real clickable hitbox, 1.19.4+), or a real mob withNoAI:1moved by functions. - Stats:
attribute @s minecraft:generic.max_health base set 300, plusattack_damage,armor,movement_speed, andscale1.20.5+. - HP bar:
bossbar add,bossbar set <id> players @a[β¦], andstore result bossbar <id> valuefrom the mob'sHealth. - Custom AI: a per-tick
execute as @e[tag=boss] at @s run function β¦doing targeting, ability cooldowns and phase changes via a score.
data/example/function/boss/tick.mcfunction (boss bar wired to health)
# on summon:
# bossbar add example:king "The Ancient King"
# bossbar set example:king max 300
# scoreboard players tag @s add king
# every tick β pull the bar value straight from the mob's Health:
execute as @e[tag=king,limit=1] store result bossbar example:king value \
run data get entity @s Health 1
execute as @e[tag=king,limit=1] run \
bossbar set example:king players @a[distance=..40]
Use the scale attribute 1.20.5+ for giant bosses; an interaction entity's width/height set the clickable hitbox for attack detection. Telegraph attacks with minecraft:apply_effect/particles, and give custom drops by merging a DeathLootTable. Smithed's Damage library gives score-based damage that plays nicely across packs.
Vanilla AI is hard to disable cleanly β NoAI:1b freezes it (then you move the mob yourself), or accept vanilla pathing and only override abilities. Boss bars don't auto-remove; clean them up in the death and uninstall paths. Give every custom mob a unique tag (and ideally a stored UUID) and kill by tag β never a bare kill @e.
Scheduling & self-ticking clocks
Two vanilla mechanisms drive time: the #minecraft:tick function tag (runs every tick) and the schedule command (runs a function after N ticks; append vs replace). A self-scheduling function β one that ends by re-scheduling itself β is the idiomatic clock: a controllable-period loop without a heavy per-tick tag.
data/example/function/loop.mcfunction (self-scheduling clock, every 20 ticks)
# ... periodic work ...
schedule function example:loop 20t replace
data/example/function/tick.mcfunction (per-entity ticking)
execute as @e[tag=custom_mob] at @s run function example:mob_tick
Kick loops off from #minecraft:load (or Lantern Load's #load:load), not from #tick directly, so a /reload re-arms them cleanly.
Stagger expensive work across ticks with a counter (score % N) so you don't do everything on the same tick. Use schedule for delays/cooldowns rather than counting down a score every tick, and cancel with schedule clear <function>.
schedule is global β it loses @s and position and runs at world origin. Re-establish context inside the scheduled function (execute as @e[tag=β¦] at @s run β¦). Two schedules on the same function with replace collapse to one β use append if you truly need both pending. Sub-tick precision isn't possible; 1 tick = 50 ms is the floor.
Hitbox detection with interaction entities
Two ways to know "is something here":
- Interaction entities 1.19.4+: invisible entities with a real
width/heighthitbox that record who clicked/attacked them in theirinteraction/attackNBT compounds (each holds{player:UUID, timestamp}). Scan those to detect clicks precisely, per-hitbox. - Area scanning:
execute positioned β¦ if entity @e[distance=..r]orif blocksweeps; combine withdx dy dzvolume selectors for box checks.
data/example/function/buttons.mcfunction (interaction-entity click detection)
# each tick, find interaction entities that were right-clicked, then clear
# the NBT so they can fire again:
execute as @e[type=interaction,tag=button] \
if data entity @s interaction run function example:button_pressed
execute as @e[type=interaction,tag=button] run data remove entity @s interaction
data/example/function/zone.mcfunction (volume test)
execute positioned 100 64 100 if entity @e[type=player,dx=5,dy=3,dz=5] run function example:in_zone
Set response:1b on an interaction entity to make it swing the player's arm on click (feedback). dx/dy/dz define an AABB from the execution position β great for trigger zones. Interaction entities are the modern, exact way to get per-object hitboxes, far better than distance approximations.
Always clear the interaction/attack NBT after reading, or it re-fires forever. Real A* pathfinding is impractical in mcfunction β fake it: raycast toward the target and step the mob along the ray, or use vanilla AI with a moving invisible target. Bound area scans tightly (type + tag + distance) and stagger big volumes across ticks.
State machines via storage & macros
data storage holds structured, typed state (nested compounds/lists) that scoreboards can't. Macros 1.20.2+ template a function's commands from storage/arguments ($(key)), so you can dispatch to different "states" and parameterise behaviour β a genuine state machine.
data/example/function/fsm/step.mcfunction (macro dispatch)
# input: {state:"charging", power:3}
$function example:fsm/state_$(state) {power:$(power)}
data/example/function/fsm/state_charging.mcfunction
$scoreboard players add @s power_meter $(power)
data modify storage example:fsm state set value "release"
Keep the whole machine in one storage path so it's atomic and inspectable; each tick, read it, run the matching state function via macro, and write the next state back.
data modify β¦ set from β¦ copies subtrees cheaply. Macro functions can build entire commands β coordinates, selectors, NBT β from data, which is powerful for generic systems (loot, dialogue trees, quest steps). Combine with return/return run 1.20.2+ for early exits and computed results.
Macros re-compile the command each call and error hard on a missing or invalid $(key) β always supply every referenced key, and validate/whitelist the state string before dispatching. Storage has no per-entity scoping; key by UUID/tag if you need per-entity FSMs.
Fixed-point math & trig
Scoreboards are integers only, so decimals are emulated by scaling (store xΓ1000, remember the implied divisor). Trig comes from lookup tables, polynomial approximations (Bhaskara), or the classic teleport trick β rotate an entity by the angle and read its facing-vector components as sine/cosine.
data/example/function/scale.mcfunction (fixed-point arithmetic)
# represent 3.14159 as 3142 (Γ1000); multiply two scaled values then re-divide:
scoreboard players operation #a math = #pi math # 3142 (= 3.142)
scoreboard players operation #a math *= #radius math # * 5 -> 15710 (= 15.710)
# read a decimal NBT value into a score at scale 1000:
execute store result score #y math run data get entity @s Pos[1] 1000
data/example/function/sin_trick.mcfunction (sine via teleport trick)
# rotate a marker to the angle, then read its local forward vector:
execute as @e[tag=trig,limit=1] rotated <angle> 0 positioned ^ ^ ^1 \
store result score #sin math run data get entity @s Pos[0] 1000 # x-offset ~ sin
The Bookshelf math module provides sin/cos/tan/asin/acos/atan/atan2 (degrees), sincos, exp/log (bases e/2/10/arbitrary), integer & decimal pow/sqrt, factorial (0β12), gcd, and combinations. Fixed-point convention: angle inputs Γ100, trig outputs Γ1000.
data/example/function/bs_sin.mcfunction (Bookshelf)
scoreboard players set $math.sin.x bs.in 4200 # 42.00 degrees
function #bs.math:sin
# result in bs.out, scaled x1000
execute store result β¦ run data get <path> <scale> multiplies as it reads β the standard bridge between decimal NBT and integer scores. atan2 is the key for "angle to target" aiming, and the teleport/rotation trick gives cheap sin/cos with no table.
Integer division truncates and loses precision fast β multiply before dividing and keep the widest scale that won't overflow a 32-bit int. Accumulated rounding drifts, so periodically re-derive values from source positions rather than integrating scores forever. Store true decimals in storage (double/float) where you can, dropping to scaled ints only for scoreboard arithmetic.
Best practices
Namespace everything. Use a unique namespace (yourpack:β¦) for functions, objectives (prefix them, e.g. yp.timer), storage, tags, teams and bossbars. Never touch the minecraft: namespace except the required tags.
Load-guard your init. Don't run gameplay straight from #minecraft:load β it runs before #minecraft:tick and can fire before dependencies exist. Register yourpack:load in Lantern Load's #load:load, guard init behind load.status, and make init idempotent so a /reload is safe.
data/yourpack/function/load.mcfunction
execute if score dep_pack load.status matches 1.. run function yourpack:init
# yourpack:init β create objectives, seed storage, then schedule the loop
scoreboard objectives add yp.timer dummy
schedule function yourpack:tick 1t replace
Never use a bare @e. Always filter by type=, a tag=, and a bounded distance=/volume. Prefer tags over broad selectors β tag entities once and address them by tag. kill @e[tag=yourpack_temp], never kill @e (which deletes item frames, paintings, armor stands and other players' entities).
Minimise per-tick commands. Move work out of #minecraft:tick into self-scheduled loops with the largest acceptable period, stagger heavy work across ticks (score % N), and cache results in storage/scores instead of recomputing.
Structured data β storage; single numbers β scoreboard. Use data storage for records/lists/nested state; scoreboards for counters, arithmetic and fast comparisons. Factor conditions into named predicates and expose extension points as function tags so other packs (and future you) can hook in without editing your files.
Ship an uninstall function. Provide one that removes objectives, bossbars, teams, entities-by-tag and storage, so players can cleanly remove your pack. Prefer schedule β¦ replace for timers/cooldowns over decrementing scores, and keep reward/interaction functions tiny with an immediate advancement revoke.
Pitfalls & workarounds
Selector ordering & limits. @e defaults to sort=arbitrary β set sort= explicitly when order matters, and always set limit=1 when you expect one entity. [tag=a,tag=b] is AND; there's no native OR (use predicates or multiple commands).
Execution context β @s vs @p/@a. Inside a function, @s is whoever the function is executing as β not necessarily the nearest player. Advancement reward functions run as the triggering player; scheduled functions run with no @s at world origin. Re-establish context after a schedule.
- Since 1.20.5: item/block-entity data is components, not the old
tagcompound.give @s stick{β¦}βgive @s stick[custom_data={β¦}]; selector item checks readcomponents/minecraft:custom_data, not{tag:β¦}. - Entities still use raw NBT (
Motion,Health,Tags) β don't conflate the two systems. - Because of this split, many pre-1.20.5 online snippets are wrong on current versions; verify NBT-vs-component syntax against Misode or mcmeta before pasting.
Can't mutate a held item's components in place. There's no clean command for it; use item replace/item modify with an item modifier, data modify the container/entity Inventory[β¦] slot, or clear + give.
maxCommandChainLength & recursion. Recursive functions (raycasts, loops) and long chains hit the gamerule cap (default 65536). Cap counters, split work across ticks, and raise the gamerule only as a last resort.
Chunk loading. Commands acting on unloaded chunks silently do nothing. Use forceload add/remove for fixed regions, or keep an entity/marker in the chunk. Always forceload remove when done to avoid leaking loaded chunks.
Scheduling vs tick timing. schedule is global and delayed by whole ticks only; it can't guarantee sub-tick ordering. Two replace schedules on the same function collapse to one β use append for both. Overwriting vanilla loot tables breaks other packs, which is why libraries like BlockState use hardcoded internal tables.
Sources
- MCC wiki β questions hub
- MCC wiki β item click & interaction entities
- MCC wiki β raycast
- MCC wiki β custom crafting
- Iris raycasting library (Aeldrion)
- AjjGUI
- player_motion (MulverineX) Β· smithed.net/packs/player_motion
- Smithed libraries (Crafter, Damage, β¦)
- Bookshelf documentation (math, raycast)
- Lantern Load
- moxlib
- minecraft.wiki β Data pack