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

events/mining.craftCraftScript
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)
    }
}
events/MiningEvents.ktKotlin
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.
events/hunting.craftCraftScript
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).

events/entities.craftCraftScript
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.

events/falls.craftCraftScript
on damage(e) priority high {
    if e.damage_type == "minecraft:fall" {
        e.amount = e.amount / 2        // softer landings
    }
}
events/ChatEvents.ktKotlin
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.