Poradniki

Porządkowanie moda

Mod piszesz raz, a utrzymujesz go latami. W utrzymaniu najbardziej pomaga nie język, tylko to, gdzie co leży. Ten poradnik proponuje jedną strukturę dla CraftScript i jedną dla Kotlina oraz wyjaśnia dlaczego.

Cztery zasady

  1. Jeden plik, jedno zadanie. Jeden plik na rudy, drugi na receptury, kolejny na każde polecenie. Jeśli do opisania pliku potrzebujesz słowa „i”, to prawdopodobnie są to dwa pliki.
  2. Grupuj według tematu, nie rozmiaru. Foldery takie jak content/, events/, commands/, ui/ i lib/. Każdy, kto otworzy mod, wie, gdzie szukać, bez czytania kodu.
  3. Oddziel dane od zachowania. To, co istnieje (przedmioty, bloki, receptury), to lista właściwości. To, co się dzieje (zdarzenia, polecenia), to kod. Jeśli je wymieszasz, zmiana liczby zmusza cię do czytania logiki.
  4. Punkt wejścia, który tylko łączy części. main.craft lub klasa moda mówią, jakie części istnieją. Nie zawierają zasad gry.

Dzięki temu zmiana balansu moda to edycja content/recipes.craft; poprawka polecenia to otwarcie commands/; a błąd w zdarzeniu nie chowa się wśród dwustu wierszy bloków.

W CraftScript

Zalecana strukturaTekst
mods/rubies/
├── main.craft               the mod and the list of its parts
├── settings.craft           settings for the server owner
├── content/                 data: what exists
│   ├── items.craft
│   ├── ores.craft
│   ├── world.craft
│   ├── dimensions.craft
│   ├── creatures.craft
│   ├── sounds.craft
│   ├── particles.craft
│   └── recipes.craft
├── events/                  behaviour: what happens
│   ├── mining.craft
│   ├── hunting.craft
│   └── welcome.craft
├── ui/                      forms: what the player sees
│   └── shop.craft
├── commands/
│   ├── realm.craft
│   ├── rubies.craft
│   └── shop.craft
├── lib/                     shared logic
│   ├── rewards.craft
│   └── storage.craft        saved data: the keys, in one place
├── sounds/
│   └── ruby/chime.ogg
├── structures/
│   └── ruby_shrine.nbt
└── textures/
    ├── item/ruby.png
    ├── block/ruby_ore.png, ruby_log.png, ruby_leaves.png, ruby_flower.png
    ├── entity/ruby_golem.png
    └── particle/ruby_spark_0.png, ruby_spark_1.png

mods/rubies/

Tylko deklaruje mod i wymienia jego części. Czytając go, wiesz, co jest i gdzie.

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 działają importy

  • import "content/items" uruchamia ten plik jeden raz. Przedmioty, bloki, zdarzenia i polecenia, które deklaruje, stają się częścią moda, tak jakby były w main.craft.
  • import { give_rubies } from "/lib/rewards" sprowadza konkretne nazwy. Ścieżka zaczynająca się od / jest czytana od folderu moda, więc działa tak samo z każdego podfolderu; bez / jest względna wobec importującego pliku. Rozszerzenie .craft jest opcjonalne.
  • import "lib/rewards" as rewards sprowadza cały moduł pod jedną nazwą (rewards.give_rubies(p, 1)). Bez as nazwą jest nazwa pliku, więc dwa importowane pliki o tej samej nazwie nadpisują się nawzajem: użyj różnych nazw albo as.
  • Żadna ścieżka nie może wyjść poza folder moda, a import cykliczny daje błąd, który mówi, gdzie wystąpił.

Wspólna logika

Funkcja używana przez dwa pliki trafia do lib/. Jeśli logika ma własny stan (ekonomia, kolejka zadań), użyj klasy z metodami, również w lib/; zobacz CraftScript. Zmienna używana tylko przez jeden plik zostaje w tym pliku, ale pamiętaj, że po restarcie przepada: wszystko, co ma przetrwać, trafia do zapisanych danych.

