diff --git a/code/__DEFINES/admin.dm b/code/__DEFINES/admin.dm index c5ef02c455..c9a6a25696 100644 --- a/code/__DEFINES/admin.dm +++ b/code/__DEFINES/admin.dm @@ -51,7 +51,7 @@ #define ADMIN_LOOKUP(user) "[key_name_admin(user)][ADMIN_QUE(user)]" #define ADMIN_LOOKUPFLW(user) "[key_name_admin(user)][ADMIN_QUE(user)] [ADMIN_FLW(user)]" #define ADMIN_SET_SD_CODE "(SETCODE)" -#define ADMIN_FULLMONTY_NONAME(user) "[ADMIN_QUE(user)] [ADMIN_PP(user)] [ADMIN_VV(user)] [ADMIN_SM(user)] [ADMIN_FLW(user)] [ADMIN_TP(user)] [ADMIN_INDIVIDUALLOG(user)]" +#define ADMIN_FULLMONTY_NONAME(user) "[ADMIN_QUE(user)] [ADMIN_PP(user)] [ADMIN_VV(user)] [ADMIN_SM(user)] [ADMIN_FLW(user)] [ADMIN_TP(user)] [ADMIN_INDIVIDUALLOG(user)] [ADMIN_SMITE(user)]" #define ADMIN_FULLMONTY(user) "[key_name_admin(user)] [ADMIN_FULLMONTY_NONAME(user)]" #define ADMIN_JMP(src) "(JMP)" #define COORD(src) "[src ? "([src.x],[src.y],[src.z])" : "nonexistent location"]" @@ -61,4 +61,8 @@ #define ADMIN_PUNISHMENT_LIGHTNING "Lightning bolt" #define ADMIN_PUNISHMENT_BRAINDAMAGE "Brain damage" #define ADMIN_PUNISHMENT_GIB "Gib" -#define ADMIN_PUNISHMENT_BSA "Bluespace Artillery Device" \ No newline at end of file +#define ADMIN_PUNISHMENT_BSA "Bluespace Artillery Device" + +#define AHELP_ACTIVE 1 +#define AHELP_CLOSED 2 +#define AHELP_RESOLVED 3 \ No newline at end of file diff --git a/code/datums/datumvars.dm b/code/datums/datumvars.dm index c40f39eb19..fbde20a8d7 100644 --- a/code/datums/datumvars.dm +++ b/code/datums/datumvars.dm @@ -1044,6 +1044,7 @@ if(result) var/newtype = GLOB.species_list[result] + admin_ticket_log("[key_name_admin(usr)] has modified the bodyparts of [H] to [result]") H.set_species(newtype) else if(href_list["editbodypart"]) @@ -1089,7 +1090,7 @@ to_chat(usr, "[C] doesn't have such bodypart.") else to_chat(usr, "Only humans can be augmented.") - + admin_ticket_log("[key_name_admin(usr)] has modified the bodyparts of [C]") else if(href_list["purrbation"]) @@ -1112,12 +1113,16 @@ if(success) to_chat(usr, "Put [H] on purrbation.") log_admin("[key_name(usr)] has put [key_name(H)] on purrbation.") - message_admins("[key_name(usr)] has put [key_name(H)] on purrbation.") + var/msg = "[key_name_admin(usr)] has put [key_name(H)] on purrbation." + message_admins(msg) + admin_ticket_log(H, msg) else to_chat(usr, "Removed [H] from purrbation.") log_admin("[key_name(usr)] has removed [key_name(H)] from purrbation.") - message_admins("[key_name(usr)] has removed [key_name(H)] from purrbation.") + var/msg = "[key_name_admin(usr)] has removed [key_name(H)] from purrbation." + message_admins(msg) + admin_ticket_log(H, msg) else if(href_list["adjustDamage"] && href_list["mobToDamage"]) if(!check_rights(0)) @@ -1158,6 +1163,7 @@ if(amount != 0) log_admin("[key_name(usr)] dealt [amount] amount of [Text] damage to [L] ") - message_admins("[key_name(usr)] dealt [amount] amount of [Text] damage to [L] ") + var/msg = "[key_name(usr)] dealt [amount] amount of [Text] damage to [L] " + message_admins(msg) + admin_ticket_log(L, msg) href_list["datumrefresh"] = href_list["mobToDamage"] - diff --git a/code/modules/admin/DB_ban/functions.dm b/code/modules/admin/DB_ban/functions.dm index 92665c06a5..12517eb385 100644 --- a/code/modules/admin/DB_ban/functions.dm +++ b/code/modules/admin/DB_ban/functions.dm @@ -6,7 +6,7 @@ if(!check_rights(R_BAN)) return - if(!SSdbcore.Connect()) + if(!SSdbcore.Connect()) to_chat(src, "Failed to establish database connection.") return @@ -71,7 +71,7 @@ computerid = bancid ip = banip - var/datum/DBQuery/query_add_ban_get_id = SSdbcore.NewQuery("SELECT id FROM [format_table_name("player")] WHERE ckey = '[ckey]'") + var/datum/DBQuery/query_add_ban_get_id = SSdbcore.NewQuery("SELECT id FROM [format_table_name("player")] WHERE ckey = '[ckey]'") if(!query_add_ban_get_id.warn_execute()) return var/validckey = 0 @@ -113,7 +113,7 @@ reason = sanitizeSQL(reason) if(maxadminbancheck) - var/datum/DBQuery/query_check_adminban_amt = SSdbcore.NewQuery("SELECT count(id) AS num FROM [format_table_name("ban")] WHERE (a_ckey = '[a_ckey]') AND (bantype = 'ADMIN_PERMABAN' OR (bantype = 'ADMIN_TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned)") + var/datum/DBQuery/query_check_adminban_amt = SSdbcore.NewQuery("SELECT count(id) AS num FROM [format_table_name("ban")] WHERE (a_ckey = '[a_ckey]') AND (bantype = 'ADMIN_PERMABAN' OR (bantype = 'ADMIN_TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned)") if(!query_check_adminban_amt.warn_execute()) return if(query_check_adminban_amt.NextRow()) @@ -126,16 +126,20 @@ if(!ip) ip = "0.0.0.0" var/sql = "INSERT INTO [format_table_name("ban")] (`bantime`,`server_ip`,`server_port`,`bantype`,`reason`,`job`,`duration`,`expiration_time`,`ckey`,`computerid`,`ip`,`a_ckey`,`a_computerid`,`a_ip`,`who`,`adminwho`) VALUES (Now(), INET_ATON('[world.internet_address]'), '[world.port]', '[bantype_str]', '[reason]', '[job]', [(duration)?"[duration]":"0"], Now() + INTERVAL [(duration>0) ? duration : 0] MINUTE, '[ckey]', '[computerid]', INET_ATON('[ip]'), '[a_ckey]', '[a_computerid]', INET_ATON('[a_ip]'), '[who]', '[adminwho]')" - var/datum/DBQuery/query_add_ban = SSdbcore.NewQuery(sql) + var/datum/DBQuery/query_add_ban = SSdbcore.NewQuery(sql) if(!query_add_ban.warn_execute()) return to_chat(usr, "Ban saved to database.") - message_admins("[key_name_admin(usr)] has added a [bantype_str] for [ckey] [(job)?"([job])":""] [(duration > 0)?"([duration] minutes)":""] with the reason: \"[reason]\" to the ban database.",1) + var/msg = "[key_name_admin(usr)] has added a [bantype_str] for [ckey] [(job)?"([job])":""] [(duration > 0)?"([duration] minutes)":""] with the reason: \"[reason]\" to the ban database." + message_admins(msg,1) + var/datum/admin_help/AH = admin_ticket_log(ckey, msg) if(announceinirc) send2irc("BAN ALERT","[a_ckey] applied a [bantype_str] on [ckey]") if(kickbannedckey) + if(AH) + AH.Resolve() //with prejudice if(banned_mob && banned_mob.client && banned_mob.client.ckey == banckey) qdel(banned_mob.client) return 1 @@ -187,13 +191,13 @@ if(job) sql += " AND job = '[job]'" - if(!SSdbcore.Connect()) + if(!SSdbcore.Connect()) return var/ban_id var/ban_number = 0 //failsafe - var/datum/DBQuery/query_unban_get_id = SSdbcore.NewQuery(sql) + var/datum/DBQuery/query_unban_get_id = SSdbcore.NewQuery(sql) if(!query_unban_get_id.warn_execute()) return while(query_unban_get_id.NextRow()) @@ -225,7 +229,7 @@ to_chat(usr, "Cancelled") return - var/datum/DBQuery/query_edit_ban_get_details = SSdbcore.NewQuery("SELECT ckey, duration, reason FROM [format_table_name("ban")] WHERE id = [banid]") + var/datum/DBQuery/query_edit_ban_get_details = SSdbcore.NewQuery("SELECT ckey, duration, reason FROM [format_table_name("ban")] WHERE id = [banid]") if(!query_edit_ban_get_details.warn_execute()) return @@ -254,7 +258,7 @@ to_chat(usr, "Cancelled") return - var/datum/DBQuery/query_edit_ban_reason = SSdbcore.NewQuery("UPDATE [format_table_name("ban")] SET reason = '[value]', edits = CONCAT(edits,'- [eckey] changed ban reason from \\\"[reason]\\\" to \\\"[value]\\\"
') WHERE id = [banid]") + var/datum/DBQuery/query_edit_ban_reason = SSdbcore.NewQuery("UPDATE [format_table_name("ban")] SET reason = '[value]', edits = CONCAT(edits,'- [eckey] changed ban reason from \\\"[reason]\\\" to \\\"[value]\\\"
') WHERE id = [banid]") if(!query_edit_ban_reason.warn_execute()) return message_admins("[key_name_admin(usr)] has edited a ban for [pckey]'s reason from [reason] to [value]",1) @@ -265,7 +269,7 @@ to_chat(usr, "Cancelled") return - var/datum/DBQuery/query_edit_ban_duration = SSdbcore.NewQuery("UPDATE [format_table_name("ban")] SET duration = [value], edits = CONCAT(edits,'- [eckey] changed ban duration from [duration] to [value]
'), expiration_time = DATE_ADD(bantime, INTERVAL [value] MINUTE) WHERE id = [banid]") + var/datum/DBQuery/query_edit_ban_duration = SSdbcore.NewQuery("UPDATE [format_table_name("ban")] SET duration = [value], edits = CONCAT(edits,'- [eckey] changed ban duration from [duration] to [value]
'), expiration_time = DATE_ADD(bantime, INTERVAL [value] MINUTE) WHERE id = [banid]") if(!query_edit_ban_duration.warn_execute()) return message_admins("[key_name_admin(usr)] has edited a ban for [pckey]'s duration from [duration] to [value]",1) @@ -287,13 +291,13 @@ var/sql = "SELECT ckey FROM [format_table_name("ban")] WHERE id = [id]" - if(!SSdbcore.Connect()) + if(!SSdbcore.Connect()) return var/ban_number = 0 //failsafe var/pckey - var/datum/DBQuery/query_unban_get_ckey = SSdbcore.NewQuery(sql) + var/datum/DBQuery/query_unban_get_ckey = SSdbcore.NewQuery(sql) if(!query_unban_get_ckey.warn_execute()) return while(query_unban_get_ckey.NextRow()) @@ -316,7 +320,7 @@ var/unban_ip = src.owner:address var/sql_update = "UPDATE [format_table_name("ban")] SET unbanned = 1, unbanned_datetime = Now(), unbanned_ckey = '[unban_ckey]', unbanned_computerid = '[unban_computerid]', unbanned_ip = INET_ATON('[unban_ip]') WHERE id = [id]" - var/datum/DBQuery/query_unban = SSdbcore.NewQuery(sql_update) + var/datum/DBQuery/query_unban = SSdbcore.NewQuery(sql_update) if(!query_unban.warn_execute()) return message_admins("[key_name_admin(usr)] has lifted [pckey]'s ban.",1) @@ -339,7 +343,7 @@ if(!check_rights(R_BAN)) return - if(!SSdbcore.Connect()) + if(!SSdbcore.Connect()) to_chat(usr, "Failed to establish database connection.") return @@ -405,7 +409,7 @@ var/bansperpage = 15 var/pagecount = 0 page = text2num(page) - var/datum/DBQuery/query_count_bans = SSdbcore.NewQuery("SELECT COUNT(id) FROM [format_table_name("ban")] WHERE 1 [playersearch] [adminsearch]") + var/datum/DBQuery/query_count_bans = SSdbcore.NewQuery("SELECT COUNT(id) FROM [format_table_name("ban")] WHERE 1 [playersearch] [adminsearch]") if(!query_count_bans.warn_execute()) return if(query_count_bans.NextRow()) @@ -431,7 +435,7 @@ output += "OPTIONS" output += "" var/limit = " LIMIT [bansperpage * page], [bansperpage]" - var/datum/DBQuery/query_search_bans = SSdbcore.NewQuery("SELECT id, bantime, bantype, reason, job, duration, expiration_time, ckey, a_ckey, unbanned, unbanned_ckey, unbanned_datetime, edits FROM [format_table_name("ban")] WHERE 1 [playersearch] [adminsearch] ORDER BY bantime DESC[limit]") + var/datum/DBQuery/query_search_bans = SSdbcore.NewQuery("SELECT id, bantime, bantype, reason, job, duration, expiration_time, ckey, a_ckey, unbanned, unbanned_ckey, unbanned_datetime, edits FROM [format_table_name("ban")] WHERE 1 [playersearch] [adminsearch] ORDER BY bantime DESC[limit]") if(!query_search_bans.warn_execute()) return @@ -498,4 +502,4 @@ output += "" - usr << browse(output,"window=lookupbans;size=900x500") + usr << browse(output,"window=lookupbans;size=900x500") \ No newline at end of file diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index c9ed982b98..22045b99ff 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -24,6 +24,7 @@ GLOBAL_LIST_INIT(admin_verbs_default, AVerbsDefault()) /client/proc/cmd_admin_pm_context, /*right-click adminPM interface*/ /client/proc/cmd_admin_pm_panel, /*admin-pm list*/ // /client/proc/mentor_memo, /*mentor memo system. show/delete/write. +SERVER needed to delete mentor memos of others*/ + /client/proc/cmd_admin_ticket_panel, /client/proc/stop_sounds ) GLOBAL_PROTECT(admin_verbs_admin) diff --git a/code/modules/admin/sql_message_system.dm b/code/modules/admin/sql_message_system.dm index 26430f7daa..029987064d 100644 --- a/code/modules/admin/sql_message_system.dm +++ b/code/modules/admin/sql_message_system.dm @@ -49,7 +49,10 @@ return if(logged) log_admin_private("[key_name(usr)] has created a [type][(type == "note" || type == "message" || type == "watchlist entry") ? " for [target_ckey]" : ""]: [text]") - message_admins("[key_name_admin(usr)] has created a [type][(type == "note" || type == "message" || type == "watchlist entry") ? " for [target_ckey]" : ""]:
[text]") + var/header = "[key_name_admin(usr)] has created a [type][(type == "note" || type == "message" || type == "watchlist entry") ? " for [target_ckey]" : ""]" + message_admins("[header]:
[text]") + admin_ticket_log(target_ckey, "[header]") + admin_ticket_log(target_ckey, text) if(browse) browse_messages("[type]") else @@ -357,4 +360,4 @@ this proc can take several minutes to execute fully if converting and cause DD t to_chat(world, "Deleting NOTESFILE") fdel(NOTESFILE) to_chat(world, "Finished mass note conversion, remember to turn off AUTOCONVERT_NOTES")*/ -#undef NOTESFILE +#undef NOTESFILE \ No newline at end of file diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index 609c763fbc..dd32085681 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -5,60 +5,19 @@ message_admins("[usr.key] has attempted to override the admin panel!") log_admin("[key_name(usr)] tried to use the admin panel without authorization.") return - if(href_list["rejectadminhelp"]) - if(world.time && (spamcooldown > world.time)) - to_chat(usr, "Please wait [max(round((spamcooldown - world.time)*0.1, 0.1), 0)] seconds.") - return + if(href_list["ahelp"]) if(!check_rights(R_ADMIN)) return - var/client/C = locate(href_list["rejectadminhelp"]) in GLOB.clients - if(!C) - return - if (deltimer(C.adminhelptimerid)) - C.giveadminhelpverb() - C << 'sound/effects/adminhelp.ogg' + var/ahelp_ref = href_list["ahelp"] + var/datum/admin_help/AH = locate(ahelp_ref) + if(AH) + AH.Action(href_list["ahelp_action"]) + else + to_chat(usr, "Ticket [ahelp_ref] has been deleted!") - to_chat(C, "- AdminHelp Rejected! -") - to_chat(C, "Your admin help was rejected. The adminhelp verb has been returned to you so that you may try again.") - to_chat(C, "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.") - - message_admins("[key_name_admin(usr)] Rejected [C.key]'s admin help. [C.key]'s Adminhelp verb has been returned to them.") - log_admin_private("[key_name(usr)] Rejected [C.key]'s admin help.") - spamcooldown = world.time + 150 // 15 seconds - - else if(href_list["icissue"]) - if(world.time && spamcooldown > world.time) - to_chat(usr, "Please wait [max(round((spamcooldown - world.time)*0.1, 0.1), 0)] seconds.") - return - var/client/C = locate(href_list["icissue"]) in GLOB.clients - if(!C) - return - - var/msg = "- AdminHelp marked as IC issue! -
" - msg += "Losing is part of the game!
" - msg += "Your character will frequently die, sometimes without even a possibility of avoiding it. Events will often be out of your control. No matter how good or prepared you are, sometimes you just lose." - - to_chat(C, msg) - - message_admins("[key_name_admin(usr)] marked [C.key]'s admin help as an IC issue.") - log_admin_private("[key_name(usr)] marked [C.key]'s admin help as an IC issue.") - spamcooldown = world.time + 150 // 15 seconds - - else if(href_list["markedread"]) - if(world.time && spamcooldown > world.time) - to_chat(usr, "Please wait [max(round((spamcooldown - world.time)*0.1, 0.1), 0)] seconds.") - return - var/client/C = locate(href_list["markedread"]) in GLOB.clients - if(!C) - return - - var/msg = "Your Adminhelp has been marked as read and is under investigation." - to_chat(C, msg) - - message_admins("[key_name_admin(usr)] marked [C.key]'s admin help as read and under investigation.") - log_admin_private("[key_name(usr)] marked [C.key]'s admin help as read and under investigation.") - spamcooldown = world.time + 150 // 15 seconds + else if(href_list["ahelp_tickets"]) + GLOB.ahelp_tickets.BrowseTickets(text2num(href_list["ahelp_tickets"])) else if(href_list["stickyban"]) stickyban(href_list["stickyban"],href_list) @@ -1124,7 +1083,7 @@ else if(href_list["messageedits"]) var/message_id = sanitizeSQL("[href_list["messageedits"]]") - var/datum/DBQuery/query_get_message_edits = SSdbcore.NewQuery("SELECT edits FROM [format_table_name("messages")] WHERE id = '[message_id]'") + var/datum/DBQuery/query_get_message_edits = SSdbcore.NewQuery("SELECT edits FROM [format_table_name("messages")] WHERE id = '[message_id]'") if(!query_get_message_edits.warn_execute()) return if(query_get_message_edits.NextRow()) @@ -1164,8 +1123,11 @@ else to_chat(M, "No ban appeals URL has been set.") log_admin_private("[key_name(usr)] has banned [M.ckey].\nReason: [key_name(M)]\nThis will be removed in [mins] minutes.") - message_admins("[key_name_admin(usr)] has banned [key_name_admin(M)].\nReason: [reason]\nThis will be removed in [mins] minutes.") - + var/msg = "[key_name_admin(usr)] has banned [key_name_admin(M)].\nReason: [reason]\nThis will be removed in [mins] minutes." + message_admins(msg) + var/datum/admin_help/AH = M.client ? M.client.current_ticket : null + if(AH) + AH.Resolve() qdel(M.client) if("No") var/reason = input(usr,"Please State Reason.","Reason") as message|null @@ -1189,7 +1151,11 @@ return ban_unban_log_save("[key_name(usr)] has permabanned [key_name(M)]. - Reason: [reason] - This is a permanent ban.") log_admin_private("[key_name(usr)] has banned [key_name_admin(M)].\nReason: [reason]\nThis is a permanent ban.") - message_admins("[key_name_admin(usr)] has banned [key_name_admin(M)].\nReason: [reason]\nThis is a permanent ban.") + var/msg = "[key_name_admin(usr)] has banned [key_name_admin(M)].\nReason: [reason]\nThis is a permanent ban." + message_admins(msg) + var/datum/admin_help/AH = M.client ? M.client.current_ticket : null + if(AH) + AH.Resolve() feedback_inc("ban_perma",1) qdel(M.client) if("Cancel") @@ -2276,4 +2242,4 @@ if(href_list["viewruntime_backto"]) error_viewer.show_to(owner, locate(href_list["viewruntime_backto"]), href_list["viewruntime_linear"]) else - error_viewer.show_to(owner, null, href_list["viewruntime_linear"]) + error_viewer.show_to(owner, null, href_list["viewruntime_linear"]) \ No newline at end of file diff --git a/code/modules/admin/verbs/adminhelp.dm b/code/modules/admin/verbs/adminhelp.dm index fc3c2ccd05..164f62be67 100644 --- a/code/modules/admin/verbs/adminhelp.dm +++ b/code/modules/admin/verbs/adminhelp.dm @@ -1,3 +1,602 @@ +/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/active_tickets = list() + var/list/closed_tickets = list() + var/list/resolved_tickets = list() + + var/obj/effect/statclick/ticket_list/astatclick = new(null, null, AHELP_ACTIVE) + var/obj/effect/statclick/ticket_list/cstatclick = new(null, null, AHELP_CLOSED) + var/obj/effect/statclick/ticket_list/rstatclick = new(null, null, AHELP_RESOLVED) + +/datum/admin_help_tickets/Destroy() + QDEL_LIST(active_tickets) + QDEL_LIST(closed_tickets) + QDEL_LIST(resolved_tickets) + QDEL_NULL(astatclick) + QDEL_NULL(cstatclick) + QDEL_NULL(rstatclick) + return ..() + +//private +/datum/admin_help_tickets/proc/ListInsert(datum/admin_help/new_ticket) + var/list/ticket_list + switch(new_ticket.state) + if(AHELP_ACTIVE) + ticket_list = active_tickets + if(AHELP_CLOSED) + ticket_list = closed_tickets + if(AHELP_RESOLVED) + ticket_list = resolved_tickets + else + CRASH("Invalid ticket state: [new_ticket.state]") + var/num_closed = ticket_list.len + if(num_closed) + for(var/I in 1 to num_closed) + var/datum/admin_help/AH = ticket_list[I] + if(AH.id > new_ticket.id) + ticket_list.Insert(I, new_ticket) + return + ticket_list += new_ticket + +//opens the ticket listings for one of the 3 states +/datum/admin_help_tickets/proc/BrowseTickets(state) + var/list/l2b + var/title + switch(state) + if(AHELP_ACTIVE) + l2b = active_tickets + title = "Active Tickets" + if(AHELP_CLOSED) + l2b = closed_tickets + title = "Closed Tickets" + if(AHELP_RESOLVED) + l2b = resolved_tickets + title = "Resolved Tickets" + if(!l2b) + return + var/list/dat = list("[title]") + dat += "Refresh

" + for(var/I in l2b) + var/datum/admin_help/AH = I + dat += "Ticket #[AH.id]: [AH.initiator_key_name]: [AH.name]
" + + usr << browse(dat.Join(), "window=ahelp_list[state];size=600x480") + +//Tickets statpanel +/datum/admin_help_tickets/proc/stat_entry() + var/num_disconnected = 0 + stat("Active Tickets:", astatclick.update("[active_tickets.len]")) + for(var/I in active_tickets) + var/datum/admin_help/AH = I + if(AH.initiator) + stat("#[AH.id]. [AH.initiator_key_name]:", AH.statclick.update()) + else + ++num_disconnected + if(num_disconnected) + stat("Disconnected:", astatclick.update("[num_disconnected]")) + stat("Closed Tickets:", cstatclick.update("[closed_tickets.len]")) + stat("Resolved Tickets:", rstatclick.update("[resolved_tickets.len]")) + +//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.AddInteraction("Client reconnected.") + +//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 active_tickets) + var/datum/admin_help/AH = I + if(AH.initiator_ckey == ckey) + return AH + +// +//TICKET LIST STATCLICK +// + +/obj/effect/statclick/ticket_list + var/current_state + +/obj/effect/statclick/ticket_list/New(loc, name, state) + current_state = state + ..() + +/obj/effect/statclick/ticket_list/Click() + GLOB.ahelp_tickets.BrowseTickets(current_state) + +// +//TICKET DATUM +// + +/datum/admin_help + var/id + var/name + var/state = AHELP_ACTIVE + + var/opened_at + var/closed_at + + var/client/initiator //semi-misnomer, it's the person who ahelped/was bwoinked + var/initiator_ckey + var/initiator_key_name + + var/list/_interactions //use AddInteraction() or, preferably, admin_ticket_log() + + var/obj/effect/statclick/ahelp/statclick + + var/static/ticket_counter = 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 + + TimeoutVerb() + + var/parsed_message = keywords_lookup(msg) + + statclick = new(null, src) + _interactions = list() + + if(is_bwoink) + AddInteraction("[key_name_admin(usr)] PM'd [LinkedReplyName()]") + message_admins("Ticket [TicketHref("#[id]")] created") + else + MessageNoRecipient(parsed_message) + + //show it to the person adminhelping too + to_chat(C, "PM to-Admins: [name]") + + //send it to irc if nobody is on and tell us how many were on + var/admin_number_present = send2irc_adminless_only(initiator_ckey, name) + 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, "No active admins are online, your adminhelp was sent to the admin irc.") + + GLOB.ahelp_tickets.active_tickets += src + +/datum/admin_help/Destroy() + RemoveActive() + GLOB.ahelp_tickets.closed_tickets -= src + GLOB.ahelp_tickets.resolved_tickets -= src + return ..() + +/datum/admin_help/proc/AddInteraction(formatted_message) + _interactions += "[gameTimestamp()]: [formatted_message]" + +//Removes the ahelp verb and returns it after 2 minutes +/datum/admin_help/proc/TimeoutVerb() + initiator.verbs -= /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]" + . = " (REJT)" + . += " (IC)" + . += " (CLOSE)" + . += " (RSLVE)" + +//private +/datum/admin_help/proc/LinkedReplyName(ref_src) + if(!ref_src) + ref_src = "\ref[src]" + return "[initiator_key_name]" + +//private +/datum/admin_help/proc/TicketHref(msg, ref_src, action = "ticket") + if(!ref_src) + ref_src = "\ref[src]" + return "[msg]" + +//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]" + var/chat_msg = "Ticket [TicketHref("#[id]", ref_src)]: [LinkedReplyName(ref_src)] [FullMonty(ref_src)]: [msg]" + + AddInteraction("[LinkedReplyName(ref_src)]: [msg]") + //send this msg to all admins + + for(var/client/X in GLOB.admins) + if(X.prefs.toggles & SOUND_ADMINHELP) + X << 'sound/effects/adminhelp.ogg' + window_flash(X, ignorepref = TRUE) + to_chat(X, chat_msg) + +//Reopen a closed ticket +/datum/admin_help/proc/Reopen() + if(state == AHELP_ACTIVE) + to_chat(usr, "This ticket is already open.") + return + + if(GLOB.ahelp_tickets.CKey2ActiveTicket(initiator_ckey)) + to_chat(usr, "This user already has an active ticket, cannot reopen this one.") + return + + statclick = new(null, src) + GLOB.ahelp_tickets.active_tickets += src + GLOB.ahelp_tickets.closed_tickets -= src + GLOB.ahelp_tickets.resolved_tickets -= src + switch(state) + if(AHELP_CLOSED) + feedback_dec("ahelp_close") + if(AHELP_RESOLVED) + feedback_dec("ahelp_resolve") + state = AHELP_ACTIVE + closed_at = null + if(initiator) + initiator.current_ticket = src + + AddInteraction("Reopened by [key_name_admin(usr)]") + var/msg = "Ticket [TicketHref("#[id]")] reopened by [key_name_admin(usr)]." + message_admins(msg) + log_admin_private(msg) + feedback_inc("ahelp_reopen") + 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 + QDEL_NULL(statclick) + GLOB.ahelp_tickets.active_tickets -= src + if(initiator && initiator.current_ticket == src) + initiator.current_ticket = null + +//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 + GLOB.ahelp_tickets.ListInsert(src) + AddInteraction("Closed by [key_name].") + if(!silent) + feedback_inc("ahelp_close") + var/msg = "Ticket [TicketHref("#[id]")] closed by [key_name]." + message_admins(msg) + log_admin_private(msg) + +//Mark open ticket as resolved/legitimate, returns ahelp verb +/datum/admin_help/proc/Resolve(key_name = key_name_admin(usr), silent = FALSE) + if(state != AHELP_ACTIVE) + return + RemoveActive() + state = AHELP_RESOLVED + GLOB.ahelp_tickets.ListInsert(src) + + if(initiator) + initiator.giveadminhelpverb() + + AddInteraction("Resolved by [key_name].") + if(!silent) + feedback_inc("ahelp_resolve") + var/msg = "Ticket [TicketHref("#[id]")] resolved by [key_name]" + message_admins(msg) + log_admin_private(msg) + +//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() + + initiator << 'sound/effects/adminhelp.ogg' + + to_chat(initiator, "- AdminHelp Rejected! -") + to_chat(initiator, "Your admin help was rejected. The adminhelp verb has been returned to you so that you may try again.") + 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.") + + feedback_inc("ahelp_reject") + var/msg = "Ticket [TicketHref("#[id]")] rejected by [key_name]" + message_admins(msg) + log_admin_private(msg) + AddInteraction("Rejected by [key_name].") + 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 = "- AdminHelp marked as IC issue! -
" + msg += "Losing is part of the game!
" + msg += "Your character will frequently die, sometimes without even a possibility of avoiding it. Events will often be out of your control. No matter how good or prepared you are, sometimes you just lose." + + if(initiator) + to_chat(initiator, msg) + + feedback_inc("ahelp_icissue") + msg = "Ticket [TicketHref("#[id]")] marked as IC by [key_name]" + message_admins(msg) + log_admin_private(msg) + AddInteraction("Marked as IC issue by [key_name]") + Resolve(silent = TRUE) + +//Show the ticket panel +/datum/admin_help/proc/TicketPanel() + var/list/dat = list("Ticket #[id]") + var/ref_src = "\ref[src]" + dat += "

Admin Help Ticket #[id]: [LinkedReplyName(ref_src)]

" + dat += "State: " + switch(state) + if(AHELP_ACTIVE) + dat += "OPEN" + if(AHELP_RESOLVED) + dat += "RESOLVED" + if(AHELP_CLOSED) + dat += "CLOSED" + else + dat += "UNKNOWN" + dat += "[GLOB.TAB][TicketHref("Refresh", ref_src)][GLOB.TAB][TicketHref("Re-Title", ref_src, "retitle")]" + if(state != AHELP_ACTIVE) + dat += "[GLOB.TAB][TicketHref("Reopen", ref_src, "reopen")]" + dat += "

Opened at: [gameTimestamp(wtime = opened_at)] (Approx [(world.time - opened_at) / 600] minutes ago)" + if(closed_at) + dat += "
Closed at: [gameTimestamp(wtime = closed_at)] (Approx [(world.time - closed_at) / 600] minutes ago)" + dat += "

" + if(initiator) + dat += "Actions: [FullMonty(ref_src)]
" + else + dat += "DISCONNECTED[GLOB.TAB][ClosureLinks(ref_src)]
" + dat += "
Log:

" + for(var/I in _interactions) + dat += "[I]
" + + usr << browse(dat.Join(), "window=ahelp[id];size=620x480") + +/datum/admin_help/proc/Retitle() + var/new_title = input(usr, "Enter a title for the ticket", "Rename Ticket", name) as text|null + if(new_title) + name = new_title + //not saying the original name cause it could be a long ass message + var/msg = "Ticket [TicketHref("#[id]")] titled [name] by [key_name_admin(usr)]" + message_admins(msg) + log_admin_private(msg) + TicketPanel() //we have to be here to do this + +//Forwarded action from admin/Topic +/datum/admin_help/proc/Action(action) + testing("Ahelp action: [action]") + switch(action) + if("ticket") + TicketPanel() + if("retitle") + Retitle() + if("reject") + Reject() + if("reply") + usr.client.cmd_ahelp_reply(initiator) + if("icissue") + ICIssue() + if("close") + Close() + if("resolve") + Resolve() + if("reopen") + Reopen() + +// +// TICKET STATCLICK +// + +/obj/effect/statclick/ahelp + var/datum/admin_help/ahelp_datum + +/obj/effect/statclick/ahelp/New(loc, datum/admin_help/AH) + ahelp_datum = AH + ..(loc) + +/obj/effect/statclick/ahelp/update() + return ..(ahelp_datum.name) + +/obj/effect/statclick/ahelp/Click() + ahelp_datum.TicketPanel() + +/obj/effect/statclick/ahelp/Destroy() + ahelp_datum = null + return ..() + +// +// CLIENT PROCS +// + +/client/proc/giveadminhelpverb() + src.verbs |= /client/verb/adminhelp + deltimer(adminhelptimerid) + adminhelptimerid = 0 + +/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, "Speech is currently admin-disabled.") + return + + //handle muting and automuting + if(prefs.muted & MUTE_ADMINHELP) + to_chat(src, "Error: Admin-PM: You cannot send adminhelps (Muted).") + return + if(handle_spam_prevention(msg,MUTE_ADMINHELP)) + return + + if(!msg) + return + + feedback_add_details("admin_verb","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. Is this for the same issue?",,"Yes","No") != "No") + if(current_ticket) + current_ticket.MessageNoRecipient(msg) + current_ticket.TimeoutVerb() + return + else + to_chat(usr, "Ticket not found, creating new one...") + else + current_ticket.AddInteraction("[key_name_admin(usr)] opened a new ticket.") + current_ticket.Close() + + new /datum/admin_help(msg, src, FALSE) + +//admin proc +/client/proc/cmd_admin_ticket_panel() + set name = "Show Ticket List" + set category = "Admin" + + if(!check_rights(R_ADMIN, TRUE)) + return + + var/browse_to + + switch(input("Display which ticket list?") as null|anything in list("Active Tickets", "Closed Tickets", "Resolved Tickets")) + if("Active Tickets") + browse_to = AHELP_ACTIVE + if("Closed Tickets") + browse_to = AHELP_CLOSED + if("Resolved Tickets") + browse_to = AHELP_RESOLVED + else + return + + GLOB.ahelp_tickets.BrowseTickets(browse_to) + +// +// 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) + 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) + return C.current_ticket + if(istext(what)) //ckey + var/datum/admin_help/AH = GLOB.ahelp_tickets.CKey2ActiveTicket(what) + if(AH) + AH.AddInteraction(message) + return AH + +// +// HELPER PROCS +// + +/proc/get_admin_counts(requiredflags = R_BAN) + . = list("total" = list(), "noflags" = list(), "afk" = list(), "stealth" = list(), "present" = list()) + for(var/client/X in GLOB.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) + if(config.useircbot) + shell("python nudge.py [msg] [msg2]") + return + +/proc/send2otherserver(source,msg,type = "Ahelp") + if(config.cross_allowed) + var/list/message = list() + message["message_sender"] = source + message["message"] = msg + message["source"] = "([config.cross_name])" + message["key"] = global.comms_key + message["crossmessage"] = type + + world.Export("[config.cross_address]?[list2params(message)]") + + +/proc/ircadminwho() + var/list/message = list("Admins: ") + var/list/admin_keys = list() + for(var/adm in GLOB.admins) + var/client/C = adm + admin_keys += "[C][C.holder.fakekey ? "(Stealth)" : ""][C.is_afk() ? "(AFK)" : ""]" + + for(var/admin in admin_keys) + if(LAZYLEN(admin_keys) > 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! @@ -67,128 +666,4 @@ else return founds - return msg - - -/client/var/adminhelptimerid = 0 - -/client/proc/giveadminhelpverb() - src.verbs |= /client/verb/adminhelp - adminhelptimerid = 0 - -/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, "Speech is currently admin-disabled.") - return - - //handle muting and automuting - if(prefs.muted & MUTE_ADMINHELP) - to_chat(src, "Error: Admin-PM: You cannot send adminhelps (Muted).") - return - if(src.handle_spam_prevention(msg,MUTE_ADMINHELP)) - return - - //clean the input msg - if(!msg) - return - msg = sanitize(copytext(msg,1,MAX_MESSAGE_LEN)) - if(!msg) return - var/original_msg = msg - - //remove our adminhelp verb temporarily to prevent spamming of admins. - src.verbs -= /client/verb/adminhelp - adminhelptimerid = addtimer(CALLBACK(src, .proc/giveadminhelpverb), 1200, TIMER_STOPPABLE) //2 minute cooldown of admin helps - - msg = keywords_lookup(msg) - - if(!mob) - return //this doesn't happen - - var/ref_client = "\ref[src]" - msg = "HELP: [key_name(src)] [ADMIN_FULLMONTY_NONAME(mob)] [ADMIN_SMITE(mob)] (REJT) (IC): [msg]" - - //send this msg to all admins - - for(var/client/X in GLOB.admins) - if(X.prefs.toggles & SOUND_ADMINHELP) - X << 'sound/effects/adminhelp.ogg' - window_flash(X, ignorepref = TRUE) - to_chat(X, msg) - - - //show it to the person adminhelping too - to_chat(src, "PM to-Admins: [original_msg]") - - //send it to irc if nobody is on and tell us how many were on - var/admin_number_present = send2irc_adminless_only(ckey,original_msg) - log_admin_private("HELP: [key_name(src)]: [original_msg] - heard by [admin_number_present] non-AFK admins who have +BAN.") - if(admin_number_present <= 0) - to_chat(src, "No active admins are online, your adminhelp was sent to the admin irc.") - feedback_add_details("admin_verb","Adminhelp") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - return - -/proc/get_admin_counts(requiredflags = R_BAN) - . = list("total" = list(), "noflags" = list(), "afk" = list(), "stealth" = list(), "present" = list()) - for(var/client/X in GLOB.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) - if(config.useircbot) - shell("python nudge.py [msg] [msg2]") - return - -/proc/send2otherserver(source,msg,type = "Ahelp") - if(config.cross_allowed) - var/list/message = list() - message["message_sender"] = source - message["message"] = msg - message["source"] = "([config.cross_name])" - message["key"] = global.comms_key - message["crossmessage"] = type - - world.Export("[config.cross_address]?[list2params(message)]") - - -/proc/ircadminwho() - var/list/message = list("Admins: ") - var/list/admin_keys = list() - for(var/adm in GLOB.admins) - var/client/C = adm - admin_keys += "[C][C.holder.fakekey ? "(Stealth)" : ""][C.is_afk() ? "(AFK)" : ""]" - - for(var/admin in admin_keys) - if(LAZYLEN(admin_keys) > 1) - message += ", [admin]" - else - message += "[admin]" - - return jointext(message, "") + return msg \ No newline at end of file diff --git a/code/modules/admin/verbs/adminjump.dm b/code/modules/admin/verbs/adminjump.dm index bc9c8f5a44..e02b71a4d4 100644 --- a/code/modules/admin/verbs/adminjump.dm +++ b/code/modules/admin/verbs/adminjump.dm @@ -104,9 +104,12 @@ to_chat(src, "Only administrators may use this command.") return - log_admin("[key_name(usr)] teleported [key_name(M)]") - message_admins("[key_name_admin(usr)] teleported [key_name_admin(M)]") - M.forceMove(get_turf(usr)) + var/atom/loc = get_turf(usr) + log_admin("[key_name(usr)] teleported [key_name(M)] to [COORD(loc)]") + var/msg = "[key_name_admin(usr)] teleported [key_name_admin(M)] to [ADMIN_COORDJMP(loc)]" + message_admins(msg) + admin_ticket_log(M, msg) + M.forceMove(loc) feedback_add_details("admin_verb","Get Mob") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! /client/proc/Getkey() @@ -121,15 +124,17 @@ var/list/keys = list() for(var/mob/M in GLOB.player_list) keys += M.client - var/client/selection = input("Please, select a player!", "Admin Jumping", null, null) as null|anything in sortKey(keys) + var/selection = input("Please, select a player!", "Admin Jumping", null, null) as null|anything in sortKey(keys) if(!selection) return - var/mob/M = selection.mob + var/mob/M = selection:mob if(!M) return log_admin("[key_name(usr)] teleported [key_name(M)]") - message_admins("[key_name_admin(usr)] teleported [key_name(M)]") + var/msg = "[key_name_admin(usr)] teleported [key_name_admin(M)]" + message_admins(msg) + admin_ticket_log(M, msg) if(M) M.forceMove(get_turf(usr)) usr.loc = M.loc @@ -146,7 +151,9 @@ if(M.forceMove(safepick(get_area_turfs(A)))) log_admin("[key_name(usr)] teleported [key_name(M)] to [A]") - message_admins("[key_name_admin(usr)] teleported [key_name_admin(M)] to [A]") + var/msg = "[key_name_admin(usr)] teleported [key_name_admin(M)] to [A]" + message_admins(msg) + admin_ticket_log(M, msg) else to_chat(src, "Failed to move mob to a valid location.") - feedback_add_details("admin_verb","Send Mob") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + feedback_add_details("admin_verb","Send Mob") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! \ No newline at end of file diff --git a/code/modules/admin/verbs/adminpm.dm b/code/modules/admin/verbs/adminpm.dm index d93a34fa09..5c76df805a 100644 --- a/code/modules/admin/verbs/adminpm.dm +++ b/code/modules/admin/verbs/adminpm.dm @@ -50,22 +50,30 @@ if(holder) to_chat(src, "Error: Admin-PM: Client not found.") return - message_admins("[key_name_admin(src)] has started replying to [key_name(C, 0, 0)]'s admin help.") - var/list/replace_chars = list("\n"=" ","\t"=" ") - var/msg = copytext(sanitize((input(src,"Message:", "Private message to [key_name(C, 0, 0)]") as message|null), replace_chars), 1, MAX_MESSAGE_LEN) - if (!msg|| msg == " ")//if they hit enter and didn't hit cancel, don't send it + + var/datum/admin_help/AH = C.current_ticket + + if(AH) + message_admins("[key_name_admin(src)] has started replying to [key_name(C, 0, 0)]'s admin help.") + var/msg = input(src,"Message:", "Private message to [key_name(C, 0, 0)]") as text|null + if (!msg) message_admins("[key_name_admin(src)] has cancelled their reply to [key_name(C, 0, 0)]'s admin help.") return - cmd_admin_pm(whom, msg) + cmd_admin_pm(whom, msg, AH) //takes input from cmd_admin_pm_context, cmd_admin_pm_panel or /client/Topic and sends them a PM. //Fetching a message if needed. src is the sender and C is the target client -/client/proc/cmd_admin_pm(whom, msg) +/client/proc/cmd_admin_pm(whom, msg, datum/admin_help/AH) if(prefs.muted & MUTE_ADMINHELP) to_chat(src, "Error: Admin-PM: You are unable to use admin PM-s (muted).") return - var/client/C + if(!holder && !current_ticket) //no ticket? https://www.youtube.com/watch?v=iHSPf6x1Fdo + to_chat(src, "You can no longer reply to this ticket, please open another one by using the Adminhelp verb if need be.") + to_chat(src, "Message: [msg]") + return + + var/client/recipient var/irc = 0 if(istext(whom)) if(cmptext(copytext(whom,1,2),"@")) @@ -73,14 +81,16 @@ if(whom == "IRCKEY") irc = 1 else - C = GLOB.directory[whom] + recipient = GLOB.directory[whom] else if(istype(whom,/client)) - C = whom + recipient = whom + + if(irc) if(!ircreplyamount) //to prevent people from spamming irc return if(!msg) - msg = input(src,"Message:", "Private message to Administrator") as message|null + msg = input(src,"Message:", "Private message to Administrator") as text|null if(!msg) return @@ -90,16 +100,16 @@ else - if(!C) + if(!recipient) if(holder) to_chat(src, "Error: Admin-PM: Client not found.") else - adminhelp(msg) //admin we are replying to left. adminhelp instead + current_ticket.MessageNoRecipient(msg) return //get message text, limit it's length.and clean/escape html if(!msg) - msg = input(src,"Message:", "Private message to [key_name(C, 0, 0)]") as message|null + msg = input(src,"Message:", "Private message to [key_name(recipient, 0, 0)]") as text|null if(!msg) return @@ -108,11 +118,11 @@ to_chat(src, "Error: Admin-PM: You are unable to use admin PM-s (muted).") return - if(!C) + if(!recipient) if(holder) to_chat(src, "Error: Admin-PM: Client not found.") else - adminhelp(msg) //admin we are replying to has vanished, adminhelp instead + current_ticket.MessageNoRecipient(msg) return if (src.handle_spam_prevention(msg,MUTE_ADMINHELP)) @@ -133,41 +143,55 @@ if(irc) to_chat(src, "PM to-Admins: [rawmsg]") + admin_ticket_log(src, "Reply PM from-[key_name(src, TRUE, TRUE)] to IRC: [keywordparsedmsg]") ircreplyamount-- send2irc("Reply: [ckey]",rawmsg) else - if(C.holder) + if(recipient.holder) if(holder) //both are admins - to_chat(C, "Admin PM from-[key_name(src, C, 1)]: [keywordparsedmsg]") - to_chat(src, "Admin PM to-[key_name(C, src, 1)]: [keywordparsedmsg]") + to_chat(recipient, "Admin PM from-[key_name(src, recipient, 1)]: [keywordparsedmsg]") + to_chat(src, "Admin PM to-[key_name(recipient, src, 1)]: [keywordparsedmsg]") + + //omg this is dumb, just fill in both their tickets + var/interaction_message = "PM from-[key_name(src, recipient, 1)] to-[key_name(recipient, src, 1)]: [keywordparsedmsg]" + admin_ticket_log(src, interaction_message) + if(recipient != src) //reeee + admin_ticket_log(recipient, interaction_message) else //recipient is an admin but sender is not - to_chat(C, "Reply PM from-[key_name(src, C, 1)]: [keywordparsedmsg]") + var/replymsg = "Reply PM from-[key_name(src, recipient, 1)]: [keywordparsedmsg]" + admin_ticket_log(src, replymsg) + to_chat(recipient, replymsg) to_chat(src, "PM to-Admins: [msg]") //play the recieving admin the adminhelp sound (if they have them enabled) - if(C.prefs.toggles & SOUND_ADMINHELP) - C << 'sound/effects/adminhelp.ogg' + if(recipient.prefs.toggles & SOUND_ADMINHELP) + recipient << 'sound/effects/adminhelp.ogg' else if(holder) //sender is an admin but recipient is not. Do BIG RED TEXT - to_chat(C, "-- Administrator private message --") - to_chat(C, "Admin PM from-[key_name(src, C, 0)]: [msg]") - to_chat(C, "Click on the administrator's name to reply.") - to_chat(src, "Admin PM to-[key_name(C, src, 1)]: [msg]") + if(!recipient.current_ticket) + new /datum/admin_help(msg, recipient, TRUE) + + to_chat(recipient, "-- Administrator private message --") + to_chat(recipient, "Admin PM from-[key_name(src, recipient, 0)]: [msg]") + to_chat(recipient, "Click on the administrator's name to reply.") + to_chat(src, "Admin PM to-[key_name(recipient, src, 1)]: [msg]") + + admin_ticket_log(recipient, "PM From [key_name_admin(src)]: [keywordparsedmsg]") //always play non-admin recipients the adminhelp sound - C << 'sound/effects/adminhelp.ogg' + recipient << 'sound/effects/adminhelp.ogg' //AdminPM popup for ApocStation and anybody else who wants to use it. Set it with POPUP_ADMIN_PM in config.txt ~Carn if(config.popup_admin_pm) spawn() //so we don't hold the caller proc up var/sender = src var/sendername = key - var/reply = input(C, msg,"Admin PM from-[sendername]", "") as message|null //show message and await a reply - if(C && reply) + var/reply = input(recipient, msg,"Admin PM from-[sendername]", "") as text|null //show message and await a reply + if(recipient && reply) if(sender) - C.cmd_admin_pm(sender,reply) //sender is still about, let's reply to them + recipient.cmd_admin_pm(sender,reply) //sender is still about, let's reply to them else adminhelp(reply) //sender has left, adminhelp instead return @@ -181,22 +205,47 @@ for(var/client/X in GLOB.admins) to_chat(X, "PM: [key_name(src, X, 0)]->IRC: \blue [keywordparsedmsg]" ) else - window_flash(C, ignorepref = TRUE) - log_admin_private("PM: [key_name(src)]->[key_name(C)]: [rawmsg]") + window_flash(recipient, ignorepref = TRUE) + log_admin_private("PM: [key_name(src)]->[key_name(recipient)]: [rawmsg]") //we don't use message_admins here because the sender/receiver might get it too for(var/client/X in GLOB.admins) - if(X.key!=key && X.key!=C.key) //check client/X is an admin and isn't the sender or recipient - to_chat(X, "PM: [key_name(src, X, 0)]->[key_name(C, X, 0)]: \blue [keywordparsedmsg]" ) + if(X.key!=key && X.key!=recipient.key) //check client/X is an admin and isn't the sender or recipient + to_chat(X, "PM: [key_name(src, X, 0)]->[key_name(recipient, X, 0)]: \blue [keywordparsedmsg]" ) /proc/IrcPm(target,msg,sender) - var/client/C = GLOB.directory[target] + var/datum/admin_help/ticket = C ? C.current_ticket : GLOB.ahelp_tickets.CKey2ActiveTicket(target) + var/compliant_msg = trim(lowertext(msg)) + var/unhandled = FALSE + var/irc_tagged = "[sender](IRC)" + switch(compliant_msg) + if("ticket close") + if(ticket) + ticket.Close(irc_tagged) + return "Ticket #[ticket.id] successfully closed" + if("ticket resolve") + if(ticket) + ticket.Resolve(irc_tagged) + return "Ticket #[ticket.id] successfully resolved" + if("ticket ic") + if(ticket) + ticket.ICIssue(irc_tagged) + return "Ticket #[ticket.id] successfully marked as IC issue" + if("ticket reject") + if(ticket) + ticket.Reject(irc_tagged) + return "Ticket #[ticket.id] successfully rejected" + else + unhandled = TRUE + if(!unhandled) + return "Ticket could not be found" + var/static/stealthkey - var/adminname = config.showircname ? "[sender](IRC)" : "Administrator" + var/adminname = config.showircname ? irc_tagged : "Administrator" if(!C) return "No client" @@ -215,6 +264,9 @@ to_chat(C, "-- Administrator private message --") to_chat(C, "Admin PM from-[adminname]: [msg]") to_chat(C, "Click on the administrator's name to reply.") + + admin_ticket_log(C, "PM From [irc_tagged]: [msg]") + window_flash(C, ignorepref = TRUE) //always play non-admin recipients the adminhelp sound C << 'sound/effects/adminhelp.ogg' @@ -238,4 +290,4 @@ GLOB.stealthminID["IRCKEY"] = stealth return stealth -#undef IRCREPLYCOUNT +#undef IRCREPLYCOUNT \ No newline at end of file diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm index 432c1a4075..46a2e906c1 100644 --- a/code/modules/admin/verbs/debug.dm +++ b/code/modules/admin/verbs/debug.dm @@ -68,8 +68,10 @@ But you can call procs that are of type /mob/living/carbon/human/proc/ for that if(!target) to_chat(usr, "Error: callproc(): owner of proc no longer exists.") return - log_admin("[key_name(src)] called [target]'s [procname]() with [lst.len ? "the arguments [list2params(lst)]":"no arguments"].") - message_admins("[key_name(src)] called [target]'s [procname]() with [lst.len ? "the arguments [list2params(lst)]":"no arguments"].") + var/msg = "[key_name(src)] called [target]'s [procname]() with [lst.len ? "the arguments [list2params(lst)]":"no arguments"]." + log_admin(msg) + message_admins(msg) + admin_ticket_log(target, msg) returnval = WrapAdminProcCall(target, procname, lst) // Pass the lst as an argument list to the proc else //this currently has no hascall protection. wasn't able to get it working. @@ -90,15 +92,16 @@ GLOBAL_PROTECT(AdminProcCall) UNTIL(!GLOB.AdminProcCall) to_chat(usr, "Running your proc") GLOB.AdminProcCall = usr.client.ckey //if this runtimes, too bad for you - try - if(target == GLOBAL_PROC) - . = call(procname)(arglist(arguments)) - else - . = call(procname)(arglist(arguments)) - catch() - //intentionally left blank + world.WrapAdminProcCall(target, procname, arguments) GLOB.AdminProcCall = null +//adv proc call this, ya nerds +/world/proc/WrapAdminProcCall(target, procname, list/arguments) + if(target == GLOBAL_PROC) + return call(procname)(arglist(arguments)) + else + return call(procname)(arglist(arguments)) + /proc/IsAdminAdvancedProcCall() return usr && usr.client && GLOB.AdminProcCall == usr.client.ckey @@ -124,7 +127,9 @@ GLOBAL_PROTECT(AdminProcCall) to_chat(usr, "Error: callproc_datum(): owner of proc no longer exists.") return log_admin("[key_name(src)] called [A]'s [procname]() with [lst.len ? "the arguments [list2params(lst)]":"no arguments"].") - message_admins("[key_name(src)] called [A]'s [procname]() with [lst.len ? "the arguments [list2params(lst)]":"no arguments"].") + var/msg = "[key_name(src)] called [A]'s [procname]() with [lst.len ? "the arguments [list2params(lst)]":"no arguments"]." + message_admins(msg) + admin_ticket_log(A, msg) feedback_add_details("admin_verb","Atom ProcCall") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! var/returnval = WrapAdminProcCall(A, procname, lst) // Pass the lst as an argument list to the proc @@ -803,4 +808,4 @@ GLOBAL_PROTECT(AdminProcCall) message_admins("[key_name_admin(src)] pumped a random event.") feedback_add_details("admin_verb","Pump Random Event") - log_admin("[key_name(src)] pumped a random event.") + log_admin("[key_name(src)] pumped a random event.") \ No newline at end of file diff --git a/code/modules/admin/verbs/listahelps.dm b/code/modules/admin/verbs/listahelps.dm deleted file mode 100644 index 622138ccbd..0000000000 --- a/code/modules/admin/verbs/listahelps.dm +++ /dev/null @@ -1,315 +0,0 @@ -/datum/adminticket - var/active = TICKET_INACTIVE //Is the adminhelp active, eg admin responded? - var/admin = TICKET_UNASSIGNED //The handling admin? Like come on. - var/id = "" //ID of the ticket, very important as its used to find adminhelps. - var/ticket_logs = list() //The logs of the adminhelp. - var/mob //The mob adminhelping mob. - var/msg = "" //The adminhelp message. - var/permckey = "" //The perm ckey, never removed essentially. - var/permuser = "" //Same as above! - var/replying = TICKET_UNREPLIED //Is someone responding to the adminhelp? - var/resolved = TICKET_UNRESOLVED //Is it resolved? Its much easier to have a "Yes" or a "No", as you can directly concat it into strings making life that much easier. - var/uckey //The saved ckey of the adminhelping user. - var/uID = "" //The UNIQUE id, made by putting part of the ckey and the ID together. Used internally in code. - var/user = "" //The user of the ahelp. - - -/client/proc/list_ahelps(user, resolved) - if(!check_rights(R_ADMIN)) - src << "Error: Only administrators may use this command." - return - - for(var/I in admintickets) - var/datum/adminticket/T = I - - if(resolved) - user << "Resolved Ahelps:" - usr << "#[T.id] By: [key_name(T.permuser)] Ckey: [T.permckey] Name: [T.permuser] Unique id: [T.uID]" - usr << " Controls: [ADMIN_QUE(mob)] [ADMIN_PP(mob)] [ADMIN_VV(mob)] [ADMIN_SM(mob)] [ADMIN_FLW(mob)] [ADMIN_TP(mob)]" - usr << " Message: [T.msg]" - usr << " Handling Admin: [T.admin]" - usr << " Replied To: [T.active]/(LOGS)" - if(T.resolved == TICKET_UNRESOLVED) - usr << " Resolved: [T.resolved] (Resolve)" - else - usr << " Resolved: [T.resolved] (Unresolve)" - - else - user << "Current Unresolved Ahelps:" - if(T.resolved == TICKET_UNRESOLVED) - usr << "#[T.id] By: [key_name(T.permuser)] Ckey: [T.permckey] Name: [T.permuser] Unique ID: [T.uID]" - usr << " Controls: [ADMIN_QUE(mob)] [ADMIN_PP(mob)] [ADMIN_VV(mob)] [ADMIN_SM(mob)] [ADMIN_FLW(mob)] [ADMIN_TP(mob)]" - usr << " Message: [T.msg]" - usr << " Handling Admin: [T.admin]" - usr << " Replied To: [T.active]/(LOGS)" - if(T.resolved == TICKET_UNRESOLVED) - usr << " Resolved: [T.resolved] (Resolve)" - else - usr << " Resolved: [T.resolved] (Unresolve)" - -/client/proc/ahelp_count(modifier) - var/amount - for(var/I in admintickets) - var/datum/adminticket/T = I - if(T.resolved == TICKET_UNRESOLVED) - amount++ - if(T.resolved == TICKET_RESOLVED) - amount++ - return amount - - -/datum/adminticket/proc/listtickets() - set category = "Admin Help" - set name = "List Adminhelps" - set desc = "List all current adminhelps" - - if(!check_rights(R_ADMIN)) - src << "Error: Only administrators may use this command." - return - - var/count = 0 - - for(var/datum/adminticket/T in admintickets) - count++ - - usr << "Current Ahelps:" - - if(count < 1) - usr << " None" - return - for(var/I in admintickets) - var/datum/adminticket/T = I - usr << "#[T.id] By: [key_name(T.permuser)] Ckey: [T.permckey] Name: [T.permuser] Unique ID: [T.uID]" - usr << " Controls: [ADMIN_QUE(mob)] [ADMIN_PP(mob)] [ADMIN_VV(mob)] [ADMIN_SM(mob)] [ADMIN_FLW(mob)] [ADMIN_TP(mob)]" - usr << " Message: [T.msg]" - usr << " Handling Admin: [T.admin]" - usr << " Replied To: [T.active]/(LOGS)" - if(T.resolved == TICKET_UNRESOLVED) - usr << " Resolved: [T.resolved] (Resolve)" - else - usr << " Resolved: [T.resolved] (Unresolve)" - -/datum/adminticket/proc/listunresolvedtickets() - set category = "Admin Help" - set name = "List Unresolved Adminhelps" - set desc = "List all current unresolved adminhelps" - - if(!check_rights(R_ADMIN)) - src << "Error: Only administrators may use this command." - return - - var/count = 0 - - for(var/I in admintickets) - var/datum/adminticket/T = I - if(T.resolved == TICKET_UNRESOLVED) - count++ - - usr << "Current Unresolved Ahelps:" - - if(count < 1) - usr << " None" - return - - for(var/I in admintickets) - var/datum/adminticket/T = I - if(T.resolved == TICKET_UNRESOLVED) - usr << "#[T.id] By: [key_name(T.permuser)] Ckey: [T.permckey] Name: [T.permuser] Unique ID: [T.uID]" - usr << " Controls: [ADMIN_QUE(mob)] [ADMIN_PP(mob)] [ADMIN_VV(mob)] [ADMIN_SM(mob)] [ADMIN_FLW(mob)] [ADMIN_TP(mob)]" - usr << " Message: [T.msg]" - usr << " Handling Admin: [T.admin]" - usr << " Replied To: [T.active]/(LOGS)" - if(T.resolved == TICKET_UNRESOLVED) - usr << " Resolved: [T.resolved] (Resolve)" - else - usr << " Resolved: [T.resolved] (Unresolve)" - -/client/proc/listhandlingahelp() - set category = "Admin Help" - set name = "View Handling Ahelps" - set desc = "List all current handling ahelps" - - if(!check_rights(R_ADMIN)) - src << "Error: Only administrators may use this command." - return - - var/count = 0 - - for(var/I in admintickets) - var/datum/adminticket/T = I - if(T.resolved == TICKET_UNRESOLVED && T.admin == ckey) - count++ - - if(count < 1) - usr << "You don't have any ACTIVE ahelps!" - return - for(var/I in admintickets) - var/datum/adminticket/T = I - if(T.resolved == TICKET_UNRESOLVED && T.admin == ckey) - usr << "#[T.id] By: [key_name(T.permuser)] Ckey: [T.permckey] Name: [T.permuser] Unique ID: [T.uID]" - usr << " Controls: [ADMIN_QUE(mob)] [ADMIN_PP(mob)] [ADMIN_VV(mob)] [ADMIN_SM(mob)] [ADMIN_FLW(mob)] [ADMIN_TP(mob)]" - usr << " Message: [T.msg]" - usr << " Handling Admin: [T.admin]" - usr << " Replied To: [T.active]/(LOGS)" - if(T.resolved == TICKET_UNRESOLVED) - usr << " Resolved: [T.resolved] (Resolve)" - else - usr << " Resolved: [T.resolved] (Unresolve)" - -/client/verb/viewmyahelp() - set category = "Admin" - set name = "View my Ahelps" - set desc = "List your ahelps" - - var/count = 0 - - for(var/I in admintickets) - var/datum/adminticket/T = I - if(T.permckey == ckey) - count++ - - if(count < 1) - usr << "You don't have any ahelps!" - return - - usr << "Resolved Ahelps" - - var/rpass = FALSE - - for(var/I in admintickets) - var/datum/adminticket/T = I - - if(T.permckey == ckey && T.resolved == TICKET_RESOLVED) - rpass = TRUE - usr << "Adminhelp ID: #[T.id] " - usr << " Message: [T.msg]" - usr << " Handling Admin: [T.admin]" - usr << " Replied To: [T.active]/(LOGS)" - usr << " Resolved: [T.resolved]" - - if(rpass == FALSE) - usr << " None" - - usr << "Unresolved Ahelps" - - var/upass = FALSE - - for(var/I in admintickets) - var/datum/adminticket/T = I - if(T.permckey == ckey && T.resolved == TICKET_UNRESOLVED) - upass = TRUE - usr << "Adminhelp ID: #[T.id] " - usr << " Message: [T.msg]" - usr << " Handling Admin: [T.admin]" - usr << " Replied To: [T.active]/(LOGS)" - usr << " Resolved: [T.resolved]" - - if(upass == FALSE) - usr << " None" - -/client/proc/createticket(player, message, uckey, mob) - var/datum/adminticket/A = new() - A.user = player - A.msg = message - A.uckey = uckey - A.permckey = uckey - A.permuser = A.user - admintickets += A - A.ticket_logs += "ADMINHELP: [A.permckey]([A.permuser]): [A.msg]" - A.mob = mob - - var/index = 0 - for(var/I in admintickets) - var/datum/adminticket/T = I - index++ - T.id = index - T.uID = "[T.permckey][T.id]" - -/client/verb/resolveticketself() - set category = "Admin" - set name = "Resolve My Adminhelp" - set desc = "Resolve my own adminhelp" - - var/pass = 0 - var/datum/adminticket/ticket - for(var/I in admintickets) - var/datum/adminticket/T = I - if(T.permckey == ckey && T.resolved != TICKET_RESOLVED) - T.resolved = TICKET_RESOLVED - pass = 1 - - switch(pass) - if(1) - src << "You have resolved your current adminhelp." - message_admins("[src] has resolved his adminhelp (#[ticket.id])") - if(0) - src << "Error, you do not have any active adminhelps." - -/client/proc/resolvehandlingahelp() - set category = "Admin Help" - set name = "Resolve Handling Ahelp" - set desc = "Resolve my own adminhelp" - - if(!check_rights(R_ADMIN)) - src << "Error: Only administrators may use this command." - return - - var/count = 0 - - var/datum/adminticket/ticket - for(var/I in admintickets) - var/datum/adminticket/T = I - if(T.admin == ckey && T.resolved != TICKET_RESOLVED) - count++ - - if(count >= 1) - usr << "Adminhelp #[T.id]([T.uID]) resolved." - message_admins("Adminhelp ID: #[T.id]([T.uID]) was resolved by [usr.ckey]") - ticket.user << "Your adminhelp (#[T.id]) has been resolved by [usr.ckey]" - ticket.user << 'sound/machines/twobeep.ogg' - ticket.resolved = TICKET_RESOLVED - - if(count < 1) - usr << "You are not currently handling any adminhelps!" - -/datum/adminticket/proc/viewlogs(NuID, mob/user) - var/dat = "

View Logs for ahelp [NuID]

" - var/datum/adminticket/ticket - - var/pass = 0 - - for(var/datum/adminticket/T in admintickets) - if(NuID == T.uID) - ticket = T - pass = 1 - - if(pass == 0) - src << "Error, log system not found for [NuID]... " - return - - dat += "" - for(var/text in ticket.ticket_logs) - dat += "" - dat += "
[text]
" - - var/datum/browser/popup = new(user, "ahelp logs", ticket.permuser, 500, 500) - popup.set_content(dat) - popup.open() - -/datum/adminticket/Topic(href, href_list) - if (!istype(src,/datum/admins)) - src = usr.client.holder - if (!istype(src,/datum/admins)) - usr << "Error: you are not an admin!" - return - - if(href_list["view_logs"]) - var/datum/adminticket/T = locate(href_list["view_logs"]) - viewlogs(T.uID, usr) - if(href_list["resolve"]) - var/datum/adminticket/T = locate(href_list["resolve"]) in admintickets - if(T && istype(T)) - message_admins("Adminhelp ID: #[T.id]([T.uID]) was [T.resolved == "Yes" ? "unresolved" : "resolved"] by [usr.ckey]") - T.user << "Your adminhelp (#[T.id]) has been [T.resolved == "Yes" ? "unresolved" : "resolved"] by [usr.ckey]" - T.user << 'sound/machines/twobeep.ogg' - T.resolved = "[T.resolved == "Yes" ? "No" : "Yes"]" \ No newline at end of file diff --git a/code/modules/admin/verbs/modifyvariables.dm b/code/modules/admin/verbs/modifyvariables.dm index 193ff49e89..18e883d7f2 100644 --- a/code/modules/admin/verbs/modifyvariables.dm +++ b/code/modules/admin/verbs/modifyvariables.dm @@ -528,7 +528,7 @@ GLOBAL_PROTECT(VVpixelmovement) variable = input("Which var?","Var") as null|anything in names if(!variable) return - + if(!O.can_vv_get(variable)) return @@ -614,4 +614,6 @@ GLOBAL_PROTECT(VVpixelmovement) return log_world("### VarEdit by [src]: [O.type] [variable]=[html_encode("[O.vars[variable]]")]") log_admin("[key_name(src)] modified [original_name]'s [variable] to [O.vars[variable]]") - message_admins("[key_name_admin(src)] modified [original_name]'s [variable] to [O.vars[variable]]") + var/msg = "[key_name_admin(src)] modified [original_name]'s [variable] to [O.vars[variable]]" + message_admins(msg) + admin_ticket_log(O, msg) \ No newline at end of file diff --git a/code/modules/admin/verbs/pray.dm b/code/modules/admin/verbs/pray.dm index 77271dbc08..70d70c899a 100644 --- a/code/modules/admin/verbs/pray.dm +++ b/code/modules/admin/verbs/pray.dm @@ -33,7 +33,7 @@ prayer_type = "CULTIST PRAYER" deity = "Nar-Sie" - msg = "\icon[cross][prayer_type][deity ? " (to [deity])" : ""]: [ADMIN_FULLMONTY(src)] [ADMIN_SC(src)] [ADMIN_SMITE(src)]: [msg]" + msg = "\icon[cross][prayer_type][deity ? " (to [deity])" : ""]: [ADMIN_FULLMONTY(src)] [ADMIN_SC(src)]: [msg]" for(var/client/C in GLOB.admins) if(C.prefs.chat_toggles & CHAT_PRAYER) @@ -48,21 +48,21 @@ /proc/Centcomm_announce(text , mob/Sender) var/msg = copytext(sanitize(text), 1, MAX_MESSAGE_LEN) - msg = "CENTCOM:[ADMIN_FULLMONTY(Sender)] [ADMIN_SMITE(Sender)] [ADMIN_CENTCOM_REPLY(Sender)]: [msg]" + msg = "CENTCOM:[ADMIN_FULLMONTY(Sender)] [ADMIN_CENTCOM_REPLY(Sender)]: [msg]" to_chat(GLOB.admins, msg) for(var/obj/machinery/computer/communications/C in GLOB.machines) C.overrideCooldown() /proc/Syndicate_announce(text , mob/Sender) var/msg = copytext(sanitize(text), 1, MAX_MESSAGE_LEN) - msg = "SYNDICATE:[ADMIN_FULLMONTY(Sender)] [ADMIN_SMITE(Sender)] [ADMIN_SYNDICATE_REPLY(Sender)]: [msg]" + msg = "SYNDICATE:[ADMIN_FULLMONTY(Sender)] [ADMIN_SYNDICATE_REPLY(Sender)]: [msg]" to_chat(GLOB.admins, msg) for(var/obj/machinery/computer/communications/C in GLOB.machines) C.overrideCooldown() /proc/Nuke_request(text , mob/Sender) var/msg = copytext(sanitize(text), 1, MAX_MESSAGE_LEN) - msg = "NUKE CODE REQUEST:[ADMIN_FULLMONTY(Sender)] [ADMIN_SMITE(Sender)] [ADMIN_CENTCOM_REPLY(Sender)] [ADMIN_SET_SD_CODE]: [msg]" + msg = "NUKE CODE REQUEST:[ADMIN_FULLMONTY(Sender)] [ADMIN_CENTCOM_REPLY(Sender)] [ADMIN_SET_SD_CODE]: [msg]" to_chat(GLOB.admins, msg) for(var/obj/machinery/computer/communications/C in GLOB.machines) - C.overrideCooldown() + C.overrideCooldown() \ No newline at end of file diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm index 361d48817a..135f69e85d 100644 --- a/code/modules/admin/verbs/randomverbs.dm +++ b/code/modules/admin/verbs/randomverbs.dm @@ -15,10 +15,11 @@ M.regenerate_icons() log_admin("[key_name(usr)] made [key_name(M)] drop everything!") - message_admins("[key_name_admin(usr)] made [key_name_admin(M)] drop everything!") + var/msg = "[key_name_admin(usr)] made [key_name_admin(M)] drop everything!" + message_admins(msg) + admin_ticket_log(M, msg) feedback_add_details("admin_verb","Drop Everything") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - /client/proc/cmd_admin_subtle_message(mob/M in GLOB.mob_list) set category = "Special Verbs" set name = "Subtle Message" @@ -41,7 +42,9 @@ to_chat(M, "You hear a voice in your head... [msg]") log_admin("SubtlePM: [key_name(usr)] -> [key_name(M)] : [msg]") - message_admins(" SubtleMessage: [key_name_admin(usr)] -> [key_name_admin(M)] : [msg]") + msg = " SubtleMessage: [key_name_admin(usr)] -> [key_name_admin(M)] : [msg]" + message_admins(msg) + admin_ticket_log(M, msg) feedback_add_details("admin_verb","Subtle Message") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! /client/proc/cmd_admin_world_narrate() @@ -82,7 +85,9 @@ to_chat(M, msg) log_admin("DirectNarrate: [key_name(usr)] to ([M.name]/[M.key]): [msg]") - message_admins(" DirectNarrate: [key_name(usr)] to ([M.name]/[M.key]): [msg]
") + msg = " DirectNarrate: [key_name(usr)] to ([M.name]/[M.key]): [msg]
" + message_admins(msg) + admin_ticket_log(M, msg) feedback_add_details("admin_verb","Direct Narrate") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! /client/proc/cmd_admin_local_narrate(atom/A) @@ -117,7 +122,9 @@ to_chat(usr, "Toggled [(M.status_flags & GODMODE) ? "ON" : "OFF"]") log_admin("[key_name(usr)] has toggled [key_name(M)]'s nodamage to [(M.status_flags & GODMODE) ? "On" : "Off"]") - message_admins("[key_name_admin(usr)] has toggled [key_name_admin(M)]'s nodamage to [(M.status_flags & GODMODE) ? "On" : "Off"]") + var/msg = "[key_name_admin(usr)] has toggled [key_name_admin(M)]'s nodamage to [(M.status_flags & GODMODE) ? "On" : "Off"]" + message_admins(msg) + admin_ticket_log(M, msg) feedback_add_details("admin_toggle","Godmode|[M.status_flags & GODMODE]") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! @@ -238,7 +245,9 @@ return 0 new_xeno.ckey = ckey - message_admins("[key_name_admin(usr)] has spawned [ckey] as a filthy xeno [alien_caste].") + var/msg = "[key_name_admin(usr)] has spawned [ckey] as a filthy xeno [alien_caste]." + message_admins(msg) + admin_ticket_log(new_xeno, msg) return 1 /* @@ -297,7 +306,9 @@ Traitors and the like can also be revived with the previous role mostly intact. G_found.mind.transfer_to(new_xeno) //be careful when doing stuff like this! I've already checked the mind isn't in use new_xeno.key = G_found.key to_chat(new_xeno, "You have been fully respawned. Enjoy the game.") - message_admins("[key_name_admin(usr)] has respawned [new_xeno.key] as a filthy xeno.") + var/msg = "[key_name_admin(usr)] has respawned [new_xeno.key] as a filthy xeno." + message_admins(msg) + admin_ticket_log(new_xeno, msg) return //all done. The ghost is auto-deleted //check if they were a monkey @@ -307,7 +318,9 @@ Traitors and the like can also be revived with the previous role mostly intact. G_found.mind.transfer_to(new_monkey) //be careful when doing stuff like this! I've already checked the mind isn't in use new_monkey.key = G_found.key to_chat(new_monkey, "You have been fully respawned. Enjoy the game.") - message_admins("[key_name_admin(usr)] has respawned [new_monkey.key] as a filthy xeno.") + var/msg = "[key_name_admin(usr)] has respawned [new_monkey.key] as a filthy xeno." + message_admins(msg) + admin_ticket_log(new_monkey, msg) return //all done. The ghost is auto-deleted @@ -404,7 +417,9 @@ Traitors and the like can also be revived with the previous role mostly intact. if(alert(new_character,"Would you like an active AI to announce this character?",,"No","Yes")=="Yes") AnnounceArrival(new_character, new_character.mind.assigned_role) - message_admins("[admin] has respawned [player_key] as [new_character.real_name].") + var/msg = "[admin] has respawned [player_key] as [new_character.real_name]." + message_admins(msg) + admin_ticket_log(new_character, msg) to_chat(new_character, "You have been fully respawned. Enjoy the game.") @@ -447,7 +462,9 @@ Traitors and the like can also be revived with the previous role mostly intact. M.revive(full_heal = 1, admin_revive = 1) log_admin("[key_name(usr)] healed / revived [key_name(M)]") - message_admins("Admin [key_name_admin(usr)] healed / revived [key_name_admin(M)]!") + var/msg = "Admin [key_name_admin(usr)] healed / revived [key_name_admin(M)]!" + message_admins(msg) + admin_ticket_log(M, msg) feedback_add_details("admin_verb","Rejuvinate") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! /client/proc/cmd_admin_create_centcom_report() @@ -1169,5 +1186,7 @@ GLOBAL_LIST_EMPTY(custom_outfits) //Admin created outfits if(ADMIN_PUNISHMENT_BSA) bluespace_artillery(target) - message_admins("[key_name_admin(usr)] punished [key_name_admin(target)] with [punishment].") - log_admin("[key_name(usr)] punished [key_name(target)] with [punishment].") + var/msg = "[key_name_admin(usr)] punished [key_name_admin(target)] with [punishment]." + message_admins(msg) + admin_ticket_log(target, msg) + log_admin("[key_name(usr)] punished [key_name(target)] with [punishment].") \ No newline at end of file diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index c63c1a5e55..6d88989b3d 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -68,6 +68,7 @@ if (topiclimiter[SECOND_COUNT] > config.secondtopiclimit) to_chat(src, "Your previous action was ignored because you've done too many in a second") return + /* if(href_list["mentor_msg"]) if(config.mentors_mobname_only) @@ -77,15 +78,13 @@ cmd_mentor_pm(href_list["mentor_msg"],null) return */ + //Logs all hrefs if(config && config.log_hrefs && GLOB.href_logfile) GLOB.href_logfile << "[time_stamp(show_ds = TRUE)] [src] (usr:[usr]) || [hsrc ? "[hsrc] " : ""][href]
" // Admin PM if(href_list["priv_msg"]) - if (href_list["ahelp_reply"]) - cmd_ahelp_reply(href_list["priv_msg"]) - return cmd_admin_pm(href_list["priv_msg"],null) return @@ -167,6 +166,8 @@ GLOBAL_LIST(external_rsc_urls) GLOB.clients += src GLOB.directory[ckey] = src + GLOB.ahelp_tickets.ClientLogin(src) + //Admin Authorisation var/localhost_addresses = list("127.0.0.1", "::1") if(address && (address in localhost_addresses)) @@ -361,6 +362,8 @@ GLOBAL_LIST(external_rsc_urls) adminGreet(1) holder.owner = null GLOB.admins -= src + + GLOB.ahelp_tickets.ClientLogout(src) GLOB.directory -= ckey GLOB.clients -= src if(movingmob != null) @@ -602,4 +605,4 @@ GLOBAL_LIST(external_rsc_urls) if (isnull(new_size)) CRASH("change_view called without argument.") - view = new_size + view = new_size \ No newline at end of file diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index d462dffce2..3f273c3bc1 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -577,7 +577,6 @@ if(ETA) stat(null, "[ETA] [SSshuttle.emergency.getTimerStr()]") - if(client && client.holder) if(statpanel("MC")) stat("Location:", "([x], [y], [z])") @@ -599,6 +598,8 @@ for(var/datum/controller/subsystem/SS in Master.subsystems) SS.stat_entry() GLOB.cameranet.stat_entry() + if(statpanel("Tickets")) + GLOB.ahelp_tickets.stat_entry() if(listed_turf && client) if(!TurfAdjacent(listed_turf)) diff --git a/code/orphaned_procs/dbcore.dm b/code/orphaned_procs/dbcore.dm deleted file mode 100644 index e1758204bd..0000000000 --- a/code/orphaned_procs/dbcore.dm +++ /dev/null @@ -1,243 +0,0 @@ -#define FAILED_DB_CONNECTION_CUTOFF 5 - -//cursors -#define Default_Cursor 0 -#define Client_Cursor 1 -#define Server_Cursor 2 -//conversions -#define TEXT_CONV 1 -#define RSC_FILE_CONV 2 -#define NUMBER_CONV 3 -//column flag values: -#define IS_NUMERIC 1 -#define IS_BINARY 2 -#define IS_NOT_NULL 4 -#define IS_PRIMARY_KEY 8 -#define IS_UNSIGNED 16 -//types -#define TINYINT 1 -#define SMALLINT 2 -#define MEDIUMINT 3 -#define INTEGER 4 -#define BIGINT 5 -#define DECIMAL 6 -#define FLOAT 7 -#define DOUBLE 8 -#define DATE 9 -#define DATETIME 10 -#define TIMESTAMP 11 -#define TIME 12 -#define STRING 13 -#define BLOB 14 -// TODO: Investigate more recent type additions and see if I can handle them. - Nadrew - - -// Deprecated! See global.dm for new configuration vars -/* - var/DB_SERVER = "" // This is the location of your MySQL server (localhost is USUALLY fine) - var/DB_PORT = 3306 // This is the port your MySQL server is running on (3306 is the default) -*/ - -DBConnection - var/_db_con // This variable contains a reference to the actual database connection. - var/dbi // This variable is a string containing the DBI MySQL requires. - var/user // This variable contains the username data. - var/password // This variable contains the password data. - var/default_cursor // This contains the default database cursor data. - // - var/server = "" - var/port = 3306 - var/failed_connections = 0 - -DBConnection/New(dbi_handler,username,password_handler,cursor_handler) - src.dbi = dbi_handler - src.user = username - src.password = password_handler - src.default_cursor = cursor_handler - _db_con = _dm_db_new_con() - -DBConnection/proc/Connect() - if(IsConnected()) - return TRUE - - if(failed_connections > FAILED_DB_CONNECTION_CUTOFF) //If it failed to establish a connection more than 5 times in a row, don't bother attempting to connect anymore. - return FALSE - - var/user = global.sqlfdbklogin - var/pass = global.sqlfdbkpass - var/db = global.sqlfdbkdb - var/address = global.sqladdress - var/port = global.sqlport - - doConnect("dbi:mysql:[db]:[address]:[port]","[user]","[pass]") - . = IsConnected() - if (!. && config.sql_enabled) - log_sql("Connect() failed | [ErrorMsg()]") - ++failed_connections - -DBConnection/proc/doConnect(dbi_handler=src.dbi,user_handler=src.user,password_handler=src.password,cursor_handler) - if(!config.sql_enabled) - return 0 - if(!src) return 0 - cursor_handler = src.default_cursor - if(!cursor_handler) cursor_handler = Default_Cursor - return _dm_db_connect(_db_con,dbi_handler,user_handler,password_handler,cursor_handler,null) - -DBConnection/proc/Disconnect() - failed_connections = 0 - return _dm_db_close(_db_con) - -DBConnection/proc/IsConnected() - if(!config.sql_enabled) return 0 - var/success = _dm_db_is_connected(_db_con) - return success - -DBConnection/proc/Quote(str) return _dm_db_quote(_db_con,str) - -DBConnection/proc/ErrorMsg() return _dm_db_error_msg(_db_con) -DBConnection/proc/SelectDB(database_name,dbi) - if(IsConnected()) Disconnect() - //return Connect("[dbi?"[dbi]":"dbi:mysql:[database_name]:[DB_SERVER]:[DB_PORT]"]",user,password) - return Connect("[dbi?"[dbi]":"dbi:mysql:[database_name]:[global.sqladdress]:[global.sqlport]"]",user,password) -DBConnection/proc/NewQuery(sql_query,cursor_handler=src.default_cursor) - if(IsAdminAdvancedProcCall()) - log_admin_private("WARNING: Advanced admin proc call DB query created!: [sql_query]") - return new/DBQuery(sql_query,src,cursor_handler) - - -DBQuery/New(sql_query,DBConnection/connection_handler,cursor_handler) - if(sql_query) src.sql = sql_query - if(connection_handler) src.db_connection = connection_handler - if(cursor_handler) src.default_cursor = cursor_handler - _db_query = _dm_db_new_query() - return ..() - - -DBQuery - var/sql // The sql query being executed. - var/default_cursor - var/list/columns //list of DB Columns populated by Columns() - var/list/conversions - var/list/item[0] //list of data values populated by NextRow() - - var/DBConnection/db_connection - var/_db_query - -DBQuery/proc/Connect(DBConnection/connection_handler) src.db_connection = connection_handler - -DBQuery/proc/warn_execute() - . = Execute() - if(!.) - to_chat(usr, "A SQL error occured during this operation, check the server logs.") - -DBQuery/proc/Execute(sql_query=src.sql,cursor_handler=default_cursor, log_error = 1) - Close() - . = _dm_db_execute(_db_query,sql_query,db_connection._db_con,cursor_handler,null) - if(!. && log_error) - log_sql("[ErrorMsg()] | Query used: [sql]") - -DBQuery/proc/NextRow() return _dm_db_next_row(_db_query,item,conversions) - -DBQuery/proc/RowsAffected() return _dm_db_rows_affected(_db_query) - -DBQuery/proc/RowCount() return _dm_db_row_count(_db_query) - -DBQuery/proc/ErrorMsg() return _dm_db_error_msg(_db_query) - -DBQuery/proc/Columns() - if(!columns) - columns = _dm_db_columns(_db_query,/DBColumn) - return columns - -DBQuery/proc/GetRowData() - var/list/columns = Columns() - var/list/results - if(columns.len) - results = list() - for(var/C in columns) - results+=C - var/DBColumn/cur_col = columns[C] - results[C] = src.item[(cur_col.position+1)] - return results - -DBQuery/proc/Close() - item.len = 0 - columns = null - conversions = null - return _dm_db_close(_db_query) - -DBQuery/proc/Quote(str) - return db_connection.Quote(str) - -DBQuery/proc/SetConversion(column,conversion) - if(istext(column)) column = columns.Find(column) - if(!conversions) conversions = new/list(column) - else if(conversions.len < column) conversions.len = column - conversions[column] = conversion - - -DBColumn - var/name - var/table - var/position //1-based index into item data - var/sql_type - var/flags - var/length - var/max_length - -DBColumn/New(name_handler,table_handler,position_handler,type_handler,flag_handler,length_handler,max_length_handler) - src.name = name_handler - src.table = table_handler - src.position = position_handler - src.sql_type = type_handler - src.flags = flag_handler - src.length = length_handler - src.max_length = max_length_handler - return ..() - - -DBColumn/proc/SqlTypeName(type_handler=src.sql_type) - switch(type_handler) - if(TINYINT) return "TINYINT" - if(SMALLINT) return "SMALLINT" - if(MEDIUMINT) return "MEDIUMINT" - if(INTEGER) return "INTEGER" - if(BIGINT) return "BIGINT" - if(FLOAT) return "FLOAT" - if(DOUBLE) return "DOUBLE" - if(DATE) return "DATE" - if(DATETIME) return "DATETIME" - if(TIMESTAMP) return "TIMESTAMP" - if(TIME) return "TIME" - if(STRING) return "STRING" - if(BLOB) return "BLOB" - - -#undef Default_Cursor -#undef Client_Cursor -#undef Server_Cursor -#undef TEXT_CONV -#undef RSC_FILE_CONV -#undef NUMBER_CONV -#undef IS_NUMERIC -#undef IS_BINARY -#undef IS_NOT_NULL -#undef IS_PRIMARY_KEY -#undef IS_UNSIGNED -#undef TINYINT -#undef SMALLINT -#undef MEDIUMINT -#undef INTEGER -#undef BIGINT -#undef DECIMAL -#undef FLOAT -#undef DOUBLE -#undef DATE -#undef DATETIME -#undef TIMESTAMP -#undef TIME -#undef STRING -#undef BLOB - - -#undef FAILED_DB_CONNECTION_CUTOFF diff --git a/code/world.dm b/code/world.dm index ddc7e65771..48129098e5 100644 --- a/code/world.dm +++ b/code/world.dm @@ -212,6 +212,7 @@ /world/proc/OnReboot(reason, feedback_c, feedback_r, round_end_sound_sent) feedback_set_details("[feedback_c]","[feedback_r]") log_game("Rebooting World. [reason]") + feedback_set("ahelp_unresolved", GLOB.ahelp_tickets.active_tickets.len) if(GLOB.blackbox) GLOB.blackbox.save_all_data_to_sql() Master.Shutdown() //run SS shutdowns diff --git a/interface/stylesheet.dm b/interface/stylesheet.dm index eff529e5cd..a758818c8c 100644 --- a/interface/stylesheet.dm +++ b/interface/stylesheet.dm @@ -66,6 +66,7 @@ h1.alert, h2.alert {color: #000000;} .notice {color: #000099;} .boldnotice {color: #000099; font-weight: bold;} .adminnotice {color: #0000ff;} +.adminhelp {color: #ff0000; font-weight: bold;} .unconscious {color: #0000ff; font-weight: bold;} .suicide {color: #ff5050; font-style: italic;} .green {color: #03ff39;}