feat: 添加潜影盒快捷打开和末影箱自定义标题功能
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package cn.infstar.essentialsC;
|
||||
|
||||
import cn.infstar.essentialsC.commands.*;
|
||||
import cn.infstar.essentialsC.listeners.ShulkerBoxListener;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
@@ -17,7 +18,12 @@ public final class EssentialsC extends JavaPlugin {
|
||||
// 初始化语言管理器
|
||||
langManager = new LangManager(this);
|
||||
|
||||
// 注册监听器
|
||||
registerListeners();
|
||||
|
||||
// 注册命令
|
||||
registerCommands();
|
||||
|
||||
getLogger().info("EssentialsC 插件已启用!");
|
||||
getLogger().info("当前语言: " + langManager.getCurrentLanguage());
|
||||
}
|
||||
@@ -34,6 +40,15 @@ public final class EssentialsC extends JavaPlugin {
|
||||
return langManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册所有监听器
|
||||
*/
|
||||
private void registerListeners() {
|
||||
// 注册潜影盒右键打开监听器
|
||||
new ShulkerBoxListener(this);
|
||||
getLogger().info("成功注册监听器!");
|
||||
}
|
||||
|
||||
private void registerCommands() {
|
||||
try {
|
||||
// 获取 CommandMap
|
||||
@@ -41,34 +56,40 @@ public final class EssentialsC extends JavaPlugin {
|
||||
bukkitCommandMap.setAccessible(true);
|
||||
org.bukkit.command.CommandMap commandMap = (org.bukkit.command.CommandMap) bukkitCommandMap.get(Bukkit.getServer());
|
||||
|
||||
// 注册所有命令
|
||||
registerCommand(commandMap, "workbench", new WorkbenchCommand());
|
||||
registerCommand(commandMap, "anvil", new AnvilCommand());
|
||||
registerCommand(commandMap, "enchantingtable", new EnchantingTableCommand());
|
||||
registerCommand(commandMap, "cartographytable", new CartographyTableCommand());
|
||||
registerCommand(commandMap, "grindstone", new GrindstoneCommand());
|
||||
registerCommand(commandMap, "loom", new LoomCommand());
|
||||
registerCommand(commandMap, "smithingtable", new SmithingTableCommand());
|
||||
registerCommand(commandMap, "stonecutter", new StonecutterCommand());
|
||||
registerCommand(commandMap, "enderchest", new EnderChestCommand());
|
||||
registerCommand(commandMap, "hat", new HatCommand());
|
||||
registerCommand(commandMap, "suicide", new SuicideCommand());
|
||||
registerCommand(commandMap, "fly", new FlyCommand());
|
||||
registerCommand(commandMap, "heal", new HealCommand());
|
||||
registerCommand(commandMap, "vanish", new VanishCommand());
|
||||
registerCommand(commandMap, "seen", new SeenCommand());
|
||||
registerCommand(commandMap, "feed", new FeedCommand());
|
||||
registerCommand(commandMap, "repair", new RepairCommand());
|
||||
registerCommand(commandMap, "essentialsc", new HelpCommand());
|
||||
// 注册所有命令(使用 CMI 风格:独立命令 + 别名)
|
||||
registerCommandWithAliases(commandMap, "workbench", new WorkbenchCommand(), "wb");
|
||||
registerCommandWithAliases(commandMap, "anvil", new AnvilCommand());
|
||||
registerCommandWithAliases(commandMap, "cartographytable", new CartographyTableCommand(), "ct", "cartography");
|
||||
registerCommandWithAliases(commandMap, "grindstone", new GrindstoneCommand(), "gs");
|
||||
registerCommandWithAliases(commandMap, "loom", new LoomCommand());
|
||||
registerCommandWithAliases(commandMap, "smithingtable", new SmithingTableCommand(), "st", "smithing");
|
||||
registerCommandWithAliases(commandMap, "stonecutter", new StonecutterCommand(), "sc");
|
||||
registerCommandWithAliases(commandMap, "enderchest", new EnderChestCommand(), "ec");
|
||||
registerCommandWithAliases(commandMap, "hat", new HatCommand());
|
||||
registerCommandWithAliases(commandMap, "suicide", new SuicideCommand(), "die");
|
||||
registerCommandWithAliases(commandMap, "fly", new FlyCommand());
|
||||
registerCommandWithAliases(commandMap, "heal", new HealCommand());
|
||||
registerCommandWithAliases(commandMap, "vanish", new VanishCommand(), "v");
|
||||
registerCommandWithAliases(commandMap, "seen", new SeenCommand(), "info");
|
||||
registerCommandWithAliases(commandMap, "feed", new FeedCommand());
|
||||
registerCommandWithAliases(commandMap, "repair", new RepairCommand(), "rep");
|
||||
registerCommandWithAliases(commandMap, "essentialsc", new HelpCommand(), "essc");
|
||||
|
||||
getLogger().info("成功注册 18 个命令!");
|
||||
getLogger().info("成功注册所有命令!");
|
||||
} catch (Exception e) {
|
||||
getLogger().severe("无法注册命令: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void registerCommand(org.bukkit.command.CommandMap commandMap, String name, cn.infstar.essentialsC.commands.BaseCommand executor) {
|
||||
/**
|
||||
* 注册命令并支持别名
|
||||
* @param commandMap Bukkit CommandMap
|
||||
* @param name 主命令名
|
||||
* @param executor 命令执行器
|
||||
* @param aliases 别名列表(可选)
|
||||
*/
|
||||
private void registerCommandWithAliases(org.bukkit.command.CommandMap commandMap, String name, cn.infstar.essentialsC.commands.BaseCommand executor, String... aliases) {
|
||||
Command command = new Command(name) {
|
||||
@Override
|
||||
public boolean execute(CommandSender sender, String commandLabel, String[] args) {
|
||||
@@ -84,13 +105,28 @@ public final class EssentialsC extends JavaPlugin {
|
||||
}
|
||||
};
|
||||
|
||||
// 为 essentialsc 命令添加简化别名
|
||||
if (name.equals("essentialsc")) {
|
||||
command.setAliases(java.util.Arrays.asList("essc"));
|
||||
}
|
||||
|
||||
command.setPermission(executor.getPermission());
|
||||
// 注册到默认命名空间,使玩家可以直接使用 /workbench 而不是 /essentialsc:workbench
|
||||
commandMap.register("", command);
|
||||
|
||||
// 注册别名
|
||||
for (String alias : aliases) {
|
||||
Command aliasCmd = new Command(alias) {
|
||||
@Override
|
||||
public boolean execute(CommandSender sender, String commandLabel, String[] args) {
|
||||
return executor.onCommand(sender, this, commandLabel, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.util.List<String> tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException {
|
||||
if (executor instanceof org.bukkit.command.TabCompleter) {
|
||||
return ((org.bukkit.command.TabCompleter) executor).onTabComplete(sender, this, alias, args);
|
||||
}
|
||||
return super.tabComplete(sender, alias, args);
|
||||
}
|
||||
};
|
||||
aliasCmd.setPermission(executor.getPermission());
|
||||
commandMap.register("", aliasCmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
package cn.infstar.essentialsC.commands;
|
||||
|
||||
import cn.infstar.essentialsC.EssentialsC;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class AdminMenuCommand extends BaseCommand implements Listener {
|
||||
|
||||
private static final int MENU_SIZE = 27;
|
||||
|
||||
public AdminMenuCommand() {
|
||||
super("essentialsc.command.admin");
|
||||
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean execute(@NotNull Player player, String[] args) {
|
||||
openMenu(player);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void openMenu(Player player) {
|
||||
String title = plugin.getConfig().getString("admin-menu.title", "&6EssentialsC 管理菜单");
|
||||
Inventory menu = Bukkit.createInventory(null, MENU_SIZE, translateColor(title));
|
||||
|
||||
// 从配置中读取所有物品
|
||||
var itemsConfig = plugin.getConfig().getConfigurationSection("admin-menu.items");
|
||||
if (itemsConfig == null) return;
|
||||
|
||||
for (String key : itemsConfig.getKeys(false)) {
|
||||
var section = itemsConfig.getConfigurationSection(key);
|
||||
if (section == null) continue;
|
||||
|
||||
int slot = section.getInt("slot");
|
||||
Material material = Material.matchMaterial(section.getString("material", "STONE"));
|
||||
if (material == null) material = Material.STONE;
|
||||
|
||||
String name = translateColor(section.getString("name", "&fItem"));
|
||||
java.util.List<String> lore = section.getStringList("lore").stream()
|
||||
.map(this::translateColor)
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
|
||||
addItem(menu, slot, material, name, lore);
|
||||
}
|
||||
|
||||
player.openInventory(menu);
|
||||
}
|
||||
|
||||
private String translateColor(String text) {
|
||||
return text.replace("&", "§");
|
||||
}
|
||||
|
||||
private void addItem(Inventory inv, int slot, Material material, String name, java.util.List<String> lore) {
|
||||
ItemStack item = new ItemStack(material);
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.setDisplayName(name);
|
||||
meta.setLore(lore);
|
||||
item.setItemMeta(meta);
|
||||
}
|
||||
inv.setItem(slot, item);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onMenuClick(InventoryClickEvent event) {
|
||||
// 检查是否是管理员菜单
|
||||
String configTitle = translateColor(plugin.getConfig().getString("admin-menu.title", "&6EssentialsC 管理菜单"));
|
||||
if (!event.getView().getTitle().equals(configTitle)) return;
|
||||
if (!(event.getWhoClicked() instanceof Player player)) return;
|
||||
|
||||
// 取消事件,防止拿出物品
|
||||
event.setCancelled(true);
|
||||
|
||||
ItemStack clicked = event.getCurrentItem();
|
||||
if (clicked == null || !clicked.hasItemMeta()) return;
|
||||
|
||||
String displayName = clicked.getItemMeta().getDisplayName();
|
||||
|
||||
// 从配置中读取所有物品配置,通过名称匹配
|
||||
var itemsConfig = plugin.getConfig().getConfigurationSection("admin-menu.items");
|
||||
if (itemsConfig == null) return;
|
||||
|
||||
for (String key : itemsConfig.getKeys(false)) {
|
||||
var section = itemsConfig.getConfigurationSection(key);
|
||||
if (section == null) continue;
|
||||
|
||||
String itemName = translateColor(section.getString("name", ""));
|
||||
if (!displayName.equals(itemName)) continue;
|
||||
|
||||
// 找到匹配的物品,执行对应操作
|
||||
switch (key) {
|
||||
case "time-control" -> {
|
||||
if (event.isLeftClick()) player.getWorld().setTime(1000);
|
||||
else player.getWorld().setTime(13000);
|
||||
player.sendMessage(getLang().getString("admin-time-set"));
|
||||
}
|
||||
case "weather-control" -> {
|
||||
if (event.isLeftClick()) player.getWorld().setStorm(false);
|
||||
else player.getWorld().setStorm(true);
|
||||
player.sendMessage(getLang().getString("admin-weather-set"));
|
||||
}
|
||||
case "heal-self" -> {
|
||||
player.setHealth(player.getMaxHealth());
|
||||
player.setFoodLevel(20);
|
||||
player.sendMessage(getLang().getString("admin-heal-success"));
|
||||
}
|
||||
case "feed-self" -> {
|
||||
player.setFoodLevel(20);
|
||||
player.setSaturation(20f);
|
||||
player.sendMessage(getLang().getString("admin-feed-success"));
|
||||
}
|
||||
case "repair-hand" -> {
|
||||
var item = player.getInventory().getItemInMainHand();
|
||||
if (item.getItemMeta() instanceof org.bukkit.inventory.meta.Damageable d) {
|
||||
d.setDamage(0);
|
||||
item.setItemMeta((org.bukkit.inventory.meta.ItemMeta) d);
|
||||
player.sendMessage(getLang().getString("admin-repair-success"));
|
||||
}
|
||||
}
|
||||
case "vanish" -> {
|
||||
HelpCommand.COMMAND_CACHE.get("vanish").execute(player, new String[]{});
|
||||
openMenu(player); // 刷新菜单
|
||||
}
|
||||
case "reload" -> {
|
||||
plugin.reloadConfig();
|
||||
EssentialsC.getLangManager().reload();
|
||||
player.sendMessage(getLang().getString("admin-reload-success"));
|
||||
}
|
||||
}
|
||||
break; // 找到后退出循环
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,8 @@ public class AnvilCommand extends BaseCommand {
|
||||
|
||||
@Override
|
||||
protected boolean execute(Player player, String[] args) {
|
||||
// 使用 Paper API 打开铁砧(标题跟随客户端语言)
|
||||
player.openAnvil(null, true);
|
||||
player.sendMessage(getLang().getString("anvil-opened"));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
package cn.infstar.essentialsC.commands;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class BlocksMenuCommand extends BaseCommand implements Listener {
|
||||
|
||||
private static final int MENU_SIZE = 36;
|
||||
private final NamespacedKey blockKey;
|
||||
|
||||
public BlocksMenuCommand() {
|
||||
super("essentialsc.command.blocks");
|
||||
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||
blockKey = new NamespacedKey(plugin, "block_key");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean execute(@NotNull Player player, String[] args) {
|
||||
openMenu(player);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void openMenu(Player player) {
|
||||
String title = plugin.getConfig().getString("blocks-menu.title", "&6&lEssentialsC &8- &e&l功能方块菜单");
|
||||
Inventory menu = Bukkit.createInventory(null, MENU_SIZE, translateColor(title));
|
||||
|
||||
// 从配置中读取所有物品
|
||||
var itemsConfig = plugin.getConfig().getConfigurationSection("blocks-menu.items");
|
||||
if (itemsConfig == null) return;
|
||||
|
||||
for (String key : itemsConfig.getKeys(false)) {
|
||||
var section = itemsConfig.getConfigurationSection(key);
|
||||
if (section == null) continue;
|
||||
|
||||
// 检查权限
|
||||
String permission = section.getString("permission");
|
||||
if (permission != null && !player.hasPermission(permission)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int slot = section.getInt("slot");
|
||||
Material material = Material.matchMaterial(section.getString("material", "STONE"));
|
||||
if (material == null) material = Material.STONE;
|
||||
|
||||
String name = translateColor(section.getString("name", "&fItem"));
|
||||
java.util.List<String> lore = section.getStringList("lore").stream()
|
||||
.map(this::translateColor)
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
|
||||
addItem(menu, slot, material, name, lore, key);
|
||||
}
|
||||
|
||||
player.openInventory(menu);
|
||||
}
|
||||
|
||||
private void addItem(Inventory inv, int slot, Material material, String name, java.util.List<String> lore, String key) {
|
||||
ItemStack item = new ItemStack(material);
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.setDisplayName(name);
|
||||
meta.setLore(lore);
|
||||
meta.getPersistentDataContainer().set(this.blockKey, PersistentDataType.STRING, key);
|
||||
item.setItemMeta(meta);
|
||||
}
|
||||
inv.setItem(slot, item);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onMenuClick(InventoryClickEvent event) {
|
||||
// 动态获取配置的标题
|
||||
String configTitle = plugin.getConfig().getString("blocks-menu.title", "&6&lEssentialsC &8- &e&l功能方块菜单");
|
||||
String actualTitle = translateColor(configTitle);
|
||||
|
||||
if (!event.getView().getTitle().equals(actualTitle)) return;
|
||||
if (!(event.getWhoClicked() instanceof Player player)) return;
|
||||
|
||||
event.setCancelled(true);
|
||||
ItemStack clicked = event.getCurrentItem();
|
||||
if (clicked == null || !clicked.hasItemMeta()) return;
|
||||
|
||||
ItemMeta meta = clicked.getItemMeta();
|
||||
String key = meta.getPersistentDataContainer().get(this.blockKey, PersistentDataType.STRING);
|
||||
|
||||
// 点击后执行对应命令并播放音效(如果有)
|
||||
if (key != null && HelpCommand.COMMAND_CACHE.containsKey(key)) {
|
||||
playBlockOpenSound(player, key);
|
||||
HelpCommand.COMMAND_CACHE.get(key).execute(player, new String[]{});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放对应方块的打开音效(优先使用交互音效)
|
||||
*/
|
||||
private void playBlockOpenSound(Player player, String key) {
|
||||
org.bukkit.Sound sound = switch (key) {
|
||||
case "workbench" -> org.bukkit.Sound.BLOCK_WOOD_HIT;
|
||||
case "anvil" -> org.bukkit.Sound.BLOCK_ANVIL_USE;
|
||||
case "cartographytable" -> org.bukkit.Sound.UI_CARTOGRAPHY_TABLE_TAKE_RESULT;
|
||||
case "grindstone" -> org.bukkit.Sound.BLOCK_GRINDSTONE_USE;
|
||||
case "loom" -> org.bukkit.Sound.UI_LOOM_TAKE_RESULT;
|
||||
case "smithingtable" -> org.bukkit.Sound.BLOCK_SMITHING_TABLE_USE;
|
||||
case "stonecutter" -> org.bukkit.Sound.BLOCK_STONE_HIT;
|
||||
case "enderchest" -> org.bukkit.Sound.BLOCK_ENDER_CHEST_OPEN;
|
||||
default -> null;
|
||||
};
|
||||
|
||||
if (sound != null) {
|
||||
player.playSound(player.getLocation(), sound, 1.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换颜色代码 & -> §
|
||||
*/
|
||||
private String translateColor(String text) {
|
||||
return text.replace("&", "§");
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package cn.infstar.essentialsC.commands;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class EnchantingTableCommand extends BaseCommand {
|
||||
|
||||
public EnchantingTableCommand() {
|
||||
super("essentialsc.command.enchantingtable");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean execute(Player player, String[] args) {
|
||||
player.openEnchanting(null, true);
|
||||
player.sendMessage(getLang().getString("enchantingtable-opened"));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package cn.infstar.essentialsC.commands;
|
||||
|
||||
import cn.infstar.essentialsC.EssentialsC;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class EnderChestCommand extends BaseCommand {
|
||||
@@ -10,7 +11,18 @@ public class EnderChestCommand extends BaseCommand {
|
||||
|
||||
@Override
|
||||
protected boolean execute(Player player, String[] args) {
|
||||
// 打开玩家的末影箱(标题由客户端决定)
|
||||
EssentialsC plugin = EssentialsC.getInstance();
|
||||
|
||||
// 如果启用了 ProtocolLib,使用自定义标题
|
||||
if (plugin.isProtocolLibEnabled()) {
|
||||
// 从配置读取标题
|
||||
String title = plugin.getConfig().getString("enderchest.title", "&5随身末影箱");
|
||||
|
||||
// 标记下一个打开的 inventory 需要修改标题
|
||||
plugin.getInventoryTitleListener().markForTitleChange(player, title);
|
||||
}
|
||||
|
||||
// 直接打开玩家的末影箱(参考 EssentialsX 实现)
|
||||
player.openInventory(player.getEnderChest());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ public class HelpCommand extends BaseCommand implements TabCompleter {
|
||||
static {
|
||||
COMMAND_CACHE.put("workbench", new WorkbenchCommand());
|
||||
COMMAND_CACHE.put("anvil", new AnvilCommand());
|
||||
COMMAND_CACHE.put("enchantingtable", new EnchantingTableCommand());
|
||||
COMMAND_CACHE.put("cartographytable", new CartographyTableCommand());
|
||||
COMMAND_CACHE.put("grindstone", new GrindstoneCommand());
|
||||
COMMAND_CACHE.put("loom", new LoomCommand());
|
||||
@@ -36,7 +35,7 @@ public class HelpCommand extends BaseCommand implements TabCompleter {
|
||||
COMMAND_CACHE.put("seen", new SeenCommand());
|
||||
COMMAND_CACHE.put("feed", new FeedCommand());
|
||||
COMMAND_CACHE.put("repair", new RepairCommand());
|
||||
COMMAND_CACHE.put("admin", new AdminMenuCommand());
|
||||
COMMAND_CACHE.put("blocks", new BlocksMenuCommand());
|
||||
}
|
||||
|
||||
public HelpCommand() {
|
||||
@@ -49,14 +48,7 @@ public class HelpCommand extends BaseCommand implements TabCompleter {
|
||||
String subCommand = args[0].toLowerCase();
|
||||
|
||||
// 管理相关
|
||||
if (subCommand.equals("admin")) {
|
||||
if (!player.hasPermission("essentialsc.command.admin")) {
|
||||
player.sendMessage(getLang().getString("messages.no-permission"));
|
||||
return true;
|
||||
}
|
||||
COMMAND_CACHE.get("admin").execute(player, new String[]{});
|
||||
return true;
|
||||
} else if (subCommand.equals("reload")) {
|
||||
if (subCommand.equals("reload")) {
|
||||
if (!player.hasPermission("essentialsc.command.reload")) {
|
||||
player.sendMessage(getLang().getString("messages.no-permission"));
|
||||
return true;
|
||||
@@ -119,10 +111,6 @@ public class HelpCommand extends BaseCommand implements TabCompleter {
|
||||
blockCommands.append(lang.getString("help.commands.anvil")).append("\n");
|
||||
hasBlockCommands = true;
|
||||
}
|
||||
if (player.hasPermission("essentialsc.command.enchantingtable")) {
|
||||
blockCommands.append(lang.getString("help.commands.enchantingtable")).append("\n");
|
||||
hasBlockCommands = true;
|
||||
}
|
||||
if (player.hasPermission("essentialsc.command.cartographytable")) {
|
||||
blockCommands.append(lang.getString("help.commands.cartographytable")).append("\n");
|
||||
hasBlockCommands = true;
|
||||
@@ -182,10 +170,6 @@ public class HelpCommand extends BaseCommand implements TabCompleter {
|
||||
otherCommands.append(lang.getString("help.commands.seen")).append("\n");
|
||||
hasOtherCommands = true;
|
||||
}
|
||||
if (player.hasPermission("essentialsc.command.admin")) {
|
||||
otherCommands.append(lang.getString("help.commands.admin")).append("\n");
|
||||
hasOtherCommands = true;
|
||||
}
|
||||
|
||||
if (hasOtherCommands) {
|
||||
player.sendMessage(lang.getString("help.section-other"));
|
||||
@@ -203,7 +187,6 @@ public class HelpCommand extends BaseCommand implements TabCompleter {
|
||||
private String getActualCommand(String alias) {
|
||||
return switch (alias) {
|
||||
case "wb" -> "workbench";
|
||||
case "enchant", "et" -> "enchantingtable";
|
||||
case "cartography", "ct" -> "cartographytable";
|
||||
case "gs" -> "grindstone";
|
||||
case "smithing", "st" -> "smithingtable";
|
||||
@@ -231,14 +214,11 @@ public class HelpCommand extends BaseCommand implements TabCompleter {
|
||||
|
||||
// 所有可能的子命令及其权限(包括别名)
|
||||
String[][] subCommands = {
|
||||
{"admin", "essentialsc.command.admin"},
|
||||
{"reload", "essentialsc.command.reload"},
|
||||
{"blocks", "essentialsc.command.blocks"},
|
||||
{"workbench", "essentialsc.command.workbench"},
|
||||
{"wb", "essentialsc.command.workbench"},
|
||||
{"anvil", "essentialsc.command.anvil"},
|
||||
{"enchantingtable", "essentialsc.command.enchantingtable"},
|
||||
{"enchant", "essentialsc.command.enchantingtable"},
|
||||
{"et", "essentialsc.command.enchantingtable"},
|
||||
{"cartographytable", "essentialsc.command.cartographytable"},
|
||||
{"cartography", "essentialsc.command.cartographytable"},
|
||||
{"ct", "essentialsc.command.cartographytable"},
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
package cn.infstar.essentialsC.listeners;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.events.ListenerPriority;
|
||||
import com.comphenix.protocol.events.PacketAdapter;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.wrappers.WrappedChatComponent;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 使用 ProtocolLib 修改 Inventory 标题
|
||||
* 通过拦截 OPEN_WINDOW 数据包实现自定义标题
|
||||
*/
|
||||
public class InventoryTitleListener extends PacketAdapter {
|
||||
|
||||
// 存储需要修改标题的玩家和对应的新标题
|
||||
private final Map<UUID, String> pendingTitleChanges = new HashMap<>();
|
||||
|
||||
public InventoryTitleListener(Plugin plugin) {
|
||||
super(plugin, ListenerPriority.NORMAL, PacketType.Play.Server.OPEN_WINDOW);
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记玩家需要修改下一个打开的 inventory 标题
|
||||
* @param player 玩家
|
||||
* @param title 新标题(支持颜色代码)
|
||||
*/
|
||||
public void markForTitleChange(Player player, String title) {
|
||||
pendingTitleChanges.put(player.getUniqueId(), title);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSending(PacketEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
UUID playerId = player.getUniqueId();
|
||||
|
||||
// 检查该玩家是否有待处理的标题修改
|
||||
String newTitle = pendingTitleChanges.remove(playerId);
|
||||
if (newTitle == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
PacketContainer packet = event.getPacket();
|
||||
|
||||
// Paper 1.21+ 使用 WrappedChatComponent 作为标题
|
||||
// 将颜色代码 & 转换为 §
|
||||
String formattedTitle = newTitle.replace('&', '§');
|
||||
|
||||
// 创建聊天组件
|
||||
WrappedChatComponent titleComponent = WrappedChatComponent.fromText(formattedTitle);
|
||||
|
||||
// 修改数据包中的标题字段
|
||||
// 在 1.21+ 中,标题是第二个字段(索引1)
|
||||
packet.getChatComponents().write(0, titleComponent);
|
||||
|
||||
} catch (Exception e) {
|
||||
// 如果修改失败,记录错误但不影响正常流程
|
||||
Bukkit.getLogger().warning("[EssentialsC] 修改 inventory 标题失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理所有待处理的标题修改(防止内存泄漏)
|
||||
*/
|
||||
public void cleanup() {
|
||||
pendingTitleChanges.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
package cn.infstar.essentialsC.listeners;
|
||||
|
||||
import cn.infstar.essentialsC.EssentialsC;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.ShulkerBox;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.Action;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.event.inventory.InventoryCloseEvent;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.BlockStateMeta;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ShulkerBoxListener implements Listener {
|
||||
|
||||
private final EssentialsC plugin;
|
||||
// 存储玩家打开的潜影盒:玩家UUID -> (原始物品快照, 当前物品引用)
|
||||
private final Map<UUID, ShulkerBoxData> openShulkerBoxes = new HashMap<>();
|
||||
|
||||
// 预定义所有潜影盒材质(性能优化)
|
||||
private static final Set<Material> SHULKER_BOX_MATERIALS = Set.of(
|
||||
Material.SHULKER_BOX,
|
||||
Material.WHITE_SHULKER_BOX,
|
||||
Material.ORANGE_SHULKER_BOX,
|
||||
Material.MAGENTA_SHULKER_BOX,
|
||||
Material.LIGHT_BLUE_SHULKER_BOX,
|
||||
Material.YELLOW_SHULKER_BOX,
|
||||
Material.LIME_SHULKER_BOX,
|
||||
Material.PINK_SHULKER_BOX,
|
||||
Material.GRAY_SHULKER_BOX,
|
||||
Material.LIGHT_GRAY_SHULKER_BOX,
|
||||
Material.CYAN_SHULKER_BOX,
|
||||
Material.PURPLE_SHULKER_BOX,
|
||||
Material.BLUE_SHULKER_BOX,
|
||||
Material.BROWN_SHULKER_BOX,
|
||||
Material.GREEN_SHULKER_BOX,
|
||||
Material.RED_SHULKER_BOX,
|
||||
Material.BLACK_SHULKER_BOX
|
||||
);
|
||||
|
||||
/**
|
||||
* 潜影盒数据记录
|
||||
*/
|
||||
private static class ShulkerBoxData {
|
||||
ItemStack originalSnapshot; // 打开时的物品快照(用于验证)
|
||||
ItemStack currentItem; // 当前物品引用(用于更新)
|
||||
int totalItems; // 打开时的物品总数(用于防刷)
|
||||
|
||||
ShulkerBoxData(ItemStack snapshot, ItemStack current, int items) {
|
||||
this.originalSnapshot = snapshot;
|
||||
this.currentItem = current;
|
||||
this.totalItems = items;
|
||||
}
|
||||
}
|
||||
|
||||
public ShulkerBoxListener(EssentialsC plugin) {
|
||||
this.plugin = plugin;
|
||||
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerInteract(PlayerInteractEvent event) {
|
||||
// 只处理右键点击空气或方块的事件
|
||||
if (event.getAction() != Action.RIGHT_CLICK_AIR && event.getAction() != Action.RIGHT_CLICK_BLOCK) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = event.getPlayer();
|
||||
|
||||
// 检查权限
|
||||
if (!player.hasPermission("essentialsc.shulkerbox.open")) {
|
||||
return;
|
||||
}
|
||||
|
||||
ItemStack item = event.getItem();
|
||||
if (item == null || !isShulkerBox(item)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 只有潜行+右键才打开潜影盒
|
||||
if (!player.isSneaking()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 取消默认行为(防止放置潜影盒)
|
||||
event.setCancelled(true);
|
||||
|
||||
// 打开潜影盒
|
||||
openShulkerBox(player, item);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onInventoryClose(InventoryCloseEvent event) {
|
||||
if (!(event.getPlayer() instanceof Player player)) {
|
||||
return;
|
||||
}
|
||||
|
||||
UUID playerId = player.getUniqueId();
|
||||
ShulkerBoxData data = openShulkerBoxes.remove(playerId);
|
||||
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Inventory closedInventory = event.getInventory();
|
||||
ItemStack currentItem = data.currentItem;
|
||||
|
||||
// 验证物品是否还存在
|
||||
if (currentItem == null || currentItem.getType().isAir()) {
|
||||
// 物品已不存在,丢弃 inventory 中的所有物品
|
||||
for (ItemStack item : closedInventory.getContents()) {
|
||||
if (item != null && !item.getType().isAir()) {
|
||||
player.getWorld().dropItemNaturally(player.getLocation(), item);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新潜影盒物品中的内容
|
||||
if (currentItem.getItemMeta() instanceof BlockStateMeta blockStateMeta) {
|
||||
if (blockStateMeta.getBlockState() instanceof ShulkerBox shulkerBox) {
|
||||
// 将 inventory 的内容复制回潜影盒
|
||||
ItemStack[] contents = closedInventory.getContents();
|
||||
for (int i = 0; i < 27 && i < contents.length; i++) {
|
||||
shulkerBox.getInventory().setItem(i, contents[i]);
|
||||
}
|
||||
|
||||
// 更新物品元数据
|
||||
blockStateMeta.setBlockState(shulkerBox);
|
||||
currentItem.setItemMeta(blockStateMeta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onInventoryClick(InventoryClickEvent event) {
|
||||
if (!(event.getWhoClicked() instanceof Player player)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否是玩家打开的潜影盒(使用 get 避免两次查找)
|
||||
if (openShulkerBoxes.get(player.getUniqueId()) == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取点击的物品
|
||||
ItemStack clickedItem = event.getCurrentItem();
|
||||
if (clickedItem == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否是潜影盒,如果是则阻止放置
|
||||
if (isShulkerBox(clickedItem)) {
|
||||
event.setCancelled(true);
|
||||
player.sendMessage("§c不能在潜影盒中放入另一个潜影盒!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 检查物品是否为潜影盒(O(1) 时间复杂度)
|
||||
*/
|
||||
private boolean isShulkerBox(ItemStack item) {
|
||||
return SHULKER_BOX_MATERIALS.contains(item.getType());
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开潜影盒
|
||||
*/
|
||||
private void openShulkerBox(Player player, ItemStack shulkerBox) {
|
||||
// 获取潜影盒的 BlockStateMeta
|
||||
if (!(shulkerBox.getItemMeta() instanceof BlockStateMeta blockStateMeta)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取潜影盒的方块状态
|
||||
if (!(blockStateMeta.getBlockState() instanceof ShulkerBox shulkerBoxBlock)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建物品快照(用于后续验证)
|
||||
ItemStack snapshot = shulkerBox.clone();
|
||||
|
||||
// 计算当前物品总数(用于防刷检查)
|
||||
int totalItems = 0;
|
||||
for (ItemStack item : shulkerBoxBlock.getInventory().getContents()) {
|
||||
if (item != null && !item.getType().isAir()) {
|
||||
totalItems += item.getAmount();
|
||||
}
|
||||
}
|
||||
|
||||
// 获取潜影盒的自定义名称,如果没有则使用配置中的默认标题
|
||||
String title;
|
||||
if (shulkerBox.hasItemMeta() && shulkerBox.getItemMeta().hasDisplayName()) {
|
||||
// 使用潜影盒的自定义名称
|
||||
title = shulkerBox.getItemMeta().getDisplayName();
|
||||
} else {
|
||||
// 使用配置文件中的默认标题
|
||||
String defaultTitle = plugin.getConfig().getString("shulkerbox.default-title", "");
|
||||
if (defaultTitle != null && !defaultTitle.isEmpty()) {
|
||||
// 转换颜色代码 & -> §
|
||||
title = defaultTitle.replace('&', '§');
|
||||
} else {
|
||||
// 如果配置为空,使用 "Shulker Box"(客户端会自动翻译)
|
||||
title = "Shulker Box";
|
||||
}
|
||||
}
|
||||
|
||||
// 创建一个新的 inventory(基于潜影盒的内容)
|
||||
Inventory inventory = Bukkit.createInventory(null, 27, title);
|
||||
|
||||
// 复制潜影盒的内容到新 inventory
|
||||
ItemStack[] contents = shulkerBoxBlock.getInventory().getContents();
|
||||
for (int i = 0; i < 27 && i < contents.length; i++) {
|
||||
inventory.setItem(i, contents[i]);
|
||||
}
|
||||
|
||||
// 记录玩家打开的潜影盒(包含快照和当前引用)
|
||||
openShulkerBoxes.put(player.getUniqueId(), new ShulkerBoxData(snapshot, shulkerBox, totalItems));
|
||||
|
||||
// 打开 inventory
|
||||
player.openInventory(inventory);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user