mirror of
https://github.com/yogstation13/Yogstation.git
synced 2025-02-26 09:04:50 +00:00
1075 lines
44 KiB
Plaintext
1075 lines
44 KiB
Plaintext
/client/var/adminhelptimerid = 0 //a timer id for returning the ahelp verb
|
||
/client/var/datum/admin_help/current_ticket //the current ticket the (usually) not-admin client is dealing with
|
||
|
||
//
|
||
//TICKET MANAGER
|
||
//
|
||
|
||
GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
|
||
|
||
/datum/admin_help_tickets
|
||
var/list/tickets_list = list()
|
||
var/ticketAmount = 0
|
||
|
||
/datum/admin_help_tickets/Destroy()
|
||
QDEL_LIST(tickets_list)
|
||
return ..()
|
||
|
||
/datum/admin_help_tickets/proc/TicketByID(id)
|
||
for(var/I in tickets_list)
|
||
for(var/J in I)
|
||
var/datum/admin_help/AH = J
|
||
if(AH.id == id)
|
||
return J
|
||
|
||
/datum/admin_help_tickets/proc/TicketsByCKey(ckey)
|
||
. = list()
|
||
for(var/I in tickets_list)
|
||
for(var/J in I)
|
||
var/datum/admin_help/AH = J
|
||
if(AH.initiator_ckey == ckey)
|
||
. += AH
|
||
|
||
//opens the ticket listings for one of the 3 states
|
||
/datum/admin_help_tickets/proc/BrowseTickets(state)
|
||
var/title
|
||
var/list/dat = list("<html><head><meta charset='UTF-8'><title>[title]</title></head>")
|
||
dat += "<A href='?_src_=holder;[HrefToken()];ahelp_tickets=[state]'>Refresh</A><br><br>"
|
||
for(var/I in tickets_list)
|
||
var/datum/admin_help/AH = I
|
||
dat += "<span class='adminnotice'>[span_adminhelp("Ticket #[AH.id]")]: <A href='?_src_=holder;[HrefToken()];ahelp=[REF(AH)];ahelp_action=ticket'>[AH.initiator_key_name]: [AH.name]</A></span><br>"
|
||
|
||
usr << browse(dat.Join(), "window=ahelp_list[state];size=600x480")
|
||
|
||
//Reassociate still open ticket if one exists
|
||
/datum/admin_help_tickets/proc/ClientLogin(client/C)
|
||
C.current_ticket = CKey2ActiveTicket(C.ckey)
|
||
if(C.current_ticket)
|
||
C.current_ticket.initiator = C
|
||
C.current_ticket.AddInteraction("Client reconnected.", ckey = C.ckey)
|
||
|
||
//Dissasociate ticket
|
||
/datum/admin_help_tickets/proc/ClientLogout(client/C)
|
||
if(C.current_ticket)
|
||
C.current_ticket.AddInteraction("Client disconnected.")
|
||
C.current_ticket.initiator = null
|
||
C.current_ticket = null
|
||
|
||
//Get a ticket given a ckey
|
||
/datum/admin_help_tickets/proc/CKey2ActiveTicket(ckey)
|
||
for(var/I in tickets_list)
|
||
var/datum/admin_help/AH = I
|
||
if(AH.initiator_ckey == ckey)
|
||
return AH
|
||
|
||
|
||
//
|
||
// TICKET LOG DATUM
|
||
//
|
||
|
||
/datum/ticket_log
|
||
var/datum/admin_help/parent
|
||
var/gametime
|
||
var/user
|
||
var/user_admin = FALSE
|
||
var/text
|
||
var/text_admin
|
||
var/for_admins
|
||
|
||
/datum/ticket_log/New(var/datum/admin_help/parent, var/user, var/text, var/for_admins = 0)
|
||
src.gametime = gameTimestamp()
|
||
src.parent = parent
|
||
if(istype(user, /client))
|
||
src.user_admin = is_admin(user)
|
||
|
||
src.for_admins = for_admins
|
||
src.user = get_fancy_key(user)
|
||
src.text = text
|
||
src.text_admin = generate_admin_info(text)
|
||
|
||
var/datum/DBQuery/add_interaction_query = SSdbcore.NewQuery(
|
||
"INSERT INTO `[format_table_name("admin_ticket_interactions")]` (`ticket_id`,`user`,`text`) VALUES (:ticket_id,:user,:text)",
|
||
list("ticket_id" = src.parent.db_id, "user" = src.user, "text" = src.text)
|
||
)
|
||
add_interaction_query.Execute()
|
||
qdel(add_interaction_query)
|
||
|
||
/datum/ticket_log/proc/isAdminComment()
|
||
return istype(user, /client) && (for_admins && !(compare_ckey(parent.initiator_ckey, user) || compare_ckey(parent.handling_admin, user)) ? 1 : 0)
|
||
|
||
/datum/ticket_log/proc/toSanitizedString()
|
||
return "[gametime] - [user] - [text]"
|
||
|
||
/datum/ticket_log/proc/toString()
|
||
return "[gametime] - [isAdminComment() ? "<font color='red'>" : ""]<b>[istype(user, /client) ? key_name_params(user, 0, 0, null, parent) : user]</b>[isAdminComment() ? "</font>" : ""] - [text]"
|
||
|
||
/datum/ticket_log/proc/toAdminString()
|
||
return "[gametime] - [isAdminComment() ? "<font color='red'>" : ""]<b>[istype(user, /client) ? key_name_params(user, 0, 1, null, parent) : user]</b>[isAdminComment() ? "</font>" : ""] - [text_admin]"
|
||
|
||
/datum/ticket_log/proc/toLogString()
|
||
return "[isAdminComment() ? "COMMENT - " : ""][istype(user, /client) ? key_name_params(user, 0, 1, null, parent) : user] - [text]"
|
||
|
||
|
||
//
|
||
//TICKET DATUM
|
||
//
|
||
|
||
/datum/admin_help
|
||
var/db_id
|
||
var/id
|
||
var/name
|
||
var/state = AHELP_ACTIVE
|
||
|
||
var/opened_at
|
||
var/closed_at
|
||
|
||
var/client/handling_admin
|
||
|
||
var/client/initiator //semi-misnomer, it's the person who ahelped/was bwoinked
|
||
var/initiator_ckey
|
||
var/initiator_key_name
|
||
var/heard_by_no_admins = FALSE
|
||
var/popups_enabled = FALSE // if TRUE, gives a pop-up to the nonadmin to respond to the ticket, whenever the admin speaks.
|
||
|
||
var/list/_interactions //use AddInteraction() or, preferably, admin_ticket_log()
|
||
var/static/ticket_counter = 0
|
||
var/static/last_bwoinking = 0
|
||
var/static/last_unclaimed_notification = 0
|
||
|
||
//call this on its own to create a ticket, don't manually assign current_ticket
|
||
//msg is the title of the ticket: usually the ahelp text
|
||
//is_bwoink is TRUE if this ticket was started by an admin PM
|
||
/datum/admin_help/New(msg, client/C, is_bwoink)
|
||
//clean the input msg
|
||
msg = sanitize(copytext(msg,1,MAX_MESSAGE_LEN))
|
||
if(!msg || !C || !C.mob)
|
||
qdel(src)
|
||
return
|
||
|
||
id = ++ticket_counter
|
||
opened_at = world.time
|
||
|
||
name = msg
|
||
|
||
initiator = C
|
||
initiator_ckey = initiator.ckey
|
||
initiator_key_name = key_name(initiator, FALSE, TRUE)
|
||
if(initiator.current_ticket) //This is a bug
|
||
stack_trace("Multiple ahelp current_tickets")
|
||
initiator.current_ticket.AddInteraction("Ticket erroneously left open by code")
|
||
initiator.current_ticket.Close()
|
||
initiator.current_ticket = src
|
||
|
||
var/datum/DBQuery/add_ticket_query = SSdbcore.NewQuery(
|
||
"INSERT INTO `[format_table_name("admin_tickets")]` (round_id, ticket_id, ckey) VALUES (:round, :ticket, :ckey);",
|
||
list("round" = GLOB.round_id, "ticket" = id, "ckey" = initiator_ckey)
|
||
)
|
||
|
||
if(!add_ticket_query.Execute())
|
||
message_admins("Failed insert ticket into ticket DB. Check the SQL error logs for more details.")
|
||
else
|
||
db_id = add_ticket_query.last_insert_id
|
||
qdel(add_ticket_query)
|
||
|
||
TimeoutVerb()
|
||
|
||
_interactions = list()
|
||
|
||
if(is_bwoink)
|
||
AddInteraction("[usr.client?.ckey] PM'd [initiator_key_name]")
|
||
message_admins("<font color='blue'>Ticket [TicketHref("#[id]")] created</font>")
|
||
else
|
||
MessageNoRecipient(msg)
|
||
|
||
//send it to irc if nobody is on and tell us how many were on
|
||
var/admin_number_present = check_admins_online()
|
||
log_admin_private("Ticket #[id]: [key_name(initiator)]: [name] - heard by [admin_number_present] non-AFK admins who have +BAN.")
|
||
if(admin_number_present <= 0)
|
||
to_chat(C, span_notice("No active admins are online, your adminhelp was sent to the admin irc."), confidential=TRUE)
|
||
heard_by_no_admins = TRUE
|
||
|
||
GLOB.ahelp_tickets.tickets_list += src
|
||
GLOB.ahelp_tickets.ticketAmount += 1
|
||
|
||
/datum/admin_help/proc/check_admins_online()
|
||
var/list/adm = get_admin_counts(R_BAN)
|
||
var/list/activemins = adm["present"]
|
||
. = activemins.len
|
||
if(. > 0)
|
||
return
|
||
send2irc_adminless_only(initiator_ckey, "Ticket #[id]: [name]")
|
||
var/list/stealthmins = adm["stealth"]
|
||
if(stealthmins.len > 0) // If there are stealthmins, do nothing
|
||
return
|
||
// There are no admins online, try deadmins
|
||
var/found_deadmin = FALSE
|
||
if(GLOB.permissions.deadmins.len > 0)
|
||
for(var/deadmin_ckey in GLOB.permissions.deadmins)
|
||
var/datum/admins/A = GLOB.permissions.deadmins[deadmin_ckey]
|
||
if(!check_rights_for(A.owner, R_BAN))
|
||
continue
|
||
var/client/client = GLOB.directory[deadmin_ckey]
|
||
if(!client)
|
||
continue
|
||
if(client.prefs.toggles & SOUND_ADMINHELP)
|
||
SEND_SOUND(client, sound('sound/effects/adminhelp.ogg'))
|
||
to_chat(client, span_danger("Ticket opened with no active admins. Ticket will be sent to discord in 30 seconds if not taken."), confidential=TRUE)
|
||
if(!found_deadmin)
|
||
found_deadmin = TRUE
|
||
addtimer(CALLBACK(src, .proc/send_to_discord), 30 SECONDS)
|
||
if(!found_deadmin)
|
||
send_to_discord()
|
||
|
||
/datum/admin_help/proc/send_to_discord()
|
||
if(state == AHELP_ACTIVE && !handling_admin)
|
||
webhook_send_ticket_unclaimed(initiator_ckey, name, id)
|
||
|
||
/datum/admin_help/Destroy()
|
||
GLOB.ahelp_tickets.tickets_list -= src
|
||
return ..()
|
||
|
||
/datum/admin_help/proc/check_owner() // Handles unclaimed tickets; returns TRUE if no longer unclaimed
|
||
if(!handling_admin && state == AHELP_ACTIVE)
|
||
var/msg = span_admin("<span class=\"prefix\">ADMIN LOG:</span> <span class=\"message linkify\"><font color='blue'>Ticket [TicketHref("#[id]")] Unclaimed!</font></span>")
|
||
for(var/client/X in GLOB.permissions.admins)
|
||
if(check_rights_for(X,R_BAN))
|
||
to_chat(X,
|
||
type = MESSAGE_TYPE_ADMINLOG,
|
||
html = msg,
|
||
confidential = TRUE)
|
||
else
|
||
if(world.time > last_unclaimed_notification)
|
||
last_unclaimed_notification = world.time + 1 SECONDS
|
||
msg = "<span class=\"prefix\">ADMIN LOG:</span> <span class=\"message linkify\"><font color='blue'>Unclaimed Tickets!</font></span>"
|
||
to_chat(X,
|
||
type = MESSAGE_TYPE_ADMINLOG,
|
||
html = msg,
|
||
confidential = TRUE)
|
||
|
||
if(world.time > last_bwoinking)
|
||
last_bwoinking = world.time + 1 SECONDS
|
||
for(var/client/X in GLOB.permissions.admins)
|
||
if(check_rights_for(X,R_BAN) && (X.prefs.toggles & SOUND_ADMINHELP)) // Can't use check_rights here since it's dependent on $usr
|
||
SEND_SOUND(X, sound('sound/effects/adminhelp.ogg'))
|
||
return FALSE
|
||
return TRUE
|
||
|
||
/datum/admin_help/proc/AddInteraction(msg, for_admins = FALSE, ckey = null)
|
||
_interactions += new /datum/ticket_log(src, usr, msg, for_admins)
|
||
webhook_send("ticket", list("ticketid" = id, "message" = strip_html_simple(msg), "roundid" = GLOB.round_id, "user" = ckey ? ckey : usr.client?.ckey))
|
||
|
||
//Removes the ahelp verb and returns it after 2 minutes
|
||
/datum/admin_help/proc/TimeoutVerb()
|
||
remove_verb(initiator, /client/verb/adminhelp)
|
||
initiator.adminhelptimerid = addtimer(CALLBACK(initiator, /client/proc/giveadminhelpverb), 1200, TIMER_STOPPABLE) //2 minute cooldown of admin helps
|
||
|
||
//private
|
||
/datum/admin_help/proc/FullMonty(ref_src)
|
||
if(!ref_src)
|
||
ref_src = "[REF(src)]"
|
||
. = ADMIN_FULLMONTY_NONAME(initiator.mob)
|
||
if(state == AHELP_ACTIVE)
|
||
. += ClosureLinks(ref_src)
|
||
|
||
//private
|
||
/datum/admin_help/proc/ClosureLinks(ref_src)
|
||
if(!ref_src)
|
||
ref_src = "[REF(src)]"
|
||
. = " (<A HREF='?_src_=holder;[HrefToken(TRUE)];ahelp=[ref_src];ahelp_action=reject'>REJT</A>)"
|
||
. += " (<A HREF='?_src_=holder;[HrefToken(TRUE)];ahelp=[ref_src];ahelp_action=icissue'>IC</A>)"
|
||
. += " (<A HREF='?_src_=holder;[HrefToken(TRUE)];ahelp=[ref_src];ahelp_action=resolve'>RSLVE</A>)"
|
||
. += " (<A HREF='?_src_=holder;[HrefToken(TRUE)];ahelp=[ref_src];ahelp_action=wiki'>WIKI</A>)"
|
||
. += " (<A HREF='?_src_=holder;[HrefToken(TRUE)];ahelp=[ref_src];ahelp_action=bug'>BUG</A>)"
|
||
. += " (<A HREF='?_src_=holder;[HrefToken(TRUE)];ahelp=[REF(src)];ahelp_action=mhelpquestion'>MHELP</a>)"
|
||
|
||
//private
|
||
/datum/admin_help/proc/LinkedReplyName(ref_src)
|
||
if(!ref_src)
|
||
ref_src = "[REF(src)]"
|
||
return "<A HREF='?_src_=holder;[HrefToken(TRUE)];ahelp=[ref_src];ahelp_action=reply'>[initiator_key_name]</A>"
|
||
|
||
//private
|
||
/datum/admin_help/proc/TicketHref(msg, ref_src, action = "ticket")
|
||
if(!ref_src)
|
||
ref_src = "[REF(src)]"
|
||
return "<A HREF='?_src_=holder;[HrefToken(TRUE)];ahelp=[ref_src];ahelp_action=[action]'>[msg]</A>"
|
||
|
||
//message from the initiator without a target, all admins will see this
|
||
//won't bug irc
|
||
/datum/admin_help/proc/MessageNoRecipient(msg)
|
||
var/ref_src = "[REF(src)]"
|
||
//Message to be sent to all admins
|
||
var/admin_msg = span_adminnotice("[span_adminhelp("Ticket [TicketHref("#[id]", ref_src)]")]<b>: [LinkedReplyName(ref_src)] [FullMonty(ref_src)]:</b> [keywords_lookup(msg)]")
|
||
|
||
AddInteraction(msg)
|
||
|
||
//send this msg to all admins
|
||
for(var/client/X in GLOB.permissions.admins)
|
||
if(X.prefs.toggles & SOUND_ADMINHELP)
|
||
SEND_SOUND(X, sound('sound/effects/adminhelp.ogg'))
|
||
window_flash(X, ignorepref = TRUE)
|
||
to_chat(X, admin_msg, confidential=TRUE)
|
||
|
||
//show it to the person adminhelping too
|
||
to_chat(initiator, span_adminnotice("PM to-<b>Admins</b>: [msg]"), confidential=TRUE)
|
||
GLOB.unclaimed_tickets += src
|
||
|
||
//Reopen a closed ticket
|
||
/datum/admin_help/proc/Reopen()
|
||
if(state == AHELP_ACTIVE)
|
||
to_chat(usr, span_warning("This ticket is already open."), confidential=TRUE)
|
||
return
|
||
|
||
if(GLOB.ahelp_tickets.CKey2ActiveTicket(initiator_ckey))
|
||
to_chat(usr, span_warning("This user already has an active ticket, cannot reopen this one."), confidential=TRUE)
|
||
return
|
||
|
||
switch(state)
|
||
if(AHELP_CLOSED)
|
||
SSblackbox.record_feedback("tally", "ahelp_stats", -1, "closed")
|
||
if(AHELP_RESOLVED)
|
||
SSblackbox.record_feedback("tally", "ahelp_stats", -1, "resolved")
|
||
|
||
AddActive()
|
||
state = AHELP_ACTIVE
|
||
|
||
AddInteraction("Reopened by [usr.ckey]")
|
||
var/msg = span_adminhelp("Ticket [TicketHref("#[id]")] reopened by [key_name_admin(usr)].")
|
||
message_admins(msg)
|
||
log_admin_private(msg)
|
||
SSblackbox.record_feedback("tally", "ahelp_stats", 1, "reopened")
|
||
TicketPanel() //can only be done from here, so refresh it
|
||
|
||
//private
|
||
/datum/admin_help/proc/RemoveActive()
|
||
if(state != AHELP_ACTIVE)
|
||
return
|
||
|
||
closed_at = world.time
|
||
if(initiator && initiator.current_ticket == src)
|
||
initiator.current_ticket = null
|
||
|
||
/datum/admin_help/proc/AddActive()
|
||
if(state == AHELP_ACTIVE)
|
||
return
|
||
|
||
closed_at = null
|
||
if(initiator && !initiator.current_ticket)
|
||
initiator.current_ticket = src
|
||
|
||
//Mark open ticket as closed/meme
|
||
/datum/admin_help/proc/Close(key_name = key_name_admin(usr), silent = FALSE)
|
||
if(state != AHELP_ACTIVE)
|
||
return
|
||
|
||
RemoveActive()
|
||
state = AHELP_CLOSED
|
||
AddInteraction("Closed by [usr.ckey].")
|
||
if(!silent)
|
||
SSblackbox.record_feedback("tally", "ahelp_stats", 1, "closed")
|
||
var/msg = "Ticket [TicketHref("#[id]")] closed by [key_name]."
|
||
message_admins(msg)
|
||
log_admin_private(msg)
|
||
|
||
GLOB.ahelp_tickets.ticketAmount -= 1
|
||
if(SSticker.current_state == GAME_STATE_FINISHED && !GLOB.ahelp_tickets.ticketAmount)
|
||
if(check_rights(R_ADMIN, FALSE) && alert(usr,"Restart the round?.","Round restart","Yes","No") == "Yes")
|
||
SSticker.Reboot(delay = 100, force = TRUE)
|
||
else
|
||
message_admins("All tickets have been closed, round can be restarted")
|
||
|
||
//Mark open ticket as resolved/legitimate, returns ahelp verb
|
||
/datum/admin_help/proc/Resolve(key_name = key_name_admin(usr), silent = FALSE)
|
||
var/resolved = FALSE
|
||
if(state == AHELP_RESOLVED)
|
||
if(initiator.current_ticket)
|
||
to_chat(initiator, span_warning("This user already has an open ticket."), confidential=TRUE)
|
||
return
|
||
|
||
AddActive()
|
||
state = AHELP_ACTIVE
|
||
GLOB.ahelp_tickets.ticketAmount += 1
|
||
else if(state == AHELP_ACTIVE)
|
||
RemoveActive()
|
||
state = AHELP_RESOLVED
|
||
resolved = TRUE
|
||
GLOB.ahelp_tickets.ticketAmount -= 1
|
||
else // AHELP_CLOSED
|
||
to_chat(usr, span_warning("This ticket has been closed and can't be unresolved."), confidential=TRUE)
|
||
return
|
||
|
||
if(resolved)
|
||
AddInteraction("Ticket #[id] marked as resolved by [usr.ckey].")
|
||
to_chat(initiator, span_adminhelp("Your ticket has been marked as resolved by [usr.client.holder?.fakekey ? "an Administrator" : key_name(usr, 0, 0)]."), confidential=TRUE)
|
||
addtimer(CALLBACK(initiator, /client/proc/giveadminhelpverb), 50)
|
||
else // AHELP_ACTIVE
|
||
AddInteraction("Ticket #[id] marked as unresolved by [usr.ckey].")
|
||
to_chat(initiator, span_adminhelp("Your ticket has been marked as unresolved by [usr.client.holder?.fakekey ? "an Administrator" : key_name(usr, 0, 0)]."), confidential=TRUE)
|
||
TimeoutVerb()
|
||
|
||
if(!silent)
|
||
if(resolved)
|
||
SSblackbox.record_feedback("tally", "ahelp_stats", 1, "resolved")
|
||
|
||
var/msg = "Ticket [TicketHref("#[id]")] [resolved ? "" : "un"]resolved by [key_name]"
|
||
message_admins(msg)
|
||
log_admin_private(msg)
|
||
|
||
if(SSticker.current_state == GAME_STATE_FINISHED && !GLOB.ahelp_tickets.ticketAmount)
|
||
if(check_rights(R_ADMIN, FALSE) && alert(usr,"Restart the round?.","Round restart","Yes","No") == "Yes")
|
||
SSticker.Reboot(delay = 100)
|
||
else
|
||
message_admins("All tickets have been closed, round can be restarted")
|
||
|
||
//Close and return ahelp verb, use if ticket is incoherent
|
||
/datum/admin_help/proc/Reject(key_name = key_name_admin(usr))
|
||
if(state != AHELP_ACTIVE)
|
||
return
|
||
|
||
if(initiator)
|
||
initiator.giveadminhelpverb()
|
||
|
||
SEND_SOUND(initiator, sound('sound/effects/adminhelp.ogg'))
|
||
|
||
to_chat(initiator, "<font color='red' size='4'><b>- AdminHelp Rejected by [usr.client.holder?.fakekey ? "an Administrator" : key_name(usr, 0, 0)]! -</b></font>", confidential=TRUE)
|
||
to_chat(initiator, "<font color='red'><b>Your admin help was rejected.</b> The adminhelp verb has been returned to you so that you may try again.</font>", confidential=TRUE)
|
||
to_chat(initiator, "Please try to be calm, clear, and descriptive in admin helps, do not assume the admin has seen any related events, and clearly state the names of anybody you are reporting.", confidential=TRUE)
|
||
|
||
SSblackbox.record_feedback("tally", "ahelp_stats", 1, "rejected")
|
||
var/msg = "Ticket [TicketHref("#[id]")] rejected by [key_name]"
|
||
message_admins(msg)
|
||
log_admin_private(msg)
|
||
AddInteraction("Rejected by [usr.ckey].")
|
||
Close(silent = TRUE)
|
||
|
||
//Resolve ticket with IC Issue message
|
||
/datum/admin_help/proc/ICIssue(key_name = key_name_admin(usr))
|
||
if(state != AHELP_ACTIVE)
|
||
return
|
||
|
||
var/msg = "<font color='red' size='4'><b>- AdminHelp marked as an IC issue by [usr.client.holder?.fakekey ? "an Administrator" : key_name(usr, 0, 0)]! -</b></font><br>"
|
||
msg += "<font color='red'>Unfortunately your issue does not warrant admin intervention, usually because none of the involved parties are breaking any OOC rules. You can however still attempt to find an IC solution to your problem.</font>"
|
||
|
||
if(initiator)
|
||
to_chat(initiator, msg, confidential=TRUE)
|
||
|
||
SSblackbox.record_feedback("tally", "ahelp_stats", 1, "IC")
|
||
msg = "Ticket [TicketHref("#[id]")] marked as IC by [key_name]"
|
||
message_admins(msg)
|
||
log_admin_private(msg)
|
||
AddInteraction("Marked as an IC issue by [usr.ckey]")
|
||
Resolve(silent = TRUE)
|
||
|
||
//Resolve ticket with Mhelp Question message
|
||
/datum/admin_help/proc/MhelpQuestion(key_name = key_name_admin(usr))
|
||
if(state != AHELP_ACTIVE)
|
||
return
|
||
|
||
var/msg = "<font color='red' size='4'><b>- AdminHelp marked as a Mentorhelp Question by [usr.client.holder?.fakekey ? "an Administrator" : key_name(usr, 0, 0)]! -</b></font><br>"
|
||
msg += "<font color='red'><b>You are asking a mentorhelp question!</b></font><br>"
|
||
msg += "<font color='red'>Please use the mentorhelp button to ask questions about game mechanics and other such questions.</font>"
|
||
msg += "<font color='red'>Your call will now be redirected to the mentors as a mentorhelp.</font>"
|
||
|
||
if(initiator)
|
||
to_chat(initiator, msg, confidential=TRUE)
|
||
initiator.mhelp(name, TRUE)
|
||
|
||
SSblackbox.record_feedback("tally", "ahelp_stats", 1, "MHelp")
|
||
msg = "Ticket [TicketHref("#[id]")] marked as MHelp by [key_name]"
|
||
message_admins(msg)
|
||
log_admin_private(msg)
|
||
AddInteraction("Marked as an MHelp question by [usr.ckey]")
|
||
Resolve(silent = TRUE)
|
||
|
||
//Resolve ticket with wiki message
|
||
/datum/admin_help/proc/WikiIssue(key_name = key_name_admin(usr))
|
||
if(state != AHELP_ACTIVE)
|
||
return
|
||
|
||
var/msg = "<font color='red' size='4'><b>- AdminHelp marked as a Wiki issue by [usr.client.holder?.fakekey ? "an Administrator" : key_name(usr, 0, 0)]! -</b></font><br>"
|
||
msg += "<font color='red'><b>Go look at the wiki!</b></font><br>"
|
||
msg += "<font color='red'>[CONFIG_GET(string/wikiurl)]</font>"
|
||
if(initiator)
|
||
to_chat(initiator, msg, confidential=TRUE)
|
||
|
||
SSblackbox.record_feedback("tally", "ahelp_stats", 1, "WIKI")
|
||
msg = "Ticket [TicketHref("#[id]")] marked as WIKI by [key_name]"
|
||
message_admins(msg)
|
||
log_admin_private(msg)
|
||
AddInteraction("Marked as WIKI issue by [usr.ckey]")
|
||
Resolve(silent = TRUE)
|
||
|
||
//Resolve ticket with bug message
|
||
/datum/admin_help/proc/GithubIssue(key_name = key_name_admin(usr))
|
||
if(state != AHELP_ACTIVE)
|
||
return
|
||
|
||
var/msg = "<font color='red' size='4'><b>- AdminHelp marked as a Github issue by [usr.client.holder?.fakekey ? "an Administrator" : key_name(usr, 0, 0)]! -</b></font><br>"
|
||
msg += "<font color='red'><b>You are reporting a Bug or Github Issue.</b></font><br>"
|
||
msg += "<font color='red'>[CONFIG_GET(string/githuburl)]/issues/new?template=bug_report.md</font>"
|
||
msg += "<font color='red'><b>Please fill out the issues form with detailed information about the bug or other issue you have discovered.</b></font><br>"
|
||
|
||
if(initiator)
|
||
to_chat(initiator, msg, confidential=TRUE)
|
||
|
||
SSblackbox.record_feedback("tally", "ahelp_stats", 1, "BUG")
|
||
msg = "Ticket [TicketHref("#[id]")] marked as BUG by [key_name]"
|
||
message_admins(msg)
|
||
log_admin_private(msg)
|
||
AddInteraction("Marked as BUG issue by [usr.ckey]")
|
||
Resolve(silent = TRUE)
|
||
|
||
//Show the ticket panel
|
||
/datum/admin_help/proc/TicketPanel()
|
||
if(GLOB.experimental_adminpanel)
|
||
ui_interact(usr)
|
||
return
|
||
|
||
var/reply_link = "<a href='?_src_=holder;[HrefToken(TRUE)];user=[REF(usr)];ahelp=[REF(src)];ahelp_action=reply'><img border='0' width='16' height='16' class='uiIcon16 icon-comment' /> Reply</a>"
|
||
var/refresh_link = "<a href='?_src_=holder;[HrefToken(TRUE)];user=[REF(usr)];ahelp=[REF(src)];ahelp_action=ticket'><img border='0' width='16' height='16' class='uiIcon16 icon-refresh' /> Refresh</a>"
|
||
|
||
var/content = ""
|
||
if(usr.client.holder)
|
||
content += "<p class='control-bar'>[reply_link] [refresh_link]</p>"
|
||
|
||
content += "<p class='title-bar'>[name]</p>"
|
||
content += "<p class='info-bar'>Primary Admin: <span id='primary-admin'>[handling_admin != null ? (usr.client.holder ? key_name(handling_admin, TRUE, TRUE) : "[key_name(handling_admin, TRUE, FALSE)]") : "Unassigned"]</span></p>"
|
||
|
||
content += "<p class='resolved-bar [state == AHELP_ACTIVE ? "unresolved" : "resolved"]' id='resolved'>[state == AHELP_ACTIVE ? "Is not resolved" : "Is resolved"]</p>"
|
||
|
||
if(usr.client.holder && initiator)
|
||
content += {"<div class='user-bar'>
|
||
<p>[key_name(initiator, 1)]</p>"}
|
||
|
||
if(initiator && initiator.mob)
|
||
content += {"<p style='margin-top: 5px;'>
|
||
<a href='?_src_=holder;[HrefToken(TRUE)];adminmoreinfo=[REF(initiator.mob)]'><img border='0' width='16' height='16' class='uiIcon16 icon-search' /> ?</a>
|
||
<a href='?_src_=holder;[HrefToken(TRUE)];adminplayeropts=[REF(initiator.mob)]'><img border='0' width='16' height='16' class='uiIcon16 icon-clipboard' /> PP</a>
|
||
<a href='?_src_=vars;[HrefToken(TRUE)];Vars=[REF(initiator.mob)]'><img border='0' width='16' height='16' class='uiIcon16 icon-clipboard' /> VV</a>
|
||
<a href='?_src_=holder;[HrefToken(TRUE)];subtlemessage=[REF(initiator.mob)]'><img border='0' width='16' height='16' class='uiIcon16 icon-mail-closed' /> SM</a>
|
||
<a href='?_src_=holder;[HrefToken(TRUE)];adminplayerobservefollow=[REF(initiator.mob)]'><img border='0' width='16' height='16' class='uiIcon16 icon-arrowthick-1-e' /> FLW</a>
|
||
<a href='?_src_=holder;[HrefToken(TRUE)];secretsadmin=check_antagonist'><img border='0' width='16' height='16' class='uiIcon16 icon-clipboard' /> CA</a>
|
||
<a href='?_src_=holder;[HrefToken(TRUE)];ahelp=[REF(src)];ahelp_action=administer' class='admin-button'><img border='0' width='16' height='16' class='uiIcon16 icon-flag' /> <span>Administer</span></a>
|
||
<a href='?_src_=holder;[HrefToken(TRUE)];ahelp=[REF(src)];ahelp_action=resolve' class='resolve-button'><img border='0' width='16' height='16' class='uiIcon16 icon-check' /> <span>[state == AHELP_ACTIVE ? "" : "Un"]Resolve</span></a>
|
||
<a href='?_src_=holder;[HrefToken(TRUE)];ahelp=[REF(src)];ahelp_action=reject' class='resolve-button'><img border='0' width='16' height='16' class='uiIcon16 icon-check' /> <span>Reject</span></a>
|
||
<a href='?_src_=holder;[HrefToken(TRUE)];ahelp=[REF(src)];ahelp_action=close' class='resolve-button'><img border='0' width='16' height='16' class='uiIcon16 icon-check' /> <span>Close</span></a>
|
||
<a href='?_src_=holder;[HrefToken(TRUE)];ahelp=[REF(src)];ahelp_action=icissue' class='resolve-button'><img border='0' width='16' height='16' class='uiIcon16 icon-check' /> <span>IC</span></a>
|
||
<a href='?_src_=holder;[HrefToken(TRUE)];ahelp=[REF(src)];ahelp_action=mhelpquestion' class='resolve-button'><img border='0' width='16' height='16' class='uiIcon16 icon-check' /> <span>MHelp</span></a>
|
||
<a href='?_src_=holder;[HrefToken(TRUE)];ahelp=[REF(src)];ahelp_action=popup' class='resolve-button'><img border='0' width='16' height='16' class='uiIcon16 icon-check' /> <span>[popups_enabled ? "De" : ""]Activate Popups</span></a>
|
||
</p>"}
|
||
if(initiator && initiator.mob)
|
||
if(initiator.mob.mind && initiator.mob.mind.assigned_role)
|
||
content += "<p class='user-info-bar'>Role: [initiator.mob.mind.assigned_role]</p>"
|
||
if(initiator.mob.mind.special_role)
|
||
content += "<p class='user-info-bar'>Antagonist: [initiator.mob.mind.special_role]</p>"
|
||
else
|
||
content += "<p class='user-info-bar'>Antagonist: No</p>"
|
||
|
||
var/turf/T = get_turf(initiator.mob)
|
||
|
||
var/location = ""
|
||
if(isturf(T))
|
||
if(isarea(T.loc))
|
||
location = "([initiator.mob.loc == T ? "at " : "in [initiator.mob.loc] at "] [T.x], [T.y], [T.z] in area <b>[T.loc]</b>)"
|
||
else
|
||
location = "([initiator.mob.loc == T ? "at " : "in [initiator.mob.loc] at "] [T.x], [T.y], [T.z])"
|
||
|
||
if(location)
|
||
content += "<p class='user-info-bar'>Location: [location]</p>"
|
||
|
||
content += "</div>"
|
||
else
|
||
if(usr.client.holder)
|
||
content += "<div class='user-bar'>"
|
||
content += {"<p style='margin-top: 5px;'>
|
||
<a href='?_src_=holder;[HrefToken(TRUE)];ahelp=[REF(src)];ahelp_action=resolve' class='resolve-button'><img border='0' width='16' height='16' class='uiIcon16 icon-check' /> <span>[state == AHELP_ACTIVE ? "" : "Un"]Resolve</span></a>
|
||
<a href='?_src_=holder;[HrefToken(TRUE)];ahelp=[REF(src)];ahelp_action=administer' class='admin-button'><img border='0' width='16' height='16' class='uiIcon16 icon-flag' /> <span>Administer</span></a>
|
||
</p>"}
|
||
content += "</div>"
|
||
|
||
content += "<div id='messages'>"
|
||
|
||
var/i = 0
|
||
for(i = 1; i <= _interactions.len; i++)
|
||
var/datum/ticket_log/item = _interactions[i]
|
||
if(!usr.client.holder)
|
||
if(!item.for_admins)
|
||
content += "<p class='message-bar'>[item.toString()]</p>"
|
||
else
|
||
content += "<p class='message-bar'>[item.toAdminString()]</p>"
|
||
|
||
// New ticket logs added to top - If reverting this, do not forget to prepend in the template!
|
||
/*for(i = log.len; i > 0; i--)
|
||
var/datum/ticket_log/item = log[i]
|
||
if((item.for_admins && usr.client.holder) || !item.for_admins)
|
||
content += "<p class='message-bar'>[item.toString()]</p>"*/
|
||
|
||
content += "</div>"
|
||
|
||
if(usr.client.holder)
|
||
content += "<p class='control-bar'>[reply_link] [refresh_link]</p>"
|
||
|
||
content += "<br /></div></body></html>"
|
||
|
||
var/html = get_html("Admin Ticket Interface", "", "", content)
|
||
|
||
usr << browse(null, "window=ViewTicketLog[id]")
|
||
usr << browse(html, "window=ViewTicketLog[id]")
|
||
|
||
// Admin claims a ticket
|
||
/datum/admin_help/proc/Administer(announce = FALSE)
|
||
if(!usr.client)
|
||
return FALSE
|
||
handling_admin = usr.client
|
||
|
||
var/datum/DBQuery/set_admin_query = SSdbcore.NewQuery(
|
||
"UPDATE `[format_table_name("admin_tickets")]` SET `a_ckey` = :ckey WHERE `id` = :id;",
|
||
list("ckey" = usr.ckey, "id" = db_id)
|
||
)
|
||
set_admin_query.Execute();
|
||
qdel(set_admin_query);
|
||
|
||
var/msg = "[usr.ckey]/([usr]) has been assigned to [TicketHref("ticket #[id]")] as primary admin."
|
||
message_admins(msg)
|
||
log_admin_private(msg)
|
||
|
||
if(announce && initiator)
|
||
to_chat(initiator,
|
||
type = MESSAGE_TYPE_ADMINPM,
|
||
html = span_notice("[key_name(usr, TRUE, FALSE)] has taken your ticket and will respond shortly."),
|
||
confidential = TRUE)
|
||
|
||
//Admin activates the pop-ups
|
||
/datum/admin_help/proc/PopUps(key_name = key_name_admin(usr))
|
||
popups_enabled = !popups_enabled
|
||
var/msg = "Ticket [TicketHref("#[id]")] has had pop-ups [popups_enabled ? "" : "de"]activated by [key_name]"
|
||
message_admins(msg)
|
||
log_admin_private(msg)
|
||
|
||
//Forwarded action from admin/Topic
|
||
/datum/admin_help/proc/Action(action)
|
||
testing("Ahelp action: [action]")
|
||
switch(action)
|
||
if("ticket")
|
||
TicketPanel()
|
||
if("reject")
|
||
Reject()
|
||
if("reply")
|
||
usr.client.cmd_ahelp_reply(initiator)
|
||
if("icissue")
|
||
ICIssue()
|
||
if("mhelpquestion")
|
||
MhelpQuestion()
|
||
if("close")
|
||
Close()
|
||
if("resolve")
|
||
Resolve()
|
||
if("reopen")
|
||
Reopen()
|
||
if("administer")
|
||
Administer(TRUE)
|
||
if("wiki")
|
||
WikiIssue()
|
||
if("bug")
|
||
GithubIssue()
|
||
if("popup")
|
||
PopUps()
|
||
|
||
//
|
||
// CLIENT PROCS
|
||
//
|
||
|
||
/client/proc/giveadminhelpverb()
|
||
//client may of have disconnected by the time this proc gets called
|
||
if(!src)
|
||
return
|
||
|
||
if(!locate(/client/verb/adminhelp) in src.verbs)
|
||
add_verb(src, /client/verb/adminhelp)
|
||
|
||
|
||
deltimer(adminhelptimerid)
|
||
adminhelptimerid = 0
|
||
|
||
/client/proc/view_tickets()
|
||
set name = "Adminlisttickets"
|
||
set category = "Admin"
|
||
|
||
if(GLOB.experimental_adminpanel)
|
||
GLOB.ahelp_tickets.ui_interact(usr)
|
||
else
|
||
view_tickets_main(TICKET_FLAG_LIST_ALL)
|
||
|
||
/client/proc/view_tickets_main(var/flag)
|
||
flag = text2num(flag)
|
||
if(!flag)
|
||
flag = TICKET_FLAG_LIST_ALL
|
||
|
||
var/content = ""
|
||
var/list/tickets_list = GLOB.ahelp_tickets.tickets_list
|
||
|
||
if(holder)
|
||
content += {"<p class='info-bar'>
|
||
<a href='?_src_=[REF(src)];[HrefToken()];action=refresh_admin_ticket_list;flag=[flag]'>Refresh List</a>
|
||
<a href='?_src_=[REF(src)];[HrefToken(TRUE)];action=refresh_admin_ticket_list;flag=[(flag | TICKET_FLAG_LIST_ALL) & ~TICKET_FLAG_LIST_MINE & ~TICKET_FLAG_LIST_UNCLAIMED]'>All Tickets</a>
|
||
|
||
<a href='?_src_=[REF(src)];[HrefToken(TRUE)];action=refresh_admin_ticket_list;flag=
|
||
[flag & TICKET_FLAG_LIST_MINE ? "[(flag & ~TICKET_FLAG_LIST_MINE) & ~TICKET_FLAG_LIST_ALL]" : "[(flag | TICKET_FLAG_LIST_MINE) & ~TICKET_FLAG_LIST_ALL]"]
|
||
'>[flag & TICKET_FLAG_LIST_MINE ? "<22> " : ""]My Tickets</a>
|
||
|
||
<a href='?_src_=[REF(src)];[HrefToken(TRUE)];action=refresh_admin_ticket_list;flag=
|
||
[flag & TICKET_FLAG_LIST_UNCLAIMED ? "[(flag & ~TICKET_FLAG_LIST_UNCLAIMED) & ~TICKET_FLAG_LIST_ALL]" : "[(flag | TICKET_FLAG_LIST_UNCLAIMED) & ~TICKET_FLAG_LIST_ALL]"]
|
||
'>[flag & TICKET_FLAG_LIST_UNCLAIMED ? "<22> " : ""]Unclaimed</a>
|
||
|
||
</p>"}
|
||
|
||
content += {"<p class='info-bar'>
|
||
Filtering:<b>
|
||
[(flag & TICKET_FLAG_LIST_ALL) ? " All" : ""]
|
||
[(flag & TICKET_FLAG_LIST_MINE) ? " Mine" : ""]
|
||
[(flag & TICKET_FLAG_LIST_UNCLAIMED) ? " Unclaimed" : ""]
|
||
</b></p>"}
|
||
|
||
|
||
var/list/resolved = new /list()
|
||
var/list/unresolved = new /list()
|
||
|
||
for(var/i = tickets_list.len, i >= 1, i--)
|
||
var/datum/admin_help/T = tickets_list[i]
|
||
|
||
var/include = FALSE
|
||
|
||
if(!(flag & TICKET_FLAG_LIST_ALL))
|
||
if(flag & TICKET_FLAG_LIST_MINE)
|
||
if(!compare_ckey(src, T.initiator_ckey) && !compare_ckey(src, T.handling_admin))
|
||
include = FALSE
|
||
else
|
||
include = TRUE
|
||
|
||
if(!include && flag & TICKET_FLAG_LIST_UNCLAIMED)
|
||
if(T.handling_admin)
|
||
include = FALSE
|
||
else
|
||
include = TRUE
|
||
else
|
||
include = TRUE
|
||
|
||
if(!include)
|
||
continue
|
||
|
||
if(T.state == AHELP_ACTIVE)
|
||
unresolved.Add(T)
|
||
else
|
||
resolved.Add(T)
|
||
|
||
if(!unresolved.len && !resolved.len)
|
||
content += "<p class='info-bar emboldened'>There are no tickets matching your filter(s)</p>"
|
||
|
||
if(unresolved.len > 0)
|
||
content += "<p class='info-bar unresolved emboldened large-font'>Unresolved Tickets ([unresolved.len]/[tickets_list.len]):</p>"
|
||
for(var/I in unresolved)
|
||
var/datum/admin_help/T = I
|
||
if(!T.initiator)
|
||
content += {"<p class='ticket-bar'>
|
||
<span class='ticket-number'>#[T.id]</span>
|
||
<b>[T.handling_admin ? "" : "[span_unclaimed("Unclaimed")]!"] [T.name]</b><br />
|
||
<b>Owner:</b> <b>[T.initiator_ckey] (DC)</b>
|
||
[T.TicketHref("<img border='0' width='16' height='16' class='uiIcon16 icon-search' /> View")]
|
||
<a href='?_src_=holder;[HrefToken(TRUE)];ahelp=[REF(T)];ahelp_action=resolve' class='resolve-button'><img border='0' width='16' height='16' class='uiIcon16 icon-check' /> <span>[T.state == AHELP_ACTIVE ? "" : "Un"]Resolve</span></a>
|
||
<a href='?_src_=holder;[HrefToken(TRUE)];ahelp=[REF(T)];ahelp_action=reject' class='resolve-button'><img border='0' width='16' height='16' class='uiIcon16 icon-check' /> <span>Reject</span></a>
|
||
<a href='?_src_=holder;[HrefToken(TRUE)];ahelp=[REF(T)];ahelp_action=close' class='resolve-button'><img border='0' width='16' height='16' class='uiIcon16 icon-check' /> <span>Close</span></a>
|
||
<a href='?_src_=holder;[HrefToken(TRUE)];ahelp=[REF(T)];ahelp_action=icissue' class='resolve-button'><img border='0' width='16' height='16' class='uiIcon16 icon-check' /> <span>IC</span></a>
|
||
</p>"}
|
||
else
|
||
var/ai_found = (T.initiator && isAI(T.initiator_ckey))
|
||
content += {"<p class='ticket-bar'>
|
||
<span class='ticket-number'>#[T.id]</span>
|
||
<b>[T.handling_admin ? "" : span_unclaimed("Unclaimed")] [T.name]</b><br />
|
||
<b>Owner:</b> <b>[key_name(T.initiator, 1)]</b><br />
|
||
[T.handling_admin ? " <b>Admin:</b> [T.handling_admin]<br />" : ""]
|
||
[T.TicketHref("<img border='0' width='16' height='16' class='uiIcon16 icon-search' /> View")]
|
||
"}
|
||
if(T.initiator.mob)
|
||
content += {"
|
||
<a href='?_src_=holder;[HrefToken(TRUE)];adminmoreinfo=[REF(T.initiator.mob)]'><img border='0' width='16' height='16' class='uiIcon16 icon-search' /> ?</a>
|
||
<a href='?_src_=holder;[HrefToken(TRUE)];adminplayeropts=[REF(T.initiator.mob)]'><img border='0' width='16' height='16' class='uiIcon16 icon-clipboard' /> PP</a>
|
||
<a href='?_src_=vars;[HrefToken(TRUE)];Vars=[REF(T.initiator.mob)]'><img border='0' width='16' height='16' class='uiIcon16 icon-clipboard' /> VV</a>
|
||
<a href='?_src_=holder;[HrefToken(TRUE)];subtlemessage=[REF(T.initiator.mob)]'><img border='0' width='16' height='16' class='uiIcon16 icon-mail-closed' /> SM</a>
|
||
<a href='?_src_=holder;[HrefToken(TRUE)];adminplayerobservefollow=[REF(T.initiator.mob)]'><img border='0' width='16' height='16' class='uiIcon16 icon-arrowthick-1-e' /> FLW</a>
|
||
<a href='?_src_=holder;[HrefToken(TRUE)];check_antagonist=1'><img border='0' width='16' height='16' class='uiIcon16 icon-clipboard' /> CA</a>
|
||
[ai_found ? " <a href='?_src_=holder;[HrefToken(TRUE)];adminchecklaws=[REF(T.initiator.mob)]'><img width='16' height='16' class='uiIcon16 icon-clipboard' /> CL</a>" : ""]
|
||
"}
|
||
content += {"
|
||
<a href='?_src_=holder;[HrefToken(TRUE)];ahelp=[REF(T)];ahelp_action=resolve' class='resolve-button'><img border='0' width='16' height='16' class='uiIcon16 icon-check' /> <span>[T.state == AHELP_ACTIVE ? "" : "Un"]Resolve</span></a>
|
||
<a href='?_src_=holder;[HrefToken(TRUE)];ahelp=[REF(T)];ahelp_action=reject' class='resolve-button'><img border='0' width='16' height='16' class='uiIcon16 icon-check' /> <span>Reject</span></a>
|
||
<a href='?_src_=holder;[HrefToken(TRUE)];ahelp=[REF(T)];ahelp_action=close' class='resolve-button'><img border='0' width='16' height='16' class='uiIcon16 icon-check' /> <span>Close</span></a>
|
||
<a href='?_src_=holder;[HrefToken(TRUE)];ahelp=[REF(T)];ahelp_action=icissue' class='resolve-button'><img border='0' width='16' height='16' class='uiIcon16 icon-check' /> <span>IC</span></a>
|
||
</p>"}
|
||
|
||
if(resolved.len > 0)
|
||
content += "<p class='info-bar resolved emboldened large-font'>Resolved Tickets ([resolved.len]/[tickets_list.len]):</p>"
|
||
for(var/I in resolved)
|
||
var/datum/admin_help/T = I
|
||
if(!T.initiator)
|
||
content += {"<p class='ticket-bar'>
|
||
<span class='ticket-number'>#[T.id]</span>
|
||
<b>[T.name]</b><br />
|
||
<b>Owner:</b> <b>[T.initiator_ckey] (DC)</b>
|
||
[T.TicketHref("<img border='0' width='16' height='16' class='uiIcon16 icon-search' /> View")]
|
||
<a href='?_src_=holder;[HrefToken(TRUE)];ahelp=[REF(T)];ahelp_action=resolve' class='resolve-button'><img border='0' width='16' height='16' class='uiIcon16 icon-check' /> <span>[T.state == AHELP_ACTIVE ? "" : "Un"]Resolve</span></a>
|
||
<a href='?_src_=holder;[HrefToken(TRUE)];ahelp=[REF(T)];ahelp_action=reject' class='resolve-button'><img border='0' width='16' height='16' class='uiIcon16 icon-check' /> <span>Reject</span></a>
|
||
<a href='?_src_=holder;[HrefToken(TRUE)];ahelp=[REF(T)];ahelp_action=close' class='resolve-button'><img border='0' width='16' height='16' class='uiIcon16 icon-check' /> <span>Close</span></a>
|
||
<a href='?_src_=holder;[HrefToken(TRUE)];ahelp=[REF(T)];ahelp_action=icissue' class='resolve-button'><img border='0' width='16' height='16' class='uiIcon16 icon-check' /> <span>IC</span></a>
|
||
</p>"}
|
||
else
|
||
var/ai_found = (T.initiator && isAI(T.initiator_ckey))
|
||
content += {"<p class='ticket-bar'>
|
||
<span class='ticket-number'>#[T.id]</span>
|
||
<b>[T.name]</b><br />
|
||
<b>Owner:</b> <b>[key_name(T.initiator, 1)]</b><br />
|
||
[T.handling_admin ? " <b>Admin:</b> [T.handling_admin]<br />" : ""]
|
||
[T.TicketHref("<img border='0' width='16' height='16' class='uiIcon16 icon-search' /> View")]
|
||
"}
|
||
if(T.initiator.mob)
|
||
content += {"
|
||
<a href='?_src_=holder;[HrefToken(TRUE)];adminmoreinfo=[REF(T.initiator.mob)]'><img border='0' width='16' height='16' class='uiIcon16 icon-search' /> ?</a>
|
||
<a href='?_src_=holder;[HrefToken(TRUE)];adminplayeropts=[REF(T.initiator.mob)]'><img border='0' width='16' height='16' class='uiIcon16 icon-clipboard' /> PP</a>
|
||
<a href='?_src_=vars;[HrefToken(TRUE)];Vars=[REF(T.initiator.mob)]'><img border='0' width='16' height='16' class='uiIcon16 icon-clipboard' /> VV</a>
|
||
<a href='?_src_=holder;[HrefToken(TRUE)];subtlemessage=[REF(T.initiator.mob)]'><img border='0' width='16' height='16' class='uiIcon16 icon-mail-closed' /> SM</a>
|
||
<a href='?_src_=holder;[HrefToken(TRUE)];adminplayerobservefollow=[REF(T.initiator.mob)]'><img border='0' width='16' height='16' class='uiIcon16 icon-arrowthick-1-e' /> FLW</a>
|
||
<a href='?_src_=holder;[HrefToken(TRUE)];check_antagonist=1'><img border='0' width='16' height='16' class='uiIcon16 icon-clipboard' /> CA</a>
|
||
[ai_found ? " <a href='?_src_=holder;[HrefToken(TRUE)];adminchecklaws=[REF(T.initiator.mob)]'><img width='16' height='16' class='uiIcon16 icon-clipboard' /> CL</a>" : ""]
|
||
"}
|
||
content += {"
|
||
<a href='?_src_=holder;[HrefToken(TRUE)];ahelp=[REF(T)];ahelp_action=resolve' class='resolve-button'><img border='0' width='16' height='16' class='uiIcon16 icon-check' /> <span>[T.state == AHELP_ACTIVE ? "" : "Un"]Resolve</span></a>
|
||
<a href='?_src_=holder;[HrefToken(TRUE)];ahelp=[REF(T)];ahelp_action=reject' class='resolve-button'><img border='0' width='16' height='16' class='uiIcon16 icon-check' /> <span>Reject</span></a>
|
||
<a href='?_src_=holder;[HrefToken(TRUE)];ahelp=[REF(T)];ahelp_action=close' class='resolve-button'><img border='0' width='16' height='16' class='uiIcon16 icon-check' /> <span>Close</span></a>
|
||
<a href='?_src_=holder;[HrefToken(TRUE)];ahelp=[REF(T)];ahelp_action=icissue' class='resolve-button'><img border='0' width='16' height='16' class='uiIcon16 icon-check' /> <span>IC</span></a>
|
||
</p>"}
|
||
else
|
||
content += "<p class='info-bar'><a href='?_src_=holder;[HrefToken(TRUE)];action=refresh_admin_ticket_list;flag=[flag]'>Refresh List</a></p>"
|
||
|
||
if(tickets_list.len == 0)
|
||
content += "<p class='info-bar emboldened'>There are no tickets in the system</p>"
|
||
else
|
||
content += "<p class='info-bar emboldened'>Your tickets:</p>"
|
||
for(var/datum/admin_help/T in tickets_list)
|
||
if(compare_ckey(T.initiator, usr))
|
||
content += {"<p class='ticket-bar [T.state == AHELP_ACTIVE ? "unresolved" : "resolved"]'>
|
||
<b>[T.name]</b>
|
||
<a href='?src=[REF(T)];user=[REF(src)];action=view_admin_ticket;ticket=[REF(T)]'><img border='0' width='16' height='16' class='uiIcon16 icon-search' /> View</a>
|
||
</p>"}
|
||
|
||
var/html = get_html("Admin Tickets", "", "", content)
|
||
|
||
usr << browse(null, "window=ViewTickets")
|
||
usr << browse(html, "window=ViewTickets")
|
||
|
||
// Used for methods where input via arg doesn't work
|
||
/client/proc/get_adminhelp()
|
||
var/msg = input(src, "Please describe your problem concisely and an admin will help as soon as they're able.", "Adminhelp contents") as text|null
|
||
if(msg)
|
||
adminhelp(msg)
|
||
|
||
/client/verb/adminhelp(msg as text)
|
||
set category = "Admin"
|
||
set name = "Adminhelp"
|
||
|
||
if(GLOB.say_disabled) //This is here to try to identify lag problems
|
||
to_chat(usr, span_danger("Speech is currently admin-disabled."), confidential=TRUE)
|
||
return
|
||
|
||
//handle muting and automuting
|
||
if(prefs.muted & MUTE_ADMINHELP)
|
||
to_chat(src, span_danger("Error: Admin-PM: You cannot send adminhelps (Muted)."), confidential=TRUE)
|
||
return
|
||
if(handle_spam_prevention(msg,MUTE_ADMINHELP))
|
||
return
|
||
|
||
msg = trim(msg)
|
||
|
||
if(!msg)
|
||
return
|
||
|
||
SSblackbox.record_feedback("tally", "admin_verb", 1, "Adminhelp") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||
if(current_ticket)
|
||
if(alert(usr, "You already have a ticket open. Would you like to create a new ticket and close your old one?",,"Yes","No") != "Yes")
|
||
if(current_ticket)
|
||
current_ticket.MessageNoRecipient(msg)
|
||
current_ticket.TimeoutVerb()
|
||
return
|
||
else
|
||
to_chat(usr, span_warning("Ticket not found, creating new one..."), confidential=TRUE)
|
||
else
|
||
current_ticket.AddInteraction("[key_name_admin(usr)] opened a new ticket.")
|
||
current_ticket.Close()
|
||
|
||
new /datum/admin_help(msg, src, FALSE)
|
||
|
||
//
|
||
// LOGGING
|
||
//
|
||
|
||
//Use this proc when an admin takes action that may be related to an open ticket on what
|
||
//what can be a client, ckey, or mob
|
||
/proc/admin_ticket_log(what, message, for_admins = TRUE)
|
||
var/client/C
|
||
var/mob/Mob = what
|
||
if(istype(Mob))
|
||
C = Mob.client
|
||
else
|
||
C = what
|
||
if(istype(C) && C.current_ticket)
|
||
C.current_ticket.AddInteraction(message, for_admins)
|
||
return C.current_ticket
|
||
if(istext(what)) //ckey
|
||
var/datum/admin_help/AH = GLOB.ahelp_tickets.CKey2ActiveTicket(what)
|
||
if(AH)
|
||
AH.AddInteraction(message, for_admins)
|
||
return AH
|
||
|
||
//
|
||
// HELPER PROCS
|
||
//
|
||
|
||
/proc/get_admin_counts(requiredflags = R_BAN)
|
||
. = list("total" = list(), "noflags" = list(), "afk" = list(), "stealth" = list(), "present" = list())
|
||
if(!GLOB.permissions)
|
||
CRASH("Tried to fetch admin counts when permissions were not initialized")
|
||
for(var/client/X in GLOB.permissions.admins)
|
||
.["total"] += X
|
||
if(requiredflags != 0 && !check_rights_for(X, requiredflags))
|
||
.["noflags"] += X
|
||
else if(X.is_afk())
|
||
.["afk"] += X
|
||
else if(X.holder.fakekey)
|
||
.["stealth"] += X
|
||
else
|
||
.["present"] += X
|
||
|
||
/proc/send2irc_adminless_only(source, msg, requiredflags = R_BAN)
|
||
var/list/adm = get_admin_counts(requiredflags)
|
||
var/list/activemins = adm["present"]
|
||
. = activemins.len
|
||
if(. <= 0)
|
||
var/final = ""
|
||
var/list/afkmins = adm["afk"]
|
||
var/list/stealthmins = adm["stealth"]
|
||
var/list/powerlessmins = adm["noflags"]
|
||
var/list/allmins = adm["total"]
|
||
if(!afkmins.len && !stealthmins.len && !powerlessmins.len)
|
||
final = "[msg] - No admins online"
|
||
else
|
||
final = "[msg] - All admins stealthed\[[english_list(stealthmins)]\], AFK\[[english_list(afkmins)]\], or lacks +BAN\[[english_list(powerlessmins)]\]! Total: [allmins.len] "
|
||
send2irc(source,final)
|
||
send2otherserver(source,final)
|
||
|
||
|
||
/proc/send2irc(msg,msg2)
|
||
msg = replacetext(replacetext(msg, "\proper", ""), "\improper", "")
|
||
msg2 = replacetext(replacetext(msg2, "\proper", ""), "\improper", "")
|
||
world.TgsTargetedChatBroadcast("[msg] | [msg2]", TRUE)
|
||
|
||
/proc/send2otherserver(source,msg,type = "Ahelp")
|
||
set waitfor = FALSE
|
||
var/comms_key = CONFIG_GET(string/comms_key)
|
||
if(!comms_key)
|
||
return
|
||
var/list/message = list()
|
||
message["message_sender"] = source
|
||
message["message"] = msg
|
||
message["source"] = "([CONFIG_GET(string/cross_comms_name)])"
|
||
message["key"] = comms_key
|
||
message += type
|
||
|
||
var/list/servers = CONFIG_GET(keyed_list/cross_server)
|
||
for(var/I in servers)
|
||
world.Export("[servers[I]]?[list2params(message)]")
|
||
|
||
|
||
/proc/ircadminwho()
|
||
var/list/message = list("Admins: ")
|
||
var/list/admin_keys = list()
|
||
for(var/adm in GLOB.permissions.admins)
|
||
var/client/C = adm
|
||
admin_keys += "[C][C.holder.fakekey ? "(Stealth)" : ""][C.is_afk() ? "(AFK)" : ""]"
|
||
|
||
for(var/admin in admin_keys)
|
||
if(LAZYLEN(message) > 1)
|
||
message += ", [admin]"
|
||
else
|
||
message += "[admin]"
|
||
|
||
return jointext(message, "")
|
||
|
||
/proc/keywords_lookup(msg,irc)
|
||
|
||
//This is a list of words which are ignored by the parser when comparing message contents for names. MUST BE IN LOWER CASE!
|
||
var/list/adminhelp_ignored_words = list("unknown","the","a","an","of","monkey","alien","as", "i")
|
||
|
||
//explode the input msg into a list
|
||
var/list/msglist = splittext(msg, " ")
|
||
|
||
//generate keywords lookup
|
||
var/list/surnames = list()
|
||
var/list/forenames = list()
|
||
var/list/ckeys = list()
|
||
var/founds = ""
|
||
for(var/mob/M in GLOB.mob_list)
|
||
var/list/indexing = list(M.real_name, M.name)
|
||
if(M.mind)
|
||
indexing += M.mind.name
|
||
|
||
for(var/string in indexing)
|
||
var/list/L = splittext(string, " ")
|
||
var/surname_found = 0
|
||
//surnames
|
||
for(var/i=L.len, i>=1, i--)
|
||
var/word = ckey(L[i])
|
||
if(word)
|
||
surnames[word] = M
|
||
surname_found = i
|
||
break
|
||
//forenames
|
||
for(var/i=1, i<surname_found, i++)
|
||
var/word = ckey(L[i])
|
||
if(word)
|
||
forenames[word] = M
|
||
//ckeys
|
||
ckeys[M.ckey] = M
|
||
|
||
var/ai_found = 0
|
||
msg = ""
|
||
var/list/mobs_found = list()
|
||
for(var/original_word in msglist)
|
||
var/word = ckey(original_word)
|
||
if(word)
|
||
if(!(word in adminhelp_ignored_words))
|
||
if(word == "ai")
|
||
ai_found = 1
|
||
else
|
||
var/mob/found = ckeys[word]
|
||
if(!found)
|
||
found = surnames[word]
|
||
if(!found)
|
||
found = forenames[word]
|
||
if(found)
|
||
if(!(found in mobs_found))
|
||
mobs_found += found
|
||
if(!ai_found && isAI(found))
|
||
ai_found = 1
|
||
var/is_antag = 0
|
||
if(found.mind && found.mind.special_role)
|
||
is_antag = 1
|
||
founds += "Name: [found.name]([found.real_name]) Ckey: [found.ckey] [is_antag ? "(Antag)" : null] "
|
||
msg += "[original_word]<font size='1' color='[is_antag ? "red" : "black"]'>(<A HREF='?_src_=holder;[HrefToken(TRUE)];adminmoreinfo=[REF(found)]'>?</A>|<A HREF='?_src_=holder;[HrefToken(TRUE)];adminplayerobservefollow=[REF(found)]'>F</A>)</font> "
|
||
continue
|
||
msg += "[original_word] "
|
||
if(irc)
|
||
if(founds == "")
|
||
return "Search Failed"
|
||
else
|
||
return founds
|
||
|
||
return msg
|