API
Налаштування й мережа
Налаштування, які гравець або власник сервера можуть змінити, не чіпаючи код, і твої власні дані між клієнтом і сервером. Обидва працюють і з CraftScript, і з Kotlin.
Налаштування в CraftScript
Окремий файл, settings.craft, оголошує налаштування з їхніми значеннями за замовчуванням; решта мода їх імпортує:
// 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", { … })першого разу створюєconfig/rubies/settings.tomlі повертає поточні значення. Тип кожного налаштування береться зі значення за замовчуванням: ціле число лишається цілим.config("settings")без значень знову читає їх після оголошення.- Третій аргумент
trueзмушує сервер надсилати свої значення клієнтам, коли ті під’єднуються. - Назва складається з малих літер, цифр,
_або-. Мод може мати кілька файлів налаштувань.
Налаштування в Kotlin
Один клас на мод зі значеннями, які можна налаштувати, їхніми значеннями за замовчуванням і описом:
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) // кожна частина отримує потрібні їй налаштування
}Першого разу Crafting створює config/rubies.toml:
# 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- Типи:
int,long,double,bool,string,stringList. - Коментарі генеруються заново; значення, які записав користувач, зберігаються.
- Неправильне значення нічого не ламає: Crafting попереджає в журналі й використовує значення за замовчуванням.
Crafting.configs.reload(config)знову читає файл;save(config)записує його.- З
synced = trueсервер надсилає свої значення клієнтам із Crafting, коли ті під’єднуються, тож усі грають за однаковими правилами.
Для твоїх власних файлів ctx.configDir (config/<mod>/) і ctx.dataDir (data/<mod>/) створюються, коли ти їх запитуєш.
Хто може зайти на сервер
Під час під’єднання клієнт і сервер порівнюють свої моди. Правила:
| Ситуація | Результат |
|---|---|
| На сервері лише плагіни або серверні моди; у клієнта немає модів | Заходить |
| На сервері є моди, що додають вміст; у клієнта немає модів | Не може зайти і бачить точний список потрібних модів |
| Клієнтові бракує мода, що додає вміст | Не може зайти: «Missing mods: rubies 1.2.0» |
| Різна мажорна версія (1.x і 2.x) | Не може зайти, показуються обидві версії |
| Різна мінорна версія (1.2 і 1.3) | Заходить, з попередженням у журналі сервера |
| Моди лише для клієнта | Завжди дозволено |
Тому версіонуй мод уважно: підвищуй мажорну версію, лише коли клієнт і сервер на різних версіях більше не можуть грати разом.
Канали в CraftScript
Канал переносить дані між клієнтом і сервером: числа, рядки, логічні значення, списки й мапи. Назва "ping" перетворюється на rubies:ping, тож не конфліктує з іншими модами.
// Кожна сторона слухає те, що отримує: пакет доходить лише до сторони, яка слухає.
// Сервер: привітатися з кожним гравцем, який заходить.
on join {
send(player, "ping", { n: 1 })
}
// Клієнт: відповісти.
listen("ping", fun(data, from) {
send_to_server("pong", { n: data.n + 1 })
})
// Сервер: прочитати відповідь (from — гравець, який її надіслав).
listen("pong", fun(data, from) {
log("{from?.name} відповідає {data.n}")
})listen(channel, fun(data, player) { … })отримує;player— відправник на сервері йnullна клієнті.send(player, channel, data)надсилає із сервера гравцеві;send_to_server(channel, data)надсилає з клієнта на сервер.- Пакет доходить лише до сторони, що слухає цей канал: кожна сторона оголошує свої канали під час під’єднання. Тому мод викликає
listenна обох сторонах для того, що кожна з них отримує. - Коли мод перезавантажують або вимикають, його виклики
listenзникають разом із ним.
Канали в Kotlin
Канал надсилає значення одного типу між клієнтом і сервером. В одиночному світі він працює так само, як на сервері.
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: хто надіслав, на сервері; null на клієнті
}
}
fun announce(text: String) = channel.broadcast(text) // сервер → усі клієнти
fun tell(player: Player, text: String) = channel.sendTo(player, text)
}- Дані кодуються в компактний двійковий формат за допомогою
PacketWriterіPacketReader. - Crafting ніколи не надсилає свої пакети клієнтові без Crafting: це від’єднало б його.
sendToнічого не надсилає гравцеві, який не оголосив цей канал;Crafting.network.isChannelAvailable(player, id)скаже це заздалегідь. - Пошкоджений пакет записується в журнал і відкидається; він ніколи не обвалює сервер.
Власні типи
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)
}