Files
Sdf1_login/txt/TicketManager.txt
T

1651 lines
58 KiB
Plaintext

package Sdf1_login;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class TicketManager {
private final Main plugin;
public static final String T_TICKET_MAIN =
"§6§l工单中心";
public static final String T_TICKET_CREATE =
"§e§l创建工单";
public static final String T_TICKET_MY =
"§b§l我的工单";
public static final String T_TICKET_ADMIN =
"§c§l工单管理";
public static final String T_TICKET_DETAIL =
"§a§l工单详情";
public static final String TYPE_BUG =
"bug反馈";
public static final String TYPE_HELP =
"求助";
public static final String TYPE_REPORT =
"举报";
public static final String TYPE_APPLY =
"申请";
public static final String TYPE_OTHER =
"其他";
public static final String TYPE_DELETE =
"删号";
public static final String TYPE_PASSWORD =
"修改密码";
private static final Map<String, Integer>
PRICES = new LinkedHashMap<>();
static {
PRICES.put(TYPE_BUG, 10);
PRICES.put(TYPE_HELP, 5);
PRICES.put(TYPE_REPORT, 8);
PRICES.put(TYPE_APPLY, 15);
PRICES.put(TYPE_OTHER, 5);
PRICES.put(TYPE_DELETE, 0);
PRICES.put(TYPE_PASSWORD, 0);
}
public TicketManager(Main plugin) {
this.plugin = plugin;
}
// ========== GUI ==========
public void openMain(Player p) {
Inventory g = Bukkit.createInventory(
null, 27, T_TICKET_MAIN);
fillBg(g);
g.setItem(10, mk(Material.PAPER,
"§e创建工单",
"§7提交bug、求助、举报等"));
g.setItem(12, mk(Material.BOOK,
"§b我的工单",
"§7查看已提交的工单"));
g.setItem(14, mk(Material.CHEST,
"§6工单记录",
"§7查看历史工单"));
if (adm(p)) {
g.setItem(16, mk(Material.REDSTONE,
"§c工单管理",
"§7管理员专用"));
}
if (plugin.getDb()
.isServiceProvider(
p.getName())) {
g.setItem(18, mk(Material.EMERALD,
"§a服务商面板",
"§7接单、处理工单"));
}
g.setItem(22, mk(Material.ARROW,
"§7返回"));
p.openInventory(g);
}
public void openCreate(Player p) {
Inventory g = Bukkit.createInventory(
null, 36, T_TICKET_CREATE);
fillBg(g);
g.setItem(10, mk(Material.BOOK,
"§cbug反馈"));
g.setItem(11, mk(Material.EMERALD,
"§a求助"));
g.setItem(12, mk(Material.BLAZE_POWDER,
"§6举报"));
g.setItem(13, mk(Material.NAME_TAG,
"§e申请"));
g.setItem(14, mk(Material.PAPER,
"§7其他"));
g.setItem(20, mk(Material.BARRIER,
"§c申请删号",
"§7申请删除账号"));
g.setItem(21, mk(Material.NETHER_WART,
"§d申请改密",
"§7请求管理员协助改密"));
g.setItem(31, mk(Material.ARROW,
"§7返回"));
p.openInventory(g);
}
public void openMyTickets(Player p) {
Inventory g = Bukkit.createInventory(
null, 54, T_TICKET_MY);
fillBg(g);
List<Map<String, Object>> list =
plugin.getDb()
.getTicketsByPlayer(
p.getName());
fillList(g, list);
g.setItem(49, mk(Material.ARROW,
"§7返回"));
p.openInventory(g);
}
public void openDetail(Player p, int tid) {
Map<String, Object> t =
plugin.getDb().getTicket(tid);
if (t == null) {
p.sendMessage("§c工单不存在");
return;
}
Inventory g = Bukkit.createInventory(
null, 27, T_TICKET_DETAIL);
fillBg(g);
String type = sv(t, "type");
String status = sv(t, "status");
String requester = sv(t, "requester");
String title = sv(t, "title");
String desc = sv(t, "description");
String assigned = sv(t, "assigned_to");
int reward = nv(t, "reward_amount");
int score = nv(t, "score");
int confirmed = nv(t, "admin_confirmed");
g.setItem(10, mk(Material.PAPER,
"§e§l" + (title.isEmpty()
? type : title),
"§7类型: §f" + type,
"§7状态: " + stxt(status),
"§7提交者: §f" + requester,
"§7奖励: §e" + reward + " 积分"));
g.setItem(12, mk(Material.BOOK,
"§7详细描述",
desc.isEmpty() ? "§7无描述"
: "§7" + (desc.length() > 40
? desc.substring(0, 40)
+ "..." : desc)));
if (!assigned.isEmpty()) {
g.setItem(14, mk(Material.PLAYER_HEAD,
"§7处理人: §f" + assigned,
confirmed == 1
? "§7评分: §e" + score
: "§7处理中"));
} else {
g.setItem(14, mk(Material.BARRIER,
"§7未分配"));
}
// 驳回原因
if ("closed".equals(status)) {
String reason = sv(t, "reject_reason");
if (!reason.isEmpty()) {
g.setItem(13, mk(Material.BARRIER,
"§c驳回原因",
"§7" + reason));
}
}
// 回复按钮(非关闭/已完结状态)
if (!"closed".equals(status)
&& !"withdrawn".equals(status)
&& !"resolved".equals(status)) {
g.setItem(15, mk(
Material.WRITABLE_BOOK,
"§b回复工单",
"§7向对方发送消息"));
g.setItem(16, mk(Material.BOOK,
"§7查看回复记录"));
}
// ===== 管理员按钮 =====
if (adm(p)) {
if ("pending".equals(status)) {
// 待分配:可分配给服务商
g.setItem(19, mk(Material.EMERALD,
"§a分配工单",
"§7指定服务商处理"));
// 也可自己处理
g.setItem(20, mk(Material.LIME_DYE,
"§a我来处理",
"§7管理员直接处理此工单"));
}
if ("processing".equals(status)
&& !assigned.equals("admin")
&& !assigned.isEmpty()) {
// 服务商在处理:可取消指派收回
g.setItem(19, mk(Material.BARRIER,
"§c取消指派",
"§7取消服务商指派,收回工单"));
// 也可自己接手处理
g.setItem(20, mk(Material.LIME_DYE,
"§a我来处理",
"§7管理员直接处理此工单"));
}
if ("processing".equals(status)
&& assigned.equals("admin")) {
// 管理员自己在处理:可标记完结
g.setItem(19, mk(Material.LIME_DYE,
"§a标记完结",
"§7确认工单处理完成"));
}
if ("replied".equals(status)
&& assigned.equals("admin")) {
g.setItem(19, mk(Material.LIME_DYE,
"§a标记完结",
"§7确认工单处理完成"));
}
// 驳回(非已关闭/已完结状态)
if (!"closed".equals(status)
&& !"resolved".equals(status)
&& !"withdrawn".equals(status)) {
g.setItem(21, mk(Material.RED_DYE,
"§c驳回工单",
"§7驳回并关闭此工单"));
}
}
// ===== 服务商按钮 =====
if (plugin.getDb()
.isServiceProvider(p.getName())
&& !TYPE_DELETE.equals(type)
&& !TYPE_PASSWORD.equals(type)) {
if ("pending".equals(status)
&& assigned.isEmpty()) {
// 待分配无人:抢单
g.setItem(19, mk(
Material.GOLD_INGOT,
"§6抢单",
"§7接取此工单"));
}
if (assigned.equals(p.getName())
&& ("processing".equals(status)
|| "replied".equals(status))) {
// 我在处理:标记完结/上报异常
g.setItem(19, mk(
Material.LIME_DYE,
"§a标记完结",
"§7上报处理完成"));
g.setItem(20, mk(
Material.REDSTONE,
"§c上报异常",
"§7报告处理中的问题"));
}
}
g.setItem(22, mk(Material.ARROW,
"§7返回"));
ItemStack info = g.getItem(10);
if (info != null) {
ItemMeta im = info.getItemMeta();
if (im != null) {
im.setCustomModelData(tid);
info.setItemMeta(im);
}
}
p.openInventory(g);
}
public void openAdminList(Player p) {
Inventory g = Bukkit.createInventory(
null, 54, T_TICKET_ADMIN);
fillBg(g);
g.setItem(45, mk(Material.EMERALD,
"§a邀请服务商"));
g.setItem(46, mk(Material.BARRIER,
"§c移除服务商"));
g.setItem(47, mk(Material.CLOCK,
"§e服务商列表"));
g.setItem(48, mk(Material.NETHER_STAR,
"§e直接派单",
"§7管理员直接创建并派单"));
List<Map<String, Object>> all =
new ArrayList<>();
all.addAll(plugin.getDb()
.getTicketsByStatus("pending"));
all.addAll(plugin.getDb()
.getTicketsByStatus("processing"));
all.addAll(plugin.getDb()
.getTicketsByStatus("replied"));
all.addAll(plugin.getDb()
.getTicketsByStatus("completed"));
all.addAll(plugin.getDb()
.getTicketsByStatus("resolved"));
all.addAll(plugin.getDb()
.getTicketsByStatus("closed"));
fillList(g, all);
g.setItem(49, mk(Material.ARROW,
"§7返回"));
p.openInventory(g);
}
public void openProviderPanel(Player p) {
Inventory g = Bukkit.createInventory(
null, 27, "§d§l服务商面板");
fillBg(g);
g.setItem(10, mk(Material.CHEST,
"§e抢单大厅",
"§7查看可接工单"));
g.setItem(12, mk(Material.CLOCK,
"§b我的工单",
"§7我正在处理的工单"));
g.setItem(14, mk(Material.PAPER,
"§6历史工单",
"§7已完成的工单"));
g.setItem(22, mk(Material.ARROW,
"§7返回"));
p.openInventory(g);
}
public void openGrabList(Player p) {
Inventory g = Bukkit.createInventory(
null, 54, "§6§l抢单大厅");
fillBg(g);
List<Map<String, Object>> pending =
plugin.getDb()
.getTicketsByStatus(
"pending");
List<Map<String, Object>> list =
new ArrayList<>();
for (Map<String, Object> t : pending) {
if (sv(t, "assigned_to").isEmpty()) {
list.add(t);
}
}
fillList(g, list);
g.setItem(49, mk(Material.ARROW,
"§7返回"));
p.openInventory(g);
}
public void openMyAssigned(Player p) {
Inventory g = Bukkit.createInventory(
null, 54,
"§b§l我处理中的工单");
fillBg(g);
List<Map<String, Object>> list =
plugin.getDb()
.getTicketsByProvider(
p.getName());
fillList(g, list);
g.setItem(49, mk(Material.ARROW,
"§7返回"));
p.openInventory(g);
}
// ========== 业务 ==========
public int submitTicket(Player p,
String type, String title,
String desc) {
int price = PRICES
.getOrDefault(type, 5);
int id = plugin.getDb().createTicket(
type, "pending", "normal",
p.getName(), title, desc,
price, "");
if (id > 0) {
if (TYPE_DELETE.equals(type)
|| TYPE_PASSWORD.equals(type)) {
plugin.getDb().updateTicketField(
id, "assigned_to", "admin");
// 播放派单音效
if (plugin.getTicketSound() != null) {
plugin.getTicketSound()
.playSound(p, "assign");
}
plugin.getDb().updateTicketStatus(
id, "processing");
p.sendMessage(
"§a§l[工单] §f工单 #"
+ id + " 已提交,"
+ "等待管理员处理");
} else {
p.sendMessage(
"§a§l[工单] §f创建成功!"
+ " #§e" + id
+ " 奖励: §e"
+ price + "积分");
}
notifyAdmins(p.getName(), id);
} else {
p.sendMessage("§c创建失败");
}
return id;
}
public void adminCreateAndAssign(
Player p, String type,
String title, String desc,
String provider, int price) {
if (!plugin.getDb()
.isServiceProvider(provider)) {
p.sendMessage("§c" + provider
+ " 不是服务商");
return;
}
int id = plugin.getDb().createTicket(
type, "processing", "normal",
p.getName(), title, desc,
price, "");
if (id <= 0) {
p.sendMessage("§c创建失败");
return;
}
plugin.getDb().assignTicket(
id, provider);
if (price > 0) {
plugin.getDb().updateTicketField(
id, "reward_amount", price);
}
p.sendMessage("§a工单 #" + id
+ " 已创建并分配给 " + provider);
Player tp = Bukkit.getPlayer(provider);
if (tp != null && tp.isOnline()) {
tp.sendMessage(
"§e§l[工单] §f管理员指派工单 #"
+ id + " 给您");
playAssignedSound(tp);
}
}
public void grabTicket(Player p, int tid) {
Map<String, Object> t =
plugin.getDb().getTicket(tid);
if (t == null) {
p.sendMessage("§c工单不存在");
return;
}
if (!"pending".equals(
sv(t, "status"))) {
p.sendMessage("§c该工单已被接单");
return;
}
if (!sv(t, "assigned_to").isEmpty()) {
p.sendMessage("§c该工单已被分配");
return;
}
plugin.getDb().assignTicket(
tid, p.getName());
p.sendMessage("§a抢单成功!工单 #" + tid);
Player rp = Bukkit.getPlayer(
sv(t, "requester"));
if (rp != null && rp.isOnline()) {
rp.sendMessage(
"§e§l[工单] §f您的工单 #"
+ tid + " 已被 "
+ p.getName()
+ " 接单");
}
}
public void assignToProvider(Player p,
int tid, String provider,
int price) {
if (!plugin.getDb()
.isServiceProvider(provider)) {
p.sendMessage("§c" + provider
+ " 不是服务商");
return;
}
plugin.getDb().assignTicket(
tid, provider);
if (price > 0) {
plugin.getDb().updateTicketStatus(
tid, "processing");
plugin.getDb().updateTicketField(
tid, "reward_amount", price);
}
p.sendMessage("§a工单 #" + tid
+ " 已分配给 " + provider);
Player tp = Bukkit.getPlayer(provider);
if (tp != null && tp.isOnline()) {
tp.sendMessage(
"§e§l[工单] §f您有新工单: #"
+ tid);
playAssignedSound(tp);
}
}
public void confirmAndScore(Player p,
int tid, int score) {
if (score < 1 || score > 5) {
p.sendMessage("§c评分1-5");
return;
}
plugin.getDb().confirmTicket(
tid, score);
p.sendMessage("§a工单 #" + tid
+ " 已确认,评分: " + score);
Map<String, Object> t =
plugin.getDb().getTicket(tid);
if (t != null) {
Player rp = Bukkit.getPlayer(
sv(t, "requester"));
if (rp != null && rp.isOnline()) {
rp.sendMessage(
"§a§l[工单] §f工单 #"
+ tid
+ " 已确认完结");
}
}
settleTicket(tid);
}
public void completeAsProvider(Player p,
int tid) {
Map<String, Object> t =
plugin.getDb().getTicket(tid);
if (t == null) {
p.sendMessage("§c工单不存在");
return;
}
if (!sv(t, "assigned_to")
.equals(p.getName())) {
p.sendMessage("§c这不是你的工单");
return;
}
plugin.getDb().completeTicket(tid);
p.sendMessage("§a工单 #" + tid
+ " 已标记完结,等待确认");
Player rp = Bukkit.getPlayer(
sv(t, "requester"));
if (rp != null && rp.isOnline()) {
rp.sendMessage(
"§a§l[工单] §f工单 #"
+ tid
+ " 已处理完成");
}
}
public void rejectTicket(Player p,
int tid, String reason) {
plugin.getDb().updateTicketStatus(
tid, "closed");
plugin.getDb().updateTicketField(
tid, "reject_reason", reason);
p.sendMessage("§c工单 #" + tid
+ " 已驳回并关闭");
Map<String, Object> t =
plugin.getDb().getTicket(tid);
if (t != null) {
Player rp = Bukkit.getPlayer(
sv(t, "requester"));
if (rp != null && rp.isOnline()) {
rp.sendMessage(
"§c§l[工单] §f工单 #"
+ tid
+ " 被驳回: "
+ reason);
}
}
}
public void replyTicket(Player p,
int tid, String message) {
Map<String, Object> t =
plugin.getDb().getTicket(tid);
if (t == null) {
p.sendMessage("§c工单不存在");
return;
}
String status = sv(t, "status");
if ("closed".equals(status)
|| "withdrawn".equals(status)
|| "resolved".equals(status)) {
p.sendMessage("§c该工单已关闭");
return;
}
plugin.getDb().addTicketReply(
tid, p.getName(), message);
p.sendMessage("§a回复成功");
String requester =
sv(t, "requester");
String assigned =
sv(t, "assigned_to");
String sender = p.getName();
if (!requester.equals(sender)) {
Player rp = Bukkit.getPlayer(
requester);
if (rp != null && rp.isOnline()) {
rp.sendMessage(
"§e§l[工单] §f工单 #"
+ tid + " 新回复");
}
}
if (!assigned.isEmpty()
&& !assigned.equals(sender)) {
Player ap = Bukkit.getPlayer(
assigned);
if (ap != null && ap.isOnline()) {
ap.sendMessage(
"§e§l[工单] §f工单 #"
+ tid + " 新回复");
}
}
}
public void showReplies(Player p,
int tid) {
List<Map<String, Object>> replies =
plugin.getDb()
.getTicketReplies(tid);
if (replies.isEmpty()) {
p.sendMessage("§7暂无回复");
return;
}
p.sendMessage("§e§l=== 回复记录 ===");
SimpleDateFormat sdf =
new SimpleDateFormat(
"MM-dd HH:mm");
for (Map<String, Object> r : replies) {
String sender = sv(r, "sender");
String msg2 = sv(r, "message");
long time = ((Number) r.get(
"create_time")).longValue();
p.sendMessage("§7[" + sdf.format(
new Date(time)) + "] §f"
+ sender + "§7: " + msg2);
}
p.sendMessage(
"§e§l=================");
}
public void withdrawTicket(Player p,
int tid) {
Map<String, Object> t =
plugin.getDb().getTicket(tid);
if (t == null) {
p.sendMessage("§c工单不存在");
return;
}
if (!sv(t, "requester")
.equals(p.getName())
&& !adm(p)) {
p.sendMessage("§c无权操作");
return;
}
plugin.getDb().updateTicketStatus(
tid, "withdrawn");
p.sendMessage("§a工单 #" + tid
+ " 已撤回");
}
public void showProviderList(Player p) {
List<Map<String, Object>> list =
plugin.getDb()
.getAllServiceProviders();
if (list.isEmpty()) {
p.sendMessage("§7暂无服务商");
return;
}
p.sendMessage(
"§e§l=== 服务商列表 ===");
for (Map<String, Object> pro : list) {
p.sendMessage("§7- §f"
+ pro.get("player_name")
+ " §7角色: §e"
+ pro.get("role"));
}
p.sendMessage(
"§e§l===================");
}
public void autoProcessCompleted() {
long timeoutMs = 10 * 60 * 1000L;
// 自动处理删号工单(5分钟自动通过)
List<Map<String, Object>> deleteTickets =
plugin.getDb()
.getTicketsByStatus(
"processing");
for (Map<String, Object> t : deleteTickets) {
String dtype = sv(t, "type");
if (!TYPE_DELETE.equals(dtype)) continue;
int did = nv(t, "id");
int dconf = nv(t, "admin_confirmed");
// 已经处理过了跳过
if (dconf != 0) continue;
String dasg = sv(t, "assigned_to");
// 只处理分配给admin的
if (!"admin".equals(dasg)) continue;
long createTime = ((Number) t.get(
"create_time")).longValue();
if (System.currentTimeMillis()
- createTime >= 5 * 60 * 1000L) {
processDeleteAccount(did, t);
}
}
// 已完结未确认→10分钟自动完结
List<Map<String, Object>> list =
plugin.getDb()
.getUnconfirmedCompleted(
timeoutMs);
for (Map<String, Object> t : list) {
int id = nv(t, "id");
plugin.getDb().confirmTicket(id, 3);
Player rp = Bukkit.getPlayer(
sv(t, "requester"));
if (rp != null && rp.isOnline())
rp.sendMessage(
"§e§l[工单] §f工单 #"
+ id + " 已自动完结");
String assigned =
sv(t, "assigned_to");
if (!assigned.isEmpty()) {
Player pp =
Bukkit.getPlayer(assigned);
if (pp != null && pp.isOnline())
pp.sendMessage(
"§e§l[工单] §f工单 #"
+ id
+ " 已自动完结");
}
settleTicket(id);
}
// 已回复但10分钟无互动→自动完结
List<Map<String, Object>> stale =
plugin.getDb()
.getRepliedStaleTickets(
timeoutMs);
for (Map<String, Object> t : stale) {
int id = nv(t, "id");
plugin.getDb().confirmTicket(id, 3);
Player rp = Bukkit.getPlayer(
sv(t, "requester"));
if (rp != null && rp.isOnline())
rp.sendMessage(
"§e§l[工单] §f工单 #"
+ id + " 超时自动完结");
settleTicket(id);
}
}
// ========== GUI点击 ==========
public boolean handleClick(Player p,
String title, int slot) {
if (title.equals(T_TICKET_MAIN)) {
if (slot == 10) openCreate(p);
else if (slot == 12)
openMyTickets(p);
else if (slot == 14)
openMyTickets(p);
else if (slot == 16) {
if (adm(p)) openAdminList(p);
else p.sendMessage("§c权限不足");
}
else if (slot == 18) {
if (plugin.getDb()
.isServiceProvider(p.getName())) {
openProviderPanel(p);
} else {
p.sendMessage("§c权限不足");
}
}
else if (slot == 22)
plugin.getGui().openMain(p);
return true;
}
if (title.equals(T_TICKET_CREATE)) {
if (slot == 31) {
openMain(p);
return true;
}
String type = null;
if (slot == 10) type = TYPE_BUG;
else if (slot == 11) type = TYPE_HELP;
else if (slot == 12) type = TYPE_REPORT;
else if (slot == 13) type = TYPE_APPLY;
else if (slot == 14) type = TYPE_OTHER;
else if (slot == 20) type = TYPE_DELETE;
else if (slot == 21) type = TYPE_PASSWORD;
if (type != null) {
p.closeInventory();
plugin.getChatInput()
.getState(p).type =
ChatInputManager.InputType
.TICKET_TITLE;
plugin.getChatInput()
.getState(p)
.ticketType = type;
if (TYPE_DELETE.equals(type)) {
p.sendMessage("§e请描述删号原因:");
} else if (TYPE_PASSWORD.equals(type)) {
p.sendMessage("§e请描述需要协助的内容:");
} else {
p.sendMessage("§e请输入工单标题:");
}
}
return true;
}
if (title.equals(T_TICKET_MY)) {
if (slot == 49) {
openMain(p);
return true;
}
if (slot >= 0 && slot < 45) {
ItemStack it = p.getOpenInventory()
.getTopInventory().getItem(slot);
int id = gt(it);
if (id > 0) openDetail(p, id);
}
return true;
}
if (title.equals(T_TICKET_DETAIL)) {
return handleClickDetail(p, slot);
}
if (title.equals(T_TICKET_ADMIN)) {
if (slot == 49) {
openMain(p);
return true;
}
if (slot == 45) {
p.closeInventory();
plugin.getChatInput()
.getState(p).type =
ChatInputManager.InputType
.TICKET_INVITE_PROVIDER;
p.sendMessage("§e输入服务商玩家名:");
return true;
}
if (slot == 46) {
p.closeInventory();
plugin.getChatInput()
.getState(p).type =
ChatInputManager.InputType
.TICKET_REMOVE_PROVIDER;
p.sendMessage("§e输入要移除的服务商:");
return true;
}
if (slot == 47) {
showProviderList(p);
return true;
}
if (slot == 48) {
p.closeInventory();
plugin.getChatInput()
.getState(p).type =
ChatInputManager.InputType
.TICKET_ADMIN_TYPE;
p.sendMessage("§e选择工单类型:");
p.sendMessage("§7bug/求助/举报/申请/其他");
return true;
}
if (slot >= 0 && slot < 45) {
ItemStack it = p.getOpenInventory()
.getTopInventory().getItem(slot);
int id = gt(it);
if (id > 0) openDetail(p, id);
}
return true;
}
if (title.equals("§d§l服务商面板")) {
if (slot == 22) {
plugin.getGui().openMain(p);
return true;
}
if (slot == 10) openGrabList(p);
else if (slot == 12) openMyAssigned(p);
else if (slot == 14) openMyTickets(p);
return true;
}
if (title.equals("§6§l抢单大厅")) {
if (slot == 49) {
openProviderPanel(p);
return true;
}
if (slot >= 0 && slot < 45) {
ItemStack it = p.getOpenInventory()
.getTopInventory().getItem(slot);
int id = gt(it);
if (id > 0) openDetail(p, id);
}
return true;
}
if (title.equals("§b§l我处理中的工单")) {
if (slot == 49) {
openProviderPanel(p);
return true;
}
if (slot >= 0 && slot < 45) {
ItemStack it = p.getOpenInventory()
.getTopInventory().getItem(slot);
int id = gt(it);
if (id > 0) openDetail(p, id);
}
return true;
}
return false;
}
private boolean handleClickDetail(Player p,
int slot) {
if (slot == 22) {
openMyTickets(p);
return true;
}
int id = gtFromDetail(p);
if (id <= 0) return true;
Map<String, Object> t =
plugin.getDb().getTicket(id);
if (t == null) return true;
String st = sv(t, "status");
int conf = nv(t, "admin_confirmed");
String asg = sv(t, "assigned_to");
// 回复按钮
if (slot == 15) {
if (!"closed".equals(st)
&& !"withdrawn".equals(st)
&& !"resolved".equals(st)) {
p.closeInventory();
plugin.getChatInput()
.getState(p).type =
ChatInputManager.InputType
.TICKET_REPLY;
plugin.getChatInput()
.getState(p).ticketId = id;
p.sendMessage("§e输入回复内容:");
}
return true;
}
// 查看回复记录
if (slot == 16) {
showReplies(p, id);
return true;
}
// slot 19
if (slot == 19) {
// 管理员:分配工单
if (adm(p) && "pending".equals(st)
&& asg.isEmpty()) {
p.closeInventory();
plugin.getChatInput()
.getState(p).type =
ChatInputManager.InputType
.TICKET_ASSIGN;
plugin.getChatInput()
.getState(p).ticketId = id;
p.sendMessage(
"§e输入: 服务商名 价格");
return true;
}
// 管理员:取消指派(服务商在处理中)
if (adm(p) && "processing".equals(st)
&& !asg.equals("admin")
&& !asg.isEmpty()) {
cancelTicket(p, id);
openDetail(p, id);
return true;
}
// 管理员:自己处理(pending)
if (adm(p) && "pending".equals(st)
&& asg.isEmpty()) {
plugin.getDb().assignTicket(
id, "admin");
plugin.getDb().updateTicketStatus(
id, "processing");
p.sendMessage("§a工单 #" + id
+ " 已由您接手处理");
openDetail(p, id);
return true;
}
// 管理员:标记完结(已回复状态)
if (adm(p) && "replied".equals(st)
&& asg.equals("admin")) {
// 管理员:标记完结(已回复状态)
if (adm(p) && "replied".equals(st)
&& asg.equals("admin")) {
Map<String, Object> tk =
plugin.getDb().getTicket(id);
String tkType = sv(tk, "type");
if (TYPE_DELETE.equals(tkType)) {
approveDeleteTicket(p, id);
return true;
}
plugin.getDb().completeTicket(id);
p.sendMessage("§a工单 #" + id
+ " 已标记完结");
openDetail(p, id);
return true;
}
}
// 服务商:抢单
if (plugin.getDb()
.isServiceProvider(p.getName())
&& "pending".equals(st)
&& asg.isEmpty()) {
grabTicket(p, id);
openDetail(p, id);
return true;
}
// 服务商:标记完结
if (plugin.getDb()
.isServiceProvider(p.getName())
&& asg.equals(p.getName())
&& ("processing".equals(st)
|| "replied".equals(st))) {
completeAsProvider(p, id);
openDetail(p, id);
return true;
}
return true;
}
// slot 20
if (slot == 20) {
// 管理员:自己处理(pending)
if (adm(p) && "pending".equals(st)
&& asg.isEmpty()) {
plugin.getDb().assignTicket(
id, "admin");
plugin.getDb().updateTicketStatus(
id, "processing");
p.sendMessage("§a工单 #" + id
+ " 已由您接手处理");
openDetail(p, id);
return true;
}
// 管理员:自己接手(服务商在处理中)
if (adm(p) && "processing".equals(st)
&& !asg.equals("admin")
&& !asg.isEmpty()) {
plugin.getDb().assignTicket(
id, "admin");
p.sendMessage("§a工单 #" + id
+ " 已由您接手处理");
openDetail(p, id);
return true;
}
// 服务商:上报异常
if (plugin.getDb()
.isServiceProvider(p.getName())
&& asg.equals(p.getName())
&& ("processing".equals(st)
|| "replied".equals(st))) {
p.closeInventory();
plugin.getChatInput()
.getState(p).type =
ChatInputManager.InputType
.TICKET_REPORT_ISSUE;
plugin.getChatInput()
.getState(p).ticketId = id;
p.sendMessage("§e输入异常描述:");
return true;
}
return true;
}
// slot 21:驳回
if (slot == 21) {
if (adm(p) && !"closed".equals(st)
&& !"resolved".equals(st)
&& !"withdrawn".equals(st)) {
p.closeInventory();
plugin.getChatInput()
.getState(p).type =
ChatInputManager.InputType
.TICKET_REJECT;
plugin.getChatInput()
.getState(p).ticketId = id;
p.sendMessage("§e输入驳回原因:");
}
return true;
}
return true;
}
// ========== 聊天输入 ==========
public boolean handleChatInput(Player p,
String msg,
ChatInputManager.InputType type) {
switch (type) {
case TICKET_TITLE: {
plugin.getChatInput()
.getState(p)
.ticketTitle = msg;
plugin.getChatInput()
.getState(p).type =
ChatInputManager.InputType
.TICKET_DESC;
p.sendMessage(
"§e请输入工单详细描述:");
return true;
}
case TICKET_DESC: {
String tt = plugin.getChatInput()
.getState(p).ticketType;
String tl = plugin.getChatInput()
.getState(p).ticketTitle;
plugin.getChatInput().reset(p);
submitTicket(p, tt, tl, msg);
return true;
}
case TICKET_SCORE: {
int tid2 = plugin.getChatInput()
.getState(p).ticketId;
try {
int sc = Integer.parseInt(
msg.trim());
Map<String, Object> tk =
plugin.getDb()
.getTicket(tid2);
String st = tk != null
? sv(tk, "status")
: "";
if ("completed".equals(st)) {
plugin.getChatInput()
.reset(p);
confirmAndScore(
p, tid2, sc);
} else {
plugin.getChatInput()
.reset(p);
p.sendMessage(
"§c工单尚未处理完成");
}
} catch (
NumberFormatException e) {
plugin.getChatInput()
.reset(p);
p.sendMessage(
"§c请输入数字1-5");
}
return true;
}
case TICKET_REJECT: {
int tid2 = plugin.getChatInput()
.getState(p).ticketId;
plugin.getChatInput().reset(p);
rejectTicket(p, tid2, msg);
return true;
}
case TICKET_ASSIGN: {
int tid2 = plugin.getChatInput()
.getState(p).ticketId;
String[] parts = msg.trim()
.split("\\s+");
String prov = parts[0];
int price = 0;
if (parts.length >= 2) {
try {
price = Integer.parseInt(
parts[1]);
} catch (
NumberFormatException e) {
p.sendMessage(
"§c价格格式错误");
}
}
plugin.getChatInput().reset(p);
assignToProvider(p, tid2,
prov, price);
runSync(() ->
openDetail(p, tid2));
return true;
}
case TICKET_INVITE_PROVIDER: {
String tgt = msg.trim();
plugin.getChatInput().reset(p);
if (!plugin.getDb()
.userExists(tgt)) {
p.sendMessage(
"§c玩家不存在");
return true;
}
plugin.getDb()
.addServiceProvider(
tgt, "waiter");
p.sendMessage("§a已邀请 " + tgt
+ " 成为服务商");
return true;
}
case TICKET_REMOVE_PROVIDER: {
String tgt = msg.trim();
plugin.getChatInput().reset(p);
if (!plugin.getDb()
.isServiceProvider(
tgt)) {
p.sendMessage("§c" + tgt
+ " 不是服务商");
return true;
}
plugin.getDb()
.removeServiceProvider(
tgt);
p.sendMessage(
"§a已移除 " + tgt);
return true;
}
default:
return false;
}
}
// ========== 私有 ==========
private void settleTicket(int tid) {
Map<String, Object> t =
plugin.getDb().getTicket(tid);
if (t == null) return;
String requester =
sv(t, "requester");
String assigned =
sv(t, "assigned_to");
int reward = nv(t, "reward_amount");
int score = nv(t, "score");
ConfigManager cfg =
plugin.getConfig2();
if (reward > 0) {
plugin.getDb().addPoints(
requester, reward);
Player rp = Bukkit.getPlayer(
requester);
if (rp != null && rp.isOnline()) {
rp.sendMessage(
"§e§l[工单] §f获得 "
+ reward
+ " 积分奖励");
}
}
if (!assigned.isEmpty()) {
int pts = (int) (score
* cfg.providerPointsPerScore);
if (pts > 0) {
plugin.getDb().addPoints(
assigned, pts);
}
double money =
cfg.baseEconomyReward
+ score
* cfg.providerEconomyPerScore;
boolean ok = false;
try {
if (plugin.getEconomy() != null) {
OfflinePlayer op =
Bukkit.getOfflinePlayer(
assigned);
plugin.getEconomy()
.depositPlayer(
op, money);
ok = true;
}
} catch (Exception e) {
plugin.getLogger().warning(
"[Sdf1_login] 经济失败: "
+ e.getMessage());
}
Player pp =
Bukkit.getPlayer(assigned);
if (pp != null && pp.isOnline()) {
pp.sendMessage(
"§a§l[工单] §f工单 #"
+ tid
+ " 确认完结");
if (pts > 0)
pp.sendMessage("§7积分: §e"
+ pts);
if (ok)
pp.sendMessage("§7经济: §a$"
+ String.format(
"%.0f", money));
}
}
}
private void notifyAdmins(String creator,
int tid) {
String tag =
plugin.getConfig2().adminTag;
for (Player op :
Bukkit.getOnlinePlayers()) {
if (op.getScoreboardTags()
.contains(tag)) {
op.sendMessage(
"§e§l[工单] §f" + creator
+ " 提交了新工单 #"
+ tid);
}
}
}
private void playAssignedSound(Player p) {
for (int i = 0; i < 3; i++) {
final int d = i * 15;
Bukkit.getScheduler()
.runTaskLater(plugin, () -> {
if (p.isOnline()) {
p.playSound(p.getLocation(),
Sound
.BLOCK_NOTE_BLOCK_CHIME,
1.0f, 0.79f);
p.playSound(p.getLocation(),
Sound
.BLOCK_NOTE_BLOCK_BASS,
1.0f, 0.79f);
p.playSound(p.getLocation(),
Sound
.BLOCK_NOTE_BLOCK_PLING,
1.0f, 0.79f);
}
}, d);
}
}
private void runSync(Runnable task) {
Bukkit.getScheduler()
.runTask(plugin, task);
}
private boolean adm(Player p) {
return p.getScoreboardTags().contains(
plugin.getConfig2().adminTag);
}
private void fillBg(Inventory g) {
ItemStack glass = mk(
Material.GRAY_STAINED_GLASS_PANE,
" ");
for (int i = 0; i < g.getSize(); i++) {
if (g.getItem(i) == null
|| g.getItem(i).getType()
== Material.AIR) {
g.setItem(i, glass);
}
}
}
private void fillList(Inventory g,
List<Map<String, Object>> list) {
if (list.isEmpty()) {
g.setItem(22, mk(Material.BARRIER,
"§7暂无工单"));
return;
}
int slot = 0;
for (Map<String, Object> t : list) {
if (slot >= 45) break;
g.setItem(slot, makeItem(t));
slot++;
}
}
/**
* 执行删号流程
*/
/**
* 执行删号流程
*/
public void processDeleteAccount(int tid,
Map<String, Object> t) {
String requester = sv(t, "requester");
plugin.getDb().updateTicketStatus(
tid, "resolved");
plugin.getDb().updateTicketField(tid,
"admin_confirmed", 1);
Player target =
Bukkit.getPlayer(requester);
if (target != null && target.isOnline()) {
plugin.backupInventory(target);
target.sendMessage(
"§a§l[工单] §f您的删号请求已通过");
target.sendMessage(
"§7物品已备份,下次登录可取回");
target.kickPlayer(
"§a删号成功,物品已备份。\n"
+ "请等待5分钟后重新注册取回物品。");
} else {
// 离线:仅标记,不操作数据库
plugin.getLogger().info(
"[Sdf1_login] " + requester
+ " 删号工单已通过,等待上线执行");
}
// 延迟执行删号
final String name = requester;
Bukkit.getScheduler().runTaskLater(
plugin, () -> {
plugin.getDb().deleteUser(name);
plugin.getLogger().info(
"[Sdf1_login] " + name
+ " 删号工单已执行");
}, 100L);
}
/**
* 管理员手动批准删号
*/
public void approveDeleteTicket(Player p,
int tid) {
Map<String, Object> t =
plugin.getDb().getTicket(tid);
if (t == null) {
p.sendMessage("§c工单不存在");
return;
}
if (!TYPE_DELETE.equals(sv(t, "type"))) {
p.sendMessage("§c这不是删号工单");
return;
}
p.sendMessage("§a工单 #" + tid
+ " 已批准,正在执行删号...");
processDeleteAccount(tid, t);
}
/**
* 管理员取消工单(服务商异常后)
*/
/**
* 管理员取消工单(解除服务商绑定,管理员可自行处理)
*/
public void cancelTicket(Player p, int tid) {
Map<String, Object> t =
plugin.getDb().getTicket(tid);
if (t == null) {
p.sendMessage("§c工单不存在");
return;
}
// 解除服务商绑定,状态回退到pending
plugin.getDb().updateTicketField(
tid, "assigned_to", "");
plugin.getDb().updateTicketStatus(
tid, "pending");
p.sendMessage("§a工单 #" + tid
+ " 已取消指派,回到待分配状态");
// 通知原服务商
String assigned = sv(t, "assigned_to");
if (!assigned.isEmpty()) {
Player ap = Bukkit.getPlayer(assigned);
if (ap != null && ap.isOnline()) {
ap.sendMessage(
"§c§l[工单] §f工单 #"
+ tid
+ " 已被管理员取消指派");
}
}
}
/**
* 服务商查看历史工单
*/
public void openProviderHistory(Player p) {
Inventory g = Bukkit.createInventory(
null, 54,
"§6§l我的历史工单");
fillBg(g);
List<Map<String, Object>> all =
plugin.getDb()
.getTicketsByProviderAll(
p.getName());
fillList(g, all);
g.setItem(49, mk(Material.ARROW,
"§7返回"));
p.openInventory(g);
}
private ItemStack makeItem(
Map<String, Object> t) {
int id = nv(t, "id");
String type = sv(t, "type");
String status = sv(t, "status");
String requester =
sv(t, "requester");
String title = sv(t, "title");
int reward = nv(t, "reward_amount");
Material mat;
switch (status) {
case "processing":
mat = Material.YELLOW_DYE;
break;
case "replied":
mat = Material.CYAN_DYE;
break;
case "completed":
mat = Material.LIME_DYE;
break;
case "resolved":
mat = Material.LIME_DYE;
break;
case "closed":
mat = Material.GRAY_DYE;
break;
case "rejected":
mat = Material.RED_DYE;
break;
case "withdrawn":
mat = Material.GRAY_DYE;
break;
default:
mat = Material.ORANGE_DYE;
break;
}
ItemStack item = new ItemStack(mat);
ItemMeta im = item.getItemMeta();
if (im != null) {
im.setDisplayName("§e#" + id
+ " " + (title.isEmpty()
? type : title));
List<String> lore =
new ArrayList<>();
lore.add("§7类型: §f" + type);
lore.add("§7状态: "
+ stxt(status));
lore.add("§7提交者: §f"
+ requester);
lore.add("§7奖励: §e" + reward
+ " 积分");
lore.add("");
lore.add("§7点击查看详情");
im.setLore(lore);
im.setCustomModelData(id);
item.setItemMeta(im);
}
return item;
}
private int gt(ItemStack item) {
if (item == null) return -1;
ItemMeta im = item.getItemMeta();
if (im == null) return -1;
if (im.hasCustomModelData())
return im.getCustomModelData();
return -1;
}
private int gtFromDetail(Player p) {
ItemStack it = p.getOpenInventory()
.getTopInventory().getItem(10);
return gt(it);
}
private String stxt(String status) {
switch (status) {
case "pending":
return "§e待处理";
case "processing":
return "§b处理中";
case "replied":
return "§9已回复";
case "completed":
return "§a待确认";
case "resolved":
return "§a已完结";
case "closed":
return "§7已关闭";
case "rejected":
return "§c已驳回";
case "withdrawn":
return "§7已撤回";
default:
return "§7" + status;
}
}
private String sv(Map<String, Object> m,
String key) {
Object v = m.get(key);
return v != null
? String.valueOf(v) : "";
}
private int nv(Map<String, Object> m,
String key) {
Object v = m.get(key);
return v instanceof Number
? ((Number) v).intValue() : 0;
}
private ItemStack mk(Material mat,
String name, String... lore) {
ItemStack it = new ItemStack(mat);
ItemMeta im = it.getItemMeta();
if (im != null) {
im.setDisplayName(name);
if (lore.length > 0) {
List<String> list =
new ArrayList<>();
for (String l : lore) {
if (!l.isEmpty())
list.add(l);
}
if (!list.isEmpty())
im.setLore(list);
}
it.setItemMeta(im);
}
return it;
}
}