feat: 添加潜影盒快捷打开和末影箱自定义标题功能

This commit is contained in:
Coldsmile_7
2026-04-15 00:55:31 +08:00
parent f6364ac36b
commit cc07647551
62 changed files with 15171 additions and 304 deletions

View File

@@ -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);
}
}
}

View File

@@ -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; // 找到后退出循环
}
}
}

View File

@@ -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;
}
}

View File

@@ -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("&", "§");
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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"},

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -10,51 +10,78 @@ settings:
# 启用或禁用命令反馈消息
enable-feedback: true
# 管理员菜单配置
admin-menu:
title: "&6EssentialsC 管理菜单"
# 功能方块菜单配置
blocks-menu:
title: "&6&lEssentialsC &8- &e&l功能方块菜单"
items:
time-control:
workbench:
slot: 10
material: CLOCK
name: "&e时间控制"
material: CRAFTING_TABLE
name: "&e工作台"
lore:
- "&7左键: 设为白天"
- "&7右键: 设为夜晚"
weather-control:
- "&7/workbench"
- "&7打开工作台"
permission: essentialsc.command.workbench
anvil:
slot: 11
material: SUNFLOWER
name: "&e天气控制"
lore:
- "&7左键: 晴天"
- "&7右键: 雨天"
heal-self:
slot: 13
material: GOLDEN_APPLE
name: "&a治疗自己"
lore:
- "&7补满生命值和饱食度"
feed-self:
slot: 14
material: BREAD
name: "&6喂饱自己"
lore:
- "&7补满饱食度"
repair-hand:
slot: 15
material: ANVIL
name: "&b修复手持物品"
name: "&e铁砧"
lore:
- "&7修复当前手持物品"
vanish:
- "&7/anvil"
- "&7打开铁砧"
permission: essentialsc.command.anvil
cartographytable:
slot: 19
material: CARTOGRAPHY_TABLE
name: "&e制图台"
lore:
- "&7/cartographytable"
- "&7打开制图台"
permission: essentialsc.command.cartographytable
grindstone:
slot: 20
material: GRINDSTONE
name: "&e砂轮"
lore:
- "&7/grindstone"
- "&7打开砂轮"
permission: essentialsc.command.grindstone
loom:
slot: 21
material: ENDER_PEARL
name: "&d隐身模式"
material: LOOM
name: "&e织布机"
lore:
- "&7点击切换隐身状态"
reload:
- "&7/loom"
- "&7打开织布机"
permission: essentialsc.command.loom
smithingtable:
slot: 22
material: BOOK
name: "&c重载配置"
material: SMITHING_TABLE
name: "&e锻造台"
lore:
- "&7重新加载配置文件"
- "&7/smithingtable"
- "&7打开锻造台"
permission: essentialsc.command.smithingtable
stonecutter:
slot: 23
material: STONECUTTER
name: "&e切石机"
lore:
- "&7/stonecutter"
- "&7打开切石机"
permission: essentialsc.command.stonecutter
enderchest:
slot: 31
material: ENDER_CHEST
name: "&e末影箱"
lore:
- "&7/enderchest"
- "&7打开末影箱"
permission: essentialsc.command.enderchest
# 潜影盒设置
shulkerbox:
# 潜影盒默认标题(当潜影盒没有自定义名称时使用)
# 支持颜色代码(使用 & 符号)
# 留空则使用 "Shulker Box"(客户端语言)
default-title: "&e潜影盒"

View File

@@ -17,12 +17,6 @@ messages:
vanish-enabled: "&aYou are now vanished!"
vanish-disabled: "&cYou are no longer vanished!"
seen-usage: "&cUsage: /seen <player>"
admin-time-set: "&aTime set!"
admin-weather-set: "&aWeather set!"
admin-heal-success: "&aHealed!"
admin-feed-success: "&aFed!"
admin-repair-success: "&aRepaired!"
admin-reload-success: "&aConfig reloaded!"
anvil-opened: "&aAnvil opened!"
enchantingtable-opened: "&aEnchanting table opened!"
heal-self: "&aYour health and hunger have been restored!"
@@ -64,6 +58,5 @@ help:
heal: " &f/heal &7- Restore health and hunger"
vanish: " &f/vanish &7- Toggle vanish mode"
seen: " &f/seen &7- View player information"
admin: " &f/admin &7- Open admin menu"
feed: " &f/feed &7- Restore hunger"
repair: " &f/repair &7- Repair hand or all items"

View File

@@ -17,13 +17,6 @@ messages:
vanish-enabled: "&a你已进入隐身模式"
vanish-disabled: "&c你已退出隐身模式"
seen-usage: "&c用法: /seen <玩家名>"
# 管理员菜单操作提示
admin-time-set: "&a时间已设置"
admin-weather-set: "&a天气已设置"
admin-heal-success: "&a已治疗"
admin-feed-success: "&a已喂饱"
admin-repair-success: "&a已修复"
admin-reload-success: "&a配置已重载"
anvil-opened: "&a已打开铁砧"
enchantingtable-opened: "&a已打开附魔台"
heal-self: "&a你的生命值和饱食度已补满"
@@ -65,6 +58,6 @@ help:
heal: " &f/heal &7- 恢复生命值和饱食度"
vanish: " &f/vanish &7- 切换隐身模式"
seen: " &f/seen &7- 查看玩家信息"
admin: " &f/admin &7- 打开管理菜单"
feed: " &f/feed &7- 补满饱食度"
repair: " &f/repair &7- 修复手中或所有物品"
blocks: " &f/blocks &7- 打开功能方块菜单"

View File

@@ -55,21 +55,24 @@ permissions:
essentialsc.command.seen:
description: Allows use of /seen command
default: op
essentialsc.command.admin:
description: Allows use of /admin command
default: op
essentialsc.command.feed:
description: Allows use of /feed command
default: op
essentialsc.command.repair:
description: Allows use of /repair command
default: op
essentialsc.command.blocks:
description: Allows use of /essc blocks command
default: true
essentialsc.command.help:
description: Allows use of /essentialsc help command
default: true
essentialsc.command.reload:
description: Allows use of /essc reload command
default: op
essentialsc.shulkerbox.open:
description: Allows right-click to open shulker boxes without placing them
default: op
essentialsc.*:
description: All EssentialsC permissions
default: false
@@ -89,8 +92,8 @@ permissions:
essentialsc.command.heal: true
essentialsc.command.vanish: true
essentialsc.command.seen: true
essentialsc.command.admin: true
essentialsc.command.reload: true
essentialsc.command.feed: true
essentialsc.command.repair: true
essentialsc.command.help: true
essentialsc.shulkerbox.open: true