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 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> 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 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> 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> pending = plugin.getDb() .getTicketsByStatus( "pending"); List> list = new ArrayList<>(); for (Map 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> 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 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 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 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 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 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> 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 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 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> list = plugin.getDb() .getAllServiceProviders(); if (list.isEmpty()) { p.sendMessage("§7暂无服务商"); return; } p.sendMessage( "§e§l=== 服务商列表 ==="); for (Map 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> deleteTickets = plugin.getDb() .getTicketsByStatus( "processing"); for (Map 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> list = plugin.getDb() .getUnconfirmedCompleted( timeoutMs); for (Map 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> stale = plugin.getDb() .getRepliedStaleTickets( timeoutMs); for (Map 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 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 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 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 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> list) { if (list.isEmpty()) { g.setItem(22, mk(Material.BARRIER, "§7暂无工单")); return; } int slot = 0; for (Map t : list) { if (slot >= 45) break; g.setItem(slot, makeItem(t)); slot++; } } /** * 执行删号流程 */ /** * 执行删号流程 */ public void processDeleteAccount(int tid, Map 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 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 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> all = plugin.getDb() .getTicketsByProviderAll( p.getName()); fillList(g, all); g.setItem(49, mk(Material.ARROW, "§7返回")); p.openInventory(g); } private ItemStack makeItem( Map 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 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 m, String key) { Object v = m.get(key); return v != null ? String.valueOf(v) : ""; } private int nv(Map 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 list = new ArrayList<>(); for (String l : lore) { if (!l.isEmpty()) list.add(l); } if (!list.isEmpty()) im.setLore(list); } it.setItemMeta(im); } return it; } }