Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c9da8c8ec2 | ||
|
|
cf246d4076 | ||
|
|
c747dbcc76 | ||
|
|
66f1e33d5e | ||
|
|
717a7a9266 |
@@ -0,0 +1,32 @@
|
||||
---
|
||||
name: Bug 反馈
|
||||
about: 报告一个 Bug 以帮助改进插件
|
||||
title: "[Bug] "
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## Bug 描述
|
||||
请简明扼要地描述 Bug 是什么。
|
||||
|
||||
## 复现步骤
|
||||
1. 进入 ...
|
||||
2. 点击 ...
|
||||
3. 出现错误 ...
|
||||
|
||||
## 期望行为
|
||||
描述你期望发生的事情。
|
||||
|
||||
## 实际行为
|
||||
描述实际发生了什么。
|
||||
|
||||
## 服务端信息
|
||||
- 服务器类型和版本(如 Paper 1.21.4):
|
||||
- 插件版本:
|
||||
|
||||
## 截图 / 日志
|
||||
如有相关截图或报错日志,请粘贴在此处。
|
||||
|
||||
## 其他信息
|
||||
补充任何你认为有助于解决问题的信息。
|
||||
@@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: 问题排查指南
|
||||
url: https://github.com/ypsdf1/sdf1_login
|
||||
about: 在提交 Bug 前,请先查阅文档确认是否已有相关说明。
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: 功能新增
|
||||
about: 提出一个新功能的需求建议
|
||||
title: "[Feature] "
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## 功能描述
|
||||
请描述你希望添加的新功能。
|
||||
|
||||
## 动机
|
||||
为什么需要这个功能?它能解决什么问题?
|
||||
|
||||
## 建议的实现方式
|
||||
如果对实现方式有想法,请在此描述。
|
||||
|
||||
## 额外信息
|
||||
补充任何你认为有助于理解需求的上下文。
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
name: 建议提交
|
||||
about: 对插件提出改进建议或反馈
|
||||
title: "[Suggestion] "
|
||||
labels: suggestion
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## 建议内容
|
||||
请描述你的建议或想法。
|
||||
|
||||
## 背景
|
||||
什么场景下产生的这个建议?
|
||||
|
||||
## 预期效果
|
||||
你认为采用这个建议后能达到什么效果?
|
||||
@@ -13,7 +13,7 @@
|
||||
| 新人礼包 | 新玩家7日礼包 | 已完成
|
||||
| 挂机检测 | 挂机检测踢出 | 已完成
|
||||
| 自定义菜单 | 可以自己diy指令 | 已完成
|
||||
| 自定义菜单图标 | 可以用自己喜欢的物品当图标 | 待施工
|
||||
| 自定义菜单图标 | 可以用自己喜欢的物品当图标 | 已完工
|
||||
|
||||
## 2.安装说明:
|
||||
|
||||
@@ -254,15 +254,16 @@ http-port=443
|
||||
|
||||
### 2.2.7 菜单.txt
|
||||
- 自定义菜单配置文件
|
||||
|
||||
```json
|
||||
# 服务器菜单
|
||||
# 使用类json格式
|
||||
// 支持使用://Java单行注释、#yml单行注释,/* */和<!--- --->块注释
|
||||
// 支持使用://Java单行注释、#yml单行注释、/* */块注释,也支持<!--- --->的块注释
|
||||
|
||||
{
|
||||
标题:主城传送
|
||||
指令:/dom tp 主城区 # 这里如果是xxx.txt则表示进入二级目录
|
||||
权限类型:玩家
|
||||
权限类型:玩家
|
||||
图标:COMPASS
|
||||
}
|
||||
|
||||
@@ -343,6 +344,14 @@ http-port=443
|
||||
- sdf1_login del 删除玩家账号<br>
|
||||
- sdf1_login radio 停止播放广播<br>
|
||||
- sdf1_login radio reload 重载广播并重新打包资源包
|
||||
- menu 开/关 是否开启自定义菜单
|
||||
|
||||
## 4. 自定义菜单物品
|
||||
- 打开主界面,选择"我的"进入<br>
|
||||

|
||||
- 将你的自定义物品与雪球交换<br>
|
||||

|
||||
- 随后系统会给你重新发放菜单,并且此时图标就是你的自定义物品了
|
||||
|
||||
## 运行截图
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 203 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 155 KiB |
@@ -160,6 +160,40 @@ public class DatabaseManager {
|
||||
+ "alert_date TEXT NOT NULL,"
|
||||
+ "notified INTEGER DEFAULT 0,"
|
||||
+ "notified_time INTEGER DEFAULT 0)");
|
||||
// PVP区域表
|
||||
st.execute("CREATE TABLE IF NOT EXISTS "
|
||||
+ "pvp_regions ("
|
||||
+ "id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
||||
+ "name TEXT NOT NULL UNIQUE,"
|
||||
+ "world TEXT NOT NULL,"
|
||||
+ "x1 INTEGER NOT NULL,"
|
||||
+ "y1 INTEGER NOT NULL,"
|
||||
+ "z1 INTEGER NOT NULL,"
|
||||
+ "x2 INTEGER NOT NULL,"
|
||||
+ "y2 INTEGER NOT NULL,"
|
||||
+ "z2 INTEGER NOT NULL,"
|
||||
+ "create_time INTEGER DEFAULT 0"
|
||||
+ ")");
|
||||
|
||||
// PVP击杀总榜表
|
||||
st.execute("CREATE TABLE IF NOT EXISTS "
|
||||
+ "pvp_kills ("
|
||||
+ "player_name TEXT NOT NULL,"
|
||||
+ "region_name TEXT NOT NULL,"
|
||||
+ "kills INTEGER DEFAULT 0,"
|
||||
+ "deaths INTEGER DEFAULT 0,"
|
||||
+ "PRIMARY KEY(player_name, region_name)"
|
||||
+ ")");
|
||||
|
||||
|
||||
// 菜单图标表
|
||||
st.execute("CREATE TABLE IF NOT EXISTS "
|
||||
+ "menu_icons ("
|
||||
+ "player_name TEXT PRIMARY KEY,"
|
||||
+ "icon_b64 TEXT DEFAULT '',"
|
||||
+ "item_name TEXT DEFAULT '',"
|
||||
+ "save_time INTEGER DEFAULT 0"
|
||||
+ ")");
|
||||
|
||||
// 工单表
|
||||
st.execute("CREATE TABLE IF NOT EXISTS "
|
||||
@@ -239,6 +273,76 @@ public class DatabaseManager {
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
// ==================== 菜单图标 ====================
|
||||
|
||||
public void saveMenuIcon(String name,
|
||||
String iconB64, String itemName) {
|
||||
try {
|
||||
PreparedStatement ps = db.prepareStatement(
|
||||
"INSERT OR REPLACE INTO menu_icons "
|
||||
+ "(player_name, icon_b64, "
|
||||
+ "item_name, save_time) "
|
||||
+ "VALUES (?,?,?,?)");
|
||||
ps.setString(1, name);
|
||||
ps.setString(2, iconB64);
|
||||
ps.setString(3, itemName);
|
||||
ps.setLong(4,
|
||||
System.currentTimeMillis());
|
||||
ps.executeUpdate();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public String getMenuIcon(String name) {
|
||||
try {
|
||||
PreparedStatement ps = db.prepareStatement(
|
||||
"SELECT icon_b64 FROM menu_icons "
|
||||
+ "WHERE player_name=?");
|
||||
ps.setString(1, name);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
String b64 = null;
|
||||
if (rs.next()) {
|
||||
b64 = rs.getString("icon_b64");
|
||||
}
|
||||
rs.close();
|
||||
ps.close();
|
||||
return b64;
|
||||
} catch (SQLException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteMenuIcon(String name) {
|
||||
try {
|
||||
PreparedStatement ps = db.prepareStatement(
|
||||
"DELETE FROM menu_icons "
|
||||
+ "WHERE player_name=?");
|
||||
ps.setString(1, name);
|
||||
ps.executeUpdate();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasMenuIcon(String name) {
|
||||
try {
|
||||
PreparedStatement ps = db.prepareStatement(
|
||||
"SELECT 1 FROM menu_icons "
|
||||
+ "WHERE player_name=? "
|
||||
+ "AND icon_b64 IS NOT NULL "
|
||||
+ "AND icon_b64 != '' "
|
||||
+ "LIMIT 1");
|
||||
ps.setString(1, name);
|
||||
boolean r = ps.executeQuery().next();
|
||||
ps.close();
|
||||
return r;
|
||||
} catch (SQLException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 用户操作 ====================
|
||||
|
||||
@@ -747,6 +851,252 @@ public class DatabaseManager {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// ==================== PVP区域 ====================
|
||||
|
||||
public void savePVPRegion(String name,
|
||||
String world,
|
||||
int x1, int y1, int z1,
|
||||
int x2, int y2, int z2) {
|
||||
try {
|
||||
PreparedStatement ps =
|
||||
db.prepareStatement(
|
||||
"INSERT OR REPLACE "
|
||||
+ "INTO pvp_regions "
|
||||
+ "(name, world, "
|
||||
+ "x1, y1, z1, "
|
||||
+ "x2, y2, z2, "
|
||||
+ "create_time) "
|
||||
+ "VALUES "
|
||||
+ "(?,?,?,?,?,?,?,?,?)");
|
||||
ps.setString(1, name);
|
||||
ps.setString(2, world);
|
||||
ps.setInt(3, x1);
|
||||
ps.setInt(4, y1);
|
||||
ps.setInt(5, z1);
|
||||
ps.setInt(6, x2);
|
||||
ps.setInt(7, y2);
|
||||
ps.setInt(8, z2);
|
||||
ps.setLong(9,
|
||||
System.currentTimeMillis());
|
||||
ps.executeUpdate();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Object>
|
||||
getPVPRegion(String name) {
|
||||
try {
|
||||
PreparedStatement ps =
|
||||
db.prepareStatement(
|
||||
"SELECT * FROM "
|
||||
+ "pvp_regions "
|
||||
+ "WHERE name=?");
|
||||
ps.setString(1, name);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
if (rs.next()) {
|
||||
Map<String, Object> row =
|
||||
new LinkedHashMap<>();
|
||||
for (int i = 1; i <= rs
|
||||
.getMetaData()
|
||||
.getColumnCount(); i++) {
|
||||
row.put(rs.getMetaData()
|
||||
.getColumnName(i),
|
||||
rs.getObject(i));
|
||||
}
|
||||
rs.close();
|
||||
ps.close();
|
||||
return row;
|
||||
}
|
||||
rs.close();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<Map<String, Object>>
|
||||
getAllPVPRegions() {
|
||||
List<Map<String, Object>> list =
|
||||
new ArrayList<>();
|
||||
try {
|
||||
Statement st =
|
||||
db.createStatement();
|
||||
ResultSet rs =
|
||||
st.executeQuery(
|
||||
"SELECT * FROM "
|
||||
+ "pvp_regions");
|
||||
while (rs.next()) {
|
||||
Map<String, Object> row =
|
||||
new LinkedHashMap<>();
|
||||
for (int i = 1; i <= rs
|
||||
.getMetaData()
|
||||
.getColumnCount(); i++) {
|
||||
row.put(rs.getMetaData()
|
||||
.getColumnName(i),
|
||||
rs.getObject(i));
|
||||
}
|
||||
list.add(row);
|
||||
}
|
||||
rs.close();
|
||||
st.close();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public void deletePVPRegion(String name) {
|
||||
try {
|
||||
PreparedStatement ps =
|
||||
db.prepareStatement(
|
||||
"DELETE FROM "
|
||||
+ "pvp_regions "
|
||||
+ "WHERE name=?");
|
||||
ps.setString(1, name);
|
||||
ps.executeUpdate();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== PVP击杀 ====================
|
||||
|
||||
public int getPVPKills(
|
||||
String player, String region) {
|
||||
try {
|
||||
PreparedStatement ps =
|
||||
db.prepareStatement(
|
||||
"SELECT kills FROM "
|
||||
+ "pvp_kills "
|
||||
+ "WHERE "
|
||||
+ "player_name=? "
|
||||
+ "AND region_name=?");
|
||||
ps.setString(1, player);
|
||||
ps.setString(2, region);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
int kills = 0;
|
||||
if (rs.next())
|
||||
kills = rs.getInt("kills");
|
||||
rs.close();
|
||||
ps.close();
|
||||
return kills;
|
||||
} catch (SQLException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public int getPVPDeaths(
|
||||
String player, String region) {
|
||||
try {
|
||||
PreparedStatement ps =
|
||||
db.prepareStatement(
|
||||
"SELECT deaths FROM "
|
||||
+ "pvp_kills "
|
||||
+ "WHERE "
|
||||
+ "player_name=? "
|
||||
+ "AND region_name=?");
|
||||
ps.setString(1, player);
|
||||
ps.setString(2, region);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
int deaths = 0;
|
||||
if (rs.next())
|
||||
deaths = rs.getInt("deaths");
|
||||
rs.close();
|
||||
ps.close();
|
||||
return deaths;
|
||||
} catch (SQLException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void addPVPKill(
|
||||
String player, String region) {
|
||||
try {
|
||||
PreparedStatement ps =
|
||||
db.prepareStatement(
|
||||
"INSERT INTO pvp_kills "
|
||||
+ "(player_name, "
|
||||
+ "region_name, "
|
||||
+ "kills, deaths) "
|
||||
+ "VALUES (?,?,1,0) "
|
||||
+ "ON CONFLICT"
|
||||
+ "(player_name, "
|
||||
+ "region_name) "
|
||||
+ "DO UPDATE SET "
|
||||
+ "kills=kills+1");
|
||||
ps.setString(1, player);
|
||||
ps.setString(2, region);
|
||||
ps.executeUpdate();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void addPVPDeath(
|
||||
String player, String region) {
|
||||
try {
|
||||
PreparedStatement ps =
|
||||
db.prepareStatement(
|
||||
"INSERT INTO pvp_kills "
|
||||
+ "(player_name, "
|
||||
+ "region_name, "
|
||||
+ "kills, deaths) "
|
||||
+ "VALUES (?,?,0,1) "
|
||||
+ "ON CONFLICT"
|
||||
+ "(player_name, "
|
||||
+ "region_name) "
|
||||
+ "DO UPDATE SET "
|
||||
+ "deaths=deaths+1");
|
||||
ps.setString(1, player);
|
||||
ps.setString(2, region);
|
||||
ps.executeUpdate();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public List<Map<String, Object>>
|
||||
getPVPKillTop(String region, int limit) {
|
||||
List<Map<String, Object>> list =
|
||||
new ArrayList<>();
|
||||
try {
|
||||
PreparedStatement ps =
|
||||
db.prepareStatement(
|
||||
"SELECT player_name, "
|
||||
+ "kills, deaths "
|
||||
+ "FROM pvp_kills "
|
||||
+ "WHERE "
|
||||
+ "region_name=? "
|
||||
+ "AND kills>0 "
|
||||
+ "ORDER BY kills "
|
||||
+ "DESC LIMIT ?");
|
||||
ps.setString(1, region);
|
||||
ps.setInt(2, limit);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
while (rs.next()) {
|
||||
Map<String, Object> row =
|
||||
new LinkedHashMap<>();
|
||||
row.put("player_name",
|
||||
rs.getString("player_name"));
|
||||
row.put("kills",
|
||||
rs.getInt("kills"));
|
||||
row.put("deaths",
|
||||
rs.getInt("deaths"));
|
||||
list.add(row);
|
||||
}
|
||||
rs.close();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
// ==================== 安全报警 ====================
|
||||
@@ -773,6 +1123,7 @@ public class DatabaseManager {
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public List<Map<String, Object>>
|
||||
|
||||
@@ -11,6 +11,14 @@ import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.bukkit.util.io.BukkitObjectInputStream;
|
||||
import org.bukkit.util.io.BukkitObjectOutputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.Base64;
|
||||
|
||||
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@@ -792,6 +800,7 @@ public class GUIManager implements Listener {
|
||||
public void openMyInfo(Player p) {
|
||||
Inventory g = Bukkit.createInventory(
|
||||
null, 54, T_MY_INFO);
|
||||
|
||||
fillBg(g);
|
||||
Map<String, Object> user = plugin.getDb()
|
||||
.getUser(p.getName());
|
||||
@@ -833,10 +842,412 @@ public class GUIManager implements Listener {
|
||||
g.setItem(15, mkItem(Material.EMERALD_BLOCK,
|
||||
"§a§l积分商城",
|
||||
"§7当前: " + points + "积分"));
|
||||
|
||||
|
||||
// 图标说明
|
||||
g.setItem(30, mkItem(Material.PAPER,
|
||||
"§e§l自定义菜单图标",
|
||||
"§7将物品放入右侧槽位",
|
||||
"§7即可保存为你的专属图标",
|
||||
"§7下次加入自动发放"));
|
||||
// ===== 菜单图标区 =====
|
||||
g.setItem(30, mkItem(Material.PAPER,
|
||||
"§e§l自定义菜单图标",
|
||||
"§7Shift+点击背包物品放入右侧槽位",
|
||||
"§7即可保存为你的专属图标",
|
||||
"§7下次加入自动发放"));
|
||||
|
||||
String iconB64 = plugin.getDb()
|
||||
.getMenuIcon(p.getName());
|
||||
if (iconB64 != null && !iconB64.isEmpty()) {
|
||||
ItemStack iconItem =
|
||||
deserializeItem(iconB64);
|
||||
if (iconItem != null) {
|
||||
g.setItem(31, iconItem);
|
||||
} else {
|
||||
putIconPlaceholder(g);
|
||||
}
|
||||
} else {
|
||||
putIconPlaceholder(g);
|
||||
}
|
||||
|
||||
g.setItem(32, mkItem(Material.WOODEN_SHOVEL,
|
||||
"§c清除图标",
|
||||
"§7点击可清除已保存的图标"));
|
||||
|
||||
g.setItem(26, mkItem(Material.ARROW, "§7返回"));
|
||||
p.openInventory(g);
|
||||
}
|
||||
|
||||
/**
|
||||
* 图标槽的空占位物品
|
||||
*/
|
||||
private static final String ICON_SLOT_DEFAULT =
|
||||
"§7默认菜单(雪球)";
|
||||
|
||||
private void putIconPlaceholder(Inventory g) {
|
||||
ItemStack empty = new ItemStack(
|
||||
Material.SNOWBALL);
|
||||
ItemMeta m = empty.getItemMeta();
|
||||
m.setDisplayName(ICON_SLOT_DEFAULT);
|
||||
m.setLore(Arrays.asList(
|
||||
"§7Shift+点击背包物品放入",
|
||||
"§7保存后下次登录自动发放"));
|
||||
empty.setItemMeta(m);
|
||||
g.setItem(31, empty);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 处理"我的信息"GUI中菜单图标槽的点击
|
||||
* 在已有的 onInventoryClick 方法中,
|
||||
* 匹配到 T_MY_INFO 标题后调用此方法
|
||||
*
|
||||
* 返回 true 表示已处理(事件应取消)
|
||||
* 返回 false 表示未处理(继续原有逻辑)
|
||||
*/
|
||||
public boolean handleMyInfoIconClick(
|
||||
Player p,
|
||||
InventoryClickEvent event) {
|
||||
|
||||
event.setCancelled(true);
|
||||
Inventory topInv = event.getView()
|
||||
.getTopInventory();
|
||||
int raw = event.getRawSlot();
|
||||
|
||||
if (raw == 32) {
|
||||
doClearIcon(p, topInv);
|
||||
return true;
|
||||
}
|
||||
if (raw != 31) return true;
|
||||
|
||||
ItemStack cursor = event.getCursor();
|
||||
boolean hasCursor = cursor != null
|
||||
&& cursor.getType() != Material.AIR;
|
||||
|
||||
// Shift-click:从背包放入
|
||||
if (event.isShiftClick()
|
||||
&& event.getClickedInventory()
|
||||
== p.getInventory()) {
|
||||
int srcSlot = event.getSlot();
|
||||
ItemStack src =
|
||||
p.getInventory().getItem(srcSlot);
|
||||
if (src != null
|
||||
&& src.getType() != Material.AIR
|
||||
&& !isCustomMenuTrigger(src)) {
|
||||
doPlaceIcon(p, topInv, src, srcSlot);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 普通点击 + 光标有物品 → 放入
|
||||
if (hasCursor
|
||||
&& !isCustomMenuTrigger(cursor)) {
|
||||
doPlaceIcon(p, topInv, cursor, -1);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 普通点击 + 光标空 + 非占位 → 清除
|
||||
if (!hasCursor) {
|
||||
ItemStack cur = topInv.getItem(31);
|
||||
if (!isPlaceholder(cur)) {
|
||||
doClearIcon(p, topInv);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ===== 放入自定义图标 =====
|
||||
private void doPlaceIcon(Player p,
|
||||
Inventory topInv,
|
||||
ItemStack item, int removeSlot) {
|
||||
|
||||
int amount = item.getAmount();
|
||||
removeSnowballFromInventory(p); //没收雪球菜单
|
||||
// 1. 从来源移除
|
||||
if (removeSlot >= 0) {
|
||||
// Shift-click:从背包格子移除
|
||||
p.getInventory()
|
||||
.setItem(removeSlot, null);
|
||||
} else {
|
||||
// 普通点击:从光标移除
|
||||
p.getOpenInventory().setCursor(null);
|
||||
}
|
||||
|
||||
// 2. 退回多余(amount-1)到背包
|
||||
if (amount > 1) {
|
||||
ItemStack excess = item.clone();
|
||||
excess.setAmount(amount - 1);
|
||||
java.util.HashMap<Integer,
|
||||
ItemStack> leftover =
|
||||
p.getInventory()
|
||||
.addItem(excess);
|
||||
for (ItemStack drop :
|
||||
leftover.values()) {
|
||||
p.getWorld().dropItemNaturally(
|
||||
p.getLocation(), drop);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 存原件(数量=1)到DB
|
||||
ItemStack original = item.clone();
|
||||
original.setAmount(1);
|
||||
String b64 = serializeItem(original);
|
||||
if (b64 == null) {
|
||||
p.sendMessage("§c保存失败");
|
||||
return;
|
||||
}
|
||||
plugin.getDb().saveMenuIcon(
|
||||
p.getName(), b64,
|
||||
original.getType().name());
|
||||
|
||||
// 4. 创建带标记的菜单物品
|
||||
ItemStack menu = original.clone();
|
||||
ItemMeta mm = menu.getItemMeta();
|
||||
if (mm != null) {
|
||||
String oldName =
|
||||
mm.getDisplayName();
|
||||
if (oldName == null
|
||||
|| oldName.isEmpty()
|
||||
|| oldName.equals(
|
||||
menu.getType().name())) {
|
||||
oldName = "自定义物品";
|
||||
}
|
||||
mm.setDisplayName(
|
||||
"\u00a7e\u00a7l[菜单] \u00a7f"
|
||||
+ oldName);
|
||||
List<String> lore = mm.hasLore()
|
||||
? new ArrayList<>(mm.getLore())
|
||||
: new ArrayList<>();
|
||||
lore.add(0,
|
||||
"\u00a77右键打开主菜单");
|
||||
mm.setLore(lore);
|
||||
mm.getPersistentDataContainer().set(
|
||||
new org.bukkit.NamespacedKey(
|
||||
plugin, "menu_trigger"),
|
||||
org.bukkit.persistence
|
||||
.PersistentDataType
|
||||
.STRING,
|
||||
"true");
|
||||
menu.setItemMeta(mm);
|
||||
}
|
||||
|
||||
// 5. 发放菜单物品
|
||||
int slot = p.getInventory()
|
||||
.firstEmpty();
|
||||
if (slot >= 0) {
|
||||
p.getInventory().setItem(slot, menu);
|
||||
} else {
|
||||
p.getWorld().dropItemNaturally(
|
||||
p.getLocation(), menu);
|
||||
}
|
||||
|
||||
// 6. 更新GUI
|
||||
topInv.setItem(31, original);
|
||||
p.sendMessage("§a已保存菜单图标: "
|
||||
+ original.getType().name());
|
||||
p.updateInventory();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===== 清除自定义图标 =====
|
||||
private void doClearIcon(Player p,
|
||||
Inventory topInv) {
|
||||
|
||||
String dbIcon = plugin.getDb()
|
||||
.getMenuIcon(p.getName());
|
||||
|
||||
// 1. 没收自定义菜单物品
|
||||
removeCustomMenuFromInventory(p);
|
||||
|
||||
// 2. 删DB
|
||||
plugin.getDb().deleteMenuIcon(
|
||||
p.getName());
|
||||
|
||||
// 3. 强制清空slot 31
|
||||
topInv.setItem(31, null);
|
||||
|
||||
// 4. 发雪球
|
||||
plugin.giveMenuSnowball(p);
|
||||
|
||||
// 5. 还原件
|
||||
if (dbIcon != null && !dbIcon.isEmpty()) {
|
||||
ItemStack original =
|
||||
deserializeItem(dbIcon);
|
||||
if (original != null) {
|
||||
p.getInventory()
|
||||
.addItem(original);
|
||||
}
|
||||
}
|
||||
|
||||
// 6. 重新设置占位符
|
||||
putIconPlaceholder(topInv);
|
||||
p.sendMessage("§c已清除菜单图标");
|
||||
p.updateInventory();
|
||||
}
|
||||
|
||||
|
||||
// ===== 辅助方法 =====
|
||||
|
||||
// 只匹配带[菜单]的非雪球物品
|
||||
private boolean isCustomMenuTrigger(
|
||||
ItemStack item) {
|
||||
if (item == null) return false;
|
||||
if (item.getType() == Material.SNOWBALL)
|
||||
return false;
|
||||
ItemMeta im = item.getItemMeta();
|
||||
if (im == null) return false;
|
||||
if (im.hasDisplayName()
|
||||
&& im.getDisplayName()
|
||||
.contains("[菜单]")) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 只移除自定义菜单物品(不动雪球)
|
||||
private void removeCustomMenuFromInventory(
|
||||
Player p) {
|
||||
for (int i = 0;
|
||||
i < p.getInventory().getSize(); i++) {
|
||||
ItemStack it = p.getInventory()
|
||||
.getItem(i);
|
||||
if (isCustomMenuTrigger(it)) {
|
||||
p.getInventory().setItem(i, null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 只移除雪球菜单
|
||||
private void removeSnowballFromInventory(
|
||||
Player p) {
|
||||
for (int i = 0;
|
||||
i < p.getInventory().getSize(); i++) {
|
||||
ItemStack it = p.getInventory()
|
||||
.getItem(i);
|
||||
if (it != null
|
||||
&& it.getType()
|
||||
== Material.SNOWBALL
|
||||
&& it.hasItemMeta()) {
|
||||
ItemMeta im = it.getItemMeta();
|
||||
if (im.hasLore()
|
||||
&& im.getLore() != null) {
|
||||
for (String ln : im.getLore()) {
|
||||
if (ln.contains(
|
||||
"\u00a78")) {
|
||||
p.getInventory()
|
||||
.setItem(i,
|
||||
null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 已有方法,不需要改
|
||||
private boolean isPlaceholder(
|
||||
ItemStack item) {
|
||||
if (item == null) return true;
|
||||
if (item.getType() == Material.AIR)
|
||||
return true;
|
||||
// fillBg 可能覆盖了slot 31
|
||||
if (item.getType()
|
||||
== Material.GRAY_STAINED_GLASS_PANE)
|
||||
return true;
|
||||
ItemMeta im = item.getItemMeta();
|
||||
if (im == null) return true;
|
||||
if ("\u00a77默认菜单(雪球)"
|
||||
.equals(im.getDisplayName()))
|
||||
return true;
|
||||
if (im.hasLore() && im.getLore() != null
|
||||
&& im.getLore().size() >= 2
|
||||
&& im.getLore().get(0)
|
||||
.contains("Shift+点击"))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// ===== 辅助方法 =====
|
||||
|
||||
private boolean isMenuTriggerItem(
|
||||
ItemStack item) {
|
||||
if (item == null) return false;
|
||||
ItemMeta im = item.getItemMeta();
|
||||
if (im == null) return false;
|
||||
// 自定义图标:显示名含[菜单]
|
||||
if (im.hasDisplayName()
|
||||
&& im.getDisplayName()
|
||||
.contains("[菜单]")) {
|
||||
return true;
|
||||
}
|
||||
// 默认雪球
|
||||
if (item.getType() == Material.SNOWBALL
|
||||
&& im.hasLore()
|
||||
&& im.getLore() != null) {
|
||||
for (String ln : im.getLore()) {
|
||||
if (ln.contains("\u00a77右键打开主菜单"))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*private boolean isPlaceholder(ItemStack item) {
|
||||
if (item == null) return true;
|
||||
if (item.getType() == Material.AIR)
|
||||
return true;
|
||||
ItemMeta im = item.getItemMeta();
|
||||
if (im == null) return true;
|
||||
if ("\u00a77默认菜单(雪球)"
|
||||
.equals(im.getDisplayName())) {
|
||||
return true;
|
||||
}
|
||||
if (im.hasLore() && im.getLore() != null
|
||||
&& im.getLore().size() >= 2
|
||||
&& im.getLore().get(0)
|
||||
.contains("Shift+点击")) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}*/
|
||||
|
||||
private void removeMenuTriggerFromInventory(
|
||||
Player p) {
|
||||
for (int i = 0;
|
||||
i < p.getInventory().getSize(); i++) {
|
||||
ItemStack it = p.getInventory()
|
||||
.getItem(i);
|
||||
if (isMenuTriggerItem(it)) {
|
||||
p.getInventory().setItem(i, null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void removeCustomTriggerFromInventory(
|
||||
Player p) {
|
||||
for (int i = 0;
|
||||
i < p.getInventory().getSize(); i++) {
|
||||
ItemStack it = p.getInventory()
|
||||
.getItem(i);
|
||||
if (it == null) continue;
|
||||
ItemMeta im = it.getItemMeta();
|
||||
if (im == null) continue;
|
||||
if (im.hasDisplayName()
|
||||
&& im.getDisplayName()
|
||||
.contains("[菜单]")) {
|
||||
p.getInventory().setItem(i, null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 在 openGiftStages 方法之前添加:
|
||||
public void openGiftStages(Player p) {
|
||||
Inventory g = Bukkit.createInventory(
|
||||
@@ -1073,11 +1484,30 @@ public class GUIManager implements Listener {
|
||||
cancel.setItemMeta(cml);
|
||||
}
|
||||
g.setItem(15, cancel);
|
||||
// ===== 图标槽31 =====
|
||||
g.setItem(30, mkItem(Material.PAPER,
|
||||
"§e§l自定义菜单图标",
|
||||
"§7Shift+点击背包物品放入右侧槽位",
|
||||
"§7即可保存为你的专属图标",
|
||||
"§7下次加入自动发放"));
|
||||
|
||||
p.openInventory(g);
|
||||
putIconPlaceholder(g);
|
||||
|
||||
String iconB64 = plugin.getDb()
|
||||
.getMenuIcon(p.getName());
|
||||
if (iconB64 != null && !iconB64.isEmpty()) {
|
||||
ItemStack iconItem =
|
||||
deserializeItem(iconB64);
|
||||
if (iconItem != null) {
|
||||
g.setItem(31, iconItem);
|
||||
}
|
||||
}
|
||||
|
||||
g.setItem(32, mkItem(Material.BARRIER,
|
||||
"§c清除图标",
|
||||
"§7点击可清除已保存的图标"));
|
||||
}
|
||||
|
||||
public void openShopAdmin(Player p) {
|
||||
public void openShopAdmin(Player p) {
|
||||
Inventory g = Bukkit.createInventory(
|
||||
null, 54, "§c§l商城管理");
|
||||
ItemStack glass = new ItemStack(
|
||||
@@ -1439,6 +1869,43 @@ public class GUIManager implements Listener {
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 本地序列化,不依赖MenuIconManager =====
|
||||
|
||||
public String serializeItem(ItemStack item) {
|
||||
try {
|
||||
ByteArrayOutputStream baos =
|
||||
new ByteArrayOutputStream();
|
||||
BukkitObjectOutputStream os =
|
||||
new BukkitObjectOutputStream(baos);
|
||||
os.writeObject(item);
|
||||
os.close();
|
||||
return Base64.getEncoder()
|
||||
.encodeToString(baos.toByteArray());
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private ItemStack deserializeItem(String b64) {
|
||||
if (b64 == null || b64.isEmpty()) return null;
|
||||
try {
|
||||
byte[] data =
|
||||
Base64.getDecoder().decode(b64);
|
||||
ByteArrayInputStream bais =
|
||||
new ByteArrayInputStream(data);
|
||||
BukkitObjectInputStream is =
|
||||
new BukkitObjectInputStream(bais);
|
||||
Object obj = is.readObject();
|
||||
is.close();
|
||||
if (obj instanceof ItemStack)
|
||||
return (ItemStack) obj;
|
||||
} catch (Exception e) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static final String ICON_SLOT_EMPTY =
|
||||
"§7图标槽(空)";
|
||||
|
||||
private ItemStack mkItem(Material mat, String name,
|
||||
String... lore) {
|
||||
|
||||
+227
-115
@@ -14,6 +14,9 @@ import java.util.TreeSet;
|
||||
import java.util.UUID;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
|
||||
|
||||
|
||||
import net.milkbowl.vault.economy.Economy;
|
||||
@@ -59,6 +62,7 @@ public class Main extends JavaPlugin
|
||||
TabCompleter {
|
||||
|
||||
private DatabaseManager db;
|
||||
private MenuIconManager menuIconMgr;
|
||||
private ConfigManager config;
|
||||
private EmailManager email;
|
||||
private LoginManager loginMgr;
|
||||
@@ -127,6 +131,8 @@ public class Main extends JavaPlugin
|
||||
public CommissionManager getCommission() {
|
||||
return commission;
|
||||
}
|
||||
//pvp
|
||||
private PVPManager pvpManager;
|
||||
|
||||
private static class PwdRollback {
|
||||
final String hash;
|
||||
@@ -165,6 +171,7 @@ public class Main extends JavaPlugin
|
||||
return db;
|
||||
}
|
||||
|
||||
|
||||
public ConfigManager getConfig2() {
|
||||
return config;
|
||||
}
|
||||
@@ -258,6 +265,9 @@ public class Main extends JavaPlugin
|
||||
// ===== 2. 数据库 =====
|
||||
db = new DatabaseManager(getDataFolder());
|
||||
db.init();
|
||||
menuIconMgr = new MenuIconManager(this);
|
||||
|
||||
|
||||
|
||||
// ===== 3. 菜单 =====
|
||||
menu = new MenuManager(this);
|
||||
@@ -347,7 +357,6 @@ public class Main extends JavaPlugin
|
||||
startPasswordReminder();
|
||||
startTicketAutoProcess();
|
||||
radio.startMainRadio();
|
||||
|
||||
new BukkitRunnable() {
|
||||
public void run() {
|
||||
if (questTracker == null) return;
|
||||
@@ -360,9 +369,73 @@ public class Main extends JavaPlugin
|
||||
}
|
||||
}.runTaskTimer(this, 600L, 600L);
|
||||
|
||||
// 12 ===PVP===
|
||||
pvpManager = new PVPManager(this);
|
||||
getServer().getPluginManager()
|
||||
.registerEvents(pvpManager, this);
|
||||
// PVP区域检测定时器
|
||||
getServer().getScheduler()
|
||||
.runTaskTimer(this,
|
||||
() -> pvpManager.tickRegions(),
|
||||
20L, 20L);
|
||||
setupPVPTab();
|
||||
|
||||
getLogger().info("Sdf1_login v1.0 | 就绪");
|
||||
}
|
||||
|
||||
private void setupPVPTab() {
|
||||
if (getCommand("pvp") != null) {
|
||||
getCommand("pvp")
|
||||
.setTabCompleter(
|
||||
(sender, cmd,
|
||||
alias, args) -> {
|
||||
List<String> list =
|
||||
new ArrayList<>();
|
||||
if (args.length == 1) {
|
||||
String[] sub = {
|
||||
"create", "stats",
|
||||
"list", "delete",
|
||||
"tool", "on", "off",
|
||||
"tempban"};
|
||||
for (String s : sub) {
|
||||
if (s.startsWith(
|
||||
args[0]
|
||||
.toLowerCase())) {
|
||||
list.add(s);
|
||||
}
|
||||
}
|
||||
} else if (args.length == 2) {
|
||||
String sub =
|
||||
args[0].toLowerCase();
|
||||
if (sub.equals("stats")) {
|
||||
for (Player pp : Bukkit
|
||||
.getOnlinePlayers()) {
|
||||
if (pp.getName()
|
||||
.toLowerCase()
|
||||
.startsWith(args[1]
|
||||
.toLowerCase())) {
|
||||
list.add(pp.getName());
|
||||
}
|
||||
}
|
||||
} else if (sub.equals("delete")
|
||||
|| sub.equals("on")
|
||||
|| sub.equals("off")
|
||||
|| sub.equals("tempban")) {
|
||||
for (String rn :
|
||||
pvpManager
|
||||
.getRegionNames()) {
|
||||
if (rn.toLowerCase()
|
||||
.startsWith(args[1]
|
||||
.toLowerCase())) {
|
||||
list.add(rn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return list;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
@@ -1243,10 +1316,10 @@ public class Main extends JavaPlugin
|
||||
// ===== Events =====
|
||||
@EventHandler
|
||||
public void onJoin(PlayerJoinEvent e) {
|
||||
|
||||
Player p = e.getPlayer();
|
||||
String name = p.getName();
|
||||
String ip = getPlayerIP(p);
|
||||
|
||||
// 强制重发资源包(清掉客户端拒绝记录)
|
||||
Bukkit.getScheduler()
|
||||
.runTaskLater(this, () -> {
|
||||
@@ -1450,51 +1523,89 @@ public class Main extends JavaPlugin
|
||||
private static final String MENU_SNOWBALL_TAG =
|
||||
"sdf1_menu";
|
||||
|
||||
// 雪球菜单
|
||||
public void giveMenuSnowball(Player p) {
|
||||
// 检查开关
|
||||
Object val = db.getField(
|
||||
p.getName(), "menu_snowball");
|
||||
if (val != null) {
|
||||
int on = val instanceof Number
|
||||
? ((Number) val).intValue()
|
||||
: 1;
|
||||
if (on == 0) {
|
||||
return; // 开关关了,不发放
|
||||
}
|
||||
? ((Number) val).intValue() : 1;
|
||||
if (on == 0) return;
|
||||
}
|
||||
// 检查是否已有
|
||||
|
||||
for (ItemStack it : p.getInventory()
|
||||
.getContents()) {
|
||||
if (isMenuSnowball(it)) {
|
||||
return;
|
||||
}
|
||||
if (isMenuSnowball(it)) return;
|
||||
if (isCustomMenuTrigger(it)) return;
|
||||
}
|
||||
// 发放
|
||||
|
||||
ItemStack customIcon =
|
||||
menuIconMgr.getIcon(p);
|
||||
if (customIcon != null) {
|
||||
tagAsMenuTrigger(customIcon);
|
||||
// 加外观标识
|
||||
ItemMeta cm = customIcon.getItemMeta();
|
||||
if (cm != null) {
|
||||
String originalName =
|
||||
cm.getDisplayName();
|
||||
if (originalName == null
|
||||
|| originalName.isEmpty()
|
||||
|| originalName.equals(
|
||||
customIcon.getType()
|
||||
.name())) {
|
||||
originalName = "自定义物品";
|
||||
}
|
||||
cm.setDisplayName(
|
||||
"\u00a7e\u00a7l[菜单] \u00a7f"
|
||||
+ originalName);
|
||||
List<String> cmLore =
|
||||
cm.hasLore()
|
||||
? new ArrayList<>(cm.getLore())
|
||||
: new ArrayList<>();
|
||||
// 避免重复添加
|
||||
if (cmLore.isEmpty()
|
||||
|| !cmLore.get(0).contains(
|
||||
"\u00a77右键打开主菜单")) {
|
||||
cmLore.add(0,
|
||||
"\u00a77右键打开主菜单");
|
||||
}
|
||||
cm.setLore(cmLore);
|
||||
customIcon.setItemMeta(cm);
|
||||
}
|
||||
int slot = p.getInventory()
|
||||
.firstEmpty();
|
||||
if (slot >= 0) {
|
||||
p.getInventory().setItem(
|
||||
slot, customIcon);
|
||||
} else {
|
||||
p.getWorld().dropItemNaturally(
|
||||
p.getLocation(), customIcon);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ItemStack snow = new ItemStack(
|
||||
Material.SNOWBALL);
|
||||
ItemMeta im = snow.getItemMeta();
|
||||
if (im != null) {
|
||||
im.setDisplayName(
|
||||
"§e§l[菜单] §f右键打开主菜单");
|
||||
List<String> lore =
|
||||
new ArrayList<>();
|
||||
lore.add("§7右键点击打开功能菜单");
|
||||
lore.add("§8" + MENU_SNOWBALL_TAG);
|
||||
"\u00a7e\u00a7l[菜单] \u00a7f右键打开主菜单");
|
||||
List<String> lore = new ArrayList<>();
|
||||
lore.add("\u00a77右键点击打开功能菜单");
|
||||
lore.add("\u00a78" + MENU_SNOWBALL_TAG);
|
||||
im.setLore(lore);
|
||||
snow.setItemMeta(im);
|
||||
}
|
||||
int slot = p.getInventory()
|
||||
.firstEmpty();
|
||||
if (slot >= 0) {
|
||||
p.getInventory().setItem(
|
||||
slot, snow);
|
||||
p.getInventory().setItem(slot, snow);
|
||||
} else {
|
||||
p.getWorld().dropItemNaturally(
|
||||
p.getLocation(), snow);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 手动发放雪球菜单(绕过开关检查)
|
||||
*/
|
||||
@@ -1549,6 +1660,37 @@ public class Main extends JavaPlugin
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// 运行时初始化版本(避免静态加载问题)
|
||||
private NamespacedKey getTriggerKey() {
|
||||
return new NamespacedKey(this, "menu_trigger");
|
||||
}
|
||||
|
||||
/**
|
||||
* 给物品打上菜单触发标记
|
||||
*/
|
||||
private void tagAsMenuTrigger(ItemStack item) {
|
||||
if (item == null) return;
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
if (meta == null) return;
|
||||
meta.getPersistentDataContainer().set(
|
||||
getTriggerKey(),
|
||||
PersistentDataType.STRING,
|
||||
"true");
|
||||
item.setItemMeta(meta);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查物品是否带菜单触发标记
|
||||
*/
|
||||
private boolean isCustomMenuTrigger(
|
||||
ItemStack item) {
|
||||
if (item == null) return false;
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
if (meta == null) return false;
|
||||
return meta.getPersistentDataContainer()
|
||||
.has(getTriggerKey(),
|
||||
PersistentDataType.STRING);
|
||||
}
|
||||
|
||||
|
||||
@EventHandler
|
||||
@@ -1583,7 +1725,6 @@ public class Main extends JavaPlugin
|
||||
return;
|
||||
}
|
||||
|
||||
// 同时检查主手和副手
|
||||
ItemStack hand = p.getInventory()
|
||||
.getItemInMainHand();
|
||||
ItemStack offhand = p.getInventory()
|
||||
@@ -1593,6 +1734,15 @@ public class Main extends JavaPlugin
|
||||
|| isMenuSnowball(offhand)) {
|
||||
e.setCancelled(true);
|
||||
gui.openMain(p);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isCustomMenuTrigger(hand)
|
||||
|| isCustomMenuTrigger(offhand)) {
|
||||
if (db.hasMenuIcon(p.getName())) {
|
||||
e.setCancelled(true);
|
||||
gui.openMain(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2023,6 +2173,9 @@ public class Main extends JavaPlugin
|
||||
public MenuManager getMenu() {
|
||||
return menu;
|
||||
}
|
||||
public PVPManager getPVPManager() {
|
||||
return pvpManager;
|
||||
}
|
||||
|
||||
|
||||
// ===== Inventory Click(旧逻辑 + 新主菜单布局 + 新功能) =====
|
||||
@@ -2411,7 +2564,6 @@ public class Main extends JavaPlugin
|
||||
break;
|
||||
}}
|
||||
// ==================== 主菜单(新布局) ====================
|
||||
// ===== 主菜单 =====
|
||||
if (title.equals(GUIManager.T_MAIN)) {
|
||||
e.setCancelled(true);
|
||||
|
||||
@@ -2479,10 +2631,14 @@ public class Main extends JavaPlugin
|
||||
"请输入管理密码:");
|
||||
}
|
||||
break;
|
||||
case 31: gui.openMenuManager(p); break;
|
||||
case 32: gui.openMain(p); break;
|
||||
case 31:
|
||||
if (p.isOp()) {
|
||||
gui.openMenuManager(p);
|
||||
}
|
||||
break;
|
||||
|
||||
case 34:
|
||||
if (isAdmin(p)) {
|
||||
if (p.isOp() || isAdmin(p)) {
|
||||
gui.openBalanceOps(p);
|
||||
}
|
||||
break;
|
||||
@@ -2567,6 +2723,9 @@ public class Main extends JavaPlugin
|
||||
// ==================== 我的信息 ====================
|
||||
if (title.equals(GUIManager.T_MY_INFO)) {
|
||||
e.setCancelled(true);
|
||||
// 先处理图标槽
|
||||
if (gui.handleMyInfoIconClick(p, e))
|
||||
return;
|
||||
if (slot == 26) gui.openMain(p);
|
||||
else if (slot == 10) {
|
||||
if (checkIn.isCheckedInToday(
|
||||
@@ -2611,6 +2770,7 @@ public class Main extends JavaPlugin
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// ==================== 管理面板 ====================
|
||||
if (title.equals(GUIManager.T_ADMIN)) {
|
||||
e.setCancelled(true);
|
||||
@@ -2838,95 +2998,6 @@ public class Main extends JavaPlugin
|
||||
p.closeInventory();
|
||||
loginMgr.handleReset(p);
|
||||
}
|
||||
/* .txt子菜单
|
||||
if (title.startsWith("§6§l")
|
||||
&& !title.equals(GUIManager.T_MAIN)
|
||||
&& !title.equals(GUIManager.T_ADMIN)
|
||||
&& !title.equals(GUIManager.T_MY_INFO)
|
||||
&& !title.equals(GUIManager.T_INVITE)
|
||||
&& !title.equals(GUIManager.T_TASK_CENTER)
|
||||
&& !title.equals(GUIManager.T_GIFT_STAGES)
|
||||
&& !title.equals("§6§l任务面板")) {
|
||||
e.setCancelled(true);
|
||||
if (slot == 49) {
|
||||
gui.openMain(p);
|
||||
return;
|
||||
}
|
||||
// ===== 二级菜单(.txt) =====
|
||||
if (title.startsWith("§6§l")
|
||||
&& !title.equals(GUIManager.T_MAIN)
|
||||
&& !title.equals(GUIManager.T_ADMIN)
|
||||
&& !title.equals(GUIManager.T_MY_INFO)
|
||||
&& !title.equals(GUIManager.T_INVITE)
|
||||
&& !title.equals(GUIManager.T_TASK_CENTER)
|
||||
&& !title.equals(GUIManager.T_GIFT_STAGES)
|
||||
&& !title.equals("§6§l任务面板")) {
|
||||
e.setCancelled(true);
|
||||
if (slot == 49) {
|
||||
gui.openMain(p);
|
||||
return;
|
||||
}
|
||||
if (slot < 45 && e.getCurrentItem() != null) {
|
||||
ItemMeta subLm =
|
||||
e.getCurrentItem().getItemMeta();
|
||||
if (subLm != null
|
||||
&& subLm.getLore() != null) {
|
||||
for (String subLn
|
||||
: subLm.getLore()) {
|
||||
if (subLn.contains(
|
||||
"§7指令: §f")) {
|
||||
String cmd =
|
||||
subLn.replace(
|
||||
"§7指令: §f",
|
||||
"").trim();
|
||||
if (cmd.isEmpty()
|
||||
|| cmd.equals("/")
|
||||
|| cmd.equals("null")) {
|
||||
p.sendMessage(
|
||||
"§c该菜单项指令为空");
|
||||
return;
|
||||
}
|
||||
p.closeInventory();
|
||||
if (cmd.startsWith("/"))
|
||||
cmd = cmd.substring(1);
|
||||
Bukkit.dispatchCommand(p, cmd);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (slot < 45 && e.getCurrentItem() != null) {
|
||||
ItemMeta lm =
|
||||
e.getCurrentItem().getItemMeta();
|
||||
if (lm != null && lm.getLore() != null) {
|
||||
for (String ln : lm.getLore()) {
|
||||
if (ln.contains("§7指令: §f")) {
|
||||
String cmd = ln.replace(
|
||||
"§7指令: §f", "")
|
||||
.trim();
|
||||
// ★ 空指令保护 ★
|
||||
if (cmd.isEmpty()
|
||||
|| cmd.equals("/")
|
||||
|| cmd.equals("null")) {
|
||||
p.sendMessage(
|
||||
"§c该菜单项指令为空");
|
||||
return;
|
||||
}
|
||||
p.closeInventory();
|
||||
if (cmd.startsWith("/"))
|
||||
cmd = cmd.substring(1);
|
||||
Bukkit.dispatchCommand(p, cmd);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3091,6 +3162,36 @@ public class Main extends JavaPlugin
|
||||
return;
|
||||
}
|
||||
String t = e.getView().getTitle();
|
||||
// 禁止T_MY_INFO拖拽,只允许点击操作
|
||||
// 我的信息:允许拖入图标槽(31)
|
||||
if (GUIManager.T_MY_INFO.equals(
|
||||
e.getView().getTitle())) {
|
||||
boolean toIconSlot = false;
|
||||
for (int raw : e.getRawSlots()) {
|
||||
if (raw == 31) {
|
||||
toIconSlot = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (toIconSlot) {
|
||||
ItemStack dragged =
|
||||
e.getOldCursor().clone();
|
||||
String b64 = gui.serializeItem(dragged);
|
||||
if (b64 != null) {
|
||||
db.saveMenuIcon(p.getName(), b64,
|
||||
dragged.getType().name());
|
||||
}
|
||||
p.sendMessage("§a已保存菜单图标: "
|
||||
+ dragged.getType().name());
|
||||
// 刷新GUI显示
|
||||
Bukkit.getScheduler()
|
||||
.runTaskLater(this, () ->
|
||||
gui.openMyInfo(p), 2L);
|
||||
}
|
||||
e.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// 回收站拖拽
|
||||
if (t.equals("§6§l垃圾回收站")) {
|
||||
@@ -3153,6 +3254,17 @@ public class Main extends JavaPlugin
|
||||
String[] args) {
|
||||
|
||||
String cmdName = cmd.getName().toLowerCase();
|
||||
|
||||
if (label.equalsIgnoreCase("pvp")) {
|
||||
if (!(sender instanceof Player)) {
|
||||
sender.sendMessage("§c仅玩家可用");
|
||||
return true;
|
||||
}
|
||||
Player p = (Player) sender;
|
||||
return pvpManager.onCommand(p, args);
|
||||
}
|
||||
|
||||
|
||||
// ===== /chat 独立命令 =====
|
||||
if (cmdName.equals("chat")
|
||||
|| cmdName.equals("聊天")) {
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
package Sdf1_login;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.bukkit.util.io.BukkitObjectInputStream;
|
||||
import org.bukkit.util.io.BukkitObjectOutputStream;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.Base64;
|
||||
|
||||
public class MenuIconManager {
|
||||
|
||||
private final Main plugin;
|
||||
private final NamespacedKey iconKey;
|
||||
|
||||
public MenuIconManager(Main plugin) {
|
||||
this.plugin = plugin;
|
||||
this.iconKey = new NamespacedKey(
|
||||
plugin, "custom_menu_icon");
|
||||
}
|
||||
|
||||
// ========== GUIManager 调用的方法 ==========
|
||||
|
||||
/**
|
||||
* Base64 → ItemStack
|
||||
* 对应 GUIManager 中的
|
||||
* plugin.getMenuIconMgr().deserializeItem(...)
|
||||
*/
|
||||
public ItemStack deserializeItem(String b64) {
|
||||
if (b64 == null || b64.isEmpty())
|
||||
return null;
|
||||
try {
|
||||
byte[] data =
|
||||
Base64.getDecoder().decode(b64);
|
||||
ByteArrayInputStream bais =
|
||||
new ByteArrayInputStream(data);
|
||||
BukkitObjectInputStream is =
|
||||
new BukkitObjectInputStream(bais);
|
||||
Object obj = is.readObject();
|
||||
is.close();
|
||||
if (obj instanceof ItemStack)
|
||||
return (ItemStack) obj;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* ItemStack → Base64
|
||||
* 对应 GUIManager 中的
|
||||
* plugin.getMenuIconMgr().serializeItem(...)
|
||||
*/
|
||||
public String serializeItem(ItemStack item) {
|
||||
if (item == null) return null;
|
||||
try {
|
||||
ByteArrayOutputStream baos =
|
||||
new ByteArrayOutputStream();
|
||||
BukkitObjectOutputStream os =
|
||||
new BukkitObjectOutputStream(baos);
|
||||
os.writeObject(item);
|
||||
os.close();
|
||||
return Base64.getEncoder()
|
||||
.encodeToString(
|
||||
baos.toByteArray());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 给物品打上菜单图标标记
|
||||
* 对应 GUIManager 中的
|
||||
* plugin.getMenuIconMgr().ensureMenuIconTag(...)
|
||||
*/
|
||||
public void ensureMenuIconTag(ItemStack item) {
|
||||
if (item == null) return;
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
if (meta == null) return;
|
||||
meta.getPersistentDataContainer().set(
|
||||
iconKey,
|
||||
PersistentDataType.STRING,
|
||||
"menu");
|
||||
item.setItemMeta(meta);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图标Key
|
||||
* 对应 GUIManager 中的
|
||||
* plugin.getMenuIconMgr().getIconKey()
|
||||
*/
|
||||
public NamespacedKey getIconKey() {
|
||||
return iconKey;
|
||||
}
|
||||
|
||||
// ========== LoginManager / Main 调用的方法 ==========
|
||||
|
||||
/**
|
||||
* 获取玩家自定义图标物品
|
||||
* 有返回物品,无返回null
|
||||
*/
|
||||
public ItemStack getIcon(Player player) {
|
||||
String b64 = plugin.getDb()
|
||||
.getMenuIcon(player.getName());
|
||||
if (b64 == null || b64.isEmpty())
|
||||
return null;
|
||||
ItemStack item = deserializeItem(b64);
|
||||
if (item == null) return null;
|
||||
ensureMenuIconTag(item);
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存图标到DB
|
||||
*/
|
||||
public void saveIcon(Player player,
|
||||
ItemStack item) {
|
||||
String b64 = serializeItem(item);
|
||||
if (b64 != null) {
|
||||
plugin.getDb().saveMenuIcon(
|
||||
player.getName(), b64,
|
||||
item.getType().name());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除图标
|
||||
*/
|
||||
public void removeIcon(Player player) {
|
||||
plugin.getDb().deleteMenuIcon(
|
||||
player.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查物品是否是自定义菜单图标
|
||||
*/
|
||||
public boolean isCustomMenuIcon(
|
||||
ItemStack item) {
|
||||
if (item == null) return false;
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
if (meta == null) return false;
|
||||
return meta.getPersistentDataContainer()
|
||||
.has(iconKey,
|
||||
PersistentDataType.STRING);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -250,6 +250,95 @@ public class RadioManager {
|
||||
Arrays.sort(files);
|
||||
for (File f : files) list.add(f);
|
||||
}
|
||||
/**
|
||||
* 解析 pack.png:优先读取插件目录,
|
||||
* 没有则搜索图片并用ffmpeg转码
|
||||
*/
|
||||
private File resolvePackIcon() {
|
||||
File dataDir = plugin.getDataFolder();
|
||||
File packPng = new File(dataDir, "pack.png");
|
||||
|
||||
if (packPng.exists()
|
||||
&& packPng.length() > 0) {
|
||||
plugin.getLogger().info(
|
||||
"[Radio] 找到 pack.png ("
|
||||
+ packPng.length()
|
||||
+ " bytes)");
|
||||
return packPng;
|
||||
}
|
||||
|
||||
// 搜索可转换的图片文件
|
||||
String[] exts = {"png", "jpg", "jpeg",
|
||||
"bmp", "webp", "tiff", "gif"};
|
||||
File[] found = dataDir.listFiles(
|
||||
(dir, name) -> {
|
||||
String low = name.toLowerCase();
|
||||
for (String ext : exts) {
|
||||
if (low.endsWith("." + ext)
|
||||
&& !name.equals(
|
||||
"pack.png")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (found != null && found.length > 0) {
|
||||
File src = found[0];
|
||||
plugin.getLogger().info(
|
||||
"[Radio] 找到图片: "
|
||||
+ src.getName()
|
||||
+ " → 转码为 pack.png");
|
||||
if (convertToPackPng(src, packPng)) {
|
||||
return packPng;
|
||||
}
|
||||
plugin.getLogger().warning(
|
||||
"[Radio] 转码失败");
|
||||
return null;
|
||||
}
|
||||
|
||||
plugin.getLogger().warning(
|
||||
"[Radio] 未找到 pack.png 或可转换图片");
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* ffmpeg 将任意图片转为 256x256 PNG
|
||||
*/
|
||||
private boolean convertToPackPng(
|
||||
File input, File output) {
|
||||
try {
|
||||
ProcessBuilder pb = new ProcessBuilder(
|
||||
"ffmpeg", "-y",
|
||||
"-i", input.getAbsolutePath(),
|
||||
"-vf",
|
||||
"scale='min(256,iw)'"
|
||||
+ ":'min(256,ih)':"
|
||||
+ "force_original_aspect_ratio"
|
||||
+ "=decrease,"
|
||||
+ "pad=256:256:"
|
||||
+ "(ow-iw)/2:"
|
||||
+ "(oh-ih)/2:"
|
||||
+ "color=0x00000000",
|
||||
"-c:v", "png",
|
||||
output.getAbsolutePath());
|
||||
pb.redirectErrorStream(true);
|
||||
Process p = pb.start();
|
||||
boolean ok = p.waitFor(
|
||||
30, TimeUnit.SECONDS);
|
||||
if (!ok) {
|
||||
p.destroyForcibly();
|
||||
return false;
|
||||
}
|
||||
return output.exists()
|
||||
&& output.length() > 0;
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().warning(
|
||||
"[Radio] ffmpeg转码失败: "
|
||||
+ e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 资源包构建 ==========
|
||||
private void buildResourcePack() {
|
||||
@@ -320,6 +409,25 @@ public class RadioManager {
|
||||
plugin.getLogger().warning(
|
||||
"[Radio] sounds.json写入失败");
|
||||
}
|
||||
// sounds.json 写入后...
|
||||
|
||||
// ===== 新增:打包 pack.png 作为资源包图标 =====
|
||||
File iconPng = resolvePackIcon();
|
||||
if (iconPng != null && iconPng.exists()) {
|
||||
try {
|
||||
Files.copy(iconPng.toPath(),
|
||||
new File(packDir, "pack.png")
|
||||
.toPath(),
|
||||
StandardCopyOption
|
||||
.REPLACE_EXISTING);
|
||||
plugin.getLogger().info(
|
||||
"[Radio] pack.png 已加入资源包");
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().warning(
|
||||
"[Radio] pack.png复制失败: "
|
||||
+ e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// pack.mcmeta(pack_format=54)
|
||||
try {
|
||||
|
||||
@@ -86,4 +86,7 @@ commands:
|
||||
usage: /垃圾清理
|
||||
menu:
|
||||
description: 雪球菜单开关控制
|
||||
usage: /menu <开/关>
|
||||
usage: /menu <开/关>
|
||||
pvp :
|
||||
description: PVP榜单
|
||||
usage: /pvp
|
||||
Reference in New Issue
Block a user