违禁品面板与二手商店加固:物品名支持中文翻译、拦截SN/宝箱物品上架
Check Source / check (push) Successful in 3s
Auto Release / build (push) Successful in 2m27s
Simple Test / test (push) Successful in 2s
Test Env / test (push) Successful in 1s

- 新增 ItemNameResolver 翻译中间人:中文物品名(钻石/黑曜石等)与英文ID双向解析,
  支持唯一前缀匹配与提示回填
- 违禁品添加入口(面板+CLI)改用 ItemNameResolver,输入中文自动转标准英文ID入库,
  列表与面板展示中文化(附英文ID)
- 二手商店三处加固:发布、确认上架、购买时拦截 SN 绑定物品与 sdf1_game 宝箱
  自定义物品(§0§k CUSTOM lore),命中即拒绝/退还/自动下架退款
This commit is contained in:
youpaishidifu
2026-10-11 18:16:57 +08:00
parent f6ad2ca3f9
commit 0837386623
5 changed files with 462 additions and 12 deletions
+5 -3
View File
@@ -2085,8 +2085,10 @@ public class AreaCLIManager {
for (int i = 0; i < land.confiscateItems.size(); i++) { for (int i = 0; i < land.confiscateItems.size(); i++) {
final int idx = i; final int idx = i;
String item = land.confiscateItems.get(i); String item = land.confiscateItems.get(i);
org.bukkit.Material mat = org.bukkit.Material.matchMaterial(item);
String cn = mat != null ? " §7" + ItemNameResolver.toChinese(mat) : "";
p.sendMessage(Component.empty() p.sendMessage(Component.empty()
.append(Component.text("§f " + (i + 1) + ". " + item + " ")) .append(Component.text("§f " + (i + 1) + ". " + item + cn + " "))
.append(Component.text("§c[移除]") .append(Component.text("§c[移除]")
.hoverEvent(HoverEvent.showText(Component.text("§e点击移除该禁带物品"))) .hoverEvent(HoverEvent.showText(Component.text("§e点击移除该禁带物品")))
.clickEvent(ClickEvent.runCommand( .clickEvent(ClickEvent.runCommand(
@@ -2127,8 +2129,8 @@ public class AreaCLIManager {
if (!isOwner && !isAdmin) { p.sendMessage(Component.text("§c需要领地所有者或管理员权限")); return; } if (!isOwner && !isAdmin) { p.sendMessage(Component.text("§c需要领地所有者或管理员权限")); return; }
areaProtect.setPendingEffectInput(p.getUniqueId(), landName, "confiscate_add", 3); areaProtect.setPendingEffectInput(p.getUniqueId(), landName, "confiscate_add", 3);
p.sendMessage(header("添加禁带物品: " + landName)); p.sendMessage(header("添加禁带物品: " + landName));
p.sendMessage(Component.text("§e请在聊天栏输入物品ID(如 diamond、minecraft:gold_ingot)")); p.sendMessage(Component.text("§e请在聊天栏输入物品名 §7(支持中文,如 §f钻石、黑曜石§7)"));
p.sendMessage(Component.text("§7输入 §c取消 §7放弃添加")); p.sendMessage(Component.text("§7也可输入英文ID: §fdiamond、minecraft:gold_ingot§7;输入 §c取消 §7放弃添加"));
} }
/** /**
+5 -4
View File
@@ -1113,7 +1113,7 @@ public class AreaGUIManager implements Listener {
// 添加禁带物品 → 关闭GUI转聊天输入 // 添加禁带物品 → 关闭GUI转聊天输入
p.closeInventory(); p.closeInventory();
areaProtect.setPendingEffectInput(p.getUniqueId(), landName, "confiscate_add", 1); areaProtect.setPendingEffectInput(p.getUniqueId(), landName, "confiscate_add", 1);
p.sendMessage("§e§l[违禁品] §f请在聊天栏输入物品ID(如 diamond、gold_ingot),输入 §c取消 §7放弃"); p.sendMessage("§e§l[违禁品] §f请在聊天栏输入物品名(支持中文,如 §e钻石、黑曜石§f),输入 §c取消 §7放弃");
return; return;
} }
if (raw == 48) { if (raw == 48) {
@@ -2413,7 +2413,8 @@ public class AreaGUIManager implements Listener {
org.bukkit.Material mat = org.bukkit.Material.matchMaterial(item); org.bukkit.Material mat = org.bukkit.Material.matchMaterial(item);
inv.setItem(slots[i], createItem( inv.setItem(slots[i], createItem(
mat != null ? mat : Material.BARRIER, mat != null ? mat : Material.BARRIER,
"§c" + item, "§c" + ItemNameResolver.toChinese(mat),
"§7英文ID: §f" + item,
"§7访客禁止携带此物品", "§7访客禁止携带此物品",
"", "",
"§e点击移除")); "§e点击移除"));
@@ -2421,8 +2422,8 @@ public class AreaGUIManager implements Listener {
// 添加物品按钮(位置40) // 添加物品按钮(位置40)
inv.setItem(40, createItem(Material.EMERALD, "§a§l添加禁带物品", inv.setItem(40, createItem(Material.EMERALD, "§a§l添加禁带物品",
"§7在聊天栏输入物品ID", "§7在聊天栏输入物品名(支持中文)",
"§7示例: diamond、gold_ingot", "§7示例: 钻石、黑曜石、diamond",
"", "",
"§e点击添加")); "§e点击添加"));
+9 -5
View File
@@ -11865,11 +11865,13 @@ public class AreaProtection implements Listener {
pendingEffectInput.put(p.getUniqueId(), new String[]{landName, "confiscate_add"}); pendingEffectInput.put(p.getUniqueId(), new String[]{landName, "confiscate_add"});
return true; return true;
} }
// ★ 验证物品ID是否有效(Material.matchMaterial) // ★ 翻译中间人:支持中文名(钻石)与英文ID(diamond、minecraft:gold_ingot)(2026-10-11)
org.bukkit.Material mat = org.bukkit.Material.matchMaterial(itemId); StringBuilder cnHint = new StringBuilder();
org.bukkit.Material mat = ItemNameResolver.resolveWithHint(itemId, cnHint);
if (mat == null) { if (mat == null) {
p.sendMessage(Component.text("§c§l[添加禁带物品] §f无效的物品ID: §e" + itemId)); p.sendMessage(Component.text("§c§l[添加禁带物品] §f无效的物品: §e" + itemId));
p.sendMessage(Component.text("§7示例: diamond、minecraft:gold_ingot、netherite_sword")); p.sendMessage(Component.text("§7支持中文: 钻石、下界合金剑、黑曜石"));
p.sendMessage(Component.text("§7也支持英文: diamond、minecraft:gold_ingot、netherite_sword"));
pendingEffectInput.put(p.getUniqueId(), new String[]{landName, "confiscate_add"}); pendingEffectInput.put(p.getUniqueId(), new String[]{landName, "confiscate_add"});
return true; return true;
} }
@@ -11880,7 +11882,9 @@ public class AreaProtection implements Listener {
} else { } else {
land.confiscateItems.add(stdId); land.confiscateItems.add(stdId);
saveAreaToDb(land); saveAreaToDb(land);
p.sendMessage(Component.text("§a§l[添加禁带物品] §f已添加访客禁带物品: §e" + stdId)); String matched = cnHint.length() > 0
? " §7(匹配到: " + cnHint + ")" : "";
p.sendMessage(Component.text("§a§l[添加禁带物品] §f已添加访客禁带物品: §e" + stdId + matched));
} }
if (plugin.areaCLIManager != null) plugin.areaCLIManager.showConfiscateManagement(p, landName); if (plugin.areaCLIManager != null) plugin.areaCLIManager.showConfiscateManagement(p, landName);
return true; return true;
@@ -0,0 +1,375 @@
package Sdf1_login;
import org.bukkit.Material;
import java.util.HashMap;
import java.util.Map;
/**
* 物品名翻译中间人(2026-10-11)。
* <p>
* 玩家在聊天栏输入物品 ID 时,不是人人都能背下 MC 每个物品的英文 ID。
* 本类把中文名(如「钻石」)翻译为标准英文 Material 名(如 diamond),
* 入库统一用英文标准名,回收/匹配时按英文名比对。
* <p>
* 解析顺序:
* 1. 英文直通(Material.matchMaterial,支持 minecraft: 前缀与 legacy 名)
* 2. 中文精确查表
* 3. 归一化后中文查表(去空格、「minecraft:」前缀、大小写不敏感)
* 4. 唯一前缀匹配(输入「钻」唯一命中「钻石」时可用;多候选则拒绝并提示)
*/
public final class ItemNameResolver {
private ItemNameResolver() {}
/** 中文名 → 英文 Material 名(大写)。与 Sdf1_game 寻宝 ITEM_CN 口径对齐。 */
private static final Map<String, String> CN2EN = new HashMap<>();
static {
// ===== 货币/矿物/锭 =====
CN2EN.put("钻石", "DIAMOND");
CN2EN.put("绿宝石", "EMERALD");
CN2EN.put("铁锭", "IRON_INGOT");
CN2EN.put("金锭", "GOLD_INGOT");
CN2EN.put("铜锭", "COPPER_INGOT");
CN2EN.put("下界合金锭", "NETHERITE_INGOT");
CN2EN.put("下界合金碎片", "NETHERITE_SCRAP");
CN2EN.put("铁粒", "IRON_NUGGET");
CN2EN.put("金粒", "GOLD_NUGGET");
CN2EN.put("煤炭", "COAL");
CN2EN.put("木炭", "CHARCOAL");
CN2EN.put("红石", "REDSTONE");
CN2EN.put("红石粉", "REDSTONE");
CN2EN.put("青金石", "LAPIS_LAZULI");
CN2EN.put("石英", "QUARTZ");
CN2EN.put("远古残骸", "ANCIENT_DEBRIS");
CN2EN.put("钻石矿石", "DIAMOND_ORE");
CN2EN.put("绿宝石矿石", "EMERALD_ORE");
CN2EN.put("金矿石", "GOLD_ORE");
CN2EN.put("铁矿石", "IRON_ORE");
CN2EN.put("煤矿石", "COAL_ORE");
CN2EN.put("红石矿石", "REDSTONE_ORE");
CN2EN.put("下界金矿石", "NETHER_GOLD_ORE");
// ===== 木材 =====
CN2EN.put("木棍", "STICK");
CN2EN.put("橡木木板", "OAK_PLANKS");
CN2EN.put("云杉木板", "SPRUCE_PLANKS");
CN2EN.put("白桦木板", "BIRCH_PLANKS");
CN2EN.put("丛林木板", "JUNGLE_PLANKS");
CN2EN.put("金合欢木板", "ACACIA_PLANKS");
CN2EN.put("深色橡木木板", "DARK_OAK_PLANKS");
CN2EN.put("红树木板", "MANGROVE_PLANKS");
CN2EN.put("樱花木板", "CHERRY_PLANKS");
CN2EN.put("竹板", "BAMBOO_PLANKS");
CN2EN.put("橡木原木", "OAK_LOG");
CN2EN.put("云杉原木", "SPRUCE_LOG");
CN2EN.put("白桦原木", "BIRCH_LOG");
CN2EN.put("丛林原木", "JUNGLE_LOG");
CN2EN.put("金合欢原木", "ACACIA_LOG");
CN2EN.put("深色橡木原木", "DARK_OAK_LOG");
CN2EN.put("红树木", "MANGROVE_LOG");
CN2EN.put("樱花木", "CHERRY_LOG");
CN2EN.put("诡异菌柄", "WARPED_STEM");
CN2EN.put("绯红菌柄", "CRIMSON_STEM");
// ===== 工具 =====
CN2EN.put("木镐", "WOODEN_PICKAXE");
CN2EN.put("石镐", "STONE_PICKAXE");
CN2EN.put("铁镐", "IRON_PICKAXE");
CN2EN.put("金镐", "GOLDEN_PICKAXE");
CN2EN.put("钻石镐", "DIAMOND_PICKAXE");
CN2EN.put("下界合金镐", "NETHERITE_PICKAXE");
CN2EN.put("木锹", "WOODEN_SHOVEL");
CN2EN.put("石锹", "STONE_SHOVEL");
CN2EN.put("铁锹", "IRON_SHOVEL");
CN2EN.put("金锹", "GOLDEN_SHOVEL");
CN2EN.put("钻石锹", "DIAMOND_SHOVEL");
CN2EN.put("下界合金锹", "NETHERITE_SHOVEL");
CN2EN.put("木斧", "WOODEN_AXE");
CN2EN.put("石斧", "STONE_AXE");
CN2EN.put("铁斧", "IRON_AXE");
CN2EN.put("金斧", "GOLDEN_AXE");
CN2EN.put("钻石斧", "DIAMOND_AXE");
CN2EN.put("下界合金斧", "NETHERITE_AXE");
CN2EN.put("木锄", "WOODEN_HOE");
CN2EN.put("石锄", "STONE_HOE");
CN2EN.put("铁锄", "IRON_HOE");
CN2EN.put("金锄", "GOLDEN_HOE");
CN2EN.put("钻石锄", "DIAMOND_HOE");
CN2EN.put("下界合金锄", "NETHERITE_HOE");
CN2EN.put("剪刀", "SHEARS");
CN2EN.put("钓鱼竿", "FISHING_ROD");
CN2EN.put("打火石", "FLINT_AND_STEEL");
CN2EN.put("燧石", "FLINT");
CN2EN.put("指南针", "COMPASS");
CN2EN.put("时钟", "CLOCK");
CN2EN.put("望远镜", "SPYGLASS");
CN2EN.put("刷怪蛋", "SPAWN_EGG");
// ===== 武器 =====
CN2EN.put("木剑", "WOODEN_SWORD");
CN2EN.put("石剑", "STONE_SWORD");
CN2EN.put("铁剑", "IRON_SWORD");
CN2EN.put("金剑", "GOLDEN_SWORD");
CN2EN.put("钻石剑", "DIAMOND_SWORD");
CN2EN.put("下界合金剑", "NETHERITE_SWORD");
CN2EN.put("弓", "BOW");
CN2EN.put("箭", "ARROW");
CN2EN.put("箭矢", "ARROW");
CN2EN.put("十字弩", "CROSSBOW");
CN2EN.put("三叉戟", "TRIDENT");
CN2EN.put("盾牌", "SHIELD");
CN2EN.put("烈焰棒", "BLAZE_ROD");
CN2EN.put("旋风棒", "BREEZE_ROD");
CN2EN.put("旋风之杖", "BREEZE_ROD");
// ===== 防具 =====
CN2EN.put("皮革头盔", "LEATHER_HELMET");
CN2EN.put("皮革外套", "LEATHER_CHESTPLATE");
CN2EN.put("皮革护腿", "LEATHER_LEGGINGS");
CN2EN.put("皮革靴子", "LEATHER_BOOTS");
CN2EN.put("锁链头盔", "CHAINMAIL_HELMET");
CN2EN.put("锁链胸甲", "CHAINMAIL_CHESTPLATE");
CN2EN.put("锁链护腿", "CHAINMAIL_LEGGINGS");
CN2EN.put("锁链靴子", "CHAINMAIL_BOOTS");
CN2EN.put("铁头盔", "IRON_HELMET");
CN2EN.put("铁胸甲", "IRON_CHESTPLATE");
CN2EN.put("铁护腿", "IRON_LEGGINGS");
CN2EN.put("铁靴子", "IRON_BOOTS");
CN2EN.put("金头盔", "GOLDEN_HELMET");
CN2EN.put("金胸甲", "GOLDEN_CHESTPLATE");
CN2EN.put("金护腿", "GOLDEN_LEGGINGS");
CN2EN.put("金靴子", "GOLDEN_BOOTS");
CN2EN.put("钻石头盔", "DIAMOND_HELMET");
CN2EN.put("钻石胸甲", "DIAMOND_CHESTPLATE");
CN2EN.put("钻石护甲", "DIAMOND_CHESTPLATE");
CN2EN.put("钻石护腿", "DIAMOND_LEGGINGS");
CN2EN.put("钻石靴子", "DIAMOND_BOOTS");
CN2EN.put("下界合金头盔", "NETHERITE_HELMET");
CN2EN.put("下界合金胸甲", "NETHERITE_CHESTPLATE");
CN2EN.put("下界合金护腿", "NETHERITE_LEGGINGS");
CN2EN.put("下界合金靴子", "NETHERITE_BOOTS");
CN2EN.put("钻石头盔", "DIAMOND_HELMET");
CN2EN.put("鞘翅", "ELYTRA");
CN2EN.put("不死图腾", "TOTEM_OF_UNDYING");
CN2EN.put("图腾", "TOTEM_OF_UNDYING");
// ===== 食物 =====
CN2EN.put("苹果", "APPLE");
CN2EN.put("金苹果", "GOLDEN_APPLE");
CN2EN.put("附魔金苹果", "ENCHANTED_GOLDEN_APPLE");
CN2EN.put("面包", "BREAD");
CN2EN.put("熟牛排", "COOKED_BEEF");
CN2EN.put("牛排", "COOKED_BEEF");
CN2EN.put("生猪排", "PORKCHOP");
CN2EN.put("熟猪排", "COOKED_PORKCHOP");
CN2EN.put("生鸡肉", "CHICKEN");
CN2EN.put("熟鸡肉", "COOKED_CHICKEN");
CN2EN.put("生羊肉", "MUTTON");
CN2EN.put("熟羊肉", "COOKED_MUTTON");
CN2EN.put("生牛肉", "BEEF");
CN2EN.put("曲奇", "COOKIE");
CN2EN.put("蛋糕", "CAKE");
CN2EN.put("胡萝卜", "CARROT");
CN2EN.put("马铃薯", "POTATO");
CN2EN.put("烤马铃薯", "BAKED_POTATO");
CN2EN.put("西瓜片", "MELON_SLICE");
CN2EN.put("南瓜派", "PUMPKIN_PIE");
CN2EN.put("糖", "SUGAR");
CN2EN.put("碗", "BOWL");
CN2EN.put("蘑菇煲", "MUSHROOM_STEW");
CN2EN.put("甜浆果", "SWEET_BERRIES");
CN2EN.put("蜂蜜瓶", "HONEY_BOTTLE");
// ===== 建筑/常见方块 =====
CN2EN.put("石头", "STONE");
CN2EN.put("圆石", "COBBLESTONE");
CN2EN.put("泥土", "DIRT");
CN2EN.put("草方块", "GRASS_BLOCK");
CN2EN.put("沙子", "SAND");
CN2EN.put("红沙", "RED_SAND");
CN2EN.put("砂砾", "GRAVEL");
CN2EN.put("粘土球", "CLAY_BALL");
CN2EN.put("粘土", "CLAY");
CN2EN.put("雪块", "SNOW_BLOCK");
CN2EN.put("冰", "ICE");
CN2EN.put("黑曜石", "OBSIDIAN");
CN2EN.put("下界砖", "NETHER_BRICKS");
CN2EN.put("萤石", "GLOWSTONE");
CN2EN.put("萤石粉", "GLOWSTONE_DUST");
CN2EN.put("海晶石", "PRISMARINE");
CN2EN.put("工作台", "CRAFTING_TABLE");
CN2EN.put("熔炉", "FURNACE");
CN2EN.put("箱子", "CHEST");
CN2EN.put("木桶", "BARREL");
CN2EN.put("末影箱", "ENDER_CHEST");
CN2EN.put("潜影盒", "SHULKER_BOX");
CN2EN.put("铁砧", "ANVIL");
CN2EN.put("附魔台", "ENCHANTING_TABLE");
CN2EN.put("酿造台", "BREWING_STAND");
CN2EN.put("信标", "BEACON");
CN2EN.put("床", "RED_BED");
CN2EN.put("红色床", "RED_BED");
CN2EN.put("门", "OAK_DOOR");
CN2EN.put("橡木门", "OAK_DOOR");
CN2EN.put("活塞", "PISTON");
CN2EN.put("粘性活塞", "STICKY_PISTON");
CN2EN.put("红石火把", "REDSTONE_TORCH");
CN2EN.put("拉杆", "LEVER");
CN2EN.put("按钮", "STONE_BUTTON");
CN2EN.put("压力板", "STONE_PRESSURE_PLATE");
CN2EN.put("铁块", "IRON_BLOCK");
CN2EN.put("金块", "GOLD_BLOCK");
CN2EN.put("钻石块", "DIAMOND_BLOCK");
CN2EN.put("绿宝石块", "EMERALD_BLOCK");
CN2EN.put("下界合金块", "NETHERITE_BLOCK");
CN2EN.put("TNT", "TNT");
CN2EN.put("苦力怕头颅", "CREEPER_HEAD");
CN2EN.put("爬行者头颅", "CREEPER_HEAD");
CN2EN.put("骷髅头颅", "SKELETON_SKULL");
CN2EN.put("僵尸头颅", "ZOMBIE_HEAD");
CN2EN.put("龙蛋", "DRAGON_EGG");
// ===== 材料/杂项 =====
CN2EN.put("末影珍珠", "ENDER_PEARL");
CN2EN.put("末影之眼", "ENDER_EYE");
CN2EN.put("恶魂之泪", "GHAST_TEAR");
CN2EN.put("潜影壳", "SHULKER_SHELL");
CN2EN.put("粘液球", "SLIME_BALL");
CN2EN.put("粘液块", "SLIME_BLOCK");
CN2EN.put("烈焰粉", "BLAZE_POWDER");
CN2EN.put("岩浆膏", "MAGMA_CREAM");
CN2EN.put("熔浆膏", "MAGMA_CREAM");
CN2EN.put("下界疣", "NETHER_WART");
CN2EN.put("地狱疣", "NETHER_WART");
CN2EN.put("发酵蛛眼", "FERMENTED_SPIDER_EYE");
CN2EN.put("蜘蛛眼", "SPIDER_EYE");
CN2EN.put("玻璃瓶", "GLASS_BOTTLE");
CN2EN.put("水瓶", "POTION");
CN2EN.put("药水", "POTION");
CN2EN.put("喷溅型药水", "SPLASH_POTION");
CN2EN.put("滞留型药水", "LINGERING_POTION");
CN2EN.put("附魔书", "ENCHANTED_BOOK");
CN2EN.put("书", "BOOK");
CN2EN.put("皮革", "LEATHER");
CN2EN.put("羽毛", "FEATHER");
CN2EN.put("线", "STRING");
CN2EN.put("火药", "GUNPOWDER");
CN2EN.put("小麦", "WHEAT");
CN2EN.put("种子", "WHEAT_SEEDS");
CN2EN.put("树苗", "OAK_SAPLING");
CN2EN.put("仙人掌", "CACTUS");
CN2EN.put("甘蔗", "SUGAR_CANE");
CN2EN.put("南瓜", "PUMPKIN");
CN2EN.put("西瓜", "MELON");
CN2EN.put("海带", "KELP");
CN2EN.put("海带干", "DRIED_KELP");
CN2EN.put("经验瓶", "EXPERIENCE_BOTTLE");
CN2EN.put("烟花火箭", "FIREWORK_ROCKET");
CN2EN.put("火焰弹", "FIRE_CHARGE");
CN2EN.put("告示牌", "OAK_SIGN");
CN2EN.put("旗帜", "WHITE_BANNER");
CN2EN.put("物品展示框", "ITEM_FRAME");
CN2EN.put("盔甲架", "ARMOR_STAND");
CN2EN.put("矿车", "MINECART");
CN2EN.put("船", "OAK_BOAT");
CN2EN.put("鞍", "SADDLE");
CN2EN.put("马铠", "IRON_HORSE_ARMOR");
CN2EN.put("铁马铠", "IRON_HORSE_ARMOR");
CN2EN.put("金马铠", "GOLDEN_HORSE_ARMOR");
CN2EN.put("钻石马铠", "DIAMOND_HORSE_ARMOR");
}
/**
* 解析玩家输入为 Material。
*
* @param input 玩家输入(英文 ID / minecraft: 前缀 / 中文名)
* @return Material;无法解析返回 null
*/
public static Material resolve(String input) {
if (input == null) return null;
String raw = input.trim();
if (raw.isEmpty()) return null;
// 1. 英文直通(含 minecraft: 前缀与 legacy 名)
Material m = Material.matchMaterial(raw);
if (m != null) return m;
// 2. 中文精确查表
String en = CN2EN.get(raw);
if (en != null) {
m = Material.matchMaterial(en);
if (m != null) return m;
}
// 3. 归一化后再查(去空格、去 minecraft: 前缀、小写)
String norm = raw.replace(" ", "").toLowerCase();
if (!norm.equals(raw)) {
m = Material.matchMaterial(norm);
if (m != null) return m;
en = CN2EN.get(norm);
if (en != null) {
m = Material.matchMaterial(en);
if (m != null) return m;
}
}
// 4. 唯一前缀匹配(输入「钻」时,若表内只有一个候选以它开头才放行)
String cnNorm = raw.replace(" ", "");
String firstKey = null, firstVal = null;
int hits = 0;
for (Map.Entry<String, String> e : CN2EN.entrySet()) {
if (e.getKey().startsWith(cnNorm)) {
hits++;
if (hits == 1) { firstKey = e.getKey(); firstVal = e.getValue(); }
}
}
if (hits == 1 && firstVal != null) {
m = Material.matchMaterial(firstVal);
if (m != null) {
// 提示玩家实际匹配到的完整名
return m;
}
}
return null;
}
/** 解析并附带「实际匹配到的中文名」用于提示(如输入「钻」→ 显示「钻石」)。 */
public static Material resolveWithHint(String input, StringBuilder hintOut) {
if (input == null) return null;
String raw = input.trim();
// 唯一前缀命中时把完整中文名写进 hint
String cnNorm = raw.replace(" ", "");
String firstKey = null;
int hits = 0;
for (String k : CN2EN.keySet()) {
if (k.startsWith(cnNorm) && !k.equals(cnNorm)) {
hits++;
if (hits == 1) firstKey = k;
}
}
if (hits == 1 && firstKey != null && hintOut != null) {
hintOut.append(firstKey);
}
return resolve(input);
}
/** 英文标准名 → 中文显示名(找不到回退为原名)。 */
public static String toChinese(Material mat) {
if (mat == null) return "";
String name = mat.name();
for (Map.Entry<String, String> e : CN2EN.entrySet()) {
if (e.getValue().equals(name)) return e.getKey();
}
// 回退:下划线转空格的 Title Case
String[] parts = name.split("_");
StringBuilder sb = new StringBuilder();
for (String part : parts) {
if (sb.length() > 0) sb.append(" ");
sb.append(part.substring(0, 1).toUpperCase())
.append(part.substring(1).toLowerCase());
}
return sb.toString();
}
}
+68
View File
@@ -3708,6 +3708,41 @@ public class ShopManager implements Listener {
return secondHandPages.getOrDefault(uuid, 0); return secondHandPages.getOrDefault(uuid, 0);
} }
/**
* 二手商店禁售判定(2026-10-11)。
* 严禁上架:① SN 自定义物品(PDC sdf1_sn / 4 类管控物品)
* ② Sdf1_game 宝箱物品(lore 含 §0§k / CUSTOM 隐藏标记)
* @return 不可上架的原因;null 表示可上架
*/
private String secondHandBlockReason(ItemStack item) {
if (item == null || item.getType() == Material.AIR) return null;
// ① SN 物品
try {
if (plugin.getSnManager() != null
&& plugin.getSnManager().isSnItem(item)) {
return "SN 绑定物品不可交易";
}
} catch (Throwable ignored) {
}
// ② Sdf1_game 宝箱物品(lore 隐藏标记 §0§kCUSTOM|...)
if (item.hasItemMeta()) {
ItemMeta meta = item.getItemMeta();
if (meta != null && meta.hasLore()) {
java.util.List<String> lore = meta.getLore();
if (lore != null) {
for (String line : lore) {
if (line == null) continue;
if (line.contains("§0§k")
|| line.contains("CUSTOM")) {
return "宝箱自定义物品不可交易";
}
}
}
}
}
return null;
}
// ===== 发布:拿主手物品 → 聊天输入售价 ===== // ===== 发布:拿主手物品 → 聊天输入售价 =====
public void startSecondHandPublish(Player p) { public void startSecondHandPublish(Player p) {
if (plugin.getBonds().isFrozen(p.getName())) { if (plugin.getBonds().isFrozen(p.getName())) {
@@ -3728,6 +3763,12 @@ public class ShopManager implements Listener {
p.sendMessage("§c§l[二手商店] §f请先手持要上架的物品"); p.sendMessage("§c§l[二手商店] §f请先手持要上架的物品");
return; return;
} }
// ★ 严禁上架 SN 自定义物品 / Sdf1_game 宝箱物品(2026-10-11)
String blocked = secondHandBlockReason(hand);
if (blocked != null) {
p.sendMessage("§c§l[二手商店] §f该物品不可上架: " + blocked);
return;
}
// 暂存主手物品并清空 // 暂存主手物品并清空
pendingPublishItem.put(p.getUniqueId(), hand.clone()); pendingPublishItem.put(p.getUniqueId(), hand.clone());
p.getInventory().setItemInMainHand(null); p.getInventory().setItemInMainHand(null);
@@ -3781,6 +3822,14 @@ public class ShopManager implements Listener {
} }
if (price < 0) price = 0; // 封底 0 债券 if (price < 0) price = 0; // 封底 0 债券
// ★ 入库前二次校验:SN / 宝箱物品严禁上架(2026-10-11)
String blocked2 = secondHandBlockReason(item);
if (blocked2 != null) {
returnItem(p, item);
p.sendMessage("§c[二手商店] 该物品不可上架: " + blocked2 + ",物品已退还");
return;
}
SecondHandListing l = new SecondHandListing(); SecondHandListing l = new SecondHandListing();
l.id = nextSecondHandId(); l.id = nextSecondHandId();
l.seller = p.getName(); l.seller = p.getName();
@@ -3856,6 +3905,25 @@ public class ShopManager implements Listener {
saveSecondHand(); saveSecondHand();
return; return;
} }
// ★ 防御历史脏数据:SN / 宝箱物品即使已挂出也不可成交(2026-10-11)
String blockedBuy = secondHandBlockReason(is);
if (blockedBuy != null) {
if (l.price > 0) {
plugin.getBonds().addBonds(
p.getName(), l.price, "shop_secondhand_refund",
l.seller, "二手商店", "违禁商品下架退款#" + l.id);
}
secondHandListings.remove(l);
saveSecondHand();
p.sendMessage("§c§l[二手商店] §f该商品为违禁物品(" + blockedBuy + "),已自动下架并退款");
Player seller = Bukkit.getPlayer(l.seller);
if (seller != null && seller.isOnline()) {
seller.sendMessage("§c§l[二手商店] §f你的商品 #" + l.id + " 含违禁物品("
+ blockedBuy + "),已自动下架");
}
openSecondHandShop(p, 0);
return;
}
HashMap<Integer, ItemStack> left = p.getInventory().addItem(is); HashMap<Integer, ItemStack> left = p.getInventory().addItem(is);
for (ItemStack drop : left.values()) { for (ItemStack drop : left.values()) {
p.getWorld().dropItemNaturally(p.getLocation(), drop); p.getWorld().dropItemNaturally(p.getLocation(), drop);