var/global/list/tickets = list() var/global/list/ticket_panels = list() /datum/ticket var/owner var/list/assigned_admins = list() var/status = TICKET_OPEN var/list/msgs = list() var/closed_by var/id var/opened_time // Real time references for SQL based logging. var/opened_rt var/closed_rt var/response_time var/reminder_timer /datum/ticket/New(var/owner) src.owner = owner tickets |= src id = tickets.len opened_time = world.time opened_rt = world.realtime if (config.ticket_reminder_period) reminder_timer = addtimer(CALLBACK(src, .proc/remind), config.ticket_reminder_period SECONDS, TIMER_UNIQUE|TIMER_STOPPABLE) /datum/ticket/proc/broadcast_closure(closing_user) var/client/owner_client = client_by_ckey(owner) if(owner_client && owner_client.adminhelped == ADMINHELPED_DISCORD) discord_bot.send_to_admins("[key_name(owner_client)]'s request for help has been closed/deemed unnecessary by [closing_user].") owner_client.adminhelped = ADMINHELPED /datum/ticket/proc/set_to_closed(closing_key) src.status = TICKET_CLOSED src.closed_by = closing_key src.closed_rt = world.realtime update_ticket_panels() if (reminder_timer) deltimer(reminder_timer) log_to_db() /datum/ticket/proc/close(var/client/closed_by) if(!closed_by) return FALSE if(status == TICKET_CLOSED) return FALSE if(status == TICKET_ASSIGNED && !((closed_by.ckey in assigned_admins) || owner == closed_by.ckey) && alert(closed_by, "You are not assigned to this ticket. Are you sure you want to close it?", "Close ticket?" , "Yes" , "No") != "Yes") return FALSE if(status == TICKET_ASSIGNED && !closed_by.holder) // non-admins can only close a ticket if no admin has taken it return FALSE broadcast_closure(key_name(closed_by)) to_chat(client_by_ckey(src.owner), "Your ticket has been closed by [closed_by].") message_admins("[src.owner]'s ticket has been closed by [key_name(closed_by)].") set_to_closed(closed_by.ckey) return TRUE /datum/ticket/proc/close_remotely(closing_user) if (!closing_user) return FALSE if (status != TICKET_OPEN) return FALSE broadcast_closure("[closing_user] (Remotely)") to_chat(client_by_ckey(src.owner), "Your ticket has been closed by [closing_user] (remotely).") message_admins("[src.owner]'s ticket has been closed by [closing_user] (remotely).") set_to_closed(closing_user) return TRUE /datum/ticket/proc/take(var/client/assigned_admin) if(!assigned_admin) return if(status == TICKET_CLOSED) return if(assigned_admin.ckey == owner) return if(status == TICKET_ASSIGNED && ((assigned_admin.ckey in assigned_admins) || alert(assigned_admin, "This ticket is already assigned. Do you want to add yourself to the ticket?", "Join ticket?" , "Yes" , "No") != "Yes")) return assigned_admins |= assigned_admin.ckey src.status = TICKET_ASSIGNED var/client/owner_client = client_by_ckey(src.owner) if(owner_client && owner_client.adminhelped == ADMINHELPED_DISCORD) discord_bot.send_to_admins("[key_name(owner_client)]'s request for help has been taken by [key_name(assigned_admin)].") owner_client.adminhelped = ADMINHELPED message_admins("[key_name(assigned_admin)] has assigned themself to [src.owner]'s ticket.") to_chat(owner_client, "[assigned_admin] has added themself to your ticket and should respond shortly. Thanks for your patience!") to_chat(assigned_admin, get_options_bar(owner_client, 2, 1, 1)) update_ticket_panels() return 1 /datum/ticket/proc/remind() if (status == TICKET_CLOSED) reminder_timer = null return var/admin_found = FALSE for (var/ckey in assigned_admins) var/client/C = client_by_ckey(ckey) if (C) admin_found = TRUE to_chat(C, "You have yet to close [owner]'s ticket!") sound_to(C, 'sound/effects/adminhelp.ogg') if (!admin_found) message_admins("[owner]'s ticket has yet to be closed!") for(var/s in staff) var/client/C = s if((C.holder.rights & (R_ADMIN|R_MOD)) && (C.prefs.toggles & SOUND_ADMINHELP)) sound_to(C, 'sound/effects/adminhelp.ogg') reminder_timer = addtimer(CALLBACK(src, .proc/remind), config.ticket_reminder_period SECONDS, TIMER_UNIQUE|TIMER_STOPPABLE) /proc/get_open_ticket_by_ckey(var/owner) for(var/datum/ticket/ticket in tickets) if(ticket.owner == owner && (ticket.status == TICKET_OPEN || ticket.status == TICKET_ASSIGNED)) return ticket // there should only be one open ticket by a client at a time, so no need to keep looking /proc/get_ticket_by_id(id) for (var/datum/ticket/ticket in tickets) if (ticket.id == id) return ticket return null /datum/ticket/proc/is_active() if(status != TICKET_ASSIGNED) return 0 for(var/admin in assigned_admins) var/client/admin_client = client_by_ckey(admin) if(admin_client && !admin_client.is_afk()) return 1 return 0 /datum/ticket/proc/log_to_db() if (status != TICKET_CLOSED) return if (!establish_db_connection(dbcon)) return var/DBQuery/Q = dbcon.NewQuery({"INSERT INTO ss13_tickets (game_id, message_count, admin_count, admin_list, opened_by, taken_by, closed_by, response_delay, opened_at, closed_at) VALUES (:g_id:, :m_count:, :a_count:, :a_list:, :opened_by:, :taken_by:, :closed_by:, :delay:, :opened_at:, :closed_at:)"}) Q.Execute(list( "g_id" = game_id, "m_count" = length(msgs), "a_count" = length(assigned_admins), "a_list" = json_encode(assigned_admins), "opened_by" = owner, "taken_by" = length(assigned_admins) ? assigned_admins[1] : null, "closed_by" = closed_by, "delay" = response_time || -1, "opened_at" = SQLtime(opened_rt), "closed_at" = SQLtime(closed_rt) )) /datum/ticket/proc/append_message(m_from, m_to, msg) msgs += new /datum/ticket_msg(m_from, m_to, msg) if (!response_time && m_from != owner) response_time = round((world.time - opened_time) / 10) update_ticket_panels() /datum/ticket_msg var/msg_from var/msg_to var/msg var/time_stamp /datum/ticket_msg/New(var/msg_from, var/msg_to, var/msg) src.msg_from = msg_from src.msg_to = msg_to src.msg = msg src.time_stamp = time_stamp() /datum/ticket_panel var/datum/ticket/open_ticket = null var/datum/browser/ticket_panel_window /datum/ticket_panel/proc/get_dat() var/client/C = ticket_panel_window.user.client if(!C) return var/list/dat = list() var/valid_holder = check_rights(R_MOD|R_ADMIN, FALSE, ticket_panel_window.user) var/list/ticket_dat = list() for(var/id = tickets.len, id >= 1, id--) var/datum/ticket/ticket = tickets[id] if(valid_holder || ticket.owner == C.ckey) var/client/owner_client = client_by_ckey(ticket.owner) var/status = "Unknown status" var/color = "#6aa84f" switch(ticket.status) if(TICKET_OPEN) status = "Opened [round((world.time - ticket.opened_time) / (1 MINUTE))] minute\s ago, unassigned" if(TICKET_ASSIGNED) status = "Assigned to [english_list(ticket.assigned_admins, "no one")]" color = "#ffffff" if(TICKET_CLOSED) status = "Closed by [ticket.closed_by]" color = "#cc2222" ticket_dat += "
  • " if(open_ticket && open_ticket == ticket) ticket_dat += "" ticket_dat += "Ticket #[id] - [ticket.owner] [owner_client ? "" : "(DC)"] - [status]
    VIEW" if(ticket.status) ticket_dat += " - PM" if(valid_holder) ticket_dat += " - [(ticket.status == TICKET_OPEN) ? "TAKE" : "JOIN"]" if(ticket.status != TICKET_CLOSED && (valid_holder || ticket.status == TICKET_OPEN)) ticket_dat += " - CLOSE" if(valid_holder) var/ref_mob = "" if(owner_client) ref_mob = "\ref[owner_client.mob]" ticket_dat += " - ? - PP - VV - SM[owner_client ? "- [admin_jump_link(owner_client.mob, src)]" : ""]" if(open_ticket && open_ticket == ticket) ticket_dat += "
    " ticket_dat += "
  • " if(ticket_dat.len) dat += "

    Available tickets:

    " if(open_ticket) dat += "

    \[X\] Messages for ticket #[open_ticket.id]:

    " var/list/msg_dat = list() for(var/datum/ticket_msg/msg in open_ticket.msgs) var/msg_to = msg.msg_to ? msg.msg_to : "Adminhelp" msg_dat += "
  • \[[msg.time_stamp]\] [msg.msg_from] -> [msg_to]: [C.holder ? generate_ahelp_key_words(C.mob, msg.msg) : msg.msg]
  • " if(msg_dat.len) dat += "
    " else dat += "

    No messages to display.

    " else dat += "

    No tickets to display.

    " dat += "" return jointext(dat, null) /datum/ticket_panel/Topic(href, href_list) ..() if(href_list["close"]) ticket_panels -= usr.client switch(href_list["action"]) if("unview") open_ticket = null ticket_panel_window.set_content(get_dat()) ticket_panel_window.update() var/datum/ticket/ticket = locate(href_list["ticket"]) if(!istype(ticket)) return switch(href_list["action"]) if("view") open_ticket = ticket ticket_panel_window.set_content(get_dat()) ticket_panel_window.update() if("take") ticket.take(usr.client) if("close") ticket.close(usr.client) if("pm") if(check_rights(R_MOD|R_ADMIN) && ticket.owner != usr.ckey) usr.client.cmd_admin_pm(client_by_ckey(ticket.owner), ticket = ticket) else if(ticket.status == TICKET_ASSIGNED) // manually check that the target client exists here as to not spam the usr for each logged out admin on the ticket var/admin_found = 0 for(var/admin in ticket.assigned_admins) var/client/admin_client = client_by_ckey(admin) if(admin_client) admin_found = 1 usr.client.cmd_admin_pm(admin_client, ticket = ticket) break if(!admin_found) to_chat(usr, "Error: Private-Message: Client not found. They may have lost connection, so please be patient!") else usr.client.adminhelp(input(usr,"", "adminhelp \"text\"") as text) /client/verb/view_tickets() set name = "View Tickets" set category = "Admin" var/datum/ticket_panel/ticket_panel = new() ticket_panels[src] = ticket_panel ticket_panel.ticket_panel_window = new(src.mob, "ticketpanel", "Ticket Manager", 1024, 768, ticket_panel) ticket_panel.ticket_panel_window.set_content(ticket_panel.get_dat()) ticket_panel.ticket_panel_window.open() /proc/update_ticket_panels() for(var/client/C in ticket_panels) var/datum/ticket_panel/ticket_panel = ticket_panels[C] if(C.mob != ticket_panel.ticket_panel_window.user) C.view_tickets() else ticket_panel.ticket_panel_window.set_content(ticket_panel.get_dat()) ticket_panel.ticket_panel_window.update()