Helper proc /proc/find_record(field, value, list/L)

It will search list/L for a /datum/data/record with fields[field] == value, and return it.

This removes a vast amount of copypasta (there's still so much left in there though). It also removes all the locate(\ref[record]) I could find, which were normal used with topic(), meaning it was susceptible to abuse.

Secbots, ed209s and turrets are now smarter with their record-checks. They now prefer to use our face-name rather than id-name. ICly, this is the bot using facial recognition. This fixes an issue where it'd try to use the name variable which could be in the format "Unknown (as their id name)" when they were disfigured, etc. causing record-lookup to fail.

Additionally, these bots will treat those without a valid record in the security database, to be treated like criminals.
This commit is contained in:
carnie
2013-08-11 12:49:55 +01:00
parent 37e76ffec4
commit ffa9bb51cd
21 changed files with 284 additions and 453 deletions
@@ -276,25 +276,13 @@
if(usr.stat || H != usr) //|| !usr.canmove || usr.restrained()) Fluff: Sechuds have eye-tracking technology and sets 'arrest' to people that the wearer looks and blinks at.
return //Non-fluff: This allows sec to set people to arrest as they get disarmed or beaten
var/perpname = "wot"
var/criminal = "None"
if(wear_id)
var/obj/item/weapon/card/id/I = wear_id.GetID()
if(I)
perpname = I.registered_name
else
perpname = name
else
perpname = name
var/perpname = H.get_face_name(H.get_id_name(""))
if(perpname)
for (var/datum/data/record/E in data_core.general)
if(E.fields["name"] == perpname)
for (var/datum/data/record/R in data_core.security)
if(R.fields["id"] == E.fields["id"])
criminal = R.fields["criminal"]
var/datum/data/record/R = find_record("name", perpname, data_core.security)
if(R)
criminal = R.fields["criminal"]
msg += "<span class = 'deptradio'>Criminal status:</span> <a href='?src=\ref[src];criminal=1'>\[[criminal]\]</a>\n"
//msg += "\[Set Hostile Identification\]\n"
+40 -61
View File
@@ -396,74 +396,65 @@
//gets assignment from ID or ID inside PDA or PDA itself
//Useful when player do something with computers
/mob/living/carbon/human/proc/get_assignment(var/if_no_id = "No id", var/if_no_job = "No job")
var/obj/item/device/pda/pda = wear_id
var/obj/item/weapon/card/id/id = wear_id
if (istype(pda))
if (pda.id && istype(pda.id, /obj/item/weapon/card/id))
. = pda.id.assignment
else
. = pda.ownjob
else if (istype(id))
var/obj/item/weapon/card/id/id = get_idcard()
if(id)
. = id.assignment
else
return if_no_id
if (!.)
. = if_no_job
return
var/obj/item/device/pda/pda = wear_id
if(istype(pda))
. = pda.ownjob
else
return if_no_id
if(!.)
return if_no_job
//gets name from ID or ID inside PDA or PDA itself
//Useful when player do something with computers
/mob/living/carbon/human/proc/get_authentification_name(var/if_no_id = "Unknown")
var/obj/item/weapon/card/id/id = get_idcard()
if(id)
return id.registered_name
var/obj/item/device/pda/pda = wear_id
var/obj/item/weapon/card/id/id = wear_id
if (istype(pda))
if (pda.id)
. = pda.id.registered_name
else
. = pda.owner
else if (istype(id))
. = id.registered_name
else
return if_no_id
return
if(istype(pda))
return pda.owner
return if_no_id
//repurposed proc. Now it combines get_id_name() and get_face_name() to determine a mob's name variable. Made into a seperate proc as it'll be useful elsewhere
/mob/living/carbon/human/proc/get_visible_name()
if( wear_mask && (wear_mask.flags_inv&HIDEFACE) ) //Wearing a mask which hides our face, use id-name if possible
return get_id_name("Unknown")
if( head && (head.flags_inv&HIDEFACE) )
return get_id_name("Unknown") //Likewise for hats
var/face_name = get_face_name()
var/id_name = get_id_name("")
if(id_name && (id_name != face_name))
return "[face_name] (as [id_name])"
return face_name
//Returns "Unknown" if facially disfigured and real_name if not. Useful for setting name when polyacided or when updating a human's name variable
/mob/living/carbon/human/proc/get_face_name()
/mob/living/carbon/human/proc/get_face_name(if_no_face="Unknown")
if( wear_mask && (wear_mask.flags_inv&HIDEFACE) ) //Wearing a mask which hides our face, use id-name if possible
return if_no_face
if( head && (head.flags_inv&HIDEFACE) )
return if_no_face //Likewise for hats
var/datum/limb/O = get_organ("head")
if( (status_flags&DISFIGURED) || (O.brutestate+O.burnstate)>2 || cloneloss>50 || !real_name ) //disfigured. use id-name if possible
return "Unknown"
return if_no_face
return real_name
//gets name from ID or PDA itself, ID inside PDA doesn't matter
//Useful when player is being seen by other mobs
/mob/living/carbon/human/proc/get_id_name(var/if_no_id = "Unknown")
var/obj/item/weapon/storage/wallet/wallet = wear_id
var/obj/item/device/pda/pda = wear_id
var/obj/item/weapon/card/id/id = wear_id
if(istype(pda)) . = pda.owner
else if(istype(id)) . = id.registered_name
if(!.) . = if_no_id //to prevent null-names making the mob unclickable
if(istype(wallet)) id = wallet.front_id
if(istype(id)) . = id.registered_name
else if(istype(pda)) . = pda.owner
if(!.) . = if_no_id //to prevent null-names making the mob unclickable
return
//gets ID card object from special clothes slot or null.
/mob/living/carbon/human/proc/get_idcard()
var/obj/item/weapon/card/id/id = wear_id
var/obj/item/device/pda/pda = wear_id
if (istype(pda) && pda.id)
id = pda.id
if (istype(id))
return id
if(wear_id)
return wear_id.GetID()
//Added a safety check in case you want to shock a human mob directly through electrocute_act.
/mob/living/carbon/human/electrocute_act(var/shock_damage, var/obj/source, var/siemens_coeff = 1.0, var/safety = 0)
@@ -535,31 +526,19 @@
var/modified = 0
var/perpname = "wot"
if(wear_id)
var/obj/item/weapon/card/id/I = wear_id.GetID()
if(I)
perpname = I.registered_name
else
perpname = name
else
perpname = name
var/perpname = H.get_face_name(H.get_id_name(""))
if(perpname)
for (var/datum/data/record/E in data_core.general)
if (E.fields["name"] == perpname)
for (var/datum/data/record/R in data_core.security)
if (R.fields["id"] == E.fields["id"])
var/datum/data/record/R = find_record("name", perpname, data_core.security)
if(R)
var/setcriminal = input(usr, "Specify a new criminal status for this person.", "Security HUD", R.fields["criminal"]) in list("None", "*Arrest*", "Incarcerated", "Parolled", "Released", "Cancel")
if(R)
if(istype(H.glasses, /obj/item/clothing/glasses/hud/security) || istype(H.glasses, /obj/item/clothing/glasses/sunglasses/sechud))
if(setcriminal != "Cancel")
R.fields["criminal"] = setcriminal
modified = 1
var/setcriminal = input(usr, "Specify a new criminal status for this person.", "Security HUD", R.fields["criminal"]) in list("None", "*Arrest*", "Incarcerated", "Parolled", "Released", "Cancel")
if(istype(H.glasses, /obj/item/clothing/glasses/hud/security) || istype(H.glasses, /obj/item/clothing/glasses/sunglasses/sechud))
if(setcriminal != "Cancel")
R.fields["criminal"] = setcriminal
modified = 1
spawn()
H.handle_regular_hud_updates()
spawn()
H.handle_regular_hud_updates()
if(!modified)
usr << "\red Unable to locate a data core entry for this person."
+2 -6
View File
@@ -214,12 +214,8 @@ var/list/ai_list = list()
set name = "Show Crew Manifest"
var/dat = "<html><head><title>Crew Roster</title></head><body><b>Crew Roster:</b><br><br>"
var/list/L = list()
for (var/datum/data/record/t in data_core.general)
var/R = t.fields["name"] + " - " + t.fields["rank"]
L += R
for(var/R in sortList(L))
dat += "[R]<br>"
for(var/datum/data/record/t in sortRecord(data_core.general))
dat += t.fields["name"] + " - " + t.fields["rank"] + "<br>"
dat += "</body></html>"
src << browse(dat, "window=airoster")
+15 -32
View File
@@ -9,42 +9,25 @@
var/image/holder
var/turf/T = get_turf(src.loc)
for(var/mob/living/carbon/human/perp in view(T))
var/perpname = "wot"
holder = perp.hud_list[ID_HUD]
holder.icon_state = "hudno_id"
if(perp.wear_id)
var/obj/item/weapon/card/id/I = perp.wear_id.GetID()
if(I)
perpname = I.registered_name
holder.icon_state = "hud[ckey(perp:wear_id:GetJobName())]"
client.images += holder
else
perpname = perp.name
holder.icon_state = "hudno_id"
client.images += holder
else
holder.icon_state = "hudno_id"
client.images += holder
holder.icon_state = "hud[ckey(perp:wear_id:GetJobName())]"
client.images += holder
for(var/datum/data/record/E in data_core.general)
if(E.fields["name"] == perpname)
var/perpname = perp.get_face_name(perp.get_id_name(""))
if(perpname)
var/datum/data/record/R = find_record("name", perpname, data_core.security)
if(R)
holder = perp.hud_list[WANTED_HUD]
for (var/datum/data/record/R in data_core.security)
if((R.fields["id"] == E.fields["id"]) && (R.fields["criminal"] == "*Arrest*"))
holder.icon_state = "hudwanted"
client.images += holder
break
else if((R.fields["id"] == E.fields["id"]) && (R.fields["criminal"] == "Incarcerated"))
holder.icon_state = "hudincarcerated"
client.images += holder
break
else if((R.fields["id"] == E.fields["id"]) && (R.fields["criminal"] == "Parolled"))
holder.icon_state = "hudparolled"
client.images += holder
break
else if((R.fields["id"] == E.fields["id"]) && (R.fields["criminal"] == "Released"))
holder.icon_state = "hudreleased"
client.images += holder
break
switch(R.fields["criminal"])
if("*Arrest*") holder.icon_state = "hudwanted"
if("Incarcerated") holder.icon_state = "hudincarcerated"
if("Parolled") holder.icon_state = "hudparolled"
if("Released") holder.icon_state = "hudreleased"
else
return
client.images += holder
/mob/living/silicon/pai/proc/medicalHUD()
if(client)
+60 -77
View File
@@ -218,33 +218,22 @@
// Accessing medical records
if("medicalrecord")
if(src.subscreen == 1)
var/datum/data/record/record = locate(href_list["med_rec"])
if(record)
var/datum/data/record/R = record
var/datum/data/record/M = record
if (!( data_core.general.Find(R) ))
src.temp = "Unable to locate requested medical record. Record may have been deleted, or never have existed."
else
for(var/datum/data/record/E in data_core.medical)
if ((E.fields["name"] == R.fields["name"] || E.fields["id"] == R.fields["id"]))
M = E
src.medicalActive1 = R
src.medicalActive2 = M
if(subscreen == 1)
medicalActive1 = find_record("id", href_list["med_rec"], data_core.general)
if(medicalActive1)
medicalActive2 = find_record("id", href_list["med_rec"], data_core.medical)
if(!medicalActive2)
medicalActive1 = null
temp = "Unable to locate requested security record. Record may have been deleted, or never have existed."
if("securityrecord")
if(src.subscreen == 1)
var/datum/data/record/record = locate(href_list["sec_rec"])
if(record)
var/datum/data/record/R = record
var/datum/data/record/M = record
if (!( data_core.general.Find(R) ))
src.temp = "Unable to locate requested security record. Record may have been deleted, or never have existed."
else
for(var/datum/data/record/E in data_core.security)
if ((E.fields["name"] == R.fields["name"] || E.fields["id"] == R.fields["id"]))
M = E
src.securityActive1 = R
src.securityActive2 = M
if(subscreen == 1)
securityActive1 = find_record("id", href_list["sec_rec"], data_core.general)
if(securityActive1)
securityActive2 = find_record("id", href_list["sec_rec"], data_core.security)
if(!securityActive2)
securityActive1 = null
temp = "Unable to locate requested security record. Record may have been deleted, or never have existed."
if("securityhud")
if(href_list["toggle"])
src.secHUD = !src.secHUD
@@ -410,70 +399,64 @@
// Crew Manifest
/mob/living/silicon/pai/proc/softwareManifest()
var/dat = ""
dat += "<h2>Crew Manifest</h2><br><br>"
var/list/L = list()
if(!isnull(data_core.general))
for (var/datum/data/record/t in sortRecord(data_core.general))
var/R = t.fields["name"] + " - " + t.fields["rank"]
L += R
for(var/R in sortList(L))
dat += "[R]<br>"
dat += "</body></html>"
return dat
. += "<h2>Crew Manifest</h2><br><br>"
if(data_core.general)
for(var/datum/data/record/t in sortRecord(data_core.general))
. += "[t.fields["name"]] - [t.fields["rank"]]"
. += "</body></html>"
return .
// Medical Records
/mob/living/silicon/pai/proc/softwareMedicalRecord()
var/dat = ""
if(src.subscreen == 0)
dat += "<h3>Medical Records</h3><HR>"
if(!isnull(data_core.general))
for(var/datum/data/record/R in sortRecord(data_core.general))
dat += text("<A href='?src=\ref[];med_rec=\ref[];software=medicalrecord;sub=1'>[]: []<BR>", src, R, R.fields["id"], R.fields["name"])
//dat += text("<HR><A href='?src=\ref[];screen=0;softFunction=medical records'>Back</A>", src)
if(src.subscreen == 1)
dat += "<CENTER><B>Medical Record</B></CENTER><BR>"
if ((istype(src.medicalActive1, /datum/data/record) && data_core.general.Find(src.medicalActive1)))
dat += text("Name: [] ID: []<BR>\nSex: []<BR>\nAge: []<BR>\nFingerprint: []<BR>\nPhysical Status: []<BR>\nMental Status: []<BR>",
src.medicalActive1.fields["name"], src.medicalActive1.fields["id"], src.medicalActive1.fields["sex"], src.medicalActive1.fields["age"], src.medicalActive1.fields["fingerprint"], src.medicalActive1.fields["p_stat"], src.medicalActive1.fields["m_stat"])
else
dat += "<pre>Requested medical record not found.</pre><BR>"
if ((istype(src.medicalActive2, /datum/data/record) && data_core.medical.Find(src.medicalActive2)))
dat += text("<BR>\n<CENTER><B>Medical Data</B></CENTER><BR>\nBlood Type: <A href='?src=\ref[];field=blood_type'>[]</A><BR>\nDNA: <A href='?src=\ref[];field=b_dna'>[]</A><BR>\n<BR>\nMinor Disabilities: <A href='?src=\ref[];field=mi_dis'>[]</A><BR>\nDetails: <A href='?src=\ref[];field=mi_dis_d'>[]</A><BR>\n<BR>\nMajor Disabilities: <A href='?src=\ref[];field=ma_dis'>[]</A><BR>\nDetails: <A href='?src=\ref[];field=ma_dis_d'>[]</A><BR>\n<BR>\nAllergies: <A href='?src=\ref[];field=alg'>[]</A><BR>\nDetails: <A href='?src=\ref[];field=alg_d'>[]</A><BR>\n<BR>\nCurrent Diseases: <A href='?src=\ref[];field=cdi'>[]</A> (per disease info placed in log/comment section)<BR>\nDetails: <A href='?src=\ref[];field=cdi_d'>[]</A><BR>\n<BR>\nImportant Notes:<BR>\n\t<A href='?src=\ref[];field=notes'>[]</A><BR>\n<BR>\n<CENTER><B>Comments/Log</B></CENTER><BR>", src, src.medicalActive2.fields["blood_type"], src, src.medicalActive2.fields["b_dna"], src, src.medicalActive2.fields["mi_dis"], src, src.medicalActive2.fields["mi_dis_d"], src, src.medicalActive2.fields["ma_dis"], src, src.medicalActive2.fields["ma_dis_d"], src, src.medicalActive2.fields["alg"], src, src.medicalActive2.fields["alg_d"], src, src.medicalActive2.fields["cdi"], src, src.medicalActive2.fields["cdi_d"], src, src.medicalActive2.fields["notes"])
else
dat += "<pre>Requested medical record not found.</pre><BR>"
dat += text("<BR>\n<A href='?src=\ref[];software=medicalrecord;sub=0'>Back</A><BR>", src)
return dat
switch(subscreen)
if(0)
. += "<h3>Medical Records</h3><HR>"
if(data_core.general)
for(var/datum/data/record/R in sortRecord(data_core.general))
. += "<A href='?src=\ref[src];med_rec=[R.fields["id"]];software=medicalrecord;sub=1'>[R.fields["id"]]: [R.fields["name"]]<BR>"
if(1)
. += "<CENTER><B>Medical Record</B></CENTER><BR>"
if(medicalActive1 in data_core.general)
. += "Name: [medicalActive1.fields["name"]] ID: [medicalActive1.fields["id"]]<BR>\nSex: [medicalActive1.fields["sex"]]<BR>\nAge: [medicalActive1.fields["age"]]<BR>\nFingerprint: [medicalActive1.fields["fingerprint"]]<BR>\nPhysical Status: [medicalActive1.fields["p_stat"]]<BR>\nMental Status: [medicalActive1.fields["m_stat"]]<BR>"
else
. += "<pre>Requested medical record not found.</pre><BR>"
if(medicalActive2 in data_core.medical)
. += "<BR>\n<CENTER><B>Medical Data</B></CENTER><BR>\nBlood Type: <A href='?src=\ref[src];field=blood_type'>[medicalActive2.fields["blood_type"]]</A><BR>\nDNA: <A href='?src=\ref[src];field=b_dna'>[medicalActive2.fields["b_dna"]]</A><BR>\n<BR>\nMinor Disabilities: <A href='?src=\ref[src];field=mi_dis'>[medicalActive2.fields["mi_dis"]]</A><BR>\nDetails: <A href='?src=\ref[src];field=mi_dis_d'>[medicalActive2.fields["mi_dis_d"]]</A><BR>\n<BR>\nMajor Disabilities: <A href='?src=\ref[src];field=ma_dis'>[medicalActive2.fields["ma_dis"]]</A><BR>\nDetails: <A href='?src=\ref[src];field=ma_dis_d'>[medicalActive2.fields["ma_dis_d"]]</A><BR>\n<BR>\nAllergies: <A href='?src=\ref[src];field=alg'>[medicalActive2.fields["alg"]]</A><BR>\nDetails: <A href='?src=\ref[src];field=alg_d'>[medicalActive2.fields["alg_d"]]</A><BR>\n<BR>\nCurrent Diseases: <A href='?src=\ref[src];field=cdi'>[medicalActive2.fields["cdi"]]</A> (per disease info placed in log/comment section)<BR>\nDetails: <A href='?src=\ref[src];field=cdi_d'>[medicalActive2.fields["cdi_d"]]</A><BR>\n<BR>\nImportant Notes:<BR>\n\t<A href='?src=\ref[src];field=notes'>[medicalActive2.fields["notes"]]</A><BR>\n<BR>\n<CENTER><B>Comments/Log</B></CENTER><BR>"
else
. += "<pre>Requested medical record not found.</pre><BR>"
. += "<BR>\n<A href='?src=\ref[src];software=medicalrecord;sub=0'>Back</A><BR>"
return .
// Security Records
/mob/living/silicon/pai/proc/softwareSecurityRecord()
var/dat = ""
if(src.subscreen == 0)
dat += "<h3>Security Records</h3><HR>"
if(!isnull(data_core.general))
for(var/datum/data/record/R in sortRecord(data_core.general))
dat += text("<A href='?src=\ref[];sec_rec=\ref[];software=securityrecord;sub=1'>[]: []<BR>", src, R, R.fields["id"], R.fields["name"])
if(src.subscreen == 1)
dat += "<h3>Security Record</h3>"
if ((istype(src.securityActive1, /datum/data/record) && data_core.general.Find(src.securityActive1)))
dat += text("Name: <A href='?src=\ref[];field=name'>[]</A> ID: <A href='?src=\ref[];field=id'>[]</A><BR>\nSex: <A href='?src=\ref[];field=sex'>[]</A><BR>\nAge: <A href='?src=\ref[];field=age'>[]</A><BR>\nRank: <A href='?src=\ref[];field=rank'>[]</A><BR>\nFingerprint: <A href='?src=\ref[];field=fingerprint'>[]</A><BR>\nPhysical Status: []<BR>\nMental Status: []<BR>", src, src.securityActive1.fields["name"], src, src.securityActive1.fields["id"], src, src.securityActive1.fields["sex"], src, src.securityActive1.fields["age"], src, src.securityActive1.fields["rank"], src, src.securityActive1.fields["fingerprint"], src.securityActive1.fields["p_stat"], src.securityActive1.fields["m_stat"])
else
dat += "<pre>Requested security record not found,</pre><BR>"
if ((istype(src.securityActive2, /datum/data/record) && data_core.security.Find(src.securityActive2)))
dat += text("<BR>\nSecurity Data<BR>\nCriminal Status: []<BR>\n<BR>\nMinor Crimes: <A href='?src=\ref[];field=mi_crim'>[]</A><BR>\nDetails: <A href='?src=\ref[];field=mi_crim_d'>[]</A><BR>\n<BR>\nMajor Crimes: <A href='?src=\ref[];field=ma_crim'>[]</A><BR>\nDetails: <A href='?src=\ref[];field=ma_crim_d'>[]</A><BR>\n<BR>\nImportant Notes:<BR>\n\t<A href='?src=\ref[];field=notes'>[]</A><BR>\n<BR>\n<CENTER><B>Comments/Log</B></CENTER><BR>", src.securityActive2.fields["criminal"], src, src.securityActive2.fields["mi_crim"], src, src.securityActive2.fields["mi_crim_d"], src, src.securityActive2.fields["ma_crim"], src, src.securityActive2.fields["ma_crim_d"], src, src.securityActive2.fields["notes"])
else
dat += "<pre>Requested security record not found,</pre><BR>"
dat += text("<BR>\n<A href='?src=\ref[];software=securityrecord;sub=0'>Back</A><BR>", src)
return dat
. = ""
switch(subscreen)
if(0)
. += "<h3>Security Records</h3><HR>"
if(data_core.general)
for(var/datum/data/record/R in sortRecord(data_core.general))
. += "<A href='?src=\ref[src];sec_rec=[R.fields["id"]];software=securityrecord;sub=1'>[R.fields["id"]]: [R.fields["name"]]<BR>"
if(1)
. += "<h3>Security Record</h3>"
if(securityActive1 in data_core.general)
. += "Name: <A href='?src=\ref[src];field=name'>[securityActive1.fields["name"]]</A> ID: <A href='?src=\ref[src];field=id'>[securityActive1.fields["id"]]</A><BR>\nSex: <A href='?src=\ref[src];field=sex'>[securityActive1.fields["sex"]]</A><BR>\nAge: <A href='?src=\ref[src];field=age'>[securityActive1.fields["age"]]</A><BR>\nRank: <A href='?src=\ref[src];field=rank'>[securityActive1.fields["rank"]]</A><BR>\nFingerprint: <A href='?src=\ref[src];field=fingerprint'>[securityActive1.fields["fingerprint"]]</A><BR>\nPhysical Status: [securityActive1.fields["p_stat"]]<BR>\nMental Status: [securityActive1.fields["m_stat"]]<BR>"
else
. += "<pre>Requested security record not found,</pre><BR>"
if(securityActive2 in data_core.security)
. += "<BR>\nSecurity Data<BR>\nCriminal Status: [securityActive2.fields["criminal"]]<BR>\n<BR>\nMinor Crimes: <A href='?src=\ref[src];field=mi_crim'>[securityActive2.fields["mi_crim"]]</A><BR>\nDetails: <A href='?src=\ref[src];field=mi_crim_d'>[securityActive2.fields["mi_crim_d"]]</A><BR>\n<BR>\nMajor Crimes: <A href='?src=\ref[src];field=ma_crim'>[securityActive2.fields["ma_crim"]]</A><BR>\nDetails: <A href='?src=\ref[src];field=ma_crim_d'>[securityActive2.fields["ma_crim_d"]]</A><BR>\n<BR>\nImportant Notes:<BR>\n\t<A href='?src=\ref[src];field=notes'>[securityActive2.fields["notes"]]</A><BR>\n<BR>\n<CENTER><B>Comments/Log</B></CENTER><BR>"
else
. += "<pre>Requested security record not found,</pre><BR>"
. += text("<BR>\n<A href='?src=\ref[];software=securityrecord;sub=0'>Back</A><BR>", src)
return .
// Universal Translator
/mob/living/silicon/pai/proc/softwareTranslator()
var/dat = {"<h3>Universal Translator</h3><br>
. = {"<h3>Universal Translator</h3><br>
When enabled, this device will automatically convert all spoken and written language into a format that any known recipient can understand.<br><br>
The device is currently [ (src.universal_speak) ? "<font color=#55FF55>en" : "<font color=#FF5555>dis" ]abled.</font><br>
<a href='byond://?src=\ref[src];software=translator;sub=0;toggle=1'>Toggle Device</a><br>
"}
return dat
return .
// Security HUD
/mob/living/silicon/pai/proc/facialRecognition()