Interactions
An interaction (minecraft:interaction) is an invisible hitbox the game reports clicks on. It is the usual base for buttons, levers and clickable NPC areas in a datapack, since it needs no item, block or mob. Vanilla creates one with /summon, and the game then only records who clicked it last:
Kore turns the click into a callback. interaction(name) { } declares the interaction, onRightClick and onLeftClick add what runs, and summon places one:
- Objectives: none.
- Storage: none.
- Entities: only the interactions you summon, each tagged
<pack>.interaction.<name>. - Forceloaded chunks: none.
- Every tick: nothing, the game fires the advancement when a click happens.
- Load and files: each click kind a definition uses (
right_click,left_click) generates one advancement atinteraction/<name>_<click>and three functions:interaction_<name>_<click>(reward),interaction_<name>_<click>_hit(callbacks) andinteraction_<click>_target, shared by every definition of the pack. Callbacks briefly tag the player<pack>.interaction.sourceand the interaction<pack>.interaction.clicked.
Right and left clicks
onRightClick { } runs when a player uses the interaction, onLeftClick { } when a player attacks it. A definition can use both, and several callbacks of the same kind run in declaration order.
The callbacks are generated when the interaction(name) { } block returns, so they only count when added inside it. An interaction built with the plain interaction { } (no name) is only a model: summon refuses it when it has callbacks, because nothing was generated for them.
Inside a callback
@s is the player who clicked and the position is the clicked interaction's, so relative coordinates and positioned work from the button. The lambda parameter, clicked by convention, is an Entity selecting that interaction:
A callback with several commands is run through a generated function, named after a hash of its commands so the pack builds identically every time. The interaction builder isn't reachable inside a callback of a plain interaction, onRightClick { width = 2f } doesn't compile. The click callbacks of an NPC are the exception, they reach the NPC builder to declare an objective(key).
Generated output
For the lever above, a right click produces this advancement at data/my_pack/advancement/interaction/lever_right_click.json:
and these functions:
The advancement fires for the clicking player and revokes itself so the next click fires it again. The game stores the last clicker on each interaction, not who clicked now, so the reward function tags the player and the target function (attacker for a left click) keeps the nearby interactions of the definition whose last clicker is that player. Their stored click is cleared after the callbacks ran, since on target and on attacker read it, so it can't fire twice.
A left click is the same with player_hurt_entity, on attacker and data remove entity @s attack.
Hitbox and response
width and height size the hitbox, in blocks: the box is centered on the position horizontally and grows upwards, and the game defaults to 1 for both. response = true makes the game react to a click, the attack sound for a left click and an arm swing for a right click.
| NBT | Kore | Default | Effect |
|---|---|---|---|
height |
height |
1 |
Height of the hitbox, upwards from the position |
response |
response |
false |
Plays the attack sound and swings the arm on clicks |
Tags |
tags |
none | Extra scoreboard tags, the definition tag is added |
width |
width |
1 |
Width of the hitbox, centered on the position |
tags is one of the entity fields every interaction shares with other entities, from the EntitySummonData base: customName, glowing, rotation(yaw, pitch) and the others are available too, and written before height.
Search radius
The reward function only looks at instances of the interaction within searchRadius blocks of the player, 24 by default. A click on an instance farther away runs no callback and leaves its stored click in place, which happens with a wide hitbox or a boosted entity_interaction_range. searchRadius is set per interaction, inside its block, and isn't part of the entity NBT:
An NPC sets it the same way, npc("bob") { interaction { searchRadius = 40 } }.
Hover
Hover callbacks aren't available because the type_specific/player.looking_at predicate needs a per-player check every tick and only tests an entity, it can't tell which one is hovered. A tick function with the predicate does the job when the pack accepts the cost:
OOP handle
summon returns an InteractionEntity, which targets that instance by UUID and takes every Entity extension. The UUID comes from DataPack.entityUUID(), a hash of the pack name, the interaction and its position, so builds stay identical.
One summon call writes one summon line with one fixed UUID, so a function that runs at several places (execute at @a run function place_lever) creates only the first entity, and the next ones fail because the UUID is already taken. Placing one interaction at several positions from one line isn't possible with the handle, since it needs the UUID. The vanilla summon with the model's toNbt() has no UUID and no handle, so it works there, with the definition tag added to tags so the clicks still match:
InteractionEntity lives in the oop module, see Entities & Players.
A Wall Button for a Gate
Without a helper, a clickable gate button is an advancement filtering on the interaction, a reward function that revokes it and finds the clicking player among the interactions, and a function per click. With Kore it is one declaration:
Right clicking the button opens the gate, left clicking it closes it again.
NPCs
An NPC is a mannequin you see and an interaction you click, summoned at the same position. npc(name) { } declares both: mannequin { } configures the body, onRightClick and onLeftClick add the callbacks, and summon places the pair.
The mannequin is immovable and invulnerable by default and carries the tag <pack>.npc.<name>. The interaction is the definition npc_<name>, so it generates what any interaction does. They share a position instead of riding each other, since a passenger sits at the attachment point and would shift the hitbox.
Kore has no hook run when a block ends, so the NPC is registered when the npc(name) { } block returns: the callbacks, objectives and sizes only count when declared inside it, and a name is unique in the pack.
Hitbox
The interaction is as wide and tall as the mannequin's pose plus 0.1, so a click reaches it before the mannequin. Setting width or height in interaction { } keeps your value for that side and sizes the other one from the pose.
| Pose | Mannequin hitbox (width x height) | Interaction |
|---|---|---|
STANDING (default) |
0.6 x 1.8 | 0.7 x 1.9 |
CROUCHING |
0.6 x 1.5 | 0.7 x 1.6 |
SWIMMING and FALL_FLYING |
0.6 x 0.6 | 0.7 x 0.7 |
SLEEPING |
0.2 x 0.2 | 0.3 x 0.3 |
A mannequin in a lying pose is drawn well outside its hitbox, the model of a swimming or sleeping one covers more than the interaction. Set interaction { width = 1.8f; height = 0.6f } to cover the drawing.
Here the interaction is 1.8 wide and 0.3 tall.
Inside an NPC callback
@s is the player and the position is the NPC's, like in any interaction callback, but clicked selects the mannequin, the entity a player sees, instead of the interaction. The selector is sorted by distance, so with two instances standing close together the one at the clicked position wins:
State
objective(key) declares the dummy objective <pack>.npc.<name>.<key>, creates it in the load function npc_<name>_load and returns its name. The callback picks who holds the score, with the usual scoreboard commands:
| Scope | Holder | Example |
|---|---|---|
| Per player and per NPC | the clicking player, self() |
visits of each player to this NPC |
| Per NPC instance | the mannequin, clicked |
how many times this very NPC was greeted |
| Per definition | a fake player, fakePlayer("total") |
clicks on all the instances of this NPC |
For a value that is read back as a Kotlin property, see State Delegates.
An NPC that talks, with timed lines and choices per player, calls a conversation from onRightClick.
NpcEntity
summon returns an NpcEntity holding the two handles, mannequin and interaction, each targeting its instance by UUID. kill() and teleportTo(position, rotation) act on both, the rotation only turns the mannequin.
Any other entity command goes through bob.mannequin or bob.interaction. NpcEntity isn't an Entity because it stands for two entities.
The two UUIDs are fixed per summon call, so a function that runs at several places (execute at @a run function place_npc) creates only the first NPC, and the next ones fail because the UUIDs are already taken. Placing one NPC at several positions from one line isn't possible with summon, since the handle needs the UUIDs. The vanilla summon with the toNbt() of both models has no handle and works there, with the interaction tag added to tags so the clicks still match:
A Shopkeeper that Opens a Menu
A shopkeeper by hand is a mannequin, an interaction, the advancement and functions of the click, a trigger objective for the dialog and a score per player. With Kore it is one declaration: a right click opens a menu and counts the visit of the player, a left click answers in the chat.
- Objectives: one dummy objective per
objective(key), named<pack>.npc.<name>.<key>. - Entities: two per
summon, a mannequin tagged<pack>.npc.<name>and an interaction tagged<pack>.interaction.npc_<name>. - Load and files:
npc_<name>_loadcreates the objectives, only when there are some. The rest is the interaction's, see the footprint above.
