Files
Yogstation/code/modules/client/verbs/ooc.dm
Jordie a28de54405 Ban system and interface update (#41176)
Spiritual successor and extension to #17798, an almost entire rebuild of the SQL ban system backend and interface.
Bantypes are removed per #8584 and #6174. All bans are now 'role bans', server bans are when a ban's role is server. Admin bans are a column, meaning it's possible to ban admins from jobs.
Bans now have only an expiry datetime, duration is calculated from this when queried.
unbanned column is removed as it's superfluous, checking unban status is now done through checking unban_datetime. unban_round_id column added. Each ip and computerid columns rearranged so ip is always first, like in other tables. Bans now permit a null ckey, ip and computerid.

Ban checking is split into two procs now is_banned_from() does a check if a ckey is banned from one or more roles and returns true or false. This effectively replaces jobban_isbanned() used in simple if() statements. If connected a client's ban cache is checked rather than querying the DB. This makes it possible for a client connected to two or more servers to ignore any bans made on one server until their ban cache is rebuilt on the others. Could be avoided with cross-server calls to update ban caches or just the removal of the ban cache but as is I've done neither since I think it's enough of an edge case to not be worth it.
The second proc is is_banned_from_with_details(), this queries the DB for a role ban on a player's ckey, ip or CID and returns the details. This replaces direct queries in IsBanned.dm and the preferences menu.

The legacy ban system is removed.

The interfaces for banning, unbanning and editing bans have been remade to require less clicking and easier simultaneous operations. The banning and jobban panel are combined. They also store player connection details when opened so a client disconnecting no longer stops a ban being placed.

New banning panel:
Key, IP and CID can all be toggled to allow excluding them from a ban.
Checking Use IP and CID from last connection lets you enter only a ckey and have the DB fill these fields in for you, if possible.
Temporary bans have a drop-menu which lets you select between seconds, minutes, hours, days, weeks, months and years so you don't need to calculate how many minutes a long ban would be. The ban is still converted into minutes on the DB however.
Checking any of the head roles will check both of the boxes for you.
The red role box indicates there is already a ban on that role for this ckey. You can apply additional role bans to stack them.

New unbanning panel:
Unbanning panel is now separate from the banning panel but otherwise functionally the same.

Ban editing panel:
Actually just a modified banning panel, all the features from it work the same here.
You can now edit almost all parameters of a ban instead of just the reason.
You can't edit severity as it's not really part of the ban.
The panels have been tested but I've not been able to get my local server to be accessible so ban functionality isn't properly confirmed. Plenty of testing will be required as I'd rather not break bans.

cl
admin: Ban interface rework. The banning and unbanning panels have received a new design which is easier to use and allows multiple role bans to be made at once.
prefix: Ban search and unbanning moved to unbanning panel, which is now a separate panel to the old banning panel.
/cl
2018-12-04 20:48:17 +01:00

360 lines
15 KiB
Plaintext

/client/verb/ooc(msg as text)
set name = "OOC" //Gave this shit a shorter name so you only have to time out "ooc" rather than "ooc message" to use it --NeoFite
set category = "OOC"
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
if(!mob)
return
if(!holder)
if(!GLOB.ooc_allowed)
to_chat(src, "<span class='danger'>OOC is globally muted.</span>")
return
if(!GLOB.dooc_allowed && (mob.stat == DEAD))
to_chat(usr, "<span class='danger'>OOC for dead mobs has been turned off.</span>")
return
if(prefs.muted & MUTE_OOC)
to_chat(src, "<span class='danger'>You cannot use OOC (muted).</span>")
return
if(is_banned_from(ckey, "OOC"))
to_chat(src, "<span class='danger'>You have been banned from OOC.</span>")
return
if(QDELETED(src))
return
msg = copytext(sanitize(msg), 1, MAX_MESSAGE_LEN)
var/raw_msg = msg
if(!msg)
return
msg = pretty_filter(msg) //yogs
msg = emoji_parse(msg)
if((copytext(msg, 1, 2) in list(".",";",":","#")) || (findtext(lowertext(copytext(msg, 1, 5)), "say")))
if(alert("Your message \"[raw_msg]\" looks like it was meant for in game communication, say it in OOC?", "Meant for OOC?", "No", "Yes") != "Yes")
return
if(!holder)
if(handle_spam_prevention(msg,MUTE_OOC))
return
if(findtext(msg, "byond://"))
to_chat(src, "<B>Advertising other servers is not allowed.</B>")
log_admin("[key_name(src)] has attempted to advertise in OOC: [msg]")
message_admins("[key_name_admin(src)] has attempted to advertise in OOC: [msg]")
return
if(!(prefs.chat_toggles & CHAT_OOC))
to_chat(src, "<span class='danger'>You have OOC muted.</span>")
return
mob.log_talk(raw_msg, LOG_OOC)
if(holder && holder.fakekey) //YOGS start - webhook support
webhook_send_ooc(holder.fakekey, msg)
else
webhook_send_ooc(key, msg) //YOGS end - webhook support
var/keyname = key
if(prefs.unlock_content)
if(prefs.toggles & MEMBER_PUBLIC)
keyname = "<font color='[prefs.ooccolor ? prefs.ooccolor : GLOB.normal_ooc_colour]'>[icon2html('icons/member_content.dmi', world, "blag")][keyname]</font>"
//The linkify span classes and linkify=TRUE below make ooc text get clickable chat href links if you pass in something resembling a url
for(var/client/C in GLOB.clients)
if(C.prefs.chat_toggles & CHAT_OOC)
if(holder)
if(!holder.fakekey || C.holder)
if(check_rights_for(src, R_ADMIN))
to_chat(C, "<span class='adminooc'>[CONFIG_GET(flag/allow_admin_ooccolor) && prefs.ooccolor ? "<font color=[prefs.ooccolor]>" :"" ]<span class='prefix'>[find_admin_rank(src)] OOC:</span> <EM>[keyname][holder.fakekey ? "/([holder.fakekey])" : ""]:</EM> <span class='message linkify'>[msg]</span></span></font>") //Yogs - Added the [find_admin_rank(src)] below, to get the rank of the admin. check yogstation\code\module\client\verbs\ooc for the proc
else
to_chat(C, "<span class='adminobserverooc'><span class='prefix'>OOC:</span> <EM>[keyname][holder.fakekey ? "/([holder.fakekey])" : ""]:</EM> <span class='message linkify'>[msg]</span></span>")
else
to_chat(C, "<font color='[GLOB.normal_ooc_colour]'><span class='ooc'><span class='prefix'>OOC:</span> <EM>[holder.fakekey ? holder.fakekey : key]:</EM> <span class='message linkify'>[msg]</span></span></font>")
else if(is_mentor()) // YOGS START - Mentor and Donor colors
to_chat(C, "<font color='[GLOB.mentor_ooc_colour]'><span class='ooc'><span class='prefix'>OOC:</span> <EM>[keyname]:</EM> <span class='message linkify'>[msg]</span></span>")
else if(!(key in C.prefs.ignoring))
if(is_donator(src))
to_chat(C, "<font color='[GLOB.normal_ooc_colour]'><span class='ooc'><span class='prefix'>\[Donator\] OOC:</span> <EM>[keyname]:</EM> <span class='message linkify'>[msg]</span></span></font>")
else
to_chat(C, "<font color='[GLOB.normal_ooc_colour]'><span class='ooc'><span class='prefix'>OOC:</span> <EM>[keyname]:</EM> <span class='message linkify'>[msg]</span></span></font>")
// YOGS END
/proc/toggle_ooc(toggle = null)
if(toggle != null) //if we're specifically en/disabling ooc
if(toggle != GLOB.ooc_allowed)
GLOB.ooc_allowed = toggle
else
return
else //otherwise just toggle it
GLOB.ooc_allowed = !GLOB.ooc_allowed
to_chat(world, "<B>The OOC channel has been globally [GLOB.ooc_allowed ? "enabled" : "disabled"].</B>")
/proc/toggle_dooc(toggle = null)
if(toggle != null)
if(toggle != GLOB.dooc_allowed)
GLOB.dooc_allowed = toggle
else
return
else
GLOB.dooc_allowed = !GLOB.dooc_allowed
GLOBAL_VAR_INIT(normal_ooc_colour, OOC_COLOR)
GLOBAL_VAR_INIT(mentor_ooc_colour, YOGS_MENTOR_OOC_COLOUR) // yogs - mentor ooc color
/client/proc/set_ooc(newColor as color)
set name = "Set Player OOC Color"
set desc = "Modifies player OOC Color"
set category = "Fun"
GLOB.normal_ooc_colour = sanitize_ooccolor(newColor)
/client/proc/reset_ooc()
set name = "Reset Player OOC Color"
set desc = "Returns player OOC Color to default"
set category = "Fun"
GLOB.normal_ooc_colour = OOC_COLOR
/client/verb/colorooc()
set name = "Set Your OOC Color"
set category = "Preferences"
if(!holder || !check_rights_for(src, R_ADMIN))
if(!is_content_unlocked())
return
var/new_ooccolor = input(src, "Please select your OOC color.", "OOC color", prefs.ooccolor) as color|null
if(new_ooccolor)
prefs.ooccolor = sanitize_ooccolor(new_ooccolor)
prefs.save_preferences()
SSblackbox.record_feedback("tally", "admin_verb", 1, "Set OOC Color") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
return
/client/verb/resetcolorooc()
set name = "Reset Your OOC Color"
set desc = "Returns your OOC Color to default"
set category = "Preferences"
if(!holder || !check_rights_for(src, R_ADMIN))
if(!is_content_unlocked())
return
prefs.ooccolor = initial(prefs.ooccolor)
prefs.save_preferences()
//Checks admin notice
/client/verb/admin_notice()
set name = "Adminnotice"
set category = "Admin"
set desc ="Check the admin notice if it has been set"
if(GLOB.admin_notice)
to_chat(src, "<span class='boldnotice'>Admin Notice:</span>\n \t [GLOB.admin_notice]")
else
to_chat(src, "<span class='notice'>There are no admin notices at the moment.</span>")
/client/verb/fix_chat()
set name = "Fix chat"
set category = "OOC"
if (!chatOutput || !istype(chatOutput))
var/action = alert(src, "Invalid Chat Output data found!\nRecreate data?", "Wot?", "Recreate Chat Output data", "Cancel")
if (action != "Recreate Chat Output data")
return
chatOutput = new /datum/chatOutput(src)
chatOutput.start()
action = alert(src, "Goon chat reloading, wait a bit and tell me if it's fixed", "", "Fixed", "Nope")
if (action == "Fixed")
log_game("GOONCHAT: [key_name(src)] Had to fix their goonchat by re-creating the chatOutput datum")
else
chatOutput.load()
action = alert(src, "How about now? (give it a moment (it may also try to load twice))", "", "Yes", "No")
if (action == "Yes")
log_game("GOONCHAT: [key_name(src)] Had to fix their goonchat by re-creating the chatOutput datum and forcing a load()")
else
action = alert(src, "Welp, I'm all out of ideas. Try closing byond and reconnecting.\nWe could also disable fancy chat and re-enable oldchat", "", "Thanks anyways", "Switch to old chat")
if (action == "Switch to old chat")
winset(src, "output", "is-visible=true;is-disabled=false")
winset(src, "browseroutput", "is-visible=false")
log_game("GOONCHAT: [key_name(src)] Failed to fix their goonchat window after recreating the chatOutput and forcing a load()")
else if (chatOutput.loaded)
var/action = alert(src, "ChatOutput seems to be loaded\nDo you want me to force a reload, wiping the chat log or just refresh the chat window because it broke/went away?", "Hmmm", "Force Reload", "Refresh", "Cancel")
switch (action)
if ("Force Reload")
chatOutput.loaded = FALSE
chatOutput.start() //this is likely to fail since it asks , but we should try it anyways so we know.
action = alert(src, "Goon chat reloading, wait a bit and tell me if it's fixed", "", "Fixed", "Nope")
if (action == "Fixed")
log_game("GOONCHAT: [key_name(src)] Had to fix their goonchat by forcing a start()")
else
chatOutput.load()
action = alert(src, "How about now? (give it a moment (it may also try to load twice))", "", "Yes", "No")
if (action == "Yes")
log_game("GOONCHAT: [key_name(src)] Had to fix their goonchat by forcing a load()")
else
action = alert(src, "Welp, I'm all out of ideas. Try closing byond and reconnecting.\nWe could also disable fancy chat and re-enable oldchat", "", "Thanks anyways", "Switch to old chat")
if (action == "Switch to old chat")
winset(src, "output", "is-visible=true;is-disabled=false")
winset(src, "browseroutput", "is-visible=false")
log_game("GOONCHAT: [key_name(src)] Failed to fix their goonchat window forcing a start() and forcing a load()")
if ("Refresh")
chatOutput.showChat()
action = alert(src, "Goon chat refreshing, wait a bit and tell me if it's fixed", "", "Fixed", "Nope, force a reload")
if (action == "Fixed")
log_game("GOONCHAT: [key_name(src)] Had to fix their goonchat by forcing a show()")
else
chatOutput.loaded = FALSE
chatOutput.load()
action = alert(src, "How about now? (give it a moment)", "", "Yes", "No")
if (action == "Yes")
log_game("GOONCHAT: [key_name(src)] Had to fix their goonchat by forcing a load()")
else
action = alert(src, "Welp, I'm all out of ideas. Try closing byond and reconnecting.\nWe could also disable fancy chat and re-enable oldchat", "", "Thanks anyways", "Switch to old chat")
if (action == "Switch to old chat")
winset(src, "output", "is-visible=true;is-disabled=false")
winset(src, "browseroutput", "is-visible=false")
log_game("GOONCHAT: [key_name(src)] Failed to fix their goonchat window forcing a show() and forcing a load()")
return
else
chatOutput.start()
var/action = alert(src, "Manually loading Chat, wait a bit and tell me if it's fixed", "", "Fixed", "Nope")
if (action == "Fixed")
log_game("GOONCHAT: [key_name(src)] Had to fix their goonchat by manually calling start()")
else
chatOutput.load()
alert(src, "How about now? (give it a moment (it may also try to load twice))", "", "Yes", "No")
if (action == "Yes")
log_game("GOONCHAT: [key_name(src)] Had to fix their goonchat by manually calling start() and forcing a load()")
else
action = alert(src, "Welp, I'm all out of ideas. Try closing byond and reconnecting.\nWe could also disable fancy chat and re-enable oldchat", "", "Thanks anyways", "Switch to old chat")
if (action == "Switch to old chat")
winset(src, "output", list2params(list("on-show" = "", "is-disabled" = "false", "is-visible" = "true")))
winset(src, "browseroutput", "is-disabled=true;is-visible=false")
log_game("GOONCHAT: [key_name(src)] Failed to fix their goonchat window after manually calling start() and forcing a load()")
/client/verb/motd()
set name = "MOTD"
set category = "OOC"
set desc ="Check the Message of the Day"
var/motd = global.config.motd
if(motd)
to_chat(src, "<div class=\"motd\">[motd]</div>", handle_whitespace=FALSE)
else
to_chat(src, "<span class='notice'>The Message of the Day has not been set.</span>")
/client/proc/self_notes()
set name = "View Admin Remarks"
set category = "OOC"
set desc = "View the notes that admins have written about you"
if(!CONFIG_GET(flag/see_own_notes))
to_chat(usr, "<span class='notice'>Sorry, that function is not enabled on this server.</span>")
return
browse_messages(null, usr.ckey, null, TRUE)
/client/proc/self_playtime()
set name = "View tracked playtime"
set category = "OOC"
set desc = "View the amount of playtime for roles the server has tracked."
if(!CONFIG_GET(flag/use_exp_tracking))
to_chat(usr, "<span class='notice'>Sorry, tracking is currently disabled.</span>")
return
var/list/body = list()
body += "<html><head><title>Playtime for [key]</title></head><BODY><BR>Playtime:"
body += get_exp_report()
body += "</BODY></HTML>"
usr << browse(body.Join(), "window=playerplaytime[ckey];size=550x615")
/client/proc/ignore_key(client)
var/client/C = client
if(C.key in prefs.ignoring)
prefs.ignoring -= C.key
else
prefs.ignoring |= C.key
to_chat(src, "You are [(C.key in prefs.ignoring) ? "now" : "no longer"] ignoring [C.key] on the OOC channel.")
prefs.save_preferences()
/client/verb/select_ignore()
set name = "Ignore"
set category = "OOC"
set desc ="Ignore a player's messages on the OOC channel"
var/see_ghost_names = isobserver(mob)
var/list/choices = list()
for(var/client/C in GLOB.clients)
if(isobserver(C.mob) && see_ghost_names)
choices["[C.mob]([C])"] = C
else
choices[C] = C
choices = sortList(choices)
var/selection = input("Please, select a player!", "Ignore", null, null) as null|anything in choices
if(!selection || !(selection in choices))
return
selection = choices[selection]
if(selection == src)
to_chat(src, "You can't ignore yourself.")
return
ignore_key(selection)
/client/proc/show_previous_roundend_report()
set name = "Your Last Round"
set category = "OOC"
set desc = "View the last round end report you've seen"
SSticker.show_roundend_report(src, TRUE)
/client/verb/fit_viewport()
set name = "Fit Viewport"
set category = "OOC"
set desc = "Fit the width of the map window to match the viewport"
// Fetch aspect ratio
var/view_size = getviewsize(view)
var/aspect_ratio = view_size[1] / view_size[2]
// Calculate desired pixel width using window size and aspect ratio
var/sizes = params2list(winget(src, "mainwindow.split;mapwindow", "size"))
var/map_size = splittext(sizes["mapwindow.size"], "x")
var/height = text2num(map_size[2])
var/desired_width = round(height * aspect_ratio)
if (text2num(map_size[1]) == desired_width)
// Nothing to do
return
var/split_size = splittext(sizes["mainwindow.split.size"], "x")
var/split_width = text2num(split_size[1])
// Calculate and apply a best estimate
// +4 pixels are for the width of the splitter's handle
var/pct = 100 * (desired_width + 4) / split_width
winset(src, "mainwindow.split", "splitter=[pct]")
// Apply an ever-lowering offset until we finish or fail
var/delta
for(var/safety in 1 to 10)
var/after_size = winget(src, "mapwindow", "size")
map_size = splittext(after_size, "x")
var/got_width = text2num(map_size[1])
if (got_width == desired_width)
// success
return
else if (isnull(delta))
// calculate a probable delta value based on the difference
delta = 100 * (desired_width - got_width) / split_width
else if ((delta > 0 && got_width > desired_width) || (delta < 0 && got_width < desired_width))
// if we overshot, halve the delta and reverse direction
delta = -delta/2
pct += delta
winset(src, "mainwindow.split", "splitter=[pct]")