Poradniki

CraftScript

Język Craftingu. Angielskie słowa kluczowe, krótka składnia, bez kompilacji: zapisujesz plik, a mod się przeładowuje. Sprawdza się w modzie na dziesięć wierszy i w takim na czterdzieści plików z klasami i typami.

Co możesz zrobić już dziś

Obszar Jak
Przedmioty item "ruby" { … }: rzadkość, połysk, jedzenie, narzędzie, wytrzymałość, tekstura, zakładka trybu kreatywnego, akcja użycia.
Bloki block "ruby_ore" { … }: twardość, odporność na wybuchy, światło, dźwięk, narzędzie i minimalny poziom, łup, tarcie, przezroczystość, akcja użycia. Kształty i modele: zobacz Kształty bloków. Schody, drzwi, płoty, skrzynie, tabliczki, uprawy, rudy i inne rodzaje z gry, siedzenia i bloki o wysokości dwóch bloków: zobacz Rodzaje bloków. Bloki mogą też reagować na kroki i ticki, być portalami, mieć stany, korzystać z redstone i przechowywać przedmioty: zobacz Bloki, przedmioty i receptury.
Generowanie świata ore, tree, patch, structure, biome: to, co pojawia się w nowych chunkach; terrain, biome_map, carver i struktury z plan/build dla lądu, biomów, jaskiń i budowli tworzonych kodem. Zobacz Generowanie świata i Generowanie kodem.
Stworzenia creature "ghost" { … }: ciało stworzenia z gry z twoją teksturą lub własnym modelem 3D, statystykami, zachowaniem, celami i łupem, a także samoistne pojawianie się, oswajanie, hodowlę, jazdę, ataki dystansowe i animacje klatek kluczowych. Zobacz Stworzenia.
Dźwięki i cząsteczki sound "chime" { file: … }, particle "spark" { textures: … }; używane po id. Zobacz Dźwięki i cząsteczki.
Receptury recipe shaped, shapeless, smelting. Modele, łup, tagi i tłumaczenia są generowane za ciebie.
Zdarzenia on join, on break(e)… wszystkie zdarzenia, z priorytetem i anulowaniem.
Zbroje, bronie, czasy odnowienia armor: { slot, defense, texture } do noszenia, on_hit przy uderzeniu, cooldown między użyciami. Zob. Bloki, przedmioty i receptury.
Efekty, atrybuty, wyposażenie e.add_effect(…), e.attribute(…), e.set_attribute(…), e.set_equipment(…). Zob. Świat i gracze.
Klawisze key "dash" { label: "Dash", default: "R" } i on key(e); zdarzenia jednostek on interact_entity i on attack. Zob. Interfejs i Zdarzenia.
Polecenia command pay(to: player, amount: int) requires "eco.pay", z aliasami, opisem i uprawnieniami.
Czas every 5 minutes { }, after 10 seconds { }, on tick.
Świat i gracze Bloki ze stanami, byty, eksplozje, cząsteczki, dźwięki, pora dnia, skrzynie, tabliczki; ekwipunek, zdrowie, tytuły, teleportacja. Zobacz Świat i gracze.
Interfejs form(…) i player.show_form, server.boss_bar, player.set_sidebar. Zobacz Interfejs.
Zapisane dane p.data.set("rubies.visits", n), p.data.get_int("rubies.visits"): w graczach, bytach, blokach i światach; przetrwają restart. Zobacz Świat i gracze.
Ustawienia i sieć config("settings", { … }), listen, send, send_to_server. Zobacz Konfiguracja i sieć.
Duże mody Opcjonalne typy, klasy z dziedziczeniem, typy wyliczeniowe, moduły, stałe.

Tylko w Kotlinie: praca w tle, biblioteki Javy oraz, w generowaniu świata, featury i tagi tworzone kodem.

Pliki

Mod to pojedynczy plik (mods/gems.craft) albo, lepiej gdy się rozrośnie, folder z main.craft i plikami, które importuje. main.craft zaczyna się od nagłówka mod, który podaje id, nazwę i wersję:

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"

Jak podzielić resztę na foldery i dlaczego: Porządkowanie moda.

Wartości i zmienne

lib/values.craftCraftScript
let coins = 100
const MAX_HOMES = 3                          // const: nie można przypisać ponownie
let greeting = "Masz {coins * 2} monet"
let names = ["Ana", "Ben"]
let prices = { gold: 5, ruby: 2 }

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

