修复宝箱自定义物品回收问题,新增拦截所有自定义物品投入容器

This commit is contained in:
ypsdf1
2026-06-07 18:40:54 +08:00
parent aea44ef92d
commit 99c877b5e8
12 changed files with 6754 additions and 734 deletions
@@ -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
+15 -1
View File
@@ -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) {
}
}
}
+58 -32
View File
@@ -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;
}
+6 -1
View File
@@ -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) {
+25
View File
@@ -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
+469 -75
View File
@@ -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
+13 -3
View File
@@ -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) {
+442
View File
@@ -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 -1
View File
@@ -1,5 +1,5 @@
name: Sdf1_game
version: 1.0
version: 1.4
main: Sdf1_game.Main
api-version: '1.21'
description: 快问快答+寻宝系统