Guías

CraftScript

El lenguaje de Crafting. Palabras clave en inglés, sintaxis corta, sin compilar: guardas el archivo y el mod se recarga. Sirve para un mod de diez líneas y para uno de cuarenta archivos con clases y tipos.

Qué puedes hacer hoy

Área Cómo
Ítems item "ruby" { … }: rareza, brillo, comida, herramienta, durabilidad, textura, pestaña creativa, acción al usar.
Bloques block "ruby_ore" { … }: dureza, resistencia, luz, sonido, herramienta y material mínimo, lo que suelta, fricción, transparencia, acción al usar. Formas y modelos: ver Formas de bloque. Escaleras, puertas, vallas, cofres, carteles, cultivos, menas y los demás tipos del juego, asientos y bloques de dos de alto: ver Tipos de bloque. Los bloques también pueden reaccionar a pisadas y ticks, ser portales, tener estados, usar redstone y guardar objetos: ver Bloques, ítems y recetas.
Generación de mundo ore, tree, patch, structure, biome: lo que aparece en los chunks nuevos; terrain, biome_map, carver y estructuras con plan/build para terreno, biomas, cuevas y edificios hechos por código. Ver Generación de mundo y Generación por código.
Criaturas creature "ghost" { … }: el cuerpo de una criatura del juego con tu textura o tu propio modelo 3D, estadísticas, comportamiento, objetivos y botín, además de aparición natural, doma, cría, monta, ataques a distancia y animaciones por fotogramas clave. Ver Criaturas.
Sonidos y partículas sound "chime" { file: … }, particle "spark" { textures: … }; se usan por su id. Ver Sonidos y partículas.
Recetas recipe shaped, shapeless, smelting. Modelos, botín, etiquetas y traducciones se generan solos.
Eventos on join, on break(e)… todos los eventos, con prioridad y cancelación.
Armadura, armas, enfriamientos armor: { slot, defense, texture } para vestirlo, on_hit al golpear, cooldown entre usos. Ver Bloques, objetos y recetas.
Efectos, atributos, equipo e.add_effect(…), e.attribute(…), e.set_attribute(…), e.set_equipment(…). Ver El mundo y los jugadores.
Teclas key "dash" { label: "Dash", default: "R" } y on key(e); eventos de entidad on interact_entity y on attack. Ver Interfaz y Eventos.
Comandos command pay(to: player, amount: int) requires "eco.pay", con alias, descripción y permisos.
Tiempo every 5 minutes { }, after 10 seconds { }, on tick.
Mundo y jugadores Bloques con estados, entidades, explosiones, partículas, sonidos, hora, cofres, carteles; inventario, vida, títulos, teletransporte. Ver El mundo y los jugadores.
Interfaz form(…) y player.show_form, server.boss_bar, player.set_sidebar. Ver Interfaz.
Datos guardados p.data.set("rubies.visits", n), p.data.get_int("rubies.visits"): en jugadores, entidades, bloques y mundos; sobreviven a un reinicio. Ver El mundo y los jugadores.
Ajustes y red config("settings", { … }), listen, send, send_to_server. Ver Configuración y red.
Mods grandes Tipos opcionales, clases con herencia, enums, módulos, constantes.

Solo en Kotlin: trabajo en segundo plano, librerías de Java y, en la generación de mundo, features y etiquetas hechas por código.

Archivos

Un mod es un archivo suelto (mods/gems.craft) o, mejor en cuanto crece, una carpeta con main.craft y archivos que importa. main.craft empieza con la cabecera mod, que da id, nombre y versión:

mods/rubies/main.craftCraftScript
mod "rubies" { name: "Rubies", version: "1.0.0", author: "you" }

// What exists
import "content/items"
import "content/ores"
import "content/world"
import "content/dimensions"
import "content/creatures"
import "content/sounds"
import "content/particles"
import "content/recipes"

// What happens
import "events/mining"
import "events/hunting"
import "events/welcome"
import "commands/realm"
import "commands/rubies"
import "commands/shop"

Cómo repartir el resto en carpetas, y por qué: Organizar un mod.

Valores y variables

lib/values.craftCraftScript
let coins = 100
const MAX_HOMES = 3                          // const: no se puede reasignar
let greeting = "You have {coins * 2} coins"
let names = ["Ana", "Ben"]
let prices = { gold: 5, ruby: 2 }

names.add("Cleo")
prices["diamond"] = 9
log(len(names), prices.gold, prices.keys())

