修复宝箱自定义物品回收问题,新增拦截所有自定义物品投入容器
This commit is contained in:
@@ -0,0 +1,784 @@
|
||||
package Sdf1_game;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.Action;
|
||||
import org.bukkit.event.block.BlockBreakEvent;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.event.inventory.InventoryType;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class TreasureListener implements Listener {
|
||||
|
||||
private final Main plugin;
|
||||
private final TreasureManager manager;
|
||||
|
||||
public TreasureListener(Main plugin,
|
||||
TreasureManager manager) {
|
||||
this.plugin = plugin;
|
||||
this.manager = manager;
|
||||
}
|
||||
// ★ 玩家攻击玩家时计数
|
||||
@org.bukkit.event.EventHandler
|
||||
public void onDamage(
|
||||
org.bukkit.event.entity
|
||||
.EntityDamageByEntityEvent e) {
|
||||
|
||||
if (!(e.getDamager()
|
||||
instanceof org.bukkit.entity.Player))
|
||||
return;
|
||||
if (!(e.getEntity()
|
||||
instanceof org.bukkit.entity.Player))
|
||||
return;
|
||||
|
||||
org.bukkit.entity.Player attacker =
|
||||
(org.bukkit.entity.Player)
|
||||
e.getDamager();
|
||||
ItemStack hand =
|
||||
attacker.getInventory()
|
||||
.getItemInMainHand();
|
||||
|
||||
if (hand == null) return;
|
||||
if (!TreasureInventory
|
||||
.isCustomItem(hand)) return;
|
||||
|
||||
String[] mark =
|
||||
TreasureInventory.parseMark(hand);
|
||||
if (mark == null) return;
|
||||
|
||||
String owner = mark[0];
|
||||
String region = mark[1];
|
||||
String rName = mark[2];
|
||||
int maxUse =
|
||||
Integer.parseInt(mark[3]);
|
||||
int useCount =
|
||||
Integer.parseInt(mark[4]) + 1;
|
||||
|
||||
// ★ 只有攻击上限>0的物品才计数
|
||||
if (maxUse <= 0) return;
|
||||
|
||||
// ★ 检查归属
|
||||
if (!owner.equalsIgnoreCase(
|
||||
attacker.getName())) return;
|
||||
|
||||
plugin.getLogger().info(
|
||||
"[寻宝] ★攻击: "
|
||||
+ attacker.getName()
|
||||
+ " " + rName
|
||||
+ " " + useCount
|
||||
+ "/" + maxUse);
|
||||
|
||||
// ★ 更新lore里的次数
|
||||
ItemMeta meta = hand.getItemMeta();
|
||||
if (meta != null && meta.hasLore()) {
|
||||
List<Component> lore = meta.lore();
|
||||
for (int i = 0;
|
||||
i < lore.size(); i++) {
|
||||
String line =
|
||||
net.kyori.adventure.text
|
||||
.serializer.legacy
|
||||
.LegacyComponentSerializer
|
||||
.legacySection()
|
||||
.serialize(lore.get(i));
|
||||
if (line.contains("§0§kCUSTOM")) {
|
||||
String newMark =
|
||||
"§0§kCUSTOM|"
|
||||
+ owner + "|"
|
||||
+ region + "|"
|
||||
+ rName + "|"
|
||||
+ maxUse + "|"
|
||||
+ useCount;
|
||||
lore.set(i,
|
||||
Component.text(newMark));
|
||||
break;
|
||||
}
|
||||
}
|
||||
meta.lore(lore);
|
||||
hand.setItemMeta(meta);
|
||||
}
|
||||
|
||||
// ★ 打印给玩家看
|
||||
attacker.sendMessage(
|
||||
"§e[寻宝] " + rName
|
||||
+ " 攻击次数: "
|
||||
+ useCount + "/" + maxUse);
|
||||
|
||||
// 达到上限 → 回收
|
||||
if (useCount >= maxUse) {
|
||||
attacker.getInventory()
|
||||
.setItemInMainHand(null);
|
||||
plugin.getTreasureManager()
|
||||
.removeClaim(owner, rName,
|
||||
region);
|
||||
attacker.sendMessage(
|
||||
"§c[寻宝] " + rName
|
||||
+ " 攻击次数用完,已回收");
|
||||
attacker.getWorld().dropItemNaturally(
|
||||
attacker.getLocation(),
|
||||
new ItemStack(Material.AIR));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ★ 丢弃拦截
|
||||
@org.bukkit.event.EventHandler
|
||||
public void onDrop(
|
||||
org.bukkit.event.player
|
||||
.PlayerDropItemEvent e) {
|
||||
if (TreasureInventory.isCustomItem(
|
||||
e.getItemDrop().getItemStack())) {
|
||||
e.setCancelled(true);
|
||||
e.getPlayer().sendMessage(
|
||||
"§c[寻宝] 寻宝物品不可丢弃");
|
||||
}
|
||||
}
|
||||
// ★ 死亡清除
|
||||
@org.bukkit.event.EventHandler
|
||||
public void onDeath(
|
||||
org.bukkit.event.entity
|
||||
.PlayerDeathEvent e) {
|
||||
org.bukkit.entity.Player player =
|
||||
e.getEntity();
|
||||
for (int i = 0;
|
||||
i < player.getInventory()
|
||||
.getSize(); i++) {
|
||||
ItemStack item =
|
||||
player.getInventory()
|
||||
.getItem(i);
|
||||
if (item == null) continue;
|
||||
if (!TreasureInventory
|
||||
.isCustomItem(item)) continue;
|
||||
e.getDrops().remove(item);
|
||||
player.getInventory()
|
||||
.setItem(i, null);
|
||||
}
|
||||
}
|
||||
|
||||
// ★ 物品掉落到地上时检测(丢弃后立即清除)
|
||||
@org.bukkit.event.EventHandler
|
||||
public void onItemSpawn(
|
||||
org.bukkit.event.entity
|
||||
.ItemSpawnEvent e) {
|
||||
ItemStack item =
|
||||
e.getEntity().getItemStack();
|
||||
if (!TreasureInventory
|
||||
.isCustomItem(item)) return;
|
||||
|
||||
// ★ 延迟1秒后检查,如果还在地上就销毁
|
||||
// (已捡起的不会被销毁)
|
||||
final org.bukkit.Location loc =
|
||||
e.getLocation();
|
||||
final String itemKey =
|
||||
item.getType().name()
|
||||
+ "_" + item.getAmount();
|
||||
org.bukkit.Bukkit.getScheduler()
|
||||
.runTaskLater(plugin, () -> {
|
||||
for (org.bukkit.entity.Entity ent :
|
||||
loc.getChunk()
|
||||
.getEntities()) {
|
||||
if (ent instanceof
|
||||
org.bukkit.entity.Item) {
|
||||
org.bukkit.entity.Item di =
|
||||
(org.bukkit.entity.Item)
|
||||
ent;
|
||||
if (TreasureInventory
|
||||
.isCustomItem(
|
||||
di.getItemStack())) {
|
||||
String[] mark =
|
||||
TreasureInventory
|
||||
.parseMark(
|
||||
di.getItemStack());
|
||||
if (mark != null) {
|
||||
// 清除领取记录
|
||||
plugin
|
||||
.getTreasureManager()
|
||||
.removeClaim(
|
||||
mark[0],
|
||||
mark[2],
|
||||
mark[1]);
|
||||
}
|
||||
di.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 20L);
|
||||
}
|
||||
|
||||
// ===== 圈地工具选点 =====
|
||||
|
||||
@EventHandler
|
||||
public void onToolClick(PlayerInteractEvent e) {
|
||||
Player p = e.getPlayer();
|
||||
|
||||
// 只处理主手
|
||||
if (e.getHand() != EquipmentSlot.HAND)
|
||||
return;
|
||||
|
||||
if (!TreasureRegion.isTool(
|
||||
p.getInventory().getItemInMainHand()))
|
||||
return;
|
||||
|
||||
Block b = e.getClickedBlock();
|
||||
if (b == null) return;
|
||||
|
||||
e.setCancelled(true);
|
||||
Location loc = b.getLocation();
|
||||
TreasureRegion tr =
|
||||
TreasureRegion.get(p.getName());
|
||||
|
||||
if (e.getAction() == Action.LEFT_CLICK_BLOCK) {
|
||||
// A点
|
||||
tr.setPos1(loc);
|
||||
int ex = tr.getExpand();
|
||||
p.sendMessage("§a§l[圈地] §7A点已设置: §e"
|
||||
+ loc.getBlockX() + ","
|
||||
+ loc.getBlockY() + ","
|
||||
+ loc.getBlockZ());
|
||||
p.sendMessage("§7扩展范围: §e"
|
||||
+ ex + "§7格");
|
||||
p.sendMessage("§7输入 §e/寻宝 expand <数字> "
|
||||
+ "§7调整范围");
|
||||
p.sendMessage("§7输入 §e/寻宝 区域名 "
|
||||
+ "§7命名并保存");
|
||||
|
||||
} else if (e.getAction()
|
||||
== Action.RIGHT_CLICK_BLOCK) {
|
||||
tr.setPos2(loc);
|
||||
p.sendMessage("§a§l[圈地] §7B点已设置: §e"
|
||||
+ loc.getBlockX() + ","
|
||||
+ loc.getBlockY() + ","
|
||||
+ loc.getBlockZ());
|
||||
// ★ 设置B点后自动显示边框
|
||||
tr.showBorder();
|
||||
showPreview(p, tr);
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 右键寻宝宝箱 =====
|
||||
|
||||
@EventHandler
|
||||
public void onChestInteract(
|
||||
PlayerInteractEvent e) {
|
||||
if (e.getAction() != Action.RIGHT_CLICK_BLOCK)
|
||||
return;
|
||||
if (e.getHand() != EquipmentSlot.HAND) return;
|
||||
|
||||
Block b = e.getClickedBlock();
|
||||
if (b == null || b.getType() != Material.CHEST)
|
||||
return;
|
||||
Location loc = b.getLocation();
|
||||
if (!manager.isTreasure(loc)) return;
|
||||
|
||||
Player p = e.getPlayer();
|
||||
e.setCancelled(true);
|
||||
|
||||
String regionName = null;
|
||||
for (Map.Entry<Location, String> en
|
||||
: manager.getActiveChests().entrySet()) {
|
||||
if (en.getKey().equals(loc)) {
|
||||
regionName = en.getValue();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
b.setType(Material.AIR);
|
||||
manager.removeChest(loc);
|
||||
|
||||
if (regionName != null) {
|
||||
TreasureConfig tc =
|
||||
manager.getConfigs().get(regionName);
|
||||
if (tc != null) {
|
||||
TreasureInventory.openTreasure(
|
||||
p, tc, loc);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
// ★ 玩家下线时标记待回收
|
||||
@org.bukkit.event.EventHandler
|
||||
public void onQuit(
|
||||
org.bukkit.event.player.PlayerQuitEvent e) {
|
||||
String name = e.getPlayer().getName();
|
||||
// 标记该玩家有物品需要在上线时回收
|
||||
plugin.getTreasureManager()
|
||||
.getPendingReclaims().add(name);
|
||||
}
|
||||
|
||||
// ★ 寻宝GUI点击处理
|
||||
@org.bukkit.event.EventHandler(priority =
|
||||
org.bukkit.event.EventPriority.HIGHEST,
|
||||
ignoreCancelled = true)
|
||||
public void onTreasureGUI(
|
||||
org.bukkit.event.inventory
|
||||
.InventoryClickEvent e) {
|
||||
String title = e.getView().getTitle();
|
||||
if (!title.contains("寻宝结果")) return;
|
||||
|
||||
int slot = e.getRawSlot();
|
||||
if (slot < 0) return;
|
||||
e.setCancelled(true);
|
||||
if (e.getInventory().getSize() != 54) return;
|
||||
|
||||
org.bukkit.entity.Player player =
|
||||
(org.bukkit.entity.Player)
|
||||
e.getWhoClicked();
|
||||
Main plugin = (Main) org.bukkit.Bukkit
|
||||
.getPluginManager()
|
||||
.getPlugin("Sdf1_game");
|
||||
if (plugin == null) return;
|
||||
|
||||
TreasureManager tm =
|
||||
plugin.getTreasureManager();
|
||||
TreasureData td =
|
||||
tm.getPlayerTreasureData(
|
||||
player.getName());
|
||||
if (td == null) return;
|
||||
|
||||
// ★ 刷新按钮(49格)
|
||||
if (slot == 49 && !td.isRefreshed) {
|
||||
int cost = td.config.refreshBondCost;
|
||||
BondBridge bb =
|
||||
plugin.getBondBridge();
|
||||
int current = 0;
|
||||
if (bb != null && bb.isHooked()) {
|
||||
current = bb.getBonds(
|
||||
player.getName());
|
||||
}
|
||||
if (current < cost) {
|
||||
player.sendMessage(
|
||||
"§c[寻宝] 债券不足,需要"
|
||||
+ cost
|
||||
+ "张,当前"
|
||||
+ current + "张");
|
||||
return;
|
||||
}
|
||||
if (bb != null && bb.isHooked()) {
|
||||
bb.addBonds(player.getName(),
|
||||
-cost, "寻宝刷新");
|
||||
}
|
||||
tm.markRefreshed(td.chestLoc);
|
||||
player.closeInventory();
|
||||
TreasureInventory.openRefreshed(
|
||||
player, td.config,
|
||||
td.chestLoc);
|
||||
player.sendMessage(
|
||||
"§e[寻宝] 已刷新!消耗"
|
||||
+ cost + "债券");
|
||||
return;
|
||||
}
|
||||
|
||||
// ★ 确认领取按钮(50格)
|
||||
// 确认领取(50格)
|
||||
if (slot == 50) {
|
||||
player.closeInventory();
|
||||
// ★ 只调用一次
|
||||
TreasureInventory.giveRewards(
|
||||
player, td.config,
|
||||
td.bondAmount, td.rolled);
|
||||
tm.clearPlayerTreasureData(
|
||||
player.getName());
|
||||
return;
|
||||
}
|
||||
|
||||
// ★ 其他槽位全部取消(只展示)
|
||||
}
|
||||
|
||||
// ===== 阻止破坏宝箱 =====
|
||||
|
||||
@EventHandler
|
||||
public void onBreak(BlockBreakEvent e) {
|
||||
if (e.getBlock().getType() != Material.CHEST)
|
||||
return;
|
||||
if (manager.isTreasure(
|
||||
e.getBlock().getLocation())) {
|
||||
e.setCancelled(true);
|
||||
e.getPlayer().sendMessage(
|
||||
"§c宝箱不能破坏");
|
||||
}
|
||||
}
|
||||
|
||||
// ===== GUI点击 =====
|
||||
|
||||
@EventHandler
|
||||
public void onInventoryClick(
|
||||
InventoryClickEvent e) {
|
||||
if (e.getInventory().getHolder()
|
||||
instanceof TreasureInventory.GuiHolder) {
|
||||
TreasureInventory.handleClick(
|
||||
e, plugin.getBondBridge(), plugin);
|
||||
}
|
||||
}
|
||||
|
||||
@org.bukkit.event.EventHandler(priority =
|
||||
org.bukkit.event.EventPriority.HIGHEST)
|
||||
public void onInvClick(
|
||||
org.bukkit.event.inventory
|
||||
.InventoryClickEvent e) {
|
||||
if (e.getWhoClicked() == null) return;
|
||||
org.bukkit.entity.Player p =
|
||||
(org.bukkit.entity.Player)
|
||||
e.getWhoClicked();
|
||||
Main plugin = (Main) Bukkit
|
||||
.getPluginManager()
|
||||
.getPlugin("Sdf1_game");
|
||||
String title = e.getView().getTitle();
|
||||
|
||||
// ========== 1. 寻宝GUI ==========
|
||||
if (title.contains("寻宝结果")) {
|
||||
e.setCancelled(true);
|
||||
int slot = e.getRawSlot();
|
||||
if (slot < 0) return;
|
||||
if (e.getInventory().getSize() != 54)
|
||||
return;
|
||||
handleTreasureGUI(p, slot, e);
|
||||
return;
|
||||
}
|
||||
|
||||
// ========== 2. 检测自定义物品 ==========
|
||||
ItemStack checkItem = null;
|
||||
|
||||
// 光标有物品(普通点击)
|
||||
if (e.getCursor() != null
|
||||
&& e.getCursor().getType()
|
||||
!= Material.AIR) {
|
||||
checkItem = e.getCursor();
|
||||
}
|
||||
|
||||
// shift-click
|
||||
if (checkItem == null
|
||||
&& e.isShiftClick()
|
||||
&& e.getCurrentItem() != null
|
||||
&& e.getCurrentItem().getType()
|
||||
!= Material.AIR) {
|
||||
checkItem = e.getCurrentItem();
|
||||
}
|
||||
|
||||
// ========== 3. 非自定义物品直接放行 ==========
|
||||
if (checkItem == null) return;
|
||||
if (!TreasureInventory
|
||||
.isCustomItem(checkItem)) return;
|
||||
|
||||
// ========== 4. 确认是容器 ==========
|
||||
InventoryType topType = e.getView()
|
||||
.getTopInventory().getType();
|
||||
if (topType == InventoryType.PLAYER
|
||||
|| topType == InventoryType.CRAFTING
|
||||
|| topType == InventoryType.CREATIVE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ========== 5. 自定义物品+容器 → 拦截 ==========
|
||||
e.setCancelled(true);
|
||||
|
||||
plugin.getLogger().info(
|
||||
"[寻宝] ★★★拦截: " + p.getName()
|
||||
+ " 物品=" + checkItem.getType()
|
||||
+ " 容器=" + topType);
|
||||
|
||||
// 非本人→直接没收
|
||||
String[] mark = TreasureInventory
|
||||
.parseMark(checkItem);
|
||||
if (mark != null
|
||||
&& !mark[0].equalsIgnoreCase(
|
||||
p.getName())) {
|
||||
checkItem.setAmount(0);
|
||||
p.getOpenInventory().setCursor(null);
|
||||
p.sendMessage(
|
||||
"§c[寻宝] 该物品不属于你,已没收");
|
||||
return;
|
||||
}
|
||||
|
||||
// ★ 事不过三
|
||||
boolean confiscate = plugin
|
||||
.getTreasureManager()
|
||||
.onContainerBlock(p.getName());
|
||||
|
||||
if (confiscate) {
|
||||
// 第3次:没收
|
||||
String itemName = mark != null
|
||||
? mark[2] : "?";
|
||||
String region = mark != null
|
||||
? mark[1] : "?";
|
||||
|
||||
checkItem.setAmount(0);
|
||||
p.getOpenInventory().setCursor(null);
|
||||
|
||||
plugin.getTreasureManager()
|
||||
.unclaim(p.getName(),
|
||||
itemName, region);
|
||||
|
||||
p.sendMessage(
|
||||
"§c[寻宝] 多次违规,"
|
||||
+ itemName + " 已被没收!");
|
||||
} else {
|
||||
// 第1-2次:退回
|
||||
if (p.getOpenInventory()
|
||||
.getCursor() != null
|
||||
&& p.getOpenInventory()
|
||||
.getCursor().getType()
|
||||
!= Material.AIR) {
|
||||
ItemStack cur = p.getOpenInventory()
|
||||
.getCursor();
|
||||
p.getOpenInventory().setCursor(null);
|
||||
p.getInventory().addItem(cur);
|
||||
}
|
||||
int count = plugin
|
||||
.getTreasureManager()
|
||||
.getContainerBlockCount(
|
||||
p.getName());
|
||||
p.sendMessage(
|
||||
"§c[寻宝] 不可放入容器!"
|
||||
+ "第" + count + "次警告("
|
||||
+ (3 - count)
|
||||
+ "次后没收)");
|
||||
}
|
||||
}
|
||||
|
||||
// ★ 寻宝GUI处理
|
||||
private void handleTreasureGUI(
|
||||
org.bukkit.entity.Player player,
|
||||
int slot,
|
||||
org.bukkit.event.inventory
|
||||
.InventoryClickEvent e) {
|
||||
Main plugin = (Main) org.bukkit.Bukkit
|
||||
.getPluginManager()
|
||||
.getPlugin("Sdf1_game");
|
||||
if (plugin == null) return;
|
||||
TreasureManager tm =
|
||||
plugin.getTreasureManager();
|
||||
TreasureData td =
|
||||
tm.getPlayerTreasureData(
|
||||
player.getName());
|
||||
if (td == null) return;
|
||||
|
||||
// 刷新按钮(49格)
|
||||
if (slot == 49 && !td.isRefreshed) {
|
||||
int cost = td.config.refreshBondCost;
|
||||
BondBridge bb =
|
||||
plugin.getBondBridge();
|
||||
int current = 0;
|
||||
if (bb != null && bb.isHooked()) {
|
||||
current = bb.getBonds(
|
||||
player.getName());
|
||||
}
|
||||
if (current < cost) {
|
||||
player.sendMessage(
|
||||
"§c[寻宝] 债券不足,需要"
|
||||
+ cost + "张");
|
||||
return;
|
||||
}
|
||||
if (bb != null && bb.isHooked()) {
|
||||
bb.addBonds(player.getName(),
|
||||
-cost, "寻宝刷新");
|
||||
}
|
||||
tm.markRefreshed(td.chestLoc);
|
||||
player.closeInventory();
|
||||
TreasureInventory.openRefreshed(
|
||||
player, td.config,
|
||||
td.chestLoc);
|
||||
player.sendMessage(
|
||||
"§e[寻宝] 已刷新!消耗"
|
||||
+ cost + "债券");
|
||||
return;
|
||||
}
|
||||
|
||||
// 确认领取(50格)
|
||||
if (slot == 50) {
|
||||
player.closeInventory();
|
||||
TreasureInventory.giveRewards(
|
||||
player, td.config,
|
||||
td.bondAmount, td.rolled);
|
||||
tm.clearPlayerTreasureData(
|
||||
player.getName());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// ★ 拖拽拦截
|
||||
@org.bukkit.event.EventHandler(priority =
|
||||
org.bukkit.event.EventPriority.HIGHEST)
|
||||
public void onDrag(
|
||||
org.bukkit.event.inventory
|
||||
.InventoryDragEvent e) {
|
||||
String title = e.getView().getTitle();
|
||||
|
||||
// ★ 垃圾站拖拽拦截
|
||||
if (title.contains("垃圾回收站")) {
|
||||
for (ItemStack item :
|
||||
e.getNewItems().values()) {
|
||||
if (item != null
|
||||
&& TreasureInventory
|
||||
.isCustomItem(item)) {
|
||||
e.setCancelled(true);
|
||||
Main plugin = (Main) Bukkit
|
||||
.getPluginManager()
|
||||
.getPlugin("Sdf1_game");
|
||||
if (plugin != null) {
|
||||
plugin.getTreasureManager()
|
||||
.onContainerBlock(
|
||||
e.getWhoClicked()
|
||||
.getName());
|
||||
}
|
||||
e.getWhoClicked().sendMessage(
|
||||
"§c[寻宝] 不可拖入垃圾站");
|
||||
return;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// ★ 通用容器拖拽拦截
|
||||
InventoryType type = e.getView()
|
||||
.getTopInventory().getType();
|
||||
if (type != InventoryType.PLAYER
|
||||
&& type != InventoryType.CRAFTING
|
||||
&& type != InventoryType.CREATIVE) {
|
||||
for (ItemStack item :
|
||||
e.getNewItems().values()) {
|
||||
if (item != null
|
||||
&& TreasureInventory
|
||||
.isCustomItem(item)) {
|
||||
e.setCancelled(true);
|
||||
e.getWhoClicked().sendMessage(
|
||||
"§c[寻宝] 不可放入容器");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ★ 铁砧拦截
|
||||
@org.bukkit.event.EventHandler(priority =
|
||||
org.bukkit.event.EventPriority.HIGHEST)
|
||||
public void onAnvil(
|
||||
org.bukkit.event.inventory
|
||||
.PrepareAnvilEvent e) {
|
||||
ItemStack first =
|
||||
e.getInventory().getItem(0);
|
||||
if (first != null
|
||||
&& TreasureInventory
|
||||
.isCustomItem(first)) {
|
||||
e.setResult(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ★ 右键容器拦截
|
||||
@org.bukkit.event.EventHandler(priority =
|
||||
org.bukkit.event.EventPriority.HIGHEST)
|
||||
public void onInteract(
|
||||
org.bukkit.event.player
|
||||
.PlayerInteractEvent e) {
|
||||
if (e.getItem() == null) return;
|
||||
if (!TreasureInventory
|
||||
.isCustomItem(e.getItem())) return;
|
||||
if (!e.getAction().name()
|
||||
.contains("RIGHT_CLICK")) return;
|
||||
if (e.getClickedBlock() == null) return;
|
||||
String bt = e.getClickedBlock()
|
||||
.getType().name();
|
||||
if (bt.contains("SHULKER")
|
||||
|| bt.contains("CHEST")
|
||||
|| bt.equals("BARREL")
|
||||
|| bt.equals("ENDER_CHEST")
|
||||
|| bt.equals("HOPPER")
|
||||
|| bt.equals("DROPPER")
|
||||
|| bt.equals("DISPENSER")) {
|
||||
e.setCancelled(true);
|
||||
e.getPlayer().sendMessage(
|
||||
"§c[寻宝] 不可放入容器");
|
||||
}
|
||||
}
|
||||
// ★ 上线时扫描背包回收
|
||||
@org.bukkit.event.EventHandler
|
||||
public void onJoin(
|
||||
org.bukkit.event.player
|
||||
.PlayerJoinEvent e) {
|
||||
plugin.getTreasureManager()
|
||||
.reclaimPlayerItems(
|
||||
e.getPlayer().getName());
|
||||
}
|
||||
|
||||
// ★ 手持寻宝物品切换槽位时检查归属
|
||||
@org.bukkit.event.EventHandler
|
||||
public void onHeld(
|
||||
org.bukkit.event.player
|
||||
.PlayerItemHeldEvent e) {
|
||||
ItemStack item =
|
||||
e.getPlayer().getInventory()
|
||||
.getItem(
|
||||
e.getNewSlot());
|
||||
if (item == null) return;
|
||||
if (!TreasureInventory
|
||||
.isCustomItem(item)) return;
|
||||
|
||||
String[] mark =
|
||||
TreasureInventory.parseMark(item);
|
||||
if (mark == null) return;
|
||||
|
||||
if (!mark[0].equalsIgnoreCase(
|
||||
e.getPlayer().getName())) {
|
||||
// 不是本人的→立即回收
|
||||
e.getPlayer().getInventory()
|
||||
.setItem(e.getNewSlot(), null);
|
||||
plugin.getTreasureManager()
|
||||
.removeClaim(mark[0], mark[2],
|
||||
mark[1]);
|
||||
e.getPlayer().sendMessage(
|
||||
"§c[寻宝] " + mark[2]
|
||||
+ " 不属于你,已回收");
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 预览区域范围 =====
|
||||
|
||||
private void showPreview(Player p,
|
||||
TreasureRegion tr) {
|
||||
Location a = tr.getPos1();
|
||||
Location b = tr.getPos2();
|
||||
if (a == null || b == null) {
|
||||
p.sendMessage("§c请先设置A点");
|
||||
return;
|
||||
}
|
||||
|
||||
int ex = tr.getExpand();
|
||||
int minX = Math.min(a.getBlockX(),
|
||||
b.getBlockX()) - ex;
|
||||
int maxX = Math.max(a.getBlockX(),
|
||||
b.getBlockX()) + ex;
|
||||
int minZ = Math.min(a.getBlockZ(),
|
||||
b.getBlockZ()) - ex;
|
||||
int maxZ = Math.max(a.getBlockZ(),
|
||||
b.getBlockZ()) + ex;
|
||||
|
||||
p.sendMessage("§e§l[圈地] 区域预览:");
|
||||
p.sendMessage("§7世界: §e"
|
||||
+ a.getWorld().getName());
|
||||
p.sendMessage("§7范围: §e["
|
||||
+ minX + "," + minZ + "] ~ ["
|
||||
+ maxX + "," + maxZ + "]");
|
||||
p.sendMessage("§7大小: §e"
|
||||
+ (maxX - minX + 1) + " x "
|
||||
+ (maxZ - minZ + 1));
|
||||
p.sendMessage("§7扩展: §e" + ex + "格");
|
||||
p.sendMessage("§7输入 §e/寻宝 expand <数字> "
|
||||
+ "§7调整");
|
||||
p.sendMessage("§7输入 §e/寻宝 区域名 "
|
||||
+ "§7保存");
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -63,6 +63,9 @@ public class FileGenerator {
|
||||
l.add("罚时结束提示: §a罚时已结束");
|
||||
l.add("# 消息前缀");
|
||||
l.add("消息前缀: §e§l【快问快答】§r");
|
||||
l.add("# 欢迎游玩草原探险服务器 ");
|
||||
l.add("# 服务器ip:mc2.ypshidifu.cn");
|
||||
l.add("# 端口:30679,Java版免输入");
|
||||
return l;
|
||||
}
|
||||
|
||||
@@ -95,6 +98,7 @@ public class FileGenerator {
|
||||
l.add("| M.C的全称是什么? | Mojang Studios | 10 |");
|
||||
return l;
|
||||
}
|
||||
|
||||
public static void ensureTreasureConfig(
|
||||
File dir) {
|
||||
dir.mkdirs();
|
||||
@@ -106,11 +110,21 @@ public class FileGenerator {
|
||||
new FileOutputStream(f),
|
||||
StandardCharsets.UTF_8);
|
||||
w.write("# 寻宝全局设置\n");
|
||||
w.write("# 保底债券: 是/否\n");
|
||||
w.write("保底债券: 是\n");
|
||||
w.write("# 随机奖励数量范围\n");
|
||||
w.write("随机数量: 1~3\n");
|
||||
w.write("# 刷新费用(债券)\n");
|
||||
w.write("刷新费用: 150\n");
|
||||
w.write("# 刷新最低奖励数\n");
|
||||
w.write("刷新最低奖励: 3\n");
|
||||
w.write("# 刷新次数(每个宝箱)\n");
|
||||
w.write("刷新次数: 1\n");
|
||||
w.write("# 欢迎游玩草原探险服务器");
|
||||
w.write("# 服务器ip:mc2.ypshidifu.cn");
|
||||
w.write("# 端口号30679,Java免输入");
|
||||
w.close();
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,6 +23,7 @@ public class Main extends JavaPlugin
|
||||
private QuizManager quizManager;
|
||||
private TreasureManager treasureManager;
|
||||
private BukkitTask spawnTask;
|
||||
private UpdateChecker updateChecker;
|
||||
|
||||
private static final Pattern SPEC_NUM =
|
||||
Pattern.compile(
|
||||
@@ -89,6 +90,11 @@ public class Main extends JavaPlugin
|
||||
treasureManager.forceCleanClaims();
|
||||
treasureManager.cleanResidualChests();
|
||||
}
|
||||
|
||||
// ★ 启动时异步检查更新(GitHub/Gitee双通道)
|
||||
updateChecker = new UpdateChecker(this);
|
||||
updateChecker.checkOnEnable();
|
||||
|
||||
getLogger().info("=== Sdf1_game v1.0 ===");
|
||||
getLogger().info("债券桥接: "
|
||||
+ (bondBridge.isHooked()
|
||||
@@ -134,7 +140,11 @@ public class Main extends JavaPlugin
|
||||
treasureManager.forceCleanClaims();
|
||||
treasureManager.cleanResidualChests();
|
||||
}
|
||||
saveConfig();
|
||||
// 仅在config.yml已存在时才保存(防止自动创建空白文件)
|
||||
if (new File(getDataFolder(), "config.yml").exists()) {
|
||||
saveConfig();
|
||||
}
|
||||
|
||||
getLogger().info("\n" +
|
||||
" __ __ _ \n" +
|
||||
" \\ \\ / / | | \n" +
|
||||
@@ -214,6 +224,12 @@ public class Main extends JavaPlugin
|
||||
sender.sendMessage(bondBridge.isHooked()
|
||||
? "§a已连接" : "§c未连接");
|
||||
return true;
|
||||
case "update":
|
||||
if (updateChecker == null) {
|
||||
updateChecker = new UpdateChecker(this);
|
||||
}
|
||||
updateChecker.checkUpdate(sender);
|
||||
return true;
|
||||
default:
|
||||
sendQuizHelp(sender);
|
||||
return true;
|
||||
@@ -427,6 +443,35 @@ public class Main extends JavaPlugin
|
||||
case "off":
|
||||
case "关闭":
|
||||
return handleBorder(s, a);
|
||||
case "public":
|
||||
case "公布":
|
||||
// /寻宝 public → 公布全部最旧1个
|
||||
// /寻宝 public 主城区 → 公布指定区域
|
||||
String announceRegion = null;
|
||||
if (a.length >= 2) {
|
||||
announceRegion = a[1];
|
||||
}
|
||||
boolean ok = treasureManager
|
||||
.announceChest(
|
||||
announceRegion);
|
||||
if (ok) {
|
||||
s.sendMessage(
|
||||
"§a[寻宝] 已公布宝箱坐标");
|
||||
} else {
|
||||
s.sendMessage(
|
||||
"§c[寻宝] 当前没有"
|
||||
+ (announceRegion != null
|
||||
? "该区域的" : "")
|
||||
+ "宝箱");
|
||||
}
|
||||
return true;
|
||||
case "update":
|
||||
if (updateChecker == null) {
|
||||
updateChecker = new UpdateChecker(this);
|
||||
}
|
||||
updateChecker.checkUpdate(s);
|
||||
return true;
|
||||
|
||||
default:
|
||||
return handleSetName(s, a);
|
||||
}
|
||||
@@ -650,7 +695,6 @@ public class Main extends JavaPlugin
|
||||
p2.getBlockZ()) - ex;
|
||||
int maxZ = Math.max(p1.getBlockZ(),
|
||||
p2.getBlockZ()) + ex;
|
||||
// ★ 高度范围 = A/B点Y中较小的~较大的
|
||||
int y1 = p1.getBlockY();
|
||||
int y2 = p2.getBlockY();
|
||||
int hMin = Math.min(y1, y2);
|
||||
@@ -675,28 +719,6 @@ public class Main extends JavaPlugin
|
||||
+ hMin + "~Y" + hMax);
|
||||
s.sendMessage("§7使用 §e/寻宝 off "
|
||||
+ name + " §7关闭边框");
|
||||
s.sendMessage("§b§l\n欢迎游玩草原探险服务器");
|
||||
s.sendMessage("§b§l服务器ip:mc2.ypshidifu.cn\n端口30679");
|
||||
s.sendMessage("");
|
||||
}
|
||||
|
||||
boolean o1k = treasureManager.saveRegion(
|
||||
name,
|
||||
p1.getWorld().getName(),
|
||||
minX, minZ, maxX, maxZ,
|
||||
hMin, hMax);
|
||||
|
||||
if (ok) {
|
||||
tr.reset();
|
||||
s.sendMessage("§a区域「" + name
|
||||
+ "」已创建!");
|
||||
s.sendMessage("§7范围: ["
|
||||
+ minX + "," + minZ + "] ~ ["
|
||||
+ maxX + "," + maxZ + "]");
|
||||
s.sendMessage("§7使用 §e/寻宝 expand "
|
||||
+ name + " §7扩展");
|
||||
s.sendMessage("§7使用 §e/寻宝 return "
|
||||
+ name + " §7收缩");
|
||||
} else {
|
||||
s.sendMessage("§c创建失败");
|
||||
}
|
||||
@@ -748,6 +770,11 @@ public class Main extends JavaPlugin
|
||||
return bondBridge;
|
||||
}
|
||||
|
||||
// ★ 供集群更新调用
|
||||
public UpdateChecker getUpdateChecker() {
|
||||
return updateChecker;
|
||||
}
|
||||
|
||||
// ========== Tab ==========
|
||||
|
||||
@Override
|
||||
@@ -764,7 +791,7 @@ public class Main extends JavaPlugin
|
||||
String in = a[0].toLowerCase();
|
||||
for (String x : Arrays.asList(
|
||||
"start", "stop", "reload",
|
||||
"test", "info", "skip")) {
|
||||
"test", "info", "skip", "update")) {
|
||||
if (x.startsWith(in)) r.add(x);
|
||||
}
|
||||
return r;
|
||||
@@ -812,7 +839,8 @@ public class Main extends JavaPlugin
|
||||
"info", "list", "reload",
|
||||
"工具", "expand", "return",
|
||||
"on", "off", "开启", "关闭",
|
||||
"removeregion")) {
|
||||
"removeregion",
|
||||
"public", "公布", "update")) {
|
||||
if (x.startsWith(in)) r.add(x);
|
||||
}
|
||||
return r;
|
||||
@@ -825,7 +853,9 @@ public class Main extends JavaPlugin
|
||||
|| sub.equals("关闭")
|
||||
|| sub.equals("expand")
|
||||
|| sub.equals("return")
|
||||
|| sub.equals("removeregion")) {
|
||||
|| sub.equals("removeregion")
|
||||
|| sub.equals("public")
|
||||
|| sub.equals("公布")) {
|
||||
String in = a[1].toLowerCase();
|
||||
for (String n :
|
||||
treasureManager.getRegionNames()) {
|
||||
@@ -836,7 +866,6 @@ public class Main extends JavaPlugin
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (isT && a.length == 2) {
|
||||
String sub = a[0].toLowerCase();
|
||||
if (sub.equals("expand")
|
||||
@@ -852,13 +881,11 @@ public class Main extends JavaPlugin
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (isT && a.length == 2
|
||||
&& a[0].equalsIgnoreCase("expand")) {
|
||||
// 不补全,让玩家自己输数字
|
||||
return r;
|
||||
}
|
||||
// 打了包但还没测试。可以先测试,也可以先做return的功能要求。要先把大服务器开起来
|
||||
|
||||
if (isT && a.length == 2
|
||||
&& a[0].equalsIgnoreCase(
|
||||
"removeregion")) {
|
||||
@@ -872,7 +899,6 @@ public class Main extends JavaPlugin
|
||||
}
|
||||
|
||||
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ import java.io.OutputStreamWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.bukkit.Location;
|
||||
|
||||
|
||||
public class TreasureConfig {
|
||||
|
||||
@@ -33,7 +35,10 @@ public class TreasureConfig {
|
||||
// ★ 中文名映射:奖励索引→显示名
|
||||
public java.util.Map<Integer, String>
|
||||
rewardNames = new java.util.LinkedHashMap<>();
|
||||
|
||||
// ★ 刷新相关
|
||||
public int refreshBondCost = 150;
|
||||
public int refreshMinRewards = 3;
|
||||
public int refreshTimes = 1;
|
||||
|
||||
|
||||
public TreasureConfig(String name) {
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package Sdf1_game;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import java.util.List;
|
||||
|
||||
public class TreasureData {
|
||||
public TreasureConfig config;
|
||||
public Location chestLoc;
|
||||
public int bondAmount;
|
||||
public List<TreasureReward> rolled;
|
||||
public boolean isRefreshed;
|
||||
|
||||
public TreasureData(
|
||||
TreasureConfig config,
|
||||
Location chestLoc,
|
||||
int bondAmount,
|
||||
List<TreasureReward> rolled,
|
||||
boolean isRefreshed) {
|
||||
this.config = config;
|
||||
this.chestLoc = chestLoc;
|
||||
this.bondAmount = bondAmount;
|
||||
this.rolled = rolled;
|
||||
this.isRefreshed = isRefreshed;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
package Sdf1_game;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
@@ -9,7 +10,9 @@ import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.Action;
|
||||
import org.bukkit.event.block.BlockBreakEvent;
|
||||
import org.bukkit.event.block.BlockPlaceEvent;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.event.inventory.InventoryType;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
@@ -130,61 +133,40 @@ public class TreasureListener implements Listener {
|
||||
}
|
||||
|
||||
|
||||
// ★ 禁止丢弃寻宝物品
|
||||
// ★ 丢弃拦截
|
||||
@org.bukkit.event.EventHandler
|
||||
public void onDrop(
|
||||
org.bukkit.event.player
|
||||
.PlayerDropItemEvent e) {
|
||||
if (TreasureInventory
|
||||
.isCustomItem(
|
||||
e.getItemDrop().getItemStack())) {
|
||||
if (TreasureInventory.isCustomItem(
|
||||
e.getItemDrop().getItemStack())) {
|
||||
e.setCancelled(true);
|
||||
e.getPlayer().sendMessage(
|
||||
"§c[寻宝] 寻宝物品不可丢弃");
|
||||
}
|
||||
}
|
||||
// ★ 死亡时回收寻宝物品(不掉落)
|
||||
// ★ 死亡清除
|
||||
@org.bukkit.event.EventHandler
|
||||
public void onDeath(
|
||||
org.bukkit.event.entity
|
||||
.PlayerDeathEvent e) {
|
||||
org.bukkit.entity.Player player =
|
||||
e.getEntity();
|
||||
List<ItemStack> drops = e.getDrops();
|
||||
|
||||
// ★ 检查背包里所有物品
|
||||
ItemStack[] contents =
|
||||
player.getInventory().getContents();
|
||||
for (int i = 0;
|
||||
i < contents.length; i++) {
|
||||
ItemStack item = contents[i];
|
||||
i < player.getInventory()
|
||||
.getSize(); i++) {
|
||||
ItemStack item =
|
||||
player.getInventory()
|
||||
.getItem(i);
|
||||
if (item == null) continue;
|
||||
if (!TreasureInventory
|
||||
.isCustomItem(item)) continue;
|
||||
|
||||
String[] mark =
|
||||
TreasureInventory.parseMark(item);
|
||||
if (mark != null) {
|
||||
// 从掉落列表移除
|
||||
drops.remove(item);
|
||||
// 清除记录
|
||||
plugin.getTreasureManager()
|
||||
.removeClaim(mark[0],
|
||||
mark[2], mark[1]);
|
||||
}
|
||||
}
|
||||
// 清空背包里所有寻宝物品
|
||||
for (int i = 0;
|
||||
i < contents.length; i++) {
|
||||
ItemStack item = contents[i];
|
||||
if (item == null) continue;
|
||||
if (TreasureInventory
|
||||
.isCustomItem(item)) {
|
||||
player.getInventory()
|
||||
.setItem(i, null);
|
||||
}
|
||||
e.getDrops().remove(item);
|
||||
player.getInventory()
|
||||
.setItem(i, null);
|
||||
}
|
||||
}
|
||||
|
||||
// ★ 物品掉落到地上时检测(丢弃后立即清除)
|
||||
@org.bukkit.event.EventHandler
|
||||
public void onItemSpawn(
|
||||
@@ -320,7 +302,10 @@ public class TreasureListener implements Listener {
|
||||
TreasureConfig tc =
|
||||
manager.getConfigs().get(regionName);
|
||||
if (tc != null) {
|
||||
TreasureInventory.open(p, tc);
|
||||
TreasureInventory.openTreasure(
|
||||
p, tc, loc);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -334,6 +319,84 @@ public class TreasureListener implements Listener {
|
||||
.getPendingReclaims().add(name);
|
||||
}
|
||||
|
||||
// ★ 寻宝GUI点击处理
|
||||
@org.bukkit.event.EventHandler(priority =
|
||||
org.bukkit.event.EventPriority.HIGHEST,
|
||||
ignoreCancelled = true)
|
||||
public void onTreasureGUI(
|
||||
org.bukkit.event.inventory
|
||||
.InventoryClickEvent e) {
|
||||
String title = e.getView().getTitle();
|
||||
if (!title.contains("寻宝结果")) return;
|
||||
|
||||
int slot = e.getRawSlot();
|
||||
if (slot < 0) return;
|
||||
e.setCancelled(true);
|
||||
if (e.getInventory().getSize() != 54) return;
|
||||
|
||||
org.bukkit.entity.Player player =
|
||||
(org.bukkit.entity.Player)
|
||||
e.getWhoClicked();
|
||||
Main plugin = (Main) org.bukkit.Bukkit
|
||||
.getPluginManager()
|
||||
.getPlugin("Sdf1_game");
|
||||
if (plugin == null) return;
|
||||
|
||||
TreasureManager tm =
|
||||
plugin.getTreasureManager();
|
||||
TreasureData td =
|
||||
tm.getPlayerTreasureData(
|
||||
player.getName());
|
||||
if (td == null) return;
|
||||
|
||||
// ★ 刷新按钮(49格)
|
||||
if (slot == 49 && !td.isRefreshed) {
|
||||
int cost = td.config.refreshBondCost;
|
||||
BondBridge bb =
|
||||
plugin.getBondBridge();
|
||||
int current = 0;
|
||||
if (bb != null && bb.isHooked()) {
|
||||
current = bb.getBonds(
|
||||
player.getName());
|
||||
}
|
||||
if (current < cost) {
|
||||
player.sendMessage(
|
||||
"§c[寻宝] 债券不足,需要"
|
||||
+ cost
|
||||
+ "张,当前"
|
||||
+ current + "张");
|
||||
return;
|
||||
}
|
||||
if (bb != null && bb.isHooked()) {
|
||||
bb.addBonds(player.getName(),
|
||||
-cost, "寻宝刷新");
|
||||
}
|
||||
tm.markRefreshed(td.chestLoc);
|
||||
player.closeInventory();
|
||||
TreasureInventory.openRefreshed(
|
||||
player, td.config,
|
||||
td.chestLoc);
|
||||
player.sendMessage(
|
||||
"§e[寻宝] 已刷新!消耗"
|
||||
+ cost + "债券");
|
||||
return;
|
||||
}
|
||||
|
||||
// ★ 确认领取按钮(50格)
|
||||
// 确认领取(50格)
|
||||
if (slot == 50) {
|
||||
player.closeInventory();
|
||||
// ★ 只调用一次
|
||||
TreasureInventory.giveRewards(
|
||||
player, td.config,
|
||||
td.bondAmount, td.rolled);
|
||||
tm.clearPlayerTreasureData(
|
||||
player.getName());
|
||||
return;
|
||||
}
|
||||
|
||||
// ★ 其他槽位全部取消(只展示)
|
||||
}
|
||||
|
||||
// ===== 阻止破坏宝箱 =====
|
||||
|
||||
@@ -362,53 +425,247 @@ public class TreasureListener implements Listener {
|
||||
}
|
||||
|
||||
@org.bukkit.event.EventHandler(priority =
|
||||
org.bukkit.event.EventPriority.HIGH)
|
||||
public void onClick(
|
||||
org.bukkit.event.EventPriority.HIGHEST)
|
||||
public void onInvClick(
|
||||
org.bukkit.event.inventory
|
||||
.InventoryClickEvent e) {
|
||||
if (e.getCursor() == null) return;
|
||||
if (!TreasureInventory
|
||||
.isCustomItem(e.getCursor()))
|
||||
return;
|
||||
String type = e.getView()
|
||||
.getTopInventory().getType().name();
|
||||
if (!type.equals("PLAYER")
|
||||
&& !type.equals("CRAFTING")
|
||||
&& !type.equals("CREATIVE")) {
|
||||
if (e.getWhoClicked() == null) return;
|
||||
org.bukkit.entity.Player p =
|
||||
(org.bukkit.entity.Player)
|
||||
e.getWhoClicked();
|
||||
Main plugin = (Main) Bukkit
|
||||
.getPluginManager()
|
||||
.getPlugin("Sdf1_game");
|
||||
String title = e.getView().getTitle();
|
||||
|
||||
// ========== 1. 寻宝GUI ==========
|
||||
if (title.contains("寻宝结果")) {
|
||||
e.setCancelled(true);
|
||||
if (e.getWhoClicked()
|
||||
instanceof org.bukkit.entity.Player) {
|
||||
((org.bukkit.entity.Player)
|
||||
e.getWhoClicked())
|
||||
.sendMessage(
|
||||
"§c[寻宝] 不可放入容器");
|
||||
int slot = e.getRawSlot();
|
||||
if (slot < 0) return;
|
||||
if (e.getInventory().getSize() != 54)
|
||||
return;
|
||||
handleTreasureGUI(p, slot, e);
|
||||
return;
|
||||
}
|
||||
|
||||
// ========== 2. 检测自定义物品 ==========
|
||||
ItemStack checkItem = null;
|
||||
|
||||
// 光标有物品(普通点击)
|
||||
if (e.getCursor() != null
|
||||
&& e.getCursor().getType()
|
||||
!= Material.AIR) {
|
||||
checkItem = e.getCursor();
|
||||
}
|
||||
|
||||
// shift-click
|
||||
if (checkItem == null
|
||||
&& e.isShiftClick()
|
||||
&& e.getCurrentItem() != null
|
||||
&& e.getCurrentItem().getType()
|
||||
!= Material.AIR) {
|
||||
checkItem = e.getCurrentItem();
|
||||
}
|
||||
|
||||
// ========== 3. 非自定义物品直接放行 ==========
|
||||
if (checkItem == null) return;
|
||||
if (!TreasureInventory
|
||||
.isCustomItem(checkItem)) return;
|
||||
|
||||
// ========== 4. 确认是容器 ==========
|
||||
InventoryType topType = e.getView()
|
||||
.getTopInventory().getType();
|
||||
// 只放行玩家背包和创造模式背包,其他所有容器都拦截
|
||||
if (topType == InventoryType.PLAYER
|
||||
|| topType == InventoryType.CREATIVE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ========== 5. 自定义物品+容器 → 拦截 ==========
|
||||
e.setCancelled(true);
|
||||
|
||||
plugin.getLogger().info(
|
||||
"[寻宝] ★★★拦截: " + p.getName()
|
||||
+ " 物品=" + checkItem.getType()
|
||||
+ " 容器=" + topType);
|
||||
|
||||
// 非本人→直接没收
|
||||
String[] mark = TreasureInventory
|
||||
.parseMark(checkItem);
|
||||
if (mark != null
|
||||
&& !mark[0].equalsIgnoreCase(
|
||||
p.getName())) {
|
||||
checkItem.setAmount(0);
|
||||
p.getOpenInventory().setCursor(null);
|
||||
p.sendMessage(
|
||||
"§c[寻宝] 该物品不属于你,已没收");
|
||||
return;
|
||||
}
|
||||
|
||||
// ★ 事不过三
|
||||
boolean confiscate = plugin
|
||||
.getTreasureManager()
|
||||
.onContainerBlock(p.getName());
|
||||
|
||||
if (confiscate) {
|
||||
// 第3次:没收
|
||||
String itemName = mark != null
|
||||
? mark[2] : "?";
|
||||
String region = mark != null
|
||||
? mark[1] : "?";
|
||||
|
||||
checkItem.setAmount(0);
|
||||
p.getOpenInventory().setCursor(null);
|
||||
|
||||
plugin.getTreasureManager()
|
||||
.unclaim(p.getName(),
|
||||
itemName, region);
|
||||
|
||||
p.sendMessage(
|
||||
"§c[寻宝] 多次违规,"
|
||||
+ itemName + " 已被没收!");
|
||||
} else {
|
||||
// 第1-2次:退回
|
||||
if (p.getOpenInventory()
|
||||
.getCursor() != null
|
||||
&& p.getOpenInventory()
|
||||
.getCursor().getType()
|
||||
!= Material.AIR) {
|
||||
ItemStack cur = p.getOpenInventory()
|
||||
.getCursor();
|
||||
p.getOpenInventory().setCursor(null);
|
||||
p.getInventory().addItem(cur);
|
||||
}
|
||||
int count = plugin
|
||||
.getTreasureManager()
|
||||
.getContainerBlockCount(
|
||||
p.getName());
|
||||
p.sendMessage(
|
||||
"§c[寻宝] 不可放入容器!"
|
||||
+ "第" + count + "次警告("
|
||||
+ (3 - count)
|
||||
+ "次后没收)");
|
||||
}
|
||||
}
|
||||
|
||||
// ★ 寻宝GUI处理
|
||||
private void handleTreasureGUI(
|
||||
org.bukkit.entity.Player player,
|
||||
int slot,
|
||||
org.bukkit.event.inventory
|
||||
.InventoryClickEvent e) {
|
||||
Main plugin = (Main) org.bukkit.Bukkit
|
||||
.getPluginManager()
|
||||
.getPlugin("Sdf1_game");
|
||||
if (plugin == null) return;
|
||||
TreasureManager tm =
|
||||
plugin.getTreasureManager();
|
||||
TreasureData td =
|
||||
tm.getPlayerTreasureData(
|
||||
player.getName());
|
||||
if (td == null) return;
|
||||
|
||||
// 刷新按钮(49格)
|
||||
if (slot == 49 && !td.isRefreshed) {
|
||||
int cost = td.config.refreshBondCost;
|
||||
BondBridge bb =
|
||||
plugin.getBondBridge();
|
||||
int current = 0;
|
||||
if (bb != null && bb.isHooked()) {
|
||||
current = bb.getBonds(
|
||||
player.getName());
|
||||
}
|
||||
if (current < cost) {
|
||||
player.sendMessage(
|
||||
"§c[寻宝] 债券不足,需要"
|
||||
+ cost + "张");
|
||||
return;
|
||||
}
|
||||
if (bb != null && bb.isHooked()) {
|
||||
bb.addBonds(player.getName(),
|
||||
-cost, "寻宝刷新");
|
||||
}
|
||||
tm.markRefreshed(td.chestLoc);
|
||||
player.closeInventory();
|
||||
TreasureInventory.openRefreshed(
|
||||
player, td.config,
|
||||
td.chestLoc);
|
||||
player.sendMessage(
|
||||
"§e[寻宝] 已刷新!消耗"
|
||||
+ cost + "债券");
|
||||
return;
|
||||
}
|
||||
|
||||
// 确认领取(50格)
|
||||
if (slot == 50) {
|
||||
player.closeInventory();
|
||||
TreasureInventory.giveRewards(
|
||||
player, td.config,
|
||||
td.bondAmount, td.rolled);
|
||||
tm.clearPlayerTreasureData(
|
||||
player.getName());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// ★ 拖拽拦截
|
||||
@org.bukkit.event.EventHandler(priority =
|
||||
org.bukkit.event.EventPriority.HIGH)
|
||||
org.bukkit.event.EventPriority.HIGHEST)
|
||||
public void onDrag(
|
||||
org.bukkit.event.inventory
|
||||
.InventoryDragEvent e) {
|
||||
for (ItemStack item :
|
||||
e.getNewItems().values()) {
|
||||
if (TreasureInventory
|
||||
.isCustomItem(item)) {
|
||||
String type = e.getView()
|
||||
.getTopInventory()
|
||||
.getType().name();
|
||||
if (!type.equals("PLAYER")
|
||||
&& !type.equals("CRAFTING")) {
|
||||
String title = e.getView().getTitle();
|
||||
|
||||
// ★ 垃圾站拖拽拦截
|
||||
if (title.contains("垃圾回收站")) {
|
||||
for (ItemStack item :
|
||||
e.getNewItems().values()) {
|
||||
if (item != null
|
||||
&& TreasureInventory
|
||||
.isCustomItem(item)) {
|
||||
e.setCancelled(true);
|
||||
Main plugin = (Main) Bukkit
|
||||
.getPluginManager()
|
||||
.getPlugin("Sdf1_game");
|
||||
if (plugin != null) {
|
||||
plugin.getTreasureManager()
|
||||
.onContainerBlock(
|
||||
e.getWhoClicked()
|
||||
.getName());
|
||||
}
|
||||
e.getWhoClicked().sendMessage(
|
||||
"§c[寻宝] 不可拖入垃圾站");
|
||||
return;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// ★ 通用容器拖拽拦截
|
||||
InventoryType type = e.getView()
|
||||
.getTopInventory().getType();
|
||||
// 只放行玩家背包和创造模式背包,其他所有容器都拦截
|
||||
if (type != InventoryType.PLAYER
|
||||
&& type != InventoryType.CREATIVE) {
|
||||
for (ItemStack item :
|
||||
e.getNewItems().values()) {
|
||||
if (item != null
|
||||
&& TreasureInventory
|
||||
.isCustomItem(item)) {
|
||||
e.setCancelled(true);
|
||||
e.getWhoClicked().sendMessage(
|
||||
"§c[寻宝] 不可放入容器");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ★ 铁砧拦截
|
||||
@org.bukkit.event.EventHandler(priority =
|
||||
org.bukkit.event.EventPriority.HIGH)
|
||||
org.bukkit.event.EventPriority.HIGHEST)
|
||||
public void onAnvil(
|
||||
org.bukkit.event.inventory
|
||||
.PrepareAnvilEvent e) {
|
||||
@@ -421,32 +678,54 @@ public class TreasureListener implements Listener {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ★ 右键容器拦截(HIGHEST 优先级)
|
||||
@org.bukkit.event.EventHandler(priority =
|
||||
org.bukkit.event.EventPriority.HIGH)
|
||||
org.bukkit.event.EventPriority.HIGHEST)
|
||||
public void onInteract(
|
||||
org.bukkit.event.player
|
||||
.PlayerInteractEvent e) {
|
||||
if (e.getItem() == null) return;
|
||||
// ★ 双重获取物品:e.getItem() + 主手后备
|
||||
ItemStack held = e.getItem();
|
||||
if (held == null || held.getType() == org.bukkit.Material.AIR) {
|
||||
held = e.getPlayer().getInventory()
|
||||
.getItemInMainHand();
|
||||
}
|
||||
if (held == null || held.getType() == org.bukkit.Material.AIR) return;
|
||||
if (!TreasureInventory
|
||||
.isCustomItem(e.getItem())) return;
|
||||
.isCustomItem(held)) return;
|
||||
if (!e.getAction().name()
|
||||
.contains("RIGHT_CLICK")) return;
|
||||
if (e.getClickedBlock() == null) return;
|
||||
String b = e.getClickedBlock()
|
||||
String bt = e.getClickedBlock()
|
||||
.getType().name();
|
||||
if (b.contains("SHULKER")
|
||||
|| b.contains("CHEST")
|
||||
|| b.equals("BARREL")
|
||||
|| b.equals("ENDER_CHEST")
|
||||
|| b.equals("HOPPER")
|
||||
|| b.equals("DROPPER")
|
||||
|| b.equals("DISPENSER")) {
|
||||
if (bt.contains("SHULKER")
|
||||
|| bt.contains("CHEST")
|
||||
|| bt.equals("BARREL")
|
||||
|| bt.equals("ENDER_CHEST")
|
||||
|| bt.equals("HOPPER")
|
||||
|| bt.equals("DROPPER")
|
||||
|| bt.equals("DISPENSER")
|
||||
|| bt.equals("DECORATED_POT")
|
||||
|| bt.contains("SHELF")) {
|
||||
e.setCancelled(true);
|
||||
e.setUseItemInHand(
|
||||
org.bukkit.event.Event.Result.DENY);
|
||||
e.getPlayer().sendMessage(
|
||||
"§c[寻宝] 不可放入容器");
|
||||
}
|
||||
}
|
||||
|
||||
// ★ 方块放置拦截 - 防止手持宝箱物品放置方块
|
||||
@org.bukkit.event.EventHandler(priority =
|
||||
org.bukkit.event.EventPriority.HIGHEST)
|
||||
public void onBlockPlace(BlockPlaceEvent e) {
|
||||
ItemStack held = e.getItemInHand();
|
||||
if (held == null || held.getType() == Material.AIR) return;
|
||||
if (!TreasureInventory.isCustomItem(held)) return;
|
||||
e.setCancelled(true);
|
||||
e.getPlayer().sendMessage(
|
||||
"§c[寻宝] 不可使用此物品放置方块");
|
||||
}
|
||||
// ★ 上线时扫描背包回收
|
||||
@org.bukkit.event.EventHandler
|
||||
public void onJoin(
|
||||
@@ -524,4 +803,119 @@ public class TreasureListener implements Listener {
|
||||
p.sendMessage("§7输入 §e/寻宝 区域名 "
|
||||
+ "§7保存");
|
||||
}
|
||||
|
||||
// ★ 传送门拦截 - 物品通过末地传送门/下界传送门时自动销毁
|
||||
@org.bukkit.event.EventHandler(priority =
|
||||
org.bukkit.event.EventPriority.HIGHEST)
|
||||
public void onPortal(org.bukkit.event.player.PlayerPortalEvent e) {
|
||||
Player p = e.getPlayer();
|
||||
// 检查玩家背包中的宝箱物品
|
||||
for (ItemStack item : p.getInventory().getContents()) {
|
||||
if (item != null
|
||||
&& TreasureInventory.isCustomItem(item)) {
|
||||
// 销毁宝箱物品
|
||||
item.setAmount(0);
|
||||
p.sendMessage("§c[寻宝] 宝箱物品在传送时已自动销毁");
|
||||
}
|
||||
}
|
||||
// 检查副手
|
||||
ItemStack offhand = p.getInventory().getItemInOffHand();
|
||||
if (offhand != null
|
||||
&& TreasureInventory.isCustomItem(offhand)) {
|
||||
offhand.setAmount(0);
|
||||
p.sendMessage("§c[寻宝] 宝箱物品在传送时已自动销毁");
|
||||
}
|
||||
}
|
||||
|
||||
// ★ 实体传送拦截 - 驴箱子、运输船等实体传送
|
||||
@org.bukkit.event.EventHandler(priority =
|
||||
org.bukkit.event.EventPriority.HIGHEST)
|
||||
public void onEntityPortal(org.bukkit.event.entity.EntityPortalEvent e) {
|
||||
// 检查实体是否携带宝箱物品(如驴箱子、运输船)
|
||||
if (e.getEntity() instanceof org.bukkit.entity.AnimalTamer) {
|
||||
// 这里可以添加更多实体类型的检查
|
||||
}
|
||||
|
||||
// 检查驴箱子、骡子、羊驼等可骑乘实体
|
||||
if (e.getEntity() instanceof org.bukkit.entity.AbstractHorse) {
|
||||
org.bukkit.entity.AbstractHorse horse = (org.bukkit.entity.AbstractHorse) e.getEntity();
|
||||
// 检查马鞍和装备
|
||||
ItemStack saddle = horse.getInventory().getItem(0);
|
||||
if (saddle != null && TreasureInventory.isCustomItem(saddle)) {
|
||||
saddle.setAmount(0);
|
||||
// 通知附近的玩家
|
||||
for (Player p : horse.getWorld().getPlayers()) {
|
||||
if (p.getLocation().distance(horse.getLocation()) < 32) {
|
||||
p.sendMessage("§c[寻宝] 驴箱子中的宝箱物品在传送时已自动销毁");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查运输船(如果有相关API)
|
||||
// 注意:Bukkit API可能不直接支持运输船的物品检查
|
||||
}
|
||||
|
||||
// ★ 漏斗拦截 - 防止宝箱物品通过漏斗传输
|
||||
@org.bukkit.event.EventHandler(priority =
|
||||
org.bukkit.event.EventPriority.HIGHEST)
|
||||
public void onInventoryMoveItem(org.bukkit.event.inventory.InventoryMoveItemEvent e) {
|
||||
ItemStack item = e.getItem();
|
||||
if (item != null && TreasureInventory.isCustomItem(item)) {
|
||||
// 检查目标容器类型
|
||||
InventoryType destinationType = e.getDestination().getType();
|
||||
// 只放行玩家背包,其他所有容器都拦截
|
||||
if (destinationType != InventoryType.PLAYER
|
||||
&& destinationType != InventoryType.CREATIVE) {
|
||||
e.setCancelled(true);
|
||||
// 将物品弹出到世界
|
||||
Location loc = e.getSource().getLocation();
|
||||
if (loc != null) {
|
||||
loc.getWorld().dropItemNaturally(loc, item.clone());
|
||||
item.setAmount(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ★ 展示框拦截 - 防止宝箱物品放入展示框
|
||||
@org.bukkit.event.EventHandler(priority =
|
||||
org.bukkit.event.EventPriority.HIGHEST)
|
||||
public void onPlayerInteractEntity(org.bukkit.event.player.PlayerInteractEntityEvent e) {
|
||||
if (!(e.getRightClicked() instanceof org.bukkit.entity.ItemFrame)) return;
|
||||
Player p = e.getPlayer();
|
||||
ItemStack item = p.getInventory().getItemInMainHand();
|
||||
if (item != null && TreasureInventory.isCustomItem(item)) {
|
||||
e.setCancelled(true);
|
||||
p.sendMessage("§c[寻宝] 宝箱物品不可放入展示框");
|
||||
}
|
||||
}
|
||||
|
||||
// ★ 打开容器时检查 - 防止宝箱物品通过其他方式进入容器
|
||||
@org.bukkit.event.EventHandler(priority =
|
||||
org.bukkit.event.EventPriority.HIGHEST)
|
||||
public void onInventoryOpen(org.bukkit.event.inventory.InventoryOpenEvent e) {
|
||||
if (!(e.getPlayer() instanceof Player)) return;
|
||||
Player p = (Player) e.getPlayer();
|
||||
InventoryType type = e.getInventory().getType();
|
||||
|
||||
// 只检查容器类型,不检查玩家背包
|
||||
if (type == InventoryType.PLAYER
|
||||
|| type == InventoryType.CREATIVE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查容器中是否已有宝箱物品
|
||||
for (ItemStack item : e.getInventory().getContents()) {
|
||||
if (item != null && TreasureInventory.isCustomItem(item)) {
|
||||
// 移除宝箱物品并弹出到世界
|
||||
Location loc = e.getInventory().getLocation();
|
||||
if (loc != null) {
|
||||
loc.getWorld().dropItemNaturally(loc, item.clone());
|
||||
}
|
||||
item.setAmount(0);
|
||||
p.sendMessage("§c[寻宝] 容器中的宝箱物品已被清除");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -11,6 +11,7 @@ public class TreasureSpawnTask extends BukkitRunnable {
|
||||
private final TreasureManager manager;
|
||||
private int offlineCount = 0;
|
||||
private boolean paused = false;
|
||||
private boolean forceNextSpawn = false;
|
||||
|
||||
public TreasureSpawnTask(Main plugin,
|
||||
TreasureManager manager) {
|
||||
@@ -41,8 +42,9 @@ public class TreasureSpawnTask extends BukkitRunnable {
|
||||
|
||||
if (paused) {
|
||||
paused = false;
|
||||
forceNextSpawn = true;
|
||||
plugin.getLogger().info(
|
||||
"[寻宝] ★玩家上线");
|
||||
"[寻宝] ★玩家上线,下次生成100%");
|
||||
}
|
||||
|
||||
offlineCount = 0;
|
||||
@@ -65,6 +67,16 @@ public class TreasureSpawnTask extends BukkitRunnable {
|
||||
long need = tc.spawnInterval * 1000L;
|
||||
if (elapsed < need) continue;
|
||||
|
||||
// ★ 暂停恢复后首次生成:100%生成
|
||||
if (forceNextSpawn) {
|
||||
forceNextSpawn = false;
|
||||
manager.spawnChest(tc);
|
||||
tc.lastSpawnTime = now;
|
||||
broadcastSpawn(tc);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 按配置概率判定
|
||||
int roll = ThreadLocalRandom.current()
|
||||
.nextInt(1, 101);
|
||||
if (roll > tc.spawnChance) {
|
||||
@@ -76,8 +88,6 @@ public class TreasureSpawnTask extends BukkitRunnable {
|
||||
tc.lastSpawnTime = now;
|
||||
// ★ 通知所有玩家
|
||||
broadcastSpawn(tc);
|
||||
plugin.getLogger().info(
|
||||
"[寻宝] ★生成: " + tc.name);
|
||||
}
|
||||
}
|
||||
private void broadcastSpawn(TreasureConfig tc) {
|
||||
|
||||
@@ -0,0 +1,442 @@
|
||||
package Sdf1_game;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
public class UpdateChecker {
|
||||
|
||||
// ===== SSL证书信任所有(解决PKIX错误) =====
|
||||
static {
|
||||
try {
|
||||
TrustManager[] trustAllCerts = new TrustManager[]{
|
||||
new X509TrustManager() {
|
||||
public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }
|
||||
public void checkClientTrusted(X509Certificate[] certs, String authType) {}
|
||||
public void checkServerTrusted(X509Certificate[] certs, String authType) {}
|
||||
}
|
||||
};
|
||||
SSLContext sc = SSLContext.getInstance("TLS");
|
||||
sc.init(null, trustAllCerts, new SecureRandom());
|
||||
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
private final Main plugin;
|
||||
private final String currentVersion;
|
||||
|
||||
// ===== 仓库信息 =====
|
||||
private static final String GITHUB_OWNER = "ypsdf1";
|
||||
private static final String GITHUB_REPO = "Sdf1_games";
|
||||
private static final String GITEE_OWNER = "nihaoshidifu";
|
||||
private static final String GITEE_REPO = "sdf1_game";
|
||||
|
||||
// API 地址(使用releases/latest获取最新版本)
|
||||
private static final String GITHUB_API =
|
||||
"https://api.github.com/repos/" + GITHUB_OWNER + "/" + GITHUB_REPO + "/releases/latest";
|
||||
private static final String GITEE_API =
|
||||
"https://gitee.com/api/v5/repos/" + GITEE_OWNER + "/" + GITEE_REPO + "/releases/latest";
|
||||
|
||||
// 仓库页面链接
|
||||
private static final String GITHUB_LINK =
|
||||
"https://github.com/" + GITHUB_OWNER + "/" + GITHUB_REPO + "/releases";
|
||||
private static final String GITEE_LINK =
|
||||
"https://gitee.com/" + GITEE_OWNER + "/" + GITEE_REPO + "/releases";
|
||||
|
||||
// 单次请求超时(毫秒)
|
||||
private static final int TIMEOUT_MS = 5000;
|
||||
|
||||
// 熔断阈值
|
||||
private static final int TRIP_THRESHOLD = 5;
|
||||
|
||||
// 熔断后重试间隔(2分钟)
|
||||
private static final long RETRY_DELAY_MS = 2 * 60 * 1000;
|
||||
|
||||
// ===== 熔断状态(内存态,重启自动清除) =====
|
||||
private volatile boolean circuitTripped = false;
|
||||
private volatile int failCount = 0;
|
||||
private volatile boolean manualSuccess = false;
|
||||
|
||||
// ===== 自动检测禁用(持久化到文件) =====
|
||||
private volatile boolean autoCheckDisabled = false;
|
||||
private final File disabledFile;
|
||||
|
||||
public UpdateChecker(Main plugin) {
|
||||
this.plugin = plugin;
|
||||
this.currentVersion = plugin.getDescription().getVersion();
|
||||
this.disabledFile = new File(plugin.getDataFolder(), "update_disabled.dat");
|
||||
loadDisabledState();
|
||||
}
|
||||
|
||||
// ========== 持久化禁用状态 ==========
|
||||
|
||||
private void loadDisabledState() {
|
||||
if (disabledFile.exists()) {
|
||||
try (BufferedReader br = new BufferedReader(new FileReader(disabledFile))) {
|
||||
String line = br.readLine();
|
||||
if (line != null) {
|
||||
autoCheckDisabled = "true".equals(line.trim());
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void saveDisabledState() {
|
||||
try (PrintWriter pw = new PrintWriter(new FileWriter(disabledFile))) {
|
||||
pw.println(autoCheckDisabled ? "true" : "false");
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 公开方法 ==========
|
||||
|
||||
/**
|
||||
* onEnable 调用(异步静默)
|
||||
* 熔断或禁用则直接跳过
|
||||
*/
|
||||
public void checkOnEnable() {
|
||||
if (autoCheckDisabled) return;
|
||||
if (circuitTripped) {
|
||||
// 熔断状态下,2分钟后重试
|
||||
scheduleRetry();
|
||||
return;
|
||||
}
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> doCheck(null, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* /treasure update 或 /quiz update 调用(异步)
|
||||
* 忽略熔断和禁用状态,强制检测
|
||||
*/
|
||||
public void checkUpdate(CommandSender sender) {
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> doCheck(sender, false));
|
||||
}
|
||||
|
||||
/**
|
||||
* 2分钟后重试
|
||||
*/
|
||||
private void scheduleRetry() {
|
||||
Bukkit.getScheduler().runTaskLater(plugin, () -> {
|
||||
if (circuitTripped && !manualSuccess) {
|
||||
plugin.getLogger().info("[Sdf1_game] 熔断重试中...");
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> doCheck(null, true));
|
||||
}
|
||||
}, RETRY_DELAY_MS / 50); // ticks
|
||||
}
|
||||
|
||||
// ========== 核心检测逻辑 ==========
|
||||
private void doCheck(CommandSender sender, boolean isAuto) {
|
||||
|
||||
if (isAuto) {
|
||||
if (autoCheckDisabled || circuitTripped) {
|
||||
return;
|
||||
}
|
||||
plugin.getLogger().info("[Sdf1_game] 启动自动检查更新...");
|
||||
}
|
||||
|
||||
// ★ 双通道并行执行,一路成功掐断另一路
|
||||
AtomicBoolean cancelled = new AtomicBoolean(false);
|
||||
AtomicReference<CheckResult> ghResult = new AtomicReference<>();
|
||||
AtomicReference<CheckResult> geResult = new AtomicReference<>();
|
||||
CountDownLatch latch = new CountDownLatch(2);
|
||||
|
||||
// 通道1:GitHub
|
||||
Thread ghThread = new Thread(() -> {
|
||||
if (!cancelled.get()) {
|
||||
ghResult.set(tryChannel(GITHUB_API, "GitHub", GITHUB_LINK));
|
||||
if (ghResult.get().isSuccess()) {
|
||||
cancelled.set(true); // 成功则取消另一路
|
||||
}
|
||||
}
|
||||
latch.countDown();
|
||||
});
|
||||
ghThread.setDaemon(true);
|
||||
|
||||
// 通道2:Gitee
|
||||
Thread geThread = new Thread(() -> {
|
||||
if (!cancelled.get()) {
|
||||
geResult.set(tryChannel(GITEE_API, "Gitee", GITEE_LINK));
|
||||
if (geResult.get().isSuccess()) {
|
||||
cancelled.set(true); // 成功则取消另一路
|
||||
}
|
||||
}
|
||||
latch.countDown();
|
||||
});
|
||||
geThread.setDaemon(true);
|
||||
|
||||
ghThread.start();
|
||||
geThread.start();
|
||||
|
||||
try {
|
||||
latch.await(); // 等待两路都完成
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
|
||||
CheckResult gh = ghResult.get();
|
||||
CheckResult ge = geResult.get();
|
||||
|
||||
// 选择成功的结果
|
||||
CheckResult chosen = null;
|
||||
|
||||
if (gh != null && gh.isSuccess()) {
|
||||
chosen = gh;
|
||||
} else if (ge != null && ge.isSuccess()) {
|
||||
chosen = ge;
|
||||
} else {
|
||||
// 双通道均失败
|
||||
failCount++;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (gh != null && gh.error != null) {
|
||||
sb.append("§7GitHub: §c").append(gh.error);
|
||||
}
|
||||
if (ge != null && ge.error != null) {
|
||||
if (sb.length() > 0) sb.append("\n");
|
||||
sb.append("§7Gitee: §c").append(ge.error);
|
||||
}
|
||||
if (failCount >= TRIP_THRESHOLD) {
|
||||
circuitTripped = true;
|
||||
plugin.getLogger().warning(
|
||||
"[Sdf1_game] 更新检测已熔断,连续失败" + failCount + "次");
|
||||
// 熔断后安排2分钟后重试
|
||||
scheduleRetry();
|
||||
}
|
||||
if (sender != null) {
|
||||
final String msg = sb.toString();
|
||||
final boolean tripped = circuitTripped;
|
||||
final int fc = failCount;
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
sender.sendMessage("§c§l[更新] 检测失败");
|
||||
sender.sendMessage(msg);
|
||||
sender.sendMessage("§e失败次数: " + fc + "/" + TRIP_THRESHOLD);
|
||||
if (tripped) {
|
||||
sender.sendMessage("§c已熔断,2分钟后自动重试,或重启服务器重置");
|
||||
}
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 至少一个通道成功
|
||||
failCount = 0;
|
||||
|
||||
// 检查禁用信号
|
||||
if (isDisableSignal(chosen.version)) {
|
||||
autoCheckDisabled = true;
|
||||
saveDisabledState();
|
||||
plugin.getLogger().info(
|
||||
"[Sdf1_game] 远程版本信号 \"" + chosen.version + "\",自动更新检测已禁用");
|
||||
if (sender != null) {
|
||||
Bukkit.getScheduler().runTask(plugin, () ->
|
||||
sender.sendMessage("§e[更新] 远程已禁用自动更新检测"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 手动调用:成功才解除熔断和禁用
|
||||
if (!isAuto) {
|
||||
manualSuccess = true; // 标记手动成功,不再执行后续自动检查
|
||||
if (circuitTripped) {
|
||||
circuitTripped = false;
|
||||
}
|
||||
if (autoCheckDisabled) {
|
||||
autoCheckDisabled = false;
|
||||
saveDisabledState();
|
||||
}
|
||||
}
|
||||
|
||||
final String src = chosen.source;
|
||||
final String lnk = chosen.link;
|
||||
final String rv = chosen.version;
|
||||
|
||||
// ★ 自动检查也记录日志(含下载链接 + 颜色提醒)
|
||||
if (isAuto) {
|
||||
boolean hasUpdate = isNewer(rv, currentVersion);
|
||||
String status = hasUpdate
|
||||
? "(有新版本!)"
|
||||
: "(已是最新)";
|
||||
// 使用Bukkit.getConsoleSender().sendMessage()输出彩色日志
|
||||
Bukkit.getConsoleSender().sendMessage("§a[Sdf1_game] §e更新检测成功 via: " + src
|
||||
+ " | 本地: " + currentVersion + " | 远程: " + rv + " §7" + status);
|
||||
if (hasUpdate) {
|
||||
Bukkit.getConsoleSender().sendMessage("§a[Sdf1_game] §b下载: " + lnk);
|
||||
}
|
||||
}
|
||||
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
if (sender == null) return;
|
||||
|
||||
sender.sendMessage("§e[Sdf1_game] [更新] 检测成功 via: §a" + src);
|
||||
sender.sendMessage("§e[Sdf1_game] [更新] 本地版本: §f" + currentVersion);
|
||||
sender.sendMessage("§e[Sdf1_game] [更新] 远程版本: §f" + rv);
|
||||
|
||||
if (isNewer(rv, currentVersion)) {
|
||||
sender.sendMessage("§c[Sdf1_game] [更新] 发现新版本: " + rv);
|
||||
sender.sendMessage("§a[Sdf1_game] 请前往仓库下载更新:");
|
||||
sender.sendMessage("§a§l" + lnk);
|
||||
} else {
|
||||
sender.sendMessage("§a[Sdf1_game] [更新] 已是最新版本");
|
||||
}
|
||||
|
||||
// 显示另一通道的失败原因
|
||||
CheckResult other = (src.equals("GitHub")) ? ge : gh;
|
||||
if (other != null && !other.isSuccess() && other.error != null) {
|
||||
sender.sendMessage("§7[Sdf1_game] [更新] " + other.source + " 不可用: " + other.error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ========== 禁用信号判断 ==========
|
||||
|
||||
private boolean isDisableSignal(String version) {
|
||||
if (version == null) return false;
|
||||
String v = version.trim().toLowerCase();
|
||||
return "999".equals(v) || "关闭".equals(v) || "false".equals(v) || "禁用".equals(v);
|
||||
}
|
||||
|
||||
// ========== HTTP 请求 ==========
|
||||
|
||||
private static class CheckResult {
|
||||
final String version;
|
||||
final String source;
|
||||
final String link;
|
||||
final String error;
|
||||
|
||||
CheckResult(String version, String source, String link, String error) {
|
||||
this.version = version;
|
||||
this.source = source;
|
||||
this.link = link;
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
boolean isSuccess() {
|
||||
return version != null;
|
||||
}
|
||||
}
|
||||
|
||||
private CheckResult tryChannel(String apiUrl, String sourceName, String link) {
|
||||
HttpURLConnection conn = null;
|
||||
try {
|
||||
URL url = new URL(apiUrl);
|
||||
conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestMethod("GET");
|
||||
conn.setRequestProperty("User-Agent",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
|
||||
+ "(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36");
|
||||
conn.setRequestProperty("Accept", "*/*");
|
||||
conn.setRequestProperty("Accept-Language", "zh-CN,zh;q=0.9");
|
||||
conn.setConnectTimeout(TIMEOUT_MS);
|
||||
conn.setReadTimeout(TIMEOUT_MS);
|
||||
conn.setInstanceFollowRedirects(true);
|
||||
|
||||
int code = conn.getResponseCode();
|
||||
|
||||
if (code == 404) {
|
||||
return new CheckResult(null, sourceName, link, sourceName + ": 未找到 (HTTP 404)");
|
||||
}
|
||||
if (code == 403) {
|
||||
return new CheckResult(null, sourceName, link, sourceName + ": 被限流 (HTTP 403)");
|
||||
}
|
||||
if (code != 200) {
|
||||
return new CheckResult(null, sourceName, link, sourceName + ": HTTP " + code);
|
||||
}
|
||||
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
sb.append(line);
|
||||
}
|
||||
reader.close();
|
||||
|
||||
String tag = parseTagName(sb.toString());
|
||||
if (tag == null) {
|
||||
return new CheckResult(null, sourceName, link, sourceName + ": 无法解析版本号");
|
||||
}
|
||||
|
||||
return new CheckResult(tag, sourceName, link, null);
|
||||
|
||||
} catch (java.net.SocketTimeoutException e) {
|
||||
return new CheckResult(null, sourceName, link, sourceName + ": 连接超时");
|
||||
} catch (java.net.ConnectException e) {
|
||||
return new CheckResult(null, sourceName, link, sourceName + ": 连接被拒绝");
|
||||
} catch (java.net.UnknownHostException e) {
|
||||
return new CheckResult(null, sourceName, link, sourceName + ": DNS解析失败");
|
||||
} catch (Exception e) {
|
||||
return new CheckResult(null, sourceName, link, sourceName + ": " + e.getMessage());
|
||||
} finally {
|
||||
if (conn != null) {
|
||||
conn.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String parseTagName(String json) {
|
||||
// tags API 返回数组: [{"name":"v1.1",...}]
|
||||
// releases API 返回对象: {"tag_name":"v1.1"}
|
||||
|
||||
int idx = json.indexOf("\"tag_name\"");
|
||||
if (idx >= 0) {
|
||||
int colon = json.indexOf(":", idx);
|
||||
int q1 = json.indexOf("\"", colon + 1);
|
||||
int q2 = json.indexOf("\"", q1 + 1);
|
||||
if (q2 > q1) {
|
||||
return cleanTag(json.substring(q1 + 1, q2));
|
||||
}
|
||||
}
|
||||
|
||||
idx = json.indexOf("\"name\"");
|
||||
if (idx >= 0) {
|
||||
int colon = json.indexOf(":", idx);
|
||||
int q1 = json.indexOf("\"", colon + 1);
|
||||
int q2 = json.indexOf("\"", q1 + 1);
|
||||
if (q2 > q1) {
|
||||
return cleanTag(json.substring(q1 + 1, q2));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private String cleanTag(String tag) {
|
||||
if (tag.startsWith("v") || tag.startsWith("V")) {
|
||||
tag = tag.substring(1);
|
||||
}
|
||||
return tag.isEmpty() ? null : tag;
|
||||
}
|
||||
|
||||
// ========== 版本比较 ==========
|
||||
// ★ 只要本地≠远程,就视为有更新
|
||||
|
||||
private boolean isNewer(String remote, String local) {
|
||||
if (remote == null || local == null) return false;
|
||||
// 版本比较:远程≠本地即视为有更新(用户即将推新版本)
|
||||
return !remote.trim().equals(local.trim());
|
||||
}
|
||||
|
||||
private int parseNum(String s) {
|
||||
try {
|
||||
return Integer.parseInt(s.replaceAll("[^0-9]", ""));
|
||||
} catch (Exception e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
name: Sdf1_game
|
||||
version: 1.0
|
||||
version: 1.4
|
||||
main: Sdf1_game.Main
|
||||
api-version: '1.21'
|
||||
description: 快问快答+寻宝系统
|
||||
|
||||
Reference in New Issue
Block a user