API
Configuration et réseau
Des réglages que le joueur ou le propriétaire du serveur peuvent changer sans toucher au code, et tes propres données entre client et serveur. Les deux fonctionnent depuis CraftScript et depuis Kotlin.
Configuration en CraftScript
Un fichier à part, settings.craft, déclare les réglages avec leurs valeurs par défaut ; le reste du mod les importe :
// 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", { … })créeconfig/rubies/settings.tomlla première fois et renvoie les valeurs actuelles. Le type de chaque réglage vient de sa valeur par défaut : un entier reste un entier.config("settings"), sans valeurs, les relit une fois qu’elles sont déclarées.- Un troisième argument
truefait envoyer par le serveur ses valeurs aux clients quand ils se connectent. - Le nom utilise des lettres minuscules, des chiffres,
_ou-. Un mod peut avoir plusieurs fichiers de réglages.
Configuration en Kotlin
Une classe par mod avec les valeurs ajustables, leurs valeurs par défaut et une description :
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) // chaque partie reçoit la config dont elle a besoin
}Crafting crée config/rubies.toml la première fois :
# 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- Types :
int,long,double,bool,string,stringList. - Les commentaires sont régénérés ; les valeurs écrites par l’utilisateur sont conservées.
- Une valeur invalide ne casse rien : Crafting prévient dans le journal et utilise la valeur par défaut.
Crafting.configs.reload(config)relit le fichier ;save(config)l’écrit.- Avec
synced = true, le serveur envoie ses valeurs aux clients qui ont Crafting quand ils se connectent, pour que tout le monde joue avec les mêmes règles.
Pour tes propres fichiers, ctx.configDir (config/<mod>/) et ctx.dataDir (data/<mod>/) sont créés quand tu les demandes.
Qui peut rejoindre un serveur
À la connexion, le client et le serveur comparent leurs mods. Les règles :
| Situation | Résultat |
|---|---|
| Le serveur n’a que des plugins ou des mods côté serveur ; le client n’a pas de mods | Se connecte |
| Le serveur a des mods qui ajoutent du contenu ; le client n’a pas de mods | Ne peut pas se connecter, et voit la liste exacte des mods nécessaires |
| Il manque au client un mod qui ajoute du contenu | Ne peut pas se connecter : « Missing mods: rubies 1.2.0 » |
| Version majeure différente (1.x et 2.x) | Ne peut pas se connecter, avec les deux versions affichées |
| Version mineure différente (1.2 et 1.3) | Se connecte, avec un avertissement dans le journal du serveur |
| Mods uniquement côté client | Toujours autorisés |
C’est pour ça qu’il faut versionner ton mod avec soin : n’augmente la version majeure que lorsqu’un client et un serveur sur des versions différentes ne peuvent plus jouer ensemble.
Canaux en CraftScript
Un canal transporte des données entre client et serveur : nombres, chaînes, booléens, listes et maps. Le nom "ping" devient rubies:ping, il n’entre donc pas en conflit avec d’autres mods.
// Chaque côté écoute ce qu’il reçoit : un paquet n’arrive qu’à un côté qui écoute.
// Serveur : dit bonjour à chaque joueur qui se connecte.
on join {
send(player, "ping", { n: 1 })
}
// Client : répond.
listen("ping", fun(data, from) {
send_to_server("pong", { n: data.n + 1 })
})
// Serveur : lit la réponse (from est le joueur qui l’a envoyée).
listen("pong", fun(data, from) {
log("{from?.name} a répondu {data.n}")
})listen(channel, fun(data, player) { … })reçoit ;playerest l’expéditeur côté serveur, etnullcôté client.send(player, channel, data)envoie du serveur vers un joueur ;send_to_server(channel, data)envoie du client vers le serveur.- Un paquet n’arrive qu’à un côté qui écoute ce canal : chaque côté annonce ses canaux à la connexion. C’est pour ça que le mod appelle
listendes deux côtés pour ce que chacun reçoit. - Quand le mod est rechargé ou désactivé, ses appels à
listendisparaissent avec lui.
Canaux en Kotlin
Un canal envoie des valeurs d’un type donné entre client et serveur. Il fonctionne de la même façon dans un monde solo que sur un serveur.
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 : qui l’a envoyé, côté serveur ; null côté client
}
}
fun announce(text: String) = channel.broadcast(text) // serveur → tous les clients
fun tell(player: Player, text: String) = channel.sendTo(player, text)
}- Les données sont encodées en binaire compact avec
PacketWriteretPacketReader. - Crafting n’envoie jamais ses paquets à un client sans Crafting : cela le déconnecterait.
sendTon’envoie rien à un joueur qui n’a pas annoncé ce canal ;Crafting.network.isChannelAvailable(player, id)te le dit à l’avance. - Un paquet mal formé est consigné et jeté ; il ne fait jamais tomber le serveur.
Types personnalisés
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)
}