[MIRROR] pAI TGUI conversion [MDB IGNORE] (#9784)

* pAI TGUI conversion

* Feeeeeeex

Co-authored-by: Jeremiah <42397676+jlsnow301@users.noreply.github.com>
Co-authored-by: GoldenAlpharex <58045821+GoldenAlpharex@users.noreply.github.com>
This commit is contained in:
SkyratBot
2021-11-30 01:18:16 -05:00
committed by GitHub
co-authored by Jeremiah GoldenAlpharex
parent 9119d4ec30
commit 43cb9a554b
11 changed files with 1603 additions and 836 deletions
+3 -1
View File
@@ -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())
+1 -1
View File
@@ -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"
+140 -178
View File
@@ -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 += {"
<style type="text/css">
p.top {
background-color: #AAAAAA; color: black;
}
tr.d0 td {
background-color: #CC9999; color: black;
}
tr.d1 td {
background-color: #9999CC; color: black;
}
</style>
"}
dat += "<p class=\"top\">Please configure your pAI personality's options. Remember, what you enter here could determine whether or not the user requesting a personality chooses you!</p>"
dat += "<table>"
dat += "<tr class=\"d0\"><td>Name:</td><td>[candidate.name]</td></tr>"
dat += "<tr class=\"d1\"><td><a href='byond://?src=[REF(src)];option=name;new=1;candidate=[REF(candidate)]'>\[Edit\]</a></td><td>What you plan to call yourself. Suggestions: Any character name you would choose for a station character OR an AI.</td></tr>"
dat += "<tr class=\"d0\"><td>Description:</td><td>[candidate.description]</td></tr>"
dat += "<tr class=\"d1\"><td><a href='byond://?src=[REF(src)];option=desc;new=1;candidate=[REF(candidate)]'>\[Edit\]</a></td><td>What sort of pAI you typically play; your mannerisms, your quirks, etc. This can be as sparse or as detailed as you like.</td></tr>"
dat += "<tr class=\"d0\"><td>Preferred Role:</td><td>[candidate.role]</td></tr>"
dat += "<tr class=\"d1\"><td><a href='byond://?src=[REF(src)];option=role;new=1;candidate=[REF(candidate)]'>\[Edit\]</a></td><td>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.</td></tr>"
dat += "<tr class=\"d0\"><td>OOC Comments:</td><td>[candidate.comments]</td></tr>"
dat += "<tr class=\"d1\"><td><a href='byond://?src=[REF(src)];option=ooc;new=1;candidate=[REF(candidate)]'>\[Edit\]</a></td><td>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.</td></tr>"
dat += "</table>"
dat += "<br>"
dat += "<h3><a href='byond://?src=[REF(src)];option=submit;new=1;candidate=[REF(candidate)]'>Submit Personality</a></h3><br>"
dat += "<a href='byond://?src=[REF(src)];option=save;new=1;candidate=[REF(candidate)]'>Save Personality</a><br>"
dat += "<a href='byond://?src=[REF(src)];option=load;new=1;candidate=[REF(candidate)]'>Load Personality</a><br>"
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 += {"
<style type="text/css">
/**
* This is the primary window proc when the pAI candidate
* hud menu is pressed by observers.
*
* Arguments
* @user - The ghost doing the pressing.
*/
/datum/controller/subsystem/pai/proc/recruitWindow(mob/user)
/// Searches for a previous candidate upon opening the menu
var/datum/pai_candidate/candidate = check_candidate(user)
if(isnull(candidate))
candidate = new /datum/pai_candidate()
candidate.key = user.key
candidates.Add(candidate)
ui_interact(user)
p.top {
background-color: #AAAAAA; color: black;
}
/// Ensures an observer has the window open
/datum/controller/subsystem/pai/ui_state(mob/user)
return GLOB.observer_state
tr.d0 td {
background-color: #CC9999; color: black;
}
tr.d1 td {
background-color: #9999CC; color: black;
}
tr.d2 td {
background-color: #99CC99; color: black;
}
</style>
"}
dat += "<p class=\"top\">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.</p>"
/// 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 += "<table>"
/// 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 += "<tr class=\"d0\"><td>Name:</td><td>[c.name]</td></tr>"
dat += "<tr class=\"d1\"><td>Description:</td><td>[c.description]</td></tr>"
dat += "<tr class=\"d0\"><td>Preferred Role:</td><td>[c.role]</td></tr>"
dat += "<tr class=\"d1\"><td>OOC Comments:</td><td>[c.comments]</td></tr>"
dat += "<tr class=\"d2\"><td><a href='byond://?src=[REF(src)];download=1;candidate=[REF(c)];device=[REF(p)]'>\[Download [c.name]\]</a></td><td></td></tr>"
/// 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 += "</table>"
/**
* 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
+45 -1
View File
@@ -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
+116 -76
View File
@@ -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 = "<TT><B>Personal AI Device</B><BR>"
if(pai)
if(!pai.master_dna || !pai.master)
dat += "<a href='byond://?src=[REF(src)];setdna=1'>Imprint Master DNA</a><br>"
dat += "Installed Personality: [pai.name]<br>"
dat += "Prime directive: <br>[pai.laws.zeroth]<br>"
for(var/slaws in pai.laws.supplied)
dat += "Additional directives: <br>[slaws]<br>"
dat += "<a href='byond://?src=[REF(src)];setlaws=1'>Configure Directives</a><br>"
dat += "<br>"
dat += "<h3>Device Settings</h3><br>"
if(pai.radio)
dat += "<b>Radio Uplink</b><br>"
dat += "Transmit: <A href='byond://?src=[REF(src)];toggle_transmit=1'>\[[pai.can_transmit? "Disable" : "Enable"] Radio Transmission\]</a><br>"
dat += "Receive: <A href='byond://?src=[REF(src)];toggle_receive=1'>\[[pai.can_receive? "Disable" : "Enable"] Radio Reception\]</a><br>"
else
dat += "<b>Radio Uplink</b><br>"
dat += "<font color=red><i>Radio firmware not loaded. Please install a pAI personality to load firmware.</i></font><br>"
if(ishuman(user))
var/mob/living/carbon/human/H = user
if(H.real_name == pai.master || H.dna.unique_enzymes == pai.master_dna)
dat += "<A href='byond://?src=[REF(src)];toggle_holo=1'>\[[pai.canholo? "Disable" : "Enable"] holomatrix projectors\]</a><br>"
dat += "<A href='byond://?src=[REF(src)];fix_speech=1'>\[Reset speech synthesis module\]</a><br>"
dat += "<A href='byond://?src=[REF(src)];wipe=1'>\[Wipe current pAI personality\]</a><br>"
else
dat += "No personality installed.<br>"
dat += "Searching for a personality... Press view available personalities to notify potential candidates."
dat += "<A href='byond://?src=[REF(src)];request=1'>\[View available personalities\]</a><br>"
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
+45 -36
View File
@@ -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
@@ -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
+243 -541
View File
@@ -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 = "<b><font color=red>ÈRrÖR Ða†Ä ÇÖRrÚþ†Ìoñ</font></b>"
right_part = "<pre>Program index hash not found</pre>"
// 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 = {"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">
<html>
<head>
<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>
<style type=\"text/css\">
#header { text-align:center; color:white; font-size: 30px; height: 35px; width: 100%; letter-spacing: 2px; z-index: 5}
#content {position: relative; left: 10px; height: 400px; width: 100%; z-index: 0}
#leftmenu {color: #AAAAAA; background-color:#333333; width: 400px; height: auto; min-height: 340px; position: absolute; z-index: 0}
#leftmenu a:link { color: #CCCCCC; }
#leftmenu a:hover { color: #CC3333; }
#leftmenu a:visited { color: #CCCCCC; }
#leftmenu a:active { color: #000000; }
#rightmenu {color: #CCCCCC; background-color:#555555; width: 200px ; height: auto; min-height: 340px; right: 10px; position: absolute; z-index: 1}
#rightmenu a:link { color: #CCCCCC; }
#rightmenu a:hover { color: #CC3333; }
#rightmenu a:visited { color: #CCCCCC; }
#rightmenu a:active { color: #000000; }
</style>
<script language='javascript' type='text/javascript'>
[js_byjax]
</script>
</head>
<body scroll=yes>
<div id=\"header\">
pAI OS
</div>
<div id=\"content\">
<div id=\"leftmenu\">[left_part]</div>
<div id=\"rightmenu\">[right_part]</div>
</div>
</body>
</html>"} //"
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 <TT> \"[target]\"</TT> 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 += "<A href='byond://?src=[REF(src)];software=refresh'>Refresh</A><br>"
// Built-in
dat += "<A href='byond://?src=[REF(src)];software=directives'>Directives</A><br>"
dat += "<A href='byond://?src=[REF(src)];software=radio;sub=0'>Radio Configuration</A><br>"
dat += "<A href='byond://?src=[REF(src)];software=image'>Screen Display</A><br>"
//dat += "Text Messaging <br>"
dat += "<br>"
// Basic
dat += "<b>Basic</b> <br>"
for(var/s in software)
if(s == "digital messenger")
dat += "<a href='byond://?src=[REF(src)];software=pdamessage;sub=0'>Digital Messenger</a> <br>"
if(s == "crew manifest")
dat += "<a href='byond://?src=[REF(src)];software=manifest;sub=0'>Crew Manifest</a> <br>"
if(s == "host scan")
dat += "<a href='byond://?src=[REF(src)];software=hostscan;sub=0'>Host Health Scan</a> <br>"
if(s == "medical records")
dat += "<a href='byond://?src=[REF(src)];software=medicalrecord;sub=0'>Medical Records</a> <br>"
if(s == "security records")
dat += "<a href='byond://?src=[REF(src)];software=securityrecord;sub=0'>Security Records</a> <br>"
if(s == "remote signaler")
dat += "<a href='byond://?src=[REF(src)];software=signaler;sub=0'>Remote Signaler</a> <br>"
if(s == "loudness booster")
dat += "<a href='byond://?src=[REF(src)];software=loudness;sub=0'>Loudness Booster</a> <br>"
if(s == "internal gps")
dat += "<a href='byond://?src=[REF(src)];software=internalgps;sub=0'>Internal GPS</a> <br>"
if(s == "printer module")
dat += "<a href='byond://?src=[REF(src)];software=printermodule;sub=0'>Printer Module</a> <br>"
dat += "<br>"
// Advanced
dat += "<b>Advanced</b> <br>"
for(var/s in software)
if(s == "camera zoom")
dat += "<a href='byond://?src=[REF(src)];software=camzoom;sub=0'>Adjust Camera Zoom</a> <br>"
if(s == "atmosphere sensor")
dat += "<a href='byond://?src=[REF(src)];software=atmosensor;sub=0'>Atmospheric Sensor</a> <br>"
if(s == "security HUD")
dat += "<a href='byond://?src=[REF(src)];software=securityhud;sub=0'>Facial Recognition Suite</a>[(secHUD) ? "<font color=#55FF55> On</font>" : "<font color=#FF5555> Off</font>"] <br>"
if(s == "medical HUD")
dat += "<a href='byond://?src=[REF(src)];software=medicalhud;sub=0'>Medical Analysis Suite</a>[(medHUD) ? "<font color=#55FF55> On</font>" : "<font color=#FF5555> Off</font>"] <br>"
if(s == "encryption keys")
dat += "<a href='byond://?src=[REF(src)];software=encryptionkeys;sub=0'>Channel Encryption Firmware</a>[(encryptmod) ? "<font color=#55FF55> On</font>" : "<font color=#FF5555> Off</font>"] <br>"
if(s == "universal translator")
var/datum/language_holder/H = get_language_holder()
dat += "<a href='byond://?src=[REF(src)];software=translator;sub=0'>Universal Translator</a>[H.omnitongue ? "<font color=#55FF55> On</font>" : "<font color=#FF5555> Off</font>"] <br>"
if(s == "door jack")
dat += "<a href='byond://?src=[REF(src)];software=doorjack;sub=0'>Door Jack</a> <br>"
dat += "<br>"
dat += "<br>"
dat += "<a href='byond://?src=[REF(src)];software=buy;sub=0'>Download additional software</a>"
return dat
/mob/living/silicon/pai/proc/downloadSoftware()
var/dat = ""
dat += "<h2>CentCom pAI Module Subversion Network</h2><br>"
dat += "<pre>Remaining Available Memory: [ram]</pre><br>"
dat += "<p style=\"text-align:center\"><b>Trunks available for checkout</b><br>"
for(var/s in available_software)
if(!software.Find(s))
var/cost = available_software[s]
var/displayName = uppertext(s)
dat += "<a href='byond://?src=[REF(src)];software=buy;sub=1;buy=[s]'>[displayName]</a> ([cost]) <br>"
else
var/displayName = lowertext(s)
dat += "[displayName] (Download Complete) <br>"
dat += "</p>"
return dat
/mob/living/silicon/pai/proc/directives()
var/dat = ""
dat += "[(master) ? "Your master: [master] ([master_dna])" : "You are bound to no one."]"
dat += "<br><br>"
dat += "<a href='byond://?src=[REF(src)];software=directive;getdna=1'>Request carrier DNA sample</a><br>"
dat += "<h2>Directives</h2><br>"
dat += "<b>Prime Directive</b><br>"
dat += "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[laws.zeroth]<br>"
dat += "<b>Supplemental Directives</b><br>"
for(var/slaws in laws.supplied)
dat += "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[slaws]<br>"
dat += "<br>"
dat += {"<i><p>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.</i></p><br><br><p>
<b>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.</b></p><br><br>-
"}
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, "<b>No DNA detected</b>")
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, "<b>No DNA detected.</b>")
return
to_chat(P, "<font color = red><h3>[M]'s UE string : [M.dna.unique_enzymes]</h3></font>")
if(M.dna.unique_enzymes == P.master_dna)
to_chat(P, "<b>DNA is a match to stored Master DNA.</b>")
to_chat(pai, "<font color = red><h3>[master]'s UE string : [master.dna.unique_enzymes]</h3></font>")
if(master.dna.unique_enzymes == pai.master_dna)
to_chat(pai, "<b>DNA is a match to stored Master DNA.</b>")
else
to_chat(P, "<b>DNA does not match stored Master DNA.</b>")
to_chat(pai, "<b>DNA does not match stored Master DNA.</b>")
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()
. += "<h2>Crew Manifest</h2><br><br>"
if(GLOB.data_core.general)
for(var/datum/data/record/t in sort_record(GLOB.data_core.general))
. += "[t.fields["name"]] - [t.fields["rank"]]<BR>"
. += "</body></html>"
return .
// Medical Records
/mob/living/silicon/pai/proc/softwareMedicalRecord()
switch(subscreen)
if(0)
. += "<h3>Medical Records</h3><HR>"
if(GLOB.data_core.general)
for(var/datum/data/record/R in sort_record(GLOB.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 GLOB.data_core.general)
. += "Name: [medicalActive1.fields["name"]] ID: [medicalActive1.fields["id"]]<BR>\nGender: [medicalActive1.fields["gender"]]<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 GLOB.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 (UE): <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()
. = ""
switch(subscreen)
if(0)
. += "<h3>Security Records</h3><HR>"
if(GLOB.data_core.general)
for(var/datum/data/record/R in sort_record(GLOB.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 GLOB.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>\nGender: <A href='?src=[REF(src)];field=gender'>[securityActive1.fields["gender"]]</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 GLOB.data_core.security)
. += "<BR>\nSecurity Data<BR>\nCriminal Status: [securityActive2.fields["criminal"]]<BR>\n<BR>\nCrimes: <A href='?src=[REF(src)];field=mcrim'>[securityActive2.fields["crim"]]</A><BR>\nDetails: <A href='?src=[REF(src)];field=crim_d'>[securityActive2.fields["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>"
. += "<BR>\n<A href='?src=[REF(src)];software=securityrecord;sub=0'>Back</A><BR>"
return .
// Encryption Keys
/mob/living/silicon/pai/proc/softwareEncryptionKeys()
var/dat = {"<h3>Encryption Key Firmware</h3><br>
When enabled, this device will be able to use up to two (2) encryption keys for departmental channel access.<br><br>
The device is currently [encryptmod ? "<font color=#55FF55>en" : "<font color=#FF5555>dis" ]abled.</font><br>[encryptmod ? "" : "<a href='byond://?src=[REF(src)];software=encryptionkeys;sub=0;toggle=1'>Activate Encryption Key Ports</a><br>"]"}
return dat
// Universal Translator
/mob/living/silicon/pai/proc/softwareTranslator()
var/datum/language_holder/H = get_language_holder()
. = {"<h3>Universal Translator</h3><br>
When enabled, this device will permanently be able to speak and understand all known forms of communication.<br><br>
The device is currently [H.omnitongue ? "<font color=#55FF55>en" : "<font color=#FF5555>dis" ]abled.</font><br>[H.omnitongue ? "" : "<a href='byond://?src=[REF(src)];software=translator;sub=0;toggle=1'>Activate Translation Module</a><br>"]"}
return .
// Security HUD
/mob/living/silicon/pai/proc/facialRecognition()
var/dat = {"<h3>Facial Recognition Overlay</h3><br>
When enabled, this package will scan all viewable faces and compare them against the known criminal database, providing real-time graphical data about any detected persons of interest.<br><br>
The package is currently [ (secHUD) ? "<font color=#55FF55>en" : "<font color=#FF5555>dis" ]abled.</font><br>
<a href='byond://?src=[REF(src)];software=securityhud;sub=0;toggle=1'>Toggle Package</a><br>
"}
return dat
// Medical HUD
/mob/living/silicon/pai/proc/medicalAnalysis()
var/dat = ""
dat += {"<h3>Medical Analysis Overlay</h3><br>
When enabled, this package will scan all nearby crewmembers' vitals and provide real-time graphical data about their state of health.<br><br>
The suite is currently [ (medHUD) ? "<font color=#55FF55>en" : "<font color=#FF5555>dis" ]abled.</font><br>
<a href='byond://?src=[REF(src)];software=medicalhud;sub=0;toggle=1'>Toggle Suite</a><br>
"}
return dat
//Health Scanner
/mob/living/silicon/pai/proc/softwareHostScan()
var/dat = ""
dat += {"<h3>Host Bisoscan Settings</h3><br>
<a href='byond://?src=[REF(src)];software=hostscan;sub=0;toggle=1'>Change Scan Type</a><br>
<a href='byond://?src=[REF(src)];software=hostscan;sub=0;toggle2=1'>Toggle Verbosity</a><br>
"}
return dat
// Atmospheric Scanner
/mob/living/silicon/pai/proc/softwareAtmo()
var/dat = "<h3>Atmospheric Sensor</h4>"
var/turf/T = get_turf(loc)
if (isnull(T))
dat += "Unable to obtain a reading.<br>"
/**
* Host scan supporting proc
*
* Allows the pAI to scan its host's health vitals
* An integrated health analyzer.
*/
/mob/living/silicon/pai/proc/hostscan()
var/mob/living/silicon/pai/pAI = usr
var/mob/living/carbon/holder = get(pAI.card.loc, /mob/living/carbon)
if(holder)
pAI.hostscan.attack(holder, pAI)
else
var/datum/gas_mixture/environment = T.return_air()
var/list/env_gases = environment.gases
to_chat(usr, span_warning("You are not being carried by anyone!"))
return FALSE
var/pressure = environment.return_pressure()
var/total_moles = environment.total_moles()
/**
* Extend cable supporting proc
*
* When doorjack is installed, allows the pAI to drop
* a cable which is placed either on the floor or in
* someone's hands based (on distance).
*/
/mob/living/silicon/pai/proc/extendcable()
QDEL_NULL(hacking_cable) //clear any old cables
hacking_cable = new
var/transfered_to_mob
if(isliving(card.loc))
var/mob/living/hacker = card.loc
if(hacker.put_in_hands(hacking_cable))
transfered_to_mob = TRUE
hacker.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."))
dat += "Air Pressure: [round(pressure,0.1)] kPa<br>"
if (total_moles)
for(var/id in env_gases)
var/gas_level = env_gases[id][MOLES]/total_moles
if(gas_level > 0.01)
dat += "[env_gases[id][GAS_META][META_GAS_NAME]]: [round(gas_level*100)]%<br>"
dat += "Temperature: [round(environment.temperature-T0C)]&deg;C<br>"
dat += "<a href='byond://?src=[REF(src)];software=atmosensor;sub=0'>Refresh Reading</a> <br>"
dat += "<br>"
return dat
// Door Jack
/mob/living/silicon/pai/proc/softwareDoor()
var/dat = "<h3>Airlock Jack</h3>"
dat += "Cable status : "
if(!hacking_cable)
dat += "<font color=#FF5555>Retracted</font> <br>"
dat += "<a href='byond://?src=[REF(src)];software=doorjack;cable=1;sub=0'>Extend Cable</a> <br>"
return dat
if(!hacking_cable.machine)
dat += "<font color=#FFFF55>Extended</font> <br>"
return dat
dat += "<font color=#55FF55>Connected</font> <br>"
if(!istype(hacking_cable.machine, /obj/machinery/door))
dat += "Connected device's firmware does not appear to be compatible with Airlock Jack protocols.<br>"
return dat
if(!hackdoor)
dat += "<a href='byond://?src=[REF(src)];software=doorjack;jack=1;sub=0'>Begin Airlock Jacking</a> <br>"
else
dat += "Jack in progress... [hackprogress]% complete.<br>"
dat += "<a href='byond://?src=[REF(src)];software=doorjack;cancel=1;sub=0'>Cancel Airlock Jack</a> <br>"
return dat
// Door Jack - supporting proc
/**
* Door jacking supporting proc
*
* This begins the hacking process on a door.
* Mostly, this gives UI feedback, while the "hack"
* is handled inside pai.dm itself.
*/
/mob/living/silicon/pai/proc/hackloop()
var/turf/T = get_turf(src)
var/turf/turf = get_turf(src)
playsound(src, 'sound/machines/airlock_alien_prying.ogg', 50, TRUE)
to_chat(usr, span_boldnotice("You begin overriding the airlock security protocols."))
for(var/mob/living/silicon/ai/AI in GLOB.player_list)
if(T.loc)
to_chat(AI, "<font color = red><b>Network Alert: Brute-force encryption crack in progress in [T.loc].</b></font>")
if(turf.loc)
to_chat(AI, "<font color = red><b>Network Alert: Brute-force security override in progress in [turf.loc].</b></font>")
else
to_chat(AI, "<font color = red><b>Network Alert: Brute-force encryption crack in progress. Unable to pinpoint location.</b></font>")
to_chat(AI, "<font color = red><b>Network Alert: Brute-force security override in progress. Unable to pinpoint location.</b></font>")
hacking = TRUE
// Digital Messenger
/mob/living/silicon/pai/proc/pdamessage()
var/dat = "<h3>Digital Messenger</h3>"
dat += {"<b>Signal/Receiver Status:</b> <A href='byond://?src=[REF(src)];software=pdamessage;toggler=1'>
[(aiPDA.toff) ? "<font color='red'>\[Off\]</font>" : "<font color='green'>\[On\]</font>"]</a><br>
<b>Ringer Status:</b> <A href='byond://?src=[REF(src)];software=pdamessage;ringer=1'>
[(aiPDA.silent) ? "<font color='red'>\[Off\]</font>" : "<font color='green'>\[On\]</font>"]</a><br><br>"}
dat += "<ul>"
if(!aiPDA.toff)
for (var/obj/item/pda/P in get_viewable_pdas())
if (P == aiPDA)
continue
dat += "<li><a href='byond://?src=[REF(src)];software=pdamessage;target=[REF(P)]'>[P]</a>"
dat += "</li>"
dat += "</ul>"
dat += "<br><br>"
dat += "Messages: <hr> [aiPDA.tnote]"
return dat
/mob/living/silicon/pai/proc/softwarePrinter()
aicamera.paiprint(usr)
/**
* Proc that switches whether a pAI can refresh
* the records window again.
*/
/mob/living/silicon/pai/proc/refresh_again()
refresh_spam = FALSE
+219
View File
@@ -0,0 +1,219 @@
import { useBackend, useLocalState } from '../backend';
import { Box, Button, LabeledList, NoticeBox, Section, Stack, Tabs } from '../components';
import { Window } from '../layouts';
type PaiCardData = {
candidates: Candidate[];
pai: Pai;
};
type Candidate = {
comments: string;
description: string;
key: string;
name: string;
};
type Pai = {
can_holo: number;
dna: string;
emagged: number;
laws: string;
master: string;
name: string;
transmit: number;
receive: number;
};
export const PaiCard = (_, context) => {
const { data } = useBackend<PaiCardData>(context);
const { pai } = data;
return (
<Window width={400} height={400} title="pAI Options Menu">
<Window.Content>{!pai ? <PaiDownload /> : <PaiOptions />}</Window.Content>
</Window>
);
};
/** Gives a list of candidates as cards */
const PaiDownload = (_, context) => {
const { act, data } = useBackend<PaiCardData>(context);
const { candidates = [] } = data;
return (
<Section
buttons={
<Button
icon="concierge-bell"
onClick={() => act('request')}
tooltip="Request candidates.">
Request
</Button>
}
fill
scrollable
title="Viewing pAI Candidates">
{!candidates.length ? (
<NoticeBox>None found!</NoticeBox>
) : (
<Stack fill vertical>
{candidates.map((candidate, index) => {
return (
<Stack.Item key={index}>
<CandidateDisplay candidate={candidate} />
</Stack.Item>
);
})}
</Stack>
)}
</Section>
);
};
/** Candidate card: Individual. Since this info is refreshing,
* had to make the comments and descriptions a separate tab.
* In longer entries, it is much more readable.
*/
const CandidateDisplay = (props, context) => {
const [tab, setTab] = useLocalState(context, 'tab', 'description');
const { candidate } = props;
const { comments, description, name } = candidate;
const onTabClickHandler = (tab: string) => {
setTab(tab);
};
return (
<Box
style={{
'background': '#111111',
'border': '1px solid #4972a1',
'border-radius': '5px',
'padding': '1rem',
}}>
<Section
buttons={
<CandidateTabs
candidate={candidate}
onTabClick={onTabClickHandler}
tab={tab}
/>
}
fill
height={12}
scrollable
title="Candidate">
<Box color="green" fontSize="16px">
Name: {name || 'Randomized Name'}
</Box>
{tab === 'description'
? (`Description: ${description.length && description || "None"}`)
: (`OOC Comments: ${comments.length && comments || "None"}`)}
</Section>
</Box>
);
};
/** Tabs for the candidate */
const CandidateTabs = (props, context) => {
const { act } = useBackend<PaiCardData>(context);
const { candidate, onTabClick, tab } = props;
const { key } = candidate;
return (
<Stack>
<Stack.Item>
<Tabs>
<Tabs.Tab
onClick={() => {
onTabClick('description');
}}
selected={tab === 'description'}>
Description
</Tabs.Tab>
<Tabs.Tab
onClick={() => {
onTabClick('comments');
}}
selected={tab === 'comments'}>
OOC
</Tabs.Tab>
</Tabs>
</Stack.Item>
<Stack.Item>
<Button
icon="download"
onClick={() => act('download', { key })}
tooltip="Accepts this pAI candidate.">
Download
</Button>
</Stack.Item>
</Stack>
);
};
/** Once a pAI has been loaded, you can alter its settings here */
const PaiOptions = (_, context) => {
const { act, data } = useBackend<PaiCardData>(context);
const { pai } = data;
const { can_holo, dna, emagged, laws, master, name, transmit, receive } = pai;
return (
<Section fill scrollable title={name}>
<LabeledList>
<LabeledList.Item label="Master">
{master || (
<Button icon="dna" onClick={() => act('set_dna')}>
Imprint
</Button>
)}
</LabeledList.Item>
{master && <LabeledList.Item label="DNA">{dna}</LabeledList.Item>}
<LabeledList.Item label="Laws">{laws}</LabeledList.Item>
<LabeledList.Item label="Holoform">
<Button
icon={can_holo ? 'toggle-on' : 'toggle-off'}
onClick={() => act('toggle_holo')}
selected={can_holo}>
Toggle
</Button>
</LabeledList.Item>
<LabeledList.Item label="Transmit">
<Button
icon={transmit ? 'toggle-on' : 'toggle-off'}
onClick={() => act('toggle_radio', { option: 'transmit' })}
selected={transmit}>
Toggle
</Button>
</LabeledList.Item>
<LabeledList.Item label="Receive">
<Button
icon={receive ? 'toggle-on' : 'toggle-off'}
onClick={() => act('toggle_radio', { option: 'receive' })}
selected={receive}>
Toggle
</Button>
</LabeledList.Item>
<LabeledList.Item label="Troubleshoot">
<Button icon="comment" onClick={() => act('fix_speech')}>
Fix Speech
</Button>
<Button icon="edit" onClick={() => act('set_laws')}>
Set Laws
</Button>
</LabeledList.Item>
<LabeledList.Item label="Personality">
<Button icon="trash" onClick={() => act('wipe_pai')}>
Erase
</Button>
</LabeledList.Item>
</LabeledList>
{!!emagged && (
<Button color="bad" disabled icon="bug" mt={1}>
Malicious Software Detected
</Button>
)}
</Section>
);
};
@@ -0,0 +1,643 @@
import { useBackend, useSharedState } from '../backend';
import { Box, Button, LabeledList, Icon, NoticeBox, ProgressBar, Section, Stack, Table, Tabs, Tooltip } from '../components';
import { Window } from '../layouts';
type PaiInterfaceData = {
available: Available;
directives: string;
door_jack?: string;
emagged: number;
image: string;
installed: string[];
languages: number;
master: Master;
pda: PDA;
ram: number;
records: Records;
refresh_spam: number;
};
type Available = {
name: string;
value: string | number;
};
type Master = {
name: string;
dna: string;
};
type PDA = {
power: number;
silent: number;
};
type Records = {
medical?: CrewRecord[];
security?: CrewRecord[];
};
type CrewRecord = {
[key: string]: string;
};
enum Tab {
System = 1,
Directive = 2,
Installed = 3,
Available = 4,
}
const DIRECTIVE_COMPREHENSION = `As an advanced software model,
you are a complex, thinking, sentient being. Unlike previous 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.`;
const DIRECTIVE_ORDER = `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 fulfillyour prime directive to the best
of your ability.`;
const SOFTWARE_DESC = {
'crew manifest': 'A tool that allows you to view the crew manifest.',
'digital messenger':
'A tool that allows you to send messages to other crew members.',
'atmosphere sensor':
'A tool that allows you to analyze local atmospheric contents.',
'photography module':
'A portable camera module. Engage, then click to shoot.',
'camera zoom': 'A tool that allows you to zoom in on your camera.',
'printer module': 'A portable printer module for photographs.',
'remote signaler':
'A remote signalling device to transmit and receive codes.',
'medical records': 'A tool that allows you to view station medical records.',
'security records':
'A tool that allows you to view station security records, warrants.',
'host scan': 'A portable health analyzer. Must be held to use.',
'medical HUD': 'Allows you to view medical status using an overlay HUD.',
'security HUD': 'Allows you to view security records using an overlay HUD.',
'loudness booster':
'Synthesizes instruments, plays sounds and imported songs.',
'newscaster':
'A tool that allows you to broadcast news to other crew members.',
'door jack': 'A tool that allows you to open doors.',
'encryption keys':
'A tool that allows you to decrypt and speak on other radio frequencies.',
'internal gps': 'A tool that allows you to track your location.',
'universal translator': 'Translation module for non-common languages.',
};
const ICON_MAP = {
'angry': 'angry',
'cat': 'cat',
'extremely-happy': 'grin-beam',
'laugh': 'grin-squint',
'happy': 'smile',
'off': 'power-off',
'sad': 'frown',
'sunglasses': 'sun',
'what': 'question',
};
export const PaiInterface = (_, context) => {
const [tab, setTab] = useSharedState(context, 'tab', 1);
const setTabHandler = (tab: number) => {
setTab(tab);
};
return (
<Window title="pAI Software Interface v2.4" width={380} height={480}>
<Window.Content>
<Stack fill vertical>
<Stack.Item grow>
{tab === Tab.System && <SystemDisplay />}
{tab === Tab.Directive && <DirectiveDisplay />}
{tab === Tab.Installed && <InstalledDisplay />}
{tab === Tab.Available && <AvailableDisplay />}
</Stack.Item>
<Stack.Item>
<TabDisplay tab={tab} onTabClick={setTabHandler} />
</Stack.Item>
</Stack>
</Window.Content>
</Window>
);
};
/** Tabs at bottom of screen */
const TabDisplay = (props) => {
const { tab, onTabClick } = props;
return (
<Tabs fluid>
<Tabs.Tab
icon="list"
onClick={() => onTabClick(Tab.System)}
selected={tab === Tab.System}>
System
</Tabs.Tab>
<Tabs.Tab
icon="list"
onClick={() => onTabClick(Tab.Directive)}
selected={tab === Tab.Directive}>
Directives
</Tabs.Tab>
<Tabs.Tab
icon="list"
onClick={() => onTabClick(Tab.Installed)}
selected={tab === Tab.Installed}>
Installed
</Tabs.Tab>
<Tabs.Tab
icon="list"
onClick={() => onTabClick(Tab.Available)}
selected={tab === Tab.Available}>
Download
</Tabs.Tab>
</Tabs>
);
};
const SystemDisplay = () => {
return (
<Stack fill vertical>
<Stack.Item grow={3}>
<SystemWallpaper />
</Stack.Item>
<Stack.Item grow>
<SystemInfo />
</Stack.Item>
</Stack>
);
};
/** Renders some ASCII art. Changes to red on emag. */
const SystemWallpaper = (_, context) => {
const { data } = useBackend<PaiInterfaceData>(context);
const { emagged } = data;
const owner = !emagged ? 'NANOTRASEN' : ' SYNDICATE';
const eyebrows = !emagged ? "/\\ ' /\\" : ' \\\\ // ';
const paiAscii = [
' ________ ________ ___',
' |\\ __ \\|\\ __ \\|\\ \\',
' \\ \\ \\|\\ \\ \\ \\|\\ \\ \\ \\ Interface',
' \\ \\ ____\\ \\ __ \\ \\ \\ Version 2.4',
' \\ \\ \\___|\\ \\ \\ \\ \\ \\ \\',
' \\ \\__\\ \\ \\__\\ \\__\\ \\__\\ Property of',
` \\|__| \\|__|\\|__|\\|__| ${owner}`,
'',
].join('\n');
const floofAscii = [
' .--. .-.',
" ,;;``;;-;,,..___.,,.-/ `;_//,.' )",
" .' ;; `; :; `;; ;; `. '/ .'",
` ,; '; ; ' '; '; ,' ${eyebrows}';`, // lol
" /' ` \\ ` ;',' ( d\\__b_),",
" / / .,;;) ', ( .' __\\",
" ;:. \\ ,_ / ', ' .'_ \\/;",
" , ,;' `;;/ / ';,\\ `-..__._,'",
" ;:. /____ ..-'--. /-' ..---. ._._/ ---.",
" | ;' ;'| \\--/;' ,' / \\ , \\",
" `.fL__;,__/-..__)_)/ `--'--'`-._)_)/ --\\.._)_)/",
].join('\n');
return (
<Section fill nowrap overflow="hidden">
<pre>
<Box color={!emagged ? 'blue' : 'crimson'}>{paiAscii}</Box>
<Box color={!emagged ? 'gold' : 'limegreen'}>{floofAscii}</Box>
</pre>
</Section>
);
};
/** Displays master info.
* You can check their DNA and change your image here.
*/
const SystemInfo = (_, context) => {
const { act, data } = useBackend<PaiInterfaceData>(context);
const { image, master } = data;
return (
<Section
buttons={
<>
<Button
disabled={!master.dna}
icon="dna"
onClick={() => act('check_dna')}
tooltip="Verifies your master's DNA. Must be carried in hand.">
Verify
</Button>
<Button
icon={ICON_MAP[image] || 'meh-blank'}
onClick={() => act('change_image')}
tooltip="Change your display image.">
Display
</Button>
</>
}
fill
scrollable
title="System Info">
<LabeledList>
<LabeledList.Item label="Master">
{master.name || 'None.'}
</LabeledList.Item>
<LabeledList.Item label="DNA">{master.dna || 'None.'}</LabeledList.Item>
</LabeledList>
</Section>
);
};
/** Shows the hardcoded PAI info along with any supplied orders. */
const DirectiveDisplay = (_, context) => {
const { data } = useBackend<PaiInterfaceData>(context);
const { directives, master } = data;
return (
<Stack fill vertical>
<Stack.Item grow={2}>
<Section fill scrollable title="Logic Core">
<Box color="label">
{DIRECTIVE_COMPREHENSION}
<br />
<br />
{DIRECTIVE_ORDER}
</Box>
</Section>
</Stack.Item>
<Stack.Item grow>
<Section fill scrollable title="Directives">
{!master.name ? (
'None.'
) : (
<LabeledList>
<LabeledList.Item label="Prime">
Serve your master.
</LabeledList.Item>
<LabeledList.Item label="Supplemental">
<Box wrap>{directives}</Box>
</LabeledList.Item>
</LabeledList>
)}
</Section>
</Stack.Item>
</Stack>
);
};
/** Renders two sections: A section of buttons and
* another section that displays the selected installed
* software info.
*/
const InstalledDisplay = (_, context) => {
const [installSelected, setInstallSelected] = useSharedState(
context,
'software',
''
);
const onInstallHandler = (software: string) => {
setInstallSelected(software);
};
return (
<Stack fill vertical>
<Stack.Item grow>
<InstalledSoftware onInstallClick={onInstallHandler} />
</Stack.Item>
<Stack.Item grow={2}>
<InstalledInfo software={installSelected} />
</Stack.Item>
</Stack>
);
};
/** Iterates over installed software to render buttons. */
const InstalledSoftware = (props, context) => {
const { data } = useBackend<PaiInterfaceData>(context);
const { installed = [] } = data;
const { onInstallClick } = props;
return (
<Section fill scrollable title="Installed Software">
{!installed.length ? (
<NoticeBox>Nothing installed!</NoticeBox>
) : (
installed.map((software) => {
return (
<Button key={software} onClick={() => onInstallClick(software)}>
{software.replace(/(^\w{1})|(\s+\w{1})/g, (letter) =>
letter.toUpperCase()
)}
</Button>
);
})
)}
</Section>
);
};
/** Software info for buttons clicked. */
const InstalledInfo = (props) => {
const { software } = props;
/** Records get their own section here */
if (software === 'medical records') {
return <RecordsDisplay record_type="medical" />;
} else if (software === 'security records') {
return <RecordsDisplay record_type="security" />;
} else {
return (
<Section
fill
scrollable
title={
!software
? 'Select a Program'
: software.replace(/(^\w{1})|(\s+\w{1})/g, (letter) =>
letter.toUpperCase())
}>
{software && (
<Stack fill vertical>
<Stack.Item>{SOFTWARE_DESC[software] || ''}</Stack.Item>
<Stack.Item grow>
<SoftwareButtons software={software} />
</Stack.Item>
</Stack>
)}
</Section>
);
}
};
/** Todo: Remove this entirely when records get a TGUI interface themselves */
const RecordsDisplay = (props, context) => {
const { act, data } = useBackend<PaiInterfaceData>(context);
const { record_type } = props;
const { records = [], refresh_spam } = data;
const convertedRecords: CrewRecord[] = records[record_type];
return (
<Section
title="Name"
buttons={
<Stack>
<Stack.Item>
<Button
disabled={refresh_spam}
onClick={() => act('refresh', { list: record_type })}
tooltip="Refresh">
<Icon mr={-0.7} name="sync" spin={refresh_spam} />
</Button>
</Stack.Item>
<Stack.Item>
<RecordLabels record_type={record_type} />
</Stack.Item>
</Stack>
}
fill
scrollable>
<Table>
{convertedRecords?.map((record) => {
return <RecordRow key={record.ref} record={record} />;
})}
</Table>
</Section>
);
};
/** Renders the labels for the record viewer */
const RecordLabels = (props) => {
const { record_type } = props;
return (
<Table>
<Table.Row>
<Table.Cell>
{record_type === 'medical' ? 'Physical Health' : 'Arrest Status'}
</Table.Cell>
<Table.Cell>
{record_type === 'medical' ? 'Mental Health' : 'Total Crimes'}
</Table.Cell>
</Table.Row>
</Table>
);
};
const RecordRow = (props) => {
const { record = [] } = props;
const convertedRecord = Object.values(record);
/** I do not want to show the ref here */
const filteredRecord = convertedRecord.splice(1);
return (
<Table.Row className="candystripe">
{filteredRecord?.map((value) => {
return <Table.Cell key={value}>{value}</Table.Cell>;
})}
</Table.Row>
);
};
/** Once a software is selected, generates custom buttons or a default
* power toggle.
*/
const SoftwareButtons = (props, context) => {
const { act, data } = useBackend<PaiInterfaceData>(context);
const { door_jack, languages, pda } = data;
const { software } = props;
switch (software) {
case 'digital messenger':
return (
<>
<Button
icon="power-off"
onClick={() => act('pda', { pda: 'power' })}
selected={pda.power}>
Power
</Button>
<Button
icon="volume-mute"
onClick={() => act('pda', { pda: 'silent' })}
selected={pda.silent}>
Silent
</Button>
<Button
disabled={!pda.power}
icon="envelope"
onClick={() => act('pda', { pda: 'message' })}>
Message
</Button>
</>
);
case 'door jack':
return (
<>
<Button
disabled={door_jack}
icon="plug"
onClick={() => act('door_jack', { jack: 'cable' })}>
Extend Cable
</Button>
<Button
color="bad"
disabled={!door_jack}
icon="door-open"
onClick={() => act('door_jack', { jack: 'jack' })}>
Hack Door
</Button>
<Button
disabled={!door_jack}
icon="unlink"
onClick={() => act('door_jack', { jack: 'cancel' })}>
Cancel
</Button>
</>
);
case 'host scan': {
return (
<>
<Button
icon="search"
onClick={() => act('host_scan', { scan: 'scan' })}>
Host Scan
</Button>
<Button
icon="cog"
onClick={() => act('host_scan', { scan: 'wounds' })}>
Toggle Wounds
</Button>
<Button
icon="cog"
onClick={() => act('host_scan', { scan: 'limbs' })}>
Toggle Limbs
</Button>
</>
);
}
case 'universal translator':
return (
<Button
icon="download"
onClick={() => act(software.toLowerCase().replace(/ /g, '_'))}
disabled={!!languages}>
{!languages ? 'Install' : 'Installed'}
</Button>
);
default:
return (
<Button
icon="power-off"
onClick={() => act(software.toLowerCase().replace(/ /g, '_'))}
tooltip="Attempts to toggle the module's power.">
Toggle
</Button>
);
}
};
const AvailableDisplay = () => {
return (
<Section
buttons={<AvailableMemory />}
fill
scrollable
title="Available Software">
<AvailableSoftware />
</Section>
);
};
/** Displays the remaining RAM left as a progressbar. */
const AvailableMemory = (_, context) => {
const { data } = useBackend<PaiInterfaceData>(context);
const { ram } = data;
return (
<Tooltip content="Available System Memory">
<Stack>
<Stack.Item>
<Icon color="purple" mt={0.7} name="microchip" />
</Stack.Item>
<Stack.Item>
<ProgressBar
minValue={0}
maxValue={100}
ranges={{
good: [67, 100],
average: [34, 66],
bad: [0, 33],
}}
value={ram}
/>
</Stack.Item>
</Stack>
</Tooltip>
);
};
/** A list of available software.
* creates table rows for each, like a vendor.
*/
const AvailableSoftware = (_, context) => {
const { data } = useBackend<PaiInterfaceData>(context);
const { available } = data;
const convertedList: Available[] = Object.entries(available).map((key) => {
return { name: key[0], value: key[1] };
});
return (
<Table>
{convertedList?.map((software) => {
return <AvailableRow key={software.name} software={software} />;
})}
</Table>
);
};
/** A row for an individual software listing. */
const AvailableRow = (props, context) => {
const { act, data } = useBackend<PaiInterfaceData>(context);
const { ram } = data;
const { installed } = data;
const { software } = props;
const purchased = installed.includes(software.name);
return (
<Table.Row className="candystripe">
<Table.Cell collapsible>
<Box color="label">
{software.name.replace(/^\w/, (c) => c.toUpperCase())}
</Box>
</Table.Cell>
<Table.Cell collapsible>
<Box color={ram < software.value && 'bad'} textAlign="right">
{!purchased && software.value}{' '}
<Icon
color={purchased || ram >= software.value ? 'purple' : 'bad'}
name={purchased ? 'check' : 'microchip'}
/>
</Box>
</Table.Cell>
<Table.Cell collapsible>
<Button
fluid
mb={0.5}
disabled={ram < software.value || purchased}
onClick={() => act('buy', { selection: software.name })}
tooltip={SOFTWARE_DESC[software.name] || ''}>
<Icon ml={1} mr={-2} name="download" />
</Button>
</Table.Cell>
</Table.Row>
);
};
+148
View File
@@ -0,0 +1,148 @@
import { useBackend, useLocalState } from '../backend';
import { Box, Button, Input, Section, Stack, Tooltip } from '../components';
import { Window } from '../layouts';
type CandidateData = {
comments: string;
description: string;
name: string;
};
const PAI_DESCRIPTION = `Personal AIs are advanced models
capable of nuanced interaction. They are designed to be used
in a variety of situations, assisting their masters in their
work. They do not possess hands, thus they cannot interact with
equipment or items. While in hologram form, you cannot be
directly killed, but you may be incapacitated.`;
const PAI_RULES = `You are expected to role play to some degree.
Keep in mind: Not entering information may lead to you not being
selected. Press submit to alert pAI cards of your candidacy.`;
export const PaiSubmit = (_, context) => {
const { data } = useBackend<CandidateData>(context);
const [input, setInput] = useLocalState<CandidateData>(context, 'input', {
comments: data.comments || '',
description: data.description || '',
name: data.name || '',
});
const onChangeHandler = (e, value) => {
setInput({ ...input, [value]: e.target.value });
};
return (
<Window width={400} height={460} title="pAI Candidacy Menu">
<Window.Content>
<Stack fill vertical>
<Stack.Item grow>
<DetailsDisplay />
</Stack.Item>
<Stack.Item>
<InputDisplay input={input} onChangeHandler={onChangeHandler} />
</Stack.Item>
<Stack.Item>
<ButtonsDisplay input={input} />
</Stack.Item>
</Stack>
</Window.Content>
</Window>
);
};
/** Displays basic info about playing pAI */
const DetailsDisplay = () => {
return (
<Section fill scrollable title="Details">
<Box color="label">
{PAI_DESCRIPTION}
<br />
<br />
{PAI_RULES}
</Box>
</Section>
);
};
/** Input boxes for submission details */
const InputDisplay = (props) => {
const { input, onChangeHandler } = props;
const { comments, description, name } = input;
return (
<Section fill title="Input">
<Stack fill vertical>
<Stack.Item>
<Tooltip content="The name of your pAI.">
<Box bold color="label">
Name
</Box>
<Input
fluid
value={name}
onChange={(e) => onChangeHandler(e, 'name')}
/>
</Tooltip>
</Stack.Item>
<Stack.Item>
<Tooltip content="This describes how you will (mis)behave in game.">
<Box bold color="label">
Description
</Box>
<Input
fluid
value={description}
onChange={(e) => onChangeHandler(e, 'description')}
/>
</Tooltip>
</Stack.Item>
<Stack.Item>
<Tooltip content="Any other OOC comments about your pAI personality.">
<Box bold color="label">
OOC Comments
</Box>
<Input
fluid
value={comments}
onChange={(e) => onChangeHandler(e, 'comments')}
/>
</Tooltip>
</Stack.Item>
</Stack>
</Section>
);
};
/** Gives the user a submit button */
const ButtonsDisplay = (props, context) => {
const { act } = useBackend<CandidateData>(context);
const { input } = props;
return (
<Section fill>
<Stack>
<Stack.Item>
<Button
onClick={() => act('save', { candidate: input })}
tooltip="Saves your candidate data locally.">
SAVE
</Button>
</Stack.Item>
<Stack.Item>
<Button
onClick={() => act('load')}
tooltip="Loads saved candidate data, if any.">
LOAD
</Button>
</Stack.Item>
<Stack.Item>
<Button
onClick={() =>
act('submit', {
candidate: input,
})}>
SUBMIT
</Button>
</Stack.Item>
</Stack>
</Section>
);
};