API
Events
An event describes something that happened in the game: a player joined, a block broke, something took damage. It doesn't describe which internal game code ran, which is why a mod that listens to join keeps working even when that code changes.
Listening to an event
import { give_rubies } from "/lib/rewards"
import { SETTINGS } from "/settings"
on break(e) {
let p = e.player
if p == null { return }
if e.block.type == "minecraft:diamond_ore" and chance(SETTINGS["bonus_chance"]) {
give_rubies(p, 1)
p.play_sound("rubies:ruby_chime")
p.world.spawn_particle("rubies:ruby_spark", e.block.location, 12, 0.4, 0.02)
}
}package com.example.rubies.events
object MiningEvents {
fun register(config: RubiesConfig) {
Crafting.events.on<BlockBreakEvent> { e ->
val player = e.player ?: return@on
if (e.block.type.path == "diamond_ore" && Math.random() < config.dropChance) {
player.give(ItemStack.of(Items.RUBY))
player.playSound(Identifier.parse(Sounds.CHIME))
player.world.spawnParticle(Identifier.parse(Particles.SPARK), e.block.location, count = 12, spread = 0.4, speed = 0.02)
}
}
}
}In CraftScript, the event's properties are available as variables (player, block…) or through the name you give it (on break(e)). You can listen to each event from as many files as you want: put each rule in its topic's file.
All events
All of them fire in the real game, on the client or the server as appropriate. In CraftScript you can use the short or the long name in snake_case (join or player_join).
| CraftScript | Kotlin | Properties | Cancelable |
|---|---|---|---|
server_starting |
ServerStartingEvent |
server | |
start |
ServerStartedEvent |
server | |
stop |
ServerStoppingEvent |
server | |
tick |
ServerTickEvent |
server, tick, phase | |
client_tick |
ClientTickEvent |
tick, phase | |
world_load |
WorldLoadEvent |
world | |
world_unload |
WorldUnloadEvent |
world | |
join |
PlayerJoinEvent |
player, join_message | |
quit, leave |
PlayerQuitEvent |
player, quit_message | |
chat |
PlayerChatEvent |
player, message | Yes |
respawn |
PlayerRespawnEvent |
player, location | |
death |
PlayerDeathEvent |
player, death_message, keep_inventory | |
interact |
PlayerInteractBlockEvent |
player, block, hand, item | Yes |
interact_entity |
PlayerInteractEntityEvent |
player, entity, hand, item | Yes |
attack |
PlayerAttackEvent |
player, target | Yes |
key |
PlayerKeyEvent |
player, key | |
use_item |
PlayerUseItemEvent |
player, item, hand | Yes |
command |
PlayerCommandEvent |
player, command | Yes |
break |
BlockBreakEvent |
player (may be null), block, drop_items | Yes |
place |
BlockPlaceEvent |
player (may be null), block, placed | Yes |
spawn |
EntitySpawnEvent |
entity | Yes |
damage |
EntityDamageEvent |
entity, damage_type, amount (modifiable), attacker | Yes |
entity_death |
EntityDeathEvent |
entity, drops | |
hit |
ProjectileHitEvent |
projectile, shooter, entity or block | Yes |
ready |
ModsLoadedEvent |
mod_ids | |
plugin_reload |
PluginReloadEvent |
plugin_id |
Changing what happens
Some properties can be changed, and the game follows them:
| Event | Property | Effect |
|---|---|---|
chat |
message |
The player says the new text. |
command |
command |
The new command runs, as that player. |
join, quit |
join_message, quit_message |
Announced to everyone, in addition to the game's message. |
respawn |
location |
The player respawns there. |
break |
drop_items = false |
The block drops nothing. |
damage |
amount |
The damage taken is the new value. |
entity_death |
drops.add(…) |
The added items drop where it died, in addition to its normal loot. |
on entity_death(e) {
if e.entity.type == "minecraft:zombie" and chance(0.05) {
e.drops.add(item("rubies:ruby", 1))
}
}interact_entity is a right click on an entity: cancel it and nothing happens (no milking, trading or riding). attack is a player hitting an entity: cancel it and there is no hit. damage gives attacker, whoever is behind the damage: the attacker, or the shooter of a projectile. key runs when a player presses a key of a mod (see Interface).
on interact_entity(e) {
if e.entity.type == "minecraft:cow" { e.player.send("This cow won't be milked"); e.cancel() }
}
on attack(e) {
if e.target.type == "minecraft:pig" { e.player.action_bar("This pig is protected"); e.cancel() }
}Not applied yet. In death, changing death_message or keep_inventory has no effect yet. Crafting warns about it once in the log instead of silently ignoring it.
Priority and cancellation
Order: highest, high, normal, low, lowest and monitor. When a listener cancels the event, the following ones don't run, except monitor listeners (and in Kotlin, those that pass ignoreCancelled = true). A monitor listener only observes: it must not change the event.
on damage(e) priority high {
if e.damage_type == "minecraft:fall" {
e.amount = e.amount / 2 // softer landings
}
}object ChatEvents {
fun register() {
Crafting.events.on<PlayerChatEvent>(EventPriority.HIGHEST) { e ->
if (e.message.startsWith("!")) e.cancel()
}
Crafting.events.on<PlayerChatEvent>(EventPriority.MONITOR) { e ->
if (!e.isCancelled) Crafting.runtime.logger("chat").info("${e.player.name}: ${e.message}")
}
}
}When something fails
- A listener that throws an error is logged with its mod's name, and the other listeners keep running.
/crafting profile(twice: start, then view) shows how much time each mod spends in its events.- An event nobody listens to isn't even built, so listening to few events costs nothing.