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:
// Values the server owner can change in config/rubies/settings.toml
const SETTINGS = config("settings", {
bonus_chance: 0.25,
welcome_rubies: 3
})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", { … })creaconfig/rubies/settings.tomlla 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
truefa 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:
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")
}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:
# 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.
// 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, enullsul 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
listensu entrambi i lati per ciò che ciascuno riceve. - Quando la mod viene ricaricata o disattivata, le sue chiamate a
listenspariscono 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.
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
PacketWriterePacketReader. - Crafting non invia mai i suoi pacchetti a un client senza Crafting: lo disconnetterebbe.
sendTonon 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
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)
}