diff --git a/code/game/objects/items/devices/paicard.dm b/code/game/objects/items/devices/paicard.dm
index b5d14aa8982..2c2c5d9ea07 100644
--- a/code/game/objects/items/devices/paicard.dm
+++ b/code/game/objects/items/devices/paicard.dm
@@ -286,6 +286,8 @@
src.overlays.Cut()
src.overlays += "pai-off"
+/obj/item/device/paicard
+ var/current_emotion = 1
/obj/item/device/paicard/proc/setEmotion(var/emotion)
if(pai)
src.overlays.Cut()
@@ -299,6 +301,7 @@
if(7) src.overlays += "pai-sad"
if(8) src.overlays += "pai-angry"
if(9) src.overlays += "pai-what"
+ current_emotion = emotion
/obj/item/device/paicard/proc/alertUpdate()
var/turf/T = get_turf_or_move(src.loc)
diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm
index 005ad324045..b1ca37d8ddc 100644
--- a/code/modules/mob/living/silicon/pai/pai.dm
+++ b/code/modules/mob/living/silicon/pai/pai.dm
@@ -60,19 +60,24 @@
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/medical_cannotfind = 0
var/datum/data/record/medicalActive1 // Datacore record declarations for record software
var/datum/data/record/medicalActive2
+ var/security_cannotfind = 0
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/hack_aborted = 0
var/obj/item/radio/integrated/signal/sradio // AI's signaller
var/translator_on = 0 // keeps track of the translator module
+ var/current_pda_messaging = null
+
/mob/living/silicon/pai/New(var/obj/item/device/paicard)
canmove = 0
src.loc = paicard
@@ -82,14 +87,14 @@
if(!card.radio)
card.radio = new /obj/item/device/radio(src.card)
radio = card.radio
-
+
//Default languages without universal translator software
add_language("Galactic Common", 1)
add_language("Sol Common", 1)
add_language("Tradeband", 1)
- add_language("Gutter", 1)
- add_language("Trinary", 1)
-
+ add_language("Gutter", 1)
+ add_language("Trinary", 1)
+
//Verbs for pAI mobile form, chassis and Say flavor text
verbs += /mob/living/silicon/pai/proc/choose_chassis
verbs += /mob/living/silicon/pai/proc/choose_verbs
diff --git a/code/modules/mob/living/silicon/pai/software.dm b/code/modules/mob/living/silicon/pai/software.dm
index a663ae6a738..de1a8067b7e 100644
--- a/code/modules/mob/living/silicon/pai/software.dm
+++ b/code/modules/mob/living/silicon/pai/software.dm
@@ -1,724 +1,125 @@
-// TODO:
-// - Additional radio modules
-// - Potentially roll HUDs and Records into one
-// - Shock collar/lock system for prisoner pAIs?
-// - Put cable in user's hand instead of on the ground
-// - Camera jack
+var/list/pai_emotions = list(
+ "Happy" = 1,
+ "Cat" = 2,
+ "Extremely Happy" = 3,
+ "Face" = 4,
+ "Laugh" = 5,
+ "Off" = 6,
+ "Sad" = 7,
+ "Angry" = 8,
+ "What" = 9
+ )
-/mob/living/silicon/pai/var/list/available_software = list(
- "crew manifest" = 5,
- "digital messenger" = 5,
- "medical records" = 15,
- "security records" = 15,
- //"camera jack" = 10,
- "door jack" = 30,
- "atmosphere sensor" = 5,
- //"heartbeat sensor" = 10,
- "security HUD" = 20,
- "medical HUD" = 20,
- "universal translator" = 35,
- //"projection array" = 15
- "remote signaller" = 5,
- )
+var/global/list/pai_software_by_key = list()
+var/global/list/default_pai_software = list()
+/hook/startup/proc/populate_pai_software_list()
+ var/r = 1 // I would use ., but it'd sacrifice runtime detection
+ for(var/type in typesof(/datum/pai_software) - /datum/pai_software)
+ var/datum/pai_software/P = new type()
+ if(pai_software_by_key[P.id])
+ var/datum/pai_software/O = pai_software_by_key[P.id]
+ world << "pAI software module [P.name] has the same key as [O.name]!"
+ r = 0
+ continue
+ pai_software_by_key[P.id] = P
+ if(P.default)
+ default_pai_software[P.id] = P
+ return r
+
+/mob/living/silicon/pai/New()
+ ..()
+ software = default_pai_software.Copy()
/mob/living/silicon/pai/verb/paiInterface()
set category = "pAI Commands"
set name = "Software Interface"
- var/dat = ""
- var/left_part = ""
- var/right_part = softwareMenu()
- src.set_machine(src)
- if(temp)
- left_part = temp
- else if(src.stat == 2) // Show some flavor text if the pAI is dead
- left_part = "ÈRrÖR Ða†Ä ÇÖRrÚþ†Ìoñ" //This file has to be saved as ANSI or this will not display correctly
- right_part = "
Program index hash not found
"
+ ui_interact(src)
- else
- switch(src.screen) // Determine which interface to show here
- if("main")
- left_part = ""
- if("directives")
- left_part = src.directives()
- if("pdamessage")
- left_part = src.pdamessage()
- if("buy")
- left_part = downloadSoftware()
- if("manifest")
- left_part = src.softwareManifest()
- if("medicalrecord")
- left_part = src.softwareMedicalRecord()
- if("securityrecord")
- left_part = src.softwareSecurityRecord()
- if("translator")
- left_part = src.softwareTranslator()
- if("atmosensor")
- left_part = src.softwareAtmo()
- if("securityhud")
- left_part = src.facialRecognition()
- if("medicalhud")
- left_part = src.medicalAnalysis()
- if("doorjack")
- left_part = src.softwareDoor()
- if("camerajack")
- left_part = src.softwareCamera()
- if("signaller")
- left_part = src.softwareSignal()
- if("radio")
- left_part = src.softwareRadio()
+/mob/living/silicon/pai/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = 1)
+ if(user != src)
+ if(ui) ui.set_status(STATUS_CLOSE, 0)
+ return
- //usr << browse_rsc('windowbak.png') // This has been moved to the mob's Login() proc
+ if(ui_key != "main")
+ var/datum/pai_software/S = software[ui_key]
+ if(S && !S.toggle)
+ S.on_ui_interact(src, ui, force_open)
+ else
+ if(ui) ui.set_status(STATUS_CLOSE, 0)
+ return
+ var/data[0]
- // Declaring a doctype is necessary to enable BYOND's crappy browser's more advanced CSS functionality
- dat = {"
-
-
-
-
-
-
-
-
-
-
-
-
- "}
- usr << browse(dat, "window=pai;size=685x449;border=0;can_close=1;can_resize=1;can_minimize=1;titlebar=1")
- onclose(usr, "pai")
- temp = null
- return
+ data["emotions"] = emotions
+ data["current_emotion"] = card.current_emotion
+
+ ui = nanomanager.try_update_ui(user, src, ui_key, ui, data, force_open)
+ if (!ui)
+ ui = new(user, src, ui_key, "pai_interface.tmpl", "pAI Software Interface", 450, 600)
+ ui.set_initial_data(data)
+ ui.open()
+ ui.set_auto_update(1)
/mob/living/silicon/pai/Topic(href, href_list)
- ..()
+ . = ..()
+ if(.) return
- if(href_list["priv_msg"]) // Admin-PMs were triggering the interface popup. Hopefully this will stop it.
- return
- var/soft = href_list["software"]
- var/sub = href_list["sub"]
- if(!soft && !sub)
- return
- if(soft)
- src.screen = soft
- if(sub)
- src.subscreen = text2num(sub)
- switch(soft)
- // Purchasing new software
- if("buy")
- if(src.subscreen == 1)
- var/target = href_list["buy"]
- if(available_software.Find(target))
- var/cost = src.available_software[target]
- if(src.ram >= cost)
- src.ram -= cost
- src.software.Add(target)
- else
- src.temp = "Insufficient RAM available."
- else
- src.temp = "Trunk \"[target]\" not found."
-
- // Configuring onboard radio
- if("radio")
- if(href_list["freq"])
- var/new_frequency = (radio.frequency + text2num(href_list["freq"]))
- if(new_frequency < 1441 || new_frequency > 1599)
- new_frequency = sanitize_frequency(new_frequency)
- else
- radio.set_frequency(new_frequency)
- else if (href_list["talk"])
- radio.broadcasting = text2num(href_list["talk"])
- else if (href_list["listen"])
- radio.listening = text2num(href_list["listen"])
-
- if("image")
- var/newImage = input("Select your new display image.", "Display Image", "Happy") in list("Happy", "Cat", "Extremely Happy", "Face", "Laugh", "Off", "Sad", "Angry", "What")
- var/pID = 1
-
- switch(newImage)
- if("Happy")
- pID = 1
- if("Cat")
- pID = 2
- if("Extremely Happy")
- pID = 3
- if("Face")
- pID = 4
- if("Laugh")
- pID = 5
- if("Off")
- pID = 6
- if("Sad")
- pID = 7
- if("Angry")
- pID = 8
- if("What")
- pID = 9
- src.card.setEmotion(pID)
-
- if("signaller")
-
- if(href_list["send"])
-
- sradio.send_signal("ACTIVATE")
- for(var/mob/O in hearers(1, src.loc))
- O.show_message(text("\icon[] *beep* *beep*", src), 3, "*beep* *beep*", 2)
-
- if(href_list["freq"])
-
- var/new_frequency = (sradio.frequency + text2num(href_list["freq"]))
- if(new_frequency < 1200 || new_frequency > 1600)
- new_frequency = sanitize_frequency(new_frequency)
- sradio.set_frequency(new_frequency)
-
- if(href_list["code"])
-
- sradio.code += text2num(href_list["code"])
- sradio.code = round(sradio.code)
- sradio.code = min(100, sradio.code)
- sradio.code = max(1, sradio.code)
-
-
-
- if("directive")
- if(href_list["getdna"])
- var/mob/living/M = src.loc
- var/count = 0
- while(!istype(M, /mob/living))
- if(!M || !M.loc) return 0 //For a runtime where M ends up in nullspace (similar to bluespace but less colourful)
- M = M.loc
- count++
- if(count >= 6)
- src << "You are not being carried by anyone!"
- return 0
- spawn CheckDNA(M, src)
-
- if("pdamessage")
- if(!isnull(pda))
- if(href_list["toggler"])
- pda.toff = !pda.toff
- else if(href_list["ringer"])
- pda.silent = !pda.silent
- else if(href_list["target"])
- if(silence_time)
- return alert("Communications circuits remain uninitialized.")
-
- var/target = locate(href_list["target"])
- pda.create_message(src, target)
-
- // Accessing medical records
- if("medicalrecord")
- if(src.subscreen == 1)
- var/datum/data/record/record = locate(href_list["med_rec"])
- if(record)
- var/datum/data/record/R = record
- var/datum/data/record/M = record
- if (!( data_core.general.Find(R) ))
- src.temp = "Unable to locate requested medical record. Record may have been deleted, or never have existed."
- else
- for(var/datum/data/record/E in data_core.medical)
- if ((E.fields["name"] == R.fields["name"] || E.fields["id"] == R.fields["id"]))
- M = E
- src.medicalActive1 = R
- src.medicalActive2 = M
- if("securityrecord")
- if(src.subscreen == 1)
- var/datum/data/record/record = locate(href_list["sec_rec"])
- if(record)
- var/datum/data/record/R = record
- var/datum/data/record/M = record
- if (!( data_core.general.Find(R) ))
- src.temp = "Unable to locate requested security record. Record may have been deleted, or never have existed."
- else
- for(var/datum/data/record/E in data_core.security)
- if ((E.fields["name"] == R.fields["name"] || E.fields["id"] == R.fields["id"]))
- M = E
- src.securityActive1 = R
- src.securityActive2 = M
- if("securityhud")
- if(href_list["toggle"])
- src.secHUD = !src.secHUD
- if("medicalhud")
- if(href_list["toggle"])
- src.medHUD = !src.medHUD
- if("translator")
- if(href_list["toggle"])
- src.translator_toggle()
- if("doorjack")
- if(href_list["jack"])
- if(src.cable && src.cable.machine)
- src.hackdoor = src.cable.machine
- src.hackloop()
- if(href_list["cancel"])
- src.hackdoor = null
- if(href_list["cable"])
- var/turf/T = get_turf_or_move(src.loc)
- src.cable = new /obj/item/weapon/pai_cable(T)
- for (var/mob/M in viewers(T))
- M.show_message("\red A port on [src] opens to reveal [src.cable], which promptly falls to the floor.", 3, "\red You hear the soft click of something light and hard falling to the ground.", 2)
- //src.updateUsrDialog() We only need to account for the single mob this is intended for, and he will *always* be able to call this window
- src.paiInterface() // So we'll just call the update directly rather than doing some default checks
- return
-
-// 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 src.software)
- if(s == "digital messenger")
- dat += "Digital Messenger [(pda.toff) ? "•" : "•"]
"
- if(s == "crew manifest")
- dat += "Crew Manifest
"
- if(s == "medical records")
- dat += "Medical Records
"
- if(s == "security records")
- dat += "Security Records
"
- if(s == "camera")
- dat += "Camera Jack
"
- if(s == "remote signaller")
- dat += "Remote Signaller
"
- dat += "
"
-
- // Advanced
- dat += "Advanced
"
- for(var/s in src.software)
- if(s == "atmosphere sensor")
- dat += "Atmospheric Sensor
"
- if(s == "heartbeat sensor")
- dat += "Heartbeat Sensor
"
- if(s == "security HUD") //This file has to be saved as ANSI or this will not display correctly
- dat += "Facial Recognition Suite [(src.secHUD) ? "•" : "•"]
"
- if(s == "medical HUD") //This file has to be saved as ANSI or this will not display correctly
- dat += "Medical Analysis Suite [(src.medHUD) ? "•" : "•"]
"
- if(s == "universal translator") //This file has to be saved as ANSI or this will not display correctly
- dat += "Universal Translator [(src.translator_on) ? "•" : "•"]
"
- if(s == "projection array")
- dat += "Projection Array
"
- if(s == "camera jack")
- dat += "Camera Jack
"
- if(s == "door jack")
- dat += "Door Jack
"
- dat += "
"
- dat += "
"
- dat += "Download additional software"
- return dat
-
-
-
-/mob/living/silicon/pai/proc/downloadSoftware()
- var/dat = ""
-
- dat += "CentComm pAI Module Subversion Network
"
- dat += "Remaining Available Memory: [src.ram]
"
- dat += "Trunks available for checkout
"
-
- for(var/s in available_software)
- if(!software.Find(s))
- var/cost = src.available_software[s]
- var/displayName = uppertext(s)
- dat += "- [displayName] ([cost])
"
+ if(href_list["software"])
+ var/soft = href_list["software"]
+ var/datum/pai_software/S = software[soft]
+ if(S.toggle)
+ S.toggle(src)
else
- var/displayName = lowertext(s)
- dat += "- [displayName] (Download Complete)
"
- dat += "
"
- return dat
+ ui_interact(src, ui_key = soft)
+ return 1
+ else if(href_list["stopic"])
+ var/soft = href_list["stopic"]
+ var/datum/pai_software/S = software[soft]
+ if(S)
+ return S.Topic(href, href_list)
-/mob/living/silicon/pai/proc/directives()
- var/dat = ""
+ else if(href_list["purchase"])
+ var/soft = href_list["purchase"]
+ var/datum/pai_software/S = pai_software_by_key[soft]
+ if(S && (ram >= S.ram_cost))
+ ram -= S.ram_cost
+ software[S.id] = S
+ return 1
- dat += "[(src.master) ? "Your master: [src.master] ([src.master_dna])" : "You are bound to no one."]"
- dat += "
"
- dat += "Request carrier DNA sample
"
- dat += "Directives
"
- dat += "Prime Directive
"
- dat += " [src.pai_law0]
"
- dat += "Supplemental Directives
"
- dat += " [src.pai_laws]
"
- 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(var/mob/M, var/mob/living/silicon/pai/P)
- 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")
- var/turf/T = get_turf_or_move(P.loc)
- for (var/mob/v in viewers(T))
- v.show_message("\blue [M] presses \his thumb against [P].", 3, "\blue [P] makes a sharp clicking sound as it extracts DNA material from [M].", 2)
- var/datum/dna/dna = M.dna
- P << "[M]'s UE string : [dna.unique_enzymes]
"
- if(dna.unique_enzymes == P.master_dna)
- P << "DNA is a match to stored Master DNA."
- else
- P << "DNA does not match stored Master DNA."
- else
- P << "[M] does not seem like \he is going to provide a DNA sample willingly."
-
-// -=-=-=-= Software =-=-=-=- //
-
-//Remote Signaller
-/mob/living/silicon/pai/proc/softwareSignal()
- var/dat = ""
- dat += "Remote Signaller
"
- dat += {"Frequency/Code for signaler:
- Frequency:
- -
- -
- [format_frequency(src.sradio.frequency)]
- +
- +
-
- Code:
- -
- -
- [src.sradio.code]
- +
- +
-
- Send Signal
"}
- return dat
-
-//Station Bounced Radio
-/mob/living/silicon/pai/proc/softwareRadio()
- var/dat = ""
- dat += "Station Bounced Radio
"
- if(!istype(src, /obj/item/device/radio/headset)) //Headsets don't get a mic button
- dat += "Microphone: [radio.broadcasting ? "Engaged" : "Disengaged"]
"
- dat += {"
- Speaker: [radio.listening ? "Engaged" : "Disengaged"]
- Frequency:
- -
- -
- [format_frequency(radio.frequency)]
- +
- +
- "}
-
- for (var/ch_name in radio.channels)
- dat+=radio.text_sec_channel(ch_name, radio.channels[ch_name])
- dat+={"[radio.text_wires()]