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
- 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.
- Grupuj według tematu, nie rozmiaru. Foldery takie jak
content/,events/,commands/,ui/ilib/. Każdy, kto otworzy mod, wie, gdzie szukać, bez czytania kodu. - 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.
- Punkt wejścia, który tylko łączy części.
main.craftlub 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
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.pngmods/rubies/
Tylko deklaruje mod i wymienia jego części. Czytając go, wiesz, co jest i gdzie.
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 wmain.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.craftjest opcjonalne.import "lib/rewards" as rewardssprowadza cały moduł pod jedną nazwą (rewards.give_rubies(p, 1)). Bezasnazwą jest nazwa pliku, więc dwa importowane pliki o tej samej nazwie nadpisują się nawzajem: użyj różnych nazw alboas.- Ż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): jednoconfig(…)ze wszystkimi wartościami domyślnymi. Inne pliki importująSETTINGSi czytają, czego potrzebują; żaden nie ma wpisanych na sztywno liczb balansu. - Zapisane dane (
lib/storage.craft): klucze i kilka małych funkcji, takich jakvisits(p)iadd_visit(p). Kod, który liczy wizyty, wywołujeadd_visit(player)i nie wie, że istnieje kluczrubies.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
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.pngsrc/main/kotlin/com/example/rubies/
Klasa wejściowa sama nic nie robi: tworzy to, co wspólne, i wywołuje każdą część.
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. Jedenobjectna 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 jakVisits.of(player)iVisits.add(player). Ustawienia trafiają do klasyConfig, 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:
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();
}
}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:
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 outcontent/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:
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. |