Skip to content

Local Addon Guide

babyaba edited this page Jul 7, 2026 · 4 revisions

🧩 Creating a Local KSL Addon

KSL addons allow you to extend the scripting engine with custom services, context extensions, and default imports. This is perfect when you want to expose functionality from your Java/Kotlin plugin to KSL scripts without modifying the core plugin.


🎯 What Can Addons Do?

  • Register Services: Expose objects that scripts can access via service<MyService>("key")
  • Extend Context: Add custom methods/properties to BukkitScriptContext that all scripts can use
  • Add Default Imports: Automatically import packages in every script
  • Hook into Lifecycle: React when scripts are loaded/unloaded

πŸ“¦ Step 1: Setup Your Plugin

Add KSL as a dependency in your build.gradle.kts:

dependencies {
    compileOnly("ru.privateserver:ksl:1.0.0") // Replace with actual version/artifact
}

Or if you're using Maven:

<dependency>
    <groupId>ru.privateserver</groupId>
    <artifactId>ksl</artifactId>
    <version>1.0.0</version>
    <scope>provided</scope>
</dependency>

Important: Mark KSL as softdepend in your plugin.yml:

name: MyAwesomeAddon
version: 1.0.0
main: com.example.MyAwesomeAddon
softdepend: [KotlinScriptLoader]

πŸ› οΈ Step 2: Implement KSLAddon

Create a class that implements the KSLAddon interface:

package com.example

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

class MyAddon : KSLAddon {
    override val addonId = "MyAwesomeAddon"
    override val addonVersion = "1.0.0"
    override val addonDescription = "Adds custom weather service to KSL"

    override fun onLoad(api: KSLAPI) {
        // Register your services here
    }

    override fun onUnload() {
        // Cleanup when addon is unloaded
    }
}

πŸ”Œ Step 3: Register Your Addon

In your main plugin class, register the addon when KSL is available:

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

class MyAwesomeAddon : JavaPlugin() {
    private val addon = MyAddon()

    override fun onEnable() {
        if (KSL.isAvailable) {
            KSL.api.registerAddon(addon)
            logger.info("Registered with KSL!")
        } else {
            logger.warning("KSL not found - addon will not work")
        }
    }

    override fun onDisable() {
        if (KSL.isAvailable) {
            KSL.api.unregisterAddon(addon.addonId)
        }
    }
}

πŸ“¦ Step 4: Register Services

Services are objects that scripts can access using the service<T>(key) function:

class WeatherService {
    fun isRaining(world: String): Boolean {
        // Your logic here
        return false
    }
    
    fun setWeather(world: String, weather: String) {
        // Your logic here
    }
}

class MyAddon : KSLAddon {
    override val addonId = "MyAwesomeAddon"
    override val addonVersion = "1.0.0"
    override val addonDescription = "Weather service for KSL"

    private val weatherService = WeatherService()

    override fun onLoad(api: KSLAPI) {
        // Register the service
        api.registerService("weather", weatherService)
        api.kslPlugin.logger.info("Weather service registered!")
    }

    override fun onUnload() {
        // Service will be automatically unregistered
    }
}

Usage in scripts:

val weather = service<WeatherService>("weather")
if (weather?.isRaining("world") == true) {
    broadcastMM("<blue>It's raining!")
}

πŸ”§ Step 5: Extend Script Context

Want to add custom methods that all scripts can use directly? Implement KSLContextExtension:

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

class MyContextExtension : KSLContextExtension {
    override val extensionId = "MyAddonExtension"

    override fun onContextCreated(context: BukkitScriptContext) {
        // Add extension functions to the context
        context.apply {
            // Add a custom method accessible in all scripts
            fun Player.isVIP(): Boolean {
                return this.hasPermission("myaddon.vip")
            }
            
            fun getAddonData(key: String): String? {
                // Your custom logic
                return "value"
            }
        }
    }

    override fun onContextDestroyed(scriptName: String) {
        // Cleanup when script is unloaded
    }
}

Register it in your addon:

class MyAddon : KSLAddon {
    override val addonId = "MyAwesomeAddon"
    override val addonVersion = "1.0.0"
    override val addonDescription = "Custom context extensions"

    override fun onLoad(api: KSLAPI) {
        val extension = MyContextExtension()
        api.registerContextExtension(extension)
    }

    override fun onUnload() {
        // Extension will be automatically unregistered
    }
}

Usage in scripts:

onEvent<PlayerJoinEvent> {
    if (player.isVIP()) {
        player.sendRichMessage("<gold>Welcome VIP!")
    }
    
    val data = getAddonData("some_key")
    player.sendMessage("Data: $data")
}

πŸ“š Step 6: Add Default Imports

Make certain packages available in all scripts automatically:

override fun onLoad(api: KSLAPI) {
    // Add imports that will be available in all scripts
    api.addDefaultImports(
        "com.example.mypackage.*",
        "com.example.utils.*",
        "kotlinx.coroutines.*"
    )
}

Now scripts can use these classes without explicit imports.


🎨 Complete Example

Here's a full working addon that combines services and context extensions:

package com.example

import org.bukkit.entity.Player
import org.bukkit.plugin.java.JavaPlugin
import ru.privateserver.ksl.*

// Service class
class EconomyService {
    private val balances = mutableMapOf<String, Double>()
    
    fun getBalance(player: Player): Double = 
        balances[player.uniqueId.toString()] ?: 0.0
    
    fun setBalance(player: Player, amount: Double) {
        balances[player.uniqueId.toString()] = amount
    }
    
    fun addMoney(player: Player, amount: Double) {
        val current = getBalance(player)
        setBalance(player, current + amount)
    }
}

// Context extension
class EconomyContextExtension : KSLContextExtension {
    override val extensionId = "EconomyExtension"

    override fun onContextCreated(context: BukkitScriptContext) {
        context.apply {
            // Extension property
            var Player.customBalance: Double
                get() = service<EconomyService>("economy")?.getBalance(this) ?: 0.0
                set(value) {
                    service<EconomyService>("economy")?.setBalance(this, value)
                }
            
            // Extension function
            fun Player.giveMoney(amount: Double) {
                service<EconomyService>("economy")?.addMoney(this, amount)
                sendRichMessage("<green>+$${amount}")
            }
        }
    }

    override fun onContextDestroyed(scriptName: String) {
        // Cleanup if needed
    }
}

// Main addon class
class EconomyAddon : KSLAddon {
    override val addonId = "EconomyAddon"
    override val addonVersion = "1.0.0"
    override val addonDescription = "Custom economy system for KSL scripts"

    private val economyService = EconomyService()

    override fun onLoad(api: KSLAPI) {
        // Register service
        api.registerService("economy", economyService)
        
        // Register context extension
        api.registerContextExtension(EconomyContextExtension())
        
        // Add default imports
        api.addDefaultImports("com.example.economy.*")
        
        api.kslPlugin.logger.info("Economy addon loaded!")
    }

    override fun onUnload() {
        api.kslPlugin.logger.info("Economy addon unloaded!")
    }
}

// Plugin main class
class MyEconomyPlugin : JavaPlugin() {
    private val addon = EconomyAddon()

    override fun onEnable() {
        if (KSL.isAvailable) {
            KSL.api.registerAddon(addon)
        }
    }

    override fun onDisable() {
        if (KSL.isAvailable) {
            KSL.api.unregisterAddon(addon.addonId)
        }
    }
}

Script usage:

onEvent<PlayerJoinEvent> {
    player.customBalance = 100.0
    player.sendRichMessage("<yellow>Your balance: <green>$${player.customBalance}")
}

registerCommand("pay") { player, args ->
    val target = Bukkit.getPlayer(args[0])
    val amount = args[1].toDouble()
    
    target?.giveMoney(amount)
    player.sendRichMessage("<green>Paid $${amount} to ${target?.name}")
}

πŸ“ Best Practices

  1. Always check KSL.isAvailable before accessing the API
  2. Use unique keys for services to avoid conflicts
  3. Clean up resources in onUnload() and onContextDestroyed()
  4. Document your services so script writers know how to use them
  5. Handle nulls gracefully - services might not be registered

πŸ” Debugging

Check if your addon is loaded:

/ksl addons

View registered services:

/ksl services

πŸŽ“ Next Steps

  • Check out the API Reference for all available methods
  • See Examples for real-world addon implementations
  • Learn about Sandbox & Security to understand how addons interact with script restrictions

Happy addon development! πŸš€

Clone this wiki locally