修复PVP地形被切小岛/出生点干预、药水NBT缺失,以及商店附魔书NBT解析
- PVPArenaManager: 删除forceTerraformSpawnArea等184行小岛死代码;新增deleteExistingPVPWorldFolder确保每次进入都是自然生成的纯净主世界(彻底消除旧版setSpawnLocation/小岛误加载);pvpEquipment与makePotion改用PotionMeta写入INSTANT_HEALTH真实治疗NBT(1.21.4 PotionType.HEALING) - ShopManager: 新增ENCHANT_MAP与inferEnchantedBook,按中文名解析附魔等级并写入EnchantmentStorageMeta,商店附魔书现在带正确附魔NBT
This commit is contained in:
@@ -14,6 +14,7 @@ import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.inventory.meta.PotionMeta;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
import org.bukkit.util.io.BukkitObjectInputStream;
|
||||
@@ -175,12 +176,18 @@ public class PVPArenaManager implements Listener {
|
||||
// 金苹果 x 8
|
||||
pvpEquipment.add(new ItemStack(Material.GOLDEN_APPLE, 8));
|
||||
|
||||
// 药水
|
||||
// 药水(带真实治疗效果 NBT)
|
||||
ItemStack potion = new ItemStack(Material.POTION, 3);
|
||||
ItemMeta potionMeta = potion.getItemMeta();
|
||||
if (potionMeta != null) {
|
||||
potionMeta.setDisplayName("§a§l瞬间治疗药水");
|
||||
potion.setItemMeta(potionMeta);
|
||||
if (potionMeta instanceof PotionMeta) {
|
||||
PotionMeta pMeta = (PotionMeta) potionMeta;
|
||||
pMeta.setDisplayName("§a§l瞬间治疗药水");
|
||||
pMeta.addCustomEffect(new org.bukkit.potion.PotionEffect(
|
||||
org.bukkit.potion.PotionEffectType.INSTANT_HEALTH,
|
||||
1, 0, false, false), true);
|
||||
pMeta.setBasePotionData(new org.bukkit.potion.PotionData(
|
||||
org.bukkit.potion.PotionType.HEALING, false, false));
|
||||
potion.setItemMeta(pMeta);
|
||||
}
|
||||
pvpEquipment.add(potion);
|
||||
}
|
||||
@@ -237,6 +244,11 @@ public class PVPArenaManager implements Listener {
|
||||
// ★ 不设 seed:让 MC 自行随机决定地形种子(插件对地形零干预)。
|
||||
// 创建后通过 getSeed() 读取 MC 选定的种子并记录日志,便于排查"出生点脚下是水"的失败种子。
|
||||
|
||||
// ★ 彻底清理磁盘上可能残留的旧世界目录:旧版本曾干预出生点(setSpawnLocation)并强制造陆,
|
||||
// 若上次删除因 Windows 文件锁失败,createWorld 会误加载到旧世界(出生点被锁定/小岛地形)。
|
||||
// 这里重试删除确保目录消失,保证每次进入都是 MC 自然生成的全新主世界与自然出生点。
|
||||
deleteExistingPVPWorldFolder();
|
||||
|
||||
World pvpWorld = creator.createWorld();
|
||||
long dt = System.currentTimeMillis() - t0;
|
||||
if (pvpWorld == null) {
|
||||
@@ -530,6 +542,25 @@ public class PVPArenaManager implements Listener {
|
||||
plugin.getLogger().info("[PVP] PVP世界已删除,下次进入将随机重新生成地形");
|
||||
}
|
||||
|
||||
/**
|
||||
* 在创建全新 PVP 世界前,彻底删除磁盘上可能残留的旧世界目录。
|
||||
* ★ 必要性:旧版本曾对出生点执行 setSpawnLocation 并对周边强制造陆(小岛),
|
||||
* 若上次删除因 Windows 文件锁而失败,createWorld() 会误加载到该旧世界
|
||||
* (出生点被锁定为 0,0、地形被切成小岛)。这里用重命名+重试确保目录真正消失,
|
||||
* 保证每次进入都是 MC 自然生成的全新主世界,自然出生点完全由 MC 决定。
|
||||
*/
|
||||
private void deleteExistingPVPWorldFolder() {
|
||||
File folder = new File(Bukkit.getWorldContainer(), PVP_WORLD_NAME);
|
||||
if (!folder.exists()) return;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
moveWorldToTrash(folder);
|
||||
if (!folder.exists()) return;
|
||||
try { Thread.sleep(300); } catch (InterruptedException ignored) { Thread.currentThread().interrupt(); }
|
||||
}
|
||||
// 极少数情况下重命名仍失败,退回递归删除
|
||||
deleteWorldFolder(folder);
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制同步生成出生点周边区块(半径 radius 个区块),确保地形已存在后再寻找安全出生点
|
||||
* 必须在主线程调用(命令处理阶段)
|
||||
@@ -540,7 +571,7 @@ public class PVPArenaManager implements Listener {
|
||||
for (int dx = -radius; dx <= radius; dx++) {
|
||||
for (int dz = -radius; dz <= radius; dz++) {
|
||||
try {
|
||||
// true=强制生成;再读取一个方块以触发区块填充,避免异步未就绪导致 findSafeSpawn 误判虚空
|
||||
// true=强制生成;再读取一个方块以触发区块填充,避免异步未就绪导致出生点下方误判为虚空
|
||||
world.loadChunk(cx + dx, cz + dz, true);
|
||||
world.getChunkAt(cx + dx, cz + dz).getBlock(8, 64, 8).getType();
|
||||
} catch (Exception e) {
|
||||
@@ -550,99 +581,7 @@ public class PVPArenaManager implements Listener {
|
||||
}
|
||||
}
|
||||
|
||||
private int lastSpawnLandScore = -1; // findSafeSpawn 最后一次搜索的陆地质量分(供 createPVPWorld 判断是否重试)
|
||||
|
||||
/**
|
||||
* 在世界中寻找安全的出生点(螺旋向外搜索最高的非空气且非流体方块作为地面)
|
||||
*
|
||||
* ★ 2026-07-07 改进:
|
||||
* 1. 搜索半径扩大到 64 格(原32),覆盖更广范围找陆地
|
||||
* 2. 新增"陆地优先"评分:草地/泥土/石头 > 沙子 > 其它固体 > 流体
|
||||
* 3. 如果出生点周边全是水(海洋 biome),沿对角线远距离搜寻陆地
|
||||
* 4. 即使找到的地面在水面上方(如水面树冠),也继续搜更好的陆地位置
|
||||
*/
|
||||
private Location findSafeSpawn(World world) {
|
||||
int baseX = world.getSpawnLocation().getBlockX();
|
||||
int baseZ = world.getSpawnLocation().getBlockZ();
|
||||
final int SEARCH_RADIUS = 64; // 扩大搜索范围(原32格,海洋biome需要更大范围)
|
||||
int bestX = baseX, bestZ = baseZ, bestY = -1;
|
||||
int bestLandScore = -1; // 陆地质量分(越高越好)
|
||||
|
||||
// 第一轮:螺旋搜索,优先选高分陆地
|
||||
for (int r = 0; r <= SEARCH_RADIUS; r++) {
|
||||
for (int dx = -r; dx <= r; dx++) {
|
||||
for (int dz = -r; dz <= r; dz++) {
|
||||
if (Math.max(Math.abs(dx), Math.abs(dz)) != r) continue;
|
||||
int x = baseX + dx, z = baseZ + dz;
|
||||
for (int y = 255; y >= 1; y--) {
|
||||
Block block = world.getBlockAt(x, y, z);
|
||||
Material type = block.getType();
|
||||
if (!type.isAir() && !isFluid(type)) {
|
||||
int landScore = getLandScore(type);
|
||||
// 同等高度优先选陆地质量更高的;或更高位置的陆地
|
||||
if (y > bestY || (y == bestY && landScore > bestLandScore)) {
|
||||
bestY = y; bestX = x; bestZ = z;
|
||||
bestLandScore = landScore;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 找到高质量陆地(非沙地)就提前结束,不需要搜完全部范围
|
||||
if (bestY >= 0 && bestLandScore >= 2) break;
|
||||
}
|
||||
|
||||
// 第二轮:如果第一轮只找到低分地面(沙地/水下固体),尝试沿8个方向远距离扫掠找真正陆地
|
||||
if (bestY >= 0 && bestLandScore < 2) {
|
||||
plugin.getLogger().info("[PVP] 出生点周边质量较低(分数=" + bestLandScore + "),启动远距离陆地搜索...");
|
||||
int[][] directions = {{1,1},{1,-1},{-1,1},{-1,-1},{1,0},{-1,0},{0,1},{0,-1}};
|
||||
for (int[] dir : directions) {
|
||||
for (int dist = SEARCH_RADIUS + 16; dist <= SEARCH_RADIUS + 128; dist += 16) {
|
||||
int tx = baseX + dir[0] * dist;
|
||||
int tz = baseZ + dir[1] * dist;
|
||||
for (int y = 255; y >= 1; y--) {
|
||||
Block block = world.getBlockAt(tx, y, tz);
|
||||
Material type = block.getType();
|
||||
if (!type.isAir() && !isFluid(type)) {
|
||||
int landScore = getLandScore(type);
|
||||
if (landScore >= 2 && y > bestY) {
|
||||
bestY = y; bestX = tx; bestZ = tz;
|
||||
bestLandScore = landScore;
|
||||
plugin.getLogger().info("[PVP] 远距离搜索发现陆地: (" + tx + "," + bestY + "," + tz + ") 分数=" + landScore);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bestLandScore >= 2) break; // 任一方向找到好陆地即可
|
||||
}
|
||||
}
|
||||
|
||||
if (bestY >= 0) {
|
||||
lastSpawnLandScore = bestLandScore;
|
||||
return new Location(world, bestX + 0.5, bestY + 1, bestZ + 0.5);
|
||||
}
|
||||
lastSpawnLandScore = -1; // 未找到安全地面(疑似虚空世界)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 陆地质量评分:用于选择最佳出生点
|
||||
* 3=优质陆地(草/泥土/石头) | 2=可接受(沙子/沙砾) | 1=低分(其它固体) | -1=流体/空气
|
||||
*/
|
||||
private int getLandScore(Material type) {
|
||||
switch (type.name()) {
|
||||
case "GRASS_BLOCK": case "DIRT": case "STONE": case "COBBLESTONE":
|
||||
case "PODZOL": case "MYCELIUM": case "FARMLAND":
|
||||
return 3; // ★ 优质陆地——最适合做出生点
|
||||
case "SAND": case "GRAVEL": case "SANDSTONE":
|
||||
return 2; // 可接受——沙滩/河岸
|
||||
default:
|
||||
if (!type.isAir() && !isFluid(type)) return 1; // 其它固体方块
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断方块是否为流体(水/熔岩),流体上不适合作为安全出生地面
|
||||
@@ -651,31 +590,6 @@ public class PVPArenaManager implements Listener {
|
||||
return type == Material.WATER || type == Material.LAVA;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保底平台:如果出生点下方全是空气则铺一个平台
|
||||
* ★ 改进:扩大到20x20 + 四角萤石标记 + 合理高度(y=120)
|
||||
*/
|
||||
private void buildFallbackPlatform(World world) {
|
||||
int centerY = 120;
|
||||
for (int dx = -10; dx <= 10; dx++) {
|
||||
for (int dz = -10; dz <= 10; dz++) {
|
||||
world.getBlockAt(dx, centerY, dz).setType(Material.STONE);
|
||||
world.getBlockAt(dx, centerY - 1, dz).setType(Material.STONE);
|
||||
// 边缘一圈加围栏防止掉落
|
||||
if (Math.abs(dx) == 10 || Math.abs(dz) == 10) {
|
||||
world.getBlockAt(dx, centerY + 1, dz).setType(Material.OAK_FENCE);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 四角萤石标记(夜间可见)
|
||||
world.getBlockAt(-10, centerY + 2, -10).setType(Material.GLOWSTONE);
|
||||
world.getBlockAt(10, centerY + 2, -10).setType(Material.GLOWSTONE);
|
||||
world.getBlockAt(-10, centerY + 2, 10).setType(Material.GLOWSTONE);
|
||||
world.getBlockAt(10, centerY + 2, 10).setType(Material.GLOWSTONE);
|
||||
// 中央标记
|
||||
world.getBlockAt(0, centerY + 1, 0).setType(Material.GLOWSTONE);
|
||||
plugin.getLogger().warning("[PVP] 已生成保底出生平台: 中心(0," + (centerY+1) + ",0), 范围20x20");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定列从顶部向下的第一个非空气、非流体方块 Y 坐标(用于确定出生点地形高度)
|
||||
@@ -688,73 +602,6 @@ public class PVPArenaManager implements Listener {
|
||||
return 64;
|
||||
}
|
||||
|
||||
/**
|
||||
* ★ 强制造陆(最后兜底):当连续多个随机种子都生成海洋/虚空时,
|
||||
* 在出生点周围强制改造出一块自然主世界风格的开阔陆地
|
||||
* (草地平原 + 缓丘 + 稀疏树木),彻底告别 20x20 保底小平台。
|
||||
*
|
||||
* 设计要点:
|
||||
* - 半径 40 格(81x81)大区域,足够玩家活动与战斗
|
||||
* - 用正弦叠加做缓丘,避免纯平面呆板
|
||||
* - 外圈 6 格缓坡沉入水面下,形成自然岛屿感
|
||||
* - 稀疏随机树木点缀
|
||||
*
|
||||
* @param world 已创建但地形不合格的PVP世界
|
||||
*/
|
||||
private void forceTerraformSpawnArea(World world) {
|
||||
Location spawn = world.getSpawnLocation();
|
||||
int cx = spawn.getBlockX();
|
||||
int cz = spawn.getBlockZ();
|
||||
final int R = 40; // 半径40格 → 81x81 开阔地形
|
||||
final int GROUND_Y = 64; // 主世界海平面附近
|
||||
Random rng = new Random(System.nanoTime() ^ (cx * 31L + cz));
|
||||
|
||||
for (int dx = -R; dx <= R; dx++) {
|
||||
for (int dz = -R; dz <= R; dz++) {
|
||||
int x = cx + dx, z = cz + dz;
|
||||
// 正弦叠加生成缓丘(自然起伏)
|
||||
double hill = Math.sin(dx * 0.08) * 2.0 + Math.cos(dz * 0.07) * 2.0;
|
||||
int topY = GROUND_Y + (int) Math.round(hill);
|
||||
// 外圈 6 格缓坡下沉入水,形成岛屿感
|
||||
int dist = Math.max(Math.abs(dx), Math.abs(dz));
|
||||
if (dist > R - 6) topY -= (dist - (R - 6));
|
||||
// 填充地层:表面草 / 上两层泥 / 更深处石
|
||||
for (int y = topY; y >= topY - 5; y--) {
|
||||
if (y < 1) break;
|
||||
Material m;
|
||||
if (y == topY) m = Material.GRASS_BLOCK;
|
||||
else if (y >= topY - 2) m = Material.DIRT;
|
||||
else m = Material.STONE;
|
||||
world.getBlockAt(x, y, z).setType(m);
|
||||
}
|
||||
// 稀疏树木点缀(约 1/140 概率,且只在接近海平面的地块)
|
||||
if (topY >= GROUND_Y - 1 && topY <= GROUND_Y + 3 && rng.nextInt(140) == 0) {
|
||||
plantOakTree(world, x, topY + 1, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 重置出生点到正中央草地上方
|
||||
world.setSpawnLocation(cx, GROUND_Y + 2, cz);
|
||||
plugin.getLogger().info("[PVP] 强制造陆完成: 出生点周边 " + (R * 2 + 1) + "x" + (R * 2 + 1)
|
||||
+ " 草地平原已生成(最后兜底,正常情况不应触发)");
|
||||
}
|
||||
|
||||
/** 在世界中种一棵简单橡树(树干4格 + 叶冠),仅用于强制造陆点缀 */
|
||||
private void plantOakTree(World world, int x, int y, int z) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
world.getBlockAt(x, y + i, z).setType(Material.OAK_LOG);
|
||||
}
|
||||
for (int dy = 3; dy <= 5; dy++) {
|
||||
for (int dx = -2; dx <= 2; dx++) {
|
||||
for (int dz = -2; dz <= 2; dz++) {
|
||||
if (Math.abs(dx) == 2 && Math.abs(dz) == 2) continue; // 去四角更圆润
|
||||
if (dy == 5 && (Math.abs(dx) > 1 || Math.abs(dz) > 1)) continue; // 树冠顶部收窄
|
||||
Block b = world.getBlockAt(x + dx, y + dy, z + dz);
|
||||
if (b.getType().isAir()) b.setType(Material.OAK_LEAVES);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== GUI关闭事件(防止ESC跳过装备选择)====================
|
||||
|
||||
@@ -1419,9 +1266,19 @@ public class PVPArenaManager implements Listener {
|
||||
private ItemStack makePotion(int count) {
|
||||
ItemStack potion = new ItemStack(Material.POTION, count);
|
||||
ItemMeta meta = potion.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.setDisplayName("§a§l瞬间治疗药水");
|
||||
potion.setItemMeta(meta);
|
||||
if (meta instanceof PotionMeta) {
|
||||
PotionMeta pMeta = (PotionMeta) meta;
|
||||
pMeta.setDisplayName("§a§l瞬间治疗药水");
|
||||
// ★ 写入真实药水效果 NBT:瞬间治疗(数量越多档位越高效果越强),
|
||||
// 否则客户端只显示"药水"却没有任何效果(玩家反馈"药水无效果")。
|
||||
pMeta.addCustomEffect(new org.bukkit.potion.PotionEffect(
|
||||
org.bukkit.potion.PotionEffectType.INSTANT_HEALTH,
|
||||
1, // 瞬间效果,duration 无意义
|
||||
count >= 8 ? 1 : 0, // 下合金档(8瓶)→治疗II,其余治疗I
|
||||
false, false), true);
|
||||
pMeta.setBasePotionData(new org.bukkit.potion.PotionData(
|
||||
org.bukkit.potion.PotionType.HEALING, false, false));
|
||||
potion.setItemMeta(pMeta);
|
||||
}
|
||||
return potion;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,9 @@ import org.bukkit.util.io.BukkitObjectOutputStream;
|
||||
import org.bukkit.potion.PotionEffect;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
import org.bukkit.inventory.meta.PotionMeta;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.inventory.meta.EnchantmentStorageMeta;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonArray;
|
||||
|
||||
@@ -128,6 +131,53 @@ public class ShopManager implements Listener {
|
||||
EFFECT_MAP.put("神龟", PotionEffectType.RESISTANCE);
|
||||
}
|
||||
|
||||
// ===== 附魔书:中文附魔名 → Bukkit Enchantment =====
|
||||
private static final Map<String, Enchantment> ENCHANT_MAP = new HashMap<>();
|
||||
static {
|
||||
regEnchant("锋利", "sharpness");
|
||||
regEnchant("亡灵杀手", "smite");
|
||||
regEnchant("节肢杀手", "bane_of_arthropods");
|
||||
regEnchant("击退", "knockback");
|
||||
regEnchant("火焰附加", "fire_aspect");
|
||||
regEnchant("掠夺", "looting");
|
||||
regEnchant("横扫之刃", "sweeping_edge");
|
||||
regEnchant("保护", "protection");
|
||||
regEnchant("火焰保护", "fire_protection");
|
||||
regEnchant("摔落保护", "feather_falling");
|
||||
regEnchant("爆炸保护", "blast_protection");
|
||||
regEnchant("弹射物保护", "projectile_protection");
|
||||
regEnchant("水下呼吸", "respiration");
|
||||
regEnchant("深海探索者", "depth_strider");
|
||||
regEnchant("冰霜行者", "frost_walker");
|
||||
regEnchant("荆棘", "thorns");
|
||||
regEnchant("精准采集", "silk_touch");
|
||||
regEnchant("效率", "efficiency");
|
||||
regEnchant("耐久", "unbreaking");
|
||||
regEnchant("时运", "fortune");
|
||||
regEnchant("经验修补", "mending");
|
||||
regEnchant("力量", "power");
|
||||
regEnchant("冲击", "punch");
|
||||
regEnchant("火焰", "flame");
|
||||
regEnchant("无限", "infinity");
|
||||
regEnchant("穿透", "piercing");
|
||||
regEnchant("激流", "riptide");
|
||||
regEnchant("忠诚", "loyalty");
|
||||
regEnchant("引雷", "channeling");
|
||||
regEnchant("多重射击", "multishot");
|
||||
regEnchant("快速装填", "quick_charge");
|
||||
regEnchant("海之眷顾", "luck_of_the_sea");
|
||||
regEnchant("饵钓", "lure");
|
||||
regEnchant("绑定诅咒", "binding_curse");
|
||||
regEnchant("消失诅咒", "vanishing_curse");
|
||||
regEnchant("穿刺", "impaling");
|
||||
}
|
||||
private static void regEnchant(String cn, String key) {
|
||||
try {
|
||||
Enchantment e = Enchantment.getByKey(NamespacedKey.minecraft(key));
|
||||
if (e != null) ENCHANT_MAP.put(cn, e);
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
public static class CartItem {
|
||||
String itemId;
|
||||
String displayName;
|
||||
@@ -1303,8 +1353,116 @@ public class ShopManager implements Listener {
|
||||
* "强效力量喷溅药水" + SPLASH_POTION
|
||||
* → PotionEffect(STRENGTH, 600, 1)
|
||||
*/
|
||||
// ===== 附魔书:由显示名推断存储附魔 =====
|
||||
private static final java.util.regex.Pattern ROMAN_PAT =
|
||||
java.util.regex.Pattern.compile("(?i)\\b(X{0,3}(?:IX|IV|V?I{1,3}))\\b");
|
||||
private static final java.util.regex.Pattern ARABIC_PAT =
|
||||
java.util.regex.Pattern.compile("\\d+");
|
||||
private static final java.util.regex.Pattern CN_PAT =
|
||||
java.util.regex.Pattern.compile("[零一二三四五六七八九十百]+");
|
||||
|
||||
private ItemStack inferEnchantedBook(String name) {
|
||||
try {
|
||||
ItemStack book = new ItemStack(Material.ENCHANTED_BOOK);
|
||||
EnchantmentStorageMeta meta = (EnchantmentStorageMeta) book.getItemMeta();
|
||||
if (meta == null) return null;
|
||||
boolean added = false;
|
||||
for (Map.Entry<String, Enchantment> e : ENCHANT_MAP.entrySet()) {
|
||||
if (!name.contains(e.getKey())) continue;
|
||||
// 若已有更具体的附魔词包含本词(如"火焰保护"命中时跳过"保护"),避免重复计入
|
||||
boolean shadowed = false;
|
||||
for (Map.Entry<String, Enchantment> o : ENCHANT_MAP.entrySet()) {
|
||||
if (o != e && o.getKey().length() > e.getKey().length()
|
||||
&& o.getKey().contains(e.getKey()) && name.contains(o.getKey())) {
|
||||
shadowed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (shadowed) continue;
|
||||
int lvl = parseEnchantLevel(name, e.getKey());
|
||||
meta.addStoredEnchant(e.getValue(), lvl, true);
|
||||
added = true;
|
||||
}
|
||||
if (!added) return null;
|
||||
book.setItemMeta(meta);
|
||||
return book;
|
||||
} catch (Exception ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private int parseEnchantLevel(String name, String keyword) {
|
||||
int idx = name.indexOf(keyword);
|
||||
if (idx < 0) return 1;
|
||||
String after = org.bukkit.ChatColor.stripColor(
|
||||
name.substring(idx + keyword.length()));
|
||||
java.util.regex.Matcher m = ROMAN_PAT.matcher(after);
|
||||
if (m.find()) return romanToInt(m.group());
|
||||
java.util.regex.Matcher m2 = ARABIC_PAT.matcher(after);
|
||||
if (m2.find()) {
|
||||
try { return Integer.parseInt(m2.group()); } catch (NumberFormatException ignored) {}
|
||||
}
|
||||
java.util.regex.Matcher m3 = CN_PAT.matcher(after);
|
||||
if (m3.find()) return cnToInt(m3.group());
|
||||
return 1;
|
||||
}
|
||||
|
||||
private int romanToInt(String s) {
|
||||
s = s.toUpperCase();
|
||||
int total = 0, prev = 0;
|
||||
for (int i = s.length() - 1; i >= 0; i--) {
|
||||
int v;
|
||||
switch (s.charAt(i)) {
|
||||
case 'I': v = 1; break;
|
||||
case 'V': v = 5; break;
|
||||
case 'X': v = 10; break;
|
||||
case 'L': v = 50; break;
|
||||
case 'C': v = 100; break;
|
||||
default: v = 0;
|
||||
}
|
||||
total += (v < prev) ? -v : v;
|
||||
prev = v;
|
||||
}
|
||||
return Math.max(1, total);
|
||||
}
|
||||
|
||||
private int cnToInt(String s) {
|
||||
int total = 0, num = 0;
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
int d;
|
||||
switch (s.charAt(i)) {
|
||||
case '零': d = 0; break;
|
||||
case '一': d = 1; break;
|
||||
case '二': d = 2; break;
|
||||
case '三': d = 3; break;
|
||||
case '四': d = 4; break;
|
||||
case '五': d = 5; break;
|
||||
case '六': d = 6; break;
|
||||
case '七': d = 7; break;
|
||||
case '八': d = 8; break;
|
||||
case '九': d = 9; break;
|
||||
case '十': d = 10; break;
|
||||
case '百': d = 100; break;
|
||||
default: d = 0;
|
||||
}
|
||||
if (d == 10 || d == 100) {
|
||||
num = (num == 0 ? 1 : num) * d;
|
||||
total += num;
|
||||
num = 0;
|
||||
} else {
|
||||
num = d;
|
||||
}
|
||||
}
|
||||
total += num;
|
||||
return Math.max(1, total);
|
||||
}
|
||||
|
||||
private ItemStack inferFromName(
|
||||
String name, Material mat) {
|
||||
// 附魔书:解析名称中的附魔词与等级,写入 EnchantmentStorageMeta
|
||||
if (mat == Material.ENCHANTED_BOOK) {
|
||||
return inferEnchantedBook(name);
|
||||
}
|
||||
// 只处理药水和箭
|
||||
if (mat != Material.POTION
|
||||
&& mat != Material.SPLASH_POTION
|
||||
|
||||
Reference in New Issue
Block a user