Doc ID: SIRC-063

LuckPerms API 知识文档

LuckPerms 是一个完整的开发者 API,允许服务器上的其他插件读取和修改 LuckPerms 数据,并轻松地将 LuckPerms 深度集成到现有插件和系统中。

Overview

LuckPerms API 文档

概述

LuckPerms 是一个完整的开发者 API,允许服务器上的其他插件读取和修改 LuckPerms 数据,并轻松地将 LuckPerms 深度集成到现有插件和系统中。

包名

  • 主包: net.luckperms.api
  • 事件包: net.luckperms.api.event
  • 节点类型: net.luckperms.api.node

API 版本

  • 当前 API 版本: 5.4
  • 使用语义化版本控制
  • API 包: net.luckperms.api

核心接口和类

1. LuckPerms (主接口)

API 的根接口,需要先获取此实例才能进行任何操作。

获取实例的方法:

// 通过 Bukkit ServicesManager
RegisteredServiceProvider<LuckPerms> provider = Bukkit.getServicesManager().getRegistration(LuckPerms.class);
if (provider != null) {
    LuckPerms api = provider.getProvider();
}

// 通过 Sponge ServicesManager
Optional<ProviderRegistration<LuckPerms>> provider = Sponge.getServiceManager().getRegistration(LuckPerms.class);
if (provider.isPresent()) {
    LuckPerms api = provider.get().getProvider();
}

// 通过单例(全平台通用)
LuckPerms api = LuckPermsProvider.get(); // 注意:如果 API 未加载会抛出 IllegalStateException

2. User (用户对象)

代表服务器上的玩家及其关联的权限数据。

获取 User 实例:

// 玩家在线时
Player player = ...;
User user = luckPerms.getPlayerAdapter(Player.class).getUser(player);

// 通过 UUID
User user = luckPerms.getUserManager().getUser(uuid);

// 玩家可能离线时(异步加载)
UserManager userManager = luckPerms.getUserManager();
CompletableFuture<User> userFuture = userManager.loadUser(uniqueId);
userFuture.thenAcceptAsync(user -> {
    // 处理用户数据
});

3. Group (权限组对象)

代表权限组。

获取 Group 实例:

Group group = luckPerms.getGroupManager().getGroup(groupName);
if (group == null) {
    // 权限组不存在
    return;
}

4. Track (路线对象)

代表权限组晋升路线。

获取 Track 实例:

Track track = luckPerms.getTrackManager().getTrack(trackName);

Node (节点对象)

Node 接口是 LuckPerms 的核心数据类,表示"权限节点",但实际上封装了更多内容。

节点属性

  • key – 节点的键
  • value – 节点的值(false 表示否定状态)
  • context – 节点生效的情境
  • expiry – 节点的过期时间

节点类型

  • PermissionNode – 分配的权限
  • RegexPermissionNode – 分配的正则权限
  • InheritanceNode – 标记持有者从其他组继承数据
  • PrefixNode – 分配的前缀
  • SuffixNode – 分配的后缀
  • MetaNode – 分配的元数据
  • WeightNode – 持有节点的对象权重
  • DisplayNameNode – 持有节点的对象显示名称

创建节点实例

// 创建任何类型的节点
Node node = Node.builder("some.node.key").build();

// 带额外属性
Node node = Node.builder("some.node.key")
    .value(false)
    .expiry(Duration.ofHours(1))
    .withContext(DefaultContextKeys.SERVER_KEY, "survival")
    .build();

// 创建特定类型节点
PermissionNode permNode = PermissionNode.builder("my.permission").build();
InheritanceNode inheritNode = InheritanceNode.builder(group).build();
PrefixNode prefixNode = PrefixNode.builder("[Some Prefix]", 100).build();
MetaNode metaNode = MetaNode.builder("some-key", "some-value").build();

修改现有节点

// 节点是不可变的,需要创建新节点
Node negated = node.toBuilder().value(false).build();

数据操作

读取用户/权限组数据

// 获取未扁平化的节点集合(不包含继承数据)
Collection<Node> nodes = user.getNodes();

// 按类型筛选节点
Set<String> groups = user.getNodes(NodeType.INHERITANCE).stream()
    .map(InheritanceNode::getGroupName)
    .collect(Collectors.toSet());

// 获取排序后的节点集合
SortedSet<Node> distinctNodes = user.getDistinctNodes();

// 获取包含继承数据的节点
Collection<Node> inheritedNodes = user.resolveInheritedNodes(queryOptions);

修改用户/权限组数据

// 添加权限
DataMutateResult result = user.data().add(Node.builder("your.node.here").build());

// 保存更改
luckPerms.getUserManager().saveUser(user);

// 使用 modify* 方法(自动处理加载和保存)
luckPerms.getUserManager().modifyUser(userUuid, user -> {
    user.data().add(Node.builder(permission).build());
});

Context (情境)

重要类

  • ContextSet – 情境组接口
  • ImmutableContextSet – 不可变情境组实现
  • MutableContextSet – 可变情境组实现

创建情境组

// 不可变情境组
ImmutableContextSet set1 = ImmutableContextSet.empty();
ImmutableContextSet set2 = ImmutableContextSet.of("world", "world_nether");
ImmutableContextSet set3 = ImmutableContextSet.builder()
    .add("world", "world_nether")
    .add("server", "survival")
    .build();

// 可变情境组
MutableContextSet mutableSet = MutableContextSet.create();
mutableSet.add("world", "text");

注册 ContextCalculator

public class CustomCalculator implements ContextCalculator<Player> {
    @Override
    public void calculate(Player target, ContextConsumer contextConsumer) {
        contextConsumer.accept("gamemode", target.getGameMode().name());
    }
    