Ustawienia i zapisane dane we własnych plikach

Dwie rzeczy, z których korzysta reszta moda, ale których wnętrza nie powinna znać:

  • Ustawienia (settings.craft, w katalogu głównym): jedno config(…) ze wszystkimi wartościami domyślnymi. Inne pliki importują SETTINGS i czytają, czego potrzebują; żaden nie ma wpisanych na sztywno liczb balansu.
  • Zapisane dane (lib/storage.craft): klucze i kilka małych funkcji, takich jak visits(p) i add_visit(p). Kod, który liczy wizyty, wywołuje add_visit(player) i nie wie, że istnieje klucz rubies.visits. Jeśli kiedyś się zmieni, zmieniasz go w jednym miejscu.

Nie nazywaj tego pliku config.craft: zaimportowany bez as, jego nazwa przesłoniłaby funkcję config.

W Kotlinie

Zalecana strukturaTekst
rubies/
├── build.gradle.kts
├── settings.gradle.kts
└── src/main/
    ├── kotlin/com/example/rubies/
    │   ├── RubiesMod.kt         entry point: only wires the parts
    │   ├── RubiesConfig.kt      player settings
    │   ├── content/
    │   │   ├── Items.kt
    │   │   ├── Blocks.kt
    │   │   ├── Creatures.kt
    │   │   ├── Sounds.kt
    │   │   ├── Particles.kt
    │   │   └── Recipes.kt
    │   ├── storage/
    │   │   └── Visits.kt        saved data: the keys, in one place
    │   ├── events/
    │   │   ├── MiningEvents.kt
    │   │   ├── CombatEvents.kt
    │   │   └── WelcomeEvents.kt
    │   ├── ui/
    │   │   └── ShopForm.kt       forms: what the player sees
    │   └── commands/
    │       ├── RubyCommand.kt
    │       └── ShopCommand.kt
    └── resources/
        ├── crafting.mod.json
        └── assets/rubies/
            ├── sounds/ruby/chime.ogg
            └── textures/
                ├── item/ruby.png
                ├── entity/ruby_golem.png
                └── particle/ruby_spark_0.png, ruby_spark_1.png

src/main/kotlin/com/example/rubies/

Klasa wejściowa sama nic nie robi: tworzy to, co wspólne, i wywołuje każdą część.

src/main/kotlin/com/example/rubies/RubiesMod.ktKotlin
package com.example.rubies

class RubiesMod : CraftingMod {
    override fun onLoad(ctx: ModContext) {
        val config = Crafting.configs.load(RubiesConfig())
        val content = ContentBuilder(ctx.metadata.id, Crafting.registries)

        Items.register(content)
        Blocks.register(content)
        Creatures.register(content)
        Sounds.register(content)
        Particles.register(content)
        Recipes.register(content)

        MiningEvents.register(config)
        CombatEvents.register(config)
        WelcomeEvents.register()
        RubyCommand.register()
        ShopCommand.register()
    }
}

Wiersze import każdego pliku pominięto dla czytelności.

  • Pakiety według tematu: content, events, commands. Jeden object na plik z funkcją register.
  • Klasa moda tylko łączy części: tworzy to, co współdzieli kilka części (konfigurację, ContentBuilder), i przekazuje im to. Żadna część niczego nie szuka sama, więc każdą zrozumiesz, czytając tylko jej plik.
  • Id jako stałe w obiekcie, który je rejestruje (Items.RUBY). Literówka staje się błędem kompilacji.
  • Zapisane dane w storage/: każdy klucz w obiekcie z funkcjami takimi jak Visits.of(player) i Visits.add(player). Ustawienia trafiają do klasy Config, która jest przekazywana tym, którzy jej potrzebują.

W Javie

Struktura jest taka sama, z klasami zamiast object. Z API korzystasz przez Crafting.INSTANCE, a nasłuchiwacze zwracają Unit.INSTANCE:

src/main/java/com/example/rubies/RubiesMod.javaJava
package com.example.rubies;

