商品价格同步API:PHP admin改价→Java拉取应用
PHP端: - core.php: shop_items表新增admin_buy_price/admin_sell_price列 - admin.php: adminShopUpdate()支持修改buy_price/sell_price,写入admin覆盖字段 - sync.php: 新增pull_shop_prices/clear_admin_prices/check_shop_prices_changed接口 Java端: - ShopItem: buyPrice/sellPrice改为非final,添加setter - WebManager: 新增pullShopPrices()/clearAdminPrices()/parseJsonObject() - 所有定时器集成pullShopPrices调用 附带修复: - Main: handlePVPArenaCommand重定向到pvpManager.onCommand - PVPArenaManager: 适配Paper 1.21.4 ChunkGenerator API - DatabaseManager: 新增getInventoryBackup(String)方法 - ShopItem: 添加setBuyPrice/setSellPrice
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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<String> filterTab(List<String> 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<String> inPVPArena = ConcurrentHashMap.newKeySet();
|
||||
|
||||
// 玩家背包备份缓存 (玩家名 -> 备份数据)
|
||||
private final Map<String, InventoryBackup> inventoryBackups = new ConcurrentHashMap<>();
|
||||
|
||||
// PVP装备列表 (管理员可配置)
|
||||
private final List<ItemStack> 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<ItemStack> getPVPEquipment() {
|
||||
return Collections.unmodifiableList(pvpEquipment);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加PVP装备
|
||||
*/
|
||||
public void addPVPEquipment(ItemStack item) {
|
||||
pvpEquipment.add(item);
|
||||
saveConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有玩家的PVP状态
|
||||
*/
|
||||
public void clearAllPVPArenaStates() {
|
||||
inPVPArena.clear();
|
||||
inventoryBackups.clear();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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<Map<String, Object>> items) {
|
||||
int updated = 0;
|
||||
for (Map<String, Object> 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;
|
||||
|
||||
@@ -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<Map<String, Object>> 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<String, Object> 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<String, Object> parseJsonObject(String json) {
|
||||
Map<String, Object> 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();
|
||||
|
||||
Reference in New Issue
Block a user