Merge branch 'master' into Medical-Turtleneck
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
/client/proc/citaPPoptions(mob/M) // why is this client and not /datum/admins? noone knows, in PP src == client, instead of holder. wtf.
|
||||
var/body = "<br>"
|
||||
if(M.client)
|
||||
body += "<A href='?_src_=holder;[HrefToken()];makementor=[M.ckey]'>Make mentor</A> | "
|
||||
body += "<A href='?_src_=holder;[HrefToken()];removementor=[M.ckey]'>Remove mentor</A>"
|
||||
return body
|
||||
@@ -0,0 +1,14 @@
|
||||
/datum/admins
|
||||
var/following = null
|
||||
|
||||
/datum/admins/associate(client/C)
|
||||
removeMentor(C.ckey) //safety to avoid multiple datums and other weird shit i cannot comprehend
|
||||
..()
|
||||
if(istype(C))
|
||||
C.mentor_datum_set(TRUE)
|
||||
|
||||
/datum/admins/disassociate()
|
||||
if(owner)
|
||||
owner.remove_mentor_verbs()
|
||||
owner.mentor_datum = null
|
||||
..()
|
||||
@@ -0,0 +1,8 @@
|
||||
/datum/admins/proc/CitadelMentorLogSecret()
|
||||
var/dat = "<B>Mentor Log<HR></B>"
|
||||
for(var/l in GLOB.mentorlog)
|
||||
dat += "<li>[l]</li>"
|
||||
|
||||
if(!GLOB.mentorlog.len)
|
||||
dat += "No mentors have done anything this round!"
|
||||
usr << browse(dat, "window=mentor_log")
|
||||
@@ -0,0 +1,55 @@
|
||||
/datum/admins/proc/citaTopic(href, href_list)
|
||||
|
||||
/datum/admins/proc/makeMentor(ckey)
|
||||
if(!usr.client)
|
||||
return
|
||||
if (!check_rights(0))
|
||||
return
|
||||
if(!ckey)
|
||||
return
|
||||
var/client/C = GLOB.directory[ckey]
|
||||
if(C)
|
||||
if(check_rights_for(C, R_ADMIN,0))
|
||||
to_chat(usr, "<span class='danger'>The client chosen is an admin! Cannot mentorize.</span>")
|
||||
return
|
||||
if(SSdbcore.Connect())
|
||||
var/datum/DBQuery/query_get_mentor = SSdbcore.NewQuery("SELECT id FROM [format_table_name("mentor")] WHERE ckey = '[ckey]'")
|
||||
if(query_get_mentor.NextRow())
|
||||
to_chat(usr, "<span class='danger'>[ckey] is already a mentor.</span>")
|
||||
return
|
||||
var/datum/DBQuery/query_add_mentor = SSdbcore.NewQuery("INSERT INTO `[format_table_name("mentor")]` (`id`, `ckey`) VALUES (null, '[ckey]')")
|
||||
if(!query_add_mentor.warn_execute())
|
||||
return
|
||||
var/datum/DBQuery/query_add_admin_log = SSdbcore.NewQuery("INSERT INTO `[format_table_name("admin_log")]` (`id` ,`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (NULL , NOW( ) , '[usr.ckey]', '[usr.client.address]', 'Added new mentor [ckey]');")
|
||||
if(!query_add_admin_log.warn_execute())
|
||||
return
|
||||
else
|
||||
to_chat(usr, "<span class='danger'>Failed to establish database connection. The changes will last only for the current round.</span>")
|
||||
new /datum/mentors(ckey)
|
||||
to_chat(usr, "<span class='adminnotice'>New mentor added.</span>")
|
||||
|
||||
/datum/admins/proc/removeMentor(ckey)
|
||||
if(!usr.client)
|
||||
return
|
||||
if (!check_rights(0))
|
||||
return
|
||||
if(!ckey)
|
||||
return
|
||||
var/client/C = GLOB.directory[ckey]
|
||||
if(C)
|
||||
if(check_rights_for(C, R_ADMIN,0))
|
||||
to_chat(usr, "<span class='danger'>The client chosen is an admin, not a mentor! Cannot de-mentorize.</span>")
|
||||
return
|
||||
C.remove_mentor_verbs()
|
||||
C.mentor_datum = null
|
||||
GLOB.mentors -= C
|
||||
if(SSdbcore.Connect())
|
||||
var/datum/DBQuery/query_remove_mentor = SSdbcore.NewQuery("DELETE FROM [format_table_name("mentor")] WHERE ckey = '[ckey]'")
|
||||
if(!query_remove_mentor.warn_execute())
|
||||
return
|
||||
var/datum/DBQuery/query_add_admin_log = SSdbcore.NewQuery("INSERT INTO `[format_table_name("admin_log")]` (`id` ,`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (NULL , NOW( ) , '[usr.ckey]', '[usr.client.address]', 'Removed mentor [ckey]');")
|
||||
if(!query_add_admin_log.warn_execute())
|
||||
return
|
||||
else
|
||||
to_chat(usr, "<span class='danger'>Failed to establish database connection. The changes will last only for the current round.</span>")
|
||||
to_chat(usr, "<span class='adminnotice'>Mentor removed.</span>")
|
||||
@@ -0,0 +1,14 @@
|
||||
/client/proc/mentor_datum_set(admin)
|
||||
mentor_datum = GLOB.mentor_datums[ckey]
|
||||
if(!mentor_datum && check_rights_for(src, R_ADMIN,0)) // admin with no mentor datum?let's fix that
|
||||
new /datum/mentors(ckey)
|
||||
if(mentor_datum)
|
||||
if(!check_rights_for(src, R_ADMIN,0) && !admin)
|
||||
GLOB.mentors |= src // don't add admins to this list too.
|
||||
mentor_datum.owner = src
|
||||
add_mentor_verbs()
|
||||
mentor_memo_output("Show")
|
||||
|
||||
/client/proc/is_mentor() // admins are mentors too.
|
||||
if(mentor_datum || check_rights_for(src, R_ADMIN,0))
|
||||
return TRUE
|
||||
@@ -142,6 +142,7 @@
|
||||
name = "Russian flask"
|
||||
category = slot_in_backpack
|
||||
path = /obj/item/reagent_containers/food/drinks/flask/russian
|
||||
cost = 2
|
||||
ckeywhitelist = list("slomka")
|
||||
|
||||
/datum/gear/stalkermask
|
||||
@@ -167,3 +168,9 @@
|
||||
category = slot_w_uniform
|
||||
path = /obj/item/clothing/suit/vermillion
|
||||
ckeywhitelist = list("fractious")
|
||||
|
||||
/datum/gear/AM4B
|
||||
name = "Foam Force AM4-B"
|
||||
category = slot_in_backpack
|
||||
path = /obj/item/gun/ballistic/automatic/AM4B
|
||||
ckeywhitelist = list("zeronetalpha")
|
||||
|
||||
@@ -18,6 +18,20 @@
|
||||
category = slot_in_backpack
|
||||
path = /obj/item/toy/plush/slimeplushie
|
||||
|
||||
/datum/gear/plushvar
|
||||
name = "Ratvar Plushie"
|
||||
category = slot_in_backpack
|
||||
path = /obj/item/toy/plush/plushvar
|
||||
cost = 5
|
||||
restricted_roles = list("Chaplain")
|
||||
|
||||
/datum/gear/narplush
|
||||
name = "Narsie Plushie"
|
||||
category = slot_in_backpack
|
||||
path = /obj/item/toy/plush/narplush
|
||||
cost = 5
|
||||
restricted_roles = list("Chaplain")
|
||||
|
||||
/datum/gear/dildo
|
||||
name = "Customizable dildo"
|
||||
category = slot_in_backpack
|
||||
|
||||
@@ -7,17 +7,17 @@
|
||||
name = "Eyepatch"
|
||||
category = slot_glasses
|
||||
path = /obj/item/clothing/glasses/eyepatch
|
||||
|
||||
|
||||
/datum/gear/heat
|
||||
name = "Heat goggles"
|
||||
category = slot_glasses
|
||||
path = /obj/item/clothing/glasses/heat
|
||||
|
||||
|
||||
/datum/gear/hipster
|
||||
name = "Hipster glasses"
|
||||
category = slot_glasses
|
||||
path = /obj/item/clothing/glasses/regular/hipster
|
||||
|
||||
|
||||
/datum/gear/jamjar
|
||||
name = "Jamjar glasses"
|
||||
category = slot_glasses
|
||||
@@ -33,7 +33,18 @@
|
||||
category = slot_glasses
|
||||
path = /obj/item/clothing/glasses/orange
|
||||
|
||||
/datum/gear/red
|
||||
name = "Red Glasses"
|
||||
category = slot_glasses
|
||||
path = /obj/item/clothing/glasses/red
|
||||
|
||||
/datum/gear/prescription
|
||||
name = "Prescription glasses"
|
||||
category = slot_glasses
|
||||
path = /obj/item/clothing/glasses/regular
|
||||
|
||||
/datum/gear/sechud
|
||||
name = "Security Hud"
|
||||
category = slot_glasses
|
||||
path = /obj/item/clothing/glasses/hud/security
|
||||
restricted_roles = list("Security Officer", "Warden", "Head of Security")
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
/datum/gear/fingerless
|
||||
name = "Fingerless Gloves"
|
||||
category = slot_gloves
|
||||
path = /obj/item/clothing/gloves/fingerless
|
||||
@@ -47,3 +47,15 @@
|
||||
name = "Wallet"
|
||||
category = slot_hands
|
||||
path = /obj/item/storage/wallet
|
||||
|
||||
/datum/gear/flask
|
||||
name = "Flask"
|
||||
category = slot_hands
|
||||
path = /obj/item/reagent_containers/food/drinks/flask
|
||||
cost = 2
|
||||
|
||||
/datum/gear/zippolighter
|
||||
name = "Zippo Lighter"
|
||||
category = slot_hands
|
||||
path = /obj/item/lighter
|
||||
cost = 2
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/datum/gear/baseball
|
||||
name = "Ballcap"
|
||||
category = slot_head
|
||||
category = slot_head
|
||||
path = /obj/item/clothing/head/soft/mime
|
||||
|
||||
/datum/gear/beanie
|
||||
@@ -37,3 +37,31 @@
|
||||
name = "Slime hat"
|
||||
category = slot_head
|
||||
path = /obj/item/clothing/head/collectable/slime
|
||||
|
||||
/datum/gear/fedora
|
||||
name = "Fedora"
|
||||
category = slot_head
|
||||
path = /obj/item/clothing/head/fedora
|
||||
|
||||
/datum/gear/that
|
||||
name = "Top Hat"
|
||||
category = slot_head
|
||||
path = /obj/item/clothing/head/that
|
||||
|
||||
/datum/gear/navybluehosberet
|
||||
name = "Head of security's Naviblue beret"
|
||||
category = slot_head
|
||||
path = /obj/item/clothing/head/beret/sec/navyhos
|
||||
restricted_roles = list("Head of Security")
|
||||
|
||||
/datum/gear/navyblueofficerberet
|
||||
name = "Security officer's Navyblue beret"
|
||||
category = slot_head
|
||||
path = /obj/item/clothing/head/beret/sec/navyofficer
|
||||
restricted_roles = list("Security Officer")
|
||||
|
||||
/datum/gear/navybluewardenberet
|
||||
name = "Warden's navyblue beret"
|
||||
category = slot_head
|
||||
path = /obj/item/clothing/head/beret/sec/navywarden
|
||||
restricted_roles = list("Warden")
|
||||
|
||||
@@ -57,3 +57,31 @@
|
||||
name = "Winter coat"
|
||||
category = slot_wear_suit
|
||||
path = /obj/item/clothing/suit/hooded/wintercoat
|
||||
|
||||
/datum/gear/militaryjacket
|
||||
name = "Military Jacket"
|
||||
category = slot_wear_suit
|
||||
path = /obj/item/clothing/suit/jacket/miljacket
|
||||
|
||||
/datum/gear/ianshirt
|
||||
name = "Ian Shirt"
|
||||
category = slot_wear_suit
|
||||
path = /obj/item/clothing/suit/ianshirt
|
||||
|
||||
/datum/gear/navybluejackethos
|
||||
name = "head of security's navyblue jacket"
|
||||
category = slot_wear_suit
|
||||
path = /obj/item/clothing/suit/security/hos
|
||||
restricted_roles = list("Head of Security")
|
||||
|
||||
/datum/gear/navybluejacketwarden
|
||||
name = "warden navyblue jacket"
|
||||
category = slot_wear_suit
|
||||
path = /obj/item/clothing/suit/security/warden
|
||||
restricted_roles = list("Warden")
|
||||
|
||||
/datum/gear/navybluejacketofficer
|
||||
name = "security officer's navyblue jacket"
|
||||
category = slot_wear_suit
|
||||
path = /obj/item/clothing/suit/security/officer
|
||||
restricted_roles = list("Security Officer")
|
||||
@@ -78,6 +78,65 @@
|
||||
category = slot_w_uniform
|
||||
path = /obj/item/clothing/under/kilt
|
||||
|
||||
|
||||
/datum/gear/navyblueuniformhos
|
||||
name = "Head of Security navyblue uniform"
|
||||
category = slot_w_uniform
|
||||
path = /obj/item/clothing/under/rank/head_of_security/navyblue
|
||||
restricted_roles = list("Head of Security")
|
||||
|
||||
/datum/gear/navyblueuniformwarden
|
||||
name = "Warden navyblue uniform"
|
||||
category = slot_w_uniform
|
||||
path = /obj/item/clothing/under/rank/warden/navyblue
|
||||
restricted_roles = list("Warden")
|
||||
|
||||
/datum/gear/navyblueuniformofficer
|
||||
name = "security officer navyblue uniform"
|
||||
category = slot_w_uniform
|
||||
path = /obj/item/clothing/under/rank/security/navyblue
|
||||
restricted_roles = list("Security officer")
|
||||
|
||||
/datum/gear/camoshorts
|
||||
name = "Camo Pants"
|
||||
category = slot_w_uniform
|
||||
path = /obj/item/clothing/under/pants/camo
|
||||
|
||||
/datum/gear/bjeans
|
||||
name = "Black Jeans"
|
||||
category = slot_w_uniform
|
||||
path = /obj/item/clothing/under/pants/blackjeans
|
||||
|
||||
/datum/gear/cjeans
|
||||
name = "Classic Jeans"
|
||||
category = slot_w_uniform
|
||||
path = /obj/item/clothing/under/pants/classicjeans
|
||||
|
||||
/datum/gear/khaki
|
||||
name = "Khaki Pants"
|
||||
category = slot_w_uniform
|
||||
path = /obj/item/clothing/under/pants/khaki
|
||||
|
||||
/datum/gear/wpants
|
||||
name = "White Pants"
|
||||
category = slot_w_uniform
|
||||
path = /obj/item/clothing/under/pants/white
|
||||
|
||||
/datum/gear/rpants
|
||||
name = "Red Pants"
|
||||
category = slot_w_uniform
|
||||
path = /obj/item/clothing/under/pants/red
|
||||
|
||||
/datum/gear/tpants
|
||||
name = "Tan Pants"
|
||||
category = slot_w_uniform
|
||||
path = /obj/item/clothing/under/pants/tan
|
||||
|
||||
/datum/gear/trpants
|
||||
name = "Track Pants"
|
||||
category = slot_w_uniform
|
||||
path = /obj/item/clothing/under/pants/track
|
||||
|
||||
/datum/gear/secskirt
|
||||
name = "Security skirt"
|
||||
category = slot_w_uniform
|
||||
@@ -89,3 +148,9 @@
|
||||
category = slot_w_uniform
|
||||
path = /obj/item/clothing/under/rank/head_of_security/skirt
|
||||
restricted_roles = list("Head of Security")
|
||||
|
||||
/datum/gear/turtleneck
|
||||
name = "Tactitool Turtleneck"
|
||||
category = slot_w_uniform
|
||||
path = /obj/item/clothing/under/syndicate/cosmetic
|
||||
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
/client/verb/mentorwho()
|
||||
set category = "Mentor"
|
||||
set name = "Mentorwho"
|
||||
var/msg = "<b>Current Mentors:</b>\n"
|
||||
for(var/X in GLOB.mentors)
|
||||
var/client/C = X
|
||||
if(!C)
|
||||
GLOB.mentors -= C
|
||||
continue // weird runtime that happens randomly
|
||||
var/suffix = ""
|
||||
if(holder)
|
||||
if(isobserver(C.mob))
|
||||
suffix += " - Observing"
|
||||
else if(istype(C.mob,/mob/dead/new_player))
|
||||
suffix += " - Lobby"
|
||||
else
|
||||
suffix += " - Playing"
|
||||
|
||||
if(C.is_afk())
|
||||
suffix += " (AFK)"
|
||||
msg += "\t[C][suffix]\n"
|
||||
to_chat(src, msg)
|
||||
|
||||
/client/verb/who()
|
||||
set name = "Who"
|
||||
set category = "OOC"
|
||||
|
||||
var/msg = ""
|
||||
|
||||
var/list/Lines = list()
|
||||
if(length(GLOB.admins))
|
||||
Lines += "<b>Admins:</b>"
|
||||
for(var/X in GLOB.admins)
|
||||
var/client/C = X
|
||||
if(C && C.holder && !C.holder.fakekey)
|
||||
Lines += "\t <font color='#FF0000'>[C.key]</font>[show_admin_info(C)] ([round(C.avgping, 1)]ms)"
|
||||
if(length(GLOB.mentors))
|
||||
Lines += "<b>Mentors:</b>"
|
||||
for(var/X in GLOB.mentors)
|
||||
var/client/C = X
|
||||
if(C)
|
||||
Lines += "\t <font color='#0033CC'>[C.key]</font>[show_admin_info(C)] ([round(C.avgping, 1)]ms)"
|
||||
|
||||
Lines += "<b>Players:</b>"
|
||||
for(var/X in sortList(GLOB.clients))
|
||||
var/client/C = X
|
||||
if(!C) continue
|
||||
var/key = C.key
|
||||
if(C.holder && C.holder.fakekey)
|
||||
key = C.holder.fakekey
|
||||
Lines += "\t [key][show_admin_info(C)] ([round(C.avgping, 1)]ms)"
|
||||
|
||||
for(var/line in Lines)
|
||||
msg += "[line]\n"
|
||||
|
||||
msg += "<b>Total Players: [length(GLOB.clients)]</b>"
|
||||
to_chat(src, msg)
|
||||
|
||||
/client/proc/show_admin_info(var/client/C)
|
||||
if(!C)
|
||||
return ""
|
||||
|
||||
if(!check_rights_for(src, R_ADMIN))
|
||||
return ""
|
||||
|
||||
var/entry = ""
|
||||
if(C.holder && C.holder.fakekey)
|
||||
entry += " <i>(as [C.holder.fakekey])</i>"
|
||||
if (isnewplayer(C.mob))
|
||||
entry += " - <font color='darkgray'><b>In Lobby</b></font>"
|
||||
else
|
||||
entry += " - Playing as [C.mob.real_name]"
|
||||
switch(C.mob.stat)
|
||||
if(UNCONSCIOUS)
|
||||
entry += " - <font color='darkgray'><b>Unconscious</b></font>"
|
||||
if(DEAD)
|
||||
if(isobserver(C.mob))
|
||||
var/mob/dead/observer/O = C.mob
|
||||
if(O.started_as_observer)
|
||||
entry += " - <font color='gray'>Observing</font>"
|
||||
else
|
||||
entry += " - <font color='black'><b>DEAD</b></font>"
|
||||
else
|
||||
entry += " - <font color='black'><b>DEAD</b></font>"
|
||||
if(is_special_character(C.mob))
|
||||
entry += " - <b><font color='red'>Antagonist</font></b>"
|
||||
entry += " (<A HREF='?_src_=holder;[HrefToken()];adminmoreinfo=\ref[C.mob]'>?</A>)"
|
||||
return entry
|
||||
|
||||
/client/verb/adminwho()
|
||||
set category = "Admin"
|
||||
set name = "Adminwho"
|
||||
|
||||
var/msg = "<b>Current Admins:</b>\n"
|
||||
if(check_rights_for(src, R_ADMIN))
|
||||
for(var/X in GLOB.admins)
|
||||
var/client/C = X
|
||||
if(!check_rights_for(C, R_ADMIN))
|
||||
continue
|
||||
msg += "\t[C] is a [C.holder.rank]"
|
||||
|
||||
if(C.holder.fakekey)
|
||||
msg += " <i>(as [C.holder.fakekey])</i>"
|
||||
|
||||
if(isobserver(C.mob))
|
||||
msg += " - Observing"
|
||||
else if(isnewplayer(C.mob))
|
||||
msg += " - Lobby"
|
||||
else
|
||||
msg += " - Playing"
|
||||
|
||||
if(C.is_afk())
|
||||
msg += " (AFK)"
|
||||
msg += "\n"
|
||||
else
|
||||
for(var/X in GLOB.admins)
|
||||
var/client/C = X
|
||||
if(!check_rights_for(C, R_ADMIN))
|
||||
continue
|
||||
if(C.is_afk())
|
||||
continue //Don't show afk admins to adminwho
|
||||
if(!C.holder.fakekey)
|
||||
msg += "\t[C] is a [C.holder.rank]\n"
|
||||
msg += "<span class='info'>Adminhelps are also sent to Discord. If no admins are available in game adminhelp anyways and an admin on Discord will see it and respond.</span>"
|
||||
to_chat(src, msg)
|
||||
@@ -0,0 +1,7 @@
|
||||
/obj/item/clothing/under/syndicate/cosmetic
|
||||
name = "tactitool turtleneck"
|
||||
desc = "Just looking at it makes you want to buy an SKS, go into the woods, and -operate-."
|
||||
icon_state = "tactifool"
|
||||
item_state = "bl_suit"
|
||||
item_color = "tactifool"
|
||||
armor = list(melee = 0, bullet = 0, laser = 0,energy = 0, bomb = 0, bio = 0, rad = 0, fire = 0, acid = 0)
|
||||
@@ -0,0 +1,26 @@
|
||||
/client/proc/mentor_follow(mob/living/M)
|
||||
if(!is_mentor())
|
||||
return
|
||||
if(isnull(M))
|
||||
return
|
||||
if(!ismob(usr))
|
||||
return
|
||||
mentor_datum.following = M
|
||||
usr.reset_perspective(M)
|
||||
verbs += /client/proc/mentor_unfollow
|
||||
to_chat(GLOB.admins, "<span class='mentor'><span class='prefix'>MENTOR:</span> <EM>[key_name(usr)]</EM> is now following <EM>[key_name(M)]</span>")
|
||||
to_chat(usr, "<span class='info'>Click the \"Stop Following\" button in the Mentor tab to stop following [key_name(M)].</span>")
|
||||
log_mentor("[key_name(usr)] began following [key_name(M)]")
|
||||
|
||||
/client/proc/mentor_unfollow()
|
||||
set category = "Mentor"
|
||||
set name = "Stop Following"
|
||||
set desc = "Stop following the followed."
|
||||
|
||||
if(!is_mentor())
|
||||
return
|
||||
usr.reset_perspective()
|
||||
verbs -= /client/proc/mentor_unfollow
|
||||
to_chat(GLOB.admins, "<span class='mentor'><span class='prefix'>MENTOR:</span> <EM>[key_name(usr)]</EM> is no longer following <EM>[key_name(mentor_datum.following)]</span>")
|
||||
log_mentor("[key_name(usr)] stopped following [key_name(mentor_datum.following)]")
|
||||
mentor_datum.following = null
|
||||
@@ -0,0 +1,113 @@
|
||||
GLOBAL_LIST_EMPTY(mentor_datums)
|
||||
GLOBAL_PROTECT(mentor_datums)
|
||||
|
||||
GLOBAL_VAR_INIT(mentor_href_token, GenerateToken())
|
||||
GLOBAL_PROTECT(mentor_href_token)
|
||||
|
||||
/datum/mentors
|
||||
var/name = "someone's mentor datum"
|
||||
var/client/owner // the actual mentor, client type
|
||||
var/target // the mentor's ckey
|
||||
var/href_token // href token for mentor commands, uses the same token used by admins.
|
||||
var/mob/following
|
||||
|
||||
/datum/mentors/New(ckey)
|
||||
if(!ckey)
|
||||
QDEL_IN(src, 0)
|
||||
throw EXCEPTION("Mentor datum created without a ckey")
|
||||
return
|
||||
target = ckey(ckey)
|
||||
name = "[ckey]'s mentor datum"
|
||||
href_token = GenerateToken()
|
||||
GLOB.mentor_datums[target] = src
|
||||
//set the owner var and load commands
|
||||
owner = GLOB.directory[ckey]
|
||||
if(owner)
|
||||
owner.mentor_datum = src
|
||||
owner.add_mentor_verbs()
|
||||
if(!check_rights_for(owner, R_ADMIN,0)) // don't add admins to mentor list.
|
||||
GLOB.mentors += owner
|
||||
|
||||
/datum/mentors/proc/CheckMentorHREF(href, href_list)
|
||||
var/auth = href_list["mentor_token"]
|
||||
. = auth && (auth == href_token || auth == GLOB.mentor_href_token)
|
||||
if(.)
|
||||
return
|
||||
var/msg = !auth ? "no" : "a bad"
|
||||
message_admins("[key_name_admin(usr)] clicked an href with [msg] authorization key!")
|
||||
if(CONFIG_GET(flag/debug_admin_hrefs))
|
||||
message_admins("Debug mode enabled, call not blocked. Please ask your coders to review this round's logs.")
|
||||
log_world("UAH: [href]")
|
||||
return TRUE
|
||||
log_admin_private("[key_name(usr)] clicked an href with [msg] authorization key! [href]")
|
||||
|
||||
/proc/RawMentorHrefToken(forceGlobal = FALSE)
|
||||
var/tok = GLOB.mentor_href_token
|
||||
if(!forceGlobal && usr)
|
||||
var/client/C = usr.client
|
||||
to_chat(world, C)
|
||||
to_chat(world, usr)
|
||||
if(!C)
|
||||
CRASH("No client for HrefToken()!")
|
||||
var/datum/mentors/holder = C.mentor_datum
|
||||
if(holder)
|
||||
tok = holder.href_token
|
||||
return tok
|
||||
|
||||
/proc/MentorHrefToken(forceGlobal = FALSE)
|
||||
return "mentor_token=[RawMentorHrefToken(forceGlobal)]"
|
||||
|
||||
/datum/mentors/Topic(href, href_list)
|
||||
..()
|
||||
if(!usr || !usr.client || usr.client != owner || !usr.client.is_mentor())
|
||||
return
|
||||
if(!CheckMentorHREF(href, href_list))
|
||||
return
|
||||
if(href_list["mentor_msg"])
|
||||
if(CONFIG_GET(flag/mentors_mobname_only))
|
||||
var/mob/M = locate(href_list["mentor_msg"])
|
||||
usr.client.cmd_mentor_pm(M,null)
|
||||
else
|
||||
usr.client.cmd_mentor_pm(href_list["mentor_msg"],null)
|
||||
return
|
||||
|
||||
//Mentor Follow
|
||||
if(href_list["mentor_follow"])
|
||||
var/mob/living/M = locate(href_list["mentor_follow"])
|
||||
|
||||
if(istype(M))
|
||||
usr.client.mentor_follow(M)
|
||||
|
||||
return
|
||||
|
||||
/proc/load_mentors()
|
||||
GLOB.mentor_datums.Cut()
|
||||
for(var/client/C in GLOB.mentors)
|
||||
C.remove_mentor_verbs()
|
||||
C.mentor_datum = null
|
||||
GLOB.mentors.Cut()
|
||||
if(CONFIG_GET(flag/mentor_legacy_system))//legacy
|
||||
var/list/lines = world.file2list("config/mentors.txt")
|
||||
for(var/line in lines)
|
||||
if(!length(line))
|
||||
continue
|
||||
if(findtextEx(line, "#", 1, 2))
|
||||
continue
|
||||
new /datum/mentors(line)
|
||||
else//Database
|
||||
if(!SSdbcore.Connect())
|
||||
log_world("Failed to connect to database in load_mentors(). Reverting to legacy system.")
|
||||
WRITE_FILE(GLOB.world_game_log, "Failed to connect to database in load_mentors(). Reverting to legacy system.")
|
||||
CONFIG_SET(flag/mentor_legacy_system, TRUE)
|
||||
load_mentors()
|
||||
return
|
||||
var/datum/DBQuery/query_load_mentors = SSdbcore.NewQuery("SELECT ckey FROM [format_table_name("mentor")]")
|
||||
if(!query_load_mentors.Execute())
|
||||
return
|
||||
while(query_load_mentors.NextRow())
|
||||
var/ckey = ckey(query_load_mentors.item[1])
|
||||
new /datum/mentors(ckey)
|
||||
|
||||
// new client var: mentor_datum. Acts the same way holder does towards admin: it holds the mentor datum. if set, the guy's a mentor.
|
||||
/client
|
||||
var/datum/mentors/mentor_datum
|
||||
@@ -0,0 +1,141 @@
|
||||
/client/proc/mentor_memo()
|
||||
set name = "Mentor Memos"
|
||||
set category = "Server"
|
||||
if(!check_rights(0))
|
||||
return
|
||||
if(!SSdbcore.IsConnected())
|
||||
to_chat(src, "<span class='danger'>Failed to establish database connection.</span>")
|
||||
return
|
||||
var/memotask = input(usr,"Choose task.","Memo") in list("Show","Write","Edit","Remove")
|
||||
if(!memotask)
|
||||
return
|
||||
mentor_memo_output(memotask)
|
||||
|
||||
/client/proc/show_mentor_memo()
|
||||
set name = "Show Memos"
|
||||
set category = "Mentor"
|
||||
if(!is_mentor())
|
||||
return
|
||||
if(!SSdbcore.IsConnected())
|
||||
to_chat(src, "<span class='danger'>Failed to establish database connection.</span>")
|
||||
return
|
||||
mentor_memo_output("Show")
|
||||
|
||||
/client/proc/mentor_memo_output(task)
|
||||
if(!task)
|
||||
return
|
||||
if(!SSdbcore.IsConnected())
|
||||
to_chat(src, "<span class='danger'>Failed to establish database connection.</span>")
|
||||
return
|
||||
var/sql_ckey = sanitizeSQL(ckey)
|
||||
switch(task)
|
||||
if("Write")
|
||||
var/datum/DBQuery/query_memocheck = SSdbcore.NewQuery("SELECT ckey FROM [format_table_name("mentor_memo")] WHERE ckey = '[sql_ckey]'")
|
||||
if(!query_memocheck.Execute())
|
||||
var/err = query_memocheck.ErrorMsg()
|
||||
log_game("SQL ERROR obtaining ckey from memo table. Error : \[[err]\]\n")
|
||||
return
|
||||
if(query_memocheck.NextRow())
|
||||
to_chat(src, "You already have set a memo.")
|
||||
return
|
||||
var/memotext = input(src,"Write your Memo","Memo") as message
|
||||
if(!memotext)
|
||||
return
|
||||
memotext = sanitizeSQL(memotext)
|
||||
var/timestamp = SQLtime()
|
||||
var/datum/DBQuery/query_memoadd = SSdbcore.NewQuery("INSERT INTO [format_table_name("mentor_memo")] (ckey, memotext, timestamp) VALUES ('[sql_ckey]', '[memotext]', '[timestamp]')")
|
||||
if(!query_memoadd.Execute())
|
||||
var/err = query_memoadd.ErrorMsg()
|
||||
log_game("SQL ERROR adding new memo. Error : \[[err]\]\n")
|
||||
return
|
||||
log_admin("[key_name(src)] has set a mentor memo: [memotext]")
|
||||
message_admins("[key_name_admin(src)] has set a mentor memo:<br>[memotext]")
|
||||
if("Edit")
|
||||
var/datum/DBQuery/query_memolist = SSdbcore.NewQuery("SELECT ckey FROM [format_table_name("mentor_memo")]")
|
||||
if(!query_memolist.Execute())
|
||||
var/err = query_memolist.ErrorMsg()
|
||||
log_game("SQL ERROR obtaining ckey from memo table. Error : \[[err]\]\n")
|
||||
return
|
||||
var/list/memolist = list()
|
||||
while(query_memolist.NextRow())
|
||||
var/lkey = query_memolist.item[1]
|
||||
memolist += "[lkey]"
|
||||
if(!memolist.len)
|
||||
to_chat(src, "No memos found in database.")
|
||||
return
|
||||
var/target_ckey = input(src, "Select whose memo to edit", "Select memo") as null|anything in memolist
|
||||
if(!target_ckey)
|
||||
return
|
||||
var/target_sql_ckey = sanitizeSQL(target_ckey)
|
||||
var/datum/DBQuery/query_memofind = SSdbcore.NewQuery("SELECT memotext FROM [format_table_name("mentor_memo")] WHERE ckey = '[target_sql_ckey]'")
|
||||
if(!query_memofind.Execute())
|
||||
var/err = query_memofind.ErrorMsg()
|
||||
log_game("SQL ERROR obtaining memotext from memo table. Error : \[[err]\]\n")
|
||||
return
|
||||
if(query_memofind.NextRow())
|
||||
var/old_memo = query_memofind.item[1]
|
||||
var/new_memo = input("Input new memo", "New Memo", "[old_memo]", null) as message
|
||||
if(!new_memo)
|
||||
return
|
||||
new_memo = sanitizeSQL(new_memo)
|
||||
var/edit_text = "Edited by [sql_ckey] on [SQLtime()] from<br>[old_memo]<br>to<br>[new_memo]<hr>"
|
||||
edit_text = sanitizeSQL(edit_text)
|
||||
var/datum/DBQuery/update_query = SSdbcore.NewQuery("UPDATE [format_table_name("mentor_memo")] SET memotext = '[new_memo]', last_editor = '[sql_ckey]', edits = CONCAT(IFNULL(edits,''),'[edit_text]') WHERE ckey = '[target_sql_ckey]'")
|
||||
if(!update_query.Execute())
|
||||
var/err = update_query.ErrorMsg()
|
||||
log_game("SQL ERROR editing memo. Error : \[[err]\]\n")
|
||||
return
|
||||
if(target_sql_ckey == sql_ckey)
|
||||
log_admin("[key_name(src)] has edited their mentor memo from [old_memo] to [new_memo]")
|
||||
message_admins("[key_name_admin(src)] has edited their mentor memo from<br>[old_memo]<br>to<br>[new_memo]")
|
||||
else
|
||||
log_admin("[key_name(src)] has edited [target_sql_ckey]'s mentor memo from [old_memo] to [new_memo]")
|
||||
message_admins("[key_name_admin(src)] has edited [target_sql_ckey]'s mentor memo from<br>[old_memo]<br>to<br>[new_memo]")
|
||||
if("Show")
|
||||
var/datum/DBQuery/query_memoshow = SSdbcore.NewQuery("SELECT ckey, memotext, timestamp, last_editor FROM [format_table_name("mentor_memo")]")
|
||||
if(!query_memoshow.Execute())
|
||||
var/err = query_memoshow.ErrorMsg()
|
||||
log_game("SQL ERROR obtaining ckey, memotext, timestamp, last_editor from memo table. Error : \[[err]\]\n")
|
||||
return
|
||||
var/output = null
|
||||
while(query_memoshow.NextRow())
|
||||
var/ckey = query_memoshow.item[1]
|
||||
var/memotext = query_memoshow.item[2]
|
||||
var/timestamp = query_memoshow.item[3]
|
||||
var/last_editor = query_memoshow.item[4]
|
||||
output += "<span class='memo'>Mentor memo by <span class='prefix'>[ckey]</span> on [timestamp]"
|
||||
if(last_editor)
|
||||
output += "<br><span class='memoedit'>Last edit by [last_editor] <A href='?_src_=holder;mentormemoeditlist=[ckey]'>(Click here to see edit log)</A></span>"
|
||||
output += "<br>[memotext]</span><br>"
|
||||
if(!output)
|
||||
to_chat(src, "No memos found in database.")
|
||||
return
|
||||
to_chat(src, output)
|
||||
if("Remove")
|
||||
var/datum/DBQuery/query_memodellist = SSdbcore.NewQuery("SELECT ckey FROM [format_table_name("mentor_memo")]")
|
||||
if(!query_memodellist.Execute())
|
||||
var/err = query_memodellist.ErrorMsg()
|
||||
log_game("SQL ERROR obtaining ckey from memo table. Error : \[[err]\]\n")
|
||||
return
|
||||
var/list/memolist = list()
|
||||
while(query_memodellist.NextRow())
|
||||
var/ckey = query_memodellist.item[1]
|
||||
memolist += "[ckey]"
|
||||
if(!memolist.len)
|
||||
to_chat(src, "No memos found in database.")
|
||||
return
|
||||
var/target_ckey = input(src, "Select whose mentor memo to delete", "Select mentor memo") as null|anything in memolist
|
||||
if(!target_ckey)
|
||||
return
|
||||
var/target_sql_ckey = sanitizeSQL(target_ckey)
|
||||
var/datum/DBQuery/query_memodel = SSdbcore.NewQuery("DELETE FROM [format_table_name("memo")] WHERE ckey = '[target_sql_ckey]'")
|
||||
if(!query_memodel.Execute())
|
||||
var/err = query_memodel.ErrorMsg()
|
||||
log_game("SQL ERROR removing memo. Error : \[[err]\]\n")
|
||||
return
|
||||
if(target_sql_ckey == sql_ckey)
|
||||
log_admin("[key_name(src)] has removed their mentor memo.")
|
||||
message_admins("[key_name_admin(src)] has removed their mentor memo.")
|
||||
else
|
||||
log_admin("[key_name(src)] has removed [target_sql_ckey]'s mentor memo.")
|
||||
message_admins("[key_name_admin(src)] has removed [target_sql_ckey]'s mentor memo.")
|
||||
@@ -0,0 +1,12 @@
|
||||
GLOBAL_PROTECT(mentor_verbs)
|
||||
GLOBAL_LIST_INIT(mentor_verbs, list(
|
||||
/client/proc/cmd_mentor_say,
|
||||
/client/proc/show_mentor_memo
|
||||
))
|
||||
|
||||
/client/proc/add_mentor_verbs()
|
||||
if(mentor_datum)
|
||||
verbs += GLOB.mentor_verbs
|
||||
|
||||
/client/proc/remove_mentor_verbs()
|
||||
verbs -= GLOB.mentor_verbs
|
||||
@@ -0,0 +1,98 @@
|
||||
/client/verb/mentorhelp(msg as text)
|
||||
set category = "Mentor"
|
||||
set name = "Mentorhelp"
|
||||
|
||||
//clean the input msg
|
||||
if(!msg) return
|
||||
|
||||
//remove out mentorhelp verb temporarily to prevent spamming of mentors.
|
||||
verbs -= /client/verb/mentorhelp
|
||||
spawn(300)
|
||||
verbs += /client/verb/mentorhelp // 30 second cool-down for mentorhelp
|
||||
|
||||
msg = sanitize(copytext(msg,1,MAX_MESSAGE_LEN))
|
||||
if(!msg) return
|
||||
if(!mob) return //this doesn't happen
|
||||
|
||||
var/show_char = CONFIG_GET(flag/mentors_mobname_only)
|
||||
var/mentor_msg = "<span class='mentornotice'><b><font color='purple'>MENTORHELP:</b> <b>[key_name_mentor(src, 1, 0, 1, show_char)]</b>: [msg]</font></span>"
|
||||
log_mentor("MENTORHELP: [key_name_mentor(src, 0, 0, 0, 0)]: [msg]")
|
||||
|
||||
for(var/client/X in GLOB.mentors | GLOB.admins)
|
||||
X << 'sound/items/bikehorn.ogg'
|
||||
to_chat(X, mentor_msg)
|
||||
|
||||
to_chat(src, "<span class='mentornotice'><font color='purple'>PM to-<b>Mentors</b>: [msg]</font></span>")
|
||||
return
|
||||
|
||||
/proc/get_mentor_counts()
|
||||
. = list("total" = 0, "afk" = 0, "present" = 0)
|
||||
for(var/X in GLOB.mentors)
|
||||
var/client/C = X
|
||||
.["total"]++
|
||||
if(C.is_afk())
|
||||
.["afk"]++
|
||||
else
|
||||
.["present"]++
|
||||
|
||||
/proc/key_name_mentor(var/whom, var/include_link = null, var/include_name = 0, var/include_follow = 0, var/char_name_only = 0)
|
||||
var/mob/M
|
||||
var/client/C
|
||||
var/key
|
||||
var/ckey
|
||||
|
||||
if(!whom) return "*null*"
|
||||
if(istype(whom, /client))
|
||||
C = whom
|
||||
M = C.mob
|
||||
key = C.key
|
||||
ckey = C.ckey
|
||||
else if(ismob(whom))
|
||||
M = whom
|
||||
C = M.client
|
||||
key = M.key
|
||||
ckey = M.ckey
|
||||
else if(istext(whom))
|
||||
key = whom
|
||||
ckey = ckey(whom)
|
||||
C = GLOB.directory[ckey]
|
||||
if(C)
|
||||
M = C.mob
|
||||
else
|
||||
return "*invalid*"
|
||||
|
||||
. = ""
|
||||
|
||||
if(!ckey)
|
||||
include_link = 0
|
||||
|
||||
if(key)
|
||||
if(include_link)
|
||||
if(CONFIG_GET(flag/mentors_mobname_only))
|
||||
. += "<a href='?_src_=mentor;mentor_msg=[REF(M)];[MentorHrefToken(TRUE)]'>"
|
||||
else
|
||||
. += "<a href='?_src_=mentor;mentor_msg=[ckey];[MentorHrefToken(TRUE)]'>"
|
||||
|
||||
if(C && C.holder && C.holder.fakekey)
|
||||
. += "Administrator"
|
||||
else if (char_name_only && CONFIG_GET(flag/mentors_mobname_only))
|
||||
if(istype(C.mob,/mob/dead/new_player) || istype(C.mob, /mob/dead/observer)) //If they're in the lobby or observing, display their ckey
|
||||
. += key
|
||||
else if(C && C.mob) //If they're playing/in the round, only show the mob name
|
||||
. += C.mob.name
|
||||
else //If for some reason neither of those are applicable and they're mentorhelping, show ckey
|
||||
. += key
|
||||
else
|
||||
. += key
|
||||
if(!C)
|
||||
. += "\[DC\]"
|
||||
|
||||
if(include_link)
|
||||
. += "</a>"
|
||||
else
|
||||
. += "*no key*"
|
||||
|
||||
if(include_follow)
|
||||
. += " (<a href='?_src_=mentor;mentor_follow=[REF(M)];[MentorHrefToken(TRUE)]'>F</a>)"
|
||||
|
||||
return .
|
||||
@@ -0,0 +1,71 @@
|
||||
//shows a list of clients we could send PMs to, then forwards our choice to cmd_Mentor_pm
|
||||
/client/proc/cmd_mentor_pm_panel()
|
||||
set category = "Mentor"
|
||||
set name = "Mentor PM"
|
||||
if(!is_mentor())
|
||||
to_chat(src, "<font color='red'>Error: Mentor-PM-Panel: Only Mentors and Admins may use this command.</font>")
|
||||
return
|
||||
var/list/client/targets[0]
|
||||
for(var/client/T)
|
||||
targets["[T]"] = T
|
||||
|
||||
var/list/sorted = sortList(targets)
|
||||
var/target = input(src,"To whom shall we send a message?","Mentor PM",null) in sorted|null
|
||||
cmd_mentor_pm(targets[target],null)
|
||||
SSblackbox.record_feedback("tally", "Mentor_verb", 1, "APM") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
|
||||
//takes input from cmd_mentor_pm_context, cmd_Mentor_pm_panel or /client/Topic and sends them a PM.
|
||||
//Fetching a message if needed. src is the sender and C is the target client
|
||||
/client/proc/cmd_mentor_pm(whom, msg)
|
||||
var/client/C
|
||||
if(ismob(whom))
|
||||
var/mob/M = whom
|
||||
C = M.client
|
||||
else if(istext(whom))
|
||||
C = GLOB.directory[whom]
|
||||
else if(istype(whom,/client))
|
||||
C = whom
|
||||
if(!C)
|
||||
if(is_mentor()) to_chat(src, "<font color='red'>Error: Mentor-PM: Client not found.</font>")
|
||||
else mentorhelp(msg) //Mentor we are replying to left. Mentorhelp instead(check below)
|
||||
return
|
||||
|
||||
//get message text, limit it's length.and clean/escape html
|
||||
if(!msg)
|
||||
msg = input(src,"Message:", "Private message") as text|null
|
||||
|
||||
if(!msg) return
|
||||
if(!C)
|
||||
if(is_mentor()) to_chat(src, "<font color='red'>Error: Mentor-PM: Client not found.</font>")
|
||||
else mentorhelp(msg) //Mentor we are replying to has vanished, Mentorhelp instead (how the fuck does this work?let's hope it works,shrug)
|
||||
return
|
||||
|
||||
msg = sanitize(copytext(msg,1,MAX_MESSAGE_LEN))
|
||||
if(!msg) return
|
||||
|
||||
log_mentor("Mentor PM: [key_name(src)]->[key_name(C)]: [msg]")
|
||||
|
||||
msg = emoji_parse(msg)
|
||||
C << 'sound/items/bikehorn.ogg'
|
||||
var/show_char = CONFIG_GET(flag/mentors_mobname_only)
|
||||
if(C.is_mentor())
|
||||
if(is_mentor())//both are mentors
|
||||
to_chat(C, "<font color='purple'>Mentor PM from-<b>[key_name_mentor(src, C, 1, 0, 0)]</b>: [msg]</font>")
|
||||
to_chat(src, "<font color='green'>Mentor PM to-<b>[key_name_mentor(C, C, 1, 0, 0)]</b>: [msg]</font>")
|
||||
|
||||
else //recipient is an mentor but sender is not
|
||||
to_chat(C, "<font color='purple'>Reply PM from-<b>[key_name_mentor(src, C, 1, 0, show_char)]</b>: [msg]</font>")
|
||||
to_chat(src, "<font color='green'>Mentor PM to-<b>[key_name_mentor(C, C, 1, 0, 0)]</b>: [msg]</font>")
|
||||
|
||||
else
|
||||
if(is_mentor()) //sender is an mentor but recipient is not.
|
||||
to_chat(C, "<font color='purple'>Mentor PM from-<b>[key_name_mentor(src, C, 1, 0, 0)]</b>: [msg]</font>")
|
||||
to_chat(src, "<font color='green'>Mentor PM to-<b>[key_name_mentor(C, C, 1, 0, show_char)]</b>: [msg]</font>")
|
||||
|
||||
//we don't use message_Mentors here because the sender/receiver might get it too
|
||||
var/show_char_sender = !is_mentor() && CONFIG_GET(flag/mentors_mobname_only)
|
||||
var/show_char_recip = !C.is_mentor() && CONFIG_GET(flag/mentors_mobname_only)
|
||||
for(var/client/X in GLOB.mentors | GLOB.admins)
|
||||
if(X.key!=key && X.key!=C.key) //check client/X is an Mentor and isn't the sender or recipient
|
||||
to_chat(X, "<B><font color='green'>Mentor PM: [key_name_mentor(src, X, 0, 0, show_char_sender)]->[key_name_mentor(C, X, 0, 0, show_char_recip)]:</B> <font color ='blue'> [msg]</font>") //inform X
|
||||
@@ -0,0 +1,18 @@
|
||||
/client/proc/cmd_mentor_say(msg as text)
|
||||
set category = "Mentor"
|
||||
set name = "Msay" //Gave this shit a shorter name so you only have to time out "msay" rather than "mentor say" to use it --NeoFite
|
||||
set hidden = 1
|
||||
if(!is_mentor())
|
||||
return
|
||||
|
||||
msg = copytext(sanitize(msg), 1, MAX_MESSAGE_LEN)
|
||||
if(!msg) return
|
||||
|
||||
msg = emoji_parse(msg)
|
||||
log_mentor("MSAY: [key_name(src)] : [msg]")
|
||||
|
||||
if(check_rights_for(src, R_ADMIN,0))
|
||||
msg = "<b><font color ='#8A2BE2'><span class='prefix'>MENTOR:</span> <EM>[key_name(src, 0, 0)]</EM>: <span class='message'>[msg]</span></font></b>"
|
||||
else
|
||||
msg = "<b><font color ='#E236D8'><span class='prefix'>MENTOR:</span> <EM>[key_name(src, 0, 0)]</EM>: <span class='message'>[msg]</span></font></b>"
|
||||
to_chat(GLOB.admins | GLOB.mentors, msg)
|
||||
@@ -0,0 +1,12 @@
|
||||
/mob/living/carbon/human/Life()
|
||||
//citadel code
|
||||
if(stat != DEAD)
|
||||
handle_arousal()
|
||||
. = ..()
|
||||
|
||||
/mob/living/carbon/human/calculate_affecting_pressure(pressure)
|
||||
if(ismob(loc))
|
||||
return ONE_ATMOSPHERE
|
||||
if(istype(loc, /obj/item/device/dogborg/sleeper))
|
||||
return ONE_ATMOSPHERE
|
||||
. = ..()
|
||||
Reference in New Issue
Block a user