Minecraft Bukkit:如何检测自定义命令是否由告示牌触发?
问题:限制自定义命令仅通过告示牌点击触发
我想编写一个自定义Bukkit命令,只有同时满足以下两个条件时才触发玩家传送:
- 玩家通过右键点击告示牌触发该命令
- 满足预先设定的其他条件
目前我的CommandExecutor的onCommand函数代码如下:
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { if (!(sender instanceof Player)) { sender.sendMessage("ERROR: You must be a player to use this command."); return true; } if (sender.hasPermission(permission) && /* Other conditions */) { // permission是命令的传入参数 ((Player) sender).teleport(new Location(world, destX, destY, destZ, destYw, destPt)); // 这些变量均由命令参数决定 return true; } return true; }
现在遇到的问题是:只要玩家拥有对应权限(比如LuckPerms的group.default),就能直接在聊天框输入/[自定义命令名称]执行传送,这不符合我的预期。
对比原版Minecraft的情况:使用以下指令生成的告示牌,无OP权限的玩家点击后可以执行tp @s 0 100 0完成传送,但直接在聊天框输入/tp命令会被系统禁止:
/give @s minecraft:oak_sign{BlockEntityTag:{front_text:{messages:['{"text":"","clickEvent":{"action":"run_command","value":"tp @s 0 100 0"}}','{"text":"MySign"}','{"text":"Select to teleport.","italic":true,"color":"white"}','{"text":""}']},is_waxed:1b}}
请问怎么在自定义Bukkit命令中实现这种效果?能不能检测玩家是否通过右键点击告示牌触发命令?或者有没有更简便的实现方式?
解决方案
方式一:通过事件监听检测命令触发来源(适合复杂自定义命令)
Bukkit/Spigot没有在onCommand方法中直接提供命令触发来源的判断,但可以通过监听事件来实现:
- 权限配置:给自定义命令设置一个高权限节点(比如
mycommand.use),默认不给任何玩家(包括默认组)分配这个权限,这样玩家直接输入命令会因为缺权限被拦截。 - 监听事件临时授权:通过
PlayerCommandPreprocessEvent检测玩家是否是通过点击告示牌触发命令,若是则临时授予权限,命令执行完后再移除:
import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerCommandPreprocessEvent; import org.bukkit.permissions.PermissionAttachment; import org.bukkit.plugin.Plugin; import java.util.HashMap; import java.util.UUID; public class CommandSourceListener implements Listener { private final Plugin plugin; private final HashMap<UUID, PermissionAttachment> tempPerms = new HashMap<>(); public CommandSourceListener(Plugin plugin) { this.plugin = plugin; } @EventHandler public void onCommandPreprocess(PlayerCommandPreprocessEvent event) { String cmd = event.getMessage().toLowerCase(); // 替换成你的自定义命令,比如"/myteleport" if (cmd.startsWith("/myteleport")) { // 判断玩家最近右键的是否是告示牌 if (event.getPlayer().getLastTwoTargetBlocks(null, 5).get(0).getType().name().contains("SIGN")) { // 临时授予权限 PermissionAttachment attachment = event.getPlayer().addAttachment(plugin); attachment.setPermission("mycommand.use", true); tempPerms.put(event.getPlayer().getUniqueId(), attachment); // 延迟1tick移除权限,确保命令执行完成 plugin.getServer().getScheduler().runTaskLater(plugin, () -> { event.getPlayer().removeAttachment(attachment); tempPerms.remove(event.getPlayer().getUniqueId()); }, 1L); } } } }
- 修改onCommand方法:只需要检查权限和你的预设条件即可,直接输入命令的玩家因为没权限会触发提示:
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { if (!(sender instanceof Player)) { sender.sendMessage("ERROR: You must be a player to use this command."); return true; } Player player = (Player) sender; if (player.hasPermission("mycommand.use") && /* 你的其他预设条件 */) { player.teleport(new Location(world, destX, destY, destZ, destYaw, destPitch)); return true; } player.sendMessage("只能通过点击指定告示牌使用该命令!"); return true; }
方式二:利用原版指令+权限系统(更简便,适合简单需求)
如果你的需求只是简单的传送,完全不用写自定义命令,直接在告示牌的clickEvent里用execute指令配合权限节点即可:
把告示牌的clickEvent的value设置为:
execute if entity @s[permission=myteleport.allow] run tp @s <目标X> <目标Y> <目标Z>
然后给需要使用的玩家组分配myteleport.allow权限,但不分配minecraft.command.tp和minecraft.command.execute权限。这样:
- 玩家点击告示牌时,
execute会检查权限,满足条件就执行传送; - 直接输入/tp或execute命令会因为缺少对应权限被系统拦截。
这种方式不需要编写自定义命令的代码,完全利用原版机制实现,轻量化且高效。
内容的提问来源于stack exchange,提问作者Laurita S.
相关产品推荐
相关产品推荐

