Anleitungen

CraftScript

Die Sprache von Crafting. Englische Schlüsselwörter, kurze Syntax, kein Kompilieren: Du speicherst die Datei und die Mod wird neu geladen. Sie taugt für eine Mod mit zehn Zeilen und für eine mit vierzig Dateien, Klassen und Typen.

Was du heute machen kannst

Bereich Wie
Gegenstände item "ruby" { … }: Seltenheit, Glanz, Nahrung, Werkzeug, Haltbarkeit, Textur, Kreativ-Tab, Benutzungsaktion.
Blöcke block "ruby_ore" { … }: Härte, Explosionswiderstand, Licht, Geräusch, Werkzeug und Mindeststufe, Drops, Reibung, Transparenz, Benutzungsaktion. Formen und Modelle: siehe Blockformen. Treppen, Türen, Zäune, Truhen, Schilder, Nutzpflanzen, Erze und die anderen Arten des Spiels, Sitze und zwei Blöcke hohe Blöcke: siehe Blockarten. Blöcke können außerdem auf Schritte und Ticks reagieren, Portale sein, Zustände haben, Redstone nutzen und Gegenstände aufnehmen: siehe Blöcke, Gegenstände und Rezepte.
Weltgenerierung ore, tree, patch, structure, biome: was in neuen Chunks erscheint; terrain, biome_map, carver und Strukturen mit plan/build für Land, Biome, Höhlen und Bauwerke, die per Code entstehen. Siehe Weltgenerierung und Generierung per Code.
Kreaturen creature "ghost" { … }: der Körper einer Kreatur aus dem Spiel mit deiner Textur oder deinem eigenen 3D-Modell, Werten, Verhalten, Zielen und Beute, dazu natürliches Erscheinen, Zähmen, Züchten, Reiten, Fernangriffe und Keyframe-Animationen. Siehe Kreaturen.
Geräusche und Partikel sound "chime" { file: … }, particle "spark" { textures: … }, benutzt über ihre ID. Siehe Geräusche und Partikel.
Rezepte recipe shaped, shapeless, smelting. Modelle, Beute, Tags und Übersetzungen werden für dich erzeugt.
Events on join, on break(e)… alle Events, mit Priorität und Abbrechen.
Rüstung, Waffen, Abklingzeiten armor: { slot, defense, texture } zum Tragen, on_hit beim Treffen, cooldown zwischen Benutzungen. Siehe Blöcke, Gegenstände und Rezepte.
Effekte, Attribute, Ausrüstung e.add_effect(…), e.attribute(…), e.set_attribute(…), e.set_equipment(…). Siehe Die Welt und die Spieler.
Tasten key "dash" { label: "Dash", default: "R" } und on key(e); Entitätsereignisse on interact_entity und on attack. Siehe Oberfläche und Ereignisse.
Befehle command pay(to: player, amount: int) requires "eco.pay", mit Aliasen, Beschreibung und Berechtigungen.
Zeit every 5 minutes { }, after 10 seconds { }, on tick.
Welt und Spieler Blöcke mit Zuständen, Objekte, Explosionen, Partikel, Geräusche, Tageszeit, Truhen, Schilder; Inventar, Gesundheit, Titel, Teleportieren. Siehe Die Welt und die Spieler.
Oberfläche form(…) und player.show_form, server.boss_bar, player.set_sidebar. Siehe Oberfläche.
Speicherdaten p.data.set("rubies.visits", n), p.data.get_int("rubies.visits"): an Spielern, Objekten, Blöcken und Welten, übersteht einen Neustart. Siehe Die Welt und die Spieler.
Einstellungen und Netzwerk config("settings", { … }), listen, send, send_to_server. Siehe Konfiguration und Netzwerk.
Große Mods Optionale Typen, Klassen mit Vererbung, Enums, Module, Konstanten.

Nur in Kotlin: Hintergrundarbeit, Java-Bibliotheken und, in der Weltgenerierung, Features und Tags per Code.

Dateien

Eine Mod ist eine einzelne Datei (mods/gems.craft) oder, besser sobald sie wächst, ein Ordner mit main.craft und den Dateien, die sie importiert. main.craft beginnt mit dem Kopf mod, der ID, Namen und Version angibt:

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"

Wie du den Rest in Ordner aufteilst und warum: Eine Mod organisieren.

Werte und Variablen

lib/values.craftCraftScript
let coins = 100
const MAX_HOMES = 3                          // const: kann nicht neu zugewiesen werden
let greeting = "Du hast {coins * 2} Münzen"
let names = ["Ana", "Ben"]
let prices = { gold: 5, ruby: 2 }

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

