Skip to content

Local Addon Guide

babyaba edited this page Jul 6, 2026 · 4 revisions
# 🛠️ Создание локального аддона и расширение DSL скриптов

В этом руководстве мы разберем, как создать собственный аддон (на примере модуля `ExampleAddon`) прямо внутри вашего проекта, зарегистрировать его в `KSLAddonManager` и добавить новые методы в `.kts` скрипты через `KSLContextExtension`[cite: 3, 4].

---

## 📂 1. Структура модуля аддона

Если вы разрабатываете аддон в рамках мультимодульного Gradle-проекта, его структура должна выглядеть следующим образом:

```text
ВашПроект/
  ├── examples/
  │   └── ExampleAddon/
  │       ├── build.gradle.kts
  │       └── src/main/kotlin/ru/example/
  │           ├── ExampleAddonPlugin.kt   # Буккит-плагин аддона
  │           ├── CoinService.kt          # Логика вашего сервиса
  │           └── CoinContextExtension.kt # Расширение контекста скриптов
  ├── build.gradle.kts
  └── settings.gradle.kts

🏛️ 2. Шаг 1: Реализация интерфейса KSLAddon

Для начала создадим основной класс аддона, который будет отвечать за его регистрацию и инициализацию в ядре KSL.

package ru.example

import ru.privateserver.ksl.KSLAddon
import ru.privateserver.ksl.KSLAPI

class CoinAddon(private val plugin: ExampleAddonPlugin) : KSLAddon {
    override val addonId: String = "coin-addon"
    override val addonVersion: String = "1.0.0"
    override val addonDescription: String = "Аддон для интеграции кастомной валюты в KSL скрипты"

    override fun onLoad(api: KSLAPI) {
        // 1. Регистрируем сервис, чтобы к нему можно было получить доступ по ключу[cite: 3, 4]
        val coinService = CoinService()
        api.registerService("coin_service", coinService)[cite: 3, 4]

        // 2. Добавляем автоматический импорт классов нашего аддона в скрипты[cite: 3, 4]
        api.addDefaultImports("ru.example.*")[cite: 3, 4]

        // 3. Регистрируем расширение контекста для внедрения новых методов в DSL[cite: 3, 4]
        api.registerContextExtension(CoinContextExtension(coinService))[cite: 3, 4]
    }

    override fun onUnload() {
        // Вызывается при выгрузке аддона (удаляем зависимости)[cite: 2, 3]
        // KSLAddonManager автоматически очистит зависимости, привязанные к этому ID[cite: 3]
    }
}

🔗 3. Шаг 2: Расширение контекста скриптов (KSLContextExtension)

Чтобы в .kts скриптах появились новые методы (например, player.getCoins() или addCoins()), нам нужно использовать KSLContextExtension. Это позволяет выполнять логику в момент создания контекста для каждого скрипта.

package ru.example

import ru.privateserver.ksl.KSLContextExtension
import ru.privateserver.ksl.BukkitScriptContext
import org.bukkit.entity.Player

class CoinContextExtension(private val coinService: CoinService) : KSLContextExtension {
    override val extensionId: String = "coin-context-ext"

    override fun onContextCreated(context: BukkitScriptContext) {
        // Данный код выполняется при компиляции/инициализации скрипта[cite: 3]
        // Здесь мы можем логировать интеграцию или готовить локальные структуры данных для скрипта
    }

    override fun onContextDestroyed(scriptName: String) {
        // Очищаем кэш или данные конкретного скрипта при его перезагрузке (`/ksl reload`)[cite: 3]
    }
}

/**
 * Расширения (Extension Functions) для DSL скриптов.
 * Благодаря автоматическому импорту "ru.example.*", эти методы будут 
 * доступны внутри любого .kts файла без ручных импортов!
 */
fun Player.getCoins(): Int {
    // Пример обращения к синглтону KSL для получения сервиса на лету
    val service = ru.privateserver.ksl.KSL.api.getService("coin_service") as? CoinService[cite: 1, 3]
    return service?.getBalance(this) ?: 0
}

fun Player.addCoins(amount: Int) {
    val service = ru.privateserver.ksl.KSL.api.getService("coin_service") as? CoinService[cite: 1, 3]
    service?.addBalance(this, amount)
}

🚀 4. Шаг 3: Регистрация аддона при старте плагина

В главном классе вашего Bukkit-плагина аддона необходимо дождаться инициализации KSL и передать наш аддон в менеджер.

package ru.example

import org.bukkit.plugin.java.JavaPlugin
import ru.privateserver.ksl.KSL

class ExampleAddonPlugin : JavaPlugin() {

    override fun onEnable() {
        // Проверяем, запущен ли загрузчик скриптов на сервере[cite: 1]
        if (!KSL.isAvailable) {[cite: 1]
            logger.severe("KotlinScriptLoader не найден! Аддон отключен.")
            server.pluginManager.disablePlugin(this)
            return
        }

        // Регистрируем наш локальный аддон в системе[cite: 3, 4]
        val addon = CoinAddon(this)
        KSL.api.registerAddon(addon)[cite: 1, 3]
    }

    override fun onDisable() {
        if (KSL.isAvailable) {[cite: 1]
            // Безопасно удаляем аддон при выключении сервера или плагина[cite: 3, 4]
            KSL.api.unregisterAddon("coin-addon")[cite: 1, 3]
        }
    }
}

📜 5. Как это выглядит для создателя скриптов в .kts

После того как ваш локальный аддон загружен:

  1. В рантайм автоматически добавился импорт пакета ru.example.*.

  2. Расширения для класса Player стали глобальными.

Теперь любой администратор или контент-мейкер может написать в своем .kts файле следующий лаконичный код:

// Пример скрипта custom_eco.kts
onEvent<PlayerJoinEvent> { event ->
    val player = event.player
    
    // Метод .getCoins() доступен автоматически благодаря нашему аддону!
    val coins = player.getCoins() 
    
    player.sendMessage("§6[Баланс] У вас на счету: $coins монет.")
}

registerCommand("givecoins", listOf("gcoins")) { player, args ->
    if (!player.isOp) return@registerCommand
    
    player.addCoins(100)
    player.sendMessage("§aВы выдали себе 100 локальных монет!")
}

🛡️ Важные детали реализации под капотом

  • Изоляция сбоев (Fail-Safety): KSLAddonManager запускает метод onLoad вашего аддона внутри блока runCatching. Если в вашем аддоне произойдет ошибка (например, не подключилась внешняя БД), KSL отловит её, выведет красивый severe-лог в консоль сервера, но продолжит работать и загружать остальные скрипты и аддоны.

  • Потоковая безопасность: Все внутренние хранилища аддонов, расширений контекста и сервисов работают на базе ConcurrentHashMap. Вы можете безопасно регистрировать или вызывать сервисы из асинхронных задач Bukkit (runAsync).


---

Отличный, максимально подробный гайд получился! Всё разложено по полочкам: от Gradle-структуры до конечного вида скрипта.

Что пишем дальше? Давай сделаем полноценный **Гайд по написанию самих скриптов (`.kts`)**, где покажем вообще всё встроенное в ядро DSL: базы данных, команды, ивенты, и как раз то, как со всем этим взлетать. Или есть другие идеи?

Clone this wiki locally