[MIRROR] up ports a bunch of TGS commands (#11173)

Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com>
This commit is contained in:
CHOMPStation2StaffMirrorBot
2025-07-13 05:34:17 -07:00
committed by GitHub
parent 42b68b856b
commit 0160eb3e68
29 changed files with 639 additions and 366 deletions

View File

@@ -46,7 +46,7 @@
#define R_DEFAULT R_NONE #define R_DEFAULT R_NONE
#define R_EVERYTHING (1<<17)-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 R_HOLDER ((R_EVERYTHING) & (~(R_MENTOR | R_STEALTH)))
#define SMITE_BREAKLEGS "Break Legs" #define SMITE_BREAKLEGS "Break Legs"
#define SMITE_BLUESPACEARTILLERY "Bluespace Artillery" #define SMITE_BLUESPACEARTILLERY "Bluespace Artillery"

View File

@@ -432,7 +432,7 @@ so as to remain in compliance with the most up-to-date laws."
name = "Admin Chat Request" name = "Admin Chat Request"
desc = "A Administrator would like to chat with you. \ desc = "A Administrator would like to chat with you. \
Click here to begin." Click here to begin."
icon_state = "32x32" icon_state = "nt_logo"
/obj/screen/alert/open_ticket/Click() /obj/screen/alert/open_ticket/Click()
if(!usr || !usr.client) return if(!usr || !usr.client) return

View File

@@ -744,6 +744,45 @@
/datum/config_entry/flag/discord_ahelps_disabled /datum/config_entry/flag/discord_ahelps_disabled
default = FALSE default = FALSE
/// So, nodebot is a supplement to the TGS discord bot pretty much. For things likes faxes and the manifest it's very helpful because it's able to render html into an image and post it.
/datum/config_entry/flag/nodebot_enabled
default = FALSE
/// We need to print the manifest to this location so nodebot can render it to chat.
/// NOTE: TO BE REPLACED BY BETTER CODE FOR FETCHING MANIFEST
/datum/config_entry/string/nodebot_location
/datum/config_entry/string/fax_channel_tag
/datum/config_entry/string/role_request_channel_tag
/// These are for the role request TGS discord bot. Role IDs to ping.
/datum/config_entry/string/role_request_id_command
/datum/config_entry/string/role_request_id_security
/datum/config_entry/string/role_request_id_engineering
/datum/config_entry/string/role_request_id_medical
/datum/config_entry/string/role_request_id_research
/datum/config_entry/string/role_request_id_supply
/datum/config_entry/string/role_request_id_service
/datum/config_entry/string/role_request_id_expedition
/datum/config_entry/string/role_request_id_silicon
/// Only turn this on if you're not using the nodebot.
/datum/config_entry/flag/discord_faxes_autoprint
default = FALSE
/// Turn this off if you don't want the TGS bot sending you messages whenever a fax is sent to central.
/datum/config_entry/flag/discord_faxes_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. /// 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 /datum/config_entry/flag/discord_ahelps_all
default = FALSE default = FALSE

View File

@@ -61,5 +61,5 @@ SUBSYSTEM_DEF(inactivity)
return ..() return ..()
/datum/controller/subsystem/inactivity/proc/can_kick(var/client/C) /datum/controller/subsystem/inactivity/proc/can_kick(var/client/C)
if(C.holder) return FALSE //VOREStation Add - Don't kick admins. if(check_rights_for(C, R_HOLDER|R_MENTOR)) return FALSE //VOREStation Add - Don't kick admins.
return TRUE return TRUE

View File

@@ -199,8 +199,11 @@ var/list/runechat_image_cache = list()
// Translate any existing messages upwards, apply exponential decay factors to timers // Translate any existing messages upwards, apply exponential decay factors to timers
message_loc = target.runechat_holder(src) message_loc = target.runechat_holder(src)
if(!owned_by)
qdel(src)
return
RegisterSignal(message_loc, COMSIG_PARENT_QDELETING, PROC_REF(qdel_self)) RegisterSignal(message_loc, COMSIG_PARENT_QDELETING, PROC_REF(qdel_self))
if(owned_by && owned_by.seen_messages) if(owned_by.seen_messages)
var/idx = 1 var/idx = 1
var/combined_height = approx_lines var/combined_height = approx_lines
for(var/datum/chatmessage/m as anything in owned_by.seen_messages[message_loc]) for(var/datum/chatmessage/m as anything in owned_by.seen_messages[message_loc])

View File

@@ -17,7 +17,7 @@
var/static/list/locked = list() var/static/list/locked = list()
/datum/datacore/proc/get_manifest(monochrome, OOC,var/snowflake = FALSE) //CHOMPStation Edit /datum/datacore/proc/get_manifest(monochrome, OOC,var/snowflake = FALSE)
var/list/heads = new() var/list/heads = new()
var/list/sec = new() var/list/sec = new()
var/list/eng = new() var/list/eng = new()

View File

@@ -116,5 +116,5 @@
peeb += dat peeb += dat
peeb += span_notice("For more detailed information on the patient's condition, utilize a body scanner at the closest medical bay.") peeb += span_notice("For more detailed information on the patient's condition, utilize a body scanner at the closest medical bay.")
user.show_message(peeb, 1) user.show_message(peeb, 1)
//CHOMPedit end. //CHOMPedit end.

View File

@@ -867,7 +867,7 @@
message_admins(span_blue("[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/ticket/T = M.client ? M.client.current_ticket : null var/datum/ticket/T = M.client ? M.client.current_ticket : null
if(T) if(T)
T.Resolve() T.Resolve(usr)
qdel(M.client) qdel(M.client)
// CHOMPedit End // CHOMPedit End
//qdel(M) // See no reason why to delete mob. Important stuff can be lost. And ban can be lifted before round ends. //qdel(M) // See no reason why to delete mob. Important stuff can be lost. And ban can be lifted before round ends.
@@ -896,7 +896,7 @@
DB_ban_record(BANTYPE_PERMA, M, -1, reason) DB_ban_record(BANTYPE_PERMA, M, -1, reason)
var/datum/ticket/T = M.client ? M.client.current_ticket : null var/datum/ticket/T = M.client ? M.client.current_ticket : null
if(T) if(T)
T.Resolve() T.Resolve(usr)
qdel(M.client) qdel(M.client)
//qdel(M) //qdel(M)
if("Cancel") if("Cancel")

View File

@@ -213,8 +213,11 @@
if(!istype(target)) if(!istype(target))
return return
var/real_user = user ? user : usr
var/user_name = real_user ? key_name(real_user) : "Remotely (Discord)"
to_chat(target,"You've been hit by bluespace artillery!") to_chat(target,"You've been hit by bluespace artillery!")
log_and_message_admins("has been hit by Bluespace Artillery fired by [key_name(user ? user : usr)]", target) log_and_message_admins("has been hit by Bluespace Artillery fired by [user_name]", target)
target.setMoveCooldown(2 SECONDS) target.setMoveCooldown(2 SECONDS)

View File

@@ -213,7 +213,7 @@ var/list/gear_datums = list()
return TOPIC_REFRESH_UPDATE_PREVIEW return TOPIC_REFRESH_UPDATE_PREVIEW
if("clear_loadout") if("clear_loadout")
active_gear_list.Cut() active_gear_list?.Cut()
return TOPIC_REFRESH_UPDATE_PREVIEW return TOPIC_REFRESH_UPDATE_PREVIEW
if("copy_loadout") if("copy_loadout")

View File

@@ -153,9 +153,12 @@ vorestation edit end */
return return
to_chat(user, span_notice("The crate is locked with a Deca-code lock.")) to_chat(user, span_notice("The crate is locked with a Deca-code lock."))
var/input = tgui_input_text(user, "Enter [codelen] digits. All digits must be unique.", "Deca-Code Lock", "") var/input = tgui_input_text(user, "Enter [codelen] digits. All digits must be unique.", "Deca-Code Lock", "", codelen)
if(!Adjacent(user)) if(!Adjacent(user))
return return
if(input == null)
to_chat(user, span_notice("You leave the crate alone."))
return
var/list/sanitised = list() var/list/sanitised = list()
var/sanitycheck = 1 var/sanitycheck = 1
for(var/i=1,i<=length(input),i++) //put the guess into a list for(var/i=1,i<=length(input),i++) //put the guess into a list
@@ -165,9 +168,11 @@ vorestation edit end */
if(sanitised[i] == sanitised[j]) if(sanitised[i] == sanitised[j])
sanitycheck = null //if a digit is repeated, reject the input sanitycheck = null //if a digit is repeated, reject the input
if(input == null || sanitycheck == null || length(input) != codelen) if(sanitycheck == null || length(input) != codelen)
to_chat(user, span_notice("You leave the crate alone.")) to_chat(user, span_notice("You aren't sure this input is a good idea."))
else if(check_input(input)) return
if(check_input(input))
to_chat(user, span_notice("The crate unlocks!")) to_chat(user, span_notice("The crate unlocks!"))
playsound(src, 'sound/machines/lockreset.ogg', 50, 1) playsound(src, 'sound/machines/lockreset.ogg', 50, 1)
set_locked(0) set_locked(0)

View File

@@ -6,7 +6,8 @@
update_client_z(null) update_client_z(null)
log_access_out(src) log_access_out(src)
unset_machine() unset_machine()
if(GLOB.admin_datums[src.ckey] && check_rights(R_HOLDER, FALSE)) var/datum/admins/is_admin = GLOB.admin_datums[src.ckey]
if(is_admin && is_admin.check_for_rights(R_HOLDER))
message_admins("Staff logout: [key_name(src)]") // Staff logout notice displays no matter what message_admins("Staff logout: [key_name(src)]") // Staff logout notice displays no matter what
if (ticker && ticker.current_state == GAME_STATE_PLAYING) //Only report this stuff if we are currently playing. if (ticker && ticker.current_state == GAME_STATE_PLAYING) //Only report this stuff if we are currently playing.
var/admins_number = GLOB.admins.len var/admins_number = GLOB.admins.len

View File

@@ -1,130 +0,0 @@
/datum/tgs_chat_command/sharktest
name = "flip"
help_text = "babies first TGS command"
admin_only = FALSE
/datum/tgs_chat_command/sharktest/Run(datum/tgs_chat_user/sender, params)
var/x
if(params != "tails" && params != "heads")
return "```You need to guess!\n either heads or tails!```"
if(prob(50))
if(params == "tails")
x = "correct"
else
x = "wrong"
return "```Tails. [x]!```"
if(params == "tails")
x = "wrong"
else
x = "correct"
return "```Heads. [x]!```"
/datum/tgs_chat_command/sharktest/alias
name = "coin"
/datum/tgs_chat_command/manifest
name = "manifest"
help_text = "Shows the current crew manifest"
admin_only = FALSE
/proc/ManifestToHtml()
var/html = ""
if(GLOB.data_core)
html = GLOB.data_core.get_manifest(FALSE,TRUE,snowflake = TRUE)
else
html = span_bold("ERROR: NO DATACORE") //Could make the error more fancy later
rustg_file_write(html,"[CONFIG_GET(string/nodebot_location)]\\html.html")
/datum/tgs_chat_command/manifest/Run(datum/tgs_chat_user/sender, params)
if(CONFIG_GET(flag/nodebot_enabled))
ManifestToHtml()
return "http://manifest.chompstation13.net/"
else
var/outp = "Crew Manifest:"
var/list/total = list()
if(GLOB.data_core)
GLOB.data_core.get_manifest_list()
for(var/list/item in GLOB.PDA_Manifest)
outp += "\n__**[item["cat"]]:**__"
for(var/list/person in item["elems"])
total |= person
outp += "\n[person["name"]] -:- [person["rank"]]"
return "**Total crew members:** [total.len]\n" + outp
/datum/tgs_chat_command/discordping
name = "discordping"
help_text = "Pings the discord associated with the associated ckey"
admin_only = TRUE
/datum/tgs_chat_command/discordping/Run(datum/tgs_chat_user/sender, params)
var/key_to_find = "[ckey(params)]"
// They didn't provide anything worth looking up.
if(!length(key_to_find))
return "[sender.friendly_name], you need to provide a Byond username at the end of the command. It can be in 'key' format (with spaces and characters) or 'ckey' format (without spaces or special characters)."
var/datum/db_query/query = SSdbcore.NewQuery("SELECT discord_id FROM erro_player WHERE ckey = :t_ckey",list("t_ckey" = key_to_find))
query.Execute()
if(!query.NextRow())
qdel(query)
return "[sender.friendly_name], the server's database is either not responding or there's no such ckey in the database."
if(!query.item[1])
qdel(query)
return "[sender.friendly_name], [key_to_find] is in the database, but has no discord ID associated with them."
var/discord_id = query.item[1]
qdel(query)
return "[key_to_find]'s discord is <@[discord_id]>"
/datum/tgs_chat_command/getkey
name = "getkey"
help_text = "Finds the key associated with a discord id"
admin_only = TRUE
/datum/tgs_chat_command/getkey/Run(datum/tgs_chat_user/sender, params)
if(!params)
return "[sender.friendly_name], you need to provide a Discord ID at the end of the command. To obtain someone's Discord ID, you need to enable developer mode on discord, and then right click on their name and click Copy ID."
var/datum/db_query/query = SSdbcore.NewQuery("SELECT ckey FROM erro_player WHERE discord_id = :t_discord", list("t_discord"=params))
query.Execute()
if(!query.NextRow())
qdel(query)
return "[sender.friendly_name], the server's database is either not responding or there's no such Discord ID in the database."
var/user_key = query.item[1]
qdel(query)
return "<@[params]>'s ckey is [user_key]"
//modded fax code to properly handle non existing files before accessing the void
/datum/tgs_chat_command/readfax/Run(sender, params)
var/list/all_params = splittext(params, " ")
var/faxid = all_params[1]
if(!all_params[1] || !fexists("[CONFIG_GET(string/fax_export_dir)]/fax_[faxid].html"))
return "Im sorry Dave, Im afraid I cant do that"
var/faxmsg = return_file_text("[CONFIG_GET(string/fax_export_dir)]/fax_[faxid].html")
return "FAX: ```[strip_html_properly(faxmsg)]```"
/datum/tgs_chat_command/vore
name = "vore"
help_text = "vore"
admin_only = FALSE
/datum/tgs_chat_command/vore/Run(datum/tgs_chat_user/sender, params)
return "vore"
// - FAX
/datum/tgs_chat_command/readfax
name = "readfax"
help_text = "Reads a fax with specified faxid"
admin_only = TRUE
/datum/tgs_chat_command/readfax/Run(sender, params)
var/list/all_params = splittext(params, " ")
var/faxid = all_params[1]
var/faxmsg = return_file_text("[CONFIG_GET(string/fax_export_dir)]/fax_[faxid].html")
return "FAX: ```[strip_html_properly(faxmsg)]```"

View File

@@ -138,3 +138,243 @@ GLOBAL_LIST_EMPTY(pending_discord_registrations)
GLOB.pending_discord_registrations[GLOB.pending_discord_registrations.len] = list("ckey" = key_to_find, "id" = sender.id, "time" = world.realtime) GLOB.pending_discord_registrations[GLOB.pending_discord_registrations.len] = list("ckey" = key_to_find, "id" = sender.id, "time" = world.realtime)
return "[sender.friendly_name], I've sent you a message in-game. Please verify your username there to complete your registration within 10 minutes." return "[sender.friendly_name], I've sent you a message in-game. Please verify your username there to complete your registration within 10 minutes."
// Coin flip
/datum/tgs_chat_command/coinflip
name = "flip"
help_text = "babies first TGS command"
admin_only = FALSE
/datum/tgs_chat_command/coinflip/Run(datum/tgs_chat_user/sender, params)
var/x
if(params != "tails" && params != "heads")
return "```You need to guess!\n either heads or tails!```"
if(prob(50))
if(params == "tails")
x = "correct"
else
x = "wrong"
return "```Tails. [x]!```"
if(params == "tails")
x = "wrong"
else
x = "correct"
return "```Heads. [x]!```"
/datum/tgs_chat_command/coinflip/alias
name = "coin"
// Manifest
/datum/tgs_chat_command/manifest
name = "manifest"
help_text = "Shows the current crew manifest"
admin_only = FALSE
/proc/ManifestToHtml()
var/html = ""
if(GLOB.data_core)
html = GLOB.data_core.get_manifest(FALSE,TRUE,snowflake = TRUE)
else
html = span_bold("ERROR: NO DATACORE") //Could make the error more fancy later
rustg_file_write(html,"[CONFIG_GET(string/nodebot_location)]\\html.html")
/datum/tgs_chat_command/manifest/Run(datum/tgs_chat_user/sender, params)
if(CONFIG_GET(flag/nodebot_enabled))
ManifestToHtml()
return "http://manifest.chompstation13.net/"
else
var/outp = "Crew Manifest:"
var/list/total = list()
if(GLOB.data_core)
GLOB.data_core.get_manifest_list()
for(var/list/item in GLOB.PDA_Manifest)
outp += "\n__**[item["cat"]]:**__"
for(var/list/person in item["elems"])
total |= person
outp += "\n[person["name"]] -:- [person["rank"]]"
return "**Total crew members:** [total.len]\n" + outp
// Discord ping
/datum/tgs_chat_command/discordping
name = "discordping"
help_text = "Pings the discord associated with the associated ckey"
admin_only = TRUE
/datum/tgs_chat_command/discordping/Run(datum/tgs_chat_user/sender, params)
var/key_to_find = "[ckey(params)]"
// They didn't provide anything worth looking up.
if(!length(key_to_find))
return "[sender.friendly_name], you need to provide a Byond username at the end of the command. It can be in 'key' format (with spaces and characters) or 'ckey' format (without spaces or special characters)."
var/datum/db_query/query = SSdbcore.NewQuery("SELECT discord_id FROM erro_player WHERE ckey = :t_ckey",list("t_ckey" = key_to_find))
query.Execute()
if(!query.NextRow())
qdel(query)
return "[sender.friendly_name], the server's database is either not responding or there's no such ckey in the database."
if(!query.item[1])
qdel(query)
return "[sender.friendly_name], [key_to_find] is in the database, but has no discord ID associated with them."
var/discord_id = query.item[1]
qdel(query)
return "[key_to_find]'s discord is <@[discord_id]>"
/datum/tgs_chat_command/getkey
name = "getkey"
help_text = "Finds the key associated with a discord id"
admin_only = TRUE
/datum/tgs_chat_command/getkey/Run(datum/tgs_chat_user/sender, params)
if(!params)
return "[sender.friendly_name], you need to provide a Discord ID at the end of the command. To obtain someone's Discord ID, you need to enable developer mode on discord, and then right click on their name and click Copy ID."
var/datum/db_query/query = SSdbcore.NewQuery("SELECT ckey FROM erro_player WHERE discord_id = :t_discord", list("t_discord"=params))
query.Execute()
if(!query.NextRow())
qdel(query)
return "[sender.friendly_name], the server's database is either not responding or there's no such Discord ID in the database."
var/user_key = query.item[1]
qdel(query)
return "<@[params]>'s ckey is [user_key]"
/datum/tgs_chat_command/vore
name = "vore"
help_text = "vore"
admin_only = FALSE
/datum/tgs_chat_command/vore/Run(datum/tgs_chat_user/sender, params)
return "vore"
// - FAX
/datum/tgs_chat_command/readfax
name = "readfax"
help_text = "Reads a fax with specified faxid"
admin_only = TRUE
/datum/tgs_chat_command/readfax/Run(sender, params)
var/list/all_params = splittext(params, " ")
if(!LAZYLEN(all_params))
return "```Invalid command, missing fax id```"
var/faxid = all_params[1]
if(!all_params[1] || !fexists("[CONFIG_GET(string/fax_export_dir)]/fax_[faxid].html"))
return "Im sorry Dave, Im afraid I cant do that"
var/faxmsg = return_file_text("[CONFIG_GET(string/fax_export_dir)]/fax_[faxid].html")
return "FAX: ```[strip_html_properly(faxmsg)]```"
// Reply to admin tickets
/datum/tgs_chat_command/ticketreply
name = "ticket"
help_text = "allows admins to reply to open tickets. Usage: ticket id \[reply, reject, icissue, close, resolve, handle, reopen\] message"
admin_only = TRUE
/datum/tgs_chat_command/ticketreply/Run(datum/tgs_chat_user/sender, params)
var/list/message_as_list = splittext(params, " ")
if(!LAZYLEN(message_as_list))
return "```Invalid command usage: ticket id \[reply, reject, icissue, close, resolve, handle, reopen\] message```"
var/id = text2num(message_as_list[1])
if(!isnum(id))
return "```First param must be the ticket ID.```"
message_as_list.Cut(1, 2)
if(!LAZYLEN(message_as_list))
return "```Invalid command usage: ticket id \[reply, reject, icissue, close, resolve, handle, reopen\] message```"
var/action = message_as_list[1]
if(isnum(action))
return "```Second param must be the action type.```"
message_as_list.Cut(1, 2)
if(!LAZYLEN(message_as_list) && action == "reply")
return "```Invalid command usage: ticket id \[reply, reject, icissue, close, resolve, handle, reopen\] message```"
if(!(action in list("reply", "reject", "icissue", "close", "resolve", "handle", "reopen")))
return "```Invalid command usage: ticket id \[reply, reject, icissue, close, resolve, handle, reopen\] message```"
var/text_message
if(LAZYLEN(message_as_list))
text_message = message_as_list.Join(" ")
var/datum/ticket/found
if(action == "reopen")
for(var/datum/ticket/ticket in GLOB.tickets.closed_tickets)
if(ticket.id == id)
found = ticket
if(!found)
return "```Ticket with id #[id] was not found in closed tickets!```"
else
for(var/datum/ticket/ticket in GLOB.tickets.active_tickets)
if(ticket.id == id)
found = ticket
if(!found)
return "```Ticket with id #[id] was not found in active tickets!```"
if(text_message)
to_chat(found.initiator, span_admin_pm_warning("Admin PM from-" + span_bold("Discord Relay") + ": [text_message]"))
found.AddInteraction("Discord Relay: [text_message]")
switch(action)
if("reject")
found.Reject("Remote (Discord)")
if("icissue")
found.ICIssue("Remote (Discord)")
if("close")
found.Close("Remote (Discord)")
if("resolve")
found.Resolve("Remote (Discord)")
if("handle")
found.HandleIssue("Remote (Discord)")
if("reopen")
found.Reopen("Remote (Discord)")
return "Ticket command ([action]) sent!"
// Remote smite
/datum/tgs_chat_command/remote_smite
name = "smite"
help_text = "allows admins to remotely smite players. Usage: smite \[bsa, lightning, pie, gib, dust\] name"
admin_only = TRUE
/datum/tgs_chat_command/remote_smite/Run(datum/tgs_chat_user/sender, params)
var/list/message_as_list = splittext(params, " ")
if(!LAZYLEN(message_as_list))
return "```Invalid command usage: smite \[bsa, lightning, pie, gib, dust\] name```"
var/smite_name = message_as_list[1]
if(!istext(smite_name))
return "```First param must be the smite name.```"
message_as_list.Cut(1, 2)
if(!LAZYLEN(message_as_list))
return "```Invalid command usage: smite \[bsa, lightning, pie, gib, dust\] name```"
var/player_name = message_as_list.Join(" ")
var/mob/living/real_target
for(var/mob/living/target in player_list)
if(target.real_name == player_name)
real_target = target
break
if(!real_target)
return "Smite [smite_name] failed to find player ([player_name]), validate their name and try again."
switch(smite_name)
if("bsa")
bluespace_artillery(real_target)
if("lightning")
var/turf/T = get_step(get_step(real_target, NORTH), NORTH)
T.Beam(real_target, icon_state="lightning[rand(1,12)]", time = 5)
real_target.electrocute_act(75,def_zone = BP_HEAD)
real_target.visible_message(span_danger("[real_target] is struck by lightning!"))
if("pie")
new/obj/effect/decal/cleanable/pie_smudge(get_turf(real_target))
playsound(real_target, 'sound/effects/slime_squish.ogg', 100, 1, get_rand_frequency(), falloff = 5)
real_target.Weaken(1)
real_target.visible_message(span_danger("[real_target] is struck by pie!"))
if("gib")
real_target.gib()
if("dust")
real_target.dust()
return "Smite [smite_name] sent!"

View File

@@ -7,7 +7,6 @@
GLOBAL_DATUM_INIT(tgui_ticket_state, /datum/tgui_state/ticket_state, new) GLOBAL_DATUM_INIT(tgui_ticket_state, /datum/tgui_state/ticket_state, new)
/datum/tgui_state/ticket_state/can_use_topic(src_object, mob/user) /datum/tgui_state/ticket_state/can_use_topic(src_object, mob/user)
//if (user.client.current_ticket) if(user.client.current_ticket)
// return STATUS_INTERACTIVE return STATUS_INTERACTIVE
//return STATUS_CLOSE return STATUS_CLOSE
return STATUS_INTERACTIVE

View File

@@ -36,7 +36,7 @@
to_chat(src, span_warning("Ticket not found, creating new one...")) to_chat(src, span_warning("Ticket not found, creating new one..."))
else else
current_ticket.AddInteraction("[usr.ckey] opened a new ticket.") current_ticket.AddInteraction("[usr.ckey] opened a new ticket.")
current_ticket.Resolve() current_ticket.Resolve(usr)
new /datum/ticket(msg, src, FALSE, 0) new /datum/ticket(msg, src, FALSE, 0)
@@ -116,7 +116,7 @@ ADMIN_VERB(cmd_mentor_ticket_panel, (R_ADMIN|R_SERVER|R_MOD|R_MENTOR), "Mentor T
to_chat(src, span_warning("Ticket not found, creating new one...")) to_chat(src, span_warning("Ticket not found, creating new one..."))
else if(current_ticket) else if(current_ticket)
current_ticket.AddInteraction("[key_name_admin(usr)] opened a new ticket.") current_ticket.AddInteraction("[key_name_admin(usr)] opened a new ticket.")
current_ticket.Close() current_ticket.Close(usr)
new /datum/ticket(msg, src, FALSE, 1) new /datum/ticket(msg, src, FALSE, 1)

View File

@@ -162,14 +162,14 @@ GLOBAL_DATUM_INIT(tickets, /datum/tickets, new)
if(C.current_ticket) if(C.current_ticket)
C.current_ticket.AddInteraction("Client reconnected.") C.current_ticket.AddInteraction("Client reconnected.")
C.current_ticket.initiator = C 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 C.current_ticket.initiator.mob.throw_alert("open ticket", /obj/screen/alert/open_ticket)
//Dissasociate ticket //Dissasociate ticket
/datum/tickets/proc/ClientLogout(client/C) /datum/tickets/proc/ClientLogout(client/C)
if(C.current_ticket) if(C.current_ticket)
var/datum/ticket/T = C.current_ticket var/datum/ticket/T = C.current_ticket
T.AddInteraction("Client disconnected.") T.AddInteraction("Client disconnected.")
// T.initiator.mob.clear_alert("open ticket") // Uncomment this line to enable player-side ticket ui T.initiator.mob.clear_alert("open ticket")
T.initiator = null T.initiator = null
T = null T = null
@@ -227,6 +227,7 @@ INITIALIZE_IMMEDIATE(/obj/effect/statclick/ticket_list)
var/closed_at var/closed_at
var/client/initiator //semi-misnomer, it's the person who ahelped/was bwoinked var/client/initiator //semi-misnomer, it's the person who ahelped/was bwoinked
var/datum/weakref/handler_ref
var/handler = "/Unassigned\\" // The admin handling the ticket var/handler = "/Unassigned\\" // The admin handling the ticket
var/initiator_ckey var/initiator_ckey
var/initiator_key_name var/initiator_key_name
@@ -287,7 +288,7 @@ INITIALIZE_IMMEDIATE(/obj/effect/statclick/ticket_list)
if(initiator.current_ticket) //This is a bug if(initiator.current_ticket) //This is a bug
log_debug("Multiple ahelp current_tickets") log_debug("Multiple ahelp current_tickets")
initiator.current_ticket.AddInteraction("Ticket erroneously left open by code") initiator.current_ticket.AddInteraction("Ticket erroneously left open by code")
initiator.current_ticket.Close() initiator.current_ticket.Close(usr)
initiator.current_ticket = src initiator.current_ticket = src
var/parsed_message = keywords_lookup(msg) var/parsed_message = keywords_lookup(msg)
@@ -340,12 +341,13 @@ INITIALIZE_IMMEDIATE(/obj/effect/statclick/ticket_list)
//TC.T = src //TC.T = src
//TC.tgui_interact(C.mob) //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 C.mob.throw_alert("open ticket", /obj/screen/alert/open_ticket)
/datum/ticket/Destroy() /datum/ticket/Destroy()
RemoveActive() RemoveActive()
GLOB.tickets.closed_tickets -= src GLOB.tickets.closed_tickets -= src
GLOB.tickets.resolved_tickets -= src GLOB.tickets.resolved_tickets -= src
handler_ref = null
return ..() return ..()
/datum/ticket/proc/AddInteraction(formatted_message) /datum/ticket/proc/AddInteraction(formatted_message)
@@ -440,7 +442,7 @@ INITIALIZE_IMMEDIATE(/obj/effect/statclick/ticket_list)
*/ */
//Reopen a closed ticket //Reopen a closed ticket
/datum/ticket/proc/Reopen() /datum/ticket/proc/Reopen(user)
if(state == AHELP_ACTIVE) if(state == AHELP_ACTIVE)
to_chat(usr, span_warning("This ticket is already open.")) to_chat(usr, span_warning("This ticket is already open."))
return return
@@ -463,20 +465,22 @@ INITIALIZE_IMMEDIATE(/obj/effect/statclick/ticket_list)
if(initiator) if(initiator)
initiator.current_ticket = src initiator.current_ticket = src
AddInteraction(span_purple("Reopened by [key_name_admin(usr)]")) var/admin_reopener_name = ismob(user) ? key_name_admin(user) : user
AddInteraction(span_purple("Reopened by [admin_reopener_name]"))
if(initiator) if(initiator)
to_chat(initiator, span_filter_adminlog("[span_purple("Ticket [TicketHref("#[id]")] was reopened by [key_name(usr,FALSE,FALSE)].")]")) to_chat(initiator, span_filter_adminlog("[span_purple("Ticket [TicketHref("#[id]")] was reopened by [ismob(user) ? key_name(usr,FALSE,FALSE) : user].")]"))
var/msg = span_adminhelp("Ticket [TicketHref("#[id]")] reopened by [key_name_admin(usr)].") var/msg = span_adminhelp("Ticket [TicketHref("#[id]")] reopened by [admin_reopener_name].")
message_admins(msg) message_admins(msg)
log_admin(msg) log_admin(msg)
feedback_inc("ticket_reopen") feedback_inc("ticket_reopen")
initiator.mob.throw_alert("open ticket", /obj/screen/alert/open_ticket)
//TicketPanel() //can only be done from here, so refresh it //TicketPanel() //can only be done from here, so refresh it
SSwebhooks.send( SSwebhooks.send(
WEBHOOK_AHELP_SENT, WEBHOOK_AHELP_SENT,
list( list(
"name" = "Ticket ([id]) (Round ID: [GLOB.round_id ? GLOB.round_id : "No database"]) reopened.", "name" = "Ticket ([id]) (Round ID: [GLOB.round_id ? GLOB.round_id : "No database"]) reopened.",
"body" = "Reopened by [key_name(usr)]." "body" = "Reopened by [ismob(user) ? key_name(user) : user]."
) )
) )
@@ -491,44 +495,46 @@ INITIALIZE_IMMEDIATE(/obj/effect/statclick/ticket_list)
initiator.current_ticket = null initiator.current_ticket = null
//Mark open ticket as closed/meme //Mark open ticket as closed/meme
/datum/ticket/proc/Close(silent = FALSE) /datum/ticket/proc/Close(user, silent = FALSE)
if(state != AHELP_ACTIVE) if(state != AHELP_ACTIVE)
return return
RemoveActive() RemoveActive()
state = AHELP_CLOSED state = AHELP_CLOSED
GLOB.tickets.ListInsert(src) GLOB.tickets.ListInsert(src)
AddInteraction(span_filter_adminlog(span_red("Closed by [key_name_admin(usr)]."))) var/admin_closer_name = ismob(user) ? key_name_admin(user) : user
AddInteraction(span_filter_adminlog(span_red("Closed by [admin_closer_name].")))
if(initiator) if(initiator)
to_chat(initiator, span_filter_adminlog("[span_red("Ticket [TicketHref("#[id]")] was closed by [key_name(usr,FALSE,FALSE)].")]")) to_chat(initiator, span_filter_adminlog("[span_red("Ticket [TicketHref("#[id]")] was closed by [ismob(user) ? key_name(usr,FALSE,FALSE) : user].")]"))
if(!silent) if(!silent)
feedback_inc("ahelp_close") feedback_inc("ahelp_close")
var/msg = "Ticket [TicketHref("#[id]")] closed by [key_name_admin(usr)]." var/msg = "Ticket [TicketHref("#[id]")] closed by [admin_closer_name]."
message_admins(msg) message_admins(msg)
log_admin(msg) log_admin(msg)
SSwebhooks.send( SSwebhooks.send(
WEBHOOK_AHELP_SENT, WEBHOOK_AHELP_SENT,
list( list(
"name" = "Ticket ([id]) (Round ID: [GLOB.round_id ? GLOB.round_id : "No database"]) closed.", "name" = "Ticket ([id]) (Round ID: [GLOB.round_id ? GLOB.round_id : "No database"]) closed.",
"body" = "Closed by [key_name(usr)].", "body" = "Closed by [ismob(user) ? key_name(user) : user].",
"color" = COLOR_WEBHOOK_BAD "color" = COLOR_WEBHOOK_BAD
) )
) )
// initiator?.mob?.clear_alert("open ticket") // Uncomment this line to enable player-side ticket ui initiator?.mob?.clear_alert("open ticket")
//Mark open ticket as resolved/legitimate, returns ahelp verb //Mark open ticket as resolved/legitimate, returns ahelp verb
/datum/ticket/proc/Resolve(silent = FALSE) /datum/ticket/proc/Resolve(user, silent = FALSE)
if(state != AHELP_ACTIVE) if(state != AHELP_ACTIVE)
return return
RemoveActive() RemoveActive()
state = AHELP_RESOLVED state = AHELP_RESOLVED
GLOB.tickets.ListInsert(src) GLOB.tickets.ListInsert(src)
AddInteraction(span_filter_adminlog(span_green("Resolved by [key_name_admin(usr)]."))) var/admin_resolver_name = ismob(user) ? key_name_admin(user) : user
AddInteraction(span_filter_adminlog(span_green("Resolved by [admin_resolver_name].")))
if(initiator) if(initiator)
to_chat(initiator, span_filter_adminlog("[span_green("Ticket [TicketHref("#[id]")] was marked resolved by [key_name(usr,FALSE,FALSE)].")]")) to_chat(initiator, span_filter_adminlog("[span_green("Ticket [TicketHref("#[id]")] was marked resolved by [ismob(user) ? key_name(usr,FALSE,FALSE) : user].")]"))
if(!silent) if(!silent)
feedback_inc("ticket_resolve") feedback_inc("ticket_resolve")
var/msg = "Ticket [TicketHref("#[id]")] resolved by [key_name_admin(usr)]" var/msg = "Ticket [TicketHref("#[id]")] resolved by [admin_resolver_name]"
if(type == 1) if(type == 1)
message_mentors(msg) message_mentors(msg)
else if (type == 0) else if (type == 0)
@@ -540,14 +546,14 @@ INITIALIZE_IMMEDIATE(/obj/effect/statclick/ticket_list)
WEBHOOK_AHELP_SENT, WEBHOOK_AHELP_SENT,
list( list(
"name" = "Ticket ([id]) (Round ID: [GLOB.round_id ? GLOB.round_id : "No database"]) resolved.", "name" = "Ticket ([id]) (Round ID: [GLOB.round_id ? GLOB.round_id : "No database"]) resolved.",
"body" = "Marked as Resolved by [key_name(usr)].", "body" = "Marked as Resolved by [ismob(user) ? key_name(user) : user].",
"color" = COLOR_WEBHOOK_GOOD "color" = COLOR_WEBHOOK_GOOD
) )
) )
// initiator?.mob?.clear_alert("open ticket") // Uncomment this line to enable player-side ticket ui initiator?.mob?.clear_alert("open ticket")
//Close and return ahelp verb, use if ticket is incoherent //Close and return ahelp verb, use if ticket is incoherent
/datum/ticket/proc/Reject(key_name = key_name_admin(usr)) /datum/ticket/proc/Reject(mob/user)
if(state != AHELP_ACTIVE) if(state != AHELP_ACTIVE)
return return
@@ -559,23 +565,24 @@ INITIALIZE_IMMEDIATE(/obj/effect/statclick/ticket_list)
[span_red(span_bold("Your admin help was rejected."))]<br>\ [span_red(span_bold("Your admin help was rejected."))]<br>\
Please try to be calm, clear, and descriptive in admin helps, do not assume the admin has seen any related events, and clearly state the names of anybody you are reporting.")) Please try to be calm, clear, and descriptive in admin helps, do not assume the admin has seen any related events, and clearly state the names of anybody you are reporting."))
var/admin_rejecter_name = ismob(user) ? key_name_admin(user) : user
feedback_inc("ahelp_reject") feedback_inc("ahelp_reject")
var/msg = "Ticket [TicketHref("#[id]")] rejected by [key_name_admin(usr)]" var/msg = "Ticket [TicketHref("#[id]")] rejected by [admin_rejecter_name]"
message_admins(msg) message_admins(msg)
log_admin(msg) log_admin(msg)
AddInteraction("Rejected by [key_name_admin(usr)].") AddInteraction("Rejected by [admin_rejecter_name].")
Close(silent = TRUE) Close(user, silent = TRUE)
SSwebhooks.send( SSwebhooks.send(
WEBHOOK_AHELP_SENT, WEBHOOK_AHELP_SENT,
list( list(
"name" = "Ticket ([id]) (Round ID: [GLOB.round_id ? GLOB.round_id : "No database"]) rejected.", "name" = "Ticket ([id]) (Round ID: [GLOB.round_id ? GLOB.round_id : "No database"]) rejected.",
"body" = "Rejected by [key_name(usr)].", "body" = "Rejected by [ismob(user) ? key_name(user) : user].",
"color" = COLOR_WEBHOOK_BAD "color" = COLOR_WEBHOOK_BAD
) )
) )
//Resolve ticket with IC Issue message //Resolve ticket with IC Issue message
/datum/ticket/proc/ICIssue(key_name = key_name_admin(usr)) /datum/ticket/proc/ICIssue(user)
if(state != AHELP_ACTIVE) if(state != AHELP_ACTIVE)
return return
@@ -586,51 +593,57 @@ INITIALIZE_IMMEDIATE(/obj/effect/statclick/ticket_list)
if(initiator) if(initiator)
to_chat(initiator, span_filter_pm(msg)) to_chat(initiator, span_filter_pm(msg))
var/admin_resolve_name = ismob(user) ? key_name_admin(user) : user
feedback_inc("ahelp_icissue") feedback_inc("ahelp_icissue")
msg = "Ticket [TicketHref("#[id]")] marked as IC by [key_name_admin(usr)]" msg = "Ticket [TicketHref("#[id]")] marked as IC by [admin_resolve_name]"
message_admins(msg) message_admins(msg)
log_admin(msg) log_admin(msg)
AddInteraction("Marked as IC issue by [key_name_admin(usr)]") AddInteraction("Marked as IC issue by [admin_resolve_name]")
Resolve(silent = TRUE) Resolve(user, silent = TRUE)
SSwebhooks.send( SSwebhooks.send(
WEBHOOK_AHELP_SENT, WEBHOOK_AHELP_SENT,
list( list(
"name" = "Ticket ([id]) (Round ID: [GLOB.round_id ? GLOB.round_id : "No database"]) marked as IC issue.", "name" = "Ticket ([id]) (Round ID: [GLOB.round_id ? GLOB.round_id : "No database"]) marked as IC issue.",
"body" = "Marked as IC Issue by [key_name(usr)].", "body" = "Marked as IC Issue by [ismob(user) ? key_name(user) : user].",
"color" = COLOR_WEBHOOK_BAD "color" = COLOR_WEBHOOK_BAD
) )
) )
//Resolve ticket with IC Issue message //Handle ticket
/datum/ticket/proc/HandleIssue() /datum/ticket/proc/HandleIssue(user)
if(state != AHELP_ACTIVE) if(state != AHELP_ACTIVE)
return return
if(handler == key_name(usr, FALSE, TRUE)) var/handler_name = ismob(user) ? key_name(user, FALSE, TRUE) : user
to_chat(usr, span_red("You are already handling this ticket.")) if(handler == handler_name)
to_chat(user, span_red("You are already handling this ticket."))
return return
var/handler_shown_name = ismob(user) ? key_name_admin(user) : user
var/msg var/msg
switch(level) switch(level)
if(0) if(0)
msg = span_green("Your MentorHelp is being handled by [key_name(usr,FALSE,FALSE)] please be patient.") msg = span_green("Your MentorHelp is being handled by [handler_shown_name] please be patient.")
if(1) if(1)
msg = span_red("Your AdminHelp is being handled by [key_name(usr,FALSE,FALSE)] please be patient.") msg = span_red("Your AdminHelp is being handled by [handler_shown_name] please be patient.")
if(initiator) if(initiator)
to_chat(initiator, msg) to_chat(initiator, msg)
feedback_inc("ahelp_handling") feedback_inc("ahelp_handling")
msg = "Ticket [TicketHref("#[id]")] being handled by [key_name(usr,FALSE,FALSE)]" msg = "Ticket [TicketHref("#[id]")] being handled by [handler_shown_name]"
message_admins(msg) message_admins(msg)
log_admin(msg) log_admin(msg)
AddInteraction("[key_name_admin(usr)] is now handling this ticket.") AddInteraction("[handler_shown_name] is now handling this ticket.")
handler = key_name(usr, FALSE, TRUE) handler = handler_name
if(ismob(user))
var/mob/our_handler_mob = user
handler_ref = WEAKREF(our_handler_mob.client)
SSwebhooks.send( SSwebhooks.send(
WEBHOOK_AHELP_SENT, WEBHOOK_AHELP_SENT,
list( list(
"name" = "Ticket ([id]) (Round ID: [GLOB.round_id ? GLOB.round_id : "No database"]) being handled.", "name" = "Ticket ([id]) (Round ID: [GLOB.round_id ? GLOB.round_id : "No database"]) being handled.",
"body" = "[key_name(usr)] is now handling the ticket." "body" = "[ismob(user) ? key_name(user) : user] is now handling the ticket."
) )
) )
@@ -681,7 +694,7 @@ INITIALIZE_IMMEDIATE(/obj/effect/statclick/ticket_list)
if("retitle") if("retitle")
Retitle() Retitle()
if("reject") if("reject")
Reject() Reject(usr)
if("reply") if("reply")
switch(level) switch(level)
if(0) if(0)
@@ -689,15 +702,15 @@ INITIALIZE_IMMEDIATE(/obj/effect/statclick/ticket_list)
if(1) if(1)
usr.client.cmd_ahelp_reply(initiator) usr.client.cmd_ahelp_reply(initiator)
if("icissue") if("icissue")
ICIssue() ICIssue(usr)
if("close") if("close")
Close() Close(usr)
if("resolve") if("resolve")
Resolve() Resolve(usr)
if("handleissue") if("handleissue")
HandleIssue() HandleIssue(usr)
if("reopen") if("reopen")
Reopen() Reopen(usr)
if("escalate") if("escalate")
Escalate() Escalate()

View File

@@ -6,22 +6,19 @@
var/datum/ticket/T var/datum/ticket/T
/datum/ticket_chat/tgui_interact(mob/user, datum/tgui/ui) /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)
//ui = SStgui.try_update_ui(user, src, ui) if(!ui)
//if(!ui) ui = new(user, src, "TicketChat", "Ticket #[T.id] - [T.LinkedReplyName("\ref[T]")]")
// ui = new(user, src, "TicketChat", "Ticket #[T.id] - [T.LinkedReplyName("\ref[T]")]") ui.open()
// ui.open() user.clear_alert("open ticket")
// user.clear_alert("open ticket")
/datum/ticket_chat/tgui_close(mob/user) /datum/ticket_chat/tgui_close(mob/user)
. = ..() . = ..()
return // Remove this line to enable player-side ticket ui if(user.client.current_ticket)
//if(user.client.current_ticket) user.throw_alert("open ticket", /obj/screen/alert/open_ticket)
// user.throw_alert("open ticket", /obj/screen/alert/open_ticket)
/datum/ticket_chat/tgui_state(mob/user) /datum/ticket_chat/tgui_state(mob/user)
return ADMIN_STATE(R_ADMIN|R_EVENT|R_DEBUG) // Remove this line to enable player-side ticket ui return GLOB.tgui_ticket_state
//return GLOB.tgui_ticket_state
/datum/ticket_chat/tgui_data(mob/user) /datum/ticket_chat/tgui_data(mob/user)
var/list/data = list() var/list/data = list()
@@ -29,9 +26,9 @@
data["id"] = T.id data["id"] = T.id
data["level"] = T.level data["level"] = T.level
// data["handler"] = T.handler // Uncomment this line to enable player-side ticket ui data["handler"] = T.handler
// data["log"] = T._interactions // Uncomment this line to enable player-side ticket ui data["log"] = T._interactions
return data return data
@@ -43,10 +40,27 @@
if(!params["msg"]) if(!params["msg"])
return return
var/sane_message = sanitize(params["msg"])
switch(T.level) switch(T.level)
if (0) if (0)
ui.user.client.cmd_mentor_pm(ui.user.client, sanitize(params["msg"]), T) if(T.initiator == ui.user.client)
var/client/handler = T.handler_ref?.resolve()
if(handler)
ui.user.client.cmd_mentor_pm(handler, sane_message, T)
return TRUE
T.AddInteraction("[T.initiator_key_name]: [sane_message]")
return TRUE
ui.user.client.cmd_mentor_pm(T.initiator, sane_message, T)
return TRUE
if (1) if (1)
ui.user.client.cmd_admin_pm(ui.user.client, sanitize(params["msg"]), T) if(T.initiator == ui.user.client)
var/client/handler = T.handler_ref?.resolve()
if(handler)
ui.user.client.cmd_admin_pm(handler, sane_message, T)
return TRUE
T.AddInteraction("[key_name_admin(ui.user)]: [sane_message]")
return TRUE
ui.user.client.cmd_admin_pm(T.initiator, sane_message, T)
return TRUE
. = TRUE . = TRUE