Zahlen (immer Dezimalzahlen), Strings, true/false, null, Listen und Maps. Doppelte Anführungszeichen interpolieren {expression}, \{ schreibt eine wörtliche geschweifte Klammer. Einfache Anführungszeichen interpolieren nicht. Chatfarben schreibst du mit &: "&aGrün".

Kontrollfluss

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

fun countdown() {
    for i in 1..5 { broadcast("{6 - i}…") }    // Bereich inklusive Ende
    for p in players() { p.send("Los!") }
}

fun who(target: Player?, fallback: Player): string {
    let p = target ?? fallback                  // ?? : Wert, falls null
    return p?.name ?? "niemand"                 // ?. : sicher bei null
}

Funktionen

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 })
}

Parameter mit Standardwerten, return und anonyme Funktionen mit fun(x) { … }, so gibst du sie an map, filter oder on_use weiter.

Events

Innerhalb von on sind die Eigenschaften des Events Variablen (player, message, block…), oder du gibst dem Event einen Namen: on chat(e). e.cancel() bricht es ab, wenn es abbrechbar ist.

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("&cBitte bleib freundlich")
    }
}

Befehle

commands/heal.craftCraftScript
command heal(target: player?, amount: number?) requires "rubies.heal" alias "h" describe "Einen Spieler heilen" {
    let p = target ?? player
    if p == null { return "Nenn einen Spieler" }
    p.health = min(p.max_health, p.health + (amount ?? 20))
    return "{p.name} geheilt"
}

In einem Befehl hast du sender, player (oder null, wenn die Konsole ihn ausführt) und args. Was du mit return zurückgibst, ist die Antwort. Fehlt ein Argument oder hat es den falschen Typ, sieht der Spieler die richtige Verwendung.

Zeit

events/reminders.craftCraftScript
every 5 minutes { broadcast("&6Denk ans Abstimmen") }

after 10 seconds { log("der Server läuft seit 10 Sekunden") }

Große Mods

Optionale Typen

Wo du einen Typ schreibst, wird er erzwungen, wo nicht, bleibt der Code frei. Auch wenn du keinen schreibst, kennt die Prüfung die Typen der gesamten API:

events/welcome.craftCraftScript
on join {
    player.helth = 20          // Tippfehler
    player.name = "Admin"      // schreibgeschützte Eigenschaft
    player.give()              // fehlendes Argument
}
crafting checkText
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

Typen: number, string, bool, id, list<T>, map<T>, T? (darf null sein), any, void, fun, die API-Typen (Player, World, Block, Entity, ItemStack, Location, Inventory…), Events und deine eigenen Klassen und Enums. Nach einer Prüfung auf null weiß die Prüfung, dass der Wert existiert:

commands/home.craftCraftScript
command home(target: player?) {
    if target == null { return "Nenn einen Spieler" }
    target.teleport(world().block_at(0, 80, 0).location)   // target ist hier nicht mehr null
}

Klassen

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 zahlen die Hälfte
    }
}

Einem Feld einen Wert zuzuweisen, das die Klasse nicht deklariert, ist ein Fehler (so rutschen keine Tippfehler durch). Definiert die Klasse init(…), ist das der Konstruktor, sonst füllen die Argumente die Felder der Reihe nach. is(value, Class) prüft den Typ.

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

Module

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

let wallets = {}

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

Alles, was eine Datei außerhalb eines Blocks deklariert, ist importierbar: Funktionen, Klassen, Enums, Konstanten. Jede Datei läuft nur einmal, auch wenn mehrere Dateien sie importieren.

Prüfen, neu laden und Sicherheit

  • Alles wird beim Laden der Mod geprüft: Ein Fehler verhindert das Laden und sagt, wo er liegt, eine Warnung geht ins Log. crafting check main.craft macht dasselbe im Terminal und folgt den Imports.
  • Im Entwicklungsmodus (-Dcrafting.dev=true, in crafting sandbox immer an) lädt Speichern die Mod neu, ebenso /crafting reload <id>. Ein neuer Gegenstand oder Block braucht einen Neustart des Spiels, alles andere ändert sich sofort.
  • Ein Skript sieht nur die Crafting-API: Es kann keine Dateien lesen, keine Verbindungen öffnen und keine Java-Klassen benutzen.
  • Jedes Event, jeder Befehl und jeder Timer hat ein Limit von 2.000.000 Schritten: Eine Endlosschleife wird mit einem Fehler abgebrochen, statt den Server einzufrieren.
  • Ein Laufzeitfehler wird mit Datei und Zeile geloggt, und der Server läuft weiter.

Alle Funktionen, Operatoren und Member: CraftScript-Referenz.