Liczby (zawsze dziesiętne), napisy, true/false, null, listy i mapy. Cudzysłowy podwójne interpolują {expression}; \{ zapisuje dosłowny nawias klamrowy. Cudzysłowy pojedyncze nie interpolują. Kolory czatu zapisuje się przez &: "&aGreen".

Sterowanie przepływem

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}…") }    // zakres domknięty
    for p in players() { p.send("Start!") }
}

fun who(target: Player?, fallback: Player): string {
    let p = target ?? fallback                  // ?? : wartość, gdy null
    return p?.name ?? "nikt"                    // ?. : bezpieczne przy null
}

Funkcje

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

Parametry z wartościami domyślnymi, return i funkcje anonimowe z fun(x) { … }, za pomocą których przekazujesz je do map, filter czy on_use.

Zdarzenia

Wewnątrz on właściwości zdarzenia są zmiennymi (player, message, block…), albo możesz nazwać zdarzenie: on chat(e). e.cancel() anuluje je, jeśli da się je anulować.

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("&cBądź miły, proszę")
    }
}

Polecenia

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

Wewnątrz polecenia masz sender, player (lub null, jeśli uruchamia je konsola) i args. To, co zwrócisz przez return, jest odpowiedzią. Jeśli brakuje argumentu lub ma zły typ, gracz widzi poprawne użycie.

Czas

events/reminders.craftCraftScript
every 5 minutes { broadcast("&6Pamiętaj, żeby zagłosować") }

after 10 seconds { log("serwer działa od 10 sekund") }

Duże mody

Opcjonalne typy

Tam, gdzie piszesz typ, jest on wymuszany; tam, gdzie nie, kod pozostaje swobodny. Nawet jeśli nie napiszesz żadnego, sprawdzarka zna typy całego API:

events/welcome.craftCraftScript
on join {
    player.helth = 20          // literówka
    player.name = "Admin"      // właściwość tylko do odczytu
    player.give()              // brakujący argument
}
crafting checkTekst
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

Typy: number, string, bool, id, list<T>, map<T>, T? (może być null), any, void, fun, typy API (Player, World, Block, Entity, ItemStack, Location, Inventory…), zdarzenia oraz twoje własne klasy i typy wyliczeniowe. Po sprawdzeniu null sprawdzarka wie, że wartość istnieje:

commands/home.craftCraftScript
command home(target: player?) {
    if target == null { return "Podaj gracza" }
    target.teleport(world().block_at(0, 80, 0).location)   // tutaj target nie jest już null
}

Klasy

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)      // VIP-y płacą połowę
    }
}

Przypisanie pola, którego klasa nie deklaruje, jest błędem (więc literówki się nie prześlizgną). Jeśli klasa definiuje init(…), to jest konstruktor; jeśli nie, argumenty wypełniają pola po kolei. is(value, Class) sprawdza typ.

Typy wyliczeniowe

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

Moduły

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

let wallets = {}

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

Wszystko, co plik deklaruje poza jakimkolwiek blokiem, da się zaimportować: funkcje, klasy, typy wyliczeniowe, stałe. Każdy plik uruchamia się tylko raz, nawet jeśli importuje go kilka plików.

Sprawdzanie, przeładowanie i bezpieczeństwo

  • Wszystko jest sprawdzane przy wczytywaniu moda: błąd blokuje wczytanie i mówi, gdzie wystąpił; ostrzeżenie trafia do dziennika. crafting check main.craft robi to samo z terminala, podążając za importami.
  • W trybie deweloperskim (-Dcrafting.dev=true, zawsze włączony w crafting sandbox) zapisanie przeładowuje mod; tak samo /crafting reload <id>. Nowy przedmiot lub blok wymaga restartu gry; wszystko inne zmienia się od razu.
  • Skrypt widzi tylko API Craftingu: nie może czytać plików, otwierać połączeń ani używać klas Javy.
  • Każde zdarzenie, polecenie lub timer ma limit 2 000 000 kroków: nieskończona pętla zostaje przerwana błędem, zamiast zamrozić serwer.
  • Błąd w czasie działania trafia do dziennika z plikiem i wierszem, a serwer działa dalej.

Każda funkcja, operator i składowa: Dokumentacja CraftScript.