View File

@@ -57,6 +57,7 @@
"state" = get_ticket_state(T.state), "state" = get_ticket_state(T.state),
"level" = T.level, "level" = T.level,
"handler" = T.handler, "handler" = T.handler,
"ishandled" = !!T.handler_ref?.resolve(),
"opened_at" = (world.time - T.opened_at), "opened_at" = (world.time - T.opened_at),
"closed_at" = (world.time - T.closed_at), "closed_at" = (world.time - T.closed_at),
"opened_at_date" = gameTimestamp(wtime = T.opened_at), "opened_at_date" = gameTimestamp(wtime = T.opened_at),
@@ -71,6 +72,7 @@
"state" = get_ticket_state(T.state), "state" = get_ticket_state(T.state),
"level" = T.level, "level" = T.level,
"handler" = T.handler, "handler" = T.handler,
"ishandled" = !!T.handler_ref?.resolve(),
"opened_at" = (world.time - T.opened_at), "opened_at" = (world.time - T.opened_at),
"closed_at" = (world.time - T.closed_at), "closed_at" = (world.time - T.closed_at),
"opened_at_date" = gameTimestamp(wtime = T.opened_at), "opened_at_date" = gameTimestamp(wtime = T.opened_at),
@@ -85,6 +87,7 @@
"state" = get_ticket_state(T.state), "state" = get_ticket_state(T.state),
"level" = T.level, "level" = T.level,
"handler" = T.handler, "handler" = T.handler,
"ishandled" = !!T.handler_ref?.resolve(),
"opened_at" = (world.time - T.opened_at), "opened_at" = (world.time - T.opened_at),
"closed_at" = (world.time - T.closed_at), "closed_at" = (world.time - T.closed_at),
"opened_at_date" = gameTimestamp(wtime = T.opened_at), "opened_at_date" = gameTimestamp(wtime = T.opened_at),
@@ -145,7 +148,7 @@
to_chat(ui.user, span_warning("Ticket not found, creating new one...")) to_chat(ui.user, span_warning("Ticket not found, creating new one..."))
else else
player.current_ticket.AddInteraction("[key_name_admin(ui.user)] opened a new ticket.") player.current_ticket.AddInteraction("[key_name_admin(ui.user)] opened a new ticket.")
player.current_ticket.Close() player.current_ticket.Close(ui.user)
// Create a new ticket and handle it. You created it afterall! // Create a new ticket and handle it. You created it afterall!
var/datum/ticket/T = new /datum/ticket(ticket_text, player, TRUE, level) var/datum/ticket/T = new /datum/ticket(ticket_text, player, TRUE, level)
@@ -153,7 +156,7 @@
T.level = 1 T.level = 1
else else
T.level = 0 T.level = 0
T.HandleIssue() T.HandleIssue(ui.user)
switch(T.level) switch(T.level)
if (0) if (0)
ui.user.client.cmd_mentor_pm(player, ticket_text, T) ui.user.client.cmd_mentor_pm(player, ticket_text, T)
@@ -168,7 +171,7 @@
ui.user.client.selected_ticket.Retitle() ui.user.client.selected_ticket.Retitle()
. = TRUE . = TRUE
if("reopen_ticket") if("reopen_ticket")
ui.user.client.selected_ticket.Reopen() ui.user.client.selected_ticket.Reopen(ui.user)
. = TRUE . = TRUE
if("undock_ticket") if("undock_ticket")
ui.user.client.selected_ticket.tgui_interact(ui.user) ui.user.client.selected_ticket.tgui_interact(ui.user)
@@ -249,7 +252,7 @@
Retitle() Retitle()
. = TRUE . = TRUE
if("reopen") if("reopen")
Reopen() Reopen(ui.user)
. = TRUE . = TRUE
if("legacy") if("legacy")
TicketPanelLegacy(ui.user) TicketPanelLegacy(ui.user)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@@ -792,22 +792,12 @@ macro "hotkeymode"
elem "ctrlshiftsubtract" elem "ctrlshiftsubtract"
name = "CTRL+SHIFT+SUBTRACT" name = "CTRL+SHIFT+SUBTRACT"
command = "planedown" command = "planedown"
<<<<<<< HEAD
// elem // CHOMPREMOVE Start // elem // CHOMPREMOVE Start
// name = "Space" // name = "Space"
// command = ".throwon" // command = ".throwon"
// elem // elem
// name = "Space+UP" // name = "Space+UP"
// command = ".throwoff" // CHOMPREMOVE End // command = ".throwoff" // CHOMPREMOVE End
=======
elem "space"
name = "Space"
command = ".throwon"
elem "spaceup"
name = "Space+UP"
command = ".throwoff"
>>>>>>> b7969a971d (Replace the alt click menu with the RPG Lootpanel (#17938))
macro "borgmacro" macro "borgmacro"
elem "tab" elem "tab"
name = "TAB" name = "TAB"

View File

@@ -5,7 +5,6 @@ import { Window } from 'tgui/layouts';
import { import {
Box, Box,
Button, Button,
Divider,
Input, Input,
LabeledList, LabeledList,
Section, Section,
@@ -104,18 +103,34 @@ export const Ticket = (props) => {
<Section <Section
title={'Ticket #' + id} title={'Ticket #' + id}
buttons={ buttons={
<Box nowrap> <Stack>
<Button icon="pen" onClick={() => act('retitle')}> <Stack.Item>
Rename Ticket <Button icon="pen" onClick={() => act('retitle')}>
</Button> Rename Ticket
<Button onClick={() => act('legacy')}>Legacy UI</Button> </Button>
<Button color={LevelColor[level]}>{Level[level]}</Button> </Stack.Item>
</Box> <Stack.Item>
<Button onClick={() => act('legacy')}>Legacy UI</Button>
</Stack.Item>
<Stack.Item>
<Box
className="TicketPanel__Label"
backgroundColor={LevelColor[level]}
>
{Level[level]}
</Box>
</Stack.Item>
</Stack>
} }
> >
<LabeledList> <LabeledList>
<LabeledList.Item label="Ticket ID"> <LabeledList.Item label="Ticket ID">
#{id}: <div dangerouslySetInnerHTML={{ __html: name }} /> <Stack>
<Stack.Item>#{id}:</Stack.Item>
<Stack.Item>
<div dangerouslySetInnerHTML={{ __html: name }} />
</Stack.Item>
</Stack>
</LabeledList.Item> </LabeledList.Item>
<LabeledList.Item label="Type">{Level[level]}</LabeledList.Item> <LabeledList.Item label="Type">{Level[level]}</LabeledList.Item>
<LabeledList.Item label="State"> <LabeledList.Item label="State">
@@ -131,23 +146,28 @@ export const Ticket = (props) => {
</LabeledList.Item> </LabeledList.Item>
) : ( ) : (
<LabeledList.Item label="Closed At"> <LabeledList.Item label="Closed At">
{closed_at_date + <Stack>
' (' + <Stack.Item>
toFixed(round((closed_at / 600) * 10, 0) / 10, 1) + {closed_at_date +
' minutes ago.)'} ' (' +
<Button onClick={() => act('reopen')}>Reopen</Button> toFixed(round((closed_at / 600) * 10, 0) / 10, 1) +
' minutes ago.)'}
</Stack.Item>
<Stack.Item>
<Button onClick={() => act('reopen')}>Reopen</Button>
</Stack.Item>
</Stack>
</LabeledList.Item> </LabeledList.Item>
)} )}
<LabeledList.Item label="Actions"> <LabeledList.Item label="Actions">
<div dangerouslySetInnerHTML={{ __html: actions }} /> <div dangerouslySetInnerHTML={{ __html: actions }} />
</LabeledList.Item> </LabeledList.Item>
<LabeledList.Item label="Log" />
</LabeledList> </LabeledList>
</Section> </Section>
<Divider /> <Stack.Divider />
</Stack.Item> </Stack.Item>
<Stack.Item grow> <Stack.Item grow>
<Section scrollable ref={messagesEndRef} fill> <Section scrollable ref={messagesEndRef} fill title="Log">
<Stack fill direction="column"> <Stack fill direction="column">
<Stack.Item grow> <Stack.Item grow>
{Object.keys(log) {Object.keys(log)

View File

@@ -3,8 +3,8 @@ import { type RefObject, useEffect, useRef, useState } from 'react';
import { useBackend } from 'tgui/backend'; import { useBackend } from 'tgui/backend';
import { Window } from 'tgui/layouts'; import { Window } from 'tgui/layouts';
import { import {
Box,
Button, Button,
Divider,
Input, Input,
LabeledList, LabeledList,
Section, Section,
@@ -76,18 +76,22 @@ export const TicketChat = (props) => {
<Section <Section
title={'Ticket #' + id} title={'Ticket #' + id}
buttons={ buttons={
<Button color={LevelColor[level]}>{Level[level]}</Button> <Box
className="TicketPanel__Label"
backgroundColor={LevelColor[level]}
>
{Level[level]}
</Box>
} }
> >
<LabeledList> <LabeledList>
<LabeledList.Item label="Assignee">{handler}</LabeledList.Item> <LabeledList.Item label="Assignee">{handler}</LabeledList.Item>
<LabeledList.Item label="Log" />
</LabeledList> </LabeledList>
</Section> </Section>
<Divider /> <Stack.Divider />
</Stack.Item> </Stack.Item>
<Stack.Item grow> <Stack.Item grow>
<Section fill ref={messagesEndRef} scrollable> <Section fill ref={messagesEndRef} scrollable title="Log">
<Stack fill direction="column"> <Stack fill direction="column">
<Stack.Item grow> <Stack.Item grow>
{Object.keys(log) {Object.keys(log)
@@ -101,6 +105,8 @@ export const TicketChat = (props) => {
</Stack.Item> </Stack.Item>
</Stack> </Stack>
</Section> </Section>
</Stack.Item>
<Stack.Item>
<Section fill> <Section fill>
<Stack fill> <Stack fill>
<Stack.Item grow> <Stack.Item grow>

View File

@@ -3,6 +3,7 @@ import { type RefObject, useEffect, useRef, useState } from 'react';
import { useBackend } from 'tgui/backend'; import { useBackend } from 'tgui/backend';
import { Window } from 'tgui/layouts'; import { Window } from 'tgui/layouts';
import { import {
Blink,
Box, Box,
Button, Button,
Divider, Divider,
@@ -67,6 +68,7 @@ type Ticket = {
state: string; state: string;
level: number; level: number;
handler: string; handler: string;
ishandled: BooleanLike;
opened_at: number; opened_at: number;
closed_at: number; closed_at: number;
opened_at_date: string; opened_at_date: string;
@@ -138,65 +140,88 @@ export const TicketsPanel = (props) => {
<Window width={1000} height={600}> <Window width={1000} height={600}>
<Window.Content> <Window.Content>
<Stack fill> <Stack fill>
<Stack.Item shrink> <Stack.Item basis="25%">
<Section title="Filter"> <Stack vertical fill>
<Dropdown <Stack.Item>
width="100%" <Section title="Filter">
maxHeight="160px" <Dropdown
options={Object.values(State)} options={Object.values(State)}
selected={State[stateFilter]} selected={State[stateFilter]}
onSelected={(val) => onSelected={(val) =>
setStateFilter( setStateFilter(
Object.keys(State)[Object.values(State).indexOf(val)], Object.keys(State)[Object.values(State).indexOf(val)],
) )
} }
/> />
<Divider /> <Divider />
<Dropdown <Dropdown
width="100%" options={Object.values(availableLevel)}
maxHeight="160px" selected={availableLevel[levelFilter]}
options={Object.values(availableLevel)} onSelected={(val) =>
selected={availableLevel[levelFilter]} setLevelFilter(Object.values(availableLevel).indexOf(val))
onSelected={(val) => }
setLevelFilter(Object.values(availableLevel).indexOf(val)) />
} </Section>
/> </Stack.Item>
</Section> <Stack.Item grow>
<Section <Section title="Tickets" scrollable fill>
title="Tickets" <Tabs vertical>
scrollable <Tabs.Tab onClick={() => act('new_ticket')}>
fill New Ticket
height="450px" <Icon name="plus" ml={0.5} />
width="300px" </Tabs.Tab>
> <Divider />
<Tabs vertical> {filtered_tickets.map((ticket) => (
<Tabs.Tab onClick={() => act('new_ticket')}> <Tabs.Tab
New Ticket key={ticket.id}
<Icon name="plus" ml={0.5} /> selected={ticket.id === selected_ticket?.id}
</Tabs.Tab> onClick={() =>
<Divider /> act('pick_ticket', { ticket_id: ticket.id })
{filtered_tickets.map((ticket) => ( }
<Tabs.Tab >
key={ticket.id} <Stack vertical>
selected={ticket.id === selected_ticket?.id} <Stack.Item>
onClick={() => act('pick_ticket', { ticket_id: ticket.id })} <Stack align="center">
> <Stack.Item>
<Box inline> {ticket.ishandled ? (
<Box> <Box
<Button color={LevelColor[ticket.level]}> textColor="white"
{availableLevel[ticket.level]} className="TicketPanel__Label"
</Button> backgroundColor={LevelColor[ticket.level]}
{ticket.name} >
</Box> {availableLevel[ticket.level]}
<Box fontSize={0.9} textColor={StateColor[ticket.state]}> </Box>
State: {State[ticket.state]} | Assignee: ) : (
{ticket.handler} <Blink>
</Box> <Box
</Box> textColor="white"
</Tabs.Tab> className="TicketPanel__Label"
))} backgroundColor={LevelColor[ticket.level]}
</Tabs> >
</Section> {availableLevel[ticket.level]}
</Box>
</Blink>
)}
</Stack.Item>
<Stack.Item>{ticket.name}</Stack.Item>
</Stack>
</Stack.Item>
<Stack.Item>
<Box
fontSize={0.9}
textColor={StateColor[ticket.state]}
>
State: {State[ticket.state]} | Assignee:
{ticket.handler}
</Box>
</Stack.Item>
</Stack>
</Tabs.Tab>
))}
</Tabs>
</Section>
</Stack.Item>
</Stack>
</Stack.Item> </Stack.Item>
<Stack.Item grow> <Stack.Item grow>
{(selected_ticket && ( {(selected_ticket && (
@@ -205,34 +230,51 @@ export const TicketsPanel = (props) => {
<Section <Section
title={'Ticket #' + selected_ticket.id} title={'Ticket #' + selected_ticket.id}
buttons={ buttons={
<Box nowrap> <Stack>
<Button <Stack.Item>
icon="arrow-up" <Button
onClick={() => act('undock_ticket')} icon="arrow-up"
> onClick={() => act('undock_ticket')}
Undock >
</Button> Undock
<Button </Button>
icon="pen" </Stack.Item>
onClick={() => act('retitle_ticket')} <Stack.Item>
> <Button
Rename Ticket icon="pen"
</Button> onClick={() => act('retitle_ticket')}
<Button onClick={() => act('legacy')}>Legacy UI</Button> >
<Button color={LevelColor[selected_ticket.level]}> Rename Ticket
{availableLevel[selected_ticket.level]} </Button>
</Button> </Stack.Item>
</Box> <Stack.Item>
<Button onClick={() => act('legacy')}>
Legacy UI
</Button>
</Stack.Item>
<Stack.Item>
<Box
className="TicketPanel__Label"
backgroundColor={LevelColor[selected_ticket.level]}
>
{availableLevel[selected_ticket.level]}
</Box>
</Stack.Item>
</Stack>
} }
> >
<LabeledList> <LabeledList>
<LabeledList.Item label="Ticket ID"> <LabeledList.Item label="Ticket ID">
#{selected_ticket.id}: <Stack>
<div <Stack.Item>#{selected_ticket.id}:</Stack.Item>
dangerouslySetInnerHTML={{ <Stack.Item>
__html: selected_ticket.name, <div
}} dangerouslySetInnerHTML={{
/> __html: selected_ticket.name,
}}
/>
</Stack.Item>
</Stack>
</LabeledList.Item> </LabeledList.Item>
<LabeledList.Item label="Type"> <LabeledList.Item label="Type">
{availableLevel[selected_ticket.level]} {availableLevel[selected_ticket.level]}
@@ -256,17 +298,25 @@ export const TicketsPanel = (props) => {
</LabeledList.Item> </LabeledList.Item>
) : ( ) : (
<LabeledList.Item label="Closed At"> <LabeledList.Item label="Closed At">
{selected_ticket.closed_at_date + <Stack>
' (' + <Stack.Item>
toFixed( {selected_ticket.closed_at_date +
round((selected_ticket.closed_at / 600) * 10, 0) / ' (' +
10, toFixed(
1, round(
) + (selected_ticket.closed_at / 600) * 10,
' minutes ago.)'} 0,
<Button onClick={() => act('reopen_ticket')}> ) / 10,
Reopen 1,
</Button> ) +
' minutes ago.)'}
</Stack.Item>
<Stack.Item>
<Button onClick={() => act('reopen_ticket')}>
Reopen
</Button>
</Stack.Item>
</Stack>
</LabeledList.Item> </LabeledList.Item>
)} )}
<LabeledList.Item label="Actions"> <LabeledList.Item label="Actions">
@@ -276,13 +326,12 @@ export const TicketsPanel = (props) => {
}} }}
/> />
</LabeledList.Item> </LabeledList.Item>
<LabeledList.Item label="Log" />
</LabeledList> </LabeledList>
</Section> </Section>
<Divider /> <Stack.Divider />
</Stack.Item> </Stack.Item>
<Stack.Item grow> <Stack.Item grow>
<Section fill ref={messagesEndRef} scrollable> <Section fill ref={messagesEndRef} scrollable title="Log">
<Stack fill direction="column"> <Stack fill direction="column">
<Stack.Item grow> <Stack.Item grow>
{Object.keys(selected_ticket.log) {Object.keys(selected_ticket.log)
@@ -336,23 +385,29 @@ export const TicketsPanel = (props) => {
<Section <Section
title="No ticket selected" title="No ticket selected"
buttons={ buttons={
<Box nowrap> <Stack>
<Button <Stack.Item>
disabled <Button
icon="arrow-up" disabled
onClick={() => act('undock_ticket')} icon="arrow-up"
> onClick={() => act('undock_ticket')}
Undock >
</Button> Undock
<Button </Button>
disabled </Stack.Item>
icon="pen" <Stack.Item>
onClick={() => act('retitle_ticket')} <Button
> disabled
Rename Ticket icon="pen"
</Button> onClick={() => act('retitle_ticket')}
<Button onClick={() => act('legacy')}>Legacy UI</Button> >
</Box> Rename Ticket
</Button>
</Stack.Item>
<Stack.Item>
<Button onClick={() => act('legacy')}>Legacy UI</Button>
</Stack.Item>
</Stack>
} }
> >
Please select a ticket on the left to view its details. Please select a ticket on the left to view its details.

View File

@@ -112,7 +112,7 @@ const VoreSoulcatcherSection = (props: {
title={'Soulcatcher (' + name + ')'} title={'Soulcatcher (' + name + ')'}
fill fill
buttons={ buttons={
<Stack> <Stack align="center">
<Stack.Item> <Stack.Item>
<VorePanelEditSwitch <VorePanelEditSwitch
action="soulcatcher_toggle" action="soulcatcher_toggle"

View File

@@ -0,0 +1,12 @@
.TicketPanel__Label {
display: inline-block;
line-height: 1.667em;
padding: 0 var(--space-m);
outline: 0;
border-radius: var(--button-border-radius);
transition-property: background-color, color, opacity;
transition-duration: var(--button-transition);
transition-timing-function: var(--button-transition-timing);
user-select: none;
background-color: var(--button-background-default);
}

View File

@@ -52,6 +52,7 @@
@include meta.load-css('./interfaces/IDCard.scss'); @include meta.load-css('./interfaces/IDCard.scss');
@include meta.load-css('./interfaces/TinderMessaging.scss'); @include meta.load-css('./interfaces/TinderMessaging.scss');
@include meta.load-css('./interfaces/Turbolift.scss'); @include meta.load-css('./interfaces/Turbolift.scss');
@include meta.load-css('./interfaces/TicketPanel.scss');
@include meta.load-css('./interfaces/VorePanel.scss'); @include meta.load-css('./interfaces/VorePanel.scss');
@include meta.load-css('./interfaces/Wires.scss'); @include meta.load-css('./interfaces/Wires.scss');

View File

@@ -14,6 +14,6 @@
background-image: none; background-image: none;
} }
.TitleBar__title { .TitleBar__title {
left: 12px; margin-left: 12px;
} }
} }

View File

@@ -4561,7 +4561,6 @@
#include "code\modules\tgchat\message.dm" #include "code\modules\tgchat\message.dm"
#include "code\modules\tgchat\to_chat.dm" #include "code\modules\tgchat\to_chat.dm"
#include "code\modules\tgs\includes.dm" #include "code\modules\tgs\includes.dm"
#include "code\modules\tgs_commands\chompstation.dm"
#include "code\modules\tgs_commands\vorestation.dm" #include "code\modules\tgs_commands\vorestation.dm"
#include "code\modules\tgui\external.dm" #include "code\modules\tgui\external.dm"
#include "code\modules\tgui\modal.dm" #include "code\modules\tgui\modal.dm"