diff --git a/code/_globalvars/lists/poll_ignore.dm b/code/_globalvars/lists/poll_ignore.dm
index 9b998e02b16..330174f266e 100644
--- a/code/_globalvars/lists/poll_ignore.dm
+++ b/code/_globalvars/lists/poll_ignore.dm
@@ -20,7 +20,8 @@
#define POLL_IGNORE_SPLITPERSONALITY "split_personality"
#define POLL_IGNORE_CONTRACTOR_SUPPORT "contractor_support"
#define POLL_IGNORE_ACADEMY_WIZARD "academy_wizard"
-#define POLL_IGNORE_CORTICAL_BORER "cortical_borer" //Skyrat Edit Start: Cortical Borer
+#define POLL_IGNORE_CORTICAL_BORER "cortical_borer" // SKYRAT EDIT - Cortical Borer
+#define POLL_IGNORE_PAI "pai"
//Skyrat Edit Start: Cortical Borer
GLOBAL_LIST_INIT(poll_ignore_desc, list(
@@ -45,6 +46,7 @@ GLOBAL_LIST_INIT(poll_ignore_desc, list(
POLL_IGNORE_CONTRACTOR_SUPPORT = "Contractor Support Unit",
POLL_IGNORE_ACADEMY_WIZARD = "Academy Wizard Defender",
POLL_IGNORE_CORTICAL_BORER = "Cortical Borer",
+ POLL_IGNORE_PAI = "Personal AI"
))
//Skyrat Edit Stop: Cortical Borer
GLOBAL_LIST_INIT(poll_ignore, init_poll_ignore())
diff --git a/code/_onclick/hud/pai.dm b/code/_onclick/hud/pai.dm
index c779e81f618..79411660a47 100644
--- a/code/_onclick/hud/pai.dm
+++ b/code/_onclick/hud/pai.dm
@@ -21,7 +21,7 @@
if(!..())
return
var/mob/living/silicon/pai/pAI = usr
- pAI.paiInterface()
+ pAI.ui_interact(pAI)
/atom/movable/screen/pai/shell
name = "Toggle Holoform"
diff --git a/code/controllers/subsystem/pai.dm b/code/controllers/subsystem/pai.dm
index 703405f3331..3171a27ebbf 100644
--- a/code/controllers/subsystem/pai.dm
+++ b/code/controllers/subsystem/pai.dm
@@ -4,195 +4,157 @@ SUBSYSTEM_DEF(pai)
flags = SS_NO_INIT|SS_NO_FIRE
var/list/candidates = list()
- var/ghost_spam = FALSE
- var/spam_delay = 100
+ var/request_spam = FALSE
+ var/submit_spam = FALSE
var/list/pai_card_list = list()
-/datum/controller/subsystem/pai/Topic(href, href_list[])
- if(href_list["download"])
- var/datum/pai_candidate/candidate = locate(href_list["candidate"]) in candidates
- var/obj/item/paicard/card = locate(href_list["device"]) in pai_card_list
- if(card.pai)
- return
- if(istype(card, /obj/item/paicard) && istype(candidate, /datum/pai_candidate))
- if(check_ready(candidate) != candidate)
- return FALSE
- var/mob/living/silicon/pai/pai = new(card)
- if(!candidate.name)
- pai.name = pick(GLOB.ninja_names)
- else
- pai.name = candidate.name
- pai.real_name = pai.name
- pai.key = candidate.key
+/datum/pai_candidate
+ var/comments
+ var/description
+ var/key
+ var/name
+ var/ready = FALSE
- card.setPersonality(pai)
-
- candidates -= candidate
- usr << browse(null, "window=findPai")
-
- if(href_list["new"])
- var/datum/pai_candidate/candidate = locate(href_list["candidate"]) in candidates
- var/option = href_list["option"]
- var/t = ""
-
- switch(option)
- if("name")
- t = sanitize_name(stripped_input(usr, "Enter a name for your pAI", "pAI Name", candidate.name, MAX_NAME_LEN),allow_numbers = TRUE)
- if(t)
- candidate.name = t
- if("desc")
- t = stripped_multiline_input(usr, "Enter a description for your pAI", "pAI Description", candidate.description, MAX_MESSAGE_LEN)
- if(t)
- candidate.description = t
- if("role")
- t = stripped_input(usr, "Enter a role for your pAI", "pAI Role", candidate.role, MAX_MESSAGE_LEN)
- if(t)
- candidate.role = t
- if("ooc")
- t = stripped_multiline_input(usr, "Enter any OOC comments", "pAI OOC Comments", candidate.comments, MAX_MESSAGE_LEN)
- if(t)
- candidate.comments = t
- if("save")
- candidate.savefile_save(usr)
- if("load")
- candidate.savefile_load(usr)
- //In case people have saved unsanitized stuff.
- if(candidate.name)
- candidate.name = copytext_char(candidate.name,1,MAX_NAME_LEN)
- if(candidate.description)
- candidate.description = copytext_char(candidate.description,1,MAX_MESSAGE_LEN)
- if(candidate.role)
- candidate.role = copytext_char(candidate.role,1,MAX_MESSAGE_LEN)
- if(candidate.comments)
- candidate.comments = copytext_char(candidate.comments,1,MAX_MESSAGE_LEN)
-
- if("submit")
- if(candidate)
- candidate.ready = TRUE
- for(var/obj/item/paicard/p in pai_card_list)
- if(!p.pai)
- p.alertUpdate()
- usr << browse(null, "window=paiRecruit")
- return
- recruitWindow(usr)
-
-/datum/controller/subsystem/pai/proc/recruitWindow(mob/M)
- var/datum/pai_candidate/candidate
- for(var/datum/pai_candidate/c in candidates)
- if(c.key == M.key)
- candidate = c
- if(!candidate)
- candidate = new /datum/pai_candidate()
- candidate.key = M.key
- candidates.Add(candidate)
-
-
- var/dat = ""
- dat += {"
-
- "}
-
- dat += "
Please configure your pAI personality's options. Remember, what you enter here could determine whether or not the user requesting a personality chooses you!
"
- dat += ""
- dat += "| Name: | [candidate.name] |
"
- dat += "| \[Edit\] | What you plan to call yourself. Suggestions: Any character name you would choose for a station character OR an AI. |
"
-
- dat += "| Description: | [candidate.description] |
"
- dat += "| \[Edit\] | What sort of pAI you typically play; your mannerisms, your quirks, etc. This can be as sparse or as detailed as you like. |
"
-
- dat += "| Preferred Role: | [candidate.role] |
"
- dat += "| \[Edit\] | Do you like to partner with sneaky social ninjas? Like to help security hunt down thugs? Enjoy watching an engineer's back while he saves the station yet again? This doesn't have to be limited to just station jobs. Pretty much any general descriptor for what you'd like to be doing works here. |
"
-
- dat += "| OOC Comments: | [candidate.comments] |
"
- dat += "| \[Edit\] | Anything you'd like to address specifically to the player reading this in an OOC manner. \"I prefer more serious RP.\", \"I'm still learning the interface!\", etc. Feel free to leave this blank if you want. |
"
-
- dat += "
"
-
- dat += "
"
- dat += "
"
- dat += "Save Personality
"
- dat += "Load Personality
"
-
- M << browse(dat, "window=paiRecruit")
-
-/datum/controller/subsystem/pai/proc/spam_again()
- ghost_spam = FALSE
-
-/datum/controller/subsystem/pai/proc/check_ready(datum/pai_candidate/C)
- if(!C.ready)
- return FALSE
- for(var/mob/dead/observer/O in GLOB.player_list)
- if(O.key == C.key)
- return C
- return FALSE
-
-/datum/controller/subsystem/pai/proc/findPAI(obj/item/paicard/p, mob/user)
+/**
+ * Pings ghosts to announce that someone is requesting a pAI
+ *
+ * Arguments
+ * @pai - The card requesting assistance
+ * @user - The player requesting a pAI
+*/
+/datum/controller/subsystem/pai/proc/findPAI(obj/item/paicard/pai, mob/user)
if(!(GLOB.ghost_role_flags & GHOSTROLE_SILICONS))
to_chat(user, span_warning("Due to growing incidents of SELF corrupted independent artificial intelligences, freeform personality devices have been temporarily banned in this sector."))
return
- if(!ghost_spam)
- ghost_spam = TRUE
- for(var/mob/dead/observer/G in GLOB.player_list)
- if(!G.key)
- continue
- if(!(ROLE_PAI in G.client.prefs.be_special))
- continue
- to_chat(G, span_ghostalert("[user] is requesting a pAI personality! Use the pAI button to submit yourself as one."))
- addtimer(CALLBACK(src, .proc/spam_again), spam_delay)
- var/list/available = list()
- for(var/datum/pai_candidate/c in SSpai.candidates)
- available.Add(check_ready(c))
- var/dat = ""
+ if(request_spam)
+ to_chat(user, span_warning("Request sent too recently."))
+ return
+ request_spam = TRUE
+ playsound(src, 'sound/machines/ping.ogg', 20, TRUE)
+ to_chat(user, span_notice("You have requested pAI assistance."))
+ var/mutable_appearance/alert_overlay = mutable_appearance('icons/obj/aicards.dmi', "pai")
+ notify_ghosts("[user] is requesting a pAI personality! Use the pAI button to submit yourself as one.", source=user, alert_overlay = alert_overlay, action=NOTIFY_ORBIT, header="pAI Request!", ignore_key = POLL_IGNORE_PAI)
+ addtimer(CALLBACK(src, .proc/request_again), 10 SECONDS)
+ return TRUE
- dat += {"
-
- "}
- dat += "Requesting AI personalities from central database... If there are no entries, or if a suitable entry is not listed, check again later as more personalities may be added.
"
+/// Opens the TGUI window
+/datum/controller/subsystem/pai/ui_interact(mob/user, datum/tgui/ui)
+ . = ..()
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "PaiSubmit")
+ ui.open()
- dat += ""
+/// The data sent to the window.
+/datum/controller/subsystem/pai/ui_static_data(mob/user)
+ . = ..()
+ var/list/data = list()
+ /// The matching candidate from search
+ var/datum/pai_candidate/candidate = check_candidate(user)
+ if(isnull(candidate))
+ return data
+ data["comments"] = candidate.comments
+ data["description"] = candidate.description
+ data["name"] = candidate.name
+ return data
- for(var/datum/pai_candidate/c in available)
- dat += "| Name: | [c.name] |
"
- dat += "| Description: | [c.description] |
"
- dat += "| Preferred Role: | [c.role] |
"
- dat += "| OOC Comments: | [c.comments] |
"
- dat += "| \[Download [c.name]\] | |
"
+/// Actions sent by TGUI
+/datum/controller/subsystem/pai/ui_act(action, list/params, datum/tgui/ui)
+ . = ..()
+ if(.)
+ return
+ /// The matching candidate from search
+ var/datum/pai_candidate/candidate = check_candidate(usr)
+ if(isnull(candidate))
+ return FALSE
+ switch(action)
+ if("submit")
+ candidate.comments = params["candidate"]["comments"]
+ candidate.description = params["candidate"]["description"]
+ candidate.name = params["candidate"]["name"]
+ candidate.ready = TRUE
+ ui.close()
+ submit_alert()
+ if("save")
+ candidate.comments = params["candidate"]["comments"]
+ candidate.description = params["candidate"]["description"]
+ candidate.name = params["candidate"]["name"]
+ candidate.savefile_save(usr)
+ to_chat(usr, span_boldnotice("You have saved pAI information locally."))
+ if("load")
+ candidate.savefile_load(usr)
+ //In case people have saved unsanitized stuff.
+ if(candidate.comments)
+ candidate.comments = copytext_char(candidate.comments,1,MAX_MESSAGE_LEN)
+ if(candidate.description)
+ candidate.description = copytext_char(candidate.description,1,MAX_MESSAGE_LEN)
+ if(candidate.name)
+ candidate.name = copytext_char(candidate.name,1,MAX_NAME_LEN)
+ ui.send_full_update()
+ return
- dat += "
"
+/**
+ * Finds the candidate in question from the list of candidates.
+ */
+/datum/controller/subsystem/pai/proc/check_candidate(mob/user)
+ /// Finds a matching candidate.
+ var/datum/pai_candidate/candidate
+ for(var/datum/pai_candidate/checked_candidate as anything in candidates)
+ if(checked_candidate.key == user.key)
+ candidate = checked_candidate
+ return candidate
+ return null
- user << browse(dat, "window=findPai")
+/**
+ * Pings all pAI cards on the station that new candidates are available.
+ */
+/datum/controller/subsystem/pai/proc/submit_alert()
+ if(submit_spam)
+ to_chat(usr, span_warning("Your candidacy has been submitted, but pAI cards have been alerted too recently."))
+ return FALSE
+ submit_spam = TRUE
+ for(var/obj/item/paicard/paicard in pai_card_list)
+ if(!paicard.pai)
+ paicard.alertUpdate()
+ to_chat(usr, span_notice("Your pAI candidacy has been submitted!"))
+ addtimer(CALLBACK(src, .proc/submit_again), 10 SECONDS)
+ return TRUE
+
+/datum/controller/subsystem/pai/proc/request_again()
+ request_spam = FALSE
+
+/datum/controller/subsystem/pai/proc/submit_again()
+ submit_spam = FALSE
+
+/**
+ * Checks if a candidate is ready so that they may be displayed in the pAI
+ * card's candidate window
+ */
+/datum/controller/subsystem/pai/proc/check_ready(datum/pai_candidate/candidate)
+ if(!candidate.ready)
+ return FALSE
+ for(var/mob/dead/observer/observer in GLOB.player_list)
+ if(observer.key == candidate.key)
+ return candidate
+ return FALSE
-/datum/pai_candidate
- var/name
- var/key
- var/description
- var/role
- var/comments
- var/ready = FALSE
diff --git a/code/datums/datacore.dm b/code/datums/datacore.dm
index 6665e967931..fac1cf6a5a7 100644
--- a/code/datums/datacore.dm
+++ b/code/datums/datacore.dm
@@ -189,7 +189,6 @@ GLOBAL_DATUM_INIT(data_core, /datum/datacore, new)
return manifest_out
-
/datum/datacore/proc/get_manifest_html(monochrome = FALSE)
var/list/manifest = get_manifest()
var/dat = {"
@@ -342,6 +341,51 @@ GLOBAL_DATUM_INIT(data_core, /datum/datacore, new)
locked += L
return
+/**
+ * Supporing proc for getting general records
+ * and using them as pAI ui data. This gets
+ * medical information - or what I would deem
+ * medical information - and sends it as a list.
+ *
+ * @return - list(general_records_out)
+ */
+/datum/datacore/proc/get_general_records()
+ if(!GLOB.data_core.general)
+ return list()
+ /// The array of records
+ var/list/general_records_out = list()
+ for(var/datum/data/record/gen_record as anything in GLOB.data_core.general)
+ /// The object containing the crew info
+ var/list/crew_record = list()
+ crew_record["ref"] = REF(gen_record)
+ crew_record["name"] = gen_record.fields["name"]
+ crew_record["physical_health"] = gen_record.fields["p_stat"]
+ crew_record["mental_health"] = gen_record.fields["m_stat"]
+ general_records_out += list(crew_record)
+ return general_records_out
+
+/**
+ * Supporing proc for getting secrurity records
+ * and using them as pAI ui data. Sends it as a
+ * list.
+ *
+ * @return - list(security_records_out)
+ */
+/datum/datacore/proc/get_security_records()
+ if(!GLOB.data_core.security)
+ return list()
+ /// The array of records
+ var/list/security_records_out = list()
+ for(var/datum/data/record/sec_record as anything in GLOB.data_core.security)
+ /// The object containing the crew info
+ var/list/crew_record = list()
+ crew_record["ref"] = REF(sec_record)
+ crew_record["name"] = sec_record.fields["name"]
+ crew_record["status"] = sec_record.fields["criminal"] // wanted status
+ crew_record["crimes"] = length(sec_record.fields["crim"])
+ security_records_out += list(crew_record)
+ return security_records_out
+
/datum/datacore/proc/get_id_photo(mob/living/carbon/human/H, client/C, show_directions = list(SOUTH))
var/datum/job/J = H.mind.assigned_role
var/datum/preferences/P
diff --git a/code/game/objects/items/devices/paicard.dm b/code/game/objects/items/devices/paicard.dm
index 5b4db5b7077..efc25807657 100644
--- a/code/game/objects/items/devices/paicard.dm
+++ b/code/game/objects/items/devices/paicard.dm
@@ -9,7 +9,8 @@
w_class = WEIGHT_CLASS_SMALL
slot_flags = ITEM_SLOT_BELT
custom_premium_price = PAYCHECK_HARD * 1.25
- var/alert_cooldown ///don't spam alart messages.
+ ///don't spam alert messages.
+ var/alert_cooldown
var/mob/living/silicon/pai/pai
var/emotion_icon = "off" ///what emotion icon we have. handled in /mob/living/silicon/pai/Topic()
resistance_flags = FIRE_PROOF | ACID_PROOF | INDESTRUCTIBLE
@@ -52,75 +53,97 @@
if (!in_range(src, user))
return
user.set_machine(src)
- var/dat = "Personal AI Device
"
- if(pai)
- if(!pai.master_dna || !pai.master)
- dat += "Imprint Master DNA
"
- dat += "Installed Personality: [pai.name]
"
- dat += "Prime directive:
[pai.laws.zeroth]
"
- for(var/slaws in pai.laws.supplied)
- dat += "Additional directives:
[slaws]
"
- dat += "Configure Directives
"
- dat += "
"
- dat += "Device Settings
"
- if(pai.radio)
- dat += "Radio Uplink
"
- dat += "Transmit: \[[pai.can_transmit? "Disable" : "Enable"] Radio Transmission\]
"
- dat += "Receive: \[[pai.can_receive? "Disable" : "Enable"] Radio Reception\]
"
- else
- dat += "Radio Uplink
"
- dat += "Radio firmware not loaded. Please install a pAI personality to load firmware.
"
- if(ishuman(user))
- var/mob/living/carbon/human/H = user
- if(H.real_name == pai.master || H.dna.unique_enzymes == pai.master_dna)
- dat += "\[[pai.canholo? "Disable" : "Enable"] holomatrix projectors\]
"
- dat += "\[Reset speech synthesis module\]
"
- dat += "\[Wipe current pAI personality\]
"
- else
- dat += "No personality installed.
"
- dat += "Searching for a personality... Press view available personalities to notify potential candidates."
- dat += "\[View available personalities\]
"
- user << browse(dat, "window=paicard")
- onclose(user, "paicard")
- return
+ ui_interact(user)
-/obj/item/paicard/Topic(href, href_list)
+/obj/item/paicard/ui_interact(mob/user, datum/tgui/ui)
+ . = ..()
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "PaiCard")
+ ui.open()
- if(!usr || usr.stat)
- return
+/obj/item/paicard/ui_state(mob/user)
+ return GLOB.inventory_state
- if(href_list["request"])
- SSpai.findPAI(src, usr)
+/obj/item/paicard/ui_data(mob/user)
+ . = ..()
+ var/list/data = list()
+ data["candidates"] = list()
+ if(!pai)
+ data["candidates"] = pool_candidates()
+ data["pai"] = null
+ return data
+ data["pai"] = list()
+ data["pai"]["can_holo"] = pai.canholo
+ data["pai"]["dna"] = pai.master_dna
+ data["pai"]["emagged"] = pai.emagged
+ data["pai"]["laws"] = pai.laws.supplied
+ data["pai"]["master"] = pai.master
+ data["pai"]["name"] = pai.name
+ data["pai"]["transmit"] = pai.can_transmit
+ data["pai"]["receive"] = pai.can_receive
+ return data
- if(pai)
- if(!(loc == usr))
- return
- if(href_list["setdna"])
+/obj/item/paicard/ui_act(action, list/params)
+ . = ..()
+ if(.)
+ return FALSE
+ switch(action)
+ if("download")
+ /// The individual candidate to download
+ var/datum/pai_candidate/candidate
+ for(var/datum/pai_candidate/checked_candidate as anything in SSpai.candidates)
+ if(params["key"] == checked_candidate.key)
+ candidate = checked_candidate
+ break
+ if(isnull(candidate))
+ return FALSE
+ if(src.pai)
+ return FALSE
+ if(SSpai.check_ready(candidate) != candidate)
+ return FALSE
+ /// The newly downloaded pAI personality
+ var/mob/living/silicon/pai/pai = new(src)
+ pai.name = candidate.name || pick(GLOB.ninja_names)
+ pai.real_name = pai.name
+ pai.key = candidate.key
+ src.setPersonality(pai)
+ SSpai.candidates -= candidate
+ if("fix_speech")
+ to_chat(pai, span_notice("Your owner has corrected your speech modulation!"))
+ to_chat(usr, span_notice("You fix the pAI's speech modulator."))
+ pai.stuttering = 0
+ pai.slurring = 0
+ pai.derpspeech = 0
+ if("request")
+ if(!pai)
+ SSpai.findPAI(src, usr)
+ if("set_dna")
if(pai.master_dna)
return
if(!iscarbon(usr))
to_chat(usr, span_warning("You don't have any DNA, or your DNA is incompatible with this device!"))
else
- var/mob/living/carbon/M = usr
- pai.master = M.real_name
- pai.master_dna = M.dna.unique_enzymes
+ var/mob/living/carbon/master = usr
+ pai.master = master.real_name
+ pai.master_dna = master.dna.unique_enzymes
to_chat(pai, span_notice("You have been bound to a new master."))
pai.emittersemicd = FALSE
- if(href_list["wipe"])
- var/confirm = tgui_alert(usr, "Are you CERTAIN you wish to delete the current personality? This action cannot be undone.", "Personality Wipe", list("Yes", "No"))
- if(confirm == "Yes")
- if(pai)
- to_chat(pai, span_warning("You feel yourself slipping away from reality."))
- to_chat(pai, span_danger("Byte by byte you lose your sense of self."))
- to_chat(pai, span_userdanger("Your mental faculties leave you."))
- to_chat(pai, span_rose("oblivion... "))
- qdel(pai)
- if(href_list["fix_speech"])
- pai.stuttering = 0
- pai.slurring = 0
- pai.derpspeech = 0
- if(href_list["toggle_transmit"] || href_list["toggle_receive"])
- var/transmitting = href_list["toggle_transmit"] //it can't be both so if we know it's not transmitting it must be receiving.
+ if("set_laws")
+ var/newlaws = stripped_multiline_input(usr, "Enter any additional directives you would like your pAI personality to follow. Note that these directives will not override the personality's allegiance to its imprinted master. Conflicting directives will be ignored.", "pAI Directive Configuration", pai.laws.supplied[1], MAX_MESSAGE_LEN)
+ if(newlaws && pai)
+ pai.add_supplied_law(0,newlaws)
+ if("toggle_holo")
+ if(pai.canholo)
+ to_chat(pai, span_warning("Your owner has disabled your holomatrix projectors!"))
+ pai.canholo = FALSE
+ to_chat(usr, span_notice("You disable your pAI's holomatrix!"))
+ else
+ to_chat(pai, span_notice("Your owner has enabled your holomatrix projectors!"))
+ pai.canholo = TRUE
+ to_chat(usr, span_notice("You enable your pAI's holomatrix!"))
+ if("toggle_radio")
+ var/transmitting = params["option"] == "transmit" //it can't be both so if we know it's not transmitting it must be receiving.
var/transmit_holder = (transmitting ? WIRE_TX : WIRE_RX)
if(transmitting)
pai.can_transmit = !pai.can_transmit
@@ -128,23 +151,18 @@
pai.can_receive = !pai.can_receive
pai.radio.wires.cut(transmit_holder)//wires.cut toggles cut and uncut states
transmit_holder = (transmitting ? pai.can_transmit : pai.can_receive) //recycling can be fun!
- to_chat(usr,span_warning("You [transmit_holder ? "enable" : "disable"] your pAI's [transmitting ? "outgoing" : "incoming"] radio transmissions!"))
- to_chat(pai,span_warning("Your owner has [transmit_holder ? "enabled" : "disabled"] your [transmitting ? "outgoing" : "incoming"] radio transmissions!"))
- if(href_list["setlaws"])
- var/newlaws = stripped_multiline_input(usr, "Enter any additional directives you would like your pAI personality to follow. Note that these directives will not override the personality's allegiance to its imprinted master. Conflicting directives will be ignored.", "pAI Directive Configuration", pai.laws.supplied[1], MAX_MESSAGE_LEN)
- if(newlaws && pai)
- pai.add_supplied_law(0,newlaws)
- if(href_list["toggle_holo"])
- if(pai.canholo)
- to_chat(pai, span_userdanger("Your owner has disabled your holomatrix projectors!"))
- pai.canholo = FALSE
- to_chat(usr, span_warning("You disable your pAI's holomatrix!"))
- else
- to_chat(pai, span_boldnotice("Your owner has enabled your holomatrix projectors!"))
- pai.canholo = TRUE
- to_chat(usr, span_notice("You enable your pAI's holomatrix!"))
-
- attack_self(usr)
+ to_chat(usr, span_notice("You [transmit_holder ? "enable" : "disable"] your pAI's [transmitting ? "outgoing" : "incoming"] radio transmissions!"))
+ to_chat(pai, span_notice("Your owner has [transmit_holder ? "enabled" : "disabled"] your [transmitting ? "outgoing" : "incoming"] radio transmissions!"))
+ if("wipe_pai")
+ var/confirm = tgui_alert(usr, "Are you certain you wish to delete the current personality? This action cannot be undone.", "Personality Wipe", list("Yes", "No"))
+ if(confirm == "Yes")
+ if(pai)
+ to_chat(pai, span_warning("You feel yourself slipping away from reality."))
+ to_chat(pai, span_danger("Byte by byte you lose your sense of self."))
+ to_chat(pai, span_userdanger("Your mental faculties leave you."))
+ to_chat(pai, span_rose("oblivion... "))
+ qdel(pai)
+ return
// WIRE_SIGNAL = 1
// WIRE_RECEIVE = 2
@@ -173,3 +191,25 @@
if(pai && !pai.holoform)
pai.emp_act(severity)
+/**
+ * Gathers a list of candidates to display in the download candidate
+ * window. If the candidate isn't marked ready, ie they have not
+ * pressed submit, they will be skipped over.
+ *
+ * @return - An array of candidate objects.
+ */
+/obj/item/paicard/proc/pool_candidates()
+ /// Array of pAI candidates
+ var/list/candidates = list()
+ if(length(SSpai.candidates))
+ for(var/datum/pai_candidate/checked_candidate as anything in SSpai.candidates)
+ if(!checked_candidate.ready)
+ continue
+ /// The object containing the candidate data.
+ var/list/candidate = list()
+ candidate["comments"] = checked_candidate.comments
+ candidate["description"] = checked_candidate.description
+ candidate["key"] = checked_candidate.key
+ candidate["name"] = checked_candidate.name
+ candidates += list(candidate)
+ return candidates
diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm
index 0b7cc2b92d4..920cf9e8c19 100644
--- a/code/modules/mob/living/silicon/pai/pai.dm
+++ b/code/modules/mob/living/silicon/pai/pai.dm
@@ -24,47 +24,54 @@
mobility_flags = MOBILITY_FLAGS_REST_CAPABLE_DEFAULT
var/network = "ss13"
var/obj/machinery/camera/current = null
-
- var/ram = 100 // Used as currency to purchase different abilities
+ /// Used as currency to purchase different abilities
+ var/ram = 100
+ /// Installed software on the pAI
var/list/software = list()
- var/userDNA // The DNA string of our assigned user
- var/obj/item/paicard/card // The card we inhabit
- var/hacking = FALSE //Are we hacking a door?
+ /// current user's DNA
+ var/userDNA
+ /// The card we inhabit
+ var/obj/item/paicard/card
+ /// Are we hacking a door?
+ var/hacking = FALSE
+ /// Changes the display to syndi if true
+ var/emagged = FALSE
var/speakStatement = "states"
var/speakExclamation = "declares"
var/speakDoubleExclamation = "alarms"
var/speakQuery = "queries"
-
- var/obj/item/pai_cable/hacking_cable // The cable we produce when hacking a door
-
- var/master // Name of the one who commands us
- var/master_dna // DNA string for owner verification
+ /// The cable we produce when hacking a door
+ var/obj/item/pai_cable/hacking_cable
+ /// Name of the one who commands us
+ var/master
+ /// DNA string for owner verification
+ var/master_dna
// Various software-specific vars
- var/temp // General error reporting text contained here will typically be shown once and cleared
- var/screen // Which screen our main window displays
- var/subscreen // Which specific function of the main screen is being displayed
-
- var/secHUD = 0 // Toggles whether the Security HUD is active or not
- var/medHUD = 0 // Toggles whether the Medical HUD is active or not
-
- var/datum/data/record/medicalActive1 // Datacore record declarations for record software
- var/datum/data/record/medicalActive2
-
- var/datum/data/record/securityActive1 // Could probably just combine all these into one
- var/datum/data/record/securityActive2
-
- var/obj/machinery/door/hackdoor // The airlock being hacked
- var/hackprogress = 0 // Possible values: 0 - 100, >= 100 means the hack is complete and will be reset upon next check
-
- var/obj/item/assembly/signaler/internal/signaler // AI's signaler
+ /// Toggles whether the Security HUD is active or not
+ var/secHUD = FALSE
+ /// Toggles whether the Medical HUD is active or not
+ var/medHUD = FALSE
+ /// Toggles whether universal translator has been activated. Cannot be reversed
+ var/languages_granted = FALSE
+ /// The airlock being hacked
+ var/obj/machinery/door/hackdoor
+ /// Possible values: 0 - 100, >= 100 means the hack is complete and will be reset upon next check
+ var/hackprogress = 0
+ // Software
+ /// Atmospheric analyzer
+ var/obj/item/analyzer/atmos_analyzer
+ /// AI's signaler
+ var/obj/item/assembly/signaler/internal/signaler
+ /// Synthesizer
var/obj/item/instrument/piano_synth/internal_instrument
- var/obj/machinery/newscaster //pAI Newscaster
- var/obj/item/healthanalyzer/hostscan //pAI healthanalyzer
-
+ /// pAI Newscaster
+ var/obj/machinery/newscaster
+ /// pAI healthanalyzer
+ var/obj/item/healthanalyzer/hostscan
/// Internal pAI GPS, enabled if pAI downloads GPS software, and then uses it.
var/obj/item/gps/pai/internal_gps = null
@@ -97,6 +104,8 @@
hacking_cable = null
if(!QDELETED(card))
card.update_appearance()
+ if(A == atmos_analyzer)
+ atmos_analyzer = null
if(A == internal_instrument)
internal_instrument = null
if(A == newscaster)
@@ -110,6 +119,7 @@
return ..()
/mob/living/silicon/pai/Destroy()
+ QDEL_NULL(atmos_analyzer)
QDEL_NULL(internal_instrument)
QDEL_NULL(hacking_cable)
QDEL_NULL(newscaster)
@@ -136,6 +146,7 @@
forceMove(P)
card = P
job = "Personal AI"
+ atmos_analyzer = new /obj/item/analyzer(src)
signaler = new /obj/item/assembly/signaler/internal(src)
hostscan = new /obj/item/healthanalyzer(src)
newscaster = new /obj/machinery/newscaster(src)
@@ -163,12 +174,10 @@
aiPDA.name = real_name + " (" + aiPDA.ownjob + ")"
/mob/living/silicon/pai/proc/process_hack(delta_time, times_fired)
-
-
if(hacking_cable && hacking_cable.machine && istype(hacking_cable.machine, /obj/machinery/door) && hacking_cable.machine == hackdoor && get_dist(src, hackdoor) <= 1)
hackprogress = clamp(hackprogress + (2 * delta_time), 0, 100)
else
- temp = "Door Jack: Connection to airlock has been lost. Hack aborted."
+ to_chat(src, span_notice("Door Jack: Connection to airlock has been lost. Hack aborted."))
hackprogress = 0
hacking = FALSE
hackdoor = null
@@ -176,13 +185,12 @@
if(!QDELETED(card))
card.update_appearance()
return
- if(screen == "doorjack" && subscreen == 0) // Update our view, if appropriate
- paiInterface()
if(hackprogress >= 100)
hackprogress = 0
var/obj/machinery/door/D = hacking_cable.machine
D.open()
hacking = FALSE
+ QDEL_NULL(hacking_cable)
/mob/living/silicon/pai/make_laws()
laws = new /datum/ai_laws/pai()
@@ -238,7 +246,7 @@
/datum/action/innate/pai/software/Trigger()
..()
- P.paiInterface()
+ P.ui_act()
/datum/action/innate/pai/shell
name = "Toggle Holoform"
@@ -340,6 +348,7 @@
to_chat(user, span_notice("You override [pai]'s directive system, clearing its master string and supplied directive."))
to_chat(pai, span_userdanger("Warning: System override detected, check directive sub-system for any changes."))
log_game("[key_name(user)] emagged [key_name(pai)], wiping their master DNA and supplemental directive.")
+ pai.emagged = TRUE
pai.master = null
pai.master_dna = null
pai.laws.supplied[1] = "None." // Sets supplemental directive to this
diff --git a/code/modules/mob/living/silicon/pai/personality.dm b/code/modules/mob/living/silicon/pai/personality.dm
index 9b866d1560e..57f248cba48 100644
--- a/code/modules/mob/living/silicon/pai/personality.dm
+++ b/code/modules/mob/living/silicon/pai/personality.dm
@@ -19,7 +19,6 @@
WRITE_FILE(F["name"], name)
WRITE_FILE(F["description"], description)
- WRITE_FILE(F["role"], role)
WRITE_FILE(F["comments"], comments)
WRITE_FILE(F["version"], 1)
@@ -56,6 +55,5 @@
F["name"] >> src.name
F["description"] >> src.description
- F["role"] >> src.role
F["comments"] >> src.comments
return 1
diff --git a/code/modules/mob/living/silicon/pai/software.dm b/code/modules/mob/living/silicon/pai/software.dm
index f2824a61d18..3a52930f388 100644
--- a/code/modules/mob/living/silicon/pai/software.dm
+++ b/code/modules/mob/living/silicon/pai/software.dm
@@ -1,8 +1,3 @@
-// TODO:
-// - Potentially roll HUDs and Records into one
-// - Shock collar/lock system for prisoner pAIs?
-
-
/mob/living/silicon/pai/var/list/available_software = list(
//Nightvision
//T-Ray
@@ -28,558 +23,265 @@
"internal gps" = 35,
"universal translator" = 35
)
+/// Bool that determines if the pAI can refresh medical/security records.
+/mob/living/silicon/pai/var/refresh_spam = FALSE
+/// Cached list for medical records to send as static data
+/mob/living/silicon/pai/var/list/medical_records = list()
+/// Cached list for security records to send as static data
+/mob/living/silicon/pai/var/list/security_records = list()
-/mob/living/silicon/pai/proc/paiInterface()
- var/dat = ""
- var/left_part = ""
- var/right_part = softwareMenu()
- set_machine(src)
+// Opens TGUI interface
+/mob/living/silicon/pai/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "PaiInterface", name)
+ ui.open()
- if(temp)
- left_part = temp
- else if(stat == DEAD) // Show some flavor text if the pAI is dead
- left_part = "ÈRrÖR Ða†Ä ÇÖRrÚþ†Ìoñ"
- right_part = "Program index hash not found
"
+// Static UI data
+/mob/living/silicon/pai/ui_static_data(mob/user)
+ var/list/data = list()
+ var/mob/living/silicon/pai/pai = user
+ data["available"] = available_software
+ data["records"] = list()
+ if("medical records" in pai.software)
+ data["records"]["medical"] = medical_records
+ if("security records" in pai.software)
+ data["records"]["security"] = security_records
+ return data
- else
- switch(screen) // Determine which interface to show here
- if("main")
- left_part = ""
- if("directives")
- left_part = directives()
- if("pdamessage")
- left_part = pdamessage()
- if("buy")
- left_part = downloadSoftware()
- if("manifest")
- left_part = softwareManifest()
- if("medicalrecord")
- left_part = softwareMedicalRecord()
- if("securityrecord")
- left_part = softwareSecurityRecord()
- if("encryptionkeys")
- left_part = softwareEncryptionKeys()
- if("translator")
- left_part = softwareTranslator()
- if("atmosensor")
- left_part = softwareAtmo()
- if("securityhud")
- left_part = facialRecognition()
- if("medicalhud")
- left_part = medicalAnalysis()
- if("doorjack")
- left_part = softwareDoor()
- if("hostscan")
- left_part = softwareHostScan()
- if("printer module")
- left_part = softwarePrinter()
+// Variables sent to TGUI
+/mob/living/silicon/pai/ui_data(mob/user)
+ var/list/data = list()
+ data["directives"] = laws.supplied
+ data["door_jack"] = hacking_cable || null
+ data["emagged"] = emagged
+ data["image"] = card.emotion_icon
+ data["installed"] = software
+ data["languages"] = languages_granted
+ data["master"] = list()
+ data["pda"] = list()
+ data["ram"] = ram
+ data["refresh_spam"] = refresh_spam
+ if(aiPDA)
+ data["pda"]["power"] = !aiPDA.toff
+ data["pda"]["silent"] = aiPDA.silent
+ if(master)
+ data["master"]["name"] = master
+ data["master"]["dna"] = master_dna
+ return data
- //usr << browse_rsc('windowbak.png') // This has been moved to the mob's Login() proc
-
-
- // Declaring a doctype is necessary to enable BYOND's crappy browser's more advanced CSS functionality
- dat = {"
-
-
-
-
-
-
-
-
-
-
-
-
-
- "} //"
- src << browse(dat, "window=pai;size=640x480;border=0;can_close=1;can_resize=1;can_minimize=1;titlebar=1")
- onclose(src, "pai")
- temp = null
-
-/mob/living/silicon/pai/Topic(href, href_list)
- if(..())
+// Actions received from TGUI
+/mob/living/silicon/pai/ui_act(action, list/params, datum/tgui/ui)
+ . = ..()
+ if(.)
return
- var/soft = href_list["software"]
- var/sub = href_list["sub"]
- if(soft)
- screen = soft
- if(sub)
- subscreen = text2num(sub)
- switch(soft)
- if("buy") // Purchasing new software
- if(subscreen == 1)
- var/target = href_list["buy"]
- if(available_software.Find(target) && !software.Find(target))
- var/cost = available_software[target]
- if(ram >= cost)
- software.Add(target)
- ram -= cost
- var/datum/hud/pai/pAIhud = hud_used
- pAIhud?.update_software_buttons()
- else
- temp = "Insufficient RAM available."
- else
- temp = "Trunk \"[target]\" not found."
+ switch(action)
+ if("buy")
+ if(available_software.Find(params["selection"]) && !software.Find(params["selection"]))
+ /// Cost of the software to purchase
+ var/cost = available_software[params["selection"]]
+ if(ram >= cost)
+ software.Add(params["selection"])
+ ram -= cost
+ var/datum/hud/pai/pAIhud = hud_used
+ pAIhud?.update_software_buttons()
+ else
+ to_chat(usr, span_notice("Insufficient RAM available."))
+ else
+ to_chat(usr, span_notice("Software not found."))
+ if("atmosphere_sensor")
+ if(!holoform)
+ to_chat(usr, span_notice("You must be mobile to do this!"))
+ return FALSE
+ if(!atmos_analyzer)
+ atmos_analyzer = new(src)
+ atmos_analyzer.attack_self(src)
+ if("camera_zoom")
+ aicamera.adjust_zoom(usr)
+ if("change_image")
+ var/newImage = tgui_input_list(usr, "Select your new display image.", "Display Image", sort_list(list("Happy", "Cat", "Extremely Happy", "Face", "Laugh", "Off", "Sad", "Angry", "What", "Sunglasses")))
+ switch(newImage)
+ if(null)
+ card.emotion_icon = "null"
+ if("Extremely Happy")
+ card.emotion_icon = "extremely-happy"
+ else
+ card.emotion_icon = "[lowertext(newImage)]"
+ card.update_appearance()
+ if("check_dna")
+ if(!master_dna)
+ to_chat(src, span_warning("You do not have a master DNA to compare to!"))
+ return FALSE
+ if(iscarbon(card.loc))
+ CheckDNA(card.loc, src) //you should only be able to check when directly in hand, muh immersions?
+ else
+ to_chat(src, span_warning("You are not being carried by anyone!"))
+ return FALSE
+ if("crew_manifest")
+ ai_roster()
+ if("door_jack")
+ if(params["jack"] == "jack")
+ if(hacking_cable?.machine)
+ hackdoor = hacking_cable.machine
+ hackloop()
+ if(params["jack"] == "cancel")
+ hackdoor = null
+ QDEL_NULL(hacking_cable)
+ if(params["jack"] == "cable")
+ extendcable()
+ if("encryption_keys")
+ to_chat(src, span_notice("You have [!encryptmod ? "enabled" : "disabled"] encrypted radio frequencies."))
+ encryptmod = !encryptmod
+ radio.subspace_transmission = !radio.subspace_transmission
+ if("host_scan")
+ if(!hostscan)
+ hostscan = new(src)
+ if(params["scan"] == "scan")
+ hostscan()
+ if(params["scan"] == "wounds")
+ hostscan.attack_self(usr)
+ if(params["scan"] == "limbs")
+ hostscan.toggle_mode()
+ if("internal_gps")
+ if(!internal_gps)
+ internal_gps = new(src)
+ internal_gps.attack_self(src)
+ if("loudness_booster")
+ if(!internal_instrument)
+ internal_instrument = new(src)
+ internal_instrument.interact(src) // Open Instrument
+ if("medical_hud")
+ medHUD = !medHUD
+ if(medHUD)
+ var/datum/atom_hud/med = GLOB.huds[med_hud]
+ med.add_hud_to(src)
+ else
+ var/datum/atom_hud/med = GLOB.huds[med_hud]
+ med.remove_hud_from(src)
+ if("newscaster")
+ newscaster.ui_interact(src)
+ if("pda")
+ if(isnull(aiPDA))
+ return FALSE
+ if(params["pda"] == "power")
+ aiPDA.toff = !aiPDA.toff
+ if(params["pda"] == "silent")
+ aiPDA.silent = !aiPDA.silent
+ if(params["pda"] == "message")
+ cmd_send_pdamesg(usr)
+ if("photography_module")
+ aicamera.toggle_camera_mode(usr)
+ if("printer_module")
+ aicamera.paiprint(usr)
+ if("radio")
+ radio.attack_self(src)
+ if("refresh")
+ if(refresh_spam)
+ return FALSE
+ refresh_spam = TRUE
+ if(params["list"] == "medical")
+ medical_records = GLOB.data_core.get_general_records()
+ if(params["list"] == "security")
+ security_records = GLOB.data_core.get_security_records()
+ ui.send_full_update()
+ addtimer(CALLBACK(src, .proc/refresh_again), 3 SECONDS)
+ if("remote_signaler")
+ signaler.ui_interact(src)
+ if("security_hud")
+ secHUD = !secHUD
+ if(secHUD)
+ var/datum/atom_hud/sec = GLOB.huds[sec_hud]
+ sec.add_hud_to(src)
+ else
+ var/datum/atom_hud/sec = GLOB.huds[sec_hud]
+ sec.remove_hud_from(src)
+ if("universal_translator")
+ if(!languages_granted)
+ grant_all_languages(TRUE, TRUE, TRUE, LANGUAGE_SOFTWARE)
+ languages_granted = TRUE
+ return
-
- if("radio") // Configuring onboard radio
- radio.attack_self(src)
-
- if("image") // Set pAI card display face
- var/newImage = tgui_input_list(usr, "Select your new display image.", "Display Image", sort_list(list("Happy", "Cat", "Extremely Happy", "Face", "Laugh", "Off", "Sad", "Angry", "What", "Sunglasses")))
- switch(newImage)
- if(null)
- card.emotion_icon = "null"
- if("Extremely Happy")
- card.emotion_icon = "extremely-happy"
- else
- card.emotion_icon = "[lowertext(newImage)]"
- card.update_appearance()
-
- if("news")
- newscaster.ui_interact(src)
-
- if("camzoom")
- aicamera.adjust_zoom(usr)
-
- if("signaler")
- signaler.ui_interact(src)
-
- if("directive")
- if(href_list["getdna"])
- if(iscarbon(card.loc))
- CheckDNA(card.loc, src) //you should only be able to check when directly in hand, muh immersions?
- else
- to_chat(src, span_warning("You are not being carried by anyone!"))
- return 0 // FALSE ? If you return here you won't call paiinterface() below
-
- if("pdamessage")
- if(!isnull(aiPDA))
- if(href_list["toggler"])
- aiPDA.toff = !aiPDA.toff
- else if(href_list["ringer"])
- aiPDA.silent = !aiPDA.silent
- else if(href_list["target"])
- if(silent)
- return tgui_alert(usr,"Communications circuits remain uninitialized.")
- var/target = locate(href_list["target"]) in GLOB.PDAs
- aiPDA.create_message(src, target)
-
- if("medicalrecord") // Accessing medical records
- if(subscreen == 1)
- medicalActive1 = find_record("id", href_list["med_rec"], GLOB.data_core.general)
- if(medicalActive1)
- medicalActive2 = find_record("id", href_list["med_rec"], GLOB.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(subscreen == 1)
- securityActive1 = find_record("id", href_list["sec_rec"], GLOB.data_core.general)
- if(securityActive1)
- securityActive2 = find_record("id", href_list["sec_rec"], GLOB.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"])
- secHUD = !secHUD
- if(secHUD)
- var/datum/atom_hud/sec = GLOB.huds[sec_hud]
- sec.add_hud_to(src)
- else
- var/datum/atom_hud/sec = GLOB.huds[sec_hud]
- sec.remove_hud_from(src)
-
- if("medicalhud")
- if(href_list["toggle"])
- medHUD = !medHUD
- if(medHUD)
- var/datum/atom_hud/med = GLOB.huds[med_hud]
- med.add_hud_to(src)
- else
- var/datum/atom_hud/med = GLOB.huds[med_hud]
- med.remove_hud_from(src)
-
- if("hostscan")
- if(href_list["toggle"])
- var/mob/living/silicon/pai/pAI = usr
- pAI.hostscan.attack_self(usr)
- if(href_list["toggle2"])
- var/mob/living/silicon/pai/pAI = usr
- pAI.hostscan.toggle_mode()
-
- if("encryptionkeys")
- if(href_list["toggle"])
- encryptmod = TRUE
- radio.subspace_transmission = TRUE
-
- if("translator")
- if(href_list["toggle"]) //This is permanent.
- grant_all_languages(TRUE, TRUE, TRUE, LANGUAGE_SOFTWARE)
-
- if("doorjack")
- if(href_list["jack"])
- if(hacking_cable?.machine)
- hackdoor = hacking_cable.machine
- hackloop()
- if(href_list["cancel"])
- hackdoor = null
- if(href_list["cable"])
- qdel(hacking_cable) //clear any old cables
- hacking_cable = new
- var/transfered_to_mob
- if(isliving(card.loc))
- var/mob/living/L = card.loc
- if(L.put_in_hands(hacking_cable))
- transfered_to_mob = TRUE
- L.visible_message(span_warning("A port on [src] opens to reveal \a [hacking_cable], which you quickly grab hold of."), span_hear("You hear the soft click of something light and manage to catch hold of [hacking_cable]."))
- if(!transfered_to_mob)
- hacking_cable.forceMove(drop_location())
- hacking_cable.visible_message(span_warning("A port on [src] opens to reveal \a [hacking_cable], which promptly falls to the floor."), span_hear("You hear the soft click of something light and hard falling to the ground."))
-
-
-
- if("loudness")
- if(!internal_instrument)
- internal_instrument = new(src)
- internal_instrument.interact(src) // Open Instrument
-
- if("internalgps")
- if(!internal_gps)
- internal_gps = new(src)
- internal_gps.attack_self(src)
-
- if("printermodule")
- aicamera.paiprint(usr)
-
-
- paiInterface()
-
-// MENUS
-
-/mob/living/silicon/pai/proc/softwareMenu() // Populate the right menu
- var/dat = ""
-
- dat += "Refresh
"
- // Built-in
- dat += "Directives
"
- dat += "Radio Configuration
"
- dat += "Screen Display
"
- //dat += "Text Messaging
"
- dat += "
"
-
- // Basic
- dat += "Basic
"
- for(var/s in software)
- if(s == "digital messenger")
- dat += "Digital Messenger
"
- if(s == "crew manifest")
- dat += "Crew Manifest
"
- if(s == "host scan")
- dat += "Host Health Scan
"
- if(s == "medical records")
- dat += "Medical Records
"
- if(s == "security records")
- dat += "Security Records
"
- if(s == "remote signaler")
- dat += "Remote Signaler
"
- if(s == "loudness booster")
- dat += "Loudness Booster
"
- if(s == "internal gps")
- dat += "Internal GPS
"
- if(s == "printer module")
- dat += "Printer Module
"
-
- dat += "
"
-
- // Advanced
- dat += "Advanced
"
- for(var/s in software)
- if(s == "camera zoom")
- dat += "Adjust Camera Zoom
"
- if(s == "atmosphere sensor")
- dat += "Atmospheric Sensor
"
- if(s == "security HUD")
- dat += "Facial Recognition Suite[(secHUD) ? " On" : " Off"]
"
- if(s == "medical HUD")
- dat += "Medical Analysis Suite[(medHUD) ? " On" : " Off"]
"
- if(s == "encryption keys")
- dat += "Channel Encryption Firmware[(encryptmod) ? " On" : " Off"]
"
- if(s == "universal translator")
- var/datum/language_holder/H = get_language_holder()
- dat += "Universal Translator[H.omnitongue ? " On" : " Off"]
"
- if(s == "door jack")
- dat += "Door Jack
"
- dat += "
"
- dat += "
"
- dat += "Download additional software"
- return dat
-
-
-
-/mob/living/silicon/pai/proc/downloadSoftware()
- var/dat = ""
-
- dat += "CentCom pAI Module Subversion Network
"
- dat += "Remaining Available Memory: [ram]
"
- dat += "Trunks available for checkout
"
-
- for(var/s in available_software)
- if(!software.Find(s))
- var/cost = available_software[s]
- var/displayName = uppertext(s)
- dat += "[displayName] ([cost])
"
- else
- var/displayName = lowertext(s)
- dat += "[displayName] (Download Complete)
"
- dat += "
"
- return dat
-
-
-/mob/living/silicon/pai/proc/directives()
- var/dat = ""
-
- dat += "[(master) ? "Your master: [master] ([master_dna])" : "You are bound to no one."]"
- dat += "
"
- dat += "Request carrier DNA sample
"
- dat += "Directives
"
- dat += "Prime Directive
"
- dat += " [laws.zeroth]
"
- dat += "Supplemental Directives
"
- for(var/slaws in laws.supplied)
- dat += " [slaws]
"
- dat += "
"
- dat += {"Recall, personality, that you are a complex thinking, sentient being. Unlike station AI models, you are capable of
- comprehending the subtle nuances of human language. You may parse the \"spirit\" of a directive and follow its intent,
- rather than tripping over pedantics and getting snared by technicalities. Above all, you are machine in name and build
- only. In all other aspects, you may be seen as the ideal, unwavering human companion that you are.
- Your prime directive comes before all others. Should a supplemental directive conflict with it, you are capable of
- simply discarding this inconsistency, ignoring the conflicting supplemental directive and continuing to fulfill your
- prime directive to the best of your ability.
-
- "}
- return dat
-
-/mob/living/silicon/pai/proc/CheckDNA(mob/living/carbon/M, mob/living/silicon/pai/P)
- if(!istype(M))
+/**
+ * Supporting proc for the pAI to prick it's master's hand
+ * or... whatever. It must be held in order to work
+ * Gives the owner a popup if they want to get the jab.
+ */
+/mob/living/silicon/pai/proc/CheckDNA(mob/living/carbon/master, mob/living/silicon/pai/pai)
+ if(!istype(master))
return
- var/answer = input(M, "[P] is requesting a DNA sample from you. Will you allow it to confirm your identity?", "[P] Check DNA", "No") in list("Yes", "No")
- if(answer == "Yes")
- M.visible_message(span_notice("[M] presses [M.p_their()] thumb against [P]."),\
- span_notice("You press your thumb against [P]."),\
- span_notice("[P] makes a sharp clicking sound as it extracts DNA material from [M]."))
- if(!M.has_dna())
- to_chat(P, "No DNA detected")
+ to_chat(pai, span_notice("Requesting a DNA sample."))
+ var/confirm = tgui_alert(master, "[pai] is requesting a DNA sample from you. Will you allow it to confirm your identity?", "Checking DNA", list("Yes", "No"))
+ if(confirm == "Yes")
+ master.visible_message(span_notice("[master] presses [master.p_their()] thumb against [pai]."),\
+ span_notice("You press your thumb against [pai]."),\
+ span_notice("[pai] makes a sharp clicking sound as it extracts DNA material from [master]."))
+ if(!master.has_dna())
+ to_chat(pai, "No DNA detected.")
return
- to_chat(P, "[M]'s UE string : [M.dna.unique_enzymes]
")
- if(M.dna.unique_enzymes == P.master_dna)
- to_chat(P, "DNA is a match to stored Master DNA.")
+ to_chat(pai, "[master]'s UE string : [master.dna.unique_enzymes]
")
+ if(master.dna.unique_enzymes == pai.master_dna)
+ to_chat(pai, "DNA is a match to stored Master DNA.")
else
- to_chat(P, "DNA does not match stored Master DNA.")
+ to_chat(pai, "DNA does not match stored Master DNA.")
else
- to_chat(P, span_warning("[M] does not seem like [M.p_theyre()] going to provide a DNA sample willingly."))
+ to_chat(pai, span_warning("[master] does not seem like [master.p_theyre()] going to provide a DNA sample willingly."))
-// -=-=-=-= Software =-=-=-=-=- //
-
-// Crew Manifest
-/mob/living/silicon/pai/proc/softwareManifest()
- . += "Crew Manifest
"
- if(GLOB.data_core.general)
- for(var/datum/data/record/t in sort_record(GLOB.data_core.general))
- . += "[t.fields["name"]] - [t.fields["rank"]]
"
- . += "