Números (siempre decimales), textos, true/false, null, listas y mapas. Las comillas dobles interpolan {expresión}; \{ escribe una llave. Las simples no interpolan. Los colores del chat se escriben con &: "&aVerde".

Control de flujo

lib/flow.craftCraftScript
fun describe(coins: number, banned: bool): string {
    if coins > 100 and not banned {
        return "rich"
    } else if coins == 0 {
        return "broke"
    }
    return "fine"
}

fun countdown() {
    for i in 1..5 { broadcast("{6 - i}…") }    // rango inclusivo
    for p in players() { p.send("Go!") }
}

fun who(target: Player?, fallback: Player): string {
    let p = target ?? fallback                  // ?? : valor si es null
    return p?.name ?? "nobody"                  // ?. : no falla con null
}

Funciones

lib/rewards.craftCraftScript
fun give_rubies(p: Player, amount: number = 1) {
    p.give(item("rubies:ruby", amount))
}

fun healthy(): list<Player> {
    return players().filter(fun(p) { return p.health > 10 })
}

Parámetros con valor por defecto, return, y funciones anónimas con fun(x) { … }, que es como se pasan a map, filter o on_use.

Eventos

Dentro de on, las propiedades del evento son variables (player, message, block…), o puedes nombrar el evento: on chat(e). e.cancel() lo cancela si es cancelable.

events/chat.craftCraftScript
const BLOCKED = ["spam", "scam"]

on chat(e) priority high {
    if BLOCKED.any(fun(w) { return e.message.contains(w) }) {
        e.cancel()
        e.player.send("&cPlease keep it friendly")
    }
}

Comandos

commands/heal.craftCraftScript
command heal(target: player?, amount: number?) requires "rubies.heal" alias "h" describe "Heal a player" {
    let p = target ?? player
    if p == null { return "Name a player" }
    p.health = min(p.max_health, p.health + (amount ?? 20))
    return "Healed {p.name}"
}

En un comando existen sender, player (o null si lo ejecuta la consola) y args. Lo que devuelves con return es la respuesta. Si falta un argumento o no es del tipo pedido, el jugador ve el uso correcto.

Tiempo

events/reminders.craftCraftScript
every 5 minutes { broadcast("&6Remember to vote") }

after 10 seconds { log("the server has been up for 10 seconds") }

Mods grandes

Tipos opcionales

Donde escribes un tipo, se exige; donde no, el código sigue libre. Aunque no escribas ninguno, el verificador conoce los tipos de toda la API:

events/welcome.craftCraftScript
on join {
    player.helth = 20          // typo
    player.name = "Admin"      // read-only property
    player.give()              // missing argument
}
crafting checkTexto
welcome.craft:2: error: Player has no property 'helth'. Did you mean 'health'?
welcome.craft:3: error: Player.name is read-only
welcome.craft:4: error: give() expects 1 argument(s), got 0
lib/heal.craftCraftScript
fun heal(p: Player, amount: number = 20): bool {
    p.health = min(p.max_health, p.health + amount)
    return true
}

// heal(player, "a lot")  →  error: Argument 'amount' of heal() must be number, got string

Tipos: number, string, bool, id, list<T>, map<T>, T? (puede ser null), any, void, fun, los de la API (Player, World, Block, Entity, ItemStack, Location, Inventory…), los eventos y tus clases y enums. Después de comprobar null, el verificador sabe que el valor existe:

commands/home.craftCraftScript
command home(target: player?) {
    if target == null { return "Name a player" }
    target.teleport(world().block_at(0, 80, 0).location)   // aquí target ya no es null
}

Clases

lib/wallet.craftCraftScript
class Wallet {
    coins: number = 100

    fun pay(n: number): bool {
        if n > this.coins { return false }
        this.coins -= n
        return true
    }
}

class VipWallet extends Wallet {
    fun pay(n: number): bool {
        return super.pay(n / 2)      // VIPs pay half
    }
}

Asignar un campo que la clase no declara es un error (así no se cuelan erratas). Si la clase define init(…), ese es el constructor; si no, los argumentos rellenan los campos en orden. is(valor, Clase) comprueba el tipo.

Enums

lib/rank.craftCraftScript
enum Rank { PEASANT, KNIGHT, LORD, KING }

fun rank_for(coins: number): Rank {
    if coins >= 1000 { return Rank.LORD }
    return Rank.PEASANT
}

// Rank.LORD.name → "LORD", Rank.values() → [PEASANT, …], Rank.from("king") → KING

Módulos

events/economy.craftCraftScript
import { VipWallet } from "/lib/wallet"
import { rank_for } from "/lib/rank"

let wallets = {}

on join {
    wallets[player.name] = VipWallet()
    player.send("Rank: {rank_for(wallets[player.name].coins).name}")
}

Se importa todo lo que un archivo declara fuera de cualquier bloque: funciones, clases, enums, constantes. Cada archivo se ejecuta una sola vez aunque lo importen varios.

Verificación, recarga y seguridad

  • Al cargar el mod se verifica todo: un error impide cargarlo y dice dónde; un aviso va al log. crafting check main.craft hace lo mismo desde la terminal, siguiendo los imports.
  • En modo desarrollo (-Dcrafting.dev=true, siempre activo en crafting sandbox) guardar recarga el mod; también /crafting reload <id>. Un ítem o bloque nuevo necesita reiniciar el juego; lo demás cambia al momento.
  • Un script solo ve la API de Crafting: no puede leer archivos, abrir conexiones ni usar clases de Java.
  • Cada evento, comando o temporizador tiene un límite de 2.000.000 de pasos: un bucle infinito se corta con un error en lugar de congelar el servidor.
  • Un error al ejecutar se registra con archivo y línea, y el servidor sigue.

Todas las funciones, operadores y miembros: Referencia de CraftScript.