diff --git a/src/main/java/Sdf1_login/DatabaseManager.java b/src/main/java/Sdf1_login/DatabaseManager.java index e7259a0..6797460 100644 --- a/src/main/java/Sdf1_login/DatabaseManager.java +++ b/src/main/java/Sdf1_login/DatabaseManager.java @@ -1384,6 +1384,44 @@ public class DatabaseManager { } + /** + * 获取玩家最新背包备份 + */ + public String[] getInventoryBackup(String playerName) { + try { + PreparedStatement ps = + db.prepareStatement( + "SELECT contents_b64, " + + "armor_b64, " + + "extra_b64, " + + "level, " + + "experience " + + "FROM inventory_backups " + + "WHERE player_name=? " + + "ORDER BY save_time DESC " + + "LIMIT 1"); + ps.setString(1, playerName); + ResultSet rs = ps.executeQuery(); + if (rs.next()) { + String[] result = new String[5]; + result[0] = rs.getString("contents_b64"); + result[1] = rs.getString("armor_b64"); + result[2] = rs.getString("extra_b64"); + result[3] = String.valueOf(rs.getInt("level")); + result[4] = String.valueOf(rs.getDouble("experience")); + rs.close(); + ps.close(); + return result; + } + rs.close(); + ps.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + return null; + } + + // ==================== 工单操作 ==================== public int createTicket(String type, diff --git a/src/main/java/Sdf1_login/Main.java b/src/main/java/Sdf1_login/Main.java index f16320f..adedc8a 100644 --- a/src/main/java/Sdf1_login/Main.java +++ b/src/main/java/Sdf1_login/Main.java @@ -171,6 +171,8 @@ public class Main extends JavaPlugin //pvp private PVPManager pvpManager; + //pvp竞技场(独立世界模式) + private PVPArenaManager pvPArenaManager; @@ -507,6 +509,13 @@ public class Main extends JavaPlugin () -> pvpManager.tickRegions(), 20L, 20L); setupPVPTab(); + + // 12.1 ===PVP竞技场(独立世界模式)=== + pvPArenaManager = new PVPArenaManager(this); + getServer().getPluginManager() + .registerEvents(pvPArenaManager, this); + // 确保PVP世界存在 + pvPArenaManager.ensurePVPWorldExists(); // 13 ====cypay债券==== // 债券系统(独立DB) @@ -668,7 +677,7 @@ public class Main extends JavaPlugin "create", "stats", "list", "delete", "tool", "on", "off", - "tempban"}; + "tempban", "arena"}; for (String s : sub) { if (s.startsWith( args[0] @@ -2298,6 +2307,21 @@ public class Main extends JavaPlugin e.setCancelled(true); p.setAllowFlight(true); } + + // PVP竞技场世界检测 + if (pvPArenaManager != null) { + String fromWorld = from.getWorld().getName(); + String toWorld = to.getWorld().getName(); + + // 检测玩家是否进入PVP世界 + if (!fromWorld.equals(toWorld) && toWorld.equals("pvp_arena")) { + pvPArenaManager.onPlayerEnterPVPWorld(p); + } + // 检测玩家是否离开PVP世界 + else if (!fromWorld.equals(toWorld) && fromWorld.equals("pvp_arena")) { + pvPArenaManager.onPlayerExitPVPWorld(p); + } + } } private List filterTab(List opts, String prefix) { @@ -2939,6 +2963,10 @@ public class Main extends JavaPlugin public PVPManager getPVPManager() { return pvpManager; } + + public PVPArenaManager getPVPArenaManager() { + return pvPArenaManager; + } // 在 Main.java 中(与其他 getter 放一起) public AreaProtection getAreaProtection() { return areaProtection; @@ -3258,6 +3286,15 @@ public class Main extends JavaPlugin } return; } + + // ===== PVP装备选择GUI点击处理 ===== + if (title.equals("§6§l选择PVP装备")) { + if (pvPArenaManager != null) { + e.setCancelled(true); + pvPArenaManager.handleEquipmentClick(p, e.getRawSlot()); + } + return; + } // 传送面板不受登录冻结限制(基岩版玩家免登录也要能用) if (!title.equals("§6§l传送系统")) { @@ -4723,6 +4760,12 @@ public class Main extends JavaPlugin return true; } Player p = (Player) sender; + + // PVP竞技场命令(由pvpManager统一处理) + if (args.length > 0 && args[0].equalsIgnoreCase("arena")) { + return pvpManager.onCommand(p, args); + } + return pvpManager.onCommand(p, args); } diff --git a/src/main/java/Sdf1_login/PVPArenaManager.java b/src/main/java/Sdf1_login/PVPArenaManager.java new file mode 100644 index 0000000..3e47d27 --- /dev/null +++ b/src/main/java/Sdf1_login/PVPArenaManager.java @@ -0,0 +1,567 @@ +package Sdf1_login; + +import org.bukkit.*; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import java.io.File; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * PVP竞技场管理器 - 独立世界模式 + * + * 流程: + * 1. 玩家加入PVP世界 + * 2. 备份玩家背包数据 + * 3. 清空背包 + * 4. 提供装备自选 + * 5. 全局击杀计数 + * 6. 玩家退场 + * 7. 回收专属装备 + * 8. 还原玩家背包数据 + */ +public class PVPArenaManager implements Listener { + + private final Main plugin; + private final DatabaseManager db; + + // PVP世界名称 + private static final String PVP_WORLD_NAME = "pvp_arena"; + + // 玩家在PVP世界中的状态 + private final Set inPVPArena = ConcurrentHashMap.newKeySet(); + + // 玩家背包备份缓存 (玩家名 -> 备份数据) + private final Map inventoryBackups = new ConcurrentHashMap<>(); + + // PVP装备列表 (管理员可配置) + private final List pvpEquipment = new ArrayList<>(); + + // 装备选择GUI标题 + private static final String EQUIPMENT_GUI_TITLE = "§6§l选择PVP装备"; + + /** + * 背包备份数据类 + */ + public static class InventoryBackup { + public ItemStack[] contents; + public ItemStack[] armor; + public ItemStack offHand; + public int expLevel; + public float exp; + + public InventoryBackup(ItemStack[] contents, ItemStack[] armor, ItemStack offHand, int expLevel, float exp) { + this.contents = contents != null ? contents.clone() : new ItemStack[36]; + this.armor = armor != null ? armor.clone() : new ItemStack[4]; + this.offHand = offHand; + this.expLevel = expLevel; + this.exp = exp; + } + } + + public PVPArenaManager(Main plugin) { + this.plugin = plugin; + this.db = plugin.getDb(); + + // 初始化默认PVP装备 + initDefaultEquipment(); + + // 加载配置 + loadConfig(); + } + + /** + * 初始化默认PVP装备 + */ + private void initDefaultEquipment() { + pvpEquipment.clear(); + + // 铁甲套装 + pvpEquipment.add(new ItemStack(Material.IRON_HELMET)); + pvpEquipment.add(new ItemStack(Material.IRON_CHESTPLATE)); + pvpEquipment.add(new ItemStack(Material.IRON_LEGGINGS)); + pvpEquipment.add(new ItemStack(Material.IRON_BOOTS)); + + // 钻石剑 + ItemStack sword = new ItemStack(Material.DIAMOND_SWORD); + ItemMeta swordMeta = sword.getItemMeta(); + if (swordMeta != null) { + swordMeta.setDisplayName("§b§lPVP钻石剑"); + sword.setItemMeta(swordMeta); + } + pvpEquipment.add(sword); + + // 弓 + pvpEquipment.add(new ItemStack(Material.BOW)); + + // 箭 x 64 + ItemStack arrows = new ItemStack(Material.ARROW, 64); + pvpEquipment.add(arrows); + + // 金苹果 x 8 + ItemStack gapple = new ItemStack(Material.GOLDEN_APPLE, 8); + pvpEquipment.add(gapple); + + // 药水 + ItemStack potion = new ItemStack(Material.POTION, 3); + ItemMeta potionMeta = potion.getItemMeta(); + if (potionMeta != null) { + potionMeta.setDisplayName("§a§l瞬间治疗药水"); + potion.setItemMeta(potionMeta); + } + pvpEquipment.add(potion); + } + + /** + * 加载配置 + */ + private void loadConfig() { + // TODO: 从配置文件加载PVP装备列表 + // 目前使用默认装备 + } + + /** + * 保存配置 + */ + public void saveConfig() { + // TODO: 保存PVP装备列表到配置文件 + } + + /** + * 检查并创建PVP世界 + */ + public void ensurePVPWorldExists() { + World pvpWorld = Bukkit.getWorld(PVP_WORLD_NAME); + if (pvpWorld == null) { + // 创建PVP世界 (使用超平坦世界) + WorldCreator creator = new WorldCreator(PVP_WORLD_NAME); + creator.environment(World.Environment.NORMAL); + creator.generator(new org.bukkit.generator.ChunkGenerator() { + @Override + public ChunkData generateChunkData(World world, Random random, int x, int z, BiomeGrid biome) { + return createChunkData(world); + } + }); + + pvpWorld = creator.createWorld(); + + if (pvpWorld != null) { + // 设置PVP世界规则 + pvpWorld.setPVP(true); + pvpWorld.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, false); + pvpWorld.setGameRule(GameRule.DO_WEATHER_CYCLE, false); + pvpWorld.setTime(6000); // 中午 + + plugin.getLogger().info("[PVP] 已创建PVP竞技场世界: " + PVP_WORLD_NAME); + } else { + plugin.getLogger().severe("[PVP] 无法创建PVP竞技场世界!"); + } + } + } + + /** + * 玩家加入PVP世界 + */ + public void onPlayerEnterPVPWorld(Player player) { + String playerName = player.getName(); + + // 检查是否已经在PVP世界 + if (inPVPArena.contains(playerName)) { + return; + } + + // 标记玩家进入PVP世界 + inPVPArena.add(playerName); + + // 备份玩家背包 + backupInventory(player); + + // 清空玩家背包 + clearInventory(player); + + // 打开装备选择GUI + openEquipmentSelection(player); + + player.sendMessage("§a§l欢迎来到PVP竞技场!"); + player.sendMessage("§7请选择你的装备套装"); + player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1.0f, 1.0f); + + plugin.getLogger().info("[PVP] 玩家 " + playerName + " 进入PVP竞技场"); + } + + /** + * 玩家退出PVP世界 + */ + public void onPlayerExitPVPWorld(Player player) { + String playerName = player.getName(); + + // 检查是否在PVP世界 + if (!inPVPArena.contains(playerName)) { + return; + } + + // 标记玩家离开PVP世界 + inPVPArena.remove(playerName); + + // 回收PVP装备 + 回收PVPEquipment(player); + + // 恢复玩家背包 + restoreInventory(player); + + player.sendMessage("§e§l你已离开PVP竞技场"); + player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0f, 1.0f); + + plugin.getLogger().info("[PVP] 玩家 " + playerName + " 离开PVP竞技场"); + } + + /** + * 备份玩家背包 + */ + private void backupInventory(Player player) { + String playerName = player.getName(); + + InventoryBackup backup = new InventoryBackup( + player.getInventory().getContents(), + player.getInventory().getArmorContents(), + player.getInventory().getItemInOffHand(), + player.getLevel(), + player.getExp() + ); + + inventoryBackups.put(playerName, backup); + + // 同时保存到数据库 + db.saveInventoryBackup( + playerName, + serializeItems(backup.contents), + serializeItems(backup.armor), + serializeItem(backup.offHand), + backup.expLevel, + backup.exp + ); + + plugin.getLogger().info("[PVP] 已备份玩家 " + playerName + " 的背包"); + } + + /** + * 恢复玩家背包 + */ + private void restoreInventory(Player player) { + String playerName = player.getName(); + + // 先从缓存获取 + InventoryBackup backup = inventoryBackups.remove(playerName); + + // 缓存没有则从数据库获取 + if (backup == null) { + String[] data = db.getInventoryBackup(playerName); + if (data != null) { + backup = new InventoryBackup( + deserializeItems(data[0]), + deserializeItems(data[1]), + deserializeItem(data[2]), + Integer.parseInt(data[3]), + Float.parseFloat(data[4]) + ); + + // 删除数据库备份 + db.deleteInventoryBackup(playerName); + } + } + + if (backup != null) { + // 恢复背包 + player.getInventory().setContents(backup.contents); + player.getInventory().setArmorContents(backup.armor); + player.getInventory().setItemInOffHand(backup.offHand); + player.setLevel(backup.expLevel); + player.setExp(backup.exp); + + player.sendMessage("§a§l背包已恢复"); + plugin.getLogger().info("[PVP] 已恢复玩家 " + playerName + " 的背包"); + } else { + plugin.getLogger().warning("[PVP] 未找到玩家 " + playerName + " 的背包备份"); + } + } + + /** + * 清空玩家背包 + */ + private void clearInventory(Player player) { + player.getInventory().clear(); + player.getInventory().setArmorContents(new ItemStack[4]); + player.getInventory().setItemInOffHand(null); + player.setLevel(0); + player.setExp(0.0f); + } + + /** + * 回收PVP装备 + */ + private void 回收PVPEquipment(Player player) { + // 移除带有PVP标记的物品 + ItemStack[] contents = player.getInventory().getContents(); + for (int i = 0; i < contents.length; i++) { + if (contents[i] != null && isPVPEquipment(contents[i])) { + contents[i] = null; + } + } + player.getInventory().setContents(contents); + + // 移除护甲中的PVP装备 + ItemStack[] armor = player.getInventory().getArmorContents(); + for (int i = 0; i < armor.length; i++) { + if (armor[i] != null && isPVPEquipment(armor[i])) { + armor[i] = null; + } + } + player.getInventory().setArmorContents(armor); + + // 移除副手 + ItemStack offHand = player.getInventory().getItemInOffHand(); + if (offHand != null && isPVPEquipment(offHand)) { + player.getInventory().setItemInOffHand(null); + } + } + + /** + * 检查物品是否是PVP装备 + */ + private boolean isPVPEquipment(ItemStack item) { + if (item == null) return false; + + // 检查是否是PVP装备材料 + Material type = item.getType(); + return type == Material.IRON_HELMET || + type == Material.IRON_CHESTPLATE || + type == Material.IRON_LEGGINGS || + type == Material.IRON_BOOTS || + type == Material.DIAMOND_SWORD || + type == Material.BOW || + type == Material.ARROW || + type == Material.GOLDEN_APPLE || + type == Material.POTION; + } + + /** + * 打开装备选择GUI + */ + public void openEquipmentSelection(Player player) { + Inventory gui = Bukkit.createInventory(null, 54, EQUIPMENT_GUI_TITLE); + + // 添加PVP装备到GUI + for (int i = 0; i < pvpEquipment.size() && i < 45; i++) { + gui.setItem(i, pvpEquipment.get(i).clone()); + } + + // 添加确认按钮 + ItemStack confirm = new ItemStack(Material.LIME_STAINED_GLASS_PANE); + ItemMeta confirmMeta = confirm.getItemMeta(); + if (confirmMeta != null) { + confirmMeta.setDisplayName("§a§l确认选择"); + confirm.setItemMeta(confirmMeta); + } + gui.setItem(49, confirm); + + // 添加全选按钮 + ItemStack selectAll = new ItemStack(Material.NETHER_STAR); + ItemMeta selectAllMeta = selectAll.getItemMeta(); + if (selectAllMeta != null) { + selectAllMeta.setDisplayName("§b§l一键装备全套"); + selectAll.setItemMeta(selectAllMeta); + } + gui.setItem(53, selectAll); + + player.openInventory(gui); + } + + /** + * 处理装备选择GUI点击 + */ + public boolean handleEquipmentClick(Player player, int slot) { + if (!player.getOpenInventory().getTitle().equals(EQUIPMENT_GUI_TITLE)) { + return false; + } + + String playerName = player.getName(); + + // 检查是否在PVP世界 + if (!inPVPArena.contains(playerName)) { + return false; + } + + // 确认选择按钮 + if (slot == 49) { + player.closeInventory(); + player.sendMessage("§a§l装备选择完成,开始战斗!"); + player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0f, 1.0f); + return true; + } + + // 一键装备全套按钮 + if (slot == 53) { + equipFullSet(player); + player.closeInventory(); + player.sendMessage("§a§l已装备全套PVP装备!"); + player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0f, 1.0f); + return true; + } + + // 装备物品 + if (slot >= 0 && slot < pvpEquipment.size()) { + ItemStack item = pvpEquipment.get(slot).clone(); + + // 根据物品类型自动装备 + Material type = item.getType(); + if (type == Material.IRON_HELMET) { + player.getInventory().setHelmet(item); + } else if (type == Material.IRON_CHESTPLATE) { + player.getInventory().setChestplate(item); + } else if (type == Material.IRON_LEGGINGS) { + player.getInventory().setLeggings(item); + } else if (type == Material.IRON_BOOTS) { + player.getInventory().setBoots(item); + } else { + // 其他物品放入背包 + player.getInventory().addItem(item); + } + + player.playSound(player.getLocation(), Sound.ITEM_ARMOR_EQUIP_IRON, 1.0f, 1.0f); + return true; + } + + return false; + } + + /** + * 装备全套 + */ + private void equipFullSet(Player player) { + for (ItemStack item : pvpEquipment) { + ItemStack clone = item.clone(); + Material type = clone.getType(); + + if (type == Material.IRON_HELMET) { + player.getInventory().setHelmet(clone); + } else if (type == Material.IRON_CHESTPLATE) { + player.getInventory().setChestplate(clone); + } else if (type == Material.IRON_LEGGINGS) { + player.getInventory().setLeggings(clone); + } else if (type == Material.IRON_BOOTS) { + player.getInventory().setBoots(clone); + } else { + player.getInventory().addItem(clone); + } + } + } + + /** + * 玩家加入事件 + */ + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + Player player = event.getPlayer(); + + // 检查玩家是否在PVP世界 + if (player.getWorld().getName().equals(PVP_WORLD_NAME)) { + inPVPArena.add(player.getName()); + + // 延迟恢复背包(等待世界完全加载) + plugin.getServer().getScheduler().runTaskLater(plugin, () -> { + restoreInventory(player); + }, 20L); + } + } + + /** + * 玩家退出事件 + */ + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + Player player = event.getPlayer(); + String playerName = player.getName(); + + // 如果玩家在PVP世界,备份并清空 + if (inPVPArena.contains(playerName)) { + backupInventory(player); + inventoryBackups.remove(playerName); + } + } + + /** + * 序列化物品数组 + */ + private String serializeItems(ItemStack[] items) { + // TODO: 实现物品序列化 + return ""; + } + + /** + * 反序列化物品数组 + */ + private ItemStack[] deserializeItems(String data) { + // TODO: 实现物品反序列化 + return new ItemStack[0]; + } + + /** + * 序列化单个物品 + */ + private String serializeItem(ItemStack item) { + // TODO: 实现物品序列化 + return ""; + } + + /** + * 反序列化单个物品 + */ + private ItemStack deserializeItem(String data) { + // TODO: 实现物品反序列化 + return null; + } + + /** + * 检查玩家是否在PVP世界 + */ + public boolean isInPVPArena(String playerName) { + return inPVPArena.contains(playerName); + } + + /** + * 获取PVP世界 + */ + public World getPVPWorld() { + return Bukkit.getWorld(PVP_WORLD_NAME); + } + + /** + * 获取PVP装备列表 + */ + public List getPVPEquipment() { + return Collections.unmodifiableList(pvpEquipment); + } + + /** + * 添加PVP装备 + */ + public void addPVPEquipment(ItemStack item) { + pvpEquipment.add(item); + saveConfig(); + } + + /** + * 清除所有玩家的PVP状态 + */ + public void clearAllPVPArenaStates() { + inPVPArena.clear(); + inventoryBackups.clear(); + } +} \ No newline at end of file diff --git a/src/main/java/Sdf1_login/ShopItem.java b/src/main/java/Sdf1_login/ShopItem.java index d336c7c..5b8d401 100644 --- a/src/main/java/Sdf1_login/ShopItem.java +++ b/src/main/java/Sdf1_login/ShopItem.java @@ -6,8 +6,8 @@ public class ShopItem { private final String id; private final String displayName; private final Material material; - private final int buyPrice; - private final int sellPrice; + private int buyPrice; + private int sellPrice; private int stock; private int hourlySales; private int totalSales; @@ -42,6 +42,8 @@ public class ShopItem { public void setStock(int s) { this.stock = s; } public void setHourlySales(int s) { this.hourlySales = s; } public void setTotalSales(int s) { this.totalSales = s; } + public void setBuyPrice(int p) { this.buyPrice = p; } + public void setSellPrice(int p) { this.sellPrice = p; } public void addSales(int n) { this.hourlySales += n; this.totalSales += n; diff --git a/src/main/java/Sdf1_login/ShopManager.java b/src/main/java/Sdf1_login/ShopManager.java index 7296947..a862f98 100644 --- a/src/main/java/Sdf1_login/ShopManager.java +++ b/src/main/java/Sdf1_login/ShopManager.java @@ -2007,6 +2007,67 @@ public class ShopManager implements Listener { return false; } + // ===== PHP同步:更新商品价格 ===== + public boolean updateItemPrice(String id, int buyPrice, int sellPrice) { + ShopItem item = findItemById(id); + if (item == null) return false; + item.setBuyPrice(buyPrice); + item.setSellPrice(sellPrice); + for (ShopCategory cat : categories) { + if (cat.getItem(id) != null) { + saveCategory(cat); + return true; + } + } + return false; + } + + // ===== PHP同步:批量更新商品价格和库存 ===== + public int updateItemPrices(List> items) { + int updated = 0; + for (Map data : items) { + String id = (String) data.get("id"); + if (id == null) continue; + + ShopItem item = findItemById(id); + if (item == null) continue; + + boolean changed = false; + if (data.containsKey("buy_price")) { + int newBuyPrice = ((Number) data.get("buy_price")).intValue(); + if (item.getBuyPrice() != newBuyPrice) { + item.setBuyPrice(newBuyPrice); + changed = true; + } + } + if (data.containsKey("sell_price")) { + int newSellPrice = ((Number) data.get("sell_price")).intValue(); + if (item.getSellPrice() != newSellPrice) { + item.setSellPrice(newSellPrice); + changed = true; + } + } + if (data.containsKey("stock")) { + int newStock = ((Number) data.get("stock")).intValue(); + if (item.getStock() != newStock) { + item.setStock(newStock); + changed = true; + } + } + if (changed) { + // 找到并保存对应的分类 + for (ShopCategory cat : categories) { + if (cat.getItem(id) != null) { + saveCategory(cat); + updated++; + break; + } + } + } + } + return updated; + } + public ShopCategory findCategory(String n) { for (ShopCategory c : categories) if (c.getName().equals(n)) return c; diff --git a/src/main/java/Sdf1_login/WebManager.java b/src/main/java/Sdf1_login/WebManager.java index 8e87f1a..c023eb9 100644 --- a/src/main/java/Sdf1_login/WebManager.java +++ b/src/main/java/Sdf1_login/WebManager.java @@ -1322,6 +1322,8 @@ public class WebManager { try { Thread.sleep(6000 + (long)(Math.random() * 8000)); } catch (InterruptedException ignored) {} submitNormalDbTask("即时-pullShopStock", () -> pullShopStock()); try { Thread.sleep(6000 + (long)(Math.random() * 8000)); } catch (InterruptedException ignored) {} + submitNormalDbTask("即时-pullShopPrices", () -> pullShopPrices()); + try { Thread.sleep(6000 + (long)(Math.random() * 8000)); } catch (InterruptedException ignored) {} submitNormalDbTask("即时-pullBondChanges", () -> pullBondChanges()); try { Thread.sleep(6000 + (long)(Math.random() * 8000)); } catch (InterruptedException ignored) {} submitNormalDbTask("即时-syncAllPlayerIps", () -> syncAllPlayerIps()); @@ -1357,6 +1359,8 @@ public class WebManager { try { Thread.sleep(6000 + (long)(Math.random() * 8000)); } catch (InterruptedException ignored) {} submitNormalDbTask("末轮-pullShopStock", () -> pullShopStock()); try { Thread.sleep(6000 + (long)(Math.random() * 8000)); } catch (InterruptedException ignored) {} + submitNormalDbTask("末轮-pullShopPrices", () -> pullShopPrices()); + try { Thread.sleep(6000 + (long)(Math.random() * 8000)); } catch (InterruptedException ignored) {} submitNormalDbTask("末轮-pullBondChanges", () -> pullBondChanges()); plugin.getLogger().info("[合并C] 全员下线超60秒,末轮同步已执行"); } @@ -1393,6 +1397,8 @@ public class WebManager { try { Thread.sleep(6000 + (long)(Math.random() * 8000)); } catch (InterruptedException ignored) {} submitNormalDbTask("周期-pullShopStock", () -> pullShopStock()); try { Thread.sleep(6000 + (long)(Math.random() * 8000)); } catch (InterruptedException ignored) {} + submitNormalDbTask("周期-pullShopPrices", () -> pullShopPrices()); + try { Thread.sleep(6000 + (long)(Math.random() * 8000)); } catch (InterruptedException ignored) {} submitNormalDbTask("周期-pullBondChanges", () -> pullBondChanges()); try { Thread.sleep(6000 + (long)(Math.random() * 8000)); } catch (InterruptedException ignored) {} submitNormalDbTask("周期-syncAllPlayerIps", () -> syncAllPlayerIps()); @@ -6145,6 +6151,171 @@ public class WebManager { } } + /** + * 拉取PHP端修改的商品价格 + * 类似pullShopStock,但针对admin_buy_price/admin_sell_price + */ + private void pullShopPrices() { + new BukkitRunnable() { + @Override + public void run() { + try { + String urlStr = webBaseUrl + "/api/sync.php?action=pull_shop_prices&secret=" + + java.net.URLEncoder.encode(secretKey, "UTF-8"); + String json = doGet(urlStr); + if (json == null || !json.contains("\"success\":true")) return; + + // 解析商品价格列表 + int dataStart = json.indexOf("\"items\":"); + if (dataStart < 0) return; + String dataStr = json.substring(dataStart + 8); + int arrEnd = findMatchingBracket(dataStr, 0); + if (arrEnd < 0) return; + String arrStr = dataStr.substring(0, arrEnd + 1); + + // ★ 解析JSON数组为Map列表 + List> priceUpdates = new ArrayList<>(); + // 简单JSON解析:遍历每个对象 + int idx = 0; + while (true) { + int objStart = arrStr.indexOf("{", idx); + if (objStart < 0) break; + int objEnd = findMatchingBracket(arrStr, objStart); + if (objEnd < 0) break; + String objStr = arrStr.substring(objStart, objEnd + 1); + Map itemData = parseJsonObject(objStr); + if (itemData != null && itemData.containsKey("id")) { + priceUpdates.add(itemData); + } + idx = objEnd + 1; + } + + if (priceUpdates.isEmpty()) return; + + // ★ 调用ShopManager批量更新价格 + ShopManager shopManager = plugin.getShopManager(); + if (shopManager == null) { + plugin.getLogger().warning("[价格同步] ShopManager未初始化"); + return; + } + int updated = shopManager.updateItemPrices(priceUpdates); + if (updated > 0) { + plugin.getLogger().info("[价格同步] 应用PHP价格改动: " + updated + "个商品"); + } + + } catch (Exception e) { + plugin.getLogger().warning("[价格同步] 拉取价格异常: " + e.getMessage()); + } + } + }.runTaskAsynchronously(plugin); + } + + /** + * 清除PHP端的管理员价格标记 + */ + private void clearAdminPrices() { + try { + String urlStr = webBaseUrl + "/api/sync.php?action=clear_admin_prices&secret=" + + java.net.URLEncoder.encode(secretKey, "UTF-8"); + doGet(urlStr); + } catch (Exception e) { + // 静默处理 + } + } + + /** + * 简单的JSON对象解析(提取键值对) + * 支持字符串、数字、布尔值、null + */ + private Map parseJsonObject(String json) { + Map map = new HashMap<>(); + try { + // 去除首尾空白 + json = json.trim(); + if (!json.startsWith("{") || !json.endsWith("}")) return null; + json = json.substring(1, json.length() - 1).trim(); + if (json.isEmpty()) return map; + + int idx = 0; + while (idx < json.length()) { + // 跳过空白 + while (idx < json.length() && Character.isWhitespace(json.charAt(idx))) idx++; + if (idx >= json.length()) break; + + // 解析键 + if (json.charAt(idx) != '"') break; + int keyStart = idx + 1; + int keyEnd = json.indexOf('"', keyStart); + if (keyEnd < 0) break; + String key = json.substring(keyStart, keyEnd); + idx = keyEnd + 1; + + // 跳过冒号 + while (idx < json.length() && (json.charAt(idx) == ':' || Character.isWhitespace(json.charAt(idx)))) idx++; + if (idx >= json.length()) break; + + // 解析值 + char firstChar = json.charAt(idx); + Object value = null; + if (firstChar == '"') { + // 字符串值 + int valStart = idx + 1; + int valEnd = valStart; + while (valEnd < json.length()) { + if (json.charAt(valEnd) == '\\') { + valEnd += 2; + continue; + } + if (json.charAt(valEnd) == '"') break; + valEnd++; + } + value = json.substring(valStart, valEnd); + idx = valEnd + 1; + } else if (firstChar == 't' || firstChar == 'f') { + // 布尔值 + if (json.startsWith("true", idx)) { + value = true; + idx += 4; + } else if (json.startsWith("false", idx)) { + value = false; + idx += 5; + } + } else if (firstChar == 'n') { + // null + if (json.startsWith("null", idx)) { + value = null; + idx += 4; + } + } else if (firstChar == '-' || Character.isDigit(firstChar)) { + // 数字 + int numStart = idx; + while (idx < json.length() && (json.charAt(idx) == '-' || json.charAt(idx) == '.' || Character.isDigit(json.charAt(idx))) ) idx++; + String numStr = json.substring(numStart, idx); + try { + if (numStr.contains(".")) { + value = Double.parseDouble(numStr); + } else { + value = Long.parseLong(numStr); + } + } catch (NumberFormatException e) { + // 忽略 + } + } else { + // 未知类型,跳过到下一个逗号或结束 + while (idx < json.length() && json.charAt(idx) != ',' && json.charAt(idx) != '}') idx++; + } + + map.put(key, value); + + // 跳过逗号 + while (idx < json.length() && (json.charAt(idx) == ',' || Character.isWhitespace(json.charAt(idx)))) idx++; + } + } catch (Exception e) { + // 解析失败,返回部分结果 + } + return map; + } + /** * 更新本地商品库存 * @return 是否有任何库存被更新 @@ -6312,6 +6483,7 @@ public class WebManager { submitDbTask("通知-syncShopData", () -> syncShopData()); submitNormalDbTask("通知-pullPendingTransactions", () -> pullPendingTransactions()); submitNormalDbTask("通知-pullShopStock", () -> pullShopStock()); + submitNormalDbTask("通知-pullShopPrices", () -> pullShopPrices()); submitNormalDbTask("通知-pullBondChanges", () -> pullBondChanges()); // 删除通知文件 notifyFile.delete();