mirror of
https://github.com/VOREStation/VOREStation.git
synced 2026-08-27 06:00:23 +01:00
Ports ticket system overhaul from downstream (#17063)
* Ports ticket system overhaul from downstream * Various fixes * fixes * Update tickets.dm * Update tickets.dm * Update ticket.dm * Refactor --------- Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com>
This commit is contained in:
@@ -41,10 +41,12 @@
|
||||
#define R_MOD (1<<13)
|
||||
#define R_EVENT (1<<14)
|
||||
#define R_HOST (1<<15) //higher than this will overflow
|
||||
#define R_MENTOR (1<<16)
|
||||
|
||||
#define R_DEFAULT R_NONE
|
||||
|
||||
#define R_EVERYTHING (1<<16)-1 //the sum of all other rank permissions, used for +EVERYTHING
|
||||
#define R_EVERYTHING (1<<17)-1 //the sum of all other rank permissions, used for +EVERYTHING
|
||||
#define R_HOLDER ((R_EVERYTHING) & (~R_MENTOR))
|
||||
|
||||
#define SMITE_BREAKLEGS "Break Legs"
|
||||
#define SMITE_BLUESPACEARTILLERY "Bluespace Artillery"
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
//check if all bitflags specified are present
|
||||
#define CHECK_MULTIPLE_BITFIELDS(flagvar, flags) ((flagvar & (flags)) == flags)
|
||||
|
||||
GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768))
|
||||
GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 4194304))
|
||||
|
||||
/* Directions */
|
||||
///All the cardinal direction bitflags.
|
||||
|
||||
@@ -204,8 +204,8 @@
|
||||
|
||||
// Mentor pm filter
|
||||
#define span_mentor(str) ("<span class='mentor'>" + str + "</span>")
|
||||
#define span_mentor_pm_notice(str) ("<span class='mentor notice'>" + str + "</span>")
|
||||
#define span_mentor_pm_warning(str) ("<span class='mentor warning'>" + str + "</span>")
|
||||
#define span_mentor_notice(str) ("<span class='mentor notice'>" + str + "</span>")
|
||||
#define span_mentor_warning(str) ("<span class='mentor warning'>" + str + "</span>")
|
||||
|
||||
/* Adminchat */
|
||||
// All of those have their own filter
|
||||
|
||||
@@ -42,6 +42,7 @@ DEFINE_BITFIELD(admin_flags, list(
|
||||
"SPAWN" = R_SPAWN,
|
||||
"STEALTH" = R_STEALTH,
|
||||
"VAREDIT" = R_VAREDIT,
|
||||
"MENTOR" = R_MENTOR,
|
||||
))
|
||||
|
||||
DEFINE_BITFIELD(datum_flags, list(
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
GLOBAL_LIST_EMPTY(admins) //all clients whom are admins
|
||||
GLOBAL_PROTECT(admins)
|
||||
GLOBAL_LIST_EMPTY(mentors)
|
||||
GLOBAL_PROTECT(mentors)
|
||||
GLOBAL_LIST_EMPTY(deadmins) //all ckeys who have used the de-admin verb.
|
||||
GLOBAL_LIST_EMPTY(stealthminID)
|
||||
GLOBAL_LIST_EMPTY(directory) //all ckeys with associated client
|
||||
|
||||
@@ -213,6 +213,7 @@
|
||||
if (rights & R_SPAWN) . += "[seperator]+SPAWN"
|
||||
if (rights & R_MOD) . += "[seperator]+MODERATOR"
|
||||
if (rights & R_EVENT) . += "[seperator]+EVENT"
|
||||
if (rights & R_MENTOR) . += "[seperator]+MENTOR"
|
||||
return .
|
||||
|
||||
// Converts a hexadecimal color (e.g. #FF0050) to a list of numbers for red, green, and blue (e.g. list(255,0,80) ).
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
|
||||
//PUBLIC - call these wherever you want
|
||||
|
||||
|
||||
/mob/proc/throw_alert(category, type, severity, obj/new_master)
|
||||
|
||||
/** Proc to create or update an alert. Returns the alert if the alert is new or updated, 0 if it was thrown already
|
||||
* category is a text string. Each mob may only have one alert per category; the previous one will be replaced
|
||||
* path is a type path of the actual alert type to throw
|
||||
@@ -13,7 +10,7 @@
|
||||
* new_master is optional and sets the alert's icon state to "template" in the ui_style icons with the master as an overlay.
|
||||
* Clicks are forwarded to master
|
||||
*/
|
||||
|
||||
/mob/proc/throw_alert(category, type, severity, obj/new_master)
|
||||
if(!category)
|
||||
return
|
||||
|
||||
@@ -428,6 +425,23 @@ so as to remain in compliance with the most up-to-date laws."
|
||||
if(isliving(usr))
|
||||
var/mob/living/L = usr
|
||||
return L.resist()
|
||||
|
||||
// TICKETS
|
||||
/obj/screen/alert/open_ticket
|
||||
icon = 'icons/logo.dmi'
|
||||
name = "Admin Chat Request"
|
||||
desc = "A Administrator would like to chat with you. \
|
||||
Click here to begin."
|
||||
icon_state = "32x32"
|
||||
|
||||
/obj/screen/alert/open_ticket/Click()
|
||||
if(!usr || !usr.client) return
|
||||
|
||||
// Open a new chat with the user
|
||||
var/datum/ticket_chat/TC = new()
|
||||
TC.T = usr.client.current_ticket
|
||||
TC.tgui_interact(usr.client.mob)
|
||||
|
||||
// PRIVATE = only edit, use, or override these if you're editing the system as a whole
|
||||
|
||||
// Re-render all alerts - also called in /datum/hud/show_hud() because it's needed there
|
||||
|
||||
@@ -735,3 +735,14 @@
|
||||
|
||||
/datum/config_entry/flag/pixel_size_limit
|
||||
default = FALSE
|
||||
|
||||
/// These are for tgs4 channels, for discord chatbots used in TGS.
|
||||
/datum/config_entry/string/ahelp_channel_tag
|
||||
|
||||
/// Turn this off if you don't want the TGS bot sending you messages whenever an ahelp ticket is created.
|
||||
/datum/config_entry/flag/discord_ahelps_disabled
|
||||
default = FALSE
|
||||
|
||||
/// Turn this on if you want all admin-PMs to go to be sent to discord, and not only the first message of a ticket.
|
||||
/datum/config_entry/flag/discord_ahelps_all
|
||||
default = FALSE
|
||||
|
||||
@@ -63,7 +63,9 @@ SUBSYSTEM_DEF(statpanels)
|
||||
//target.stat_panel.send_message("update_split_admin_tabs", !!(target.prefs.toggles & SPLIT_ADMIN_TABS))
|
||||
target.stat_panel.send_message("update_split_admin_tabs", FALSE)
|
||||
|
||||
if(!("MC" in target.panel_tabs) || !("Tickets" in target.panel_tabs))
|
||||
if(check_rights_for(target, R_MENTOR))
|
||||
target.stat_panel.send_message("add_tickets_tabs", target.holder.href_token)
|
||||
else if(!("MC" in target.panel_tabs) || !("Tickets" in target.panel_tabs))
|
||||
target.stat_panel.send_message("add_admin_tabs", target.holder.href_token)
|
||||
|
||||
//if(target.stat_tab == "MC" && ((num_fires % mc_wait == 0) || target?.prefs.read_preference(/datum/preference/toggle/fast_mc_refresh)))
|
||||
@@ -178,9 +180,8 @@ SUBSYSTEM_DEF(statpanels)
|
||||
|
||||
/datum/controller/subsystem/statpanels/proc/set_tickets_tab(client/target)
|
||||
var/list/tickets = list()
|
||||
if(check_rights_for(target, R_ADMIN|R_SERVER|R_MOD)) //Prevents non-staff from opening the list of ahelp tickets
|
||||
tickets += GLOB.ahelp_tickets.stat_entry(target)
|
||||
tickets += GLOB.mhelp_tickets.stat_entry(target)
|
||||
if(check_rights_for(target, R_ADMIN|R_SERVER|R_MOD|R_MENTOR)) //Prevents non-staff from opening the list of ahelp tickets
|
||||
tickets = GLOB.tickets.stat_entry(target)
|
||||
target.stat_panel.send_message("update_tickets", tickets)
|
||||
|
||||
/datum/controller/subsystem/statpanels/proc/set_SDQL2_tab(client/target)
|
||||
|
||||
@@ -494,7 +494,6 @@ var/world_topic_spam_protect_time = world.timeofday
|
||||
|
||||
/hook/startup/proc/loadMods()
|
||||
world.load_mods()
|
||||
world.load_mentors() // no need to write another hook.
|
||||
return 1
|
||||
|
||||
/world/proc/load_mods()
|
||||
@@ -518,40 +517,6 @@ var/world_topic_spam_protect_time = world.timeofday
|
||||
var/datum/admins/D = new /datum/admins(title, rights, ckey)
|
||||
D.associate(GLOB.directory[ckey])
|
||||
|
||||
/world/proc/load_mentors()
|
||||
if(CONFIG_GET(flag/admin_legacy_system))
|
||||
var/text = file2text("config/mentors.txt")
|
||||
if (!text)
|
||||
error("Failed to load config/mentors.txt")
|
||||
else
|
||||
var/list/lines = splittext(text, "\n")
|
||||
for(var/line in lines)
|
||||
if (!line)
|
||||
continue
|
||||
if (copytext(line, 1, 2) == ";")
|
||||
continue
|
||||
|
||||
var/ckey = copytext(line, 1, length(line)+1)
|
||||
var/datum/mentor/M = new /datum/mentor(ckey)
|
||||
M.associate(GLOB.directory[ckey])
|
||||
else
|
||||
establish_db_connection()
|
||||
if(!SSdbcore.IsConnected())
|
||||
error("Failed to connect to database in load_mentors().")
|
||||
log_misc("Failed to connect to database in load_mentors().")
|
||||
return
|
||||
|
||||
var/datum/db_query/query = SSdbcore.NewQuery("SELECT ckey, mentor FROM erro_mentor")
|
||||
query.Execute()
|
||||
while(query.NextRow())
|
||||
var/ckey = query.item[1]
|
||||
var/mentor = query.item[2]
|
||||
|
||||
if(mentor)
|
||||
var/datum/mentor/M = new /datum/mentor(ckey)
|
||||
M.associate(GLOB.directory[ckey])
|
||||
qdel(query)
|
||||
|
||||
/world/proc/update_status()
|
||||
var/s = ""
|
||||
|
||||
|
||||
@@ -81,6 +81,8 @@ GLOBAL_PROTECT(protected_ranks)
|
||||
flag = R_MOD
|
||||
if("EVENT")
|
||||
flag = R_EVENT
|
||||
if("MENTOR")
|
||||
flag = R_MENTOR
|
||||
if("@")
|
||||
if(previous_rank)
|
||||
switch(group_count)
|
||||
|
||||
@@ -1,183 +0,0 @@
|
||||
// Reports are a way to notify admins of wrongdoings that happened
|
||||
// while no admin was present. They work a bit similar to news, but
|
||||
// they can only be read by admins and moderators.
|
||||
|
||||
// a single admin report
|
||||
/datum//admin_report/var
|
||||
ID // the ID of the report
|
||||
body // the content of the report
|
||||
author // key of the author
|
||||
date // date on which this was created
|
||||
done // whether this was handled
|
||||
|
||||
offender_key // store the key of the offender
|
||||
offender_cid // store the cid of the offender
|
||||
|
||||
/datum//report_topic_handler
|
||||
Topic(href,href_list)
|
||||
..()
|
||||
var/client/C = locate(href_list["client"])
|
||||
if(href_list["action"] == "show_reports")
|
||||
C.display_admin_reports()
|
||||
else if(href_list["action"] == "remove")
|
||||
C.mark_report_done(text2num(href_list["ID"]))
|
||||
else if(href_list["action"] == "edit")
|
||||
C.edit_report(text2num(href_list["ID"]))
|
||||
|
||||
var/datum/report_topic_handler/report_topic_handler
|
||||
|
||||
world/New()
|
||||
..()
|
||||
report_topic_handler = new
|
||||
|
||||
// add a new news datums
|
||||
/proc/make_report(body, author, okey, cid)
|
||||
var/savefile/Reports = new("data/reports.sav")
|
||||
var/list/reports
|
||||
var/lastID
|
||||
|
||||
Reports["reports"] >> reports
|
||||
Reports["lastID"] >> lastID
|
||||
|
||||
if(!reports) reports = list()
|
||||
if(!lastID) lastID = 0
|
||||
|
||||
var/datum/admin_report/created = new()
|
||||
created.ID = ++lastID
|
||||
created.body = body
|
||||
created.author = author
|
||||
created.date = world.realtime
|
||||
created.done = 0
|
||||
created.offender_key = okey
|
||||
created.offender_cid = cid
|
||||
|
||||
reports.Insert(1, created)
|
||||
|
||||
Reports["reports"] << reports
|
||||
Reports["lastID"] << lastID
|
||||
|
||||
// load the reports from disk
|
||||
/proc/load_reports()
|
||||
var/savefile/Reports = new("data/reports.sav")
|
||||
var/list/reports
|
||||
|
||||
Reports["reports"] >> reports
|
||||
|
||||
if(!reports) reports = list()
|
||||
|
||||
return reports
|
||||
|
||||
// check if there are any unhandled reports
|
||||
/client/proc/unhandled_reports()
|
||||
if(!src.holder) return 0
|
||||
var/list/reports = load_reports()
|
||||
|
||||
for(var/datum/admin_report/N in reports)
|
||||
if(N.done)
|
||||
continue
|
||||
else return 1
|
||||
|
||||
return 0
|
||||
|
||||
// checks if the player has an unhandled report against him
|
||||
/client/proc/is_reported()
|
||||
var/list/reports = load_reports()
|
||||
|
||||
for(var/datum/admin_report/N in reports) if(!N.done)
|
||||
if(N.offender_key == src.key)
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
// display only the reports that haven't been handled
|
||||
/client/proc/display_admin_reports()
|
||||
set category = "Admin.Moderation"
|
||||
set name = "Display Admin Reports"
|
||||
if(!src.holder) return
|
||||
|
||||
var/list/reports = load_reports()
|
||||
|
||||
var/output = ""
|
||||
if(unhandled_reports())
|
||||
// load the list of unhandled reports
|
||||
for(var/datum/admin_report/N in reports)
|
||||
if(N.done)
|
||||
continue
|
||||
output += span_bold("Reported player:") + " [N.offender_key](CID: [N.offender_cid])<br>"
|
||||
output += span_bold("Offense:") + "[N.body]<br>"
|
||||
output += "<small>Occurred at [time2text(N.date,"MM/DD hh:mm:ss")]</small><br>"
|
||||
output += "<small>authored by <i>[N.author]</i></small><br>"
|
||||
output += " <a href='byond://?src=\ref[report_topic_handler];client=\ref[src];[HrefToken()];action=remove;ID=[N.ID]'>Flag as Handled</a>"
|
||||
if(src.key == N.author)
|
||||
output += " <a href='byond://?src=\ref[report_topic_handler];client=\ref[src];[HrefToken()];action=edit;ID=[N.ID]'>Edit</a>"
|
||||
output += "<br>"
|
||||
output += "<br>"
|
||||
else
|
||||
output += "Whoops, no reports!"
|
||||
|
||||
var/datum/browser/popup = new(src, "news", "News", 600, 400)
|
||||
popup.set_content(output)
|
||||
popup.open()
|
||||
|
||||
/client/proc/Report(mob/M as mob in world)
|
||||
set category = "Admin.Moderation"
|
||||
if(!src.holder)
|
||||
return
|
||||
|
||||
var/CID = "Unknown"
|
||||
if(M.client)
|
||||
CID = M.client.computer_id
|
||||
|
||||
var/body = tgui_input_text(src.mob, "Describe in detail what you're reporting [M] for", "Report")
|
||||
if(!body) return
|
||||
|
||||
|
||||
make_report(body, key, M.key, CID)
|
||||
|
||||
spawn(1)
|
||||
display_admin_reports()
|
||||
|
||||
/client/proc/mark_report_done(ID as num)
|
||||
if(!src.holder || src.holder.level < 0)
|
||||
return
|
||||
|
||||
var/savefile/Reports = new("data/reports.sav")
|
||||
var/list/reports
|
||||
|
||||
Reports["reports"] >> reports
|
||||
|
||||
var/datum/admin_report/found
|
||||
for(var/datum/admin_report/N in reports)
|
||||
if(N.ID == ID)
|
||||
found = N
|
||||
if(!found)
|
||||
to_chat(src, span_boldwarning("* An error occurred, sorry."))
|
||||
|
||||
found.done = 1
|
||||
|
||||
Reports["reports"] << reports
|
||||
|
||||
|
||||
/client/proc/edit_report(ID as num)
|
||||
if(!src.holder || src.holder.level < 0)
|
||||
to_chat(src, span_boldwarning("You tried to modify the news, but you're not an admin!"))
|
||||
return
|
||||
|
||||
var/savefile/Reports = new("data/reports.sav")
|
||||
var/list/reports
|
||||
|
||||
Reports["reports"] >> reports
|
||||
|
||||
var/datum/admin_report/found
|
||||
for(var/datum/admin_report/N in reports)
|
||||
if(N.ID == ID)
|
||||
found = N
|
||||
if(!found)
|
||||
to_chat(src, span_boldwarning("* An error occurred, sorry."))
|
||||
|
||||
var/body = tgui_input_text(src.mob, "Enter a body for the news", "Body", multiline = TRUE, prevent_enter = TRUE)
|
||||
if(!body) return
|
||||
|
||||
found.body = body
|
||||
|
||||
Reports["reports"] << reports
|
||||
@@ -1,25 +1,4 @@
|
||||
//admin verb groups - They can overlap if you so wish. Only one of each verb will exist in the verbs list regardless
|
||||
var/list/admin_verbs_default = list(
|
||||
// /datum/admins/proc/show_player_panel, //shows an interface for individual players, with various links (links require additional flags, //VOREStation Remove,
|
||||
// /client/proc/player_panel_new, //shows an interface for all players, with links to various panels, //VOREStation Remove,
|
||||
// /client/proc/player_panel, //VOREStation Remove,
|
||||
/client/proc/cmd_admin_say, //VOREStation Add,
|
||||
/client/proc/cmd_mod_say, //VOREStation Add,
|
||||
/client/proc/cmd_event_say, //VOREStation Add,
|
||||
/client/proc/cmd_mentor_ticket_panel,
|
||||
/client/proc/cmd_mentor_say,
|
||||
// /client/proc/hide_verbs, //hides all our adminverbs, //VOREStation Remove,
|
||||
// /client/proc/hide_most_verbs, //hides all our hideable adminverbs, //VOREStation Remove,
|
||||
// /client/proc/debug_variables, //allows us to -see- the variables of any instance in the game. +VAREDIT needed to modify, //VOREStation Remove,
|
||||
// /client/proc/mark_datum_mapview, //VOREStation Remove,
|
||||
// /client/proc/cmd_check_new_players, //allows us to see every new player, //VOREStation Remove,
|
||||
// /client/proc/check_antagonists, //shows all antags,
|
||||
// /client/proc/cmd_mod_say,
|
||||
// /client/proc/deadchat //toggles deadchat on/off,
|
||||
// /client/proc/toggle_ahelp_sound,
|
||||
/client/proc/debugstatpanel,
|
||||
)
|
||||
|
||||
var/list/admin_verbs_admin = list(
|
||||
/client/proc/toggle_vantag_hud,
|
||||
/datum/admins/proc/set_tcrystals,
|
||||
@@ -83,9 +62,6 @@ var/list/admin_verbs_admin = list(
|
||||
/datum/admins/proc/toggleoocdead, //toggles ooc on/off for everyone who is dead,
|
||||
/datum/admins/proc/togglehubvisibility, //toggles visibility on the BYOND Hub.,
|
||||
/datum/admins/proc/toggledsay, //toggles dsay on/off for everyone,
|
||||
/client/proc/cmd_admin_say, //admin-only ooc chat,
|
||||
/client/proc/cmd_mod_say,
|
||||
/client/proc/cmd_event_say,
|
||||
/datum/admins/proc/PlayerNotes,
|
||||
/datum/admins/proc/show_player_info,
|
||||
/client/proc/free_slot, //frees slot for chosen job,
|
||||
@@ -117,8 +93,6 @@ var/list/admin_verbs_admin = list(
|
||||
/datum/admins/proc/sendFax,
|
||||
/client/proc/despawn_player,
|
||||
/datum/admins/proc/view_feedback,
|
||||
/client/proc/make_mentor,
|
||||
/client/proc/unmake_mentor,
|
||||
/client/proc/delbook,
|
||||
/client/proc/toggle_spawning_with_recolour,
|
||||
/client/proc/start_vote,
|
||||
@@ -199,7 +173,6 @@ var/list/admin_verbs_server = list(
|
||||
/datum/admins/proc/delay,
|
||||
/datum/admins/proc/toggleaban,
|
||||
/datum/admins/proc/togglepersistence,
|
||||
/client/proc/cmd_mod_say,
|
||||
/client/proc/toggle_log_hrefs,
|
||||
/datum/admins/proc/immreboot,
|
||||
/client/proc/everyone_random,
|
||||
@@ -290,9 +263,6 @@ var/list/admin_verbs_possess = list(
|
||||
/proc/possess,
|
||||
/proc/release
|
||||
)
|
||||
var/list/admin_verbs_permissions = list(
|
||||
/client/proc/edit_admin_permissions
|
||||
)
|
||||
var/list/admin_verbs_rejuv = list(
|
||||
/client/proc/respawn_character
|
||||
)
|
||||
@@ -387,8 +357,6 @@ var/list/admin_verbs_mod = list(
|
||||
/client/proc/debug_variables, //allows us to -see- the variables of any instance in the game. +VAREDIT needed to modify, //VOREStation Add,
|
||||
/client/proc/mark_datum_mapview, //VOREStation Add,
|
||||
/client/proc/cmd_check_new_players, //allows us to see every new player, //VOREStation Add,
|
||||
/client/proc/cmd_mod_say,
|
||||
/client/proc/cmd_event_say,
|
||||
/datum/admins/proc/show_player_info,
|
||||
/datum/admins/proc/show_traitor_panel,
|
||||
/client/proc/player_panel_new,
|
||||
@@ -409,7 +377,6 @@ var/list/admin_verbs_mod = list(
|
||||
|
||||
var/list/admin_verbs_event_manager = list(
|
||||
/client/proc/toggle_vantag_hud,
|
||||
/client/proc/cmd_event_say,
|
||||
/client/proc/cmd_admin_pm_context,
|
||||
/client/proc/cmd_admin_pm_panel,
|
||||
/client/proc/admin_ghost,
|
||||
@@ -515,8 +482,6 @@ var/list/admin_verbs_event_manager = list(
|
||||
/client/proc/admin_memo, //admin memo system. show/delete/write. +SERVER needed to delete admin memos of others,
|
||||
/client/proc/dsay, //talk in deadchat using our ckey/fakekey,
|
||||
/client/proc/secrets,
|
||||
/client/proc/cmd_mod_say,
|
||||
/client/proc/cmd_event_say,
|
||||
/datum/admins/proc/show_player_info,
|
||||
/client/proc/free_slot, //frees slot for chosen job,
|
||||
/client/proc/cmd_admin_change_custom_event,
|
||||
@@ -543,7 +508,6 @@ var/list/admin_verbs_event_manager = list(
|
||||
/datum/admins/proc/startnow,
|
||||
/datum/admins/proc/restart,
|
||||
/datum/admins/proc/delay,
|
||||
/client/proc/cmd_mod_say,
|
||||
/datum/admins/proc/immreboot,
|
||||
/client/proc/everyone_random,
|
||||
/client/proc/cmd_admin_delete, //delete an instance/object/mob/etc,
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
/client/proc/add_admin_verbs()
|
||||
// OLD ADMIN VERB SYSTEM
|
||||
if(holder)
|
||||
var/rights = holder.rank_flags()
|
||||
add_verb(src, admin_verbs_default)
|
||||
var/rights = holder.rank_flags()
|
||||
if(rights & R_HOLDER)
|
||||
if(rights & R_BUILDMODE) add_verb(src, /client/proc/togglebuildmodeself)
|
||||
if(rights & R_ADMIN) add_verb(src, admin_verbs_admin)
|
||||
if(rights & R_FUN) add_verb(src, admin_verbs_fun)
|
||||
@@ -12,7 +11,6 @@
|
||||
if(CONFIG_GET(flag/debugparanoid) && !(rights & R_ADMIN))
|
||||
remove_verb(src, admin_verbs_paranoid_debug) //Right now it's just callproc but we can easily add others later on.
|
||||
if(rights & R_POSSESS) add_verb(src, admin_verbs_possess)
|
||||
if(rights & R_PERMISSIONS) add_verb(src, admin_verbs_permissions)
|
||||
if(rights & R_STEALTH) add_verb(src, /client/proc/stealth)
|
||||
if(rights & R_REJUVINATE) add_verb(src, admin_verbs_rejuv)
|
||||
if(rights & R_SOUNDS) add_verb(src, admin_verbs_sounds)
|
||||
@@ -26,14 +24,12 @@
|
||||
/client/proc/remove_admin_verbs()
|
||||
// OLD ADMIN VERB SYSTEM
|
||||
remove_verb(src, list(
|
||||
admin_verbs_default,
|
||||
/client/proc/togglebuildmodeself,
|
||||
admin_verbs_admin,
|
||||
admin_verbs_fun,
|
||||
admin_verbs_server,
|
||||
admin_verbs_debug,
|
||||
admin_verbs_possess,
|
||||
admin_verbs_permissions,
|
||||
/client/proc/stealth,
|
||||
admin_verbs_rejuv,
|
||||
admin_verbs_sounds,
|
||||
@@ -130,7 +126,7 @@
|
||||
set category = "Admin.Game"
|
||||
set desc = "Toggles ghost-like invisibility (Don't abuse this)"
|
||||
|
||||
if(holder && mob)
|
||||
if(check_rights(R_HOLDER) && mob)
|
||||
if(mob.invisibility > INVISIBILITY_OBSERVER)
|
||||
to_chat(mob, span_warning("You can't use this, your current invisibility level ([mob.invisibility]) is above the observer level ([INVISIBILITY_OBSERVER])."))
|
||||
return
|
||||
@@ -149,7 +145,7 @@
|
||||
/client/proc/player_panel()
|
||||
set name = "Player Panel"
|
||||
set category = "Admin.Game"
|
||||
if(holder)
|
||||
if(check_rights(R_HOLDER))
|
||||
holder.player_panel_old()
|
||||
feedback_add_details("admin_verb","PP") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
return
|
||||
@@ -157,7 +153,7 @@
|
||||
/client/proc/player_panel_new()
|
||||
set name = "Player Panel New"
|
||||
set category = "Admin.Game"
|
||||
if(holder)
|
||||
if(check_rights(R_HOLDER))
|
||||
holder.player_panel_new()
|
||||
feedback_add_details("admin_verb","PPN") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
return
|
||||
@@ -165,7 +161,7 @@
|
||||
/client/proc/check_antagonists()
|
||||
set name = "Check Antagonists"
|
||||
set category = "Admin.Investigate"
|
||||
if(holder)
|
||||
if(check_rights(R_HOLDER))
|
||||
holder.check_antagonists()
|
||||
log_admin("[key_name(usr)] checked antagonists.") //for tsar~
|
||||
feedback_add_details("admin_verb","CHA") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
@@ -192,7 +188,7 @@ ADMIN_VERB(game_panel, R_ADMIN|R_SERVER|R_FUN, "Game Panel", "Look at the state
|
||||
/client/proc/secrets()
|
||||
set name = "Secrets"
|
||||
set category = "Admin.Secrets"
|
||||
if (holder)
|
||||
if(check_rights(R_HOLDER))
|
||||
holder.Secrets()
|
||||
feedback_add_details("admin_verb","S") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
return
|
||||
@@ -219,7 +215,7 @@ ADMIN_VERB(game_panel, R_ADMIN|R_SERVER|R_FUN, "Game Panel", "Look at the state
|
||||
/client/proc/stealth()
|
||||
set category = "Admin.Game"
|
||||
set name = "Stealth Mode"
|
||||
if(holder)
|
||||
if(check_rights(R_HOLDER))
|
||||
if(holder.fakekey)
|
||||
holder.fakekey = null
|
||||
if(isnewplayer(src.mob))
|
||||
@@ -396,7 +392,7 @@ ADMIN_VERB(deadmin, R_NONE, "DeAdmin", "Shed your admin powers.", ADMIN_CATEGORY
|
||||
/client/proc/check_ai_laws()
|
||||
set name = "Check AI Laws"
|
||||
set category = "Admin.Silicon"
|
||||
if(holder)
|
||||
if(check_rights(R_HOLDER))
|
||||
src.holder.output_ai_laws()
|
||||
|
||||
/client/proc/rename_silicon()
|
||||
@@ -458,7 +454,7 @@ ADMIN_VERB(deadmin, R_NONE, "DeAdmin", "Shed your admin powers.", ADMIN_CATEGORY
|
||||
/client/proc/mod_panel()
|
||||
set name = "Moderator Panel"
|
||||
set category = "Admin.Moderation"
|
||||
/* if(holder)
|
||||
/* if(check_rights(R_HOLDER))
|
||||
holder.mod_panel()*/
|
||||
// feedback_add_details("admin_verb","MP") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
return
|
||||
@@ -466,14 +462,14 @@ ADMIN_VERB(deadmin, R_NONE, "DeAdmin", "Shed your admin powers.", ADMIN_CATEGORY
|
||||
/client/proc/playernotes()
|
||||
set name = "Show Player Info"
|
||||
set category = "Admin.Moderation"
|
||||
if(holder)
|
||||
if(check_rights(R_HOLDER))
|
||||
holder.PlayerNotes()
|
||||
return
|
||||
|
||||
/client/proc/free_slot()
|
||||
set name = "Free Job Slot"
|
||||
set category = "Admin.Game"
|
||||
if(holder)
|
||||
if(check_rights(R_HOLDER))
|
||||
var/list/jobs = list()
|
||||
for (var/datum/job/J in job_master.occupations)
|
||||
if (J.current_positions >= J.total_positions && J.total_positions != -1)
|
||||
@@ -542,11 +538,8 @@ ADMIN_VERB(deadmin, R_NONE, "DeAdmin", "Shed your admin powers.", ADMIN_CATEGORY
|
||||
log_admin("[key_name(usr)] gave [key_name(T)] the spell [S].")
|
||||
message_admins(span_blue("[key_name_admin(usr)] gave [key_name(T)] the spell [S]."), 1)
|
||||
|
||||
/client/proc/debugstatpanel()
|
||||
set name = "Debug Stat Panel"
|
||||
set category = "Debug.Misc"
|
||||
|
||||
src.stat_panel.send_message("create_debug")
|
||||
ADMIN_VERB(debugstatpanel, R_DEBUG, "Debug Stat Panel", "Allows to debug the statpanel", "Debug.Misc")
|
||||
user.stat_panel.send_message("create_debug")
|
||||
|
||||
/client/proc/spawn_reagent()
|
||||
set name = "Spawn Reagent"
|
||||
@@ -618,7 +611,7 @@ ADMIN_VERB(deadmin, R_NONE, "DeAdmin", "Shed your admin powers.", ADMIN_CATEGORY
|
||||
var/atom/movable/orbiter
|
||||
var/input
|
||||
|
||||
if(holder.marked_datum)
|
||||
if(check_rights(R_HOLDER) && holder.marked_datum)
|
||||
input = tgui_alert(usr, "You have \n[holder.marked_datum] marked, should this be the center of the orbit, or the orbiter?", "Orbit", list("Center", "Orbiter", "Neither"))
|
||||
switch(input)
|
||||
if("Center")
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
/client/proc/edit_admin_permissions()
|
||||
set category = "Admin.Secrets"
|
||||
set name = "Permissions Panel"
|
||||
set desc = "Edit admin permissions"
|
||||
|
||||
if(!check_rights(R_PERMISSIONS))
|
||||
return
|
||||
|
||||
usr.client.holder.edit_admin_permissions()
|
||||
ADMIN_VERB(edit_admin_permissions, R_PERMISSIONS, "Permissions Panel", "Edit admin permissions.", "Admin.Secrets")
|
||||
user.holder.edit_admin_permissions()
|
||||
|
||||
/datum/admins/proc/edit_admin_permissions(action, target, operation, page)
|
||||
if(!check_rights(R_PERMISSIONS))
|
||||
|
||||
+16
-16
@@ -29,21 +29,21 @@
|
||||
check_antagonists()
|
||||
return
|
||||
|
||||
if(href_list["ahelp"])
|
||||
if(!check_rights(R_ADMIN|R_MOD|R_DEBUG|R_EVENT))
|
||||
if(href_list["ticket"])
|
||||
if(!check_rights(R_ADMIN|R_MOD|R_DEBUG|R_EVENT|R_MENTOR))
|
||||
return
|
||||
|
||||
var/ahelp_ref = href_list["ahelp"]
|
||||
var/datum/admin_help/AH = locate(ahelp_ref)
|
||||
if(AH)
|
||||
AH.Action(href_list["ahelp_action"])
|
||||
var/ticket_ref = href_list["ticket"]
|
||||
var/datum/ticket/T = locate(ticket_ref)
|
||||
if(T)
|
||||
T.Action(href_list["ticket_action"])
|
||||
else
|
||||
to_chat(usr, "Ticket [ahelp_ref] has been deleted!")
|
||||
to_chat(usr, "Ticket [ticket_ref] has been deleted!")
|
||||
|
||||
else if(href_list["ahelp_tickets"])
|
||||
GLOB.ahelp_tickets.BrowseTickets(text2num(href_list["ahelp_tickets"]))
|
||||
else if(href_list["tickets"])
|
||||
GLOB.tickets.BrowseTickets(text2num(href_list["tickets"]))
|
||||
|
||||
mentor_commands(href, href_list, src)
|
||||
// mentor_commands(href, href_list, src) - Skip because client is already admin & contents handled above
|
||||
|
||||
if(href_list["dbsearchckey"] || href_list["dbsearchadmin"])
|
||||
|
||||
@@ -865,9 +865,9 @@
|
||||
to_chat(M, span_filter_system(span_warning("No ban appeals URL has been set.")))
|
||||
log_admin("[usr.client.ckey] has banned [M.ckey].\nReason: [reason]\nThis will be removed in [mins] minutes.")
|
||||
message_admins(span_blue("[usr.client.ckey] has banned [M.ckey].\nReason: [reason]\nThis will be removed in [mins] minutes."))
|
||||
var/datum/admin_help/AH = M.client ? M.client.current_ticket : null
|
||||
if(AH)
|
||||
AH.Resolve()
|
||||
var/datum/ticket/T = M.client ? M.client.current_ticket : null
|
||||
if(T)
|
||||
T.Resolve()
|
||||
qdel(M.client)
|
||||
//qdel(M) // See no reason why to delete mob. Important stuff can be lost. And ban can be lifted before round ends.
|
||||
if("No")
|
||||
@@ -893,9 +893,9 @@
|
||||
message_admins(span_blue("[usr.client.ckey] has banned [M.ckey].\nReason: [reason]\nThis is a permanent ban."))
|
||||
feedback_inc("ban_perma",1)
|
||||
DB_ban_record(BANTYPE_PERMA, M, -1, reason)
|
||||
var/datum/admin_help/AH = M.client ? M.client.current_ticket : null
|
||||
if(AH)
|
||||
AH.Resolve()
|
||||
var/datum/ticket/T = M.client ? M.client.current_ticket : null
|
||||
if(T)
|
||||
T.Resolve()
|
||||
qdel(M.client)
|
||||
//qdel(M)
|
||||
if("Cancel")
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
/datum/admin_help/proc/send2adminchat()
|
||||
if(!CONFIG_GET(string/chat_webhook_url))
|
||||
return
|
||||
|
||||
var/list/adm = get_admin_counts()
|
||||
var/list/afkmins = adm["afk"]
|
||||
var/list/allmins = adm["total"]
|
||||
|
||||
spawn(0) //Unreliable world.Exports()
|
||||
var/query_string = "type=adminhelp"
|
||||
query_string += "&key=[url_encode(CONFIG_GET(string/chat_webhook_key))]"
|
||||
query_string += "&from=[url_encode(key_name(initiator))]"
|
||||
query_string += "&msg=[url_encode(html_decode(name))]"
|
||||
query_string += "&admin_number=[allmins.len]"
|
||||
query_string += "&admin_number_afk=[afkmins.len]"
|
||||
world.Export("[CONFIG_GET(string/chat_webhook_url)]?[query_string]")
|
||||
|
||||
/client/verb/adminspice()
|
||||
set category = "Admin"
|
||||
set name = "Request Spice"
|
||||
set desc = "Request admins to spice round up for you"
|
||||
|
||||
//handle muting and automuting
|
||||
if(prefs.muted & MUTE_ADMINHELP)
|
||||
to_chat(usr, span_danger("Error: You cannot request spice (muted from adminhelps)."))
|
||||
return
|
||||
|
||||
if(tgui_alert(usr, "Are you sure you want to request the admins spice things up for you? You accept the consequences if you do.","Spicy!",list("Yes","No")) == "Yes")
|
||||
message_admins("[ADMIN_FULLMONTY(usr)] has requested the round be spiced up a little.")
|
||||
to_chat(usr, span_notice("You have requested some more spice in your round."))
|
||||
else
|
||||
to_chat(usr, span_notice("Spice request cancelled."))
|
||||
return
|
||||
|
||||
//if they requested spice, then remove spice verb temporarily to prevent spamming
|
||||
remove_verb(usr, /client/verb/adminspice)
|
||||
spawn(10 MINUTES)
|
||||
if(usr) // In case we left in the 10 minute cooldown
|
||||
add_verb(usr, /client/verb/adminspice) // 10 minute cool-down for spice request
|
||||
@@ -53,19 +53,19 @@
|
||||
to_chat(src, span_admin_pm_warning("Error: Admin-PM: Client not found."))
|
||||
return
|
||||
|
||||
var/datum/admin_help/AH = C.current_ticket
|
||||
var/datum/ticket/T = C.current_ticket
|
||||
|
||||
if(AH)
|
||||
if(T)
|
||||
message_admins(span_pm("[key_name_admin(src)] has started replying to [key_name(C, 0, 0)]'s admin help."))
|
||||
var/msg = tgui_input_text(src,"Message:", "Private message to [key_name(C, 0, 0)]", multiline = TRUE)
|
||||
if (!msg)
|
||||
message_admins(span_pm("[key_name_admin(src)] has cancelled their reply to [key_name(C, 0, 0)]'s admin help."))
|
||||
return
|
||||
cmd_admin_pm(whom, msg, AH)
|
||||
cmd_admin_pm(whom, msg, T)
|
||||
|
||||
//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, datum/admin_help/AH)
|
||||
/client/proc/cmd_admin_pm(whom, msg, datum/ticket/T)
|
||||
if(prefs.muted & MUTE_ADMINHELP)
|
||||
to_chat(src, span_admin_pm_warning("Error: Admin-PM: You are unable to use admin PM-s (muted)."))
|
||||
return
|
||||
@@ -171,7 +171,7 @@
|
||||
else
|
||||
if(holder) //sender is an admin but recipient is not. Do BIG RED TEXT
|
||||
if(!recipient.current_ticket)
|
||||
new /datum/admin_help(msg, recipient, TRUE)
|
||||
new /datum/ticket(msg, recipient, TRUE, 1)
|
||||
|
||||
to_chat(recipient, span_admin_pm_warning(span_huge(span_bold("-- Administrator private message --"))))
|
||||
to_chat(recipient, span_admin_pm_warning("Admin PM from-<b>[key_name(src, recipient, 0)]</b>: [msg]"))
|
||||
@@ -218,7 +218,7 @@
|
||||
/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/datum/ticket/ticket = C ? C.current_ticket : GLOB.tickets.CKey2ActiveTicket(target)
|
||||
var/compliant_msg = trim(lowertext(msg))
|
||||
var/irc_tagged = "[sender](IRC)"
|
||||
var/list/splits = splittext(compliant_msg, " ")
|
||||
|
||||
@@ -1,63 +1,53 @@
|
||||
/client/proc/cmd_admin_say(msg as text)
|
||||
set category = "Admin.Chat"
|
||||
set name = "Asay" //Gave this shit a shorter name so you only have to time out "asay" rather than "admin say" to use it --NeoFite
|
||||
set hidden = 1
|
||||
if(!check_rights(R_ADMIN)) //VOREStation Edit
|
||||
return
|
||||
|
||||
msg = sanitize(msg)
|
||||
ADMIN_VERB(cmd_admin_say, R_ADMIN, "ASay", "Send a message to other admins", "Admin.Chat", message as text)
|
||||
var/msg = sanitize(message)
|
||||
if(!msg)
|
||||
return
|
||||
|
||||
log_adminsay(msg,src)
|
||||
log_adminsay(msg, user)
|
||||
|
||||
for(var/client/C in GLOB.admins)
|
||||
if(check_rights_for(C, R_ADMIN))
|
||||
to_chat(C, span_admin_channel(create_text_tag("admin", "ADMIN:", C) + " " + span_name("[key_name(usr, 1)]") + "([admin_jump_link(mob, src)]): " + span_name("[msg]") ))
|
||||
to_chat(C, span_admin_channel(create_text_tag("admin", "ADMIN:", C) + " " + span_name("[key_name(user, 1)]") + "([admin_jump_link(user.mob, C.holder)]): " + span_name("[msg]") ))
|
||||
|
||||
feedback_add_details("admin_verb","M") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
/client/proc/cmd_mod_say(msg as text)
|
||||
set category = "Admin.Chat"
|
||||
set name = "Msay"
|
||||
set hidden = 1
|
||||
|
||||
if(!check_rights(R_ADMIN|R_MOD|R_SERVER)) //VOREStation Edit
|
||||
return
|
||||
|
||||
msg = sanitize(msg)
|
||||
log_modsay(msg,src)
|
||||
ADMIN_VERB(cmd_mod_say, (R_ADMIN|R_MOD|R_SERVER), "Msay", "Send a message to other mod", "Admin.Chat", message as text)
|
||||
var/msg = sanitize(message)
|
||||
log_modsay(msg, user)
|
||||
|
||||
if (!msg)
|
||||
return
|
||||
|
||||
var/sender_name = key_name(usr, 1)
|
||||
var/sender_name = key_name(user, 1)
|
||||
if(check_rights(R_ADMIN, 0))
|
||||
sender_name = span_admin("[sender_name]")
|
||||
for(var/client/C in GLOB.admins)
|
||||
if(check_rights(R_ADMIN|R_MOD|R_SERVER)) //VOREStation Edit
|
||||
to_chat(C, span_mod_channel(create_text_tag("mod", "MOD:", C) + " " + span_name("[sender_name]") + "([admin_jump_link(mob, C.holder)]): " + span_name("[msg]") ))
|
||||
to_chat(C, span_mod_channel(create_text_tag("mod", "MOD:", C) + " " + span_name("[sender_name]") + "([admin_jump_link(user.mob, C.holder)]): " + span_name("[msg]") ))
|
||||
|
||||
feedback_add_details("admin_verb","MS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
/client/proc/cmd_event_say(msg as text)
|
||||
set category = "Admin.Chat"
|
||||
set name = "Esay"
|
||||
set hidden = 1
|
||||
|
||||
if(!check_rights(R_ADMIN|R_MOD|R_EVENT|R_SERVER)) //VOREStation Edit
|
||||
return
|
||||
|
||||
msg = sanitize(msg)
|
||||
log_eventsay(msg,src)
|
||||
ADMIN_VERB(cmd_event_say, (R_ADMIN|R_MOD|R_EVENT|R_SERVER), "Esay", "Send a message to other event manager", "Admin.Chat", message as text)
|
||||
var/msg = sanitize(message)
|
||||
log_eventsay(msg, user)
|
||||
|
||||
if (!msg)
|
||||
return
|
||||
|
||||
var/sender_name = key_name(usr, 1)
|
||||
var/sender_name = key_name(user, 1)
|
||||
if(check_rights(R_ADMIN, 0))
|
||||
sender_name = span_admin("[sender_name]")
|
||||
for(var/client/C in GLOB.admins)
|
||||
to_chat(C, span_event_channel(create_text_tag("event", "EVENT:", C) + " " + span_name("[sender_name]") + "([admin_jump_link(mob, C.holder)]): " + span_name("[msg]") ))
|
||||
to_chat(C, span_event_channel(create_text_tag("event", "EVENT:", C) + " " + span_name("[sender_name]") + "([admin_jump_link(user.mob, C.holder)]): " + span_name("[msg]") ))
|
||||
|
||||
feedback_add_details("admin_verb","GS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
ADMIN_VERB(cmd_mentor_say, (R_ADMIN|R_MOD|R_EVENT|R_SERVER|R_MENTOR), "Mentorsay", "Send a message to other mentors", "Admin.Chat", message as text)
|
||||
var/msg = sanitize(message)
|
||||
if (!msg)
|
||||
return
|
||||
|
||||
log_admin("Mentorsay: [key_name(user)]: [msg]")
|
||||
|
||||
for(var/client/C in GLOB.admins)
|
||||
to_chat(C, create_text_tag("mentor", "MENTOR:", C) + " " + span_mentor_channel(span_name("[user]") + ": " + span_message("[msg]")))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//DO NOT ADD MORE TO THIS FILE.
|
||||
//Use vv_do_topic()!
|
||||
/client/proc/view_var_Topic(href, href_list, hsrc)
|
||||
if((usr.client != src) || !src.holder)
|
||||
if((usr.client != src) || check_rights(R_HOLDER))
|
||||
return
|
||||
var/datum/target = locate(href_list["target"])
|
||||
if(istype(target))
|
||||
@@ -18,10 +18,10 @@
|
||||
|
||||
var/mob/M = locate(href_list["rename"])
|
||||
if(!istype(M))
|
||||
to_chat(usr, "This can only be used on instances of type /mob")
|
||||
to_chat(src, "This can only be used on instances of type /mob")
|
||||
return
|
||||
|
||||
var/new_name = sanitize(tgui_input_text(usr,"What would you like to name this mob?","Input a name",M.real_name,MAX_NAME_LEN), MAX_NAME_LEN)
|
||||
var/new_name = sanitize(tgui_input_text(src,"What would you like to name this mob?","Input a name",M.real_name,MAX_NAME_LEN), MAX_NAME_LEN)
|
||||
if( !new_name || !M ) return
|
||||
|
||||
message_admins("Admin [key_name_admin(usr)] renamed [key_name_admin(M)] to [new_name].")
|
||||
@@ -33,7 +33,7 @@
|
||||
|
||||
var/D = locate(href_list["datumedit"])
|
||||
if(!istype(D,/datum) && !istype(D,/client))
|
||||
to_chat(usr, "This can only be used on instances of types /client or /datum")
|
||||
to_chat(src, "This can only be used on instances of types /client or /datum")
|
||||
return
|
||||
|
||||
modify_variables(D, href_list["varnameedit"], 1)
|
||||
@@ -43,7 +43,7 @@
|
||||
|
||||
var/D = locate(href_list["datumchange"])
|
||||
if(!istype(D,/datum) && !istype(D,/client))
|
||||
to_chat(usr, "This can only be used on instances of types /client or /datum")
|
||||
to_chat(src, "This can only be used on instances of types /client or /datum")
|
||||
return
|
||||
|
||||
modify_variables(D, href_list["varnamechange"], 0)
|
||||
@@ -53,7 +53,7 @@
|
||||
|
||||
var/atom/A = locate(href_list["datummass"])
|
||||
if(!istype(A))
|
||||
to_chat(usr, "This can only be used on instances of type /atom")
|
||||
to_chat(src, "This can only be used on instances of type /atom")
|
||||
return
|
||||
|
||||
cmd_mass_modify_object_variables(A, href_list["varnamemass"])
|
||||
@@ -63,7 +63,7 @@
|
||||
|
||||
var/mob/M = locate(href_list["mob_player_panel"])
|
||||
if(!istype(M))
|
||||
to_chat(usr, "This can only be used on instances of type /mob")
|
||||
to_chat(src, "This can only be used on instances of type /mob")
|
||||
return
|
||||
|
||||
src.holder.show_player_panel(M)
|
||||
@@ -74,7 +74,7 @@
|
||||
|
||||
var/mob/M = locate(href_list["give_spell"])
|
||||
if(!istype(M))
|
||||
to_chat(usr, "This can only be used on instances of type /mob")
|
||||
to_chat(src, "This can only be used on instances of type /mob")
|
||||
return
|
||||
|
||||
src.give_spell(M)
|
||||
@@ -86,7 +86,7 @@
|
||||
|
||||
var/mob/living/M = locate(href_list["give_modifier"])
|
||||
if(!istype(M))
|
||||
to_chat(usr, "This can only be used on instances of type /mob/living")
|
||||
to_chat(src, "This can only be used on instances of type /mob/living")
|
||||
return
|
||||
|
||||
src.admin_give_modifier(M)
|
||||
@@ -98,22 +98,22 @@
|
||||
|
||||
var/mob/living/carbon/human/H = locate(href_list["give_wound_internal"])
|
||||
if(!istype(H))
|
||||
to_chat(usr, span_notice("This can only be used on instances of type /mob/living/carbon/human"))
|
||||
to_chat(src, span_notice("This can only be used on instances of type /mob/living/carbon/human"))
|
||||
return
|
||||
|
||||
var/severity = tgui_input_number(usr, "How much damage should the bleeding internal wound cause? \
|
||||
var/severity = tgui_input_number(src, "How much damage should the bleeding internal wound cause? \
|
||||
Bleed timer directly correlates with this. 0 cancels. Input is rounded to nearest integer.",
|
||||
"Wound Severity", 0)
|
||||
if(!severity) return
|
||||
|
||||
var/obj/item/organ/external/chosen_organ = tgui_input_list(usr, "Choose an external organ to inflict IB on!", "Organ Choice", H.organs)
|
||||
var/obj/item/organ/external/chosen_organ = tgui_input_list(src, "Choose an external organ to inflict IB on!", "Organ Choice", H.organs)
|
||||
if(!chosen_organ || !istype(chosen_organ))
|
||||
to_chat(usr, span_notice("The chosen organ is of inappropriate type or no longer exists."))
|
||||
return
|
||||
|
||||
var/datum/wound/internal_bleeding/I = new /datum/wound/internal_bleeding(severity)
|
||||
if(!I || !istype(I))
|
||||
to_chat(usr, span_notice("Could not initialize internal wound"))
|
||||
to_chat(src, span_notice("Could not initialize internal wound"))
|
||||
log_debug("[usr] attempted to create an internal bleeding wound on [H]'s [chosen_organ] of [severity] damage \
|
||||
and wound initialization failed")
|
||||
|
||||
@@ -134,7 +134,7 @@
|
||||
|
||||
var/mob/M = locate(href_list["godmode"])
|
||||
if(!istype(M))
|
||||
to_chat(usr, "This can only be used on instances of type /mob")
|
||||
to_chat(src, "This can only be used on instances of type /mob")
|
||||
return
|
||||
|
||||
src.cmd_admin_godmode(M)
|
||||
@@ -145,7 +145,7 @@
|
||||
|
||||
var/mob/M = locate(href_list["gib"])
|
||||
if(!istype(M))
|
||||
to_chat(usr, "This can only be used on instances of type /mob")
|
||||
to_chat(src, "This can only be used on instances of type /mob")
|
||||
return
|
||||
|
||||
src.cmd_admin_gib(M)
|
||||
@@ -155,7 +155,7 @@
|
||||
|
||||
var/mob/M = locate(href_list["build_mode"])
|
||||
if(!istype(M))
|
||||
to_chat(usr, "This can only be used on instances of type /mob")
|
||||
to_chat(src, "This can only be used on instances of type /mob")
|
||||
return
|
||||
|
||||
togglebuildmode(M)
|
||||
@@ -166,7 +166,7 @@
|
||||
|
||||
var/mob/M = locate(href_list["drop_everything"])
|
||||
if(!istype(M))
|
||||
to_chat(usr, "This can only be used on instances of type /mob")
|
||||
to_chat(src, "This can only be used on instances of type /mob")
|
||||
return
|
||||
|
||||
if(usr.client)
|
||||
@@ -177,7 +177,7 @@
|
||||
|
||||
var/mob/M = locate(href_list["direct_control"])
|
||||
if(!istype(M))
|
||||
to_chat(usr, "This can only be used on instances of type /mob")
|
||||
to_chat(src, "This can only be used on instances of type /mob")
|
||||
return
|
||||
|
||||
if(usr.client)
|
||||
@@ -188,22 +188,22 @@
|
||||
|
||||
var/mob/M = locate(href_list["give_ai"])
|
||||
if(!isliving(M))
|
||||
to_chat(usr, span_notice("This can only be used on instances of type /mob/living"))
|
||||
to_chat(src, span_notice("This can only be used on instances of type /mob/living"))
|
||||
return
|
||||
var/mob/living/L = M
|
||||
if(L.client || L.teleop)
|
||||
to_chat(usr, span_warning("This cannot be used on player mobs!"))
|
||||
to_chat(src, span_warning("This cannot be used on player mobs!"))
|
||||
return
|
||||
|
||||
if(L.ai_holder) //Cleaning up the original ai
|
||||
var/ai_holder_old = L.ai_holder
|
||||
L.ai_holder = null
|
||||
qdel(ai_holder_old) //Only way I could make #TESTING - Unable to be GC'd to stop. del() logs show it works.
|
||||
L.ai_holder_type = tgui_input_list(usr, "Choose AI holder", "AI Type", typesof(/datum/ai_holder/))
|
||||
L.ai_holder_type = tgui_input_list(src, "Choose AI holder", "AI Type", typesof(/datum/ai_holder/))
|
||||
L.initialize_ai_holder()
|
||||
L.faction = sanitize(tgui_input_text(usr, "Please input AI faction", "AI faction", "neutral"))
|
||||
L.a_intent = tgui_input_list(usr, "Please choose AI intent", "AI intent", list(I_HURT, I_HELP))
|
||||
if(tgui_alert(usr, "Make mob wake up? This is needed for carbon mobs.", "Wake mob?", list("Yes", "No")) == "Yes")
|
||||
L.faction = sanitize(tgui_input_text(src, "Please input AI faction", "AI faction", "neutral"))
|
||||
L.a_intent = tgui_input_list(src, "Please choose AI intent", "AI intent", list(I_HURT, I_HELP))
|
||||
if(tgui_alert(src, "Make mob wake up? This is needed for carbon mobs.", "Wake mob?", list("Yes", "No")) == "Yes")
|
||||
L.AdjustSleeping(-100)
|
||||
|
||||
else if(href_list["make_skeleton"])
|
||||
@@ -211,7 +211,7 @@
|
||||
|
||||
var/mob/living/carbon/human/H = locate(href_list["make_skeleton"])
|
||||
if(!istype(H))
|
||||
to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human")
|
||||
to_chat(src, "This can only be used on instances of type /mob/living/carbon/human")
|
||||
return
|
||||
|
||||
H.ChangeToSkeleton()
|
||||
@@ -223,17 +223,17 @@
|
||||
|
||||
var/obj/O = locate(href_list["delall"])
|
||||
if(!isobj(O))
|
||||
to_chat(usr, "This can only be used on instances of type /obj")
|
||||
to_chat(src, "This can only be used on instances of type /obj")
|
||||
return
|
||||
|
||||
var/action_type = tgui_alert(usr, "Strict type ([O.type]) or type and all subtypes?","Type Selection",list("Strict type","Type and subtypes","Cancel"))
|
||||
var/action_type = tgui_alert(src, "Strict type ([O.type]) or type and all subtypes?","Type Selection",list("Strict type","Type and subtypes","Cancel"))
|
||||
if(action_type == "Cancel" || !action_type)
|
||||
return
|
||||
|
||||
if(tgui_alert(usr, "Are you really sure you want to delete all objects of type [O.type]?","Delete All?",list("Yes","No")) != "Yes")
|
||||
if(tgui_alert(src, "Are you really sure you want to delete all objects of type [O.type]?","Delete All?",list("Yes","No")) != "Yes")
|
||||
return
|
||||
|
||||
if(tgui_alert(usr, "Second confirmation required. Delete?","REALLY?",list("Yes","No")) != "Yes")
|
||||
if(tgui_alert(src, "Second confirmation required. Delete?","REALLY?",list("Yes","No")) != "Yes")
|
||||
return
|
||||
|
||||
var/O_type = O.type
|
||||
@@ -246,7 +246,7 @@
|
||||
qdel(Obj)
|
||||
CHECK_TICK
|
||||
if(!i)
|
||||
to_chat(usr, "No objects of this type exist")
|
||||
to_chat(src, "No objects of this type exist")
|
||||
return
|
||||
log_admin("[key_name(usr)] deleted all objects of type [O_type] ([i] objects deleted) ")
|
||||
message_admins(span_notice("[key_name(usr)] deleted all objects of type [O_type] ([i] objects deleted) "))
|
||||
@@ -258,7 +258,7 @@
|
||||
qdel(Obj)
|
||||
CHECK_TICK
|
||||
if(!i)
|
||||
to_chat(usr, "No objects of this type exist")
|
||||
to_chat(src, "No objects of this type exist")
|
||||
return
|
||||
log_admin("[key_name(usr)] deleted all objects of type or subtype of [O_type] ([i] objects deleted) ")
|
||||
message_admins(span_notice("[key_name(usr)] deleted all objects of type or subtype of [O_type] ([i] objects deleted) "))
|
||||
@@ -267,7 +267,7 @@
|
||||
|
||||
var/obj/item/pda/P = locate(href_list["fakepdapropconvo"])
|
||||
if(!istype(P))
|
||||
to_chat(usr, span_warning("This can only be done to instances of type /pda"))
|
||||
to_chat(src, span_warning("This can only be done to instances of type /pda"))
|
||||
return
|
||||
|
||||
P.createPropFakeConversation_admin(usr)
|
||||
@@ -277,7 +277,7 @@
|
||||
|
||||
var/atom/A = locate(href_list["rotatedatum"])
|
||||
if(!istype(A))
|
||||
to_chat(usr, "This can only be done to instances of type /atom")
|
||||
to_chat(src, "This can only be done to instances of type /atom")
|
||||
return
|
||||
|
||||
switch(href_list["rotatedir"])
|
||||
@@ -290,12 +290,12 @@
|
||||
|
||||
var/mob/living/carbon/human/H = locate(href_list["makemonkey"])
|
||||
if(!istype(H))
|
||||
to_chat(usr, "This can only be done to instances of type /mob/living/carbon/human")
|
||||
to_chat(src, "This can only be done to instances of type /mob/living/carbon/human")
|
||||
return
|
||||
|
||||
if(tgui_alert(usr, "Confirm mob type change?","Confirm",list("Transform","Cancel")) != "Transform") return
|
||||
if(tgui_alert(src, "Confirm mob type change?","Confirm",list("Transform","Cancel")) != "Transform") return
|
||||
if(!H)
|
||||
to_chat(usr, "Mob doesn't exist anymore")
|
||||
to_chat(src, "Mob doesn't exist anymore")
|
||||
return
|
||||
holder.Topic(href, list("monkeyone"=href_list["makemonkey"]))
|
||||
|
||||
@@ -304,12 +304,12 @@
|
||||
|
||||
var/mob/living/carbon/human/H = locate(href_list["makerobot"])
|
||||
if(!istype(H))
|
||||
to_chat(usr, "This can only be done to instances of type /mob/living/carbon/human")
|
||||
to_chat(src, "This can only be done to instances of type /mob/living/carbon/human")
|
||||
return
|
||||
|
||||
if(tgui_alert(usr, "Confirm mob type change?","Confirm",list("Transform","Cancel")) != "Transform") return
|
||||
if(tgui_alert(src, "Confirm mob type change?","Confirm",list("Transform","Cancel")) != "Transform") return
|
||||
if(!H)
|
||||
to_chat(usr, "Mob doesn't exist anymore")
|
||||
to_chat(src, "Mob doesn't exist anymore")
|
||||
return
|
||||
holder.Topic(href, list("makerobot"=href_list["makerobot"]))
|
||||
|
||||
@@ -318,12 +318,12 @@
|
||||
|
||||
var/mob/living/carbon/human/H = locate(href_list["makealien"])
|
||||
if(!istype(H))
|
||||
to_chat(usr, "This can only be done to instances of type /mob/living/carbon/human")
|
||||
to_chat(src, "This can only be done to instances of type /mob/living/carbon/human")
|
||||
return
|
||||
|
||||
if(tgui_alert(usr, "Confirm mob type change?","Confirm",list("Transform","Cancel")) != "Transform") return
|
||||
if(tgui_alert(src, "Confirm mob type change?","Confirm",list("Transform","Cancel")) != "Transform") return
|
||||
if(!H)
|
||||
to_chat(usr, "Mob doesn't exist anymore")
|
||||
to_chat(src, "Mob doesn't exist anymore")
|
||||
return
|
||||
holder.Topic(href, list("makealien"=href_list["makealien"]))
|
||||
|
||||
@@ -332,12 +332,12 @@
|
||||
|
||||
var/mob/living/carbon/human/H = locate(href_list["makeai"])
|
||||
if(!istype(H))
|
||||
to_chat(usr, "This can only be done to instances of type /mob/living/carbon/human")
|
||||
to_chat(src, "This can only be done to instances of type /mob/living/carbon/human")
|
||||
return
|
||||
|
||||
if(tgui_alert(usr, "Confirm mob type change?","Confirm",list("Transform","Cancel")) != "Transform") return
|
||||
if(tgui_alert(src, "Confirm mob type change?","Confirm",list("Transform","Cancel")) != "Transform") return
|
||||
if(!H)
|
||||
to_chat(usr, "Mob doesn't exist anymore")
|
||||
to_chat(src, "Mob doesn't exist anymore")
|
||||
return
|
||||
holder.Topic(href, list("makeai"=href_list["makeai"]))
|
||||
|
||||
@@ -346,67 +346,67 @@
|
||||
|
||||
var/mob/living/carbon/human/H = locate(href_list["setspecies"])
|
||||
if(!istype(H))
|
||||
to_chat(usr, "This can only be done to instances of type /mob/living/carbon/human")
|
||||
to_chat(src, "This can only be done to instances of type /mob/living/carbon/human")
|
||||
return
|
||||
|
||||
var/new_species = tgui_input_list(usr, "Please choose a new species.","Species", GLOB.all_species)
|
||||
var/new_species = tgui_input_list(src, "Please choose a new species.","Species", GLOB.all_species)
|
||||
|
||||
if(!H)
|
||||
to_chat(usr, "Mob doesn't exist anymore")
|
||||
to_chat(src, "Mob doesn't exist anymore")
|
||||
return
|
||||
|
||||
if(H.set_species(new_species))
|
||||
to_chat(usr, "Set species of [H] to [H.species].")
|
||||
to_chat(src, "Set species of [H] to [H.species].")
|
||||
else
|
||||
to_chat(usr, "Failed! Something went wrong.")
|
||||
to_chat(src, "Failed! Something went wrong.")
|
||||
|
||||
else if(href_list["addlanguage"])
|
||||
if(!check_rights(R_SPAWN)) return
|
||||
|
||||
var/mob/H = locate(href_list["addlanguage"])
|
||||
if(!istype(H))
|
||||
to_chat(usr, "This can only be done to instances of type /mob")
|
||||
to_chat(src, "This can only be done to instances of type /mob")
|
||||
return
|
||||
|
||||
var/new_language = tgui_input_list(usr, "Please choose a language to add.","Language", GLOB.all_languages)
|
||||
var/new_language = tgui_input_list(src, "Please choose a language to add.","Language", GLOB.all_languages)
|
||||
|
||||
if(!new_language)
|
||||
return
|
||||
|
||||
if(!H)
|
||||
to_chat(usr, "Mob doesn't exist anymore")
|
||||
to_chat(src, "Mob doesn't exist anymore")
|
||||
return
|
||||
|
||||
if(H.add_language(new_language))
|
||||
to_chat(usr, "Added [new_language] to [H].")
|
||||
to_chat(src, "Added [new_language] to [H].")
|
||||
else
|
||||
to_chat(usr, "Mob already knows that language.")
|
||||
to_chat(src, "Mob already knows that language.")
|
||||
|
||||
else if(href_list["remlanguage"])
|
||||
if(!check_rights(R_SPAWN)) return
|
||||
|
||||
var/mob/H = locate(href_list["remlanguage"])
|
||||
if(!istype(H))
|
||||
to_chat(usr, "This can only be done to instances of type /mob")
|
||||
to_chat(src, "This can only be done to instances of type /mob")
|
||||
return
|
||||
|
||||
if(!H.languages.len)
|
||||
to_chat(usr, "This mob knows no languages.")
|
||||
to_chat(src, "This mob knows no languages.")
|
||||
return
|
||||
|
||||
var/datum/language/rem_language = tgui_input_list(usr, "Please choose a language to remove.","Language", H.languages)
|
||||
var/datum/language/rem_language = tgui_input_list(src, "Please choose a language to remove.","Language", H.languages)
|
||||
|
||||
if(!rem_language)
|
||||
return
|
||||
|
||||
if(!H)
|
||||
to_chat(usr, "Mob doesn't exist anymore")
|
||||
to_chat(src, "Mob doesn't exist anymore")
|
||||
return
|
||||
|
||||
if(H.remove_language(rem_language.name))
|
||||
to_chat(usr, "Removed [rem_language] from [H].")
|
||||
to_chat(src, "Removed [rem_language] from [H].")
|
||||
else
|
||||
to_chat(usr, "Mob doesn't know that language.")
|
||||
to_chat(src, "Mob doesn't know that language.")
|
||||
|
||||
else if(href_list["addverb"])
|
||||
if(!check_rights(R_DEBUG)) return
|
||||
@@ -414,7 +414,7 @@
|
||||
var/mob/H = locate(href_list["addverb"])
|
||||
|
||||
if(!ismob(H))
|
||||
to_chat(usr, "This can only be done to instances of type /mob")
|
||||
to_chat(src, "This can only be done to instances of type /mob")
|
||||
return
|
||||
var/list/possibleverbs = list()
|
||||
possibleverbs += "Cancel" // One for the top...
|
||||
@@ -434,9 +434,9 @@
|
||||
possibleverbs -= H.verbs
|
||||
possibleverbs += "Cancel" // ...And one for the bottom
|
||||
|
||||
var/verb = tgui_input_list(usr, "Select a verb!", "Verbs", possibleverbs)
|
||||
var/verb = tgui_input_list(src, "Select a verb!", "Verbs", possibleverbs)
|
||||
if(!H)
|
||||
to_chat(usr, "Mob doesn't exist anymore")
|
||||
to_chat(src, "Mob doesn't exist anymore")
|
||||
return
|
||||
if(!verb || verb == "Cancel")
|
||||
return
|
||||
@@ -449,11 +449,11 @@
|
||||
var/mob/H = locate(href_list["remverb"])
|
||||
|
||||
if(!istype(H))
|
||||
to_chat(usr, "This can only be done to instances of type /mob")
|
||||
to_chat(src, "This can only be done to instances of type /mob")
|
||||
return
|
||||
var/verb = tgui_input_list(usr, "Please choose a verb to remove.","Verbs", H.verbs)
|
||||
var/verb = tgui_input_list(src, "Please choose a verb to remove.","Verbs", H.verbs)
|
||||
if(!H)
|
||||
to_chat(usr, "Mob doesn't exist anymore")
|
||||
to_chat(src, "Mob doesn't exist anymore")
|
||||
return
|
||||
if(!verb)
|
||||
return
|
||||
@@ -465,18 +465,18 @@
|
||||
|
||||
var/mob/living/carbon/M = locate(href_list["addorgan"])
|
||||
if(!istype(M))
|
||||
to_chat(usr, "This can only be done to instances of type /mob/living/carbon")
|
||||
to_chat(src, "This can only be done to instances of type /mob/living/carbon")
|
||||
return
|
||||
|
||||
var/new_organ = tgui_input_list(usr, "Please choose an organ to add.","Organ", subtypesof(/obj/item/organ))
|
||||
var/new_organ = tgui_input_list(src, "Please choose an organ to add.","Organ", subtypesof(/obj/item/organ))
|
||||
if(!new_organ) return
|
||||
|
||||
if(!M)
|
||||
to_chat(usr, "Mob doesn't exist anymore")
|
||||
to_chat(src, "Mob doesn't exist anymore")
|
||||
return
|
||||
|
||||
if(locate(new_organ) in M.internal_organs)
|
||||
to_chat(usr, "Mob already has that organ.")
|
||||
to_chat(src, "Mob already has that organ.")
|
||||
return
|
||||
|
||||
new new_organ(M)
|
||||
@@ -487,20 +487,20 @@
|
||||
|
||||
var/mob/living/carbon/M = locate(href_list["remorgan"])
|
||||
if(!istype(M))
|
||||
to_chat(usr, "This can only be done to instances of type /mob/living/carbon")
|
||||
to_chat(src, "This can only be done to instances of type /mob/living/carbon")
|
||||
return
|
||||
|
||||
var/obj/item/organ/rem_organ = tgui_input_list(usr, "Please choose an organ to remove.","Organ", M.internal_organs)
|
||||
var/obj/item/organ/rem_organ = tgui_input_list(src, "Please choose an organ to remove.","Organ", M.internal_organs)
|
||||
|
||||
if(!M)
|
||||
to_chat(usr, "Mob doesn't exist anymore")
|
||||
to_chat(src, "Mob doesn't exist anymore")
|
||||
return
|
||||
|
||||
if(!(locate(rem_organ) in M.internal_organs))
|
||||
to_chat(usr, "Mob does not have that organ.")
|
||||
to_chat(src, "Mob does not have that organ.")
|
||||
return
|
||||
|
||||
to_chat(usr, "Removed [rem_organ] from [M].")
|
||||
to_chat(src, "Removed [rem_organ] from [M].")
|
||||
rem_organ.removed()
|
||||
qdel(rem_organ)
|
||||
|
||||
@@ -510,12 +510,12 @@
|
||||
var/mob/H = locate(href_list["fix_nano"])
|
||||
|
||||
if(!istype(H) || !H.client)
|
||||
to_chat(usr, "This can only be done on mobs with clients")
|
||||
to_chat(src, "This can only be done on mobs with clients")
|
||||
return
|
||||
|
||||
H.client.send_resources()
|
||||
|
||||
to_chat(usr, "Resource files sent")
|
||||
to_chat(src, "Resource files sent")
|
||||
to_chat(H, "Your NanoUI Resource files have been refreshed")
|
||||
|
||||
log_admin("[key_name(usr)] resent the NanoUI resource files to [key_name(H)] ")
|
||||
@@ -525,7 +525,7 @@
|
||||
|
||||
var/mob/M = locate(href_list["regenerateicons"])
|
||||
if(!ismob(M))
|
||||
to_chat(usr, "This can only be done to instances of type /mob")
|
||||
to_chat(src, "This can only be done to instances of type /mob")
|
||||
return
|
||||
M.regenerate_icons()
|
||||
|
||||
@@ -537,10 +537,10 @@
|
||||
|
||||
var/Text = href_list["adjustDamage"]
|
||||
|
||||
var/amount = tgui_input_number(usr, "Deal how much damage to mob? (Negative values here heal)","Adjust [Text]loss",0, min_value=-INFINITY, round_value=FALSE)
|
||||
var/amount = tgui_input_number(src, "Deal how much damage to mob? (Negative values here heal)","Adjust [Text]loss",0, min_value=-INFINITY, round_value=FALSE)
|
||||
|
||||
if(!L)
|
||||
to_chat(usr, "Mob doesn't exist anymore")
|
||||
to_chat(src, "Mob doesn't exist anymore")
|
||||
return
|
||||
|
||||
switch(Text)
|
||||
@@ -551,7 +551,7 @@
|
||||
if("brain") L.adjustBrainLoss(amount)
|
||||
if("clone") L.adjustCloneLoss(amount)
|
||||
else
|
||||
to_chat(usr, "You caused an error. DEBUG: Text:[Text] Mob:[L]")
|
||||
to_chat(src, "You caused an error. DEBUG: Text:[Text] Mob:[L]")
|
||||
return
|
||||
|
||||
if(amount != 0)
|
||||
@@ -570,11 +570,11 @@
|
||||
var/client/C = value["value"]
|
||||
if (!C)
|
||||
return
|
||||
var/prompt = tgui_alert(usr, "Do you want to grant [C] access to view this VV window? (they will not be able to edit or change anysrc nor open nested vv windows unless they themselves are an admin)", "Confirm", list("Yes", "No"))
|
||||
var/prompt = tgui_alert(src, "Do you want to grant [C] access to view this VV window? (they will not be able to edit or change anysrc nor open nested vv windows unless they themselves are an admin)", "Confirm", list("Yes", "No"))
|
||||
if (prompt != "Yes")
|
||||
return
|
||||
if(!thing)
|
||||
to_chat(usr, span_warning("The object you tried to expose to [C] no longer exists (GC'd)"))
|
||||
to_chat(src, span_warning("The object you tried to expose to [C] no longer exists (GC'd)"))
|
||||
return
|
||||
message_admins("[key_name_admin(usr)] Showed [key_name_admin(C)] a <a href='byond://?_src_=vars;[HrefToken(TRUE)];datumrefresh=\ref[thing]'>VV window</a>")
|
||||
log_admin("Admin [key_name(usr)] Showed [key_name(C)] a VV window of a [src]")
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
#define ICON_STATE_CHECKED 1 /// this dmi is checked. We don't check this one anymore.
|
||||
#define ICON_STATE_NULL 2 /// this dmi has null-named icon_state, allowing it to show a sprite on vv editor.
|
||||
|
||||
ADMIN_VERB_AND_CONTEXT_MENU(debug_variables, (R_DEBUG|R_SERVER|R_ADMIN|R_SPAWN|R_FUN|R_EVENT), "View Variables", "View the variables of a datum.", "Debug.Investigate", datum/thing in world)
|
||||
user.debug_variables(thing)
|
||||
// This is kept as a separate proc because admins are able to show VV to non-admins
|
||||
|
||||
/client/proc/debug_variables(datum/D in world)
|
||||
set category = "Debug.Investigate"
|
||||
set name = "View Variables"
|
||||
|
||||
@@ -179,7 +179,6 @@
|
||||
|
||||
switch(href_list["_src_"])
|
||||
if("holder") hsrc = holder
|
||||
if("mentorholder") hsrc = (check_rights(R_ADMIN, 0) ? holder : mentorholder)
|
||||
if("usr") hsrc = mob
|
||||
if("prefs") return prefs.process_link(usr,href_list)
|
||||
if("vars") return view_var_Topic(href,href_list,hsrc)
|
||||
@@ -257,8 +256,7 @@
|
||||
initialize_commandbar_spy()
|
||||
tgui_panel = new(src, "browseroutput")
|
||||
|
||||
GLOB.ahelp_tickets.ClientLogin(src)
|
||||
GLOB.mhelp_tickets.ClientLogin(src)
|
||||
GLOB.tickets.ClientLogin(src)
|
||||
|
||||
//Admin Authorisation
|
||||
holder = GLOB.admin_datums[ckey]
|
||||
@@ -266,10 +264,6 @@
|
||||
GLOB.admins += src
|
||||
holder.owner = src
|
||||
|
||||
mentorholder = mentor_datums[ckey]
|
||||
if (mentorholder)
|
||||
mentorholder.associate(GLOB.directory[ckey])
|
||||
|
||||
//preferences datum - also holds some persistant data for the client (because we may as well keep these datums to a minimum)
|
||||
prefs = preferences_datums[ckey]
|
||||
if(prefs)
|
||||
@@ -374,8 +368,7 @@
|
||||
if (!QDELING(src))
|
||||
stack_trace("Client does not purport to be QDELING, this is going to cause bugs in other places!")
|
||||
|
||||
GLOB.ahelp_tickets.ClientLogout(src)
|
||||
GLOB.mhelp_tickets.ClientLogout(src)
|
||||
GLOB.tickets.ClientLogout(src)
|
||||
|
||||
// Yes this is the same as what's found in qdel(). Yes it does need to be here
|
||||
// Get off my back
|
||||
@@ -387,9 +380,6 @@
|
||||
if(holder)
|
||||
holder.owner = null
|
||||
GLOB.admins -= src
|
||||
if (mentorholder)
|
||||
mentorholder.owner = null
|
||||
GLOB.mentors -= src
|
||||
GLOB.directory -= ckey
|
||||
GLOB.clients -= src
|
||||
|
||||
|
||||
@@ -62,10 +62,12 @@
|
||||
var/modmsg = ""
|
||||
var/devmsg = ""
|
||||
var/eventMmsg = ""
|
||||
var/mentormsg = ""
|
||||
var/num_mods_online = 0
|
||||
var/num_admins_online = 0
|
||||
var/num_devs_online = 0
|
||||
var/num_event_managers_online = 0
|
||||
var/num_mentors_online = 0
|
||||
for(var/client/C in GLOB.admins) // VOREStation Edit - GLOB
|
||||
var/temp = ""
|
||||
var/category = R_ADMIN
|
||||
@@ -84,6 +86,9 @@
|
||||
else if(check_rights_for(C, R_STEALTH)) // event managers //VOREStation Edit: Retired Staff
|
||||
category = R_EVENT
|
||||
num_event_managers_online++
|
||||
else if(check_rights_for(C, R_MENTOR))
|
||||
category = R_MENTOR
|
||||
num_mentors_online++
|
||||
|
||||
temp += "\t[C] is a [C.holder.rank_names()]"
|
||||
if(holder)
|
||||
@@ -110,6 +115,8 @@
|
||||
devmsg += temp
|
||||
if(R_EVENT)
|
||||
eventMmsg += temp
|
||||
if(R_MENTOR)
|
||||
mentormsg += temp
|
||||
|
||||
msg = span_bold("Current Admins ([num_admins_online]):") + "\n" + msg
|
||||
|
||||
@@ -122,27 +129,8 @@
|
||||
if(CONFIG_GET(flag/show_event_managers))
|
||||
msg += "\n" + span_bold(" Current Miscellaneous ([num_event_managers_online]):") + "\n" + eventMmsg
|
||||
|
||||
var/num_mentors_online = 0
|
||||
var/mmsg = ""
|
||||
|
||||
for(var/client/C in GLOB.mentors)
|
||||
num_mentors_online++
|
||||
mmsg += "\t[C] is a Mentor"
|
||||
if(holder)
|
||||
if(isobserver(C.mob))
|
||||
mmsg += " - Observing"
|
||||
else if(isnewplayer(C.mob))
|
||||
mmsg += " - Lobby"
|
||||
else
|
||||
mmsg += " - Playing"
|
||||
|
||||
if(C.is_afk())
|
||||
var/seconds = C.last_activity_seconds()
|
||||
mmsg += " (AFK - [round(seconds / 60)] minutes, [seconds % 60] seconds)"
|
||||
mmsg += "\n"
|
||||
|
||||
if(CONFIG_GET(flag/show_mentors))
|
||||
msg += "\n" + span_bold(" Current Mentors ([num_mentors_online]):") + "\n" + mmsg
|
||||
msg += "\n" + span_bold(" Current Mentors ([num_mentors_online]):") + "\n" + mentormsg
|
||||
|
||||
msg += "\n" + span_info("Adminhelps are also sent to Discord. If no admins are available in game try anyway and an admin on Discord may see it and respond.")
|
||||
|
||||
|
||||
@@ -1,268 +0,0 @@
|
||||
/client
|
||||
var/datum/mentor/mentorholder = null
|
||||
|
||||
var/list/mentor_datums = list()
|
||||
|
||||
var/list/mentor_verbs_default = list(
|
||||
/client/proc/cmd_mentor_ticket_panel,
|
||||
/client/proc/cmd_mentor_say,
|
||||
/client/proc/cmd_dementor
|
||||
)
|
||||
|
||||
/datum/mentor
|
||||
var/client/owner = null
|
||||
|
||||
/datum/mentor/New(ckey)
|
||||
if(!ckey)
|
||||
error("Mentor datum created without a ckey argument. Datum has been deleted")
|
||||
qdel(src)
|
||||
return
|
||||
mentor_datums[ckey] = src
|
||||
|
||||
/datum/mentor/proc/associate(client/C)
|
||||
if(istype(C))
|
||||
owner = C
|
||||
owner.mentorholder = src
|
||||
owner.add_mentor_verbs()
|
||||
GLOB.mentors |= C
|
||||
|
||||
/datum/mentor/proc/disassociate()
|
||||
if(owner)
|
||||
GLOB.mentors -= owner
|
||||
owner.remove_mentor_verbs()
|
||||
owner.mentorholder = null
|
||||
mentor_datums[owner.ckey] = null
|
||||
qdel(src)
|
||||
|
||||
/client/proc/add_mentor_verbs()
|
||||
if(mentorholder)
|
||||
add_verb(src, mentor_verbs_default)
|
||||
|
||||
/client/proc/remove_mentor_verbs()
|
||||
if(mentorholder)
|
||||
remove_verb(src, mentor_verbs_default)
|
||||
|
||||
/client/proc/make_mentor()
|
||||
set category = "Admin.Secrets"
|
||||
set name = "Make Mentor"
|
||||
if(!holder)
|
||||
to_chat(src, span_admin_pm_warning("Error: Only administrators may use this command."))
|
||||
return
|
||||
var/list/client/targets[0]
|
||||
for(var/client/T in GLOB.clients)
|
||||
targets["[T.key]"] = T
|
||||
var/target = tgui_input_list(src,"Who do you want to make a mentor?","Make Mentor", sortList(targets))
|
||||
if(!target)
|
||||
return
|
||||
var/client/C = targets[target]
|
||||
if(has_mentor_powers(C) || GLOB.deadmins[C.ckey]) // If an admin is deadminned you could mentor them and that will cause fuckery if they readmin
|
||||
to_chat(src, span_admin_pm_warning("Error: They already have mentor powers."))
|
||||
return
|
||||
var/datum/mentor/M = new /datum/mentor(C.ckey)
|
||||
M.associate(C)
|
||||
to_chat(C, span_admin_pm_notice("You have been granted mentorship."))
|
||||
to_chat(src, span_admin_pm_notice("You have made [C] a mentor."))
|
||||
log_admin("[key_name(src)] made [key_name(C)] a mentor.")
|
||||
feedback_add_details("admin_verb","Make Mentor") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
/client/proc/unmake_mentor()
|
||||
set category = "Admin.Secrets"
|
||||
set name = "Unmake Mentor"
|
||||
if(!holder)
|
||||
to_chat(src, span_admin_pm_warning("Error: Only administrators may use this command."))
|
||||
return
|
||||
var/list/client/targets[0]
|
||||
for(var/client/T in GLOB.mentors)
|
||||
targets["[T.key]"] = T
|
||||
var/target = tgui_input_list(src,"Which mentor do you want to unmake?","Unmake Mentor", sortList(targets))
|
||||
if(!target)
|
||||
return
|
||||
var/client/C = targets[target]
|
||||
C.mentorholder.disassociate()
|
||||
to_chat(C, span_admin_pm_warning("Your mentorship has been revoked."))
|
||||
to_chat(src, span_admin_pm_notice("You have revoked [C]'s mentorship."))
|
||||
log_admin("[key_name(src)] revoked [key_name(C)]'s mentorship.")
|
||||
feedback_add_details("admin_verb","Unmake Mentor") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
/client/proc/cmd_mentor_say(msg as text)
|
||||
set category = "Admin.Chat"
|
||||
set name ="Mentorsay"
|
||||
|
||||
//check rights
|
||||
if (!has_mentor_powers(src))
|
||||
return
|
||||
|
||||
msg = sanitize(msg)
|
||||
if (!msg)
|
||||
return
|
||||
|
||||
log_admin("Mentorsay: [key_name(src)]: [msg]")
|
||||
|
||||
for(var/client/C in GLOB.mentors)
|
||||
to_chat(C, create_text_tag("mentor", "MENTOR:", C) + " " + span_mentor_channel(span_name("[src]") + ": " + span_message("[msg]")))
|
||||
for(var/client/C in GLOB.admins)
|
||||
to_chat(C, create_text_tag("mentor", "MENTOR:", C) + " " + span_mentor_channel(span_name("[src]") + ": " + span_message("[msg]")))
|
||||
|
||||
/proc/mentor_commands(href, href_list, client/C)
|
||||
if(href_list["mhelp"])
|
||||
var/mhelp_ref = href_list["mhelp"]
|
||||
var/datum/mentor_help/MH = locate(mhelp_ref)
|
||||
if (MH && istype(MH, /datum/mentor_help))
|
||||
MH.Action(href_list["mhelp_action"])
|
||||
else
|
||||
to_chat(C, "Ticket [mhelp_ref] has been deleted!")
|
||||
|
||||
if (href_list["mhelp_tickets"])
|
||||
GLOB.mhelp_tickets.BrowseTickets(text2num(href_list["mhelp_tickets"]))
|
||||
|
||||
|
||||
/datum/mentor/Topic(href, href_list)
|
||||
..()
|
||||
if (usr.client != src.owner || (!usr.client.mentorholder))
|
||||
log_admin("[key_name(usr)] tried to illegally use mentor functions.")
|
||||
message_admins("[usr.key] tried to illegally use mentor functions.")
|
||||
return
|
||||
|
||||
mentor_commands(href, href_list, usr)
|
||||
|
||||
/client/proc/cmd_dementor()
|
||||
set category = "Admin.Misc"
|
||||
set name = "De-mentor"
|
||||
|
||||
if(tgui_alert(usr, "Confirm self-dementor for the round? You can't re-mentor yourself without someone promoting you.","Dementor",list("Yes","No")) == "Yes")
|
||||
src.mentorholder.disassociate()
|
||||
|
||||
/client/proc/cmd_mhelp_reply(whom)
|
||||
if(prefs.muted & MUTE_ADMINHELP)
|
||||
to_chat(src, span_admin_pm_warning("Error: Mentor-PM: You are unable to use admin PM-s (muted)."))
|
||||
return
|
||||
var/client/C
|
||||
if(istext(whom))
|
||||
C = GLOB.directory[whom]
|
||||
else if(istype(whom,/client))
|
||||
C = whom
|
||||
if(!C)
|
||||
if(has_mentor_powers(src))
|
||||
to_chat(src, span_admin_pm_warning("Error: Mentor-PM: Client not found."))
|
||||
return
|
||||
|
||||
var/datum/mentor_help/MH = C.current_mentorhelp
|
||||
|
||||
if(MH)
|
||||
message_mentors(span_mentor_channel("[src] has started replying to [C]'s mentor help."))
|
||||
var/msg = tgui_input_text(src,"Message:", "Private message to [C]", multiline = TRUE)
|
||||
if (!msg)
|
||||
message_mentors(span_mentor_channel("[src] has cancelled their reply to [C]'s mentor help."))
|
||||
return
|
||||
cmd_mentor_pm(whom, msg, MH)
|
||||
|
||||
/proc/has_mentor_powers(client/C)
|
||||
return C.holder || C.mentorholder
|
||||
|
||||
// This not really a great place to put it, but this verb replaces adminhelp in hotkeys so that people requesting help can select the type they need
|
||||
// You can still directly adminhelp if necessary, this ONLY replaces the inbuilt hotkeys
|
||||
|
||||
/client/verb/requesthelp()
|
||||
set category = "Admin"
|
||||
set name = "Request help"
|
||||
set hidden = 1
|
||||
|
||||
var/mhelp = tgui_alert(usr, "Select the help you need.","Request for Help",list("Adminhelp","Mentorhelp"))
|
||||
if(!mhelp)
|
||||
return
|
||||
|
||||
var/msg = tgui_input_text(usr, "Input your request for help.", "Request for Help ([mhelp])", multiline = TRUE)
|
||||
if(!msg)
|
||||
return
|
||||
|
||||
if (mhelp == "Mentorhelp")
|
||||
mentorhelp(msg)
|
||||
return
|
||||
|
||||
adminhelp(msg)
|
||||
|
||||
|
||||
/client/proc/cmd_mentor_pm(whom, msg, datum/mentor_help/MH)
|
||||
set category = "Admin"
|
||||
set name = "Mentor-PM"
|
||||
set hidden = 1
|
||||
|
||||
if(prefs.muted & MUTE_ADMINHELP)
|
||||
to_chat(src, span_mentor_pm_warning("Error: Mentor-PM: You are unable to use mentor PM-s (muted)."))
|
||||
return
|
||||
|
||||
//Not a mentor and no open ticket
|
||||
if(!has_mentor_powers(src) && !current_mentorhelp)
|
||||
to_chat(src, span_mentor_pm_warning("You can no longer reply to this ticket, please open another one by using the Mentorhelp verb if need be."))
|
||||
to_chat(src, span_mentor_pm_notice("Message: [msg]"))
|
||||
return
|
||||
|
||||
var/client/recipient
|
||||
|
||||
if(istext(whom))
|
||||
recipient = GLOB.directory[whom]
|
||||
|
||||
else if(istype(whom,/client))
|
||||
recipient = whom
|
||||
|
||||
//get message text, limit it's length.and clean/escape html
|
||||
if(!msg)
|
||||
msg = tgui_input_text(src,"Message:", "Mentor-PM to [whom]", multiline = TRUE)
|
||||
|
||||
if(!msg)
|
||||
return
|
||||
|
||||
if(prefs.muted & MUTE_ADMINHELP)
|
||||
to_chat(src, span_mentor_pm_warning("Error: Mentor-PM: You are unable to use mentor PM-s (muted)."))
|
||||
return
|
||||
|
||||
if(!recipient)
|
||||
if(has_mentor_powers(src))
|
||||
to_chat(src, span_mentor_pm_warning("Error:Mentor-PM: Client not found."))
|
||||
to_chat(src, msg)
|
||||
else
|
||||
log_admin("Mentorhelp: [key_name(src)]: [msg]")
|
||||
current_mentorhelp.MessageNoRecipient(msg)
|
||||
return
|
||||
|
||||
//Has mentor powers but the recipient no longer has an open ticket
|
||||
if(has_mentor_powers(src) && !recipient.current_mentorhelp)
|
||||
to_chat(src, span_mentor_pm_warning("You can no longer reply to this ticket."))
|
||||
to_chat(src, span_mentor_pm_notice("Message: [msg]"))
|
||||
return
|
||||
|
||||
if (src.handle_spam_prevention(msg,MUTE_ADMINHELP))
|
||||
return
|
||||
|
||||
msg = trim(sanitize(copytext(msg,1,MAX_MESSAGE_LEN)))
|
||||
if(!msg)
|
||||
return
|
||||
|
||||
var/interaction_message = span_mentor_pm_notice("Mentor-PM from-<b>[src]</b> to-<b>[recipient]</b>: [msg]")
|
||||
|
||||
if (recipient.current_mentorhelp && !has_mentor_powers(recipient))
|
||||
recipient.current_mentorhelp.AddInteraction(interaction_message)
|
||||
if (src.current_mentorhelp && !has_mentor_powers(src))
|
||||
src.current_mentorhelp.AddInteraction(interaction_message)
|
||||
|
||||
// It's a little fucky if they're both mentors, but while admins may need to adminhelp I don't really see any reason a mentor would have to mentorhelp since you can literally just ask any other mentors online
|
||||
if (has_mentor_powers(recipient) && has_mentor_powers(src))
|
||||
if (recipient.current_mentorhelp)
|
||||
recipient.current_mentorhelp.AddInteraction(interaction_message)
|
||||
if (src.current_mentorhelp)
|
||||
src.current_mentorhelp.AddInteraction(interaction_message)
|
||||
|
||||
to_chat(recipient, span_mentor(span_italics("Mentor-PM from-<b><a href='byond://?mentorhelp_msg=\ref[src]'>[src]</a></b>: [msg]")))
|
||||
to_chat(src, span_mentor(span_italics("Mentor-PM to-<b>[recipient]</b>: [msg]")))
|
||||
|
||||
log_admin("[key_name(src)]->[key_name(recipient)]: [msg]")
|
||||
|
||||
if(recipient.prefs?.read_preference(/datum/preference/toggle/play_mentorhelp_ping))
|
||||
recipient << 'sound/effects/mentorhelp.mp3'
|
||||
|
||||
for(var/client/C in GLOB.mentors)
|
||||
if (C != recipient && C != src)
|
||||
to_chat(C, interaction_message)
|
||||
for(var/client/C in GLOB.admins)
|
||||
if (C != recipient && C != src)
|
||||
to_chat(C, interaction_message)
|
||||
@@ -1,507 +0,0 @@
|
||||
/client/var/datum/mentor_help/current_mentorhelp
|
||||
|
||||
//
|
||||
//TICKET MANAGER
|
||||
//
|
||||
|
||||
GLOBAL_DATUM_INIT(mhelp_tickets, /datum/mentor_help_tickets, new)
|
||||
|
||||
/datum/mentor_help_tickets
|
||||
var/list/active_tickets = list()
|
||||
var/list/resolved_tickets = list()
|
||||
|
||||
var/obj/effect/statclick/mticket_list/astatclick = new(null, null, AHELP_ACTIVE)
|
||||
var/obj/effect/statclick/mticket_list/rstatclick = new(null, null, AHELP_RESOLVED)
|
||||
|
||||
/datum/mentor_help_tickets/Destroy()
|
||||
QDEL_LIST(active_tickets)
|
||||
QDEL_LIST(resolved_tickets)
|
||||
QDEL_NULL(astatclick)
|
||||
QDEL_NULL(rstatclick)
|
||||
return ..()
|
||||
|
||||
//private
|
||||
/datum/mentor_help_tickets/proc/ListInsert(datum/mentor_help/new_ticket)
|
||||
var/list/mticket_list
|
||||
switch(new_ticket.state)
|
||||
if(AHELP_ACTIVE)
|
||||
mticket_list = active_tickets
|
||||
if(AHELP_RESOLVED)
|
||||
mticket_list = resolved_tickets
|
||||
else
|
||||
CRASH("Invalid ticket state: [new_ticket.state]")
|
||||
var/num_closed = mticket_list.len
|
||||
if(num_closed)
|
||||
for(var/I in 1 to num_closed)
|
||||
var/datum/mentor_help/MH = mticket_list[I]
|
||||
if(MH.id > new_ticket.id)
|
||||
mticket_list.Insert(I, new_ticket)
|
||||
return
|
||||
mticket_list += new_ticket
|
||||
|
||||
//opens the ticket listings, only two states here
|
||||
/datum/mentor_help_tickets/proc/BrowseTickets(state)
|
||||
var/list/l2b
|
||||
var/title
|
||||
switch(state)
|
||||
if(AHELP_ACTIVE)
|
||||
l2b = active_tickets
|
||||
title = "Active Tickets"
|
||||
if(AHELP_RESOLVED)
|
||||
l2b = resolved_tickets
|
||||
title = "Resolved Tickets"
|
||||
if(!l2b)
|
||||
return
|
||||
var/list/dat = list()
|
||||
dat += "<A href='byond://?_src_=mentorholder;[HrefToken()];mhelp_tickets=[state]'>Refresh</A><br><br>"
|
||||
for(var/datum/mentor_help/MH as anything in l2b)
|
||||
dat += span_adminnotice(span_adminhelp("Ticket #[MH.id]") + " <A href='byond://?_src_=mentorholder;mhelp=\ref[MH];[HrefToken()];mhelp_action=ticket'>[MH.initiator_ckey]: [MH.name]</A>") + "<br>"
|
||||
|
||||
var/datum/browser/popup = new(usr, "mhelp_list[state]", "Mentor Help List", 600, 480)
|
||||
popup.add_head_content("<title>[title]</title>")
|
||||
popup.set_content(dat.Join())
|
||||
popup.open()
|
||||
|
||||
//Tickets statpanel
|
||||
/datum/mentor_help_tickets/proc/stat_entry()
|
||||
SHOULD_CALL_PARENT(TRUE)
|
||||
SHOULD_NOT_SLEEP(TRUE)
|
||||
var/list/L = list()
|
||||
var/num_disconnected = 0
|
||||
L[++L.len] = list("== Mentor Tickets ==", "", null, null)
|
||||
L[++L.len] = list("Active Tickets:", "[astatclick.update("[active_tickets.len]")]", null, REF(astatclick))
|
||||
astatclick.update("[active_tickets.len]")
|
||||
for(var/datum/mentor_help/MH as anything in active_tickets)
|
||||
if(MH.initiator)
|
||||
L[++L.len] = list("#[MH.id]. [MH.initiator_ckey]:", "[MH.statclick.update()]", REF(MH))
|
||||
else
|
||||
++num_disconnected
|
||||
if(num_disconnected)
|
||||
L[++L.len] = list("Disconnected:", "[astatclick.update("[num_disconnected]")]", null, REF(astatclick))
|
||||
L[++L.len] = list("Resolved Tickets:", "[rstatclick.update("[resolved_tickets.len]")]", null, REF(rstatclick))
|
||||
return L
|
||||
|
||||
//Reassociate still open ticket if one exists
|
||||
/datum/mentor_help_tickets/proc/ClientLogin(client/C)
|
||||
C.current_mentorhelp = CKey2ActiveTicket(C.ckey)
|
||||
if(C.current_mentorhelp)
|
||||
C.current_mentorhelp.AddInteraction("Client reconnected.")
|
||||
C.current_mentorhelp.initiator = C
|
||||
|
||||
//Dissasociate ticket
|
||||
/datum/mentor_help_tickets/proc/ClientLogout(client/C)
|
||||
if(C.current_mentorhelp)
|
||||
C.current_mentorhelp.AddInteraction("Client disconnected.")
|
||||
C.current_mentorhelp.initiator = null
|
||||
C.current_mentorhelp = null
|
||||
|
||||
//Get a ticket given a ckey
|
||||
/datum/mentor_help_tickets/proc/CKey2ActiveTicket(ckey)
|
||||
for(var/datum/admin_help/MH as anything in active_tickets)
|
||||
if(MH.initiator_ckey == ckey)
|
||||
return MH
|
||||
|
||||
//
|
||||
//TICKET LIST STATCLICK
|
||||
//
|
||||
|
||||
/obj/effect/statclick/mticket_list
|
||||
var/current_state
|
||||
|
||||
INITIALIZE_IMMEDIATE(/obj/effect/statclick/mticket_list)
|
||||
/obj/effect/statclick/mticket_list/Initialize(mapload, name, state)
|
||||
. = ..()
|
||||
current_state = state
|
||||
|
||||
/obj/effect/statclick/mticket_list/Click()
|
||||
GLOB.mhelp_tickets.BrowseTickets(current_state)
|
||||
|
||||
//
|
||||
//TICKET DATUM
|
||||
//
|
||||
|
||||
/datum/mentor_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_mentorhelp
|
||||
//msg is the title of the ticket: usually the ahelp text
|
||||
/datum/mentor_help/New(msg, client/C)
|
||||
//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 = C.ckey
|
||||
initiator_key_name = key_name(initiator, FALSE, TRUE)
|
||||
if(initiator.current_mentorhelp) //This is a bug
|
||||
log_debug("Ticket erroneously left open by code")
|
||||
initiator.current_mentorhelp.AddInteraction("Ticket erroneously left open by code")
|
||||
initiator.current_mentorhelp.Resolve()
|
||||
initiator.current_mentorhelp = src
|
||||
|
||||
statclick = new(null, src)
|
||||
_interactions = list()
|
||||
|
||||
log_admin("Mentorhelp: [key_name(C)]: [msg]")
|
||||
MessageNoRecipient(msg)
|
||||
//show it to the person adminhelping too
|
||||
to_chat(C, span_mentor(span_italics("Mentor-PM to-" + span_bold("Mentors") + ": [name]")))
|
||||
|
||||
GLOB.mhelp_tickets.active_tickets += src
|
||||
|
||||
/datum/mentor_help/Destroy()
|
||||
RemoveActive()
|
||||
GLOB.mhelp_tickets.resolved_tickets -= src
|
||||
return ..()
|
||||
|
||||
/datum/mentor_help/proc/AddInteraction(formatted_message)
|
||||
_interactions += "[gameTimestamp()]: [formatted_message]"
|
||||
|
||||
//private
|
||||
/datum/mentor_help/proc/ClosureLinks(ref_src)
|
||||
if(!ref_src)
|
||||
ref_src = "\ref[src]"
|
||||
. = " (<A href='byond://?_src_=mentorholder;mhelp=[ref_src];[HrefToken()];mhelp_action=resolve'>RSLVE</A>)"
|
||||
|
||||
//private
|
||||
/datum/mentor_help/proc/LinkedReplyName(ref_src)
|
||||
if(!ref_src)
|
||||
ref_src = "\ref[src]"
|
||||
return "<A href='byond://?_src_=mentorholder;mhelp=[ref_src];[HrefToken()];mhelp_action=reply'>[initiator_ckey]</A>"
|
||||
|
||||
//private
|
||||
/datum/mentor_help/proc/TicketHref(msg, ref_src, action = "ticket")
|
||||
if(!ref_src)
|
||||
ref_src = "\ref[src]"
|
||||
return "<A href='byond://?_src_=mentorholder;mhelp=[ref_src];[HrefToken()];mhelp_action=[action]'>[msg]</A>"
|
||||
|
||||
//message from the initiator without a target, all people with mentor powers will see this
|
||||
/datum/mentor_help/proc/MessageNoRecipient(msg)
|
||||
var/ref_src = "\ref[src]"
|
||||
var/chat_msg = span_notice("(<A href='byond://?_src_=mentorholder;mhelp=[ref_src];[HrefToken()];mhelp_action=escalate'>ESCALATE</A>) Ticket [TicketHref("#[id]", ref_src)]" + span_bold(": [LinkedReplyName(ref_src)]:") + " [msg]")
|
||||
AddInteraction(span_red("[LinkedReplyName(ref_src)]: [msg]"))
|
||||
for (var/client/C in GLOB.mentors)
|
||||
if (C.prefs?.read_preference(/datum/preference/toggle/play_mentorhelp_ping))
|
||||
C << 'sound/effects/mentorhelp.mp3'
|
||||
for (var/client/C in GLOB.admins)
|
||||
if (C.prefs?.read_preference(/datum/preference/toggle/play_mentorhelp_ping))
|
||||
C << 'sound/effects/mentorhelp.mp3'
|
||||
message_mentors(chat_msg)
|
||||
|
||||
//Reopen a closed ticket
|
||||
/datum/mentor_help/proc/Reopen()
|
||||
if(state == AHELP_ACTIVE)
|
||||
to_chat(usr, span_warning("This ticket is already open."))
|
||||
return
|
||||
|
||||
if(GLOB.mhelp_tickets.CKey2ActiveTicket(initiator_ckey))
|
||||
to_chat(usr, span_warning("This user already has an active ticket, cannot reopen this one."))
|
||||
return
|
||||
|
||||
statclick = new(null, src)
|
||||
GLOB.mhelp_tickets.active_tickets += src
|
||||
GLOB.mhelp_tickets.resolved_tickets -= src
|
||||
switch(state)
|
||||
if(AHELP_RESOLVED)
|
||||
feedback_dec("mhelp_resolve")
|
||||
state = AHELP_ACTIVE
|
||||
closed_at = null
|
||||
if(initiator)
|
||||
initiator.current_mentorhelp = src
|
||||
|
||||
AddInteraction(span_purple("Reopened by [usr.ckey]"))
|
||||
if(initiator)
|
||||
to_chat(initiator, span_filter_adminlog("[span_purple("Ticket [TicketHref("#[id]")] was reopened by [usr.ckey].")]"))
|
||||
var/msg = span_adminhelp("Ticket [TicketHref("#[id]")] reopened by [usr.ckey].")
|
||||
message_mentors(msg)
|
||||
log_admin(msg)
|
||||
feedback_inc("mhelp_reopen")
|
||||
TicketPanel() //can only be done from here, so refresh it
|
||||
|
||||
//private
|
||||
/datum/mentor_help/proc/RemoveActive()
|
||||
if(state != AHELP_ACTIVE)
|
||||
return
|
||||
closed_at = world.time
|
||||
QDEL_NULL(statclick)
|
||||
GLOB.mhelp_tickets.active_tickets -= src
|
||||
if(initiator && initiator.current_mentorhelp == src)
|
||||
initiator.current_mentorhelp = null
|
||||
|
||||
//Mark open ticket as resolved/legitimate, returns mentorhelp verb
|
||||
/datum/mentor_help/proc/Resolve(silent = FALSE)
|
||||
if(state != AHELP_ACTIVE)
|
||||
return
|
||||
RemoveActive()
|
||||
state = AHELP_RESOLVED
|
||||
GLOB.mhelp_tickets.ListInsert(src)
|
||||
|
||||
AddInteraction(span_filter_adminlog("[span_green("Resolved by [usr.ckey].")]"))
|
||||
if(initiator)
|
||||
to_chat(initiator, span_filter_adminlog("[span_green("Ticket [TicketHref("#[id]")] was marked resolved by [usr.ckey].")]"))
|
||||
if(!silent)
|
||||
feedback_inc("mhelp_resolve")
|
||||
var/msg = "Ticket [TicketHref("#[id]")] resolved by [usr.ckey]"
|
||||
message_mentors(msg)
|
||||
log_admin(msg)
|
||||
|
||||
//Show the ticket panel
|
||||
/datum/mentor_help/proc/TicketPanel()
|
||||
tgui_interact(usr.client.mob)
|
||||
|
||||
/datum/mentor_help/proc/TicketPanelLegacy()
|
||||
var/list/dat = list()
|
||||
var/ref_src = "\ref[src]"
|
||||
dat += "<h4>Mentor Help Ticket #[id]: [LinkedReplyName(ref_src)]</h4>"
|
||||
dat += span_bold("State: ")
|
||||
switch(state)
|
||||
if(AHELP_ACTIVE)
|
||||
dat += span_bold(span_red("OPEN"))
|
||||
if(AHELP_RESOLVED)
|
||||
dat += span_bold(span_green("RESOLVED"))
|
||||
else
|
||||
dat += "UNKNOWN"
|
||||
dat += "[GLOB.TAB][TicketHref("Refresh", ref_src)]"
|
||||
if(state != AHELP_ACTIVE)
|
||||
dat += "[GLOB.TAB][TicketHref("Reopen", ref_src, "reopen")]"
|
||||
dat += "<br><br>Opened at: [gameTimestamp(wtime = opened_at)] (Approx [(world.time - opened_at) / 600] minutes ago)"
|
||||
if(closed_at)
|
||||
dat += "<br>Closed at: [gameTimestamp(wtime = closed_at)] (Approx [(world.time - closed_at) / 600] minutes ago)"
|
||||
dat += "<br><br>"
|
||||
if(initiator)
|
||||
dat += span_bold("Actions:") + " [Context(ref_src)]<br>"
|
||||
else
|
||||
dat += span_bold("DISCONNECTED") + "[GLOB.TAB][ClosureLinks(ref_src)]<br>"
|
||||
dat += "<br>" + span_bold("Log:") + "<br><br>"
|
||||
for(var/I in _interactions)
|
||||
dat += "[I]<br>"
|
||||
|
||||
var/datum/browser/popup = new(usr, "mhelp[id]", "Mento Help [id]", 620, 480)
|
||||
popup.add_head_content("<title>Ticket #[id]</title>")
|
||||
popup.set_content(dat.Join())
|
||||
popup.open()
|
||||
|
||||
/datum/mentor_help/tgui_fallback(payload)
|
||||
if(..())
|
||||
return
|
||||
|
||||
TicketPanelLegacy()
|
||||
|
||||
/datum/mentor_help/tgui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "MentorTicketPanel", "Ticket #[id] - [LinkedReplyName("\ref[src]")]")
|
||||
ui.open()
|
||||
|
||||
/datum/mentor_help/tgui_state(mob/user)
|
||||
return GLOB.tgui_mentor_state
|
||||
|
||||
/datum/mentor_help/tgui_data(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
data["id"] = id
|
||||
|
||||
var/ref_src = "\ref[src]"
|
||||
data["title"] = name
|
||||
data["name"] = LinkedReplyName(ref_src)
|
||||
|
||||
switch(state)
|
||||
if(AHELP_ACTIVE)
|
||||
data["state"] = "open"
|
||||
if(AHELP_RESOLVED)
|
||||
data["state"] = "resolved"
|
||||
else
|
||||
data["state"] = "unknown"
|
||||
|
||||
data["opened_at"] = (world.time - opened_at)
|
||||
data["closed_at"] = (world.time - closed_at)
|
||||
data["opened_at_date"] = gameTimestamp(wtime = opened_at)
|
||||
data["closed_at_date"] = gameTimestamp(wtime = closed_at)
|
||||
|
||||
data["actions"] = Context(ref_src)
|
||||
|
||||
data["log"] = _interactions
|
||||
|
||||
return data
|
||||
|
||||
/datum/mentor_help/tgui_act(action, params)
|
||||
if(..())
|
||||
return
|
||||
switch(action)
|
||||
if("escalate")
|
||||
Escalate()
|
||||
. = TRUE
|
||||
if("reopen")
|
||||
Reopen()
|
||||
. = TRUE
|
||||
if("legacy")
|
||||
TicketPanelLegacy()
|
||||
. = TRUE
|
||||
|
||||
//Kick ticket to admins
|
||||
/datum/mentor_help/proc/Escalate()
|
||||
if(tgui_alert(usr, "Really escalate this ticket to admins? No mentors will ever be able to interact with it again if you do.","Escalate",list("Yes","No")) != "Yes")
|
||||
return
|
||||
if (src.initiator == null) // You can't escalate a mentorhelp of someone who's logged out because it won't create the adminhelp properly
|
||||
to_chat(usr, span_mentor_pm_warning("Error: client not found, unable to escalate."))
|
||||
return
|
||||
var/datum/admin_help/AH = new /datum/admin_help(src.name, src.initiator, FALSE)
|
||||
message_mentors("[usr.ckey] escalated Ticket [TicketHref("#[id]")]")
|
||||
log_admin("[key_name(usr)] escalated mentorhelp [src.name]")
|
||||
to_chat(src.initiator, span_mentor("[usr.ckey] escalated your mentorhelp to admins."))
|
||||
AH._interactions = src._interactions
|
||||
GLOB.mhelp_tickets.active_tickets -= src
|
||||
GLOB.mhelp_tickets.resolved_tickets -= src
|
||||
qdel(src)
|
||||
|
||||
/datum/mentor_help/proc/Context(ref_src)
|
||||
if(!ref_src)
|
||||
ref_src = "\ref[src]"
|
||||
if(state == AHELP_ACTIVE)
|
||||
. += ClosureLinks(ref_src)
|
||||
if(state != AHELP_RESOLVED)
|
||||
. += " (<A href='byond://?_src_=mentorholder;[HrefToken()];mhelp=[ref_src];mhelp_action=escalate'>ESCALATE</A>)"
|
||||
|
||||
//Forwarded action from admin/Topic OR mentor/Topic depending on which rank the caller has
|
||||
/datum/mentor_help/proc/Action(action)
|
||||
switch(action)
|
||||
if("ticket")
|
||||
TicketPanel()
|
||||
if("reply")
|
||||
usr.client.cmd_mhelp_reply(initiator)
|
||||
if("resolve")
|
||||
Resolve()
|
||||
if("reopen")
|
||||
Reopen()
|
||||
if("escalate")
|
||||
Escalate()
|
||||
|
||||
//
|
||||
// TICKET STATCLICK
|
||||
//
|
||||
|
||||
/obj/effect/statclick/mhelp
|
||||
var/datum/mentor_help/mhelp_datum
|
||||
|
||||
INITIALIZE_IMMEDIATE(/obj/effect/statclick/mhelp)
|
||||
/obj/effect/statclick/mhelp/Initialize(mapload, datum/mentor_help/MH)
|
||||
mhelp_datum = MH
|
||||
. = ..()
|
||||
|
||||
/obj/effect/statclick/mhelp/update()
|
||||
return ..(mhelp_datum.name)
|
||||
|
||||
/obj/effect/statclick/mhelp/Click()
|
||||
mhelp_datum.TicketPanel()
|
||||
|
||||
/obj/effect/statclick/mhelp/Destroy()
|
||||
mhelp_datum = null
|
||||
return ..()
|
||||
|
||||
//
|
||||
// CLIENT PROCS
|
||||
//
|
||||
|
||||
/client/verb/mentorhelp(msg as text)
|
||||
set category = "Admin"
|
||||
set name = "Mentorhelp"
|
||||
|
||||
//handle muting and automuting
|
||||
if(prefs.muted & MUTE_ADMINHELP)
|
||||
to_chat(src, span_danger("Error: Mentor-PM: You cannot send adminhelps (Muted)."))
|
||||
return
|
||||
if(handle_spam_prevention(msg,MUTE_ADMINHELP))
|
||||
return
|
||||
|
||||
if(!msg)
|
||||
return
|
||||
|
||||
// Making sure there's actually a mentor or admin who can respond.
|
||||
var/list/admins = get_admin_counts()
|
||||
var/list/activeAdmins = admins["present"]
|
||||
var/list/mentors = GLOB.mentors
|
||||
if(!mentors.len && !activeAdmins.len)
|
||||
var/choice = tgui_alert(usr, "There are no active admins or mentors online. Would you like to make an ahelp instead, so that staff is notified of your issue? \
|
||||
Alternatively, you may go to the discord yourself and repeat your question in #cadet-academy. Please note, if choosing the later, do not include current-round information.",
|
||||
"Send to discord?", list("Admin-help!", "Still mentorhelp!", "Cancel"))
|
||||
if(choice == "Admin-help!")
|
||||
usr.client.adminhelp(msg)
|
||||
remove_verb(src, /client/verb/mentorhelp)
|
||||
spawn(1200)
|
||||
add_verb(src, /client/verb/mentorhelp) // 2 minute cd to prevent abusing this to spam admins.
|
||||
return
|
||||
else if(!choice || choice == "Cancel")
|
||||
return
|
||||
|
||||
|
||||
|
||||
//remove out adminhelp verb temporarily to prevent spamming of admins.
|
||||
remove_verb(src, /client/verb/mentorhelp)
|
||||
spawn(600)
|
||||
add_verb(src, /client/verb/mentorhelp) // 1 minute cool-down for mentorhelps
|
||||
|
||||
feedback_add_details("admin_verb","Mentorhelp") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
if(current_mentorhelp)
|
||||
var/input = tgui_alert(usr, "You already have a ticket open. Is this for the same issue?","Duplicate?",list("Yes","No"))
|
||||
if(!input)
|
||||
return
|
||||
if(input == "Yes")
|
||||
if(current_mentorhelp)
|
||||
log_admin("Mentorhelp: [key_name(src)]: [msg]")
|
||||
current_mentorhelp.MessageNoRecipient(msg)
|
||||
to_chat(usr, span_mentor_pm_notice("Mentor-PM to-" + span_bold("Mentors") + ": [msg]"))
|
||||
return
|
||||
else
|
||||
to_chat(usr, span_warning("Ticket not found, creating new one..."))
|
||||
else
|
||||
current_mentorhelp.AddInteraction("[usr.ckey] opened a new ticket.")
|
||||
current_mentorhelp.Resolve()
|
||||
|
||||
new /datum/mentor_help(msg, src, FALSE)
|
||||
|
||||
//admin proc
|
||||
/client/proc/cmd_mentor_ticket_panel()
|
||||
set name = "Mentor Ticket List"
|
||||
set category = "Admin.Misc"
|
||||
|
||||
var/browse_to
|
||||
|
||||
switch(tgui_input_list(usr, "Display which ticket list?", "List Choice", list("Active Tickets", "Resolved Tickets")))
|
||||
if("Active Tickets")
|
||||
browse_to = AHELP_ACTIVE
|
||||
if("Resolved Tickets")
|
||||
browse_to = AHELP_RESOLVED
|
||||
else
|
||||
return
|
||||
|
||||
GLOB.mhelp_tickets.BrowseTickets(browse_to)
|
||||
|
||||
/proc/message_mentors(var/msg)
|
||||
msg = span_mentor_channel(span_prefix("Mentor: " + span_message(msg)))
|
||||
|
||||
for(var/client/C in GLOB.mentors)
|
||||
to_chat(C, msg)
|
||||
for(var/client/C in GLOB.admins)
|
||||
to_chat(C, msg)
|
||||
@@ -102,7 +102,7 @@
|
||||
*
|
||||
* required payload list A list of the payload supposed to be set on the regular UI.
|
||||
*/
|
||||
/datum/proc/tgui_fallback(list/payload)
|
||||
/datum/proc/tgui_fallback(list/payload, mob/user)
|
||||
SHOULD_CALL_PARENT(TRUE)
|
||||
SEND_SIGNAL(src, COMSIG_UI_FALLBACK, usr)
|
||||
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
GLOBAL_DATUM_INIT(tgui_mentor_state, /datum/tgui_state/mentor_state, new)
|
||||
|
||||
/datum/tgui_state/mentor_state/can_use_topic(src_object, mob/user)
|
||||
if(has_mentor_powers(user.client))
|
||||
if(check_rights_for(user.client, R_ADMIN|R_EVENT|R_DEBUG|R_MENTOR))
|
||||
return STATUS_INTERACTIVE
|
||||
return STATUS_CLOSE
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* tgui state: ticket_state
|
||||
*
|
||||
* Grants the user UI_INTERACTIVE, if a ticket is open.
|
||||
*/
|
||||
|
||||
GLOBAL_DATUM_INIT(tgui_ticket_state, /datum/tgui_state/ticket_state, new)
|
||||
|
||||
/datum/tgui_state/ticket_state/can_use_topic(src_object, mob/user)
|
||||
//if (user.client.current_ticket)
|
||||
// return STATUS_INTERACTIVE
|
||||
//return STATUS_CLOSE
|
||||
return STATUS_INTERACTIVE
|
||||
@@ -430,7 +430,7 @@
|
||||
#ifdef TGUI_DEBUGGING
|
||||
log_tgui(user, "Fallback Triggered: [href_list["payload"]], Window: [window.id], Source: [src_object]")
|
||||
#endif
|
||||
src_object.tgui_fallback(payload)
|
||||
src_object.tgui_fallback(payload, user)
|
||||
if(TGUI_MANAGED_BYONDUI_TYPE_RENDER)
|
||||
var/byond_ui_id = payload[TGUI_MANAGED_BYONDUI_PAYLOAD_ID]
|
||||
if(!byond_ui_id || LAZYLEN(open_byondui_elements) > TGUI_MANAGED_BYONDUI_LIMIT)
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
return TRUE
|
||||
if(ADMIN_CHANNEL)
|
||||
if(check_rights(R_ADMIN, show_msg = FALSE))
|
||||
client.cmd_admin_say(entry)
|
||||
SSadmin_verbs.dynamic_invoke_verb(client, /datum/admin_verb/cmd_admin_say, entry)
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
|
||||
@@ -0,0 +1,295 @@
|
||||
//
|
||||
// CLIENT PROCS
|
||||
//
|
||||
|
||||
/client/verb/mentorhelp(msg as text)
|
||||
set category = "Admin"
|
||||
set name = "Mentorhelp"
|
||||
|
||||
//handle muting and automuting
|
||||
if(prefs.muted & MUTE_ADMINHELP)
|
||||
to_chat(src, span_danger("Error: Mentor-PM: You cannot send adminhelps (Muted)."))
|
||||
return
|
||||
if(handle_spam_prevention(msg,MUTE_ADMINHELP))
|
||||
return
|
||||
|
||||
if(!msg)
|
||||
return
|
||||
|
||||
//remove out adminhelp verb temporarily to prevent spamming of admins.
|
||||
remove_verb(src,/client/verb/mentorhelp)
|
||||
spawn(600)
|
||||
add_verb(src,/client/verb/mentorhelp ) // 1 minute cool-down for mentorhelps
|
||||
|
||||
feedback_add_details("admin_verb","Mentorhelp") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
if(current_ticket)
|
||||
var/input = tgui_alert(src, "You already have a ticket open. Is this for the same issue?","Duplicate?",list("Yes","No"))
|
||||
if(!input)
|
||||
return
|
||||
if(input == "Yes")
|
||||
if(current_ticket)
|
||||
log_admin("Mentorhelp: [key_name(src)]: [msg]")
|
||||
current_ticket.MessageNoRecipient(msg)
|
||||
to_chat(src, span_adminnotice(span_mentor("Mentor-PM to-" + span_bold("Mentors") + ": [msg]")))
|
||||
return
|
||||
else
|
||||
to_chat(src, span_warning("Ticket not found, creating new one..."))
|
||||
else
|
||||
current_ticket.AddInteraction("[usr.ckey] opened a new ticket.")
|
||||
current_ticket.Resolve()
|
||||
|
||||
new /datum/ticket(msg, src, FALSE, 0)
|
||||
|
||||
//admin proc
|
||||
ADMIN_VERB(cmd_mentor_ticket_panel, (R_ADMIN|R_SERVER|R_MOD|R_MENTOR), "Mentor Ticket List", "Opens the list of mentor tickets", "Admin.Misc")
|
||||
var/browse_to
|
||||
|
||||
switch(tgui_input_list(user, "Display which ticket list?", "List Choice", list("Active Tickets", "Resolved Tickets")))
|
||||
if("Active Tickets")
|
||||
browse_to = AHELP_ACTIVE
|
||||
if("Resolved Tickets")
|
||||
browse_to = AHELP_RESOLVED
|
||||
else
|
||||
return
|
||||
|
||||
GLOB.tickets.BrowseTickets(browse_to)
|
||||
|
||||
/proc/message_mentors(var/msg)
|
||||
msg = span_mentor_channel(span_prefix("Mentor:") + span_message("[msg]"))
|
||||
|
||||
for(var/client/C in GLOB.admins)
|
||||
to_chat(C, msg)
|
||||
|
||||
//
|
||||
// CLIENT PROCS
|
||||
//
|
||||
|
||||
/client/verb/requesthelp()
|
||||
set category = "Admin"
|
||||
set name = "Request help"
|
||||
set hidden = 1
|
||||
|
||||
var/mhelp = tgui_alert(usr, "Select the help you need.","Request for Help",list("Adminhelp","Mentorhelp"))
|
||||
if(!mhelp)
|
||||
return
|
||||
|
||||
var/msg = tgui_input_text(usr, "Input your request for help.", "Request for Help ([mhelp])", multiline = TRUE)
|
||||
if(!msg)
|
||||
return
|
||||
|
||||
if (mhelp == "Mentorhelp")
|
||||
mentorhelp(msg)
|
||||
return
|
||||
|
||||
adminhelp(msg)
|
||||
|
||||
/client/verb/adminhelp(msg as text)
|
||||
set category = "Admin"
|
||||
set name = "Adminhelp"
|
||||
|
||||
//handle muting and automuting
|
||||
if(prefs.muted & MUTE_ADMINHELP)
|
||||
to_chat(src, span_danger("Error: Admin-PM: You cannot send adminhelps (Muted)."))
|
||||
return
|
||||
if(handle_spam_prevention(msg,MUTE_ADMINHELP))
|
||||
return
|
||||
|
||||
if(!msg)
|
||||
return
|
||||
|
||||
//remove out adminhelp verb temporarily to prevent spamming of admins.
|
||||
remove_verb(src,/client/verb/adminhelp)
|
||||
spawn(1200)
|
||||
add_verb(src,/client/verb/adminhelp ) // 2 minute cool-down for adminhelp
|
||||
|
||||
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)
|
||||
var/input = tgui_alert(src, "You already have a ticket open. Is this for the same issue?","Duplicate?",list("Yes","No"))
|
||||
if(!input)
|
||||
return
|
||||
if(input == "Yes")
|
||||
if(current_ticket)
|
||||
current_ticket.MessageNoRecipient(msg)
|
||||
to_chat(src, span_adminnotice("PM to-" + span_bold("Admins") + ": [msg]"))
|
||||
return
|
||||
else
|
||||
to_chat(src, span_warning("Ticket not found, creating new one..."))
|
||||
else if(current_ticket)
|
||||
current_ticket.AddInteraction("[key_name_admin(usr)] opened a new ticket.")
|
||||
current_ticket.Close()
|
||||
|
||||
new /datum/ticket(msg, src, FALSE, 1)
|
||||
|
||||
//admin proc
|
||||
/client/proc/cmd_admin_ticket_panel()
|
||||
set name = "Show Ticket List"
|
||||
set category = "Admin.Misc"
|
||||
|
||||
if(!check_rights(R_ADMIN|R_MOD|R_DEBUG|R_EVENT, TRUE))
|
||||
return
|
||||
|
||||
var/browse_to
|
||||
|
||||
switch(tgui_input_list(usr, "Display which ticket list?", "List Choice", 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.tickets.BrowseTickets(browse_to)
|
||||
|
||||
//// VOREstation Additions Below
|
||||
|
||||
/datum/ticket/proc/send2adminchat()
|
||||
if(!CONFIG_GET(string/chat_webhook_url))
|
||||
return
|
||||
|
||||
var/list/adm = get_admin_counts()
|
||||
var/list/afkmins = adm["afk"]
|
||||
var/list/allmins = adm["total"]
|
||||
|
||||
spawn(0) //Unreliable world.Exports()
|
||||
var/query_string = "type=adminhelp"
|
||||
query_string += "&key=[url_encode(CONFIG_GET(string/chat_webhook_key))]"
|
||||
query_string += "&from=[url_encode(key_name(initiator))]"
|
||||
query_string += "&msg=[url_encode(html_decode(name))]"
|
||||
query_string += "&admin_number=[allmins.len]"
|
||||
query_string += "&admin_number_afk=[afkmins.len]"
|
||||
world.Export("[CONFIG_GET(string/chat_webhook_url)]?[query_string]")
|
||||
|
||||
/client/verb/adminspice()
|
||||
set category = "Admin"
|
||||
set name = "Request Spice"
|
||||
set desc = "Request admins to spice round up for you"
|
||||
|
||||
//handle muting and automuting
|
||||
if(prefs.muted & MUTE_ADMINHELP)
|
||||
to_chat(usr, span_danger("Error: You cannot request spice (muted from adminhelps)."))
|
||||
return
|
||||
|
||||
if(tgui_alert(usr, "Are you sure you want to request the admins spice things up for you? You accept the consequences if you do.","Spicy!",list("Yes","No")) == "Yes")
|
||||
message_admins("[ADMIN_FULLMONTY(usr)] has requested the round be spiced up a little.")
|
||||
to_chat(usr, span_notice("You have requested some more spice in your round."))
|
||||
else
|
||||
to_chat(usr, span_notice("Spice request cancelled."))
|
||||
return
|
||||
|
||||
//if they requested spice, then remove spice verb temporarily to prevent spamming
|
||||
remove_verb(usr,/client/verb/adminspice)
|
||||
spawn(10 MINUTES)
|
||||
if(usr) // In case we left in the 10 minute cooldown
|
||||
add_verb(usr,/client/verb/adminspice ) // 10 minute cool-down for spice request
|
||||
|
||||
//
|
||||
// MENTOR PROCS
|
||||
//
|
||||
|
||||
/client/proc/cmd_mhelp_reply(whom)
|
||||
if(prefs.muted & MUTE_ADMINHELP)
|
||||
to_chat(src, span_admin_pm_warning("Error: Mentor-PM: You are unable to use admin PM-s (muted)."))
|
||||
return
|
||||
var/client/C
|
||||
if(istext(whom))
|
||||
C = GLOB.directory[whom]
|
||||
else if(istype(whom,/client))
|
||||
C = whom
|
||||
if(!C)
|
||||
if(src.holder)
|
||||
to_chat(src, span_admin_pm_warning("Error: Mentor-PM: Client not found."))
|
||||
return
|
||||
|
||||
var/datum/ticket/T = C.current_ticket
|
||||
|
||||
if(T)
|
||||
message_mentors(span_mentor_channel("[src] has started replying to [C]'s mentor help."))
|
||||
var/msg = tgui_input_text(src,"Message:", "Private message to [C]", multiline = TRUE)
|
||||
if (!msg)
|
||||
message_mentors(span_mentor_channel("[src] has cancelled their reply to [C]'s mentor help."))
|
||||
return
|
||||
cmd_mentor_pm(whom, msg, T)
|
||||
|
||||
/client/proc/cmd_mentor_pm(whom, msg, datum/ticket/T)
|
||||
set category = "Admin"
|
||||
set name = "Mentor-PM"
|
||||
set hidden = 1
|
||||
|
||||
if(prefs.muted & MUTE_ADMINHELP)
|
||||
to_chat(src, span_mentor_warning("Error: Mentor-PM: You are unable to use mentor PM-s (muted)."))
|
||||
return
|
||||
|
||||
//Not a mentor and no open ticket
|
||||
if(!src.holder && !current_ticket)
|
||||
to_chat(src, span_mentor_warning("You can no longer reply to this ticket, please open another one by using the Mentorhelp verb if need be."))
|
||||
to_chat(src, span_mentor_notice("Message: [msg]"))
|
||||
return
|
||||
|
||||
var/client/recipient
|
||||
|
||||
if(istext(whom))
|
||||
recipient = GLOB.directory[whom]
|
||||
|
||||
else if(istype(whom,/client))
|
||||
recipient = whom
|
||||
|
||||
//get message text, limit it's length.and clean/escape html
|
||||
if(!msg)
|
||||
msg = tgui_input_text(src,"Message:", "Mentor-PM to [whom]", multiline = TRUE)
|
||||
|
||||
if(!msg)
|
||||
return
|
||||
|
||||
if(prefs.muted & MUTE_ADMINHELP)
|
||||
to_chat(src, span_mentor_warning("Error: Mentor-PM: You are unable to use mentor PM-s (muted)."))
|
||||
return
|
||||
|
||||
if(!recipient)
|
||||
if(src.holder)
|
||||
to_chat(src, span_mentor_warning("Error:Mentor-PM: Client not found."))
|
||||
to_chat(src, msg)
|
||||
else
|
||||
log_admin("Mentorhelp: [key_name(src)]: [msg]")
|
||||
current_ticket.MessageNoRecipient(msg)
|
||||
return
|
||||
|
||||
//Has mentor powers but the recipient no longer has an open ticket
|
||||
if(src.holder && !recipient.current_ticket)
|
||||
to_chat(src, span_mentor_warning("You can no longer reply to this ticket."))
|
||||
to_chat(src, span_mentor_notice("Message: [msg]"))
|
||||
return
|
||||
|
||||
if (src.handle_spam_prevention(msg,MUTE_ADMINHELP))
|
||||
return
|
||||
|
||||
msg = trim(sanitize(copytext(msg,1,MAX_MESSAGE_LEN)))
|
||||
if(!msg)
|
||||
return
|
||||
|
||||
var/interaction_message = span_mentor_notice("Mentor-PM from-<b>[src]</b> to-<b>[recipient]</b>: [msg]")
|
||||
|
||||
if (recipient.current_ticket && !recipient.holder && recipient.current_ticket.level == 0)
|
||||
recipient.current_ticket.AddInteraction(interaction_message)
|
||||
if (src.current_ticket && !src.holder && src.current_ticket.level == 0)
|
||||
src.current_ticket.AddInteraction(interaction_message)
|
||||
|
||||
// It's a little fucky if they're both mentors, but while admins may need to adminhelp I don't really see any reason a mentor would have to mentorhelp since you can literally just ask any other mentors online
|
||||
if (recipient.holder && src.holder)
|
||||
if (recipient.current_ticket && recipient != src && recipient.current_ticket.level == 0)
|
||||
recipient.current_ticket.AddInteraction(interaction_message)
|
||||
if (src.current_ticket && src.current_ticket.level == 0)
|
||||
src.current_ticket.AddInteraction(interaction_message)
|
||||
|
||||
to_chat(recipient, span_mentor(span_italics("Mentor-PM from-<b><a href='byond://?mentorhelp_msg=\ref[src]'>[src]</a></b>: [msg]")))
|
||||
to_chat(src, span_mentor(span_italics("Mentor-PM to-<b>[recipient]</b>: [msg]")))
|
||||
|
||||
log_admin("[key_name(src)]->[key_name(recipient)]: [msg]")
|
||||
|
||||
if(recipient.prefs?.read_preference(/datum/preference/toggle/play_mentorhelp_ping))
|
||||
recipient << 'sound/effects/mentorhelp.mp3'
|
||||
|
||||
for(var/client/C in GLOB.admins)
|
||||
if (C != recipient && C != src)
|
||||
to_chat(C, interaction_message)
|
||||
@@ -1,12 +1,32 @@
|
||||
/client/var/datum/admin_help/current_ticket //the current ticket the (usually) not-admin client is dealing with
|
||||
/client/var/datum/ticket/current_ticket //the current ticket the (usually) not-admin client is dealing with
|
||||
/client/var/datum/ticket/selected_ticket //the current ticket being viewed in the Tickets Panel (usually) admin/mentor client
|
||||
|
||||
/proc/get_ahelp_channel()
|
||||
var/datum/tgs_api/v5/api = TGS_READ_GLOBAL(tgs)
|
||||
if(istype(api) && CONFIG_GET(string/ahelp_channel_tag))
|
||||
for(var/datum/tgs_chat_channel/channel in api.chat_channels)
|
||||
if(channel.custom_tag == CONFIG_GET(string/ahelp_channel_tag))
|
||||
return list(channel)
|
||||
return 0
|
||||
|
||||
/proc/ahelp_discord_message(var/message)
|
||||
if(!message)
|
||||
return
|
||||
if(CONFIG_GET(flag/discord_ahelps_disabled))
|
||||
return
|
||||
var/datum/tgs_chat_channel/ahelp_channel = get_ahelp_channel()
|
||||
if(ahelp_channel)
|
||||
world.TgsChatBroadcast(message,ahelp_channel)
|
||||
else
|
||||
world.TgsTargetedChatBroadcast(message,TRUE)
|
||||
|
||||
//
|
||||
//TICKET MANAGER
|
||||
//
|
||||
|
||||
GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
|
||||
GLOBAL_DATUM_INIT(tickets, /datum/tickets, new)
|
||||
|
||||
/datum/admin_help_tickets
|
||||
/datum/tickets
|
||||
var/list/active_tickets = list()
|
||||
var/list/closed_tickets = list()
|
||||
var/list/resolved_tickets = list()
|
||||
@@ -15,7 +35,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
|
||||
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()
|
||||
/datum/tickets/Destroy()
|
||||
QDEL_LIST(active_tickets)
|
||||
QDEL_LIST(closed_tickets)
|
||||
QDEL_LIST(resolved_tickets)
|
||||
@@ -25,7 +45,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
|
||||
return ..()
|
||||
|
||||
//private
|
||||
/datum/admin_help_tickets/proc/ListInsert(datum/admin_help/new_ticket)
|
||||
/datum/tickets/proc/ListInsert(datum/ticket/new_ticket)
|
||||
var/list/ticket_list
|
||||
switch(new_ticket.state)
|
||||
if(AHELP_ACTIVE)
|
||||
@@ -39,16 +59,17 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
|
||||
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)
|
||||
var/datum/ticket/T = ticket_list[I]
|
||||
if(T.id > new_ticket.id)
|
||||
ticket_list.Insert(I, new_ticket)
|
||||
return
|
||||
ticket_list += new_ticket
|
||||
|
||||
/datum/tickets/proc/BrowseTickets(state)
|
||||
tgui_interact(usr)
|
||||
|
||||
//opens the ticket listings for one of the 3 states
|
||||
/datum/admin_help_tickets/proc/BrowseTickets(state)
|
||||
if(!check_rights(R_ADMIN|R_SERVER)) //Prevents non-staff from opening the list of ahelp tickets
|
||||
return
|
||||
/datum/tickets/proc/BrowseTicketsLegacy(state)
|
||||
var/list/l2b
|
||||
var/title
|
||||
switch(state)
|
||||
@@ -65,50 +86,116 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
|
||||
return
|
||||
var/list/dat = list("<html><head><title>[title]</title></head>")
|
||||
dat += "<A href='byond://?_src_=holder;[HrefToken()];ahelp_tickets=[state]'>Refresh</A><br><br>"
|
||||
for(var/datum/admin_help/AH as anything in l2b)
|
||||
dat += span_adminnotice(span_adminhelp("Ticket #[AH.id]") + ": <A href='byond://?_src_=holder;ahelp=\ref[AH];[HrefToken()];ahelp_action=ticket'>[AH.initiator_key_name]: [AH.name]</A>") + "<br>"
|
||||
for(var/datum/ticket/T as anything in l2b)
|
||||
dat += span_adminnotice(span_adminhelp("Ticket #[T.id]") + ": <A href='byond://?_src_=holder;ahelp=\ref[T];[HrefToken()];ahelp_action=ticket'>[T.initiator_key_name]: [T.name]</A>") + "<br>"
|
||||
dat += "</html>"
|
||||
usr << browse(dat.Join(), "window=ahelp_list[state];size=600x480")
|
||||
|
||||
//Tickets statpanel
|
||||
/datum/admin_help_tickets/proc/stat_entry()
|
||||
/datum/tickets/proc/stat_entry(client/target)
|
||||
SHOULD_CALL_PARENT(TRUE)
|
||||
SHOULD_NOT_SLEEP(TRUE)
|
||||
var/list/L = list()
|
||||
var/num_disconnected = 0
|
||||
L[++L.len] = list("== Admin Tickets ==", "", null, null)
|
||||
L[++L.len] = list("Active Tickets:", "[astatclick.update("[active_tickets.len]")]", null, REF(astatclick))
|
||||
astatclick.update("[active_tickets.len]")
|
||||
for(var/datum/admin_help/AH as anything in active_tickets)
|
||||
if(AH.initiator)
|
||||
L[++L.len] = list("#[AH.id]. [AH.initiator_key_name]:", "[AH.statclick.update()]", REF(AH))
|
||||
var/num_adm_tickets_disconnected = 0
|
||||
var/num_men_tickets_disconnected = 0
|
||||
|
||||
var/list/admin_tickets = list()
|
||||
var/list/mentor_tickets = list()
|
||||
|
||||
for(var/datum/ticket/T as anything in active_tickets)
|
||||
switch (T.level)
|
||||
if(1)
|
||||
admin_tickets += T
|
||||
if(0)
|
||||
mentor_tickets += T
|
||||
|
||||
var/closed_admin_tickets = 0
|
||||
var/closed_mentor_tickets = 0
|
||||
|
||||
for(var/datum/ticket/T as anything in closed_tickets)
|
||||
switch (T.level)
|
||||
if(1)
|
||||
closed_admin_tickets++
|
||||
if(0)
|
||||
closed_mentor_tickets++
|
||||
|
||||
var/resolved_admin_tickets = 0
|
||||
var/resolved_mentor_tickets = 0
|
||||
|
||||
for(var/datum/ticket/T as anything in resolved_tickets)
|
||||
switch (T.level)
|
||||
if(1)
|
||||
resolved_admin_tickets++
|
||||
if(0)
|
||||
resolved_mentor_tickets++
|
||||
|
||||
if(check_rights_for(target, R_ADMIN|R_SERVER|R_MOD))
|
||||
L[++L.len] = list("== Admin Tickets ==", "", null, null)
|
||||
L[++L.len] = list("Active Tickets:", "[astatclick.update("[admin_tickets.len]")]", null, REF(astatclick))
|
||||
for(var/datum/ticket/T as anything in admin_tickets)
|
||||
if(T.initiator)
|
||||
L[++L.len] = list("ADM #[T.id]. [T.initiator_key_name]:", T.name, null, REF(T.statclick))
|
||||
else
|
||||
num_adm_tickets_disconnected++
|
||||
if(num_adm_tickets_disconnected)
|
||||
L[++L.len] = list("Disconnected:", "[astatclick.update("[num_adm_tickets_disconnected]")]", null, REF(astatclick))
|
||||
L[++L.len] = list("Closed Tickets:", "[cstatclick.update("[closed_admin_tickets]")]", null, REF(cstatclick))
|
||||
L[++L.len] = list("Resolved Tickets:", "[rstatclick.update("[resolved_admin_tickets]")]", null, REF(rstatclick))
|
||||
|
||||
L[++L.len] = list("== Mentor Tickets ==", "", null, null)
|
||||
L[++L.len] = list("Active Tickets:", "[astatclick.update("[mentor_tickets.len]")]", null, REF(astatclick))
|
||||
for(var/datum/ticket/T as anything in mentor_tickets)
|
||||
if(T.initiator)
|
||||
L[++L.len] = list("MEN #[T.id]. [T.initiator_key_name]:", T.name, null, REF(T.statclick))
|
||||
else
|
||||
++num_disconnected
|
||||
if(num_disconnected)
|
||||
L[++L.len] = list("Disconnected:", "[astatclick.update("[num_disconnected]")]", null, REF(astatclick))
|
||||
L[++L.len] = list("Closed Tickets:", "[cstatclick.update("[closed_tickets.len]")]", null, REF(cstatclick))
|
||||
L[++L.len] = list("Resolved Tickets:", "[rstatclick.update("[resolved_tickets.len]")]", null, REF(rstatclick))
|
||||
num_men_tickets_disconnected++
|
||||
if(num_men_tickets_disconnected)
|
||||
L[++L.len] = list("Disconnected:", "[astatclick.update("[num_men_tickets_disconnected]")]", null, REF(astatclick))
|
||||
L[++L.len] = list("Closed Tickets:", "[cstatclick.update("[closed_mentor_tickets]")]", null, REF(cstatclick))
|
||||
L[++L.len] = list("Resolved Tickets:", "[rstatclick.update("[resolved_mentor_tickets]")]", null, REF(rstatclick))
|
||||
|
||||
return L
|
||||
|
||||
//Reassociate still open ticket if one exists
|
||||
/datum/admin_help_tickets/proc/ClientLogin(client/C)
|
||||
/datum/tickets/proc/ClientLogin(client/C)
|
||||
C.current_ticket = CKey2ActiveTicket(C.ckey)
|
||||
if(C.current_ticket)
|
||||
C.current_ticket.AddInteraction("Client reconnected.")
|
||||
C.current_ticket.initiator = C
|
||||
// C.current_ticket.initiator.mob.throw_alert("open ticket", /obj/screen/alert/open_ticket) // Uncomment this line to enable player-side ticket ui
|
||||
|
||||
//Dissasociate ticket
|
||||
/datum/admin_help_tickets/proc/ClientLogout(client/C)
|
||||
/datum/tickets/proc/ClientLogout(client/C)
|
||||
if(C.current_ticket)
|
||||
C.current_ticket.AddInteraction("Client disconnected.")
|
||||
C.current_ticket.initiator = null
|
||||
C.current_ticket = null
|
||||
var/datum/ticket/T = C.current_ticket
|
||||
T.AddInteraction("Client disconnected.")
|
||||
// T.initiator.mob.clear_alert("open ticket") // Uncomment this line to enable player-side ticket ui
|
||||
T.initiator = null
|
||||
T = null
|
||||
|
||||
//Get a ticket given a ckey
|
||||
/datum/admin_help_tickets/proc/CKey2ActiveTicket(ckey)
|
||||
for(var/datum/admin_help/AH as anything in active_tickets)
|
||||
if(AH.initiator_ckey == ckey)
|
||||
return AH
|
||||
/datum/tickets/proc/CKey2ActiveTicket(ckey)
|
||||
for(var/datum/ticket/T as anything in active_tickets)
|
||||
if(T.initiator_ckey == ckey)
|
||||
return T
|
||||
|
||||
//Get a ticket by ticket id
|
||||
/datum/tickets/proc/ID2Ticket(id)
|
||||
if(!check_rights((R_ADMIN|R_SERVER|R_MOD|R_MENTOR), TRUE))
|
||||
message_admins("[usr] has attempted to look up a ticket with ID [id] without sufficent privileges.")
|
||||
return
|
||||
|
||||
for(var/datum/ticket/T as anything in active_tickets)
|
||||
if(T.id == id)
|
||||
return T
|
||||
|
||||
for(var/datum/ticket/T as anything in resolved_tickets)
|
||||
if(T.id == id)
|
||||
return T
|
||||
|
||||
for(var/datum/ticket/T as anything in closed_tickets)
|
||||
if(T.id == id)
|
||||
return T
|
||||
|
||||
//
|
||||
//TICKET LIST STATCLICK
|
||||
@@ -117,42 +204,70 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
|
||||
/obj/effect/statclick/ticket_list
|
||||
var/current_state
|
||||
|
||||
/obj/effect/statclick/ticket_list/Initialize(mapload, text, target)
|
||||
INITIALIZE_IMMEDIATE(/obj/effect/statclick/ticket_list)
|
||||
/obj/effect/statclick/ticket_list/Initialize(mapload, name, state)
|
||||
current_state = state
|
||||
. = ..()
|
||||
current_state = target
|
||||
|
||||
/obj/effect/statclick/ticket_list/Click()
|
||||
GLOB.ahelp_tickets.BrowseTickets(current_state)
|
||||
GLOB.tickets.BrowseTickets(current_state)
|
||||
|
||||
//called by admin topic
|
||||
/obj/effect/statclick/ticket_list/proc/Action()
|
||||
Click()
|
||||
//
|
||||
//TICKET DATUM
|
||||
//
|
||||
|
||||
/datum/admin_help
|
||||
/datum/ticket
|
||||
var/id
|
||||
var/name
|
||||
var/level = 0 // 0 = Mentor, 1 = Admin
|
||||
var/list/tags
|
||||
var/state = AHELP_ACTIVE
|
||||
|
||||
var/opened_at
|
||||
var/closed_at
|
||||
|
||||
var/client/initiator //semi-misnomer, it's the person who ahelped/was bwoinked
|
||||
var/handler = "/Unassigned\\" // The admin handling the ticket
|
||||
var/initiator_ckey
|
||||
var/initiator_key_name
|
||||
|
||||
var/list/_interactions //use AddInteraction() or, preferably, admin_ticket_log()
|
||||
|
||||
var/obj/effect/statclick/ahelp/statclick
|
||||
var/obj/effect/statclick/ticket/statclick
|
||||
|
||||
var/static/ticket_counter = 0
|
||||
|
||||
//call this on its own to create a ticket, don't manually assign current_ticket
|
||||
/*
|
||||
//call this on its own to create a ticket, don't manually assign current_mentorhelp
|
||||
//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)
|
||||
/datum/mentor_help/New(msg, client/C)
|
||||
initiator_ckey = C.ckey
|
||||
initiator_key_name = key_name(initiator, FALSE, TRUE)
|
||||
if(initiator.current_mentorhelp) //This is a bug
|
||||
log_debug("Ticket erroneously left open by code")
|
||||
initiator.current_mentorhelp.AddInteraction("Ticket erroneously left open by code")
|
||||
initiator.current_mentorhelp.Resolve()
|
||||
initiator.current_mentorhelp = src
|
||||
statclick = new(null, src)
|
||||
_interactions = list()
|
||||
log_admin("Mentorhelp: [key_name(C)]: [msg]")
|
||||
MessageNoRecipient(msg)
|
||||
//show it to the person adminhelping too
|
||||
to_chat(C, span_mentor(span_italics("Mentor-PM to-" + span_bold("Mentors") + ": [name]"))
|
||||
GLOB.ahelp_tickets.active_tickets += src */
|
||||
|
||||
/**
|
||||
* public
|
||||
*
|
||||
* Create a new Ticket.
|
||||
* Call this on its own to create a ticket, don't manually assign current_ticket
|
||||
*
|
||||
* required msg string The title of the ticket: usually the ahelp text
|
||||
* required C client The object or datum which owns the UI.
|
||||
* required is_bwoink boolean TRUE if this ticket was started by an admin PM
|
||||
* required level integer The level of the ticket. 0 = Admin, 1 = Mentor
|
||||
*/
|
||||
/datum/ticket/New(msg, client/C, is_bwoink, ticket_level)
|
||||
//clean the input msg
|
||||
msg = sanitize(copytext(msg,1,MAX_MESSAGE_LEN))
|
||||
if(!msg || !C || !C.mob)
|
||||
@@ -164,6 +279,8 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
|
||||
|
||||
name = msg
|
||||
|
||||
level = ticket_level
|
||||
|
||||
initiator = C
|
||||
initiator_ckey = initiator.ckey
|
||||
initiator_key_name = key_name(initiator, FALSE, TRUE)
|
||||
@@ -183,15 +300,28 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
|
||||
message_admins(span_blue("Ticket [TicketHref("#[id]")] created"))
|
||||
else
|
||||
MessageNoRecipient(parsed_message)
|
||||
send2adminchat() //VOREStation Add
|
||||
//show it to the person adminhelping too
|
||||
to_chat(C, span_admin_pm_notice("PM to-" + span_bold("Admins") + ": [name]"))
|
||||
switch(level)
|
||||
if(0)
|
||||
to_chat(C, span_mentor_notice("PM to-" + span_bold("Mentors") + ": [name]"))
|
||||
|
||||
if(1)
|
||||
to_chat(C, span_adminnotice("PM to-" + span_bold("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("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_admin_pm_notice("No active admins are online, your adminhelp was sent to the admin discord.")) //VOREStation Edit
|
||||
to_chat(C, span_notice("No active admins are online, your adminhelp was sent to the admin discord."))
|
||||
send2adminchat()
|
||||
|
||||
var/list/adm = get_admin_counts()
|
||||
var/list/activemins = adm["present"]
|
||||
var activeMins = activemins.len
|
||||
if(is_bwoink)
|
||||
ahelp_discord_message("ADMINHELP: FROM: [key_name_admin(usr)] TO [initiator_ckey]/[initiator_key_name] - MSG: **[msg]** - Heard by [activeMins] NON-AFK staff members.")
|
||||
else
|
||||
ahelp_discord_message("ADMINHELP: FROM: [initiator_ckey]/[initiator_key_name] - MSG: **[msg]** - Heard by [activeMins] NON-AFK staff members.")
|
||||
|
||||
// Also send it to discord since that's the hip cool thing now.
|
||||
SSwebhooks.send(
|
||||
@@ -203,86 +333,127 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
|
||||
)
|
||||
)
|
||||
|
||||
GLOB.ahelp_tickets.active_tickets += src
|
||||
GLOB.tickets.active_tickets += src
|
||||
|
||||
/datum/admin_help/Destroy()
|
||||
// Open a new chat with the user
|
||||
//var/datum/ticket_chat/TC = new()
|
||||
//TC.T = src
|
||||
//TC.tgui_interact(C.mob)
|
||||
|
||||
// C.mob.throw_alert("open ticket", /obj/screen/alert/open_ticket) // Uncomment this line to enable player-side ticket ui
|
||||
|
||||
/datum/ticket/Destroy()
|
||||
RemoveActive()
|
||||
GLOB.ahelp_tickets.closed_tickets -= src
|
||||
GLOB.ahelp_tickets.resolved_tickets -= src
|
||||
GLOB.tickets.closed_tickets -= src
|
||||
GLOB.tickets.resolved_tickets -= src
|
||||
return ..()
|
||||
|
||||
/datum/admin_help/proc/AddInteraction(formatted_message)
|
||||
_interactions += "[gameTimestamp()]: [formatted_message]"
|
||||
/datum/ticket/proc/AddInteraction(formatted_message)
|
||||
var/curinteraction = "[gameTimestamp()]: [formatted_message]"
|
||||
if(CONFIG_GET(flag/discord_ahelps_all))
|
||||
ahelp_discord_message("ADMINHELP: TICKETID:[id] [strip_html_properly(curinteraction)]")
|
||||
_interactions += curinteraction
|
||||
|
||||
/datum/ticket/proc/TicketPanel()
|
||||
tgui_interact(usr.client.mob)
|
||||
|
||||
//private
|
||||
/datum/admin_help/proc/FullMonty(ref_src)
|
||||
/datum/ticket/proc/FullMonty(ref_src, admin = FALSE)
|
||||
if(!ref_src)
|
||||
ref_src = "\ref[src]"
|
||||
if(initiator && initiator.mob)
|
||||
. = ADMIN_FULLMONTY_NONAME(initiator.mob)
|
||||
if(admin)
|
||||
. = ADMIN_FULLMONTY_NONAME(initiator.mob)
|
||||
else
|
||||
. = "Initiator disconnected."
|
||||
if(state == AHELP_ACTIVE)
|
||||
. += ClosureLinks(ref_src)
|
||||
|
||||
//private
|
||||
/datum/admin_help/proc/ClosureLinks(ref_src)
|
||||
/datum/ticket/proc/ClosureLinks(ref_src)
|
||||
if(!ref_src)
|
||||
ref_src = "\ref[src]"
|
||||
. = " (<A href='byond://?_src_=holder;ahelp=[ref_src];[HrefToken(TRUE)];ahelp_action=reject'>REJT</A>)"
|
||||
. += " (<A href='byond://?_src_=holder;ahelp=[ref_src];[HrefToken(TRUE)];ahelp_action=icissue'>IC</A>)"
|
||||
. += " (<A href='byond://?_src_=holder;ahelp=[ref_src];[HrefToken(TRUE)];ahelp_action=close'>CLOSE</A>)"
|
||||
. += " (<A href='byond://?_src_=holder;ahelp=[ref_src];[HrefToken(TRUE)];ahelp_action=resolve'>RSLVE</A>)"
|
||||
. += " (<A href='byond://?_src_=holder;ahelp=[ref_src];[HrefToken(TRUE)];ahelp_action=handleissue'>HANDLE</A>)"
|
||||
|
||||
if(level == 1)
|
||||
. = " (<A href='byond://?_src_=holder;ticket=[ref_src];[HrefToken(TRUE)];ticket_action=reject'>REJT</A>)"
|
||||
. += " (<A href='byond://?_src_=holder;ticket=[ref_src];[HrefToken(TRUE)];ticket_action=icissue'>IC</A>)"
|
||||
. += " (<A href='byond://?_src_=holder;ticket=[ref_src];[HrefToken(TRUE)];ticket_action=close'>CLOSE</A>)"
|
||||
. += " (<A href='byond://?_src_=holder;ticket=[ref_src];[HrefToken(TRUE)];ticket_action=resolve'>RSLVE</A>)"
|
||||
. += " (<A href='byond://?_src_=holder;ticket=[ref_src];[HrefToken(TRUE)];ticket_action=handleissue'>HANDLE</A>)"
|
||||
else
|
||||
. = " (<A href='byond://?_src_=holder;ticket=[ref_src];[HrefToken(TRUE)];ticket_action=resolve'>RSLVE</A>)"
|
||||
. += " (<A href='byond://?_src_=holder;ticket=[ref_src];[HrefToken(TRUE)];ticket_action=escalate'>ESCALATE</A>)"
|
||||
|
||||
//private
|
||||
/datum/admin_help/proc/LinkedReplyName(ref_src)
|
||||
/datum/ticket/proc/LinkedReplyName(ref_src)
|
||||
if(!ref_src)
|
||||
ref_src = "\ref[src]"
|
||||
return "<A href='byond://?_src_=holder;ahelp=[ref_src];[HrefToken()];ahelp_action=reply'>[initiator_key_name]</A>"
|
||||
return "<A href='byond://?_src_=holder;ticket=[ref_src];[HrefToken(TRUE)];ticket_action=reply'>[initiator_key_name]</A>"
|
||||
|
||||
//private
|
||||
/datum/admin_help/proc/TicketHref(msg, ref_src, action = "ticket")
|
||||
/datum/ticket/proc/TicketHref(msg, ref_src, action = "ticket")
|
||||
if(!ref_src)
|
||||
ref_src = "\ref[src]"
|
||||
return "<A href='byond://?_src_=holder;ahelp=[ref_src];[HrefToken()];ahelp_action=[action]'>[msg]</A>"
|
||||
return "<A href='byond://?_src_=holder;ticket=[ref_src];[HrefToken(TRUE)];ticket_action=[action]'>[msg]</A>"
|
||||
|
||||
/*
|
||||
var/chat_msg = span_notice("(<A href='byond://?_src_=holder;ahelp=[ref_src];[HrefToken()];ahelp_action=escalate'>ESCALATE</A>) Ticket [TicketHref("#[id]", ref_src)]" + span_bold(": [LinkedReplyName(ref_src)]:") + " [msg]")
|
||||
*/
|
||||
|
||||
//message from the initiator without a target, all admins will see this
|
||||
//won't bug irc
|
||||
/datum/admin_help/proc/MessageNoRecipient(msg)
|
||||
/datum/ticket/proc/MessageNoRecipient(msg)
|
||||
var/ref_src = "\ref[src]"
|
||||
var/chat_msg = span_admin_pm_notice(span_adminhelp("Ticket [TicketHref("#[id]", ref_src)]") + span_bold(": [LinkedReplyName(ref_src)] [FullMonty(ref_src)]:") + msg)
|
||||
|
||||
AddInteraction(span_red("[LinkedReplyName(ref_src)]: [msg]"))
|
||||
//send this msg to all admins
|
||||
|
||||
for(var/client/X in GLOB.admins)
|
||||
if(!check_rights_for(X, R_ADMIN))
|
||||
continue
|
||||
if(X.prefs?.read_preference(/datum/preference/toggle/holder/play_adminhelp_ping))
|
||||
X << 'sound/effects/adminhelp.ogg'
|
||||
window_flash(X)
|
||||
to_chat(X, chat_msg)
|
||||
if(level == 0)
|
||||
for (var/client/C in GLOB.admins)
|
||||
if (C.prefs?.read_preference(/datum/preference/toggle/play_mentorhelp_ping))
|
||||
C << 'sound/effects/mentorhelp.mp3'
|
||||
message_mentors(chat_msg)
|
||||
else if(level == 1)
|
||||
for(var/client/X in GLOB.admins)
|
||||
if(X.prefs?.read_preference(/datum/preference/toggle/holder/play_adminhelp_ping))
|
||||
X << 'sound/effects/adminhelp.ogg'
|
||||
window_flash(X)
|
||||
to_chat(X, chat_msg)
|
||||
|
||||
/*
|
||||
//Reopen a closed ticket
|
||||
/datum/mentor_help/proc/Reopen()
|
||||
switch(state)
|
||||
if(AHELP_RESOLVED)
|
||||
feedback_dec("ahelp_resolve")
|
||||
AddInteraction(span_purple("Reopened by [usr.ckey]"))
|
||||
if(initiator)
|
||||
to_chat(initiator, span_filter_adminlog(span_purple("Ticket [TicketHref("#[id]")] was reopened by [usr.ckey].")))
|
||||
var/msg = span_adminhelp("Ticket [TicketHref("#[id]")] reopened by [usr.ckey].")
|
||||
message_mentors(msg)
|
||||
log_admin(msg)
|
||||
*/
|
||||
|
||||
//Reopen a closed ticket
|
||||
/datum/admin_help/proc/Reopen()
|
||||
/datum/ticket/proc/Reopen()
|
||||
if(state == AHELP_ACTIVE)
|
||||
to_chat(usr, span_warning("This ticket is already open."))
|
||||
return
|
||||
|
||||
if(GLOB.ahelp_tickets.CKey2ActiveTicket(initiator_ckey))
|
||||
if(GLOB.tickets.CKey2ActiveTicket(initiator_ckey))
|
||||
to_chat(usr, span_warning("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
|
||||
GLOB.tickets.active_tickets += src
|
||||
GLOB.tickets.closed_tickets -= src
|
||||
GLOB.tickets.resolved_tickets -= src
|
||||
switch(state)
|
||||
if(AHELP_CLOSED)
|
||||
feedback_dec("ahelp_close")
|
||||
feedback_dec("ticket_close")
|
||||
if(AHELP_RESOLVED)
|
||||
feedback_dec("ahelp_resolve")
|
||||
feedback_dec("ticket_resolve")
|
||||
state = AHELP_ACTIVE
|
||||
closed_at = null
|
||||
if(initiator)
|
||||
@@ -294,8 +465,8 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
|
||||
var/msg = span_adminhelp("Ticket [TicketHref("#[id]")] reopened by [key_name_admin(usr)].")
|
||||
message_admins(msg)
|
||||
log_admin(msg)
|
||||
feedback_inc("ahelp_reopen")
|
||||
TicketPanel() //can only be done from here, so refresh it
|
||||
feedback_inc("ticket_reopen")
|
||||
//TicketPanel() //can only be done from here, so refresh it
|
||||
|
||||
SSwebhooks.send(
|
||||
WEBHOOK_AHELP_SENT,
|
||||
@@ -306,22 +477,22 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
|
||||
)
|
||||
|
||||
//private
|
||||
/datum/admin_help/proc/RemoveActive()
|
||||
/datum/ticket/proc/RemoveActive()
|
||||
if(state != AHELP_ACTIVE)
|
||||
return
|
||||
closed_at = world.time
|
||||
QDEL_NULL(statclick)
|
||||
GLOB.ahelp_tickets.active_tickets -= src
|
||||
GLOB.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(silent = FALSE)
|
||||
/datum/ticket/proc/Close(silent = FALSE)
|
||||
if(state != AHELP_ACTIVE)
|
||||
return
|
||||
RemoveActive()
|
||||
state = AHELP_CLOSED
|
||||
GLOB.ahelp_tickets.ListInsert(src)
|
||||
GLOB.tickets.ListInsert(src)
|
||||
AddInteraction(span_filter_adminlog(span_red("Closed by [key_name_admin(usr)].")))
|
||||
if(initiator)
|
||||
to_chat(initiator, span_filter_adminlog("[span_red("Ticket [TicketHref("#[id]")] was closed by [key_name(usr,FALSE,FALSE)].")]"))
|
||||
@@ -338,34 +509,41 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
|
||||
"color" = COLOR_WEBHOOK_BAD
|
||||
)
|
||||
)
|
||||
// initiator?.mob?.clear_alert("open ticket") // Uncomment this line to enable player-side ticket ui
|
||||
|
||||
//Mark open ticket as resolved/legitimate, returns ahelp verb
|
||||
/datum/admin_help/proc/Resolve(silent = FALSE)
|
||||
/datum/ticket/proc/Resolve(silent = FALSE)
|
||||
if(state != AHELP_ACTIVE)
|
||||
return
|
||||
RemoveActive()
|
||||
state = AHELP_RESOLVED
|
||||
GLOB.ahelp_tickets.ListInsert(src)
|
||||
GLOB.tickets.ListInsert(src)
|
||||
|
||||
AddInteraction(span_filter_adminlog(span_green("Resolved by [key_name_admin(usr)].")))
|
||||
if(initiator)
|
||||
to_chat(initiator, span_filter_adminlog("[span_green("Ticket [TicketHref("#[id]")] was marked resolved by [key_name(usr,FALSE,FALSE)].")]"))
|
||||
if(!silent)
|
||||
feedback_inc("ahelp_resolve")
|
||||
feedback_inc("ticket_resolve")
|
||||
var/msg = "Ticket [TicketHref("#[id]")] resolved by [key_name_admin(usr)]"
|
||||
message_admins(msg)
|
||||
if(type == 1)
|
||||
message_mentors(msg)
|
||||
else if (type == 0)
|
||||
message_admins(msg)
|
||||
|
||||
log_admin(msg)
|
||||
SSwebhooks.send(
|
||||
WEBHOOK_AHELP_SENT,
|
||||
list(
|
||||
"name" = "Ticket ([id]) (Game ID: [game_id]) resolved.",
|
||||
"body" = "Marked as Resolved by [key_name(usr)].",
|
||||
"color" = COLOR_WEBHOOK_GOOD
|
||||
if(type == 1)
|
||||
SSwebhooks.send(
|
||||
WEBHOOK_AHELP_SENT,
|
||||
list(
|
||||
"name" = "Ticket ([id]) (Game ID: [game_id]) resolved.",
|
||||
"body" = "Marked as Resolved by [key_name(usr)].",
|
||||
"color" = COLOR_WEBHOOK_GOOD
|
||||
)
|
||||
)
|
||||
)
|
||||
// initiator?.mob?.clear_alert("open ticket") // Uncomment this line to enable player-side ticket ui
|
||||
|
||||
//Close and return ahelp verb, use if ticket is incoherent
|
||||
/datum/admin_help/proc/Reject(key_name = key_name_admin(usr))
|
||||
/datum/ticket/proc/Reject(key_name = key_name_admin(usr))
|
||||
if(state != AHELP_ACTIVE)
|
||||
return
|
||||
|
||||
@@ -393,7 +571,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
|
||||
)
|
||||
|
||||
//Resolve ticket with IC Issue message
|
||||
/datum/admin_help/proc/ICIssue(key_name = key_name_admin(usr))
|
||||
/datum/ticket/proc/ICIssue(key_name = key_name_admin(usr))
|
||||
if(state != AHELP_ACTIVE)
|
||||
return
|
||||
|
||||
@@ -402,7 +580,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
|
||||
msg += "[span_red("Your AdminHelp may also be unanswerable due to ongoing events.")]"
|
||||
|
||||
if(initiator)
|
||||
to_chat(initiator,span_filter_pm(msg))
|
||||
to_chat(initiator, span_filter_pm(msg))
|
||||
|
||||
feedback_inc("ahelp_icissue")
|
||||
msg = "Ticket [TicketHref("#[id]")] marked as IC by [key_name_admin(usr)]"
|
||||
@@ -420,11 +598,20 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
|
||||
)
|
||||
|
||||
//Resolve ticket with IC Issue message
|
||||
/datum/admin_help/proc/HandleIssue()
|
||||
/datum/ticket/proc/HandleIssue()
|
||||
if(state != AHELP_ACTIVE)
|
||||
return
|
||||
|
||||
var/msg = span_red("Your AdminHelp is being handled by [key_name(usr,FALSE,FALSE)] please be patient.")
|
||||
if(handler == key_name(usr, FALSE, TRUE))
|
||||
to_chat(usr, span_red("You are already handling this ticket."))
|
||||
return
|
||||
|
||||
var/msg
|
||||
switch(level)
|
||||
if(0)
|
||||
msg = span_green("Your MentorHelp is being handled by [key_name(usr,FALSE,FALSE)] please be patient.")
|
||||
if(1)
|
||||
msg = span_red("Your AdminHelp is being handled by [key_name(usr,FALSE,FALSE)] please be patient.")
|
||||
|
||||
if(initiator)
|
||||
to_chat(initiator, msg)
|
||||
@@ -434,51 +621,16 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
|
||||
message_admins(msg)
|
||||
log_admin(msg)
|
||||
AddInteraction("[key_name_admin(usr)] is now handling this ticket.")
|
||||
var/query_string = "type=admintake"
|
||||
query_string += "&key=[url_encode(CONFIG_GET(string/chat_webhook_key))]"
|
||||
query_string += "&admin=[url_encode(key_name(usr))]"
|
||||
query_string += "&user=[url_encode(key_name(initiator))]"
|
||||
world.Export("[CONFIG_GET(string/chat_webhook_url)]?[query_string]")
|
||||
handler = key_name(usr, FALSE, TRUE)
|
||||
SSwebhooks.send(
|
||||
WEBHOOK_AHELP_SENT,
|
||||
list(
|
||||
"name" = "Ticket ([id]) (Game ID: [game_id]) being handled.",
|
||||
"body" = "[key_name(usr)] is now handling the ticket."
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
//Show the ticket panel
|
||||
/datum/admin_help/proc/TicketPanel()
|
||||
tgui_interact(usr.client.mob)
|
||||
|
||||
/datum/admin_help/proc/TicketPanelLegacy()
|
||||
var/list/dat = list("<html><head><title>Ticket #[id]</title></head>")
|
||||
var/ref_src = "\ref[src]"
|
||||
dat += "<h4>Admin Help Ticket #[id]: [LinkedReplyName(ref_src)]</h4>"
|
||||
dat += span_bold("State: ")
|
||||
switch(state)
|
||||
if(AHELP_ACTIVE)
|
||||
dat += span_red(span_bold("OPEN"))
|
||||
if(AHELP_RESOLVED)
|
||||
dat += span_green(span_bold("RESOLVED"))
|
||||
if(AHELP_CLOSED)
|
||||
dat += span_bold("CLOSED")
|
||||
else
|
||||
dat += span_bold("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 += "<br><br>Opened at: [gameTimestamp(wtime = opened_at)] (Approx [(world.time - opened_at) / 600] minutes ago)"
|
||||
if(closed_at)
|
||||
dat += "<br>Closed at: [gameTimestamp(wtime = closed_at)] (Approx [(world.time - closed_at) / 600] minutes ago)"
|
||||
dat += "<br><br>"
|
||||
if(initiator)
|
||||
dat += span_bold("Actions:") + " [FullMonty(ref_src)]<br>"
|
||||
else
|
||||
dat += span_bold("DISCONNECTED") + "[GLOB.TAB][ClosureLinks(ref_src)]<br>"
|
||||
dat += "<br>" + span_bold("Log:") + "<br><br>"
|
||||
for(var/I in _interactions)
|
||||
dat += "[I]<br>"
|
||||
dat += "</html>"
|
||||
usr << browse(dat.Join(), "window=ahelp[id];size=620x480")
|
||||
|
||||
/datum/admin_help/proc/Retitle()
|
||||
/datum/ticket/proc/Retitle()
|
||||
var/new_title = tgui_input_text(usr, "Enter a title for the ticket", "Rename Ticket", name)
|
||||
if(new_title)
|
||||
name = new_title
|
||||
@@ -486,70 +638,38 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
|
||||
var/msg = "Ticket [TicketHref("#[id]")] titled [name] by [key_name_admin(usr)]"
|
||||
message_admins(msg)
|
||||
log_admin(msg)
|
||||
TicketPanel() //we have to be here to do this
|
||||
//TicketPanel() //we have to be here to do this
|
||||
|
||||
/datum/admin_help/tgui_fallback(payload)
|
||||
if(..())
|
||||
//Kick ticket to next level
|
||||
/datum/ticket/proc/Escalate()
|
||||
if(tgui_alert(usr, "Really escalate this ticket to admins? No mentors will ever be able to interact with it again if you do.","Escalate",list("Yes","No")) != "Yes")
|
||||
return
|
||||
if (src.initiator == null) // You can't escalate a mentorhelp of someone who's logged out because it won't create the adminhelp properly
|
||||
to_chat(usr, span_mentor_warning("Error: client not found, unable to escalate."))
|
||||
return
|
||||
|
||||
TicketPanelLegacy()
|
||||
level = level + 1
|
||||
|
||||
/datum/admin_help/tgui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "AdminTicketPanel", "Ticket #[id] - [LinkedReplyName("\ref[src]")]")
|
||||
ui.open()
|
||||
|
||||
/datum/admin_help/tgui_state(mob/user)
|
||||
return GLOB.tgui_admin_state
|
||||
|
||||
/datum/admin_help/tgui_data(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
data["id"] = id
|
||||
|
||||
var/ref_src = "\ref[src]"
|
||||
data["title"] = name
|
||||
data["name"] = LinkedReplyName(ref_src)
|
||||
|
||||
switch(state)
|
||||
if(AHELP_ACTIVE)
|
||||
data["state"] = "open"
|
||||
if(AHELP_RESOLVED)
|
||||
data["state"] = "resolved"
|
||||
if(AHELP_CLOSED)
|
||||
data["state"] = "closed"
|
||||
else
|
||||
data["state"] = "unknown"
|
||||
|
||||
data["opened_at"] = (world.time - opened_at)
|
||||
data["closed_at"] = (world.time - closed_at)
|
||||
data["opened_at_date"] = gameTimestamp(wtime = opened_at)
|
||||
data["closed_at_date"] = gameTimestamp(wtime = closed_at)
|
||||
|
||||
data["actions"] = FullMonty(ref_src)
|
||||
|
||||
data["log"] = _interactions
|
||||
|
||||
return data
|
||||
|
||||
/datum/admin_help/tgui_act(action, params)
|
||||
if(..())
|
||||
return
|
||||
switch(action)
|
||||
if("retitle")
|
||||
Retitle()
|
||||
. = TRUE
|
||||
if("reopen")
|
||||
Reopen()
|
||||
. = TRUE
|
||||
if("legacy")
|
||||
TicketPanelLegacy()
|
||||
. = TRUE
|
||||
message_mentors("[usr.ckey] escalated Ticket [TicketHref("#[id]")]")
|
||||
log_admin("[key_name(usr)] escalated ticket [src.name]")
|
||||
to_chat(src.initiator, span_mentor("[usr.ckey] escalated your ticket to admins."))
|
||||
|
||||
//Forwarded action from admin/Topic
|
||||
/datum/admin_help/proc/Action(action)
|
||||
testing("Ahelp action: [action]")
|
||||
/datum/ticket/proc/Action(action)
|
||||
//testing("Ticket action: [action]")
|
||||
|
||||
// Actions everyone can do
|
||||
switch(level)
|
||||
if(0)
|
||||
if(!check_rights_for(usr.client, (R_ADMIN|R_SERVER|R_MOD|R_MENTOR)))
|
||||
return
|
||||
if(1)
|
||||
if(!check_rights_for(usr.client, (R_ADMIN|R_SERVER|R_MOD)))
|
||||
return
|
||||
|
||||
perform_action(action)
|
||||
|
||||
/datum/ticket/proc/perform_action(action)
|
||||
switch(action)
|
||||
if("ticket")
|
||||
TicketPanel()
|
||||
@@ -558,7 +678,11 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
|
||||
if("reject")
|
||||
Reject()
|
||||
if("reply")
|
||||
usr.client.cmd_ahelp_reply(initiator)
|
||||
switch(level)
|
||||
if(0)
|
||||
usr.client.cmd_mhelp_reply(initiator)
|
||||
if(1)
|
||||
usr.client.cmd_ahelp_reply(initiator)
|
||||
if("icissue")
|
||||
ICIssue()
|
||||
if("close")
|
||||
@@ -569,91 +693,31 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
|
||||
HandleIssue()
|
||||
if("reopen")
|
||||
Reopen()
|
||||
if("escalate")
|
||||
Escalate()
|
||||
|
||||
//
|
||||
// TICKET STATCLICK
|
||||
//
|
||||
|
||||
/obj/effect/statclick/ahelp
|
||||
var/datum/admin_help/ahelp_datum
|
||||
/obj/effect/statclick/ticket
|
||||
var/datum/ticket/ticket_datum
|
||||
|
||||
/obj/effect/statclick/ahelp/Initialize(mapload, text, target)
|
||||
INITIALIZE_IMMEDIATE(/obj/effect/statclick/ticket)
|
||||
/obj/effect/statclick/ticket/Initialize(mapload, datum/ticket/T)
|
||||
ticket_datum = T
|
||||
. = ..()
|
||||
ahelp_datum = target
|
||||
|
||||
/obj/effect/statclick/ahelp/update()
|
||||
return ..(ahelp_datum.name)
|
||||
/obj/effect/statclick/ticket/update()
|
||||
return ..(ticket_datum.name)
|
||||
|
||||
/obj/effect/statclick/ahelp/Click()
|
||||
ahelp_datum.TicketPanel()
|
||||
/obj/effect/statclick/ticket/Click()
|
||||
ticket_datum.TicketPanel()
|
||||
|
||||
/obj/effect/statclick/ahelp/Destroy()
|
||||
ahelp_datum = null
|
||||
/obj/effect/statclick/ticket/Destroy()
|
||||
ticket_datum = null
|
||||
return ..()
|
||||
|
||||
//
|
||||
// CLIENT PROCS
|
||||
//
|
||||
|
||||
/client/verb/adminhelp(msg as text)
|
||||
set category = "Admin"
|
||||
set name = "Adminhelp"
|
||||
|
||||
//handle muting and automuting
|
||||
if(prefs.muted & MUTE_ADMINHELP)
|
||||
to_chat(src, span_danger("Error: Admin-PM: You cannot send adminhelps (Muted)."))
|
||||
return
|
||||
if(handle_spam_prevention(msg,MUTE_ADMINHELP))
|
||||
return
|
||||
|
||||
if(!msg)
|
||||
return
|
||||
|
||||
//remove out adminhelp verb temporarily to prevent spamming of admins.
|
||||
remove_verb(src, /client/verb/adminhelp)
|
||||
spawn(1200)
|
||||
add_verb(src, /client/verb/adminhelp) // 2 minute cool-down for adminhelps
|
||||
|
||||
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)
|
||||
var/input = tgui_alert(usr, "You already have a ticket open. Is this for the same issue?","Duplicate?",list("Yes","No"))
|
||||
if(!input)
|
||||
return
|
||||
if(input == "Yes")
|
||||
if(current_ticket)
|
||||
current_ticket.MessageNoRecipient(msg)
|
||||
to_chat(usr, span_admin_pm_notice("PM to-" + span_bold("Admins") + ": [msg]"))
|
||||
return
|
||||
else
|
||||
to_chat(usr, span_warning("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.Misc"
|
||||
|
||||
if(!check_rights(R_ADMIN|R_MOD|R_DEBUG|R_EVENT, TRUE))
|
||||
return
|
||||
|
||||
var/browse_to
|
||||
|
||||
switch(tgui_input_list(usr, "Display which ticket list?", "List Choice", 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
|
||||
//
|
||||
@@ -671,10 +735,10 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
|
||||
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
|
||||
var/datum/ticket/T = GLOB.tickets.CKey2ActiveTicket(what)
|
||||
if(T)
|
||||
T.AddInteraction(message)
|
||||
return T
|
||||
|
||||
//
|
||||
// HELPER PROCS
|
||||
@@ -0,0 +1,52 @@
|
||||
//
|
||||
//PLAYERSIDE TICKET UI
|
||||
//
|
||||
|
||||
/datum/ticket_chat
|
||||
var/datum/ticket/T
|
||||
|
||||
/datum/ticket_chat/tgui_interact(mob/user, datum/tgui/ui)
|
||||
return // Remove this line to enable player-side ticket ui
|
||||
//ui = SStgui.try_update_ui(user, src, ui)
|
||||
//if(!ui)
|
||||
// ui = new(user, src, "TicketChat", "Ticket #[T.id] - [T.LinkedReplyName("\ref[T]")]")
|
||||
// ui.open()
|
||||
// user.clear_alert("open ticket")
|
||||
|
||||
/datum/ticket_chat/tgui_close(mob/user)
|
||||
. = ..()
|
||||
return // Remove this line to enable player-side ticket ui
|
||||
//if(user.client.current_ticket)
|
||||
// user.throw_alert("open ticket", /obj/screen/alert/open_ticket)
|
||||
|
||||
/datum/ticket_chat/tgui_state(mob/user)
|
||||
return GLOB.tgui_admin_state // Remove this line to enable player-side ticket ui
|
||||
//return GLOB.tgui_ticket_state
|
||||
|
||||
/datum/ticket_chat/tgui_data(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
data["id"] = T.id
|
||||
|
||||
data["level"] = T.level
|
||||
// data["handler"] = T.handler // Uncomment this line to enable player-side ticket ui
|
||||
|
||||
// data["log"] = T._interactions // Uncomment this line to enable player-side ticket ui
|
||||
|
||||
return data
|
||||
|
||||
/datum/ticket_chat/tgui_act(action, params, datum/tgui/ui)
|
||||
if(..())
|
||||
return
|
||||
switch(action)
|
||||
if("send_msg")
|
||||
if(!params["msg"])
|
||||
return
|
||||
|
||||
switch(T.level)
|
||||
if (0)
|
||||
ui.user.client.cmd_mentor_pm(ui.user.client, sanitize(params["msg"]), T)
|
||||
if (1)
|
||||
ui.user.client.cmd_admin_pm(ui.user.client, sanitize(params["msg"]), T)
|
||||
|
||||
. = TRUE
|
||||
@@ -0,0 +1,334 @@
|
||||
//
|
||||
//TICKET MANAGER
|
||||
//
|
||||
|
||||
/datum/tickets/tgui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "TicketsPanel", "Tickets")
|
||||
ui.open()
|
||||
|
||||
/datum/tickets/tgui_state(mob/user)
|
||||
return GLOB.tgui_mentor_state
|
||||
|
||||
/datum/tickets/proc/get_ticket_state(state)
|
||||
var/ticket_state
|
||||
switch(state)
|
||||
if(AHELP_ACTIVE)
|
||||
ticket_state = "open"
|
||||
// TODO: Mentor tickets cannot be resolved
|
||||
if(AHELP_RESOLVED)
|
||||
ticket_state = "resolved"
|
||||
if(AHELP_CLOSED)
|
||||
ticket_state = "closed"
|
||||
else
|
||||
ticket_state = "unknown"
|
||||
|
||||
return ticket_state
|
||||
|
||||
/datum/tickets/tgui_data(mob/user)
|
||||
var/list/data = list()
|
||||
var/list/tickets = list()
|
||||
|
||||
var/selected_ticket = null
|
||||
|
||||
if(user.client.selected_ticket)
|
||||
var/datum/ticket/T = user.client.selected_ticket
|
||||
if(check_rights_for(user.client, (R_ADMIN|R_SERVER|R_MOD)) || (check_rights_for(user.client, R_MENTOR) && T.level < 1))
|
||||
selected_ticket = list(
|
||||
"id" = T.id,
|
||||
"name" = T.LinkedReplyName(),
|
||||
"state" = get_ticket_state(T.state),
|
||||
"level" = T.level,
|
||||
"handler" = T.handler,
|
||||
"opened_at" = (world.time - T.opened_at),
|
||||
"closed_at" = (world.time - T.closed_at),
|
||||
"opened_at_date" = gameTimestamp(wtime = T.opened_at),
|
||||
"closed_at_date" = gameTimestamp(wtime = T.closed_at),
|
||||
"actions" = check_rights_for(user.client, (R_ADMIN|R_SERVER|R_MOD)) ? T.FullMonty(,TRUE) : T.FullMonty(),
|
||||
"log" = T._interactions,
|
||||
)
|
||||
|
||||
for(var/datum/ticket/T as anything in GLOB.tickets.active_tickets)
|
||||
if(check_rights_for(user.client, (R_ADMIN|R_SERVER|R_MOD)) || (check_rights_for(user.client, R_MENTOR) && T.level < 1))
|
||||
tickets.Add(list(list(
|
||||
"id" = T.id,
|
||||
"name" = T.initiator_key_name,
|
||||
"state" = get_ticket_state(T.state),
|
||||
"level" = T.level,
|
||||
"handler" = T.handler,
|
||||
"opened_at" = (world.time - T.opened_at),
|
||||
"closed_at" = (world.time - T.closed_at),
|
||||
"opened_at_date" = gameTimestamp(wtime = T.opened_at),
|
||||
"closed_at_date" = gameTimestamp(wtime = T.closed_at),
|
||||
)))
|
||||
|
||||
for(var/datum/ticket/T as anything in GLOB.tickets.closed_tickets)
|
||||
if(check_rights_for(user.client, (R_ADMIN|R_SERVER|R_MOD)) || (check_rights_for(user.client, R_MENTOR) && T.level < 1))
|
||||
tickets.Add(list(list(
|
||||
"id" = T.id,
|
||||
"name" = T.initiator_key_name,
|
||||
"state" = get_ticket_state(T.state),
|
||||
"level" = T.level,
|
||||
"handler" = T.handler,
|
||||
"opened_at" = (world.time - T.opened_at),
|
||||
"closed_at" = (world.time - T.closed_at),
|
||||
"opened_at_date" = gameTimestamp(wtime = T.opened_at),
|
||||
"closed_at_date" = gameTimestamp(wtime = T.closed_at),
|
||||
)))
|
||||
|
||||
for(var/datum/ticket/T as anything in GLOB.tickets.resolved_tickets)
|
||||
if(check_rights_for(user.client, (R_ADMIN|R_SERVER|R_MOD)) || (check_rights_for(user.client, R_MENTOR) && T.level < 1))
|
||||
tickets.Add(list(list(
|
||||
"id" = T.id,
|
||||
"name" = T.initiator_key_name,
|
||||
"state" = get_ticket_state(T.state),
|
||||
"level" = T.level,
|
||||
"handler" = T.handler,
|
||||
"opened_at" = (world.time - T.opened_at),
|
||||
"closed_at" = (world.time - T.closed_at),
|
||||
"opened_at_date" = gameTimestamp(wtime = T.opened_at),
|
||||
"closed_at_date" = gameTimestamp(wtime = T.closed_at),
|
||||
)))
|
||||
data["tickets"] = tickets
|
||||
data["is_admin"] = check_rights_for(user.client, (R_ADMIN|R_SERVER|R_MOD))
|
||||
data["selected_ticket"] = selected_ticket
|
||||
|
||||
return data
|
||||
|
||||
/datum/tickets/tgui_act(action, params, datum/tgui/ui)
|
||||
if(..())
|
||||
return
|
||||
switch(action)
|
||||
if("legacy")
|
||||
var/choice = tgui_input_list(ui.user, "Which tickets do you want to list?", "Tickets", list("Active", "Closed", "Resolved"))
|
||||
TicketListLegacy(ui.user, choice)
|
||||
. = TRUE
|
||||
if("new_ticket")
|
||||
var/list/ckeys = list()
|
||||
for(var/client/C in GLOB.clients)
|
||||
ckeys += C.key
|
||||
|
||||
var/ckey = lowertext(tgui_input_list(ui.user, "Please select the ckey of the user.", "Select CKEY", ckeys))
|
||||
if(!ckey)
|
||||
return
|
||||
|
||||
var/client/player
|
||||
for(var/client/C in GLOB.clients)
|
||||
if(C.ckey == ckey)
|
||||
player = C
|
||||
|
||||
if(!player)
|
||||
to_chat(ui.user, span_warning("Ckey ([ckey]) not online."))
|
||||
return
|
||||
|
||||
var/ticket_text = tgui_input_text(ui.user, "What should the initial text be?", "New Ticket")
|
||||
if(!ticket_text)
|
||||
to_chat(ui.user, span_warning("Ticket message cannot be empty."))
|
||||
return
|
||||
|
||||
var/level = tgui_alert(ui.user, "Is this ticket Admin-Level or Mentor-Level?", "Ticket Level", list("Admin", "Mentor"))
|
||||
if(!level)
|
||||
return
|
||||
|
||||
feedback_add_details("admin_verb","Admincreatedticket") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
if(player.current_ticket)
|
||||
var/input = tgui_alert(ui.user, "The player already has a ticket open. Is this for the same issue?","Duplicate?",list("Yes","No"))
|
||||
if(!input)
|
||||
return
|
||||
if(input == "Yes")
|
||||
if(player.current_ticket)
|
||||
player.current_ticket.MessageNoRecipient(ticket_text)
|
||||
to_chat(ui.user, span_adminnotice("PM to-" + span_bold("Admins") + ": [ticket_text]"))
|
||||
return
|
||||
else
|
||||
to_chat(ui.user, span_warning("Ticket not found, creating new one..."))
|
||||
else
|
||||
player.current_ticket.AddInteraction("[key_name_admin(ui.user)] opened a new ticket.")
|
||||
player.current_ticket.Close()
|
||||
|
||||
// Create a new ticket and handle it. You created it afterall!
|
||||
var/datum/ticket/T = new /datum/ticket(ticket_text, player, TRUE, level)
|
||||
if(level == "Admin")
|
||||
T.level = 1
|
||||
else
|
||||
T.level = 0
|
||||
T.HandleIssue()
|
||||
switch(T.level)
|
||||
if (0)
|
||||
ui.user.client.cmd_mentor_pm(player, ticket_text, T)
|
||||
if (1)
|
||||
ui.user.client.cmd_admin_pm(player, ticket_text, T)
|
||||
. = TRUE
|
||||
if("pick_ticket")
|
||||
var/datum/ticket/T = ID2Ticket(params["ticket_id"])
|
||||
ui.user.client.selected_ticket = T
|
||||
. = TRUE
|
||||
if("retitle_ticket")
|
||||
ui.user.client.selected_ticket.Retitle()
|
||||
. = TRUE
|
||||
if("reopen_ticket")
|
||||
ui.user.client.selected_ticket.Reopen()
|
||||
. = TRUE
|
||||
if("undock_ticket")
|
||||
ui.user.client.selected_ticket.tgui_interact(ui.user)
|
||||
ui.user.client.selected_ticket = null
|
||||
. = TRUE
|
||||
if("send_msg")
|
||||
if(!params["msg"])
|
||||
return
|
||||
|
||||
switch(ui.user.client.selected_ticket.level)
|
||||
if (0)
|
||||
ui.user.client.cmd_mentor_pm(ui.user.client.selected_ticket.initiator, sanitize(params["msg"]), ui.user.client.selected_ticket)
|
||||
if (1)
|
||||
ui.user.client.cmd_admin_pm(ui.user.client.selected_ticket.initiator, sanitize(params["msg"]), ui.user.client.selected_ticket)
|
||||
. = TRUE
|
||||
|
||||
/datum/tickets/tgui_fallback(payload, user)
|
||||
if(..())
|
||||
return
|
||||
|
||||
var/choice = tgui_input_list(user, "Which tickets do you want to list?", "Tickets", list("Active", "Closed", "Resolved"))
|
||||
|
||||
TicketListLegacy(user, choice)
|
||||
|
||||
//
|
||||
//TICKET DATUM
|
||||
//
|
||||
|
||||
/datum/ticket/tgui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "Ticket", "Ticket #[id] - [LinkedReplyName("\ref[src]")]")
|
||||
ui.open()
|
||||
|
||||
/datum/ticket/tgui_state(mob/user)
|
||||
return GLOB.tgui_mentor_state
|
||||
|
||||
/datum/ticket/tgui_data(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
data["id"] = id
|
||||
|
||||
var/ref_src = "\ref[src]"
|
||||
data["title"] = name
|
||||
data["name"] = LinkedReplyName(ref_src)
|
||||
data["ticket_ref"] = ref_src
|
||||
|
||||
switch(state)
|
||||
if(AHELP_ACTIVE)
|
||||
data["state"] = "open"
|
||||
// TODO: Mentor tickets cannot be resolved
|
||||
if(AHELP_RESOLVED)
|
||||
data["state"] = "resolved"
|
||||
if(AHELP_CLOSED)
|
||||
data["state"] = "closed"
|
||||
else
|
||||
data["state"] = "unknown"
|
||||
|
||||
data["level"] = level
|
||||
data["handler"] = handler
|
||||
|
||||
data["opened_at"] = (world.time - opened_at)
|
||||
data["closed_at"] = (world.time - closed_at)
|
||||
data["opened_at_date"] = gameTimestamp(wtime = opened_at)
|
||||
data["closed_at_date"] = gameTimestamp(wtime = closed_at)
|
||||
|
||||
data["actions"] = check_rights_for(user.client, (R_ADMIN|R_SERVER|R_MOD)) ? FullMonty(ref_src, TRUE) : FullMonty(ref_src)
|
||||
|
||||
data["log"] = _interactions
|
||||
|
||||
return data
|
||||
|
||||
/datum/ticket/tgui_act(action, params, datum/tgui/ui)
|
||||
if(..())
|
||||
return
|
||||
switch(action)
|
||||
if("retitle")
|
||||
Retitle()
|
||||
. = TRUE
|
||||
if("reopen")
|
||||
Reopen()
|
||||
. = TRUE
|
||||
if("legacy")
|
||||
TicketPanelLegacy(ui.user)
|
||||
. = TRUE
|
||||
if("send_msg")
|
||||
if(!params["msg"] || !params["ticket_ref"])
|
||||
return
|
||||
|
||||
var/datum/ticket/T = locate(params["ticket_ref"])
|
||||
|
||||
switch(level)
|
||||
if (0)
|
||||
ui.user.client.cmd_mentor_pm(T.initiator, sanitize(params["msg"]), T)
|
||||
if (1)
|
||||
ui.user.client.cmd_admin_pm(T.initiator, sanitize(params["msg"]), T)
|
||||
|
||||
. = TRUE
|
||||
|
||||
/datum/ticket/tgui_fallback(payload, user)
|
||||
if(..())
|
||||
return
|
||||
|
||||
TicketPanelLegacy(user)
|
||||
|
||||
/datum/ticket/proc/TicketPanelLegacy(mob/user)
|
||||
var/list/dat = list("<html><head><title>Ticket #[id]</title></head>")
|
||||
var/ref_src = "\ref[src]"
|
||||
dat += "<h4>Ticket #[id]: [LinkedReplyName(ref_src)]</h4>"
|
||||
dat += "<b>State: "
|
||||
switch(state)
|
||||
if(AHELP_ACTIVE)
|
||||
dat += span_red("OPEN")
|
||||
if(AHELP_RESOLVED)
|
||||
dat += span_green("RESOLVED")
|
||||
if(AHELP_CLOSED)
|
||||
dat += "CLOSED"
|
||||
else
|
||||
dat += "UNKNOWN"
|
||||
dat += "</b>[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 += "<br><br>Opened at: [gameTimestamp(wtime = opened_at)] (Approx [(world.time - opened_at) / 600] minutes ago)"
|
||||
if(closed_at)
|
||||
dat += "<br>Closed at: [gameTimestamp(wtime = closed_at)] (Approx [(world.time - closed_at) / 600] minutes ago)"
|
||||
dat += "<br><br>"
|
||||
if(initiator)
|
||||
dat += span_bold("Actions:") + " [check_rights_for(user.client, (R_ADMIN|R_SERVER|R_MOD)) ? FullMonty(ref_src, TRUE) : FullMonty(ref_src)]<br>"
|
||||
else
|
||||
dat += span_bold("DISCONNECTED") + "[GLOB.TAB][ClosureLinks(ref_src)]<br>"
|
||||
dat += "<br><b>Log:</b><br><br>"
|
||||
for(var/I in _interactions)
|
||||
dat += "[I]<br>"
|
||||
dat += "</html>"
|
||||
user << browse(dat.Join(), "window=ahelp[id];size=620x480")
|
||||
|
||||
/datum/tickets/proc/TicketListLegacy(mob/user, state)
|
||||
var/list/dat = list("<html><head><title>[state] Tickets</title></head>")
|
||||
var/tickets_found = 0
|
||||
|
||||
if(state == "Active")
|
||||
for(var/datum/ticket/T as anything in GLOB.tickets.active_tickets)
|
||||
if(!check_rights_for(user.client, (R_ADMIN|R_SERVER|R_MOD)) && T.level > 0)
|
||||
continue
|
||||
dat += "[T.level == 0 ? "Mentorhelp" : "Adminhelp"] - [T.TicketHref("#[T.id] - [T.initiator_ckey]: [T.name]")]"
|
||||
tickets_found++
|
||||
else if(state == "Closed")
|
||||
for(var/datum/ticket/T as anything in GLOB.tickets.closed_tickets)
|
||||
if(!check_rights_for(user.client, (R_ADMIN|R_SERVER|R_MOD)) && T.level > 0)
|
||||
continue
|
||||
dat += "[T.level == 0 ? "Mentorhelp" : "Adminhelp"] - [T.TicketHref("#[T.id] - [T.initiator_ckey]: [T.name]")]"
|
||||
tickets_found++
|
||||
else if(state == "Resolved")
|
||||
for(var/datum/ticket/T as anything in GLOB.tickets.resolved_tickets)
|
||||
if(!check_rights_for(user.client, (R_ADMIN|R_SERVER|R_MOD)) && T.level > 0)
|
||||
continue
|
||||
dat += "[T.level == 0 ? "Mentorhelp" : "Adminhelp"] - [T.TicketHref("#[T.id] - [T.initiator_ckey]: [T.name]")]"
|
||||
tickets_found++
|
||||
|
||||
if(tickets_found == 0)
|
||||
dat += "No [state] tickets found."
|
||||
dat += "</html>"
|
||||
user << browse(dat.Join(), "window=ahelp-list;size=250x350")
|
||||
@@ -592,3 +592,12 @@ JUKEBOX_TRACK_FILES config/jukebox.json
|
||||
|
||||
## Uncomment to limit resize caps depending on sprite icon height
|
||||
#PIXEL_SIZE_LIMIT
|
||||
|
||||
# This is the tgs4 channel tag, for discord chatbots used in TGS
|
||||
#AHELP_CHANNEL_TAG admin
|
||||
|
||||
# Turn this off if you don't want the TGS bot sending you messages whenever an ahelp ticket is created.
|
||||
#DISCORD_AHELPS_DISABLED
|
||||
|
||||
#Turn this on if you want all admin-PMs to go to be sent to discord, and not only the first message of a ticket.
|
||||
#DISCORD_AHELPS_ALL
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
; just add the ckey (lowercase) of every mentor on a separate line
|
||||
; lines starting with ; are comments and will be ignored
|
||||
|
||||
; not_a_user
|
||||
@@ -1066,6 +1066,11 @@ Byond.subscribeTo('add_admin_tabs', function (ht) {
|
||||
addPermanentTab("Tickets");
|
||||
});
|
||||
|
||||
Byond.subscribeTo('add_tickets_tabs', function (ht) {
|
||||
href_token = ht;
|
||||
addPermanentTab("Tickets");
|
||||
});
|
||||
|
||||
Byond.subscribeTo('update_examine', function (payload) {
|
||||
examine = payload.EX;
|
||||
if (examine.length > 0 && !verb_tabs.includes("Examine")) {
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
@@ -1,93 +0,0 @@
|
||||
/* eslint react/no-danger: "off" */
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { Window } from 'tgui/layouts';
|
||||
import { Button, LabeledList, Section, Stack } from 'tgui-core/components';
|
||||
import { round, toFixed } from 'tgui-core/math';
|
||||
|
||||
const State = {
|
||||
open: 'Open',
|
||||
resolved: 'Resolved',
|
||||
closed: 'Closed',
|
||||
unknown: 'Unknown',
|
||||
};
|
||||
|
||||
type Data = {
|
||||
id: number;
|
||||
title: string;
|
||||
name: string;
|
||||
state: string;
|
||||
opened_at: number;
|
||||
closed_at: number;
|
||||
opened_at_date: string;
|
||||
closed_at_date: string;
|
||||
actions: string;
|
||||
log: string[];
|
||||
};
|
||||
|
||||
export const AdminTicketPanel = (props) => {
|
||||
const { act, data } = useBackend<Data>();
|
||||
const {
|
||||
id,
|
||||
title,
|
||||
name,
|
||||
state,
|
||||
opened_at,
|
||||
closed_at,
|
||||
opened_at_date,
|
||||
closed_at_date,
|
||||
actions,
|
||||
log,
|
||||
} = data;
|
||||
return (
|
||||
<Window width={900} height={600}>
|
||||
<Window.Content scrollable>
|
||||
<Section
|
||||
title={'Ticket #' + id}
|
||||
buttons={
|
||||
<Stack>
|
||||
<Stack.Item>
|
||||
<Button icon="pen" onClick={() => act('retitle')}>
|
||||
Rename Ticket
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button onClick={() => act('legacy')}>Legacy UI</Button>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
}
|
||||
>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Admin Help Ticket">
|
||||
#{id}: <div dangerouslySetInnerHTML={{ __html: name }} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="State">{State[state]}</LabeledList.Item>
|
||||
{State[state] === State.open ? (
|
||||
<LabeledList.Item label="Opened At">
|
||||
{opened_at_date +
|
||||
' (' +
|
||||
toFixed(round((opened_at / 600) * 10, 0) / 10, 1) +
|
||||
' minutes ago.)'}
|
||||
</LabeledList.Item>
|
||||
) : (
|
||||
<LabeledList.Item label="Closed At">
|
||||
{closed_at_date +
|
||||
' (' +
|
||||
toFixed(round((closed_at / 600) * 10, 0) / 10, 1) +
|
||||
' minutes ago.)'}
|
||||
<Button onClick={() => act('reopen')}>Reopen</Button>
|
||||
</LabeledList.Item>
|
||||
)}
|
||||
<LabeledList.Item label="Actions">
|
||||
<div dangerouslySetInnerHTML={{ __html: actions }} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Log">
|
||||
{Object.keys(log).map((L, i) => (
|
||||
<div key={i} dangerouslySetInnerHTML={{ __html: log[L] }} />
|
||||
))}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
@@ -1,91 +0,0 @@
|
||||
/* eslint react/no-danger: "off" */
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { Window } from 'tgui/layouts';
|
||||
import { Button, LabeledList, Section, Stack } from 'tgui-core/components';
|
||||
import { round, toFixed } from 'tgui-core/math';
|
||||
|
||||
const State = {
|
||||
open: 'Open',
|
||||
resolved: 'Resolved',
|
||||
unknown: 'Unknown',
|
||||
};
|
||||
|
||||
type Data = {
|
||||
id: number;
|
||||
title: string;
|
||||
name: string;
|
||||
state: string;
|
||||
opened_at: number;
|
||||
closed_at: number;
|
||||
opened_at_date: string;
|
||||
closed_at_date: string;
|
||||
actions: string;
|
||||
log: string[];
|
||||
};
|
||||
|
||||
export const MentorTicketPanel = (props) => {
|
||||
const { act, data } = useBackend<Data>();
|
||||
const {
|
||||
id,
|
||||
name,
|
||||
state,
|
||||
opened_at,
|
||||
closed_at,
|
||||
opened_at_date,
|
||||
closed_at_date,
|
||||
actions,
|
||||
log,
|
||||
} = data;
|
||||
return (
|
||||
<Window width={900} height={600}>
|
||||
<Window.Content scrollable>
|
||||
<Section
|
||||
title={'Ticket #' + id}
|
||||
buttons={
|
||||
<Stack>
|
||||
<Stack.Item>
|
||||
<Button icon="arrow-up" onClick={() => act('escalate')}>
|
||||
Escalate
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button onClick={() => act('legacy')}>Legacy UI</Button>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
}
|
||||
>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Mentor Help Ticket">
|
||||
#{id}: <div dangerouslySetInnerHTML={{ __html: name }} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="State">{State[state]}</LabeledList.Item>
|
||||
{State[state] === State.open ? (
|
||||
<LabeledList.Item label="Opened At">
|
||||
{opened_at_date +
|
||||
' (' +
|
||||
toFixed(round((opened_at / 600) * 10, 0) / 10, 1) +
|
||||
' minutes ago.)'}
|
||||
</LabeledList.Item>
|
||||
) : (
|
||||
<LabeledList.Item label="Closed At">
|
||||
{closed_at_date +
|
||||
' (' +
|
||||
toFixed(round((closed_at / 600) * 10, 0) / 10, 1) +
|
||||
' minutes ago.)'}
|
||||
<Button onClick={() => act('reopen')}>Reopen</Button>
|
||||
</LabeledList.Item>
|
||||
)}
|
||||
<LabeledList.Item label="Actions">
|
||||
<div dangerouslySetInnerHTML={{ __html: actions }} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Log">
|
||||
{Object.keys(log).map((L, i) => (
|
||||
<div key={i} dangerouslySetInnerHTML={{ __html: log[L] }} />
|
||||
))}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,207 @@
|
||||
/* eslint react/no-danger: "off" */
|
||||
import { type RefObject, useEffect, useRef, useState } from 'react';
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { Window } from 'tgui/layouts';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Divider,
|
||||
Input,
|
||||
LabeledList,
|
||||
Section,
|
||||
Stack,
|
||||
} from 'tgui-core/components';
|
||||
import { KEY } from 'tgui-core/keys';
|
||||
import { round, toFixed } from 'tgui-core/math';
|
||||
|
||||
const Level = {
|
||||
0: 'Mentorhelp',
|
||||
1: 'Adminhelp',
|
||||
2: 'GM Request',
|
||||
};
|
||||
|
||||
const LevelColor = {
|
||||
0: 'green',
|
||||
1: 'red',
|
||||
2: 'pink',
|
||||
};
|
||||
|
||||
const Tag = {
|
||||
example: 'Example',
|
||||
};
|
||||
|
||||
const State = {
|
||||
open: 'Open',
|
||||
resolved: 'Resolved',
|
||||
closed: 'Closed',
|
||||
unknown: 'Unknown',
|
||||
};
|
||||
|
||||
type Data = {
|
||||
id: number;
|
||||
title: string;
|
||||
name: string;
|
||||
ticket_ref: string;
|
||||
state: string;
|
||||
level: number;
|
||||
handler: string;
|
||||
opened_at: number;
|
||||
closed_at: number;
|
||||
opened_at_date: string;
|
||||
closed_at_date: string;
|
||||
actions: string;
|
||||
log: string[];
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', (event) => {
|
||||
console.log(event);
|
||||
});
|
||||
|
||||
export const Ticket = (props) => {
|
||||
const { act, data } = useBackend<Data>();
|
||||
const [ticketChat, setTicketChat] = useState('');
|
||||
|
||||
const messagesEndRef: RefObject<HTMLDivElement | null> = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const scroll = messagesEndRef.current;
|
||||
if (scroll) {
|
||||
scroll.scrollTop = scroll.scrollHeight;
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const scroll = messagesEndRef.current;
|
||||
if (scroll) {
|
||||
const height = scroll.scrollHeight;
|
||||
const bottom = scroll.scrollTop + scroll.offsetHeight;
|
||||
const scrollTracking = Math.abs(height - bottom) < 24;
|
||||
if (scrollTracking) {
|
||||
scroll.scrollTop = scroll.scrollHeight;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const {
|
||||
id,
|
||||
name,
|
||||
ticket_ref,
|
||||
state,
|
||||
level,
|
||||
handler,
|
||||
opened_at,
|
||||
closed_at,
|
||||
opened_at_date,
|
||||
closed_at_date,
|
||||
actions,
|
||||
log,
|
||||
} = data;
|
||||
return (
|
||||
<Window width={900} height={600}>
|
||||
<Window.Content>
|
||||
<Stack fill vertical>
|
||||
<Stack.Item>
|
||||
<Section
|
||||
title={'Ticket #' + id}
|
||||
buttons={
|
||||
<Box nowrap>
|
||||
<Button icon="pen" onClick={() => act('retitle')}>
|
||||
Rename Ticket
|
||||
</Button>
|
||||
<Button onClick={() => act('legacy')}>Legacy UI</Button>
|
||||
<Button color={LevelColor[level]}>{Level[level]}</Button>
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Ticket ID">
|
||||
#{id}: <div dangerouslySetInnerHTML={{ __html: name }} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Type">{Level[level]}</LabeledList.Item>
|
||||
<LabeledList.Item label="State">
|
||||
{State[state]}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Assignee">{handler}</LabeledList.Item>
|
||||
{State[state] === State.open ? (
|
||||
<LabeledList.Item label="Opened At">
|
||||
{opened_at_date +
|
||||
' (' +
|
||||
toFixed(round((opened_at / 600) * 10, 0) / 10, 1) +
|
||||
' minutes ago.)'}
|
||||
</LabeledList.Item>
|
||||
) : (
|
||||
<LabeledList.Item label="Closed At">
|
||||
{closed_at_date +
|
||||
' (' +
|
||||
toFixed(round((closed_at / 600) * 10, 0) / 10, 1) +
|
||||
' minutes ago.)'}
|
||||
<Button onClick={() => act('reopen')}>Reopen</Button>
|
||||
</LabeledList.Item>
|
||||
)}
|
||||
<LabeledList.Item label="Actions">
|
||||
<div dangerouslySetInnerHTML={{ __html: actions }} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Log" />
|
||||
</LabeledList>
|
||||
</Section>
|
||||
<Divider />
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<Section scrollable ref={messagesEndRef} fill>
|
||||
<Stack fill direction="column">
|
||||
<Stack.Item grow>
|
||||
{Object.keys(log)
|
||||
.slice(0)
|
||||
.map((L, i) => (
|
||||
<div
|
||||
key={i}
|
||||
dangerouslySetInnerHTML={{ __html: log[L] }}
|
||||
/>
|
||||
))}
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Section fill>
|
||||
<Stack fill>
|
||||
<Stack.Item grow>
|
||||
<Input
|
||||
autoFocus
|
||||
autoSelect
|
||||
fluid
|
||||
placeholder="Enter a message..."
|
||||
value={ticketChat}
|
||||
onChange={(value: string) => setTicketChat(value)}
|
||||
onKeyDown={(e) => {
|
||||
if (KEY.Enter === e.key) {
|
||||
act('send_msg', {
|
||||
msg: ticketChat,
|
||||
ticket_ref: ticket_ref,
|
||||
});
|
||||
setTicketChat('');
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
onClick={() => {
|
||||
act('send_msg', {
|
||||
msg: ticketChat,
|
||||
ticket_ref: ticket_ref,
|
||||
});
|
||||
setTicketChat('');
|
||||
}}
|
||||
>
|
||||
Send
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,139 @@
|
||||
/* eslint react/no-danger: "off" */
|
||||
import { type RefObject, useEffect, useRef, useState } from 'react';
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { Window } from 'tgui/layouts';
|
||||
import {
|
||||
Button,
|
||||
Divider,
|
||||
Input,
|
||||
LabeledList,
|
||||
Section,
|
||||
Stack,
|
||||
} from 'tgui-core/components';
|
||||
import { KEY } from 'tgui-core/keys';
|
||||
|
||||
const Level = {
|
||||
0: 'Mentorhelp',
|
||||
1: 'Adminhelp',
|
||||
2: 'GM Request',
|
||||
};
|
||||
|
||||
const LevelColor = {
|
||||
0: 'green',
|
||||
1: 'red',
|
||||
2: 'pink',
|
||||
};
|
||||
|
||||
const Tag = {
|
||||
example: 'Example',
|
||||
};
|
||||
|
||||
const State = {
|
||||
open: 'Open',
|
||||
resolved: 'Resolved',
|
||||
closed: 'Closed',
|
||||
unknown: 'Unknown',
|
||||
};
|
||||
|
||||
type Data = {
|
||||
id: number;
|
||||
level: number;
|
||||
handler: string;
|
||||
log: string[];
|
||||
};
|
||||
|
||||
export const TicketChat = (props) => {
|
||||
const { act, data } = useBackend<Data>();
|
||||
const [ticketChat, setTicketChat] = useState('');
|
||||
const { id, level, handler, log } = data;
|
||||
|
||||
const messagesEndRef: RefObject<HTMLDivElement | null> = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const scroll = messagesEndRef.current;
|
||||
if (scroll) {
|
||||
scroll.scrollTop = scroll.scrollHeight;
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const scroll = messagesEndRef.current;
|
||||
if (scroll) {
|
||||
const height = scroll.scrollHeight;
|
||||
const bottom = scroll.scrollTop + scroll.offsetHeight;
|
||||
const scrollTracking = Math.abs(height - bottom) < 24;
|
||||
if (scrollTracking) {
|
||||
scroll.scrollTop = scroll.scrollHeight;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Window width={900} height={600}>
|
||||
<Window.Content>
|
||||
<Stack fill vertical>
|
||||
<Stack.Item>
|
||||
<Section
|
||||
title={'Ticket #' + id}
|
||||
buttons={
|
||||
<Button color={LevelColor[level]}>{Level[level]}</Button>
|
||||
}
|
||||
>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Assignee">{handler}</LabeledList.Item>
|
||||
<LabeledList.Item label="Log" />
|
||||
</LabeledList>
|
||||
</Section>
|
||||
<Divider />
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<Section fill ref={messagesEndRef} scrollable>
|
||||
<Stack fill direction="column">
|
||||
<Stack.Item grow>
|
||||
{Object.keys(log)
|
||||
.slice(0)
|
||||
.map((L, i) => (
|
||||
<div
|
||||
key={i}
|
||||
dangerouslySetInnerHTML={{ __html: log[L] }}
|
||||
/>
|
||||
))}
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
<Section fill>
|
||||
<Stack fill>
|
||||
<Stack.Item grow>
|
||||
<Input
|
||||
autoFocus
|
||||
autoSelect
|
||||
fluid
|
||||
placeholder="Enter a message..."
|
||||
value={ticketChat}
|
||||
onChange={(value: string) => setTicketChat(value)}
|
||||
onKeyDown={(e) => {
|
||||
if (KEY.Enter === e.key) {
|
||||
act('send_msg', { msg: ticketChat });
|
||||
setTicketChat('');
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
onClick={() => {
|
||||
act('send_msg', { msg: ticketChat });
|
||||
setTicketChat('');
|
||||
}}
|
||||
>
|
||||
Send
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,366 @@
|
||||
/* eslint react/no-danger: "off" */
|
||||
import { type RefObject, useEffect, useRef, useState } from 'react';
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { Window } from 'tgui/layouts';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Divider,
|
||||
Dropdown,
|
||||
Icon,
|
||||
Input,
|
||||
LabeledList,
|
||||
Section,
|
||||
Stack,
|
||||
Tabs,
|
||||
} from 'tgui-core/components';
|
||||
import { KEY } from 'tgui-core/keys';
|
||||
import { round, toFixed } from 'tgui-core/math';
|
||||
import type { BooleanLike } from 'tgui-core/react';
|
||||
|
||||
const AdminLevel = {
|
||||
0: 'Mentor',
|
||||
1: 'Admin',
|
||||
2: 'All Levels',
|
||||
};
|
||||
|
||||
const MentorLevel = {
|
||||
0: 'Mentor',
|
||||
};
|
||||
|
||||
const LevelColor = {
|
||||
0: 'green',
|
||||
1: 'red',
|
||||
2: 'pink',
|
||||
};
|
||||
|
||||
const Tag = {
|
||||
example: 'Example',
|
||||
};
|
||||
|
||||
const State = {
|
||||
open: 'Open',
|
||||
resolved: 'Resolved',
|
||||
closed: 'Closed',
|
||||
unknown: 'Unknown',
|
||||
all: 'All States',
|
||||
};
|
||||
|
||||
const StateColor = {
|
||||
open: 'white',
|
||||
resolved: 'green',
|
||||
closed: 'grey',
|
||||
unknown: 'orange',
|
||||
};
|
||||
|
||||
type Data = {
|
||||
tickets: Ticket[];
|
||||
|
||||
selected_ticket: Ticket;
|
||||
is_admin: BooleanLike;
|
||||
};
|
||||
|
||||
type Ticket = {
|
||||
id: number;
|
||||
title: string;
|
||||
name: string;
|
||||
state: string;
|
||||
level: number;
|
||||
handler: string;
|
||||
opened_at: number;
|
||||
closed_at: number;
|
||||
opened_at_date: string;
|
||||
closed_at_date: string;
|
||||
actions: string;
|
||||
log: string[];
|
||||
|
||||
ref: string;
|
||||
selected: BooleanLike;
|
||||
};
|
||||
|
||||
const getFilteredTickets = (
|
||||
tickets: Ticket[],
|
||||
state: string,
|
||||
level: number,
|
||||
): Ticket[] => {
|
||||
const result: Ticket[] = [];
|
||||
|
||||
tickets.forEach((t) => {
|
||||
if (
|
||||
(t.state === state || state === 'all') &&
|
||||
(t.level === level || level === 2)
|
||||
) {
|
||||
result.push(t);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const TicketsPanel = (props) => {
|
||||
const { act, data } = useBackend<Data>();
|
||||
const { tickets, selected_ticket, is_admin } = data;
|
||||
|
||||
const [stateFilter, setStateFilter] = useState('open');
|
||||
const [levelFilter, setLevelFilter] = useState(2);
|
||||
|
||||
const [ticketChat, setTicketChat] = useState('');
|
||||
|
||||
const messagesEndRef: RefObject<HTMLDivElement | null> = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const scroll = messagesEndRef.current;
|
||||
if (scroll) {
|
||||
scroll.scrollTop = scroll.scrollHeight;
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const scroll = messagesEndRef.current;
|
||||
if (scroll) {
|
||||
const height = scroll.scrollHeight;
|
||||
const bottom = scroll.scrollTop + scroll.offsetHeight;
|
||||
const scrollTracking = Math.abs(height - bottom) < 24;
|
||||
if (scrollTracking) {
|
||||
scroll.scrollTop = scroll.scrollHeight;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const availableLevel = is_admin ? AdminLevel : MentorLevel;
|
||||
|
||||
const filtered_tickets = getFilteredTickets(
|
||||
tickets,
|
||||
stateFilter,
|
||||
levelFilter,
|
||||
);
|
||||
return (
|
||||
<Window width={1000} height={600}>
|
||||
<Window.Content>
|
||||
<Stack fill>
|
||||
<Stack.Item shrink>
|
||||
<Section title="Filter">
|
||||
<Dropdown
|
||||
width="100%"
|
||||
maxHeight="160px"
|
||||
options={Object.values(State)}
|
||||
selected={State[stateFilter]}
|
||||
onSelected={(val) =>
|
||||
setStateFilter(
|
||||
Object.keys(State)[Object.values(State).indexOf(val)],
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Divider />
|
||||
<Dropdown
|
||||
width="100%"
|
||||
maxHeight="160px"
|
||||
options={Object.values(availableLevel)}
|
||||
selected={availableLevel[levelFilter]}
|
||||
onSelected={(val) =>
|
||||
setLevelFilter(Object.values(availableLevel).indexOf(val))
|
||||
}
|
||||
/>
|
||||
</Section>
|
||||
<Section
|
||||
title="Tickets"
|
||||
scrollable
|
||||
fill
|
||||
height="450px"
|
||||
width="300px"
|
||||
>
|
||||
<Tabs vertical>
|
||||
<Tabs.Tab onClick={() => act('new_ticket')}>
|
||||
New Ticket
|
||||
<Icon name="plus" ml={0.5} />
|
||||
</Tabs.Tab>
|
||||
<Divider />
|
||||
{filtered_tickets.map((ticket) => (
|
||||
<Tabs.Tab
|
||||
key={ticket.id}
|
||||
selected={ticket.id === selected_ticket?.id}
|
||||
onClick={() => act('pick_ticket', { ticket_id: ticket.id })}
|
||||
>
|
||||
<Box inline>
|
||||
<Box>
|
||||
<Button color={LevelColor[ticket.level]}>
|
||||
{availableLevel[ticket.level]}
|
||||
</Button>
|
||||
{ticket.name}
|
||||
</Box>
|
||||
<Box fontSize={0.9} textColor={StateColor[ticket.state]}>
|
||||
State: {State[ticket.state]} | Assignee:
|
||||
{ticket.handler}
|
||||
</Box>
|
||||
</Box>
|
||||
</Tabs.Tab>
|
||||
))}
|
||||
</Tabs>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
{(selected_ticket && (
|
||||
<Stack fill vertical>
|
||||
<Stack.Item>
|
||||
<Section
|
||||
title={'Ticket #' + selected_ticket.id}
|
||||
buttons={
|
||||
<Box nowrap>
|
||||
<Button
|
||||
icon="arrow-up"
|
||||
onClick={() => act('undock_ticket')}
|
||||
>
|
||||
Undock
|
||||
</Button>
|
||||
<Button
|
||||
icon="pen"
|
||||
onClick={() => act('retitle_ticket')}
|
||||
>
|
||||
Rename Ticket
|
||||
</Button>
|
||||
<Button onClick={() => act('legacy')}>Legacy UI</Button>
|
||||
<Button color={LevelColor[selected_ticket.level]}>
|
||||
{availableLevel[selected_ticket.level]}
|
||||
</Button>
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Ticket ID">
|
||||
#{selected_ticket.id}:
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: selected_ticket.name,
|
||||
}}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Type">
|
||||
{availableLevel[selected_ticket.level]}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="State">
|
||||
{State[selected_ticket.state]}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Assignee">
|
||||
{selected_ticket.handler}
|
||||
</LabeledList.Item>
|
||||
{State[selected_ticket.state] === State.open ? (
|
||||
<LabeledList.Item label="Opened At">
|
||||
{selected_ticket.opened_at_date +
|
||||
' (' +
|
||||
toFixed(
|
||||
round((selected_ticket.opened_at / 600) * 10, 0) /
|
||||
10,
|
||||
1,
|
||||
) +
|
||||
' minutes ago.)'}
|
||||
</LabeledList.Item>
|
||||
) : (
|
||||
<LabeledList.Item label="Closed At">
|
||||
{selected_ticket.closed_at_date +
|
||||
' (' +
|
||||
toFixed(
|
||||
round((selected_ticket.closed_at / 600) * 10, 0) /
|
||||
10,
|
||||
1,
|
||||
) +
|
||||
' minutes ago.)'}
|
||||
<Button onClick={() => act('reopen_ticket')}>
|
||||
Reopen
|
||||
</Button>
|
||||
</LabeledList.Item>
|
||||
)}
|
||||
<LabeledList.Item label="Actions">
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: selected_ticket.actions,
|
||||
}}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Log" />
|
||||
</LabeledList>
|
||||
</Section>
|
||||
<Divider />
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<Section fill ref={messagesEndRef} scrollable>
|
||||
<Stack fill direction="column">
|
||||
<Stack.Item grow>
|
||||
{Object.keys(selected_ticket.log)
|
||||
.slice(0)
|
||||
.map((L, i) => (
|
||||
<div
|
||||
key={i}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: selected_ticket.log[L],
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Section fill>
|
||||
<Stack fill>
|
||||
<Stack.Item grow>
|
||||
<Input
|
||||
autoFocus
|
||||
autoSelect
|
||||
fluid
|
||||
placeholder="Enter a message..."
|
||||
value={ticketChat}
|
||||
onChange={(value: string) => setTicketChat(value)}
|
||||
onKeyDown={(e) => {
|
||||
if (KEY.Enter === e.key) {
|
||||
act('send_msg', { msg: ticketChat });
|
||||
setTicketChat('');
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
onClick={() => {
|
||||
act('send_msg', { msg: ticketChat });
|
||||
setTicketChat('');
|
||||
}}
|
||||
>
|
||||
Send
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
)) || (
|
||||
<Section
|
||||
title="No ticket selected"
|
||||
buttons={
|
||||
<Box nowrap>
|
||||
<Button
|
||||
disabled
|
||||
icon="arrow-up"
|
||||
onClick={() => act('undock_ticket')}
|
||||
>
|
||||
Undock
|
||||
</Button>
|
||||
<Button
|
||||
disabled
|
||||
icon="pen"
|
||||
onClick={() => act('retitle_ticket')}
|
||||
>
|
||||
Rename Ticket
|
||||
</Button>
|
||||
<Button onClick={() => act('legacy')}>Legacy UI</Button>
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
Please select a ticket on the left to view its details.
|
||||
</Section>
|
||||
)}
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
@@ -18,8 +18,9 @@ bitflags = {
|
||||
"R_MOD": 1<<13,
|
||||
"R_EVENT": 1<<14,
|
||||
"R_HOST": 1<<15,
|
||||
"R_MENTOR": 1<<16,
|
||||
"-------------": 0,
|
||||
"EVERYTHING": (1<<16)-1
|
||||
"EVERYTHING": (1<<17)-1
|
||||
}
|
||||
|
||||
class BitflagCalculator:
|
||||
|
||||
+5
-4
@@ -1971,8 +1971,6 @@
|
||||
#include "code\modules\admin\secrets\random_events\trigger_cordical_borer_infestation.dm"
|
||||
#include "code\modules\admin\secrets\random_events\trigger_xenomorph_infestation.dm"
|
||||
#include "code\modules\admin\verb_datums\_admin_verb_datum.dm"
|
||||
#include "code\modules\admin\verbs\adminhelp.dm"
|
||||
#include "code\modules\admin\verbs\adminhelp_vr.dm"
|
||||
#include "code\modules\admin\verbs\adminjump.dm"
|
||||
#include "code\modules\admin\verbs\adminpm.dm"
|
||||
#include "code\modules\admin\verbs\adminsay.dm"
|
||||
@@ -2986,8 +2984,6 @@
|
||||
#include "code\modules\media\media_tracks.dm"
|
||||
#include "code\modules\media\mediamanager.dm"
|
||||
#include "code\modules\media\walkpod.dm"
|
||||
#include "code\modules\mentor\mentor.dm"
|
||||
#include "code\modules\mentor\mentorhelp.dm"
|
||||
#include "code\modules\metric\activity.dm"
|
||||
#include "code\modules\metric\count.dm"
|
||||
#include "code\modules\metric\department.dm"
|
||||
@@ -4374,6 +4370,7 @@
|
||||
#include "code\modules\tgui\states\observer.dm"
|
||||
#include "code\modules\tgui\states\physical.dm"
|
||||
#include "code\modules\tgui\states\self.dm"
|
||||
#include "code\modules\tgui\states\ticket.dm"
|
||||
#include "code\modules\tgui\states\vorepanel_vr.dm"
|
||||
#include "code\modules\tgui\states\zlevel.dm"
|
||||
#include "code\modules\tgui_input\alert.dm"
|
||||
@@ -4389,6 +4386,10 @@
|
||||
#include "code\modules\tgui_panel\external.dm"
|
||||
#include "code\modules\tgui_panel\telemetry.dm"
|
||||
#include "code\modules\tgui_panel\tgui_panel.dm"
|
||||
#include "code\modules\tickets\procs.dm"
|
||||
#include "code\modules\tickets\tickets.dm"
|
||||
#include "code\modules\tickets\tickets_player_ui.dm"
|
||||
#include "code\modules\tickets\tickets_ui.dm"
|
||||
#include "code\modules\tooltip\tooltip.dm"
|
||||
#include "code\modules\turbolift\_turbolift.dm"
|
||||
#include "code\modules\turbolift\turbolift.dm"
|
||||
|
||||
Reference in New Issue
Block a user