API

Config at network

Settings na puwedeng baguhin ng player o ng may-ari ng server nang hindi ginagalaw ang code, at sarili mong data sa pagitan ng client at server. Gumagana ang dalawa mula sa CraftScript at mula sa Kotlin.

Config sa CraftScript

Isang sariling file, settings.craft, ang nagdedeklara ng settings kasama ang mga default value nito; ini-import ito ng natitirang bahagi ng mod:

settings.craftCraftScript
// Values the server owner can change in config/rubies/settings.toml
const SETTINGS = config("settings", {
    bonus_chance: 0.25,
    welcome_rubies: 3
})
events/mining.craftCraftScript
import { give_rubies } from "/lib/rewards"
import { SETTINGS } from "/settings"

on break(e) {
    let p = e.player
    if p == null { return }
    if e.block.type == "minecraft:diamond_ore" and chance(SETTINGS["bonus_chance"]) {
        give_rubies(p, 1)
        p.play_sound("rubies:ruby_chime")
        p.world.spawn_particle("rubies:ruby_spark", e.block.location, 12, 0.4, 0.02)
    }
}
  • Ginagawa ng config("settings", { … }) ang config/rubies/settings.toml sa unang pagkakataon at ibinabalik ang kasalukuyang mga value. Galing sa default value nito ang type ng bawat setting: nananatiling integer ang integer.
  • Binabasa muli ng config("settings"), na walang value, ang mga ito pagkatapos maideklara.
  • Kapag true ang ikatlong argument, ipinapadala ng server ang mga value nito sa mga client kapag kumonekta sila.
  • Gumagamit ang pangalan ng maliliit na titik, digit, _ o -. Puwedeng magkaroon ng ilang settings file ang isang mod.

Config sa Kotlin

Isang class bawat mod na may mga value na puwedeng baguhin, ang mga default nito at paliwanag:

RubiesConfig.ktKotlin
package com.example.rubies

class RubiesConfig : Config("rubies", synced = true) {
    var dropChance by double("drop_chance", 0.25, "Chance that diamond ore also drops a ruby")
    var armorReduction by double("armor_reduction", 0.2, "Damage reduction while holding a ruby")
}
RubiesMod.ktKotlin
override fun onLoad(ctx: ModContext) {
    val config = Crafting.configs.load(RubiesConfig())
    MiningEvents.register(config)     // natatanggap ng bawat bahagi ang config na kailangan nito
}

Ginagawa ng Crafting ang config/rubies.toml sa unang pagkakataon:

config/rubies.tomlTOML
# rubies — generated by Crafting. Edit freely; comments are regenerated.

# Chance that diamond ore also drops a ruby
# default: 0.25
drop_chance = 0.25

# Damage reduction while holding a ruby
# default: 0.2
armor_reduction = 0.2
  • Mga type: int, long, double, bool, string, stringList.
  • Ginagawang muli ang mga comment; pinapanatili ang mga value na isinulat ng user.
  • Walang nasisira ang invalid na value: nagbababala ang Crafting sa log at ginagamit ang default value.
  • Binabasa muli ng Crafting.configs.reload(config) ang file; isinusulat ito ng save(config).
  • Kapag synced = true, ipinapadala ng server ang mga value nito sa mga client na may Crafting kapag kumonekta sila, kaya pare-pareho ang patakarang sinusunod ng lahat.

Para sa sarili mong mga file, ginagawa ang ctx.configDir (config/<mod>/) at ctx.dataDir (data/<mod>/) kapag hiningi mo ang mga ito.

Sino ang makakapasok sa server

Sa pagkonekta, pinaghahambing ng client at server ang mga mod nila. Ang mga patakaran:

Sitwasyon Resulta
Plugin o mod na pang-server lang ang nasa server; walang mod ang client Nakakapasok
May mod ang server na nagdadagdag ng content; walang mod ang client Hindi makakapasok, at makikita ang eksaktong listahan ng mga mod na kailangan nito
Kulang ang client ng mod na nagdadagdag ng content Hindi makakapasok: “Missing mods: rubies 1.2.0”
Magkaibang major version (1.x at 2.x) Hindi makakapasok, ipinapakita ang dalawang bersyon
Magkaibang minor version (1.2 at 1.3) Nakakapasok, may warning sa log ng server
Mga mod na pang-client lang Laging pinapayagan

Kaya dapat maingat mong lagyan ng bersyon ang mod mo: itaas lang ang major version kapag hindi na makakapaglaro nang magkasama ang client at server na magkaiba ang bersyon.

Mga channel sa CraftScript

Nagdadala ng data ang channel sa pagitan ng client at server: mga numero, string, boolean, list at map. Nagiging rubies:ping ang pangalang "ping", kaya hindi ito sumasalungat sa ibang mod.

net/ping.craftCraftScript
// Nakikinig ang bawat panig sa tinatanggap nito: umaabot lang ang packet sa panig na nakikinig.

// Server: batiin ang bawat player na pumapasok.
on join {
    send(player, "ping", { n: 1 })
}

// Client: sumagot.
listen("ping", fun(data, from) {
    send_to_server("pong", { n: data.n + 1 })
})

// Server: basahin ang sagot (from ang player na nagpadala nito).
listen("pong", fun(data, from) {
    log("{from?.name} sumagot ng {data.n}")
})
  • Tumatanggap ang listen(channel, fun(data, player) { … }); ang player ay ang nagpadala sa server, at null sa client.
  • Nagpapadala ang send(player, channel, data) mula sa server papunta sa isang player; nagpapadala ang send_to_server(channel, data) mula sa client papunta sa server.
  • Umaabot lang ang packet sa panig na nakikinig sa channel na iyon: ina-anunsyo ng bawat panig ang mga channel nito sa pagkonekta. Kaya tinatawag ng mod ang listen sa dalawang panig para sa anumang tinatanggap ng bawat isa.
  • Kapag na-reload o na-disable ang mod, nawawala rin kasama nito ang mga tawag nitong listen.

Mga channel sa Kotlin

Nagpapadala ang channel ng mga value ng iisang type sa pagitan ng client at server. Pareho itong gumagana sa single-player na mundo at sa server.

net/SparkleChannel.ktKotlin
package com.example.rubies.net

object SparkleChannel {
    private val channel = Crafting.network.channel(Identifier.of("rubies", "sparkle"), PacketCodec.STRING)

    fun register() {
        channel.onReceive { message, ctx ->
            // ctx.player: ang nagpadala nito, sa server; null sa client
        }
    }

    fun announce(text: String) = channel.broadcast(text)         // server → bawat client
    fun tell(player: Player, text: String) = channel.sendTo(player, text)
}
  • Ine-encode ang data sa compact na binary gamit ang PacketWriter at PacketReader.
  • Hindi kailanman nagpapadala ang Crafting ng mga packet nito sa client na walang Crafting: madidiskonekta ito. Walang ipinapadala ang sendTo sa player na hindi nag-anunsyo ng channel na iyon; sinasabi ito nang maaga ng Crafting.network.isChannelAvailable(player, id).
  • Nilo-log at itinatapon ang sirang packet; hindi nito kailanman pinababagsak ang server.

Sariling mga type

net/BountyChannel.ktKotlin
package com.example.rubies.net

data class Bounty(val target: String, val reward: Int)

object BountyChannel {
    private val CODEC = PacketCodec.codec<Bounty>(
        { b, out -> out.string(b.target).varInt(b.reward) },
        { input -> Bounty(input.string(), input.varInt()) },
    )
    val channel = Crafting.network.channel(Identifier.of("rubies", "bounty"), CODEC)
}