输入至少两个字符

KaMenu API 文档

文档版本: v2.1.0

KaMenu 提供公开 API,允许其他插件打开文件菜单、渲染内存 YAML 菜单、注册自定义动作命名空间,并解析原版或第三方物品的原生 Sprite 文本组件。

API 类#

org.katacr.kamenu.api.KaMenuAPI#

openMenu(Player player, String menuId, List<String> arguments = emptyList())#

打开由 KaMenu MenuManager 加载的菜单。

参数:

  • player - 目标玩家
  • menuId - 菜单 ID,例如 "main_menu" 或 "shop/weapons"
  • arguments - 按位置传入目标菜单的参数列表,可在菜单中使用 {arg:0}、{arg:1} 读取

返回值:

  • boolean - 是否成功提交打开请求
import org.katacr.kamenu.api.KaMenuAPI

val success = KaMenuAPI.openMenu(player, "main_menu")
KaMenuAPI.openMenu(player, "shop/weapons", listOf("weapons", "vip"))
import org.katacr.kamenu.api.KaMenuAPI;
import java.util.List;

boolean success = KaMenuAPI.openMenu(player, "main_menu");
KaMenuAPI.openMenu(player, "shop/weapons", List.of("weapons", "vip"));

openYaml(Player player, String yaml, String contextId = "external", List<String> arguments = emptyList())#

解析内存中的 YAML 字符串,并作为 KaMenu 菜单打开。该 YAML 不会写入 menus 目录,也不需要执行菜单重载。

参数:

  • player - 目标玩家
  • yaml - 完整的 KaMenu 菜单 YAML 内容
  • contextId - 外部上下文标识,用于日志定位来源
  • arguments - 按位置传入目标菜单的参数列表

返回值:

  • boolean - 是否成功解析 YAML 并提交打开请求
val yaml = """
Title: "&a游戏中心"
Body:
  welcome:
    type: message
    text: "&7欢迎你,%player_name%!"
Bottom:
  type: notice
  confirm:
    text: "&a加入"
    actions:
      - "kgc:join lobby"
""".trimIndent()

KaMenuAPI.openYaml(player, yaml, "kagamecenter:main", listOf("lobby", "solo"))

如果 YAML 解析失败,KaMenu 会在警告日志中写入 contextId。

openConfig(Player player, YamlConfiguration config, String contextId = "external", List<String> arguments = emptyList())#

打开内存中的 YamlConfiguration。该配置不要求来自 MenuManager。

参数:

  • player - 目标玩家
  • config - 完整的 KaMenu 菜单配置
  • contextId - 外部上下文标识,用于日志定位来源
  • arguments - 按位置传入目标菜单的参数列表

返回值:

  • boolean - 是否成功提交打开请求
import org.bukkit.configuration.file.YamlConfiguration
import org.katacr.kamenu.api.KaMenuAPI

val config = YamlConfiguration()
config.loadFromString(yaml)

KaMenuAPI.openConfig(player, config, "myplugin:dynamic-shop", listOf("diamond", "buy"))

三个平台都会在渲染前执行 Events.Open;若动作链执行 return,菜单不会继续打开。外部内存菜单使用 reset 时会重新打开当前内存 YamlConfiguration,不要求配置来自 MenuManager。

目标菜单可通过 Settings.pass_arguments 设置默认参数和最少参数数量。显式传入参数优先按索引使用;缺少的索引会使用目标菜单的 default 补位。菜单配置中可使用 {arg:0}、{arg:1}、{args} 和 {arg_count}。当补位后的参数数量小于 must 时,菜单不会打开。

registerActionHandler(String namespace, KaMenuActionHandler handler)#

注册自定义动作命名空间。形如 namespace:payload 的动作会先交给已注册的 handler 处理,再决定是否继续走 KaMenu 内置动作逻辑。

参数:

  • namespace - 不包含 : 的动作命名空间,例如 "kgc"
  • handler - 自定义动作处理器

返回值:

  • boolean - 是否注册成功
import org.katacr.kamenu.api.KaMenuAPI

KaMenuAPI.registerActionHandler("kgc") { player, action, variables, config ->
    val payload = action.removePrefix("kgc:").trim()

    when {
        payload == "open-main" -> {
            KaMenuAPI.openYaml(player, buildMainMenuYaml(player), "kagamecenter:main")
            true
        }
        payload.startsWith("join ") -> {
            val arenaId = payload.removePrefix("join ").trim()
            joinArena(player, arenaId)
            true
        }
        else -> false
    }
}

菜单 YAML 中可以在任意支持动作列表的位置使用该命名空间:

