API
Config at network
Settings na puwedeng baguhin ng player o ng may-ari ng server nang hindi ginagalaw ang code, at sarili mong data sa pagitan ng client at server. Gumagana ang dalawa mula sa CraftScript at mula sa Kotlin.
Config sa CraftScript
Isang sariling file, settings.craft, ang nagdedeklara ng settings kasama ang mga default value nito; ini-import ito ng natitirang bahagi ng mod:
// 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)
}
}- Ginagawa ng
config("settings", { … })angconfig/rubies/settings.tomlsa unang pagkakataon at ibinabalik ang kasalukuyang mga value. Galing sa default value nito ang type ng bawat setting: nananatiling integer ang integer. - Binabasa muli ng
config("settings"), na walang value, ang mga ito pagkatapos maideklara. - Kapag
trueang ikatlong argument, ipinapadala ng server ang mga value nito sa mga client kapag kumonekta sila. - Gumagamit ang pangalan ng maliliit na titik, digit,
_o-. Puwedeng magkaroon ng ilang settings file ang isang mod.
Config sa Kotlin
Isang class bawat mod na may mga value na puwedeng baguhin, ang mga default nito at paliwanag:
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) // natatanggap ng bawat bahagi ang config na kailangan nito
}Ginagawa ng Crafting ang config/rubies.toml sa unang pagkakataon:
# 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- Mga type:
int,long,double,bool,string,stringList. - Ginagawang muli ang mga comment; pinapanatili ang mga value na isinulat ng user.
- Walang nasisira ang invalid na value: nagbababala ang Crafting sa log at ginagamit ang default value.
- Binabasa muli ng
Crafting.configs.reload(config)ang file; isinusulat ito ngsave(config). - Kapag
synced = true, ipinapadala ng server ang mga value nito sa mga client na may Crafting kapag kumonekta sila, kaya pare-pareho ang patakarang sinusunod ng lahat.
Para sa sarili mong mga file, ginagawa ang ctx.configDir (config/<mod>/) at ctx.dataDir (data/<mod>/) kapag hiningi mo ang mga ito.
Sino ang makakapasok sa server
Sa pagkonekta, pinaghahambing ng client at server ang mga mod nila. Ang mga patakaran:
| Sitwasyon | Resulta |
|---|---|
| Plugin o mod na pang-server lang ang nasa server; walang mod ang client | Nakakapasok |
| May mod ang server na nagdadagdag ng content; walang mod ang client | Hindi makakapasok, at makikita ang eksaktong listahan ng mga mod na kailangan nito |
| Kulang ang client ng mod na nagdadagdag ng content | Hindi makakapasok: “Missing mods: rubies 1.2.0” |
| Magkaibang major version (1.x at 2.x) | Hindi makakapasok, ipinapakita ang dalawang bersyon |
| Magkaibang minor version (1.2 at 1.3) | Nakakapasok, may warning sa log ng server |
| Mga mod na pang-client lang | Laging pinapayagan |
Kaya dapat maingat mong lagyan ng bersyon ang mod mo: itaas lang ang major version kapag hindi na makakapaglaro nang magkasama ang client at server na magkaiba ang bersyon.
Mga channel sa CraftScript
Nagdadala ng data ang channel sa pagitan ng client at server: mga numero, string, boolean, list at map. Nagiging rubies:ping ang pangalang "ping", kaya hindi ito sumasalungat sa ibang mod.
// Nakikinig ang bawat panig sa tinatanggap nito: umaabot lang ang packet sa panig na nakikinig.
// Server: batiin ang bawat player na pumapasok.
on join {
send(player, "ping", { n: 1 })
}
// Client: sumagot.
listen("ping", fun(data, from) {
send_to_server("pong", { n: data.n + 1 })
})
// Server: basahin ang sagot (from ang player na nagpadala nito).
listen("pong", fun(data, from) {
log("{from?.name} sumagot ng {data.n}")
})- Tumatanggap ang
listen(channel, fun(data, player) { … }); angplayeray ang nagpadala sa server, atnullsa client. - Nagpapadala ang
send(player, channel, data)mula sa server papunta sa isang player; nagpapadala angsend_to_server(channel, data)mula sa client papunta sa server. - Umaabot lang ang packet sa panig na nakikinig sa channel na iyon: ina-anunsyo ng bawat panig ang mga channel nito sa pagkonekta. Kaya tinatawag ng mod ang
listensa dalawang panig para sa anumang tinatanggap ng bawat isa. - Kapag na-reload o na-disable ang mod, nawawala rin kasama nito ang mga tawag nitong
listen.
Mga channel sa Kotlin
Nagpapadala ang channel ng mga value ng iisang type sa pagitan ng client at server. Pareho itong gumagana sa single-player na mundo at sa 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: ang nagpadala nito, sa server; null sa client
}
}
fun announce(text: String) = channel.broadcast(text) // server → bawat client
fun tell(player: Player, text: String) = channel.sendTo(player, text)
}- Ine-encode ang data sa compact na binary gamit ang
PacketWriteratPacketReader. - Hindi kailanman nagpapadala ang Crafting ng mga packet nito sa client na walang Crafting: madidiskonekta ito. Walang ipinapadala ang
sendTosa player na hindi nag-anunsyo ng channel na iyon; sinasabi ito nang maaga ngCrafting.network.isChannelAvailable(player, id). - Nilo-log at itinatapon ang sirang packet; hindi nito kailanman pinababagsak ang server.
Sariling mga type
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)
}