public final class RubiesMod implements CraftingMod {
    @Override
    public void onLoad(ModContext ctx) {
        ContentBuilder content = new ContentBuilder(ctx.getMetadata().getId(), Crafting.INSTANCE.getRegistries());
        Items.register(content);
        WelcomeEvents.register();
    }
}
src/main/java/com/example/rubies/events/WelcomeEvents.javaJava
package com.example.rubies.events;

public final class WelcomeEvents {
    public static void register() {
        Crafting.INSTANCE.getEvents().subscribe(PlayerJoinEvent.class, EventPriority.NORMAL, false, null, e -> {
            e.getPlayer().send("&aWelcome, " + e.getPlayer().getName() + "!");
            return Unit.INSTANCE;
        });
    }
}

Kompletny przykład: Frostlands

Frostlands to jeden mod w CraftScript podzielony na części, który korzysta z prawie wszystkiego, co opisuje ten poradnik. Jego main.craft tylko wymienia części:

mods/frostlands/main.craftCraftScript
mod "frostlands" { name: "Frostlands", version: "1.0.0" }

// One file per part of the mod. Each part only declares what it is about.
import "content/materials"   // frost crystals: their ore, the recipes made from them
import "content/gear"        // armor, blade, wand, the frozen effect and the frost bolt
import "content/blocks"      // the gate, a lamp, an ice chest, a frost pad
import "world/frostlands"    // the frozen dimension
import "creatures/drake"     // tame, ride and fly a frost drake
import "creatures/wisp"      // wisps that shoot frost bolts at the undead
import "creatures/yak"       // yaks to breed
import "controls/keys"       // dash and roar keys
import "events/welcome"      // a first greeting
import "commands/kit"        // /frostkit for trying it all out
  • content/materials: kryształ, jego ruda i receptury, które go używają.
  • content/gear: pancerz, mroźne ostrze, różdżka strzelająca pociskiem moda z czasem odnowienia oraz efekt moda.
  • content/blocks: portal, lampa zapalana kliknięciem lub redstone, lodowa skrzynia i płyta do skakania.
  • world/frostlands: wymiar.
  • creatures/drake: stworzenie z własnym modelem i płynnymi animacjami; można je oswoić, ujeżdżać i latać na nim, a na klawisz ryczy.
  • creatures/wisp: błędne ogniki strzelające do nieumarłych.
  • creatures/yak: jaki do hodowli.
  • controls/keys: klawisze zrywu i ryku.
  • events/welcome: pierwsze powitanie.
  • commands/kit: komenda do wypróbowania wszystkiego.

Plik stworzenia deklaruje tylko to jedno stworzenie. Oto drake:

mods/frostlands/creatures/drake.craftCraftScript
creature "drake" {
    body: "minecraft:bee"
    model: "entity/drake"
    texture: "entity/drake"
    size: [1.4, 1.1]
    health: 30
    speed: 0.4
    tame_item: "frostlands:frost_crystal"
    rideable: true
    behaviour: [{ follow_owner: 1.4, distance: 10 }, { fly_around: 1 }, { look_at_players: 8 }, "look_around"]
    spawns: [{ biomes: ["minecraft:ice_spikes"], weight: 3, group: [1, 1] }]
}

Zobacz Stworzenia, aby poznać każdą z tych właściwości.

Kiedy dzielić

Sygnał Co zrobić
Plik przekracza około 150 wierszy Znajdź tematy, które miesza, i rozdziel je.
Kopiujesz tę samą funkcję do dwóch plików Przenieś ją do lib/ i zaimportuj.
Liczba balansu pojawia się w kilku miejscach Ustawienie w settings.craft (lub w klasie Config w Kotlinie) albo stała, jeśli nie powinna być zmienialna.
Ten sam klucz danych piszesz w kilku plikach Przenieś go do lib/storage.craft z dwiema funkcjami do odczytu i zapisu.
Zdarzenie robi trzy różne rzeczy Trzy nasłuchiwacze tego samego zdarzenia, każdy w pliku swojego tematu.