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:
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
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
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
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.
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
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
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:
on join {
player.helth = 20 // Tippfehler
player.name = "Admin" // schreibgeschützte Eigenschaft
player.give() // fehlendes Argument
}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 0fun 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 stringTypen: 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:
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
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
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") → KINGModule
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.craftmacht dasselbe im Terminal und folgt den Imports. - Im Entwicklungsmodus (
-Dcrafting.dev=true, incrafting sandboximmer 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.