API
Configuración y red
Ajustes que el jugador o el dueño del servidor pueden cambiar sin tocar el código, y datos propios entre el cliente y el servidor. Las dos funcionan desde CraftScript y desde Kotlin.
Configuración en CraftScript
Un archivo propio, settings.craft, declara los ajustes con sus valores por defecto; el resto del mod los 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 primera vez y devuelve los valores actuales. El tipo de cada ajuste sale de su valor por defecto: un número entero sigue siendo entero.config("settings"), sin valores, vuelve a leerlos después de declararlos.- Un tercer argumento
truehace que el servidor envíe sus valores a los clientes al conectarse. - El nombre usa minúsculas, dígitos,
_o-. Un mod puede tener varios archivos de ajustes.
Configuración en Kotlin
Una clase por mod con los valores ajustables, su valor por defecto y una explicación:
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) // each part gets the config it needs
}Crafting crea config/rubies.toml la primera vez:
# 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- Tipos:
int,long,double,bool,string,stringList. - Los comentarios se regeneran; los valores que escribió el usuario se conservan.
- Un valor inválido no rompe nada: Crafting avisa en el log y usa el valor por defecto.
Crafting.configs.reload(config)vuelve a leer el archivo;save(config)lo escribe.- Con
synced = true, el servidor envía sus valores a los clientes que tienen Crafting al conectarse, para que todos jueguen con las mismas reglas.
Para archivos propios, ctx.configDir (config/<mod>/) y ctx.dataDir (data/<mod>/) se crean cuando los pides.
Quién puede entrar a un servidor
Al conectarse, cliente y servidor comparan sus mods. Las reglas:
| Situación | Resultado |
|---|---|
| El servidor solo tiene plugins o mods de servidor; el cliente no tiene mods | Entra |
| El servidor tiene mods que añaden contenido; el cliente no tiene mods | No entra, y ve la lista exacta de mods que necesita |
| Al cliente le falta un mod que añade contenido | No entra: “Missing mods: rubies 1.2.0” |
| Versión mayor distinta (1.x y 2.x) | No entra, con las dos versiones |
| Versión menor distinta (1.2 y 1.3) | Entra, con un aviso en el log del servidor |
| Mods solo de cliente | Siempre permitidos |
Por eso conviene versionar el mod con cuidado: sube la versión mayor solo cuando cliente y servidor de versiones distintas ya no puedan jugar juntos.
Canales en CraftScript
Un canal lleva datos entre cliente y servidor: números, textos, booleanos, listas y mapas. El nombre "ping" es rubies:ping, así que no choca con otros mods.
// Every side listens to what it receives: a packet only reaches a side that listens.
// Server: say hello to each player that joins.
on join {
send(player, "ping", { n: 1 })
}
// Client: answer.
listen("ping", fun(data, from) {
send_to_server("pong", { n: data.n + 1 })
})
// Server: read the answer (from is the player who sent it).
listen("pong", fun(data, from) {
log("{from?.name} answered {data.n}")
})listen(canal, fun(data, player) { … })recibe;playeres quien lo envió en el servidor, ynullen el cliente.send(player, canal, data)envía del servidor a un jugador;send_to_server(canal, data), del cliente al servidor.- Un paquete solo llega a un lado que escucha ese canal: cada lado anuncia sus canales al conectarse. Por eso el mod llama a
listenen los dos lados para lo que cada uno recibe. - Al recargar o desactivar el mod, sus
listendesaparecen con él.
Canales en Kotlin
Un canal envía valores de un tipo entre cliente y servidor. Funciona igual en un mundo de un jugador que en un servidor.
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: who sent it, on the server; null on the client
}
}
fun announce(text: String) = channel.broadcast(text) // server → every client
fun tell(player: Player, text: String) = channel.sendTo(player, text)
}- Los datos se codifican en binario compacto con
PacketWriteryPacketReader. - Crafting nunca envía sus paquetes a un cliente sin Crafting: lo desconectaría.
sendTono envía nada a un jugador que no anunció ese canal;Crafting.network.isChannelAvailable(player, id)lo dice de antemano. - Un paquete mal formado se registra y se descarta; nunca tumba el servidor.
Tipos propios
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)
}