API

Configurazione e rete

Impostazioni che il giocatore o il proprietario del server possono cambiare senza toccare il codice, e dati tuoi tra client e server. Entrambe le cose funzionano da CraftScript e da Kotlin.

Configurazione in CraftScript

Un file a sé, settings.craft, dichiara le impostazioni con i loro valori predefiniti; il resto della mod le importa:

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)
    }
}
  • config("settings", { … }) crea config/rubies/settings.toml la prima volta e restituisce i valori attuali. Il tipo di ogni impostazione deriva dal suo valore predefinito: un intero resta un intero.
  • config("settings"), senza valori, li rilegge dopo che sono stati dichiarati.
  • Un terzo argomento true fa sì che il server invii i suoi valori ai client quando si connettono.
  • Il nome usa lettere minuscole, cifre, _ o -. Una mod può avere più file di impostazioni.

Configurazione in Kotlin

Una classe per mod con i valori regolabili, i loro predefiniti e una descrizione:

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)     // ogni parte riceve la configurazione che le serve
}

Crafting crea config/rubies.toml la prima volta:

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
  • Tipi: int, long, double, bool, string, stringList.
  • I commenti vengono rigenerati; i valori scritti dall’utente vengono mantenuti.
  • Un valore non valido non rompe nulla: Crafting avvisa nel log e usa il valore predefinito.
  • Crafting.configs.reload(config) rilegge il file; save(config) lo scrive.
  • Con synced = true, il server invia i suoi valori ai client che hanno Crafting quando si connettono, così tutti giocano con le stesse regole.

Per i tuoi file, ctx.configDir (config/<mod>/) e ctx.dataDir (data/<mod>/) vengono creati quando li richiedi.

Chi può entrare in un server

Alla connessione, client e server confrontano le loro mod. Le regole:

Situazione Risultato
Il server ha solo plugin o mod lato server; il client non ha mod Entra
Il server ha mod che aggiungono contenuti; il client non ha mod Non può entrare, e vede l’elenco esatto delle mod che gli servono
Al client manca una mod che aggiunge contenuti Non può entrare: “Missing mods: rubies 1.2.0”
Versione principale diversa (1.x e 2.x) Non può entrare, con entrambe le versioni mostrate
Versione secondaria diversa (1.2 e 1.3) Entra, con un avviso nel log del server
Mod solo client Sempre consentite

Per questo devi assegnare le versioni della tua mod con attenzione: aumenta la versione principale solo quando un client e un server con versioni diverse non possono più giocare insieme.

Canali in CraftScript

Un canale trasporta dati tra client e server: numeri, stringhe, booleani, liste e mappe. Il nome "ping" diventa rubies:ping, così non entra in conflitto con altre mod.

net/ping.craftCraftScript
// Ogni lato ascolta ciò che riceve: un pacchetto raggiunge solo un lato che ascolta.

// Server: saluta ogni giocatore che entra.
on join {
    send(player, "ping", { n: 1 })
}

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

// Server: leggi la risposta (from è il giocatore che l’ha inviata).
listen("pong", fun(data, from) {
    log("{from?.name} ha risposto {data.n}")
})
  • listen(channel, fun(data, player) { … }) riceve; player è il mittente sul server, e null sul client.
  • send(player, channel, data) invia dal server a un giocatore; send_to_server(channel, data) invia dal client al server.
  • Un pacchetto raggiunge solo un lato che ascolta su quel canale: ogni lato annuncia i suoi canali alla connessione. Per questo la mod chiama listen su entrambi i lati per ciò che ciascuno riceve.
  • Quando la mod viene ricaricata o disattivata, le sue chiamate a listen spariscono con lei.

Canali in Kotlin

Un canale invia valori di un tipo tra client e server. Funziona allo stesso modo in un mondo in giocatore singolo e su un 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: chi l’ha inviato, sul server; null sul client
        }
    }

    fun announce(text: String) = channel.broadcast(text)         // server → tutti i client
    fun tell(player: Player, text: String) = channel.sendTo(player, text)
}
  • I dati vengono codificati in binario compatto con PacketWriter e PacketReader.
  • Crafting non invia mai i suoi pacchetti a un client senza Crafting: lo disconnetterebbe. sendTo non invia nulla a un giocatore che non ha annunciato quel canale; Crafting.network.isChannelAvailable(player, id) te lo dice in anticipo.
  • Un pacchetto malformato viene registrato nel log e scartato; non fa mai cadere il server.

Tipi personalizzati

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