@@ -0,0 +1,699 @@
|
||||
/client/var/adminhelptimerid = 0 //a timer id for returning the ahelp verb
|
||||
/client/var/datum/admin_help/current_ticket //the current ticket the (usually) not-admin client is dealing with
|
||||
|
||||
//
|
||||
//TICKET MANAGER
|
||||
//
|
||||
|
||||
GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
|
||||
|
||||
/datum/admin_help_tickets
|
||||
var/list/active_tickets = list()
|
||||
var/list/closed_tickets = list()
|
||||
var/list/resolved_tickets = list()
|
||||
|
||||
var/obj/effect/statclick/ticket_list/astatclick = new(null, null, AHELP_ACTIVE)
|
||||
var/obj/effect/statclick/ticket_list/cstatclick = new(null, null, AHELP_CLOSED)
|
||||
var/obj/effect/statclick/ticket_list/rstatclick = new(null, null, AHELP_RESOLVED)
|
||||
|
||||
/datum/admin_help_tickets/Destroy()
|
||||
QDEL_LIST(active_tickets)
|
||||
QDEL_LIST(closed_tickets)
|
||||
QDEL_LIST(resolved_tickets)
|
||||
QDEL_NULL(astatclick)
|
||||
QDEL_NULL(cstatclick)
|
||||
QDEL_NULL(rstatclick)
|
||||
return ..()
|
||||
|
||||
/datum/admin_help_tickets/proc/TicketByID(id)
|
||||
var/list/lists = list(active_tickets, closed_tickets, resolved_tickets)
|
||||
for(var/I in lists)
|
||||
for(var/J in I)
|
||||
var/datum/admin_help/AH = J
|
||||
if(AH.id == id)
|
||||
return J
|
||||
|
||||
/datum/admin_help_tickets/proc/TicketsByCKey(ckey)
|
||||
. = list()
|
||||
var/list/lists = list(active_tickets, closed_tickets, resolved_tickets)
|
||||
for(var/I in lists)
|
||||
for(var/J in I)
|
||||
var/datum/admin_help/AH = J
|
||||
if(AH.initiator_ckey == ckey)
|
||||
. += AH
|
||||
|
||||
//private
|
||||
/datum/admin_help_tickets/proc/ListInsert(datum/admin_help/new_ticket)
|
||||
var/list/ticket_list
|
||||
switch(new_ticket.state)
|
||||
if(AHELP_ACTIVE)
|
||||
ticket_list = active_tickets
|
||||
if(AHELP_CLOSED)
|
||||
ticket_list = closed_tickets
|
||||
if(AHELP_RESOLVED)
|
||||
ticket_list = resolved_tickets
|
||||
else
|
||||
CRASH("Invalid ticket state: [new_ticket.state]")
|
||||
var/num_closed = ticket_list.len
|
||||
if(num_closed)
|
||||
for(var/I in 1 to num_closed)
|
||||
var/datum/admin_help/AH = ticket_list[I]
|
||||
if(AH.id > new_ticket.id)
|
||||
ticket_list.Insert(I, new_ticket)
|
||||
return
|
||||
ticket_list += new_ticket
|
||||
|
||||
//opens the ticket listings for one of the 3 states
|
||||
/datum/admin_help_tickets/proc/BrowseTickets(state)
|
||||
var/list/l2b
|
||||
var/title
|
||||
switch(state)
|
||||
if(AHELP_ACTIVE)
|
||||
l2b = active_tickets
|
||||
title = "Active Tickets"
|
||||
if(AHELP_CLOSED)
|
||||
l2b = closed_tickets
|
||||
title = "Closed Tickets"
|
||||
if(AHELP_RESOLVED)
|
||||
l2b = resolved_tickets
|
||||
title = "Resolved Tickets"
|
||||
if(!l2b)
|
||||
return
|
||||
var/list/dat = list("<html><head><title>[title]</title></head>")
|
||||
dat += "<A href='?_src_=holder;[HrefToken()];ahelp_tickets=[state]'>Refresh</A><br><br>"
|
||||
for(var/I in l2b)
|
||||
var/datum/admin_help/AH = I
|
||||
dat += "<span class='adminnotice'><span class='adminhelp'>Ticket #[AH.id]</span>: <A href='?_src_=holder;[HrefToken()];ahelp=[REF(AH)];ahelp_action=ticket'>[AH.initiator_key_name]: [AH.name]</A></span><br>"
|
||||
|
||||
usr << browse(dat.Join(), "window=ahelp_list[state];size=600x480")
|
||||
|
||||
//Tickets statpanel
|
||||
/datum/admin_help_tickets/proc/stat_entry()
|
||||
var/num_disconnected = 0
|
||||
stat("Active Tickets:", astatclick.update("[active_tickets.len]"))
|
||||
for(var/I in active_tickets)
|
||||
var/datum/admin_help/AH = I
|
||||
if(AH.initiator)
|
||||
stat("#[AH.id]. [AH.initiator_key_name]:", AH.statclick.update())
|
||||
else
|
||||
++num_disconnected
|
||||
if(num_disconnected)
|
||||
stat("Disconnected:", astatclick.update("[num_disconnected]"))
|
||||
stat("Closed Tickets:", cstatclick.update("[closed_tickets.len]"))
|
||||
stat("Resolved Tickets:", rstatclick.update("[resolved_tickets.len]"))
|
||||
|
||||
//Reassociate still open ticket if one exists
|
||||
/datum/admin_help_tickets/proc/ClientLogin(client/C)
|
||||
C.current_ticket = CKey2ActiveTicket(C.ckey)
|
||||
if(C.current_ticket)
|
||||
C.current_ticket.initiator = C
|
||||
C.current_ticket.AddInteraction("Client reconnected.")
|
||||
|
||||
//Dissasociate ticket
|
||||
/datum/admin_help_tickets/proc/ClientLogout(client/C)
|
||||
if(C.current_ticket)
|
||||
C.current_ticket.AddInteraction("Client disconnected.")
|
||||
C.current_ticket.initiator = null
|
||||
C.current_ticket = null
|
||||
|
||||
//Get a ticket given a ckey
|
||||
/datum/admin_help_tickets/proc/CKey2ActiveTicket(ckey)
|
||||
for(var/I in active_tickets)
|
||||
var/datum/admin_help/AH = I
|
||||
if(AH.initiator_ckey == ckey)
|
||||
return AH
|
||||
|
||||
//
|
||||
//TICKET LIST STATCLICK
|
||||
//
|
||||
|
||||
/obj/effect/statclick/ticket_list
|
||||
var/current_state
|
||||
|
||||
/obj/effect/statclick/ticket_list/New(loc, name, state)
|
||||
current_state = state
|
||||
..()
|
||||
|
||||
/obj/effect/statclick/ticket_list/Click()
|
||||
GLOB.ahelp_tickets.BrowseTickets(current_state)
|
||||
|
||||
//
|
||||
//TICKET DATUM
|
||||
//
|
||||
|
||||
/datum/admin_help
|
||||
var/id
|
||||
var/name
|
||||
var/state = AHELP_ACTIVE
|
||||
|
||||
var/opened_at
|
||||
var/closed_at
|
||||
|
||||
var/client/initiator //semi-misnomer, it's the person who ahelped/was bwoinked
|
||||
var/initiator_ckey
|
||||
var/initiator_key_name
|
||||
var/heard_by_no_admins = FALSE
|
||||
|
||||
var/list/_interactions //use AddInteraction() or, preferably, admin_ticket_log()
|
||||
|
||||
var/obj/effect/statclick/ahelp/statclick
|
||||
|
||||
var/static/ticket_counter = 0
|
||||
|
||||
//call this on its own to create a ticket, don't manually assign current_ticket
|
||||
//msg is the title of the ticket: usually the ahelp text
|
||||
//is_bwoink is TRUE if this ticket was started by an admin PM
|
||||
/datum/admin_help/New(msg, client/C, is_bwoink)
|
||||
//clean the input msg
|
||||
msg = sanitize(copytext(msg,1,MAX_MESSAGE_LEN))
|
||||
if(!msg || !C || !C.mob)
|
||||
qdel(src)
|
||||
return
|
||||
|
||||
id = ++ticket_counter
|
||||
opened_at = world.time
|
||||
|
||||
name = msg
|
||||
|
||||
initiator = C
|
||||
initiator_ckey = initiator.ckey
|
||||
initiator_key_name = key_name(initiator, FALSE, TRUE)
|
||||
if(initiator.current_ticket) //This is a bug
|
||||
stack_trace("Multiple ahelp current_tickets")
|
||||
initiator.current_ticket.AddInteraction("Ticket erroneously left open by code")
|
||||
initiator.current_ticket.Close()
|
||||
initiator.current_ticket = src
|
||||
|
||||
TimeoutVerb()
|
||||
|
||||
statclick = new(null, src)
|
||||
_interactions = list()
|
||||
|
||||
if(is_bwoink)
|
||||
AddInteraction("<font color='blue'>[key_name_admin(usr)] PM'd [LinkedReplyName()]</font>")
|
||||
message_admins("<font color='blue'>Ticket [TicketHref("#[id]")] created</font>")
|
||||
else
|
||||
MessageNoRecipient(msg)
|
||||
|
||||
//send it to irc if nobody is on and tell us how many were on
|
||||
var/admin_number_present = send2irc_adminless_only(initiator_ckey, "Ticket #[id]: [name]")
|
||||
log_admin_private("Ticket #[id]: [key_name(initiator)]: [name] - heard by [admin_number_present] non-AFK admins who have +BAN.")
|
||||
if(admin_number_present <= 0)
|
||||
to_chat(C, "<span class='notice'>No active admins are online, your adminhelp was sent to the admin irc.</span>")
|
||||
heard_by_no_admins = TRUE
|
||||
|
||||
GLOB.ahelp_tickets.active_tickets += src
|
||||
|
||||
/datum/admin_help/Destroy()
|
||||
RemoveActive()
|
||||
GLOB.ahelp_tickets.closed_tickets -= src
|
||||
GLOB.ahelp_tickets.resolved_tickets -= src
|
||||
return ..()
|
||||
|
||||
/datum/admin_help/proc/AddInteraction(formatted_message)
|
||||
if(heard_by_no_admins && usr && usr.ckey != initiator_ckey)
|
||||
heard_by_no_admins = FALSE
|
||||
send2irc(initiator_ckey, "Ticket #[id]: Answered by [key_name(usr)]")
|
||||
_interactions += "[TIME_STAMP("hh:mm:ss", FALSE)]: [formatted_message]"
|
||||
|
||||
//Removes the ahelp verb and returns it after 2 minutes
|
||||
/datum/admin_help/proc/TimeoutVerb()
|
||||
initiator.verbs -= /client/verb/adminhelp
|
||||
initiator.adminhelptimerid = addtimer(CALLBACK(initiator, /client/proc/giveadminhelpverb), 1200, TIMER_STOPPABLE) //2 minute cooldown of admin helps
|
||||
|
||||
//private
|
||||
/datum/admin_help/proc/FullMonty(ref_src)
|
||||
if(!ref_src)
|
||||
ref_src = "[REF(src)]"
|
||||
. = ADMIN_FULLMONTY_NONAME(initiator.mob)
|
||||
if(state == AHELP_ACTIVE)
|
||||
. += ClosureLinks(ref_src)
|
||||
|
||||
//private
|
||||
/datum/admin_help/proc/ClosureLinks(ref_src)
|
||||
if(!ref_src)
|
||||
ref_src = "[REF(src)]"
|
||||
. = " (<A HREF='?_src_=holder;[HrefToken(TRUE)];ahelp=[ref_src];ahelp_action=reject'>REJT</A>)"
|
||||
. += " (<A HREF='?_src_=holder;[HrefToken(TRUE)];ahelp=[ref_src];ahelp_action=icissue'>IC</A>)"
|
||||
. += " (<A HREF='?_src_=holder;[HrefToken(TRUE)];ahelp=[ref_src];ahelp_action=close'>CLOSE</A>)"
|
||||
. += " (<A HREF='?_src_=holder;[HrefToken(TRUE)];ahelp=[ref_src];ahelp_action=resolve'>RSLVE</A>)"
|
||||
. += " (<A HREF='?_src_=holder;[HrefToken(TRUE)];ahelp=[ref_src];ahelp_action=handleissue'>HANDLE</A>)"
|
||||
|
||||
//private
|
||||
/datum/admin_help/proc/LinkedReplyName(ref_src)
|
||||
if(!ref_src)
|
||||
ref_src = "[REF(src)]"
|
||||
return "<A HREF='?_src_=holder;[HrefToken(TRUE)];ahelp=[ref_src];ahelp_action=reply'>[initiator_key_name]</A>"
|
||||
|
||||
//private
|
||||
/datum/admin_help/proc/TicketHref(msg, ref_src, action = "ticket")
|
||||
if(!ref_src)
|
||||
ref_src = "[REF(src)]"
|
||||
return "<A HREF='?_src_=holder;[HrefToken(TRUE)];ahelp=[ref_src];ahelp_action=[action]'>[msg]</A>"
|
||||
|
||||
//message from the initiator without a target, all admins will see this
|
||||
//won't bug irc
|
||||
/datum/admin_help/proc/MessageNoRecipient(msg)
|
||||
var/ref_src = "[REF(src)]"
|
||||
//Message to be sent to all admins
|
||||
var/admin_msg = "<span class='adminnotice'><span class='adminhelp'>Ticket [TicketHref("#[id]", ref_src)]</span><b>: [LinkedReplyName(ref_src)] [FullMonty(ref_src)]:</b> <span class='linkify'>[keywords_lookup(msg)]</span></span>"
|
||||
|
||||
AddInteraction("<font color='red'>[LinkedReplyName(ref_src)]: [msg]</font>")
|
||||
|
||||
//send this msg to all admins
|
||||
for(var/client/X in GLOB.admins)
|
||||
if(X.prefs.toggles & SOUND_ADMINHELP)
|
||||
SEND_SOUND(X, sound('sound/effects/adminhelp.ogg'))
|
||||
window_flash(X, ignorepref = TRUE)
|
||||
to_chat(X, admin_msg)
|
||||
|
||||
//show it to the person adminhelping too
|
||||
to_chat(initiator, "<span class='adminnotice'>PM to-<b>Admins</b>: <span class='linkify'>[msg]</span></span>")
|
||||
|
||||
//Reopen a closed ticket
|
||||
/datum/admin_help/proc/Reopen()
|
||||
if(state == AHELP_ACTIVE)
|
||||
to_chat(usr, "<span class='warning'>This ticket is already open.</span>")
|
||||
return
|
||||
|
||||
if(GLOB.ahelp_tickets.CKey2ActiveTicket(initiator_ckey))
|
||||
to_chat(usr, "<span class='warning'>This user already has an active ticket, cannot reopen this one.</span>")
|
||||
return
|
||||
|
||||
statclick = new(null, src)
|
||||
GLOB.ahelp_tickets.active_tickets += src
|
||||
GLOB.ahelp_tickets.closed_tickets -= src
|
||||
GLOB.ahelp_tickets.resolved_tickets -= src
|
||||
switch(state)
|
||||
if(AHELP_CLOSED)
|
||||
SSblackbox.record_feedback("tally", "ahelp_stats", -1, "closed")
|
||||
if(AHELP_RESOLVED)
|
||||
SSblackbox.record_feedback("tally", "ahelp_stats", -1, "resolved")
|
||||
state = AHELP_ACTIVE
|
||||
closed_at = null
|
||||
if(initiator)
|
||||
initiator.current_ticket = src
|
||||
|
||||
AddInteraction("<font color='purple'>Reopened by [key_name_admin(usr)]</font>")
|
||||
var/msg = "<span class='adminhelp'>Ticket [TicketHref("#[id]")] reopened by [key_name_admin(usr)].</span>"
|
||||
message_admins(msg)
|
||||
log_admin_private(msg)
|
||||
SSblackbox.record_feedback("tally", "ahelp_stats", 1, "reopened")
|
||||
TicketPanel() //can only be done from here, so refresh it
|
||||
|
||||
//private
|
||||
/datum/admin_help/proc/RemoveActive()
|
||||
if(state != AHELP_ACTIVE)
|
||||
return
|
||||
closed_at = world.time
|
||||
QDEL_NULL(statclick)
|
||||
GLOB.ahelp_tickets.active_tickets -= src
|
||||
if(initiator && initiator.current_ticket == src)
|
||||
initiator.current_ticket = null
|
||||
|
||||
//Mark open ticket as closed/meme
|
||||
/datum/admin_help/proc/Close(key_name = key_name_admin(usr), silent = FALSE)
|
||||
if(state != AHELP_ACTIVE)
|
||||
return
|
||||
RemoveActive()
|
||||
state = AHELP_CLOSED
|
||||
GLOB.ahelp_tickets.ListInsert(src)
|
||||
to_chat(initiator, "<span class='adminhelp'>Ticket closed by [usr?.client?.holder?.fakekey? usr.client.holder.fakekey : "an administrator"].</span>")
|
||||
AddInteraction("<font color='red'>Closed by [key_name].</font>")
|
||||
if(!silent)
|
||||
SSblackbox.record_feedback("tally", "ahelp_stats", 1, "closed")
|
||||
var/msg = "Ticket [TicketHref("#[id]")] closed by [key_name]."
|
||||
message_admins(msg)
|
||||
log_admin_private(msg)
|
||||
|
||||
//Mark open ticket as resolved/legitimate, returns ahelp verb
|
||||
/datum/admin_help/proc/Resolve(key_name = key_name_admin(usr), silent = FALSE)
|
||||
if(state != AHELP_ACTIVE)
|
||||
return
|
||||
RemoveActive()
|
||||
state = AHELP_RESOLVED
|
||||
GLOB.ahelp_tickets.ListInsert(src)
|
||||
|
||||
addtimer(CALLBACK(initiator, /client/proc/giveadminhelpverb), 50)
|
||||
|
||||
AddInteraction("<font color='green'>Resolved by [key_name].</font>")
|
||||
to_chat(initiator, "<span class='adminhelp'>Your ticket has been resolved by [usr?.client?.holder?.fakekey? usr.client.holder.fakekey : "an administrator"]. The Adminhelp verb will be returned to you shortly.</span>")
|
||||
if(!silent)
|
||||
SSblackbox.record_feedback("tally", "ahelp_stats", 1, "resolved")
|
||||
var/msg = "Ticket [TicketHref("#[id]")] resolved by [key_name]"
|
||||
message_admins(msg)
|
||||
log_admin_private(msg)
|
||||
|
||||
//Close and return ahelp verb, use if ticket is incoherent
|
||||
/datum/admin_help/proc/Reject(key_name = key_name_admin(usr))
|
||||
if(state != AHELP_ACTIVE)
|
||||
return
|
||||
|
||||
if(initiator)
|
||||
initiator.giveadminhelpverb()
|
||||
|
||||
SEND_SOUND(initiator, sound('sound/effects/adminhelp.ogg'))
|
||||
|
||||
to_chat(initiator, "<font color='red' size='4'><b>- AdminHelp Rejected by [usr?.client?.holder?.fakekey? usr.client.holder.fakekey : "an administrator"]! -</b></font>")
|
||||
to_chat(initiator, "<font color='red'><b>Your admin help was rejected.</b> The adminhelp verb has been returned to you so that you may try again.</font>")
|
||||
to_chat(initiator, "Please try to be calm, clear, and descriptive in admin helps, do not assume the admin has seen any related events, and clearly state the names of anybody you are reporting.")
|
||||
|
||||
SSblackbox.record_feedback("tally", "ahelp_stats", 1, "rejected")
|
||||
var/msg = "Ticket [TicketHref("#[id]")] rejected by [key_name]"
|
||||
message_admins(msg)
|
||||
log_admin_private(msg)
|
||||
AddInteraction("Rejected by [key_name].")
|
||||
Close(silent = TRUE)
|
||||
|
||||
//Resolve ticket with IC Issue message
|
||||
/datum/admin_help/proc/ICIssue(key_name = key_name_admin(usr))
|
||||
if(state != AHELP_ACTIVE)
|
||||
return
|
||||
|
||||
var/msg = "<font color='red' size='4'><b>- AdminHelp marked as IC issue by [usr?.client?.holder?.fakekey? usr.client.holder.fakekey : "an administrator"]! -</b></font><br>"
|
||||
msg += "<font color='red'><b>Losing is part of the game!</b></font><br>"
|
||||
msg += "<font color='red'>It is also possible that your ahelp is unable to be answered properly, due to events occurring in the round.</font>"
|
||||
if(initiator)
|
||||
to_chat(initiator, msg)
|
||||
|
||||
SSblackbox.record_feedback("tally", "ahelp_stats", 1, "IC")
|
||||
msg = "Ticket [TicketHref("#[id]")] marked as IC by [key_name]"
|
||||
message_admins(msg)
|
||||
log_admin_private(msg)
|
||||
AddInteraction("Marked as IC issue by [key_name]")
|
||||
Resolve(silent = TRUE)
|
||||
|
||||
//Let the initiator know their ahelp is being handled
|
||||
/datum/admin_help/proc/HandleIssue(key_name = key_name_admin(usr))
|
||||
if(state != AHELP_ACTIVE)
|
||||
return
|
||||
|
||||
var/msg = "<span class ='adminhelp'>Your ticket is now being handled by an admin. Please be patient.</span>"
|
||||
|
||||
if(initiator)
|
||||
to_chat(initiator, msg)
|
||||
|
||||
SSblackbox.record_feedback("tally", "ahelp_stats", 1, "handling")
|
||||
msg = "Ticket [TicketHref("#[id]")] is being handled by [key_name]"
|
||||
message_admins(msg)
|
||||
log_admin_private(msg)
|
||||
AddInteraction("Being handled by [key_name]")
|
||||
|
||||
//Show the ticket panel
|
||||
/datum/admin_help/proc/TicketPanel()
|
||||
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 += "<b>State: "
|
||||
switch(state)
|
||||
if(AHELP_ACTIVE)
|
||||
dat += "<font color='red'>OPEN</font>"
|
||||
if(AHELP_RESOLVED)
|
||||
dat += "<font color='green'>RESOLVED</font>"
|
||||
if(AHELP_CLOSED)
|
||||
dat += "CLOSED"
|
||||
else
|
||||
dat += "UNKNOWN"
|
||||
dat += "</b>[FOURSPACES][TicketHref("Refresh", ref_src)][FOURSPACES][TicketHref("Re-Title", ref_src, "retitle")]"
|
||||
if(state != AHELP_ACTIVE)
|
||||
dat += "[FOURSPACES][TicketHref("Reopen", ref_src, "reopen")]"
|
||||
dat += "<br><br>Opened at: [GAMETIMESTAMP("hh:mm:ss", closed_at)] (Approx [DisplayTimeText(world.time - opened_at)] ago)"
|
||||
if(closed_at)
|
||||
dat += "<br>Closed at: [GAMETIMESTAMP("hh:mm:ss", closed_at)] (Approx [DisplayTimeText(world.time - closed_at)] ago)"
|
||||
dat += "<br><br>"
|
||||
if(initiator)
|
||||
dat += "<b>Actions:</b> [FullMonty(ref_src)]<br>"
|
||||
else
|
||||
dat += "<b>DISCONNECTED</b>[FOURSPACES][ClosureLinks(ref_src)]<br>"
|
||||
dat += "<br><b>Log:</b><br><br>"
|
||||
for(var/I in _interactions)
|
||||
dat += "[I]<br>"
|
||||
|
||||
usr << browse(dat.Join(), "window=ahelp[id];size=620x480")
|
||||
|
||||
/datum/admin_help/proc/Retitle()
|
||||
var/new_title = input(usr, "Enter a title for the ticket", "Rename Ticket", name) as text|null
|
||||
if(new_title)
|
||||
name = new_title
|
||||
//not saying the original name cause it could be a long ass message
|
||||
var/msg = "Ticket [TicketHref("#[id]")] titled [name] by [key_name_admin(usr)]"
|
||||
message_admins(msg)
|
||||
log_admin_private(msg)
|
||||
TicketPanel() //we have to be here to do this
|
||||
|
||||
//Forwarded action from admin/Topic
|
||||
/datum/admin_help/proc/Action(action)
|
||||
testing("Ahelp action: [action]")
|
||||
switch(action)
|
||||
if("ticket")
|
||||
TicketPanel()
|
||||
if("retitle")
|
||||
Retitle()
|
||||
if("reject")
|
||||
Reject()
|
||||
if("reply")
|
||||
usr.client.cmd_ahelp_reply(initiator)
|
||||
if("icissue")
|
||||
ICIssue()
|
||||
if("close")
|
||||
Close()
|
||||
if("resolve")
|
||||
Resolve()
|
||||
if("handleissue")
|
||||
HandleIssue()
|
||||
if("reopen")
|
||||
Reopen()
|
||||
|
||||
//
|
||||
// TICKET STATCLICK
|
||||
//
|
||||
|
||||
/obj/effect/statclick/ahelp
|
||||
var/datum/admin_help/ahelp_datum
|
||||
|
||||
/obj/effect/statclick/ahelp/Initialize(mapload, datum/admin_help/AH)
|
||||
ahelp_datum = AH
|
||||
. = ..()
|
||||
|
||||
/obj/effect/statclick/ahelp/update()
|
||||
return ..(ahelp_datum.name)
|
||||
|
||||
/obj/effect/statclick/ahelp/Click()
|
||||
ahelp_datum.TicketPanel()
|
||||
|
||||
/obj/effect/statclick/ahelp/Destroy()
|
||||
ahelp_datum = null
|
||||
return ..()
|
||||
|
||||
//
|
||||
// CLIENT PROCS
|
||||
//
|
||||
|
||||
/client/proc/giveadminhelpverb()
|
||||
src.verbs |= /client/verb/adminhelp
|
||||
deltimer(adminhelptimerid)
|
||||
adminhelptimerid = 0
|
||||
|
||||
// Used for methods where input via arg doesn't work
|
||||
/client/proc/get_adminhelp()
|
||||
var/msg = input(src, "Please describe your problem concisely and an admin will help as soon as they're able.", "Adminhelp contents") as text
|
||||
adminhelp(msg)
|
||||
|
||||
/client/verb/adminhelp(msg as text)
|
||||
set category = "Admin"
|
||||
set name = "Adminhelp"
|
||||
|
||||
if(GLOB.say_disabled) //This is here to try to identify lag problems
|
||||
to_chat(usr, "<span class='danger'>Speech is currently admin-disabled.</span>")
|
||||
return
|
||||
|
||||
//handle muting and automuting
|
||||
if(prefs.muted & MUTE_ADMINHELP)
|
||||
to_chat(src, "<span class='danger'>Error: Admin-PM: You cannot send adminhelps (Muted).</span>")
|
||||
return
|
||||
if(handle_spam_prevention(msg,MUTE_ADMINHELP))
|
||||
return
|
||||
|
||||
msg = trim(msg)
|
||||
|
||||
if(!msg)
|
||||
return
|
||||
|
||||
SSblackbox.record_feedback("tally", "admin_verb", 1, "Adminhelp") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
if(current_ticket)
|
||||
if(alert(usr, "You already have a ticket open. Is this for the same issue?",,"Yes","No") != "No")
|
||||
if(current_ticket)
|
||||
current_ticket.MessageNoRecipient(msg)
|
||||
current_ticket.TimeoutVerb()
|
||||
return
|
||||
else
|
||||
to_chat(usr, "<span class='warning'>Ticket not found, creating new one...</span>")
|
||||
else
|
||||
current_ticket.AddInteraction("[key_name_admin(usr)] opened a new ticket.")
|
||||
current_ticket.Close()
|
||||
|
||||
new /datum/admin_help(msg, src, FALSE)
|
||||
|
||||
//
|
||||
// LOGGING
|
||||
//
|
||||
|
||||
//Use this proc when an admin takes action that may be related to an open ticket on what
|
||||
//what can be a client, ckey, or mob
|
||||
/proc/admin_ticket_log(what, message)
|
||||
var/client/C
|
||||
var/mob/Mob = what
|
||||
if(istype(Mob))
|
||||
C = Mob.client
|
||||
else
|
||||
C = what
|
||||
if(istype(C) && C.current_ticket)
|
||||
C.current_ticket.AddInteraction(message)
|
||||
return C.current_ticket
|
||||
if(istext(what)) //ckey
|
||||
var/datum/admin_help/AH = GLOB.ahelp_tickets.CKey2ActiveTicket(what)
|
||||
if(AH)
|
||||
AH.AddInteraction(message)
|
||||
return AH
|
||||
|
||||
//
|
||||
// HELPER PROCS
|
||||
//
|
||||
|
||||
/proc/get_admin_counts(requiredflags = R_BAN)
|
||||
. = list("total" = list(), "noflags" = list(), "afk" = list(), "stealth" = list(), "present" = list())
|
||||
for(var/client/X in GLOB.admins)
|
||||
.["total"] += X
|
||||
if(requiredflags != 0 && !check_rights_for(X, requiredflags))
|
||||
.["noflags"] += X
|
||||
else if(X.is_afk())
|
||||
.["afk"] += X
|
||||
else if(X.holder.fakekey)
|
||||
.["stealth"] += X
|
||||
else
|
||||
.["present"] += X
|
||||
|
||||
/proc/send2irc_adminless_only(source, msg, requiredflags = R_BAN)
|
||||
var/list/adm = get_admin_counts(requiredflags)
|
||||
var/list/activemins = adm["present"]
|
||||
. = activemins.len
|
||||
if(. <= 0)
|
||||
var/final = ""
|
||||
var/list/afkmins = adm["afk"]
|
||||
var/list/stealthmins = adm["stealth"]
|
||||
var/list/powerlessmins = adm["noflags"]
|
||||
var/list/allmins = adm["total"]
|
||||
if(!afkmins.len && !stealthmins.len && !powerlessmins.len)
|
||||
final = "[msg] - No admins online"
|
||||
else
|
||||
final = "[msg] - All admins stealthed\[[english_list(stealthmins)]\], AFK\[[english_list(afkmins)]\], or lacks +BAN\[[english_list(powerlessmins)]\]! Total: [allmins.len] "
|
||||
send2irc(source,final)
|
||||
send2otherserver(source,final)
|
||||
|
||||
|
||||
/proc/send2irc(msg,msg2)
|
||||
msg = replacetext(replacetext(msg, "\proper", ""), "\improper", "")
|
||||
msg2 = replacetext(replacetext(msg2, "\proper", ""), "\improper", "")
|
||||
world.TgsTargetedChatBroadcast("[msg] | [msg2]", TRUE)
|
||||
|
||||
/proc/send2otherserver(source,msg,type = "Ahelp")
|
||||
var/comms_key = CONFIG_GET(string/comms_key)
|
||||
if(!comms_key)
|
||||
return
|
||||
var/list/message = list()
|
||||
message["message_sender"] = source
|
||||
message["message"] = msg
|
||||
message["source"] = "([CONFIG_GET(string/cross_comms_name)])"
|
||||
message["key"] = comms_key
|
||||
message += type
|
||||
|
||||
var/list/servers = CONFIG_GET(keyed_list/cross_server)
|
||||
for(var/I in servers)
|
||||
world.Export("[servers[I]]?[list2params(message)]")
|
||||
|
||||
|
||||
/proc/ircadminwho()
|
||||
var/list/message = list("Admins: ")
|
||||
var/list/admin_keys = list()
|
||||
for(var/adm in GLOB.admins)
|
||||
var/client/C = adm
|
||||
admin_keys += "[C][C.holder.fakekey ? "(Stealth)" : ""][C.is_afk() ? "(AFK)" : ""]"
|
||||
|
||||
for(var/admin in admin_keys)
|
||||
if(LAZYLEN(message) > 1)
|
||||
message += ", [admin]"
|
||||
else
|
||||
message += "[admin]"
|
||||
|
||||
return jointext(message, "")
|
||||
|
||||
/proc/keywords_lookup(msg,irc)
|
||||
|
||||
//This is a list of words which are ignored by the parser when comparing message contents for names. MUST BE IN LOWER CASE!
|
||||
var/list/adminhelp_ignored_words = list("unknown","the","a","an","of","monkey","alien","as", "i")
|
||||
|
||||
//explode the input msg into a list
|
||||
var/list/msglist = splittext(msg, " ")
|
||||
|
||||
//generate keywords lookup
|
||||
var/list/surnames = list()
|
||||
var/list/forenames = list()
|
||||
var/list/ckeys = list()
|
||||
var/founds = ""
|
||||
for(var/mob/M in GLOB.mob_list)
|
||||
var/list/indexing = list(M.real_name, M.name)
|
||||
if(M.mind)
|
||||
indexing += M.mind.name
|
||||
|
||||
for(var/string in indexing)
|
||||
var/list/L = splittext(string, " ")
|
||||
var/surname_found = 0
|
||||
//surnames
|
||||
for(var/i=L.len, i>=1, i--)
|
||||
var/word = ckey(L[i])
|
||||
if(word)
|
||||
surnames[word] = M
|
||||
surname_found = i
|
||||
break
|
||||
//forenames
|
||||
for(var/i=1, i<surname_found, i++)
|
||||
var/word = ckey(L[i])
|
||||
if(word)
|
||||
forenames[word] = M
|
||||
//ckeys
|
||||
ckeys[M.ckey] = M
|
||||
|
||||
var/ai_found = 0
|
||||
msg = ""
|
||||
var/list/mobs_found = list()
|
||||
for(var/original_word in msglist)
|
||||
var/word = ckey(original_word)
|
||||
if(word)
|
||||
if(!(word in adminhelp_ignored_words))
|
||||
if(word == "ai")
|
||||
ai_found = 1
|
||||
else
|
||||
var/mob/found = ckeys[word]
|
||||
if(!found)
|
||||
found = surnames[word]
|
||||
if(!found)
|
||||
found = forenames[word]
|
||||
if(found)
|
||||
if(!(found in mobs_found))
|
||||
mobs_found += found
|
||||
if(!ai_found && isAI(found))
|
||||
ai_found = 1
|
||||
var/is_antag = 0
|
||||
if(found.mind && found.mind.special_role)
|
||||
is_antag = 1
|
||||
founds += "Name: [found.name]([found.real_name]) Key: [found.key] Ckey: [found.ckey] [is_antag ? "(Antag)" : null] "
|
||||
msg += "[original_word]<font size='1' color='[is_antag ? "red" : "black"]'>(<A HREF='?_src_=holder;[HrefToken(TRUE)];adminmoreinfo=[REF(found)]'>?</A>|<A HREF='?_src_=holder;[HrefToken(TRUE)];adminplayerobservefollow=[REF(found)]'>F</A>)</font> "
|
||||
continue
|
||||
msg += "[original_word] "
|
||||
if(irc)
|
||||
if(founds == "")
|
||||
return "Search Failed"
|
||||
else
|
||||
return founds
|
||||
|
||||
return msg
|
||||
@@ -0,0 +1,97 @@
|
||||
/proc/show_air_status_to(turf/target, mob/user)
|
||||
var/datum/gas_mixture/env = target.return_air()
|
||||
var/list/env_gases = env.gases
|
||||
var/burning = FALSE
|
||||
if(isopenturf(target))
|
||||
var/turf/open/T = target
|
||||
if(T.active_hotspot)
|
||||
burning = TRUE
|
||||
|
||||
var/list/lines = list("<span class='adminnotice'>[AREACOORD(target)]: [env.temperature] K ([env.temperature - T0C] C), [env.return_pressure()] kPa[(burning)?(", <font color='red'>burning</font>"):(null)]</span>")
|
||||
for(var/id in env_gases)
|
||||
var/moles = env_gases[id]
|
||||
if (moles >= 0.00001)
|
||||
lines += "[GLOB.meta_gas_names[id]]: [moles] mol"
|
||||
to_chat(usr, lines.Join("\n"))
|
||||
|
||||
/client/proc/air_status(turf/target)
|
||||
set category = "Debug"
|
||||
set name = "Display Air Status"
|
||||
|
||||
if(!isturf(target))
|
||||
return
|
||||
show_air_status_to(target, usr)
|
||||
SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Air Status") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
/client/proc/fix_next_move()
|
||||
set category = "Debug"
|
||||
set name = "Unfreeze Everyone"
|
||||
var/largest_move_time = 0
|
||||
var/largest_click_time = 0
|
||||
var/mob/largest_move_mob = null
|
||||
var/mob/largest_click_mob = null
|
||||
for(var/mob/M in world)
|
||||
if(!M.client)
|
||||
continue
|
||||
if(M.next_move >= largest_move_time)
|
||||
largest_move_mob = M
|
||||
if(M.next_move > world.time)
|
||||
largest_move_time = M.next_move - world.time
|
||||
else
|
||||
largest_move_time = 1
|
||||
if(M.next_click >= largest_click_time)
|
||||
largest_click_mob = M
|
||||
if(M.next_click > world.time)
|
||||
largest_click_time = M.next_click - world.time
|
||||
else
|
||||
largest_click_time = 0
|
||||
log_admin("DEBUG: [key_name(M)] next_move = [M.next_move] lastDblClick = [M.next_click] world.time = [world.time]")
|
||||
M.next_move = 1
|
||||
M.next_click = 0
|
||||
message_admins("[ADMIN_LOOKUPFLW(largest_move_mob)] had the largest move delay with [largest_move_time] frames / [DisplayTimeText(largest_move_time)]!")
|
||||
message_admins("[ADMIN_LOOKUPFLW(largest_click_mob)] had the largest click delay with [largest_click_time] frames / [DisplayTimeText(largest_click_time)]!")
|
||||
message_admins("world.time = [world.time]")
|
||||
SSblackbox.record_feedback("tally", "admin_verb", 1, "Unfreeze Everyone") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
return
|
||||
|
||||
/client/proc/radio_report()
|
||||
set category = "Debug"
|
||||
set name = "Radio report"
|
||||
|
||||
var/output = "<b>Radio Report</b><hr>"
|
||||
for (var/fq in SSradio.frequencies)
|
||||
output += "<b>Freq: [fq]</b><br>"
|
||||
var/datum/radio_frequency/fqs = SSradio.frequencies[fq]
|
||||
if (!fqs)
|
||||
output += " <b>ERROR</b><br>"
|
||||
continue
|
||||
for (var/filter in fqs.devices)
|
||||
var/list/f = fqs.devices[filter]
|
||||
if (!f)
|
||||
output += " [filter]: ERROR<br>"
|
||||
continue
|
||||
output += " [filter]: [f.len]<br>"
|
||||
for (var/device in f)
|
||||
if (istype(device, /atom))
|
||||
var/atom/A = device
|
||||
output += " [device] ([AREACOORD(A)])<br>"
|
||||
else
|
||||
output += " [device]<br>"
|
||||
|
||||
usr << browse(output,"window=radioreport")
|
||||
SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Radio Report") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
/client/proc/reload_admins()
|
||||
set name = "Reload Admins"
|
||||
set category = "Admin"
|
||||
|
||||
if(!src.holder)
|
||||
return
|
||||
|
||||
var/confirm = alert(src, "Are you sure you want to reload all admins?", "Confirm", "Yes", "No")
|
||||
if(confirm !="Yes")
|
||||
return
|
||||
|
||||
load_admins()
|
||||
SSblackbox.record_feedback("tally", "admin_verb", 1, "Reload All Admins") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
message_admins("[key_name_admin(usr)] manually reloaded admins")
|
||||
@@ -0,0 +1,358 @@
|
||||
//- Are all the floors with or without air, as they should be? (regular or airless)
|
||||
//- Does the area have an APC?
|
||||
//- Does the area have an Air Alarm?
|
||||
//- Does the area have a Request Console?
|
||||
//- Does the area have lights?
|
||||
//- Does the area have a light switch?
|
||||
//- Does the area have enough intercoms?
|
||||
//- Does the area have enough security cameras? (Use the 'Camera Range Display' verb under Debug)
|
||||
//- Is the area connected to the scrubbers air loop?
|
||||
//- Is the area connected to the vent air loop? (vent pumps)
|
||||
//- Is everything wired properly?
|
||||
//- Does the area have a fire alarm and firedoors?
|
||||
//- Do all pod doors work properly?
|
||||
//- Are accesses set properly on doors, pod buttons, etc.
|
||||
//- Are all items placed properly? (not below vents, scrubbers, tables)
|
||||
//- Does the disposal system work properly from all the disposal units in this room and all the units, the pipes of which pass through this room?
|
||||
//- Check for any misplaced or stacked piece of pipe (air and disposal)
|
||||
//- Check for any misplaced or stacked piece of wire
|
||||
//- Identify how hard it is to break into the area and where the weak points are
|
||||
//- Check if the area has too much empty space. If so, make it smaller and replace the rest with maintenance tunnels.
|
||||
|
||||
GLOBAL_LIST_INIT(admin_verbs_debug_mapping, list(
|
||||
/client/proc/camera_view, //-errorage
|
||||
/client/proc/sec_camera_report, //-errorage
|
||||
/client/proc/intercom_view, //-errorage
|
||||
/client/proc/air_status, //Air things
|
||||
/client/proc/Cell, //More air things
|
||||
/client/proc/atmosscan, //check plumbing
|
||||
/client/proc/powerdebug, //check power
|
||||
/client/proc/count_objects_on_z_level,
|
||||
/client/proc/count_objects_all,
|
||||
/client/proc/cmd_assume_direct_control, //-errorage
|
||||
/client/proc/startSinglo,
|
||||
/client/proc/set_server_fps, //allows you to set the ticklag.
|
||||
/client/proc/cmd_admin_grantfullaccess,
|
||||
/client/proc/cmd_admin_areatest_all,
|
||||
/client/proc/cmd_admin_areatest_station,
|
||||
/client/proc/cmd_admin_test_atmos_controllers,
|
||||
/client/proc/cmd_admin_rejuvenate,
|
||||
/datum/admins/proc/show_traitor_panel,
|
||||
/client/proc/disable_communication,
|
||||
/client/proc/cmd_show_at_list,
|
||||
/client/proc/cmd_show_at_markers,
|
||||
/client/proc/manipulate_organs,
|
||||
/client/proc/start_line_profiling,
|
||||
/client/proc/stop_line_profiling,
|
||||
/client/proc/show_line_profiling,
|
||||
/client/proc/create_mapping_job_icons,
|
||||
/client/proc/debug_z_levels,
|
||||
/client/proc/place_ruin
|
||||
))
|
||||
GLOBAL_PROTECT(admin_verbs_debug_mapping)
|
||||
|
||||
/obj/effect/debugging/mapfix_marker
|
||||
name = "map fix marker"
|
||||
icon = 'icons/mob/screen_gen.dmi'
|
||||
icon_state = "mapfixmarker"
|
||||
desc = "I am a mappers mistake."
|
||||
|
||||
/obj/effect/debugging/marker
|
||||
icon = 'icons/turf/areas.dmi'
|
||||
icon_state = "yellow"
|
||||
|
||||
/obj/effect/debugging/marker/Move()
|
||||
return 0
|
||||
|
||||
/client/proc/camera_view()
|
||||
set category = "Mapping"
|
||||
set name = "Camera Range Display"
|
||||
|
||||
var/on = FALSE
|
||||
for(var/turf/T in world)
|
||||
if(T.maptext)
|
||||
on = TRUE
|
||||
T.maptext = null
|
||||
|
||||
if(!on)
|
||||
var/list/seen = list()
|
||||
for(var/obj/machinery/camera/C in GLOB.cameranet.cameras)
|
||||
for(var/turf/T in C.can_see())
|
||||
seen[T]++
|
||||
for(var/turf/T in seen)
|
||||
T.maptext = "[seen[T]]"
|
||||
SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Camera Range") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Camera Range")
|
||||
|
||||
|
||||
|
||||
/client/proc/sec_camera_report()
|
||||
set category = "Mapping"
|
||||
set name = "Camera Report"
|
||||
|
||||
if(!Master)
|
||||
alert(usr,"Master_controller not found.","Sec Camera Report")
|
||||
return 0
|
||||
|
||||
var/list/obj/machinery/camera/CL = list()
|
||||
|
||||
for(var/obj/machinery/camera/C in GLOB.cameranet.cameras)
|
||||
CL += C
|
||||
|
||||
var/output = {"<B>Camera Abnormalities Report</B><HR>
|
||||
<B>The following abnormalities have been detected. The ones in red need immediate attention: Some of those in black may be intentional.</B><BR><ul>"}
|
||||
|
||||
for(var/obj/machinery/camera/C1 in CL)
|
||||
for(var/obj/machinery/camera/C2 in CL)
|
||||
if(C1 != C2)
|
||||
if(C1.c_tag == C2.c_tag)
|
||||
output += "<li><font color='red'>c_tag match for cameras at [ADMIN_VERBOSEJMP(C1)] and [ADMIN_VERBOSEJMP(C2)] - c_tag is [C1.c_tag]</font></li>"
|
||||
if(C1.loc == C2.loc && C1.dir == C2.dir && C1.pixel_x == C2.pixel_x && C1.pixel_y == C2.pixel_y)
|
||||
output += "<li><font color='red'>FULLY overlapping cameras at [ADMIN_VERBOSEJMP(C1)] Networks: [json_encode(C1.network)] and [json_encode(C2.network)]</font></li>"
|
||||
if(C1.loc == C2.loc)
|
||||
output += "<li>Overlapping cameras at [ADMIN_VERBOSEJMP(C1)] Networks: [json_encode(C1.network)] and [json_encode(C2.network)]</li>"
|
||||
var/turf/T = get_step(C1,turn(C1.dir,180))
|
||||
if(!T || !isturf(T) || !T.density )
|
||||
if(!(locate(/obj/structure/grille) in T))
|
||||
var/window_check = 0
|
||||
for(var/obj/structure/window/W in T)
|
||||
if (W.dir == turn(C1.dir,180) || W.dir in list(5,6,9,10) )
|
||||
window_check = 1
|
||||
break
|
||||
if(!window_check)
|
||||
output += "<li><font color='red'>Camera not connected to wall at [ADMIN_VERBOSEJMP(C1)] Network: [json_encode(C1.network)]</font></li>"
|
||||
|
||||
output += "</ul>"
|
||||
usr << browse(output,"window=airreport;size=1000x500")
|
||||
SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Camera Report") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
/client/proc/intercom_view()
|
||||
set category = "Mapping"
|
||||
set name = "Intercom Range Display"
|
||||
|
||||
var/static/intercom_range_display_status = FALSE
|
||||
intercom_range_display_status = !intercom_range_display_status //blame cyberboss if this breaks something
|
||||
|
||||
for(var/obj/effect/debugging/marker/M in world)
|
||||
qdel(M)
|
||||
|
||||
if(intercom_range_display_status)
|
||||
for(var/obj/item/radio/intercom/I in world)
|
||||
for(var/turf/T in orange(7,I))
|
||||
var/obj/effect/debugging/marker/F = new/obj/effect/debugging/marker(T)
|
||||
if (!(F in view(7,I.loc)))
|
||||
qdel(F)
|
||||
SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Intercom Range") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
/client/proc/cmd_show_at_list()
|
||||
set category = "Mapping"
|
||||
set name = "Show roundstart AT list"
|
||||
set desc = "Displays a list of active turfs coordinates at roundstart"
|
||||
|
||||
var/dat = {"<b>Coordinate list of Active Turfs at Roundstart</b>
|
||||
<br>Real-time Active Turfs list you can see in Air Subsystem at active_turfs var<br>"}
|
||||
|
||||
for(var/t in GLOB.active_turfs_startlist)
|
||||
var/turf/T = t
|
||||
dat += "[ADMIN_VERBOSEJMP(T)]\n"
|
||||
dat += "<br>"
|
||||
|
||||
usr << browse(dat, "window=at_list")
|
||||
|
||||
SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Roundstart Active Turfs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
/client/proc/cmd_show_at_markers()
|
||||
set category = "Mapping"
|
||||
set name = "Show roundstart AT markers"
|
||||
set desc = "Places a marker on all active-at-roundstart turfs"
|
||||
|
||||
var/count = 0
|
||||
for(var/obj/effect/abstract/marker/at/AT in GLOB.all_abstract_markers)
|
||||
qdel(AT)
|
||||
count++
|
||||
|
||||
if(count)
|
||||
to_chat(usr, "[count] AT markers removed.")
|
||||
else
|
||||
for(var/t in GLOB.active_turfs_startlist)
|
||||
new /obj/effect/abstract/marker/at(t)
|
||||
count++
|
||||
to_chat(usr, "[count] AT markers placed.")
|
||||
|
||||
SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Roundstart Active Turf Markers")
|
||||
|
||||
/client/proc/enable_debug_verbs()
|
||||
set category = "Debug"
|
||||
set name = "Debug verbs - Enable"
|
||||
if(!check_rights(R_DEBUG))
|
||||
return
|
||||
verbs -= /client/proc/enable_debug_verbs
|
||||
verbs.Add(/client/proc/disable_debug_verbs, GLOB.admin_verbs_debug_mapping)
|
||||
SSblackbox.record_feedback("tally", "admin_verb", 1, "Enable Debug Verbs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
/client/proc/disable_debug_verbs()
|
||||
set category = "Debug"
|
||||
set name = "Debug verbs - Disable"
|
||||
verbs.Remove(/client/proc/disable_debug_verbs, GLOB.admin_verbs_debug_mapping)
|
||||
verbs += /client/proc/enable_debug_verbs
|
||||
SSblackbox.record_feedback("tally", "admin_verb", 1, "Disable Debug Verbs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
/client/proc/count_objects_on_z_level()
|
||||
set category = "Mapping"
|
||||
set name = "Count Objects On Level"
|
||||
var/level = input("Which z-level?","Level?") as text
|
||||
if(!level)
|
||||
return
|
||||
var/num_level = text2num(level)
|
||||
if(!num_level)
|
||||
return
|
||||
if(!isnum(num_level))
|
||||
return
|
||||
|
||||
var/type_text = input("Which type path?","Path?") as text
|
||||
if(!type_text)
|
||||
return
|
||||
var/type_path = text2path(type_text)
|
||||
if(!type_path)
|
||||
return
|
||||
|
||||
var/count = 0
|
||||
|
||||
var/list/atom/atom_list = list()
|
||||
|
||||
for(var/atom/A in world)
|
||||
if(istype(A,type_path))
|
||||
var/atom/B = A
|
||||
while(!(isturf(B.loc)))
|
||||
if(B && B.loc)
|
||||
B = B.loc
|
||||
else
|
||||
break
|
||||
if(B)
|
||||
if(B.z == num_level)
|
||||
count++
|
||||
atom_list += A
|
||||
|
||||
to_chat(world, "There are [count] objects of type [type_path] on z-level [num_level]")
|
||||
SSblackbox.record_feedback("tally", "admin_verb", 1, "Count Objects Zlevel") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
/client/proc/count_objects_all()
|
||||
set category = "Mapping"
|
||||
set name = "Count Objects All"
|
||||
|
||||
var/type_text = input("Which type path?","") as text
|
||||
if(!type_text)
|
||||
return
|
||||
var/type_path = text2path(type_text)
|
||||
if(!type_path)
|
||||
return
|
||||
|
||||
var/count = 0
|
||||
|
||||
for(var/atom/A in world)
|
||||
if(istype(A,type_path))
|
||||
count++
|
||||
|
||||
to_chat(world, "There are [count] objects of type [type_path] in the game world")
|
||||
SSblackbox.record_feedback("tally", "admin_verb", 1, "Count Objects All") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
|
||||
//This proc is intended to detect lag problems relating to communication procs
|
||||
GLOBAL_VAR_INIT(say_disabled, FALSE)
|
||||
/client/proc/disable_communication()
|
||||
set category = "Mapping"
|
||||
set name = "Disable all communication verbs"
|
||||
|
||||
GLOB.say_disabled = !GLOB.say_disabled
|
||||
if(GLOB.say_disabled)
|
||||
message_admins("[key] used 'Disable all communication verbs', killing all communication methods.")
|
||||
else
|
||||
message_admins("[key] used 'Disable all communication verbs', restoring all communication methods.")
|
||||
|
||||
//This generates the icon states for job starting location landmarks.
|
||||
/client/proc/create_mapping_job_icons()
|
||||
set name = "Generate job landmarks icons"
|
||||
set category = "Mapping"
|
||||
var/icon/final = icon()
|
||||
var/mob/living/carbon/human/dummy/D = new(locate(1,1,1)) //spawn on 1,1,1 so we don't have runtimes when items are deleted
|
||||
D.setDir(SOUTH)
|
||||
for(var/job in subtypesof(/datum/job))
|
||||
var/datum/job/JB = new job
|
||||
switch(JB.title)
|
||||
if("AI")
|
||||
final.Insert(icon('icons/mob/ai.dmi', "ai", SOUTH, 1), "AI")
|
||||
if("Cyborg")
|
||||
final.Insert(icon('icons/mob/robots.dmi', "robot", SOUTH, 1), "Cyborg")
|
||||
else
|
||||
for(var/obj/item/I in D)
|
||||
qdel(I)
|
||||
randomize_human(D)
|
||||
JB.equip(D, TRUE, FALSE)
|
||||
COMPILE_OVERLAYS(D)
|
||||
var/icon/I = icon(getFlatIcon(D), frame = 1)
|
||||
final.Insert(I, JB.title)
|
||||
qdel(D)
|
||||
//Also add the x
|
||||
for(var/x_number in 1 to 4)
|
||||
final.Insert(icon('icons/mob/screen_gen.dmi', "x[x_number == 1 ? "" : x_number]"), "x[x_number == 1 ? "" : x_number]")
|
||||
fcopy(final, "icons/mob/landmarks.dmi")
|
||||
|
||||
/client/proc/debug_z_levels()
|
||||
set name = "Debug Z-Levels"
|
||||
set category = "Mapping"
|
||||
|
||||
var/list/z_list = SSmapping.z_list
|
||||
var/list/messages = list()
|
||||
messages += "<b>World</b>: [world.maxx] x [world.maxy] x [world.maxz]<br>"
|
||||
|
||||
var/list/linked_levels = list()
|
||||
var/min_x = INFINITY
|
||||
var/min_y = INFINITY
|
||||
var/max_x = -INFINITY
|
||||
var/max_y = -INFINITY
|
||||
|
||||
for(var/z in 1 to max(world.maxz, z_list.len))
|
||||
if (z > z_list.len)
|
||||
messages += "<b>[z]</b>: Unmanaged (out of bounds)<br>"
|
||||
continue
|
||||
var/datum/space_level/S = z_list[z]
|
||||
if (!S)
|
||||
messages += "<b>[z]</b>: Unmanaged (null)<br>"
|
||||
continue
|
||||
var/linkage
|
||||
switch (S.linkage)
|
||||
if (UNAFFECTED)
|
||||
linkage = "no linkage"
|
||||
if (SELFLOOPING)
|
||||
linkage = "self-looping"
|
||||
if (CROSSLINKED)
|
||||
linkage = "linked at ([S.xi], [S.yi])"
|
||||
linked_levels += S
|
||||
min_x = min(min_x, S.xi)
|
||||
min_y = min(min_y, S.yi)
|
||||
max_x = max(max_x, S.xi)
|
||||
max_y = max(max_y, S.yi)
|
||||
else
|
||||
linkage = "unknown linkage '[S.linkage]'"
|
||||
|
||||
messages += "<b>[z]</b>: [S.name], [linkage], traits: [json_encode(S.traits)]<br>"
|
||||
if (S.z_value != z)
|
||||
messages += "-- z_value is [S.z_value], should be [z]<br>"
|
||||
if (S.name == initial(S.name))
|
||||
messages += "-- name not set<br>"
|
||||
if (z > world.maxz)
|
||||
messages += "-- exceeds max z"
|
||||
|
||||
var/grid[max_x - min_x + 1][max_y - min_y + 1]
|
||||
for(var/datum/space_level/S in linked_levels)
|
||||
grid[S.xi - min_x + 1][S.yi - min_y + 1] = S.z_value
|
||||
|
||||
messages += "<table border='1'>"
|
||||
for(var/y in max_y to min_y step -1)
|
||||
var/list/part = list()
|
||||
for(var/x in min_x to max_x)
|
||||
part += "[grid[x - min_x + 1][y - min_y + 1]]"
|
||||
messages += "<tr><td>[part.Join("</td><td>")]</td></tr>"
|
||||
messages += "</table>"
|
||||
|
||||
to_chat(src, messages.Join(""))
|
||||
@@ -0,0 +1,644 @@
|
||||
GLOBAL_LIST_INIT(VVlocked, list("vars", "datum_flags", "client", "virus", "viruses", "cuffed", "last_eaten", "unlock_content", "force_ending"))
|
||||
GLOBAL_PROTECT(VVlocked)
|
||||
GLOBAL_LIST_INIT(VVicon_edit_lock, list("icon", "icon_state", "overlays", "underlays", "resize"))
|
||||
GLOBAL_PROTECT(VVicon_edit_lock)
|
||||
GLOBAL_LIST_INIT(VVckey_edit, list("key", "ckey"))
|
||||
GLOBAL_PROTECT(VVckey_edit)
|
||||
GLOBAL_LIST_INIT(VVpixelmovement, list("step_x", "step_y", "bound_height", "bound_width", "bound_x", "bound_y"))
|
||||
GLOBAL_PROTECT(VVpixelmovement)
|
||||
|
||||
|
||||
/client/proc/vv_get_class(var/var_name, var/var_value)
|
||||
if(isnull(var_value))
|
||||
. = VV_NULL
|
||||
|
||||
else if (isnum(var_value))
|
||||
if (var_name in GLOB.bitfields)
|
||||
. = VV_BITFIELD
|
||||
else
|
||||
. = VV_NUM
|
||||
|
||||
else if (istext(var_value))
|
||||
if (findtext(var_value, "\n"))
|
||||
. = VV_MESSAGE
|
||||
else
|
||||
. = VV_TEXT
|
||||
|
||||
else if (isicon(var_value))
|
||||
. = VV_ICON
|
||||
|
||||
else if (ismob(var_value))
|
||||
. = VV_MOB_REFERENCE
|
||||
|
||||
else if (isloc(var_value))
|
||||
. = VV_ATOM_REFERENCE
|
||||
|
||||
else if (istype(var_value, /client))
|
||||
. = VV_CLIENT
|
||||
|
||||
else if (istype(var_value, /datum))
|
||||
. = VV_DATUM_REFERENCE
|
||||
|
||||
else if (ispath(var_value))
|
||||
if (ispath(var_value, /atom))
|
||||
. = VV_ATOM_TYPE
|
||||
else if (ispath(var_value, /datum))
|
||||
. = VV_DATUM_TYPE
|
||||
else
|
||||
. = VV_TYPE
|
||||
|
||||
else if (islist(var_value))
|
||||
. = VV_LIST
|
||||
|
||||
else if (isfile(var_value))
|
||||
. = VV_FILE
|
||||
else
|
||||
. = VV_NULL
|
||||
|
||||
/client/proc/vv_get_value(class, default_class, current_value, list/restricted_classes, list/extra_classes, list/classes, var_name)
|
||||
. = list("class" = class, "value" = null)
|
||||
if (!class)
|
||||
if (!classes)
|
||||
classes = list (
|
||||
VV_NUM,
|
||||
VV_TEXT,
|
||||
VV_MESSAGE,
|
||||
VV_ICON,
|
||||
VV_ATOM_REFERENCE,
|
||||
VV_DATUM_REFERENCE,
|
||||
VV_MOB_REFERENCE,
|
||||
VV_CLIENT,
|
||||
VV_ATOM_TYPE,
|
||||
VV_DATUM_TYPE,
|
||||
VV_TYPE,
|
||||
VV_FILE,
|
||||
VV_NEW_ATOM,
|
||||
VV_NEW_DATUM,
|
||||
VV_NEW_TYPE,
|
||||
VV_NEW_LIST,
|
||||
VV_NULL,
|
||||
VV_RESTORE_DEFAULT
|
||||
)
|
||||
|
||||
if(holder && holder.marked_datum && !(VV_MARKED_DATUM in restricted_classes))
|
||||
classes += "[VV_MARKED_DATUM] ([holder.marked_datum.type])"
|
||||
if (restricted_classes)
|
||||
classes -= restricted_classes
|
||||
|
||||
if (extra_classes)
|
||||
classes += extra_classes
|
||||
|
||||
.["class"] = input(src, "What kind of data?", "Variable Type", default_class) as null|anything in classes
|
||||
if (holder && holder.marked_datum && .["class"] == "[VV_MARKED_DATUM] ([holder.marked_datum.type])")
|
||||
.["class"] = VV_MARKED_DATUM
|
||||
|
||||
|
||||
switch(.["class"])
|
||||
if (VV_TEXT)
|
||||
.["value"] = input("Enter new text:", "Text", current_value) as null|text
|
||||
if (.["value"] == null)
|
||||
.["class"] = null
|
||||
return
|
||||
if (VV_MESSAGE)
|
||||
.["value"] = input("Enter new text:", "Text", current_value) as null|message
|
||||
if (.["value"] == null)
|
||||
.["class"] = null
|
||||
return
|
||||
|
||||
|
||||
if (VV_NUM)
|
||||
.["value"] = input("Enter new number:", "Num", current_value) as null|num
|
||||
if (.["value"] == null)
|
||||
.["class"] = null
|
||||
return
|
||||
|
||||
if (VV_BITFIELD)
|
||||
.["value"] = input_bitfield(usr, "Editing bitfield: [var_name]", var_name, current_value)
|
||||
if (.["value"] == null)
|
||||
.["class"] = null
|
||||
return
|
||||
|
||||
if (VV_ATOM_TYPE)
|
||||
.["value"] = pick_closest_path(FALSE)
|
||||
if (.["value"] == null)
|
||||
.["class"] = null
|
||||
return
|
||||
|
||||
if (VV_DATUM_TYPE)
|
||||
.["value"] = pick_closest_path(FALSE, get_fancy_list_of_datum_types())
|
||||
if (.["value"] == null)
|
||||
.["class"] = null
|
||||
return
|
||||
|
||||
if (VV_TYPE)
|
||||
var/type = current_value
|
||||
var/error = ""
|
||||
do
|
||||
type = input("Enter type:[error]", "Type", type) as null|text
|
||||
if (!type)
|
||||
break
|
||||
type = text2path(type)
|
||||
error = "\nType not found, Please try again"
|
||||
while(!type)
|
||||
if (!type)
|
||||
.["class"] = null
|
||||
return
|
||||
.["value"] = type
|
||||
|
||||
|
||||
if (VV_ATOM_REFERENCE)
|
||||
var/type = pick_closest_path(FALSE)
|
||||
var/subtypes = vv_subtype_prompt(type)
|
||||
if (subtypes == null)
|
||||
.["class"] = null
|
||||
return
|
||||
var/list/things = vv_reference_list(type, subtypes)
|
||||
var/value = input("Select reference:", "Reference", current_value) as null|anything in things
|
||||
if (!value)
|
||||
.["class"] = null
|
||||
return
|
||||
.["value"] = things[value]
|
||||
|
||||
if (VV_DATUM_REFERENCE)
|
||||
var/type = pick_closest_path(FALSE, get_fancy_list_of_datum_types())
|
||||
var/subtypes = vv_subtype_prompt(type)
|
||||
if (subtypes == null)
|
||||
.["class"] = null
|
||||
return
|
||||
var/list/things = vv_reference_list(type, subtypes)
|
||||
var/value = input("Select reference:", "Reference", current_value) as null|anything in things
|
||||
if (!value)
|
||||
.["class"] = null
|
||||
return
|
||||
.["value"] = things[value]
|
||||
|
||||
if (VV_MOB_REFERENCE)
|
||||
var/type = pick_closest_path(FALSE, make_types_fancy(typesof(/mob)))
|
||||
var/subtypes = vv_subtype_prompt(type)
|
||||
if (subtypes == null)
|
||||
.["class"] = null
|
||||
return
|
||||
var/list/things = vv_reference_list(type, subtypes)
|
||||
var/value = input("Select reference:", "Reference", current_value) as null|anything in things
|
||||
if (!value)
|
||||
.["class"] = null
|
||||
return
|
||||
.["value"] = things[value]
|
||||
|
||||
|
||||
|
||||
if (VV_CLIENT)
|
||||
.["value"] = input("Select reference:", "Reference", current_value) as null|anything in GLOB.clients
|
||||
if (.["value"] == null)
|
||||
.["class"] = null
|
||||
return
|
||||
|
||||
|
||||
if (VV_FILE)
|
||||
.["value"] = input("Pick file:", "File") as null|file
|
||||
if (.["value"] == null)
|
||||
.["class"] = null
|
||||
return
|
||||
|
||||
|
||||
if (VV_ICON)
|
||||
.["value"] = input("Pick icon:", "Icon") as null|icon
|
||||
if (.["value"] == null)
|
||||
.["class"] = null
|
||||
return
|
||||
|
||||
|
||||
if (VV_MARKED_DATUM)
|
||||
.["value"] = holder.marked_datum
|
||||
if (.["value"] == null)
|
||||
.["class"] = null
|
||||
return
|
||||
|
||||
|
||||
if (VV_NEW_ATOM)
|
||||
var/type = pick_closest_path(FALSE)
|
||||
if (!type)
|
||||
.["class"] = null
|
||||
return
|
||||
.["type"] = type
|
||||
var/atom/newguy = new type()
|
||||
newguy.datum_flags |= DF_VAR_EDITED
|
||||
.["value"] = newguy
|
||||
|
||||
if (VV_NEW_DATUM)
|
||||
var/type = pick_closest_path(FALSE, get_fancy_list_of_datum_types())
|
||||
if (!type)
|
||||
.["class"] = null
|
||||
return
|
||||
.["type"] = type
|
||||
var/datum/newguy = new type()
|
||||
newguy.datum_flags |= DF_VAR_EDITED
|
||||
.["value"] = newguy
|
||||
|
||||
if (VV_NEW_TYPE)
|
||||
var/type = current_value
|
||||
var/error = ""
|
||||
do
|
||||
type = input("Enter type:[error]", "Type", type) as null|text
|
||||
if (!type)
|
||||
break
|
||||
type = text2path(type)
|
||||
error = "\nType not found, Please try again"
|
||||
while(!type)
|
||||
if (!type)
|
||||
.["class"] = null
|
||||
return
|
||||
.["type"] = type
|
||||
var/datum/newguy = new type()
|
||||
if(istype(newguy))
|
||||
newguy.datum_flags |= DF_VAR_EDITED
|
||||
.["value"] = newguy
|
||||
|
||||
|
||||
if (VV_NEW_LIST)
|
||||
.["value"] = list()
|
||||
.["type"] = /list
|
||||
|
||||
/client/proc/vv_parse_text(O, new_var)
|
||||
if(O && findtext(new_var,"\["))
|
||||
var/process_vars = alert(usr,"\[] detected in string, process as variables?","Process Variables?","Yes","No")
|
||||
if(process_vars == "Yes")
|
||||
. = string2listofvars(new_var, O)
|
||||
|
||||
//do they want you to include subtypes?
|
||||
//FALSE = no subtypes, strict exact type pathing (or the type doesn't have subtypes)
|
||||
//TRUE = Yes subtypes
|
||||
//NULL = User cancelled at the prompt or invalid type given
|
||||
/client/proc/vv_subtype_prompt(var/type)
|
||||
if (!ispath(type))
|
||||
return
|
||||
var/list/subtypes = subtypesof(type)
|
||||
if (!subtypes || !subtypes.len)
|
||||
return FALSE
|
||||
if (subtypes && subtypes.len)
|
||||
switch(alert("Strict object type detection?", "Type detection", "Strictly this type","This type and subtypes", "Cancel"))
|
||||
if("Strictly this type")
|
||||
return FALSE
|
||||
if("This type and subtypes")
|
||||
return TRUE
|
||||
else
|
||||
return
|
||||
|
||||
/client/proc/vv_reference_list(type, subtypes)
|
||||
. = list()
|
||||
var/list/types = list(type)
|
||||
if (subtypes)
|
||||
types = typesof(type)
|
||||
|
||||
var/list/fancytypes = make_types_fancy(types)
|
||||
|
||||
for(var/fancytype in fancytypes) //swap the assoication
|
||||
types[fancytypes[fancytype]] = fancytype
|
||||
|
||||
var/things = get_all_of_type(type, subtypes)
|
||||
|
||||
var/i = 0
|
||||
for(var/thing in things)
|
||||
var/datum/D = thing
|
||||
i++
|
||||
//try one of 3 methods to shorten the type text:
|
||||
// fancy type,
|
||||
// fancy type with the base type removed from the begaining,
|
||||
// the type with the base type removed from the begaining
|
||||
var/fancytype = types[D.type]
|
||||
if (findtext(fancytype, types[type]))
|
||||
fancytype = copytext(fancytype, length(types[type])+1)
|
||||
var/shorttype = copytext("[D.type]", length("[type]")+1)
|
||||
if (length(shorttype) > length(fancytype))
|
||||
shorttype = fancytype
|
||||
if (!length(shorttype))
|
||||
shorttype = "/"
|
||||
|
||||
.["[D]([shorttype])[REF(D)]#[i]"] = D
|
||||
|
||||
/client/proc/mod_list_add_ass(atom/O) //hehe
|
||||
|
||||
var/list/L = vv_get_value(restricted_classes = list(VV_RESTORE_DEFAULT))
|
||||
var/class = L["class"]
|
||||
if (!class)
|
||||
return
|
||||
var/var_value = L["value"]
|
||||
|
||||
if(class == VV_TEXT || class == VV_MESSAGE)
|
||||
var/list/varsvars = vv_parse_text(O, var_value)
|
||||
for(var/V in varsvars)
|
||||
var_value = replacetext(var_value,"\[[V]]","[O.vars[V]]")
|
||||
|
||||
return var_value
|
||||
|
||||
|
||||
/client/proc/mod_list_add(list/L, atom/O, original_name, objectvar)
|
||||
var/list/LL = vv_get_value(restricted_classes = list(VV_RESTORE_DEFAULT))
|
||||
var/class = LL["class"]
|
||||
if (!class)
|
||||
return
|
||||
var/var_value = LL["value"]
|
||||
|
||||
if(class == VV_TEXT || class == VV_MESSAGE)
|
||||
var/list/varsvars = vv_parse_text(O, var_value)
|
||||
for(var/V in varsvars)
|
||||
var_value = replacetext(var_value,"\[[V]]","[O.vars[V]]")
|
||||
|
||||
if (O)
|
||||
L = L.Copy()
|
||||
|
||||
L += var_value
|
||||
|
||||
switch(alert("Would you like to associate a value with the list entry?",,"Yes","No"))
|
||||
if("Yes")
|
||||
L[var_value] = mod_list_add_ass(O) //hehe
|
||||
if (O)
|
||||
if (O.vv_edit_var(objectvar, L) == FALSE)
|
||||
to_chat(src, "Your edit was rejected by the object.")
|
||||
return
|
||||
log_world("### ListVarEdit by [src]: [(O ? O.type : "/list")] [objectvar]: ADDED=[var_value]")
|
||||
log_admin("[key_name(src)] modified [original_name]'s [objectvar]: ADDED=[var_value]")
|
||||
message_admins("[key_name_admin(src)] modified [original_name]'s [objectvar]: ADDED=[var_value]")
|
||||
|
||||
/client/proc/mod_list(list/L, atom/O, original_name, objectvar, index, autodetect_class = FALSE)
|
||||
if(!check_rights(R_VAREDIT))
|
||||
return
|
||||
if(!istype(L, /list))
|
||||
to_chat(src, "Not a List.")
|
||||
return
|
||||
|
||||
if(L.len > 1000)
|
||||
var/confirm = alert(src, "The list you're trying to edit is very long, continuing may crash the server.", "Warning", "Continue", "Abort")
|
||||
if(confirm != "Continue")
|
||||
return
|
||||
|
||||
|
||||
|
||||
var/list/names = list()
|
||||
for (var/i in 1 to L.len)
|
||||
var/key = L[i]
|
||||
var/value
|
||||
if (IS_NORMAL_LIST(L) && !isnum(key))
|
||||
value = L[key]
|
||||
if (value == null)
|
||||
value = "null"
|
||||
names["#[i] [key] = [value]"] = i
|
||||
if (!index)
|
||||
var/variable = input("Which var?","Var") as null|anything in names + "(ADD VAR)" + "(CLEAR NULLS)" + "(CLEAR DUPES)" + "(SHUFFLE)"
|
||||
|
||||
if(variable == null)
|
||||
return
|
||||
|
||||
if(variable == "(ADD VAR)")
|
||||
mod_list_add(L, O, original_name, objectvar)
|
||||
return
|
||||
|
||||
if(variable == "(CLEAR NULLS)")
|
||||
L = L.Copy()
|
||||
listclearnulls(L)
|
||||
if (!O.vv_edit_var(objectvar, L))
|
||||
to_chat(src, "Your edit was rejected by the object.")
|
||||
return
|
||||
log_world("### ListVarEdit by [src]: [O.type] [objectvar]: CLEAR NULLS")
|
||||
log_admin("[key_name(src)] modified [original_name]'s [objectvar]: CLEAR NULLS")
|
||||
message_admins("[key_name_admin(src)] modified [original_name]'s list [objectvar]: CLEAR NULLS")
|
||||
return
|
||||
|
||||
if(variable == "(CLEAR DUPES)")
|
||||
L = uniqueList(L)
|
||||
if (!O.vv_edit_var(objectvar, L))
|
||||
to_chat(src, "Your edit was rejected by the object.")
|
||||
return
|
||||
log_world("### ListVarEdit by [src]: [O.type] [objectvar]: CLEAR DUPES")
|
||||
log_admin("[key_name(src)] modified [original_name]'s [objectvar]: CLEAR DUPES")
|
||||
message_admins("[key_name_admin(src)] modified [original_name]'s list [objectvar]: CLEAR DUPES")
|
||||
return
|
||||
|
||||
if(variable == "(SHUFFLE)")
|
||||
L = shuffle(L)
|
||||
if (!O.vv_edit_var(objectvar, L))
|
||||
to_chat(src, "Your edit was rejected by the object.")
|
||||
return
|
||||
log_world("### ListVarEdit by [src]: [O.type] [objectvar]: SHUFFLE")
|
||||
log_admin("[key_name(src)] modified [original_name]'s [objectvar]: SHUFFLE")
|
||||
message_admins("[key_name_admin(src)] modified [original_name]'s list [objectvar]: SHUFFLE")
|
||||
return
|
||||
|
||||
index = names[variable]
|
||||
|
||||
|
||||
var/assoc_key
|
||||
if (index == null)
|
||||
return
|
||||
var/assoc = 0
|
||||
var/prompt = alert(src, "Do you want to edit the key or its assigned value?", "Associated List", "Key", "Assigned Value", "Cancel")
|
||||
if (prompt == "Cancel")
|
||||
return
|
||||
if (prompt == "Assigned Value")
|
||||
assoc = 1
|
||||
assoc_key = L[index]
|
||||
var/default
|
||||
var/variable
|
||||
if (assoc)
|
||||
variable = L[assoc_key]
|
||||
else
|
||||
variable = L[index]
|
||||
|
||||
default = vv_get_class(objectvar, variable)
|
||||
|
||||
to_chat(src, "Variable appears to be <b>[uppertext(default)]</b>.")
|
||||
|
||||
to_chat(src, "Variable contains: [variable]")
|
||||
|
||||
if(default == VV_NUM)
|
||||
var/dir_text = ""
|
||||
var/tdir = variable
|
||||
if(tdir > 0 && tdir < 16)
|
||||
if(tdir & 1)
|
||||
dir_text += "NORTH"
|
||||
if(tdir & 2)
|
||||
dir_text += "SOUTH"
|
||||
if(tdir & 4)
|
||||
dir_text += "EAST"
|
||||
if(tdir & 8)
|
||||
dir_text += "WEST"
|
||||
|
||||
if(dir_text)
|
||||
to_chat(usr, "If a direction, direction is: [dir_text]")
|
||||
|
||||
var/original_var = variable
|
||||
|
||||
if (O)
|
||||
L = L.Copy()
|
||||
var/class
|
||||
if(autodetect_class)
|
||||
if (default == VV_TEXT)
|
||||
default = VV_MESSAGE
|
||||
class = default
|
||||
var/list/LL = vv_get_value(default_class = default, current_value = original_var, restricted_classes = list(VV_RESTORE_DEFAULT), extra_classes = list(VV_LIST, "DELETE FROM LIST"))
|
||||
class = LL["class"]
|
||||
if (!class)
|
||||
return
|
||||
var/new_var = LL["value"]
|
||||
|
||||
if(class == VV_MESSAGE)
|
||||
class = VV_TEXT
|
||||
|
||||
switch(class) //Spits a runtime error if you try to modify an entry in the contents list. Dunno how to fix it, yet.
|
||||
if(VV_LIST)
|
||||
mod_list(variable, O, original_name, objectvar)
|
||||
|
||||
if("DELETE FROM LIST")
|
||||
L.Cut(index, index+1)
|
||||
if (O)
|
||||
if (O.vv_edit_var(objectvar, L))
|
||||
to_chat(src, "Your edit was rejected by the object.")
|
||||
return
|
||||
log_world("### ListVarEdit by [src]: [O.type] [objectvar]: REMOVED=[html_encode("[original_var]")]")
|
||||
log_admin("[key_name(src)] modified [original_name]'s [objectvar]: REMOVED=[original_var]")
|
||||
message_admins("[key_name_admin(src)] modified [original_name]'s [objectvar]: REMOVED=[original_var]")
|
||||
return
|
||||
|
||||
if(VV_TEXT)
|
||||
var/list/varsvars = vv_parse_text(O, new_var)
|
||||
for(var/V in varsvars)
|
||||
new_var = replacetext(new_var,"\[[V]]","[O.vars[V]]")
|
||||
|
||||
|
||||
if(assoc)
|
||||
L[assoc_key] = new_var
|
||||
else
|
||||
L[index] = new_var
|
||||
if (O)
|
||||
if (O.vv_edit_var(objectvar, L) == FALSE)
|
||||
to_chat(src, "Your edit was rejected by the object.")
|
||||
return
|
||||
log_world("### ListVarEdit by [src]: [(O ? O.type : "/list")] [objectvar]: [original_var]=[new_var]")
|
||||
log_admin("[key_name(src)] modified [original_name]'s [objectvar]: [original_var]=[new_var]")
|
||||
message_admins("[key_name_admin(src)] modified [original_name]'s varlist [objectvar]: [original_var]=[new_var]")
|
||||
|
||||
/proc/vv_varname_lockcheck(param_var_name)
|
||||
if(param_var_name in GLOB.VVlocked)
|
||||
if(!check_rights(R_DEBUG))
|
||||
return FALSE
|
||||
if(param_var_name in GLOB.VVckey_edit)
|
||||
if(!check_rights(R_SPAWN|R_DEBUG))
|
||||
return FALSE
|
||||
if(param_var_name in GLOB.VVicon_edit_lock)
|
||||
if(!check_rights(R_FUN|R_DEBUG))
|
||||
return FALSE
|
||||
if(param_var_name in GLOB.VVpixelmovement)
|
||||
if(!check_rights(R_DEBUG))
|
||||
return FALSE
|
||||
var/prompt = alert(usr, "Editing this var may irreparably break tile gliding for the rest of the round. THIS CAN'T BE UNDONE", "DANGER", "ABORT ", "Continue", " ABORT")
|
||||
if (prompt != "Continue")
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
|
||||
/client/proc/modify_variables(atom/O, param_var_name = null, autodetect_class = 0)
|
||||
if(!check_rights(R_VAREDIT))
|
||||
return
|
||||
|
||||
var/class
|
||||
var/variable
|
||||
var/var_value
|
||||
|
||||
if(param_var_name)
|
||||
if(!(param_var_name in O.vars))
|
||||
to_chat(src, "A variable with this name ([param_var_name]) doesn't exist in this datum ([O])")
|
||||
return
|
||||
variable = param_var_name
|
||||
|
||||
else
|
||||
var/list/names = list()
|
||||
for (var/V in O.vars)
|
||||
names += V
|
||||
|
||||
names = sortList(names)
|
||||
|
||||
variable = input("Which var?","Var") as null|anything in names
|
||||
if(!variable)
|
||||
return
|
||||
|
||||
if(!O.can_vv_get(variable))
|
||||
return
|
||||
|
||||
var_value = O.vars[variable]
|
||||
if(!vv_varname_lockcheck(variable))
|
||||
return
|
||||
if(istype(O, /datum/armor))
|
||||
var/prompt = alert(src, "Editing this var changes this value on potentially thousands of items that share the same combination of armor values. If you want to edit the armor of just one item, use the \"Modify armor values\" dropdown item", "DANGER", "ABORT ", "Continue", " ABORT")
|
||||
if (prompt != "Continue")
|
||||
return
|
||||
|
||||
|
||||
var/default = vv_get_class(variable, var_value)
|
||||
|
||||
if(isnull(default))
|
||||
to_chat(src, "Unable to determine variable type.")
|
||||
else
|
||||
to_chat(src, "Variable appears to be <b>[uppertext(default)]</b>.")
|
||||
|
||||
to_chat(src, "Variable contains: [var_value]")
|
||||
|
||||
if(default == VV_NUM)
|
||||
var/dir_text = ""
|
||||
if(var_value > 0 && var_value < 16)
|
||||
if(var_value & 1)
|
||||
dir_text += "NORTH"
|
||||
if(var_value & 2)
|
||||
dir_text += "SOUTH"
|
||||
if(var_value & 4)
|
||||
dir_text += "EAST"
|
||||
if(var_value & 8)
|
||||
dir_text += "WEST"
|
||||
|
||||
if(dir_text)
|
||||
to_chat(src, "If a direction, direction is: [dir_text]")
|
||||
|
||||
if(autodetect_class && default != VV_NULL)
|
||||
if (default == VV_TEXT)
|
||||
default = VV_MESSAGE
|
||||
class = default
|
||||
|
||||
var/list/value = vv_get_value(class, default, var_value, extra_classes = list(VV_LIST), var_name = variable)
|
||||
class = value["class"]
|
||||
|
||||
if (!class)
|
||||
return
|
||||
var/var_new = value["value"]
|
||||
|
||||
if(class == VV_MESSAGE)
|
||||
class = VV_TEXT
|
||||
|
||||
var/original_name = "[O]"
|
||||
|
||||
switch(class)
|
||||
if(VV_LIST)
|
||||
if(!islist(var_value))
|
||||
mod_list(list(), O, original_name, variable)
|
||||
|
||||
mod_list(var_value, O, original_name, variable)
|
||||
return
|
||||
|
||||
if(VV_RESTORE_DEFAULT)
|
||||
var_new = initial(O.vars[variable])
|
||||
|
||||
if(VV_TEXT)
|
||||
var/list/varsvars = vv_parse_text(O, var_new)
|
||||
for(var/V in varsvars)
|
||||
var_new = replacetext(var_new,"\[[V]]","[O.vars[V]]")
|
||||
|
||||
|
||||
if (O.vv_edit_var(variable, var_new) == FALSE)
|
||||
to_chat(src, "Your edit was rejected by the object.")
|
||||
return
|
||||
vv_update_display(O, "varedited", VV_MSG_EDITED)
|
||||
SEND_GLOBAL_SIGNAL(COMSIG_GLOB_VAR_EDIT, args)
|
||||
log_world("### VarEdit by [key_name(src)]: [O.type] [variable]=[var_value] => [var_new]")
|
||||
log_admin("[key_name(src)] modified [original_name]'s [variable] from [html_encode("[var_value]")] to [html_encode("[var_new]")]")
|
||||
var/msg = "[key_name_admin(src)] modified [original_name]'s [variable] from [var_value] to [var_new]"
|
||||
message_admins(msg)
|
||||
admin_ticket_log(O, msg)
|
||||
return TRUE
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,70 @@
|
||||
/datum/admins/proc/spawn_objasmob(object as text)
|
||||
set category = "Debug"
|
||||
set desc = "(obj path) Spawn object-mob"
|
||||
set name = "Spawn object-mob"
|
||||
|
||||
if(!check_rights(R_SPAWN))
|
||||
return
|
||||
|
||||
var/chosen = pick_closest_path(object, make_types_fancy(subtypesof(/obj)))
|
||||
|
||||
if (!chosen)
|
||||
return
|
||||
|
||||
var/mob/living/simple_animal/hostile/mimic/copy/basemob = /mob/living/simple_animal/hostile/mimic/copy
|
||||
|
||||
var/obj/chosen_obj = text2path(chosen)
|
||||
|
||||
var/list/settings = list(
|
||||
"mainsettings" = list(
|
||||
"name" = list("desc" = "Name", "type" = "string", "value" = "Bob"),
|
||||
"maxhealth" = list("desc" = "Max. health", "type" = "number", "value" = 100),
|
||||
"access" = list("desc" = "Access ID", "type" = "datum", "path" = "/obj/item/card/id", "value" = "Default"),
|
||||
"objtype" = list("desc" = "Base obj type", "type" = "datum", "path" = "/obj", "value" = "[chosen]"),
|
||||
"googlyeyes" = list("desc" = "Googly eyes", "type" = "boolean", "value" = "No"),
|
||||
"disableai" = list("desc" = "Disable AI", "type" = "boolean", "value" = "Yes"),
|
||||
"idledamage" = list("desc" = "Damaged while idle", "type" = "boolean", "value" = "No"),
|
||||
"dropitem" = list("desc" = "Drop obj on death", "type" = "boolean", "value" = "Yes"),
|
||||
"mobtype" = list("desc" = "Base mob type", "type" = "datum", "path" = "/mob/living/simple_animal/hostile/mimic/copy", "value" = "/mob/living/simple_animal/hostile/mimic/copy"),
|
||||
"ckey" = list("desc" = "ckey", "type" = "ckey", "value" = "none"),
|
||||
)
|
||||
)
|
||||
|
||||
var/list/prefreturn = presentpreflikepicker(usr,"Customize mob", "Customize mob", Button1="Ok", width = 450, StealFocus = 1,Timeout = 0, settings=settings)
|
||||
if (prefreturn["button"] == 1)
|
||||
settings = prefreturn["settings"]
|
||||
var/mainsettings = settings["mainsettings"]
|
||||
chosen_obj = text2path(mainsettings["objtype"]["value"])
|
||||
|
||||
basemob = text2path(mainsettings["mobtype"]["value"])
|
||||
if (!ispath(basemob, /mob/living/simple_animal/hostile/mimic/copy) || !ispath(chosen_obj, /obj))
|
||||
to_chat(usr, "Mob or object path invalid")
|
||||
|
||||
basemob = new basemob(get_turf(usr), new chosen_obj(get_turf(usr)), usr, mainsettings["dropitem"]["value"] == "Yes" ? FALSE : TRUE, (mainsettings["googlyeyes"]["value"] == "Yes" ? FALSE : TRUE))
|
||||
|
||||
if (mainsettings["disableai"]["value"] == "Yes")
|
||||
basemob.toggle_ai(AI_OFF)
|
||||
|
||||
if (mainsettings["idledamage"]["value"] == "No")
|
||||
basemob.idledamage = FALSE
|
||||
|
||||
if (mainsettings["access"])
|
||||
var/newaccess = text2path(mainsettings["access"]["value"])
|
||||
if (ispath(newaccess))
|
||||
basemob.access_card = new newaccess
|
||||
|
||||
if (mainsettings["maxhealth"]["value"])
|
||||
if (!isnum(mainsettings["maxhealth"]["value"]))
|
||||
mainsettings["maxhealth"]["value"] = text2num(mainsettings["maxhealth"]["value"])
|
||||
if (mainsettings["maxhealth"]["value"] > 0)
|
||||
basemob.maxHealth = basemob.maxHealth = mainsettings["maxhealth"]["value"]
|
||||
|
||||
if (mainsettings["name"]["value"])
|
||||
basemob.name = basemob.real_name = html_decode(mainsettings["name"]["value"])
|
||||
|
||||
if (mainsettings["ckey"]["value"] != "none")
|
||||
basemob.ckey = mainsettings["ckey"]["value"]
|
||||
|
||||
|
||||
log_admin("[key_name(usr)] spawned a sentient object-mob [basemob] from [chosen_obj] at [AREACOORD(usr)]")
|
||||
SSblackbox.record_feedback("tally", "admin_verb", 1, "Spawn object-mob") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
Reference in New Issue
Block a user