    @Override
    public ContextSet estimatePotentialContexts() {
        ImmutableContextSet.Builder builder = ImmutableContextSet.builder();
        for (GameMode gameMode : GameMode.values()) {
            builder.add("gamemode", gameMode.name().toLowerCase());
        }
        return builder.build();
    }
}

// 注册计算器
luckPerms.getContextManager().registerCalculator(new CustomCalculator());

查询活跃情境

Player player = ...;
ImmutableContextSet contextSet = luckPerms.getContextManager().getContext(player);
QueryOptions queryOptions = luckPerms.getContextManager().getQueryOptions(player);

CachedData (缓存数据)

获取缓存数据

// 通过 PlayerAdapter
Player player = ...;
PlayerAdapter<Player> adapter = luckperms.getPlayerAdapter(Player.class);
CachedPermissionData permissionData = adapter.getPermissionData(player);
CachedMetaData metaData = adapter.getMetaData(player);

// 通过 User/Group
CachedPermissionData permissionData = user.getCachedData().getPermissionData();
CachedMetaData metaData = user.getCachedData().getMetaData();

// 指定查询选项
CachedPermissionData permissionData = user.getCachedData().getPermissionData(queryOptions);

权限检查

// 运行权限检查
Tristate checkResult = permissionData.checkPermission("some.permission.node");
boolean checkResultAsBoolean = checkResult.asBoolean();

// 完整方法
public boolean hasPermission(User user, String permission) {
    return user.getCachedData().getPermissionData().checkPermission(permission).asBoolean();
}

获取前缀/后缀

String prefix = user.getCachedData().getMetaData().getPrefix();
String suffix = user.getCachedData().getMetaData().getSuffix();

获取元数据

String metaValue = user.getCachedData().getMetaData().getMetaValue("some-key");

自定义元数据存储和查询

设置元数据

public void setLevel(Player player, int level) {
    User user = luckPerms.getPlayerAdapter(Player.class).getUser(player);
    MetaNode node = MetaNode.builder("level", Integer.toString(level)).build();
    
    // 清除现有的相同键的元数据节点
    user.data().clear(NodeType.META.predicate(mn -> mn.getMetaKey().equals("level")));
    
    // 添加新节点
    user.data().add(node);
    
    // 保存
    luckPerms.getUserManager().saveUser(user);
}

查询元数据

public int getLevel(Player player) {
    CachedMetaData metaData = luckPerms.getPlayerAdapter(Player.class).getMetaData(player);
    return metaData.getMetaValue("level", Integer::parseInt).orElse(0);
}

事件系统

事件监听器

import net.luckperms.api.event.EventBus;
import net.luckperms.api.event.log.LogPublishEvent;
import net.luckperms.api.event.user.UserLoadEvent;

public class MyListener {
    private final MyPlugin plugin;
    
    public MyListener(MyPlugin plugin, LuckPerms luckPerms) {
        this.plugin = plugin;
        EventBus eventBus = luckPerms.getEventBus();
        
        // 使用表达式 lambda
        eventBus.subscribe(this.plugin, LogPublishEvent.class, e -> /* ... */);
        
        // 使用语句 lambda
        eventBus.subscribe(this.plugin, UserLoadEvent.class, e -> {
            // ...
        });
        
        // 使用方法引用
        eventBus.subscribe(this.plugin, UserPromoteEvent.class, this::onUserPromote);
    }
    
    private void onUserPromote(UserPromoteEvent event) {
        // ...
    }
}

重要事件

  • UserDataRecalculateEvent – 用户缓存数据刷新时调用
  • NodeAddEvent – 节点添加到用户/权限组时调用
  • NodeRemoveEvent – 节点从用户/权限组移除时调用
  • NodeClearEvent – 用户/权限组的所有/部分节点被移除时调用
  • NodeMutateEvent – 上述事件的基类

实用信息

线程安全

  • 所有 LuckPerms 内部都是线程安全的
  • 可以从异步调度任务安全地与 API 交互

不可变性

  • 返回的集合默认是不可变的
  • 不能对返回的集合进行修改

阻塞操作

  • 某些方法不是"主线程友好"的
  • 大多数阻塞方法返回 CompletableFuture
  • 建议在异步任务中与 API 交互

使用 CompletableFutures

// 阻塞方式(仅在异步线程中使用)
CompletableFuture<User> userFuture = userManager.loadUser(uniqueId);
User user = userFuture.join();

// 回调方式
userFuture.thenAcceptAsync(user -> {
    // 处理用户数据
}, executor);

常用工具方法

检查玩家是否在权限组中

public static boolean isPlayerInGroup(Player player, String group) {
    return player.hasPermission("group." + group);
}

查找玩家所在权限组

public static String getPlayerGroup(Player player, Collection<String> possibleGroups) {
    for (String group : possibleGroups) {
        if (player.hasPermission("group." + group)) {
            return group;
        }
    }
    return null;
}

平台支持的操作对象类型

平台 操作对象类型
Bukkit org.bukkit.entity.Player
BungeeCord net.md_5.bungee.api.connection.ProxiedPlayer
Sponge org.spongepowered.api.service.permission.Subject
Fabric net.minecraft.server.network.ServerPlayerEntity
Forge net.minecraft.server.level.ServerPlayer
Nukkit cn.nukkit.Player
Velocity com.velocitypowered.api.proxy.Player

重要注意事项

  1. 在线与离线玩家: 在线玩家保证有已加载的 User 对象,离线玩家可能没有
  2. 数据保存: 修改用户/权限组数据后必须调用保存方法
  3. 异步操作: 建议在异步任务中进行数据操作
  4. 缓存数据: 频繁查询时使用 CachedData 而不是直接查询 User/Group
  5. 事件监听: LuckPerms 使用独立的事件系统,需要直接注册到 LuckPerms

内容来自 SnowCutieOwO – Wiki