Mga gabay

Pag-aayos ng mod

Isang beses mo lang isusulat ang isang mod pero aalagaan mo ito nang maraming taon. Ang pinakanakakatulong sa pag-aalaga ay hindi ang wika: kung saan nakalagay ang lahat. Nagmumungkahi ang gabay na ito ng isang istruktura para sa CraftScript at isa para sa Kotlin, at ipinapaliwanag kung bakit.

Apat na patakaran

  1. Isang responsibilidad bawat file. Isang file para sa mga ore, isa pa para sa mga recipe, isa pa para sa bawat command. Kung kailangan mo ang salitang “at” para ilarawan ang isang file, malamang na dalawang file iyon.
  2. Igrupo ayon sa paksa, hindi sa laki. Mga folder gaya ng content/, events/, commands/, ui/ at lib/. Alam ng sinumang magbukas ng mod kung saan titingin nang hindi binabasa ang code.
  3. Ihiwalay ang data sa behaviour. Ang meron (mga item, block, recipe) ay listahan ng mga property. Ang nangyayari (mga event, command) ay code. Kapag pinaghalo mo sila, mapipilitan kang magbasa ng logic para lang magpalit ng isang numero.
  4. Entry point na nagkakabit lang ng mga bahagi. Sinasabi ng main.craft o ng class ng mod kung anong mga bahagi ang meron. Wala silang laman na patakaran ng laro.

Sa ganito, ang pag-rebalance ng mod ay pag-edit ng content/recipes.craft; ang pag-ayos ng command ay pagbukas ng commands/; at hindi nakatago ang bug sa isang event sa gitna ng dalawandaang linya ng mga block.

Sa CraftScript

Inirerekomendang istrukturaText
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/

Idinedeklara lang nito ang mod at inililista ang mga bahagi nito. Sa pagbasa nito, malalaman mo kung ano ang meron at nasaan.

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"

Paano gumagana ang mga import

  • Pinapatakbo nang isang beses ng import "content/items" ang file na iyon. Nagiging bahagi ng mod ang mga item, block, event at command na idinedeklara nito, na para bang nasa main.craft ang mga ito.
  • Nagdadala ng mga partikular na pangalan ang import { give_rubies } from "/lib/rewards". Binabasa mula sa folder ng mod ang path na nagsisimula sa /, kaya pareho itong gumagana mula sa anumang subfolder; kung walang /, relative ito sa file na nag-i-import. Opsyonal ang extension na .craft.
  • Dinadala ng import "lib/rewards" as rewards ang buong module sa ilalim ng isang pangalan (rewards.give_rubies(p, 1)). Kung walang as, ang pangalan ay ang pangalan ng file, kaya nag-o-overwrite sa isa't isa ang dalawang na-import na file na may parehong pangalan: gumamit ng magkaibang pangalan o ng as.
  • Walang path na puwedeng lumabas sa folder ng mod, at nagbibigay ng error ang circular import na nagsasabi kung saan.

Shared na logic

Nasa lib/ ang function na ginagamit ng dalawang file. Kung may sariling state ang logic (isang ekonomiya, isang pila ng quest), gumamit ng class kasama ang mga method nito, nasa lib/ din; tingnan ang CraftScript. Ang variable na isang file lang ang gumagamit ay nananatili sa file na iyon, pero tandaan na nawawala ito kapag nag-restart: anumang kailangang tumagal ay nasa saved data.

Settings at saved data sa sarili nilang file

Dalawang bagay na ginagamit ng natitirang bahagi ng mod pero hindi dapat alam ang loob:

  • Mga setting (settings.craft, sa root): iisang config(…) kasama ang lahat ng default value. Ini-import ng ibang file ang SETTINGS at binabasa ang kailangan nila; walang naglalagay nang direkta ng mga numero ng balance sa code.
  • Naka-save na data (lib/storage.craft): ang mga key at ilang maliliit na function, gaya ng visits(p) at add_visit(p). Tinatawag ng code na nagbibilang ng mga visit ang add_visit(player) at hindi nito alam na may key na rubies.visits. Kung magbago ito balang araw, sa iisang lugar mo lang ito babaguhin.

Huwag pangalanang config.craft ang file na iyon: kapag na-import nang walang as, matatabunan ng pangalan nito ang function na config.

Sa Kotlin

Inirerekomendang istrukturaText
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/

Walang ginagawa nang mag-isa ang entry class: ginagawa nito ang mga shared at tinatawag ang bawat bahagi.

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

Inalis ang mga linyang import ng bawat file para mas madaling basahin.

  • Mga package ayon sa paksa: content, events, commands. Isang object bawat file na may function na register.
  • Nagkakabit lang ng mga bahagi ang class ng mod: ginagawa nito ang shared ng ilang bahagi (ang configuration, ang ContentBuilder) at ipinapasa ito sa kanila. Walang bahaging kumukuha ng anuman nang mag-isa, kaya maiintindihan mo ang bawat isa sa pagbasa lang ng file nito.
  • Mga id bilang constant sa object na nagre-register sa kanila (Items.RUBY). Nagiging compile error ang typo.
  • Saved data sa storage/: bawat key sa isang object na may mga function gaya ng Visits.of(player) at Visits.add(player). Nasa class na Config ang settings, na ipinapasa sa sinumang nangangailangan.

Sa Java

Pareho ang istruktura, pero class sa halip na object. Ginagamit mo ang API sa pamamagitan ng Crafting.INSTANCE, at nagre-return ng Unit.INSTANCE ang mga listener:

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

Isang kumpletong halimbawa: Frostlands

Ang Frostlands ay iisang CraftScript mod na nakaayos ayon sa bahagi, at ginagamit nito ang karamihan sa inilalarawan ng gabay na ito. Ang main.craft nito ay naglilista lang ng mga bahagi:

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: isang kristal, ang ore nito at ang mga resipe mula rito.
  • content/gear: armor, isang talim na nagyeyelo, isang wand na bumabaril ng projectile ng mod na may cooldown, at isang effect ng mod.
  • content/blocks: isang portal gate, isang lampara na sinisindihan sa click o ng redstone, isang ice chest at isang jump pad.
  • world/frostlands: isang dimensyon.
  • creatures/drake: isang nilalang na may sariling model at makinis na mga animation; maaari mo itong paamuin, sakyan at palipanin, at umuungol ito sa isang key.
  • creatures/wisp: mga wisp na bumabaril sa mga undead.
  • creatures/yak: mga yak na nagpaparami.
  • controls/keys: ang mga key ng dash at ungol.
  • events/welcome: isang unang pagbati.
  • commands/kit: isang command para subukan ang lahat.

Ang file ng nilalang ay nagdedeklara lang ng nilalang na iyon. Ito ang 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] }]
}

Tingnan ang Mga nilalang para sa bawat isa sa mga property na iyon.

Kailan maghahati

Senyales Ano ang gagawin
Lumampas sa mga 150 linya ang isang file Hanapin ang mga paksang pinaghahalo nito at paghiwalayin ang mga ito.
Kinokopya mo ang parehong function sa dalawang file Ilipat ito sa lib/ at i-import.
Lumalabas sa ilang lugar ang isang numero ng balance Isang setting sa settings.craft (o sa class na Config sa Kotlin), o constant kung hindi ito dapat mababago.
Isinusulat mo ang parehong data key sa ilang file Ilipat ito sa lib/storage.craft kasama ang dalawang function para magbasa at mag-save.
Tatlong magkakaibang bagay ang ginagawa ng isang event Tatlong listener para sa iisang event, bawat isa sa file ng paksa nito.