API
Configuração e rede
Ajustes que o jogador ou o dono do servidor pode mudar sem mexer no código, e seus próprios dados entre cliente e servidor. Os dois funcionam em CraftScript e em Kotlin.
Configuração em CraftScript
Um arquivo próprio, settings.craft, declara os ajustes com seus valores padrão; o resto do mod os 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", { … })criaconfig/rubies/settings.tomlna primeira vez e retorna os valores atuais. O tipo de cada ajuste vem do valor padrão: um inteiro continua inteiro.config("settings"), sem valores, lê os ajustes de novo depois de declarados.- Um terceiro argumento
truefaz o servidor enviar seus valores aos clientes quando eles se conectam. - O nome usa letras minúsculas, dígitos,
_ou-. Um mod pode ter vários arquivos de ajustes.
Configuração em Kotlin
Uma classe por mod com os valores ajustáveis, seus padrões e uma descrição:
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) // cada parte recebe a configuração de que precisa
}O Crafting cria config/rubies.toml na primeira 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. - Os comentários são regenerados; os valores que o usuário escreveu são mantidos.
- Um valor inválido não quebra nada: o Crafting avisa no log e usa o valor padrão.
Crafting.configs.reload(config)lê o arquivo de novo;save(config)o grava.- Com
synced = true, o servidor envia seus valores aos clientes que têm o Crafting quando eles se conectam, para que todos joguem com as mesmas regras.
Para seus próprios arquivos, ctx.configDir (config/<mod>/) e ctx.dataDir (data/<mod>/) são criados quando você os pede.
Quem pode entrar em um servidor
Ao conectar, cliente e servidor comparam seus mods. As regras:
| Situação | Resultado |
|---|---|
| O servidor só tem plugins ou mods de servidor; o cliente não tem mods | Entra |
| O servidor tem mods que adicionam conteúdo; o cliente não tem mods | Não entra, e vê a lista exata dos mods de que precisa |
| Falta ao cliente um mod que adiciona conteúdo | Não entra: “Missing mods: rubies 1.2.0” |
| Versão principal diferente (1.x e 2.x) | Não entra, com as duas versões exibidas |
| Versão secundária diferente (1.2 e 1.3) | Entra, com um aviso no log do servidor |
| Mods só de cliente | Sempre permitidos |
Por isso, versione seu mod com cuidado: aumente a versão principal só quando um cliente e um servidor em versões diferentes não puderem mais jogar juntos.
Canais em CraftScript
Um canal leva dados entre cliente e servidor: números, strings, booleanos, listas e mapas. O nome "ping" vira rubies:ping, então não colide com outros mods.
// Cada lado escuta o que recebe: um pacote só chega a um lado que escuta.
// Servidor: cumprimenta cada jogador que entra.
on join {
send(player, "ping", { n: 1 })
}
// Cliente: responde.
listen("ping", fun(data, from) {
send_to_server("pong", { n: data.n + 1 })
})
// Servidor: lê a resposta (from é o jogador que a enviou).
listen("pong", fun(data, from) {
log("{from?.name} respondeu {data.n}")
})listen(channel, fun(data, player) { … })recebe;playeré quem enviou, no servidor, enullno cliente.send(player, channel, data)envia do servidor para um jogador;send_to_server(channel, data)envia do cliente para o servidor.- Um pacote só chega a um lado que escuta esse canal: cada lado anuncia seus canais ao conectar. Por isso o mod chama
listennos dois lados para o que cada um recebe. - Quando o mod é recarregado ou desativado, suas chamadas a
listenvão embora com ele.
Canais em Kotlin
Um canal envia valores de um tipo entre cliente e servidor. Funciona igual em um mundo de um jogador e em um 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: quem enviou, no servidor; null no cliente
}
}
fun announce(text: String) = channel.broadcast(text) // servidor → todos os clientes
fun tell(player: Player, text: String) = channel.sendTo(player, text)
}- Os dados são codificados em binário compacto com
PacketWriterePacketReader. - O Crafting nunca envia seus pacotes a um cliente sem Crafting: isso o desconectaria.
sendTonão envia nada a um jogador que não anunciou esse canal;Crafting.network.isChannelAvailable(player, id)avisa você antes. - Um pacote malformado é registrado no log e descartado; ele nunca derruba o servidor.
Tipos personalizados
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)
}