Bottom:
  type: notice
  confirm:
    text: "&a加入"
    actions:
      - "kgc:join lobby"

handler 会收到已解析变量后的动作字符串、输入变量(如 $(name))以及当前菜单配置。返回 true 表示动作已处理;返回 false 表示未处理,KaMenu 会继续尝试内置动作逻辑。handler 抛出异常时,KaMenu 会记录命名空间和动作内容。

unregisterActionHandler(String namespace)#

注销自定义动作命名空间。

KaMenuAPI.unregisterActionHandler("kgc")

resolveItemSprite(String itemId)#

将物品 ID 解析为可直接拼接到 Adventure 文本的原生 Component Sprite。支持原版材质(如 "diamond")以及 KaMenu 支持的外部物品前缀:ce: / craftengine:、ia: / itemsadder: 和 oraxen:。

参数:

  • itemId - 原版材质名或带提供方前缀的物品 ID,例如 "ce:hfrpg:hefeng_shard"

返回值:

  • Component - 已固定白色的原生 Sprite Component
  • null - KaMenu 未初始化、服务端低于 Minecraft 1.21.9,或物品无法解析

返回的是原生 Component,不是 legacy 或 MiniMessage 字符串。调用方应直接 append 它,不能再经 LegacyComponentSerializer、ItemMeta#setLore(List<String>) 或其他字符串 API 回写,否则 Sprite 会降级为文本。

import net.kyori.adventure.text.Component
import org.katacr.kamenu.api.KaMenuAPI

val icon = KaMenuAPI.resolveItemSprite("ce:hfrpg:hefeng_shard")
val line = Component.text("强化道具:").append(icon ?: Component.empty()).append(Component.text(" 荷风碎片"))
meta.lore(listOf(line))
import net.kyori.adventure.text.Component;
import org.katacr.kamenu.api.KaMenuAPI;
import java.util.List;

Component icon = KaMenuAPI.resolveItemSprite("ce:hfrpg:hefeng_shard");
Component line = Component.text("强化道具:")
    .append(icon == null ? Component.empty() : icon)
    .append(Component.text(" 荷风碎片"));
meta.lore(List.of(line));

该方法会访问第三方物品 API 和 CraftEngine 最终资源包;请只在 Bukkit 主线程或 Folia 对应玩家/区域线程调用。

isAvailable()#

检查 KaMenu 是否已加载并可用。

if (KaMenuAPI.isAvailable()) {
    KaMenuAPI.openMenu(player, "main_menu")
}

getPlugin()#

获取 KaMenu 插件实例。如果 KaMenu 未加载,返回 null。

动作处理器接口#

package org.katacr.kamenu.api

import org.bukkit.configuration.file.YamlConfiguration
import org.bukkit.entity.Player

fun interface KaMenuActionHandler {
    fun execute(
        player: Player,
        action: String,
        variables: Map<String, String>,
        rawConfig: YamlConfiguration?
    ): Boolean
}

建议为你的插件选择唯一命名空间。除非你明确希望拦截内置动作,否则不要使用 tell、open、command、console、js 等内置动作名称作为命名空间。

在其他插件中使用#

方法 1:反射调用#

如果你的插件不需要直接编译依赖 KaMenu,可以使用反射调用 API:

fun openKaMenu(player: Player, menuId: String): Boolean {
    return try {
        val kaMenuPlugin = player.server.pluginManager.getPlugin("KaMenu") ?: return false
        val apiClass = Class.forName("org.katacr.kamenu.api.KaMenuAPI")
        val openMenuMethod = apiClass.getMethod("openMenu", Player::class.java, String::class.java)
        openMenuMethod.invoke(null, player, menuId) == true
    } catch (e: Exception) {
        e.printStackTrace()
        false
    }
}

方法 2:添加插件依赖#

如果希望使用类型安全的 API,可以在 plugin.yml 中添加 KaMenu 作为依赖:

name: YourPlugin
version: 1.0.0
main: com.yourname.yourplugin.Main
api-version: "1.21"
depend:
  - KaMenu

然后直接调用:

import org.katacr.kamenu.api.KaMenuAPI

fun openShop(player: Player) {
    KaMenuAPI.openMenu(player, "shop/weapons")
}

注意事项#

  1. 插件依赖:调用 API 前请确保 KaMenu 已安装并启用。
  2. 菜单来源:文件菜单使用 openMenu,动态内存菜单使用 openYaml 或 openConfig。
  3. 线程:openConfig 和 openYaml 可以从异步代码调用;KaMenu 会将实际 Dialog 渲染调度回服务器主线程。
  4. Open 事件:内存菜单同样会在渲染前执行 Events.Open。
  5. 错误处理:建议检查布尔返回值,并在 API 调用失败时给玩家或日志提供反馈。

完整示例#

package com.example.plugin

import org.bukkit.command.Command
import org.bukkit.command.CommandSender
import org.bukkit.entity.Player
import org.bukkit.plugin.java.JavaPlugin
import org.katacr.kamenu.api.KaMenuAPI

class ExamplePlugin : JavaPlugin() {
    override fun onEnable() {
        if (!KaMenuAPI.isAvailable()) {
            logger.warning("KaMenu 插件未安装,部分功能将不可用")
        }

        KaMenuAPI.registerActionHandler("example") { player, action, variables, config ->
            val payload = action.removePrefix("example:").trim()
            if (payload == "reward") {
                player.sendMessage("§a奖励动作已由 ExamplePlugin 处理")
                true
            } else {
                false
            }
        }
    }

    override fun onDisable() {
        KaMenuAPI.unregisterActionHandler("example")
    }

    override fun onCommand(
        sender: CommandSender,
        command: Command,
        label: String,
        args: Array<out String>
    ): Boolean {
        if (!command.name.equals("openmenu", ignoreCase = true)) {
            return false
        }
        if (sender !is Player) {
            sender.sendMessage("§c该指令只能由玩家执行")
            return true
        }

        val yaml = """
        Title: "&a示例菜单"
        Body:
          text:
            type: message
            text: "&7该菜单来自内存 YAML。"
        Bottom:
          type: notice
          confirm:
            text: "&a领取奖励"
            actions:
              - "example:reward"
        """.trimIndent()

        val success = KaMenuAPI.openYaml(sender, yaml, "example:command")
        if (!success) {
            sender.sendMessage("§c打开菜单失败")
        }
        return true
    }
}

监听事件#

KaMenu 提供两个公开 Bukkit 事件,供其他插件(如 EcoQuests)监听菜单打开与数据变化,以驱动任务、统计等逻辑。事件类位于 org.katacr.kamenu.api.event 包。

KaMenuOpenEvent#

玩家打开 KaMenu 菜单时触发。该事件在菜单真正打开(容器菜单或 Dialog 菜单均可)前同步抛出,携带菜单 ID、来源与参数。

字段 类型 说明
player Player 打开菜单的玩家
menuId String 菜单 ID,如 "主菜单/主菜单"
source KaMenuOpenEvent.Source 打开来源:COMMAND / BUTTON / API / OTHER
arguments List<String> 传入菜单的参数列表
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.katacr.kamenu.api.event.KaMenuOpenEvent

class MyListener : Listener {
    @EventHandler
    fun onMenuOpen(event: KaMenuOpenEvent) {
        val player = event.player
        if (event.menuId == "主菜单/主菜单") {
            // 玩家打开了主菜单
        }
    }
}

KaMenuDataChangeEvent#

KaMenu 数据存储(data / gdata / list / glist / meta / 临时数据)发生写入、修改、增减或删除时触发。

字段 类型 说明
scope KaMenuDataChangeEvent.Scope 作用域:PLAYER_DATA / GLOBAL_DATA / PLAYER_LIST / GLOBAL_LIST / META / PLAYER_TMP
action KaMenuDataChangeEvent.Action 操作:SET / MODIFY / ADD / REMOVE / CLEAR
player Player? 相关玩家;全局作用域或玩家不在线时为 null
key String 数据键
value String? 写入/追加的新值;删除类操作可能为 null
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.katacr.kamenu.api.event.KaMenuDataChangeEvent

class DataListener : Listener {
    @EventHandler
    fun onDataChange(event: KaMenuDataChangeEvent) {
        if (event.scope == KaMenuDataChangeEvent.Scope.PLAYER_DATA && event.key == "quest_progress") {
            val player = event.player ?: return
            // 玩家 data 键 quest_progress 发生变化
        }
    }
}

注意事项#

  1. 事件是同步抛出的:KaMenuOpenEvent 在主线程抛出;KaMenuDataChangeEvent 由异步数据库写操作触发,KaMenu 会先切回主线程再抛出。监听器内可安全调用 Bukkit API。
  2. 在线玩家:KaMenuDataChangeEvent.player 仅在玩家在线时非空;跨服/离线玩家数据变化时该字段为 null。
  3. 列表写操作:list / glist 方法复用 data / gdata 写入路径,因此一次列表写会同时触发对应的标量事件与列表事件,可按 scope 区分。
  4. 示例联动:EcoQuests 等插件可通过监听 KaMenuOpenEvent 在任务书里添加"打开菜单"类任务。