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 = {" - - - - - - -
- pAI OS -
-
-
[left_part]
-
[right_part]
-
- - "} - 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

" - 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()]"} - - return dat - -// Crew Manifest -/mob/living/silicon/pai/proc/softwareManifest() - var/dat = "" - dat += "

Crew Manifest


" - if(data_core) - dat += data_core.get_manifest(0) // make it monochrome - dat += "
" - return dat - -// Medical Records -/mob/living/silicon/pai/proc/softwareMedicalRecord() - var/dat = "" - if(src.subscreen == 0) - dat += "

Medical Records


" - if(!isnull(data_core.general)) - for(var/datum/data/record/R in sortRecord(data_core.general)) - dat += text("[]: []
", src, R, R.fields["id"], R.fields["name"]) - //dat += text("
Back", src) - if(src.subscreen == 1) - dat += "
Medical Record

" - if ((istype(src.medicalActive1, /datum/data/record) && data_core.general.Find(src.medicalActive1))) - dat += text("Name: []
\nID: []
\nSex: []
\nAge: []
\nFingerprint: []
\nPhysical Status: []
\nMental Status: []
", - src.medicalActive1.fields["name"], src.medicalActive1.fields["id"], src.medicalActive1.fields["sex"], src.medicalActive1.fields["age"], src.medicalActive1.fields["fingerprint"], src.medicalActive1.fields["p_stat"], src.medicalActive1.fields["m_stat"]) - else - dat += "
Requested medical record not found.

" - if ((istype(src.medicalActive2, /datum/data/record) && data_core.medical.Find(src.medicalActive2))) - dat += text("
\n
Medical Data

\nBlood Type: []
\nDNA: []
\n
\nMinor Disabilities: []
\nDetails: []
\n
\nMajor Disabilities: []
\nDetails: []
\n
\nAllergies: []
\nDetails: []
\n
\nCurrent Diseases: [] (per disease info placed in log/comment section)
\nDetails: []
\n
\nImportant Notes:
\n\t[]
\n
\n
Comments/Log

", src, src.medicalActive2.fields["b_type"], src, src.medicalActive2.fields["b_dna"], src, src.medicalActive2.fields["mi_dis"], src, src.medicalActive2.fields["mi_dis_d"], src, src.medicalActive2.fields["ma_dis"], src, src.medicalActive2.fields["ma_dis_d"], src, src.medicalActive2.fields["alg"], src, src.medicalActive2.fields["alg_d"], src, src.medicalActive2.fields["cdi"], src, src.medicalActive2.fields["cdi_d"], src, src.medicalActive2.fields["notes"]) - else - dat += "
Requested medical record not found.

" - dat += text("
\nBack
", src) - return dat - -// Security Records -/mob/living/silicon/pai/proc/softwareSecurityRecord() - var/dat = "" - if(src.subscreen == 0) - dat += "

Security Records


" - if(!isnull(data_core.general)) - for(var/datum/data/record/R in sortRecord(data_core.general)) - dat += text("[]: []
", src, R, R.fields["id"], R.fields["name"]) - if(src.subscreen == 1) - dat += "

Security Record

" - if ((istype(src.securityActive1, /datum/data/record) && data_core.general.Find(src.securityActive1))) - dat += text("Name:
[]
\nID: []
\nSex: []
\nAge: []
\nRank: []
\nFingerprint: []
\nPhysical Status: []
\nMental Status: []
", src, src.securityActive1.fields["name"], src, src.securityActive1.fields["id"], src, src.securityActive1.fields["sex"], src, src.securityActive1.fields["age"], src, src.securityActive1.fields["rank"], src, src.securityActive1.fields["fingerprint"], src.securityActive1.fields["p_stat"], src.securityActive1.fields["m_stat"]) - else - dat += "
Requested security record not found,

" - if ((istype(src.securityActive2, /datum/data/record) && data_core.security.Find(src.securityActive2))) - dat += text("
\nSecurity Data
\nCriminal Status: []
\n
\nMinor Crimes: []
\nDetails: []
\n
\nMajor Crimes: []
\nDetails: []
\n
\nImportant Notes:
\n\t[]
\n
\n
Comments/Log

", src.securityActive2.fields["criminal"], src, src.securityActive2.fields["mi_crim"], src, src.securityActive2.fields["mi_crim_d"], src, src.securityActive2.fields["ma_crim"], src, src.securityActive2.fields["ma_crim_d"], src, src.securityActive2.fields["notes"]) - else - dat += "
Requested security record not found,

" - dat += text("
\nBack
", src) - return dat - -// Universal Translator -/mob/living/silicon/pai/proc/softwareTranslator() - var/dat = {"

Universal Translator


- When enabled, this device will automatically convert all spoken and written language into a format that any known recipient can understand.

- The device is currently [ (src.universal_speak) ? "en" : "dis" ]abled.
- Toggle Device
- "} - return dat - -// Security HUD -/mob/living/silicon/pai/proc/facialRecognition() - var/dat = {"

Facial Recognition Suite


- 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.

- The suite is currently [ (src.secHUD) ? "en" : "dis" ]abled.
- Toggle Suite
- "} - return dat - -// Medical HUD -/mob/living/silicon/pai/proc/medicalAnalysis() - var/dat = "" - if(src.subscreen == 0) - dat += {"

Medical Analysis Suite


-

Visual Status Overlay

- When enabled, this package will scan all nearby crewmembers' vitals and provide real-time graphical data about their state of health.

- The suite is currently [ (src.medHUD) ? "en" : "dis" ]abled.
- Toggle Suite
-
- Host Bioscan
- "} - if(src.subscreen == 1) - dat += {"

Medical Analysis Suite


-

Host Bioscan

- "} - var/mob/living/M - //If we are not deployed, check the holder of the card. - if(src.loc == card) - M = card.loc - - //If we are deployed or the card is not held, check the first living mob in our turf. - if(!M || !istype(M)) - var/turf/T = get_turf(src) - M = locate(/mob/living/) in T.contents - - if(!M || !istype(M)) - src.temp = "Error: No biological host found.
" - src.subscreen = 0 - return dat - - dat += {"Bioscan Results for [M]:
- Overall Status: [M.stat > 1 ? "dead" : "[M.health]% healthy"]

- - Scan Breakdown:
- Respiratory: [M.getOxyLoss() > 50 ? "" : ""][M.getOxyLoss()]
- Toxicology: [M.getToxLoss() > 50 ? "" : ""][M.getToxLoss()]
- Burns: [M.getFireLoss() > 50 ? "" : ""][M.getFireLoss()]
- Structural Integrity: [M.getBruteLoss() > 50 ? "" : ""][M.getBruteLoss()]
- Body Temperature: [M.bodytemperature-T0C]°C ([M.bodytemperature*1.8-459.67]°F)
- "} - dat += "
Refresh Bioscan
" - dat += "
Visual Status Overlay
" - return dat - -// Atmospheric Scanner -/mob/living/silicon/pai/proc/softwareAtmo() - var/dat = "

Atmospheric Sensor


" - - var/turf/T = get_turf_or_move(src.loc) - if (isnull(T)) - dat += "Unable to obtain a reading.
" - else - var/datum/gas_mixture/environment = T.return_air() - - var/pressure = environment.return_pressure() - var/total_moles = environment.total_moles() - - dat += "Air Pressure: [round(pressure,0.1)] kPa
" - - if (total_moles) - var/o2_level = environment.oxygen/total_moles - var/n2_level = environment.nitrogen/total_moles - var/co2_level = environment.carbon_dioxide/total_moles - var/plasma_level = environment.toxins/total_moles - var/unknown_level = 1-(o2_level+n2_level+co2_level+plasma_level) - dat += "Nitrogen: [round(n2_level*100)]%
" - dat += "Oxygen: [round(o2_level*100)]%
" - dat += "Carbon Dioxide: [round(co2_level*100)]%
" - dat += "Plasma: [round(plasma_level*100)]%
" - if(unknown_level > 0.01) - dat += "OTHER: [round(unknown_level)]%
" - dat += "Temperature: [round(environment.temperature-T0C)]°C
" - dat += "
Refresh Reading" - return dat - -// Camera Jack - Clearly not finished -/mob/living/silicon/pai/proc/softwareCamera() - var/dat = "

Camera Jack


" - dat += "Cable status : " - - if(!src.cable) - dat += "Retracted
" - return dat - if(!src.cable.machine) - dat += "Extended
" - return dat - - var/obj/machinery/machine = src.cable.machine - dat += "Connected
" - - if(!istype(machine, /obj/machinery/camera)) - src << "DERP" - return dat - -// Door Jack -/mob/living/silicon/pai/proc/softwareDoor() - var/dat = "

Airlock Jack


" - dat += "Cable status : " - if(!src.cable) - dat += "Retracted
" - dat += "Extend Cable
" - return dat - if(!src.cable.machine) - dat += "Extended
" - return dat - - var/obj/machinery/machine = src.cable.machine - dat += "Connected
" - if(!istype(machine, /obj/machinery/door)) - dat += "Connected device's firmware does not appear to be compatible with Airlock Jack protocols.
" - return dat -// var/obj/machinery/airlock/door = machine - - if(!src.hackdoor) - dat += "Begin Airlock Jacking
" - else - dat += "Jack in progress... [src.hackprogress]% complete.
" - dat += "Cancel Airlock Jack
" - //src.hackdoor = machine - //src.hackloop() - return dat - -// Door Jack - supporting proc -/mob/living/silicon/pai/proc/hackloop() - var/turf/T = get_turf_or_move(src.loc) - for(var/mob/living/silicon/ai/AI in player_list) - if(T.loc) - AI << "Network Alert: Brute-force encryption crack in progress in [T.loc]." - else - AI << "Network Alert: Brute-force encryption crack in progress. Unable to pinpoint location." - while(src.hackprogress < 100) - if(src.cable && src.cable.machine && istype(src.cable.machine, /obj/machinery/door) && src.cable.machine == src.hackdoor && get_dist(src, src.hackdoor) <= 1) - hackprogress += rand(1, 10) - else - src.temp = "Door Jack: Connection to airlock has been lost. Hack aborted." - hackprogress = 0 - src.hackdoor = null - return - if(hackprogress >= 100) // This is clunky, but works. We need to make sure we don't ever display a progress greater than 100, - hackprogress = 100 // but we also need to reset the progress AFTER it's been displayed - if(src.screen == "doorjack" && src.subscreen == 0) // Update our view, if appropriate - src.paiInterface() - if(hackprogress >= 100) - src.hackprogress = 0 - src.cable.machine:open() - sleep(50) // Update every 5 seconds - -// Digital Messenger -/mob/living/silicon/pai/proc/pdamessage() - - var/dat = "

Digital Messenger


" - dat += {"Signal/Receiver Status: - [(pda.toff) ? " \[Off\]" : " \[On\]"]
- Ringer Status: - [(pda.silent) ? " \[Off\]" : " \[On\]"]

"} - dat += "
    " - if(!pda.toff) - for (var/obj/item/device/pda/P in sortAtom(PDAs)) - if (!P.owner||P.toff||P == src.pda||P.hidden) continue - dat += "
  • [P]" - dat += "
  • " - dat += "
" - dat += "Messages:
" - - dat += "" - dat += "" - for(var/index in pda.tnote) - if(index["sent"]) - dat += addtext("") - else - dat += addtext("") - dat += "
To", index["owner"],": ", index["message"], "
From", index["owner"],": ", index["message"], "
" - return dat - -/mob/living/silicon/pai/proc/translator_toggle() - - // Sol Common, Tradeband, Gutter and Trinary are added with New() and are therefore the current default, always active languages - - if(translator_on) - translator_on = 0 - - remove_language("Sinta'unathi") - remove_language("Siik'tajr") - remove_language("Skrellian") - remove_language("Vox-pidgin") - remove_language("Rootspeak") - remove_language("Trinary") - remove_language("Chittin") - remove_language("Bubblish") - remove_language("Clownish") - - src << "\blue Translator Module toggled OFF." - - else - translator_on = 1 - - add_language("Sinta'unathi") - add_language("Siik'tajr") - add_language("Skrellian") - add_language("Vox-pidgin") - add_language("Rootspeak") - add_language("Trinary") - add_language("Chittin") - add_language("Bubblish") - add_language("Clownish") - - src << "\blue Translator Module toggled ON." \ No newline at end of file + else if(href_list["image"]) + var/img = text2num(href_list["image"]) + if(1 <= img && img <= 9) + card.setEmotion(img) + return 1 \ No newline at end of file diff --git a/code/modules/mob/living/silicon/pai/software_modules.dm b/code/modules/mob/living/silicon/pai/software_modules.dm new file mode 100644 index 00000000000..7a80859d1b6 --- /dev/null +++ b/code/modules/mob/living/silicon/pai/software_modules.dm @@ -0,0 +1,539 @@ +/datum/pai_software + // Name for the software. This is used as the button text when buying or opening/toggling the software + var/name = "pAI software module" + // RAM cost; pAIs start with 100 RAM, spending it on programs + var/ram_cost = 0 + // ID for the software. This must be unique + var/id = "" + // Whether this software is a toggle or not + // Toggled software should override toggle() and is_active() + // Non-toggled software should override on_ui_interact() and Topic() + var/toggle = 1 + // Whether pAIs should automatically receive this module at no cost + var/default = 0 + + proc/on_ui_interact(mob/living/silicon/pai/user, datum/nanoui/ui=null, force_open=1) + return + + proc/toggle(mob/living/silicon/pai/user) + return + + proc/is_active(mob/living/silicon/pai/user) + return 0 + +/datum/pai_software/directives + name = "Directives" + ram_cost = 0 + id = "directives" + toggle = 0 + default = 1 + + on_ui_interact(mob/living/silicon/pai/user, datum/nanoui/ui=null, force_open=1) + var/data[0] + + data["master"] = user.master + data["dna"] = user.master_dna + data["prime"] = user.pai_law0 + data["supplemental"] = user.pai_laws + + ui = nanomanager.try_update_ui(user, user, id, ui, data, force_open) + if(!ui) + // Don't copy-paste this unless you're making a pAI software module! + ui = new(user, user, id, "pai_directives.tmpl", "pAI Directives", 450, 600) + ui.set_initial_data(data) + ui.open() + ui.set_auto_update(1) + + Topic(href, href_list) + var/mob/living/silicon/pai/P = usr + if(!istype(P)) return + + if(href_list["getdna"]) + var/mob/living/M = P.loc + var/count = 0 + + // Find the carrier + while(!istype(M, /mob/living)) + if(!M || !M.loc || count > 6) + //For a runtime where M ends up in nullspace (similar to bluespace but less colourful) + src << "You are not being carried by anyone!" + return 0 + M = M.loc + count++ + + // Check the carrier + 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("[M] presses \his thumb against [P].", 3, "[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." + return 1 + +/datum/pai_software/radio_config + name = "Radio Configuration" + ram_cost = 0 + id = "radio" + toggle = 0 + default = 1 + + on_ui_interact(mob/living/silicon/pai/user, datum/nanoui/ui = null, force_open = 1) + var/data[0] + + data["listening"] = user.radio.broadcasting + data["frequency"] = format_frequency(user.radio.frequency) + + var/channels[0] + for(var/ch_name in user.radio.channels) + var/ch_stat = user.radio.channels[ch_name] + var/ch_dat[0] + ch_dat["name"] = ch_name + // FREQ_LISTENING is const in /obj/item/device/radio + ch_dat["listening"] = !!(ch_stat & user.radio.FREQ_LISTENING) + channels[++channels.len] = ch_dat + + data["channels"] = channels + + ui = nanomanager.try_update_ui(user, user, id, ui, data, force_open) + if(!ui) + ui = new(user, user, id, "pai_radio.tmpl", "Radio Configuration", 300, 150) + ui.set_initial_data(data) + ui.open() + + Topic(href, href_list) + var/mob/living/silicon/pai/P = usr + if(!istype(P)) return + + P.radio.Topic(href, href_list) + return 1 + +/datum/pai_software/crew_manifest + name = "Crew Manifest" + ram_cost = 5 + id = "manifest" + toggle = 0 + + on_ui_interact(mob/living/silicon/pai/user, datum/nanoui/ui=null, force_open=1) + + var/data[0] + data["manifest"] = data_core.get_manifest_json() + // This is dumb, but NanoUI breaks if it has no data to send + data["a"] = "a" + + ui = nanomanager.try_update_ui(user, user, id, ui, data, force_open) + if(!ui) + // Don't copy-paste this unless you're making a pAI software module! + ui = new(user, user, id, "pai_manifest.tmpl", "Crew Manifest", 450, 600) + ui.set_initial_data(data) + ui.open() + ui.set_auto_update(1) + +/datum/pai_software/messenger + name = "Digital Messenger" + ram_cost = 5 + id = "messenger" + toggle = 0 + + on_ui_interact(mob/living/silicon/pai/user, datum/nanoui/ui=null, force_open=1) + var/data[0] + + data["receiver_off"] = user.pda.toff + data["ringer_off"] = user.pda.silent + data["current_ref"] = null + data["current_name"] = user.current_pda_messaging + + var/pdas[0] + if(!user.pda.toff) + for(var/obj/item/device/pda/P in sortAtom(PDAs)) + if(!P.owner || P.toff || P == user.pda || P.hidden) continue + var/pda[0] + pda["name"] = "[P]" + pda["owner"] = "[P.owner]" + pda["ref"] = "\ref[P]" + if(P.owner == user.current_pda_messaging) + data["current_ref"] = "\ref[P]" + pdas[++pdas.len] = pda + + data["pdas"] = pdas + + var/messages[0] + if(user.current_pda_messaging) + for(var/index in user.pda.tnote) + if(index["owner"] != user.current_pda_messaging) + continue + var/msg[0] + var/sent = index["sent"] + msg["sent"] = sent ? 1 : 0 + msg["target"] = index["owner"] + msg["message"] = index["message"] + messages[++messages.len] = msg + + data["messages"] = messages + + ui = nanomanager.try_update_ui(user, user, id, ui, data, force_open) + if(!ui) + // Don't copy-paste this unless you're making a pAI software module! + ui = new(user, user, id, "pai_messenger.tmpl", "Digital Messenger", 450, 600) + ui.set_initial_data(data) + ui.open() + ui.set_auto_update(1) + + Topic(href, href_list) + var/mob/living/silicon/pai/P = usr + if(!istype(P)) return + + if(!isnull(P.pda)) + if(href_list["toggler"]) + P.pda.toff = href_list["toggler"] != "1" + return 1 + else if(href_list["ringer"]) + P.pda.silent = href_list["ringer"] != "1" + return 1 + else if(href_list["select"]) + var/s = href_list["select"] + if(s == "*NONE*") + P.current_pda_messaging = null + else + P.current_pda_messaging = s + return 1 + else if(href_list["target"]) + if(P.silence_time) + return alert("Communications circuits remain uninitialized.") + + var/target = locate(href_list["target"]) + P.pda.create_message(P, target, 1) + return 1 + +/datum/pai_software/med_records + name = "Medical Records" + ram_cost = 15 + id = "med_records" + toggle = 0 + + on_ui_interact(mob/living/silicon/pai/user, datum/nanoui/ui=null, force_open=1) + var/data[0] + + var/records[0] + for(var/datum/data/record/general in sortRecord(data_core.general)) + var/record[0] + record["name"] = general.fields["name"] + record["ref"] = "\ref[general]" + records[++records.len] = record + + data["records"] = records + + var/datum/data/record/G = user.medicalActive1 + var/datum/data/record/M = user.medicalActive2 + data["general"] = G ? G.fields : null + data["medical"] = M ? M.fields : null + data["could_not_find"] = user.medical_cannotfind + + ui = nanomanager.try_update_ui(user, user, id, ui, data, force_open) + if(!ui) + // Don't copy-paste this unless you're making a pAI software module! + ui = new(user, user, id, "pai_medrecords.tmpl", "Medical Records", 450, 600) + ui.set_initial_data(data) + ui.open() + ui.set_auto_update(1) + + Topic(href, href_list) + var/mob/living/silicon/pai/P = usr + if(!istype(P)) return + + if(href_list["select"]) + var/datum/data/record/record = locate(href_list["select"]) + if(record) + var/datum/data/record/R = record + var/datum/data/record/M = null + if (!( data_core.general.Find(R) )) + P.medical_cannotfind = 1 + else + P.medical_cannotfind = 0 + 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 + P.medicalActive1 = R + P.medicalActive2 = M + else + P.medical_cannotfind = 1 + return 1 + +/datum/pai_software/sec_records + name = "Security Records" + ram_cost = 15 + id = "sec_records" + toggle = 0 + + on_ui_interact(mob/living/silicon/pai/user, datum/nanoui/ui=null, force_open=1) + var/data[0] + + var/records[0] + for(var/datum/data/record/general in sortRecord(data_core.general)) + var/record[0] + record["name"] = general.fields["name"] + record["ref"] = "\ref[general]" + records[++records.len] = record + + data["records"] = records + + var/datum/data/record/G = user.securityActive1 + var/datum/data/record/S = user.securityActive2 + data["general"] = G ? G.fields : null + data["security"] = S ? S.fields : null + data["could_not_find"] = user.security_cannotfind + + ui = nanomanager.try_update_ui(user, user, id, ui, data, force_open) + if(!ui) + // Don't copy-paste this unless you're making a pAI software module! + ui = new(user, user, id, "pai_secrecords.tmpl", "Security Records", 450, 600) + ui.set_initial_data(data) + ui.open() + ui.set_auto_update(1) + + Topic(href, href_list) + var/mob/living/silicon/pai/P = usr + if(!istype(P)) return + + if(href_list["select"]) + var/datum/data/record/record = locate(href_list["select"]) + if(record) + var/datum/data/record/R = record + var/datum/data/record/S = null + if (!( data_core.general.Find(R) )) + P.securityActive1 = null + P.securityActive2 = null + P.security_cannotfind = 1 + else + P.security_cannotfind = 0 + for(var/datum/data/record/E in data_core.security) + if ((E.fields["name"] == R.fields["name"] || E.fields["id"] == R.fields["id"])) + S = E + P.securityActive1 = R + P.securityActive2 = S + else + P.securityActive1 = null + P.securityActive2 = null + P.security_cannotfind = 1 + return 1 + +/datum/pai_software/door_jack + name = "Door Jack" + ram_cost = 30 + id = "door_jack" + toggle = 0 + + on_ui_interact(mob/living/silicon/pai/user, datum/nanoui/ui=null, force_open=1) + var/data[0] + + data["cable"] = user.cable != null + data["machine"] = user.cable && (user.cable.machine != null) + data["inprogress"] = user.hackdoor != null + data["progress_a"] = round(user.hackprogress / 10) + data["progress_b"] = user.hackprogress % 10 + data["aborted"] = user.hack_aborted + + ui = nanomanager.try_update_ui(user, user, id, ui, data, force_open) + if(!ui) + // Don't copy-paste this unless you're making a pAI software module! + ui = new(user, user, id, "pai_doorjack.tmpl", "Door Jack", 300, 150) + ui.set_initial_data(data) + ui.open() + ui.set_auto_update(1) + + Topic(href, href_list) + var/mob/living/silicon/pai/P = usr + if(!istype(P)) return + + if(href_list["jack"]) + if(P.cable && P.cable.machine) + P.hackdoor = P.cable.machine + P.hackloop() + return 1 + else if(href_list["cancel"]) + P.hackdoor = null + return 1 + else if(href_list["cable"]) + var/turf/T = get_turf_or_move(P.loc) + P.hack_aborted = 0 + P.cable = new /obj/item/weapon/pai_cable(T) + for(var/mob/M in viewers(T)) + M.show_message("A port on [P] opens to reveal [P.cable], which promptly falls to the floor.", 3, + "You hear the soft click of something light and hard falling to the ground.", 2) + return 1 + +/mob/living/silicon/pai/proc/hackloop() + var/turf/T = get_turf_or_move(src.loc) + for(var/mob/living/silicon/ai/AI in player_list) + if(T.loc) + AI << "Network Alert: Brute-force encryption crack in progress in [T.loc]." + else + AI << "Network Alert: Brute-force encryption crack in progress. Unable to pinpoint location." + var/obj/machinery/door/D = cable.machine + if(!istype(D)) + hack_aborted = 1 + hackprogress = 0 + cable.machine = null + hackdoor = null + return + while(hackprogress < 1000) + if(cable && cable.machine == D && cable.machine == hackdoor && get_dist(src, hackdoor) <= 1) + hackprogress = min(hackprogress+rand(1, 20), 1000) + else + hack_aborted = 1 + hackprogress = 0 + hackdoor = null + return + if(hackprogress >= 1000) + hackprogress = 0 + D.open() + cable.machine = null + return + sleep(10) // Update every second + +/datum/pai_software/atmosphere_sensor + name = "Atmosphere Sensor" + ram_cost = 5 + id = "atmos_sense" + toggle = 0 + + on_ui_interact(mob/living/silicon/pai/user, datum/nanoui/ui=null, force_open=1) + var/data[0] + + var/turf/T = get_turf_or_move(user.loc) + if(!T) + data["reading"] = 0 + data["pressure"] = 0 + data["temperature"] = 0 + data["temperatureC"] = 0 + data["gas"] = list() + else + var/datum/gas_mixture/env = T.return_air() + data["reading"] = 1 + var/pres = env.return_pressure() * 10 + data["pressure"] = "[round(pres/10)].[pres%10]" + data["temperature"] = round(env.temperature) + data["temperatureC"] = round(env.temperature-T0C) + + var/t_moles = env.total_moles + var/gases[0] + if(t_moles) + var/n2[0] + n2["name"] = "Nitrogen" + n2["percent"] = round((env.nitrogen/t_moles)*100) + var/o2[0] + o2["name"] = "Oxygen" + o2["percent"] = round((env.oxygen/t_moles)*100) + var/co2[0] + co2["name"] = "Carbon Dioxide" + co2["percent"] = round((env.carbon_dioxide/t_moles)*100) + var/plasma[0] + plasma["name"] = "Plasma" + plasma["percent"] = round((env.toxins/t_moles)*100) + var/other[0] + other["name"] = "Other" + other["percent"] = round(1-((env.oxygen/t_moles)+(env.nitrogen/t_moles)+(env.carbon_dioxide/t_moles)+(env.toxins/t_moles))) + gases[++gases.len] = n2 + gases[++gases.len] = o2 + gases[++gases.len] = co2 + gases[++gases.len] = plasma + gases[++gases.len] = other + data["gas"] = gases + + ui = nanomanager.try_update_ui(user, user, id, ui, data, force_open) + if(!ui) + // Don't copy-paste this unless you're making a pAI software module! + ui = new(user, user, id, "pai_atmosphere.tmpl", "Atmosphere Sensor", 350, 300) + ui.set_initial_data(data) + ui.open() + +/datum/pai_software/sec_hud + name = "Security HUD" + ram_cost = 20 + id = "sec_hud" + + toggle(mob/living/silicon/pai/user) + user.secHUD = !user.secHUD + + is_active(mob/living/silicon/pai/user) + return user.secHUD + +/datum/pai_software/med_hud + name = "Medical HUD" + ram_cost = 20 + id = "med_hud" + + toggle(mob/living/silicon/pai/user) + user.medHUD = !user.medHUD + + is_active(mob/living/silicon/pai/user) + return user.medHUD + +/datum/pai_software/translator + name = "Universal Translator" + ram_cost = 35 + id = "translator" + + toggle(mob/living/silicon/pai/user) + // Sol Common, Tradeband and Gutter are added with New() and are therefore the current default, always active languages + user.translator_on = !user.translator_on + if(user.translator_on) + user.add_language("Sinta'unathi") + user.add_language("Siik'tajr") + user.add_language("Skrellian") + else + user.remove_language("Sinta'unathi") + user.remove_language("Siik'tajr") + user.remove_language("Skrellian") + + is_active(mob/living/silicon/pai/user) + return user.translator_on + +/datum/pai_software/signaller + name = "Remote Signaller" + ram_cost = 5 + id = "signaller" + toggle = 0 + + on_ui_interact(mob/living/silicon/pai/user, datum/nanoui/ui=null, force_open=1) + var/data[0] + + data["frequency"] = format_frequency(user.sradio.frequency) + data["code"] = user.sradio.code + + ui = nanomanager.try_update_ui(user, user, id, ui, data, force_open) + if(!ui) + // Don't copy-paste this unless you're making a pAI software module! + ui = new(user, user, id, "pai_signaller.tmpl", "Signaller", 320, 150) + ui.set_initial_data(data) + ui.open() + + Topic(href, href_list) + var/mob/living/silicon/pai/P = usr + if(!istype(P)) return + + if(href_list["send"]) + P.sradio.send_signal("ACTIVATE") + for(var/mob/O in hearers(1, P.loc)) + O.show_message(text("\icon[] *beep* *beep*", P), 3, "*beep* *beep*", 2) + return 1 + + else if(href_list["freq"]) + var/new_frequency = (P.sradio.frequency + text2num(href_list["freq"])) + if(new_frequency < 1200 || new_frequency > 1600) + new_frequency = sanitize_frequency(new_frequency) + P.sradio.set_frequency(new_frequency) + return 1 + + else if(href_list["code"]) + P.sradio.code += text2num(href_list["code"]) + P.sradio.code = round(P.sradio.code) + P.sradio.code = min(100, P.sradio.code) + P.sradio.code = max(1, P.sradio.code) + return 1 diff --git a/code/modules/nano/nanointeraction.dm b/code/modules/nano/nanointeraction.dm index 6deadd1927b..6380d1faeef 100644 --- a/code/modules/nano/nanointeraction.dm +++ b/code/modules/nano/nanointeraction.dm @@ -111,8 +111,8 @@ if(. == STATUS_UPDATE && (TK in mutations)) // If we have telekinesis and remain close enough, allow interaction. return STATUS_INTERACTIVE -/var/global/datum/topic_state/default_state = new() - +/var/global/datum/topic_state/default_state = new() + /datum/topic_state var/flags = 0 diff --git a/nano/templates/pai_atmosphere.tmpl b/nano/templates/pai_atmosphere.tmpl new file mode 100644 index 00000000000..b1a0b0404b8 --- /dev/null +++ b/nano/templates/pai_atmosphere.tmpl @@ -0,0 +1,36 @@ + + +{{if data.reading}} +
+
+ Pressure: +
+
+ {{:data.pressure}} kPa +
+
+ +
+
+ Temperature: +
+
+ {{:data.temperature}}K ({{:data.temperatureC}}°C) +
+
+ + {{for data.gas}} +
+
+ {{:value.name}}: +
+
+ {{:value.percent}}% +
+
+ {{/for}} +{{else}} + Unable to obtain air reading! +{{/if}} diff --git a/nano/templates/pai_directives.tmpl b/nano/templates/pai_directives.tmpl new file mode 100644 index 00000000000..2caff849a26 --- /dev/null +++ b/nano/templates/pai_directives.tmpl @@ -0,0 +1,50 @@ + + +
+
+ Master: +
+
+ {{if data.master}} + {{:data.master}} ({{:data.dna}}) + {{:helper.link("Request carrier DNA sample", '', {"stopic":"directives", "getdna":1})}} + {{else}} + None + {{/if}} +
+
+ +
+
+ Prime Directive: +
+
+ {{:data.prime}} +
+
+ +
+
+ Supplemental Directive(s): +
+
+ {{if data.supplemental}} + {{:data.supplemental}} + {{else}} + None + {{/if}} +
+
+ +
+ + 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. + +
\ No newline at end of file diff --git a/nano/templates/pai_doorjack.tmpl b/nano/templates/pai_doorjack.tmpl new file mode 100644 index 00000000000..6862663b9b9 --- /dev/null +++ b/nano/templates/pai_doorjack.tmpl @@ -0,0 +1,44 @@ + + +
+
+ Cable: +
+
+ {{if data.machine}} + {{:helper.link("Connected", '', {}, 'selected')}} + {{else data.cable}} + {{:helper.link("Extended", '', {}, 'yellowButton')}} + {{else}} + {{:helper.link("Retracted", '', {"stopic":"door_jack", "cable":1})}} + {{/if}} +
+
+ +{{if data.machine}} +
+
+ Hack: +
+
+ {{if data.inprogress}} + {{if data.progress_a <= 33}} +
{{:data.progress_a}}.{{:data.progress_b}}%
+ {{else data.progress_a <= 67}} +
{{:data.progress_a}}.{{:data.progress_b}}%
+ {{else}} +
{{:data.progress_a}}.{{:data.progress_b}}%
+ {{/if}} + {{:helper.link("Cancel", '', {"stopic":"door_jack", "cancel":1}, null, 'redButton')}} + {{else}} + {{:helper.link("Start", '', {"stopic":"door_jack", "jack":1})}} + {{/if}} +
+
+{{else data.aborted}} +
+
Hack aborted!
+
+{{/if}} \ No newline at end of file diff --git a/nano/templates/pai_interface.tmpl b/nano/templates/pai_interface.tmpl new file mode 100644 index 00000000000..e6441932665 --- /dev/null +++ b/nano/templates/pai_interface.tmpl @@ -0,0 +1,45 @@ + + + + + Emotion: + + + {{for data.emotions}} + {{:helper.link(value.name, '', {"image" : value.id}, data.current_emotion == value.id ? 'selected' : null)}} + {{/for}} + + + + + + Installed Software: + + + {{for data.bought}} + {{:helper.link(value.name, '', {"software" : value.id}, null, value.on ? 'selected' : null)}} + {{/for}} + + + + + + Available RAM: + + + {{:data.available_ram}} + + + + + + Downloadable Software: + + + {{for data.not_bought}} + {{:helper.link(value.name + " (" + value.ram + ")", '', {"purchase" : value.id}, value.ram <= data.available_ram ? null : 'disabled')}} + {{/for}} + + \ No newline at end of file diff --git a/nano/templates/pai_manifest.tmpl b/nano/templates/pai_manifest.tmpl new file mode 100644 index 00000000000..77de9e3cad1 --- /dev/null +++ b/nano/templates/pai_manifest.tmpl @@ -0,0 +1,86 @@ + + +
+
+ {{if data.manifest.heads}} + + {{for data.manifest["heads"]}} + {{if value.rank == "Captain"}} + + {{else}} + + {{/if}} + {{/for}} + {{/if}} + {{if data.manifest.sec}} + + {{for data.manifest["sec"]}} + {{if value.rank == "Head of Security"}} + + {{else}} + + {{/if}} + {{/for}} + {{/if}} + {{if data.manifest.eng}} + + {{for data.manifest["eng"]}} + {{if value.rank == "Chief Engineer"}} + + {{else}} + + {{/if}} + {{/for}} + {{/if}} + {{if data.manifest.med}} + + {{for data.manifest["med"]}} + {{if value.rank == "Chief Medical Officer"}} + + {{else}} + + {{/if}} + {{/for}} + {{/if}} + {{if data.manifest.sci}} + + {{for data.manifest["sci"]}} + {{if value.rank == "Research Director"}} + + {{else}} + + {{/if}} + {{/for}} + {{/if}} + {{if data.manifest.supp}} + + {{for data.manifest["supp"]}} + {{if value.rank == "Head of Personnel"}} + + {{else}} + + {{/if}} + {{/for}} + {{/if}} + {{if data.manifest.civ}} + + {{for data.manifest["civ"]}} + {{if value.rank == "Head of Personnel"}} + + {{else}} + + {{/if}} + {{/for}} + {{/if}} + {{if data.manifest.misc}} + + {{for data.manifest["misc"]}} + + {{/for}} + {{/if}} +
Command
{{:value.name}}{{:value.rank}}{{:value.active}}
{{:value.name}}{{:value.rank}}{{:value.active}}
Security
{{:value.name}}{{:value.rank}}{{:value.active}}
{{:value.name}}{{:value.rank}}{{:value.active}}
Engineering
{{:value.name}}{{:value.rank}}{{:value.active}}
{{:value.name}}{{:value.rank}}{{:value.active}}
Medical
{{:value.name}}{{:value.rank}}{{:value.active}}
{{:value.name}}{{:value.rank}}{{:value.active}}
Science
{{:value.name}}{{:value.rank}}{{:value.active}}
{{:value.name}}{{:value.rank}}{{:value.active}}
Support
{{:value.name}}{{:value.rank}}{{:value.active}}
{{:value.name}}{{:value.rank}}{{:value.active}}
Civilian
{{:value.name}}{{:value.rank}}{{:value.active}}
{{:value.name}}{{:value.rank}}{{:value.active}}
Misc
{{:value.name}}{{:value.rank}}{{:value.active}}
+
\ No newline at end of file diff --git a/nano/templates/pai_medrecords.tmpl b/nano/templates/pai_medrecords.tmpl new file mode 100644 index 00000000000..37b1d7061ca --- /dev/null +++ b/nano/templates/pai_medrecords.tmpl @@ -0,0 +1,86 @@ + + +{{if data.records}} + {{for data.records}} +
+ {{:helper.link(value.name, '', {"stopic":"med_records", "select":value.ref})}} +
+ {{/for}} +{{/if}} +
+{{if data.general}} +
+
Name
+
{{:data.general.name}}
+
+
+
Record ID
+
{{:data.general.id}}
+
+
+
Sex
+
{{:data.general.sex}}
+
+
+
Species
+
{{:data.general.species}}
+
+
+
Age
+
{{:data.general.age}}
+
+
+
Rank
+
{{:data.general.rank}}
+
+
+
Fingerprint
+
{{:data.general.fingerprint}}
+
+
+
Physical Status
+
{{:data.general.p_stat}}
+
+
+
Mental Status
+
{{:data.general.m_stat}}
+
+{{/if}} +{{if data.medical}} +
+
Blood Type
+
{{:data.medical.b_type}}
+
+
+
Minor Disabilities
+
{{:data.medical.mi_dis}}
+
{{:data.medical.mi_dis_d}}
+
+
+
Major Disabilities
+
{{:data.medical.ma_dis}}
+
{{:data.medical.ma_dis_d}}
+
+
+
Allergies
+
{{:data.medical.alg}}
+
{{:data.medical.alg_d}}
+
+
+
Current Diseases
+
{{:data.medical.cdi}}
+
{{:data.medical.cdi_d}}
+
+
+
Important Notes
+
{{:data.medical.notes}}
+
+{{/if}} + +{{if data.could_not_find}} +
+ Failed to find some records; the information above may be incomplete or missing. +
+{{/if}} \ No newline at end of file diff --git a/nano/templates/pai_messenger.tmpl b/nano/templates/pai_messenger.tmpl new file mode 100644 index 00000000000..fb20f826f27 --- /dev/null +++ b/nano/templates/pai_messenger.tmpl @@ -0,0 +1,80 @@ + + +
+
+ Receiver: +
+
+ {{if data.receiver_off}} + {{:helper.link("On", '', {"stopic":"messenger", "toggler":1})}} + {{:helper.link("Off", '', {"stopic":"messenger", "toggler":2}, 'selected')}} + {{else}} + {{:helper.link("On", '', {"stopic":"messenger", "toggler":1}, 'selected')}} + {{:helper.link("Off", '', {"stopic":"messenger", "toggler":2})}} + {{/if}} +
+
+ +
+
+ Ringer: +
+
+ {{if data.ringer_off}} + {{:helper.link("On", '', {"stopic":"messenger", "ringer":1})}} + {{:helper.link("Off", '', {"stopic":"messenger", "ringer":2}, 'selected')}} + {{else}} + {{:helper.link("On", '', {"stopic":"messenger", "ringer":1}, 'selected')}} + {{:helper.link("Off", '', {"stopic":"messenger", "ringer":2})}} + {{/if}} +
+
+ +{{for data.pdas}} +
+ {{:helper.link(value.name, '', {"stopic":"messenger", "select":value.owner})}} + {{:helper.link("Quick Message", '', {"stopic":"messenger", "target":value.ref})}} +
+{{/for}} +
+{{if data.current_ref}} +
+
+ Selected PDA: +
+
+
{{:data.current_name}}
+ {{:helper.link("Send Message", '', {"stopic":"messenger", "target":data.current_ref})}} +
+
+{{else data.current_name}} +
+
+ Selected PDA: +
+
+ {{:data.current_name}} (Cannot send!) +
+
+{{/if}} + +{{for data.messages}} +
+
+ {{if value.sent}} + To {{:value.target}}: + {{else}} + From {{:value.target}}: + {{/if}} +
+
+ {{:value.message}} +
+
+{{/for}} + +{{if data.current_name}} + {{:helper.link("Clear Screen", '', {"stopic":"messenger", "select":"*NONE*"})}} +{{/if}} diff --git a/nano/templates/pai_radio.tmpl b/nano/templates/pai_radio.tmpl new file mode 100644 index 00000000000..bcdacdb48f1 --- /dev/null +++ b/nano/templates/pai_radio.tmpl @@ -0,0 +1,48 @@ + + +
+
+ Microphone: +
+
+ {{if data.listening}} + {{:helper.link("On", '', {"stopic":"radio", "talk":1}, 'selected')}} + {{:helper.link("Off", '', {"stopic":"radio", "talk":1})}} + {{else}} + {{:helper.link("On", '', {"stopic":"radio", "talk":1})}} + {{:helper.link("Off", '', {"stopic":"radio", "talk":1}, 'selected')}} + {{/if}} +
+
+ +
+
+ Frequency: +
+
+ {{:helper.link("--", '', {"stopic":"radio", "freq":-10})}} + {{:helper.link("-", '', {"stopic":"radio", "freq": -2})}} +
{{:data.frequency}}
+ {{:helper.link("+", '', {"stopic":"radio", "freq": 2})}} + {{:helper.link("++", '', {"stopic":"radio", "freq": 10})}} +
+
+ +{{for data.channels}} +
+
+ {{:value.name}} +
+
+ {{if value.listening}} + {{:helper.link("On", '', {"stopic":"radio", "channel":value.name, "listen":1}, 'selected')}} + {{:helper.link("Off", '', {"stopic":"radio", "channel":value.name, "listen":1})}} + {{else}} + {{:helper.link("On", '', {"stopic":"radio", "channel":value.name, "listen":1})}} + {{:helper.link("Off", '', {"stopic":"radio", "channel":value.name, "listen":1}, 'selected')}} + {{/if}} +
+
+{{/for}} \ No newline at end of file diff --git a/nano/templates/pai_secrecords.tmpl b/nano/templates/pai_secrecords.tmpl new file mode 100644 index 00000000000..e139034c0de --- /dev/null +++ b/nano/templates/pai_secrecords.tmpl @@ -0,0 +1,76 @@ + + +{{if data.records}} + {{for data.records}} +
+ {{:helper.link(value.name, '', {"stopic":"sec_records", "select":value.ref})}} +
+ {{/for}} +{{/if}} +
+{{if data.general}} +
+
Name
+
{{:data.general.name}}
+
+
+
Record ID
+
{{:data.general.id}}
+
+
+
Sex
+
{{:data.general.sex}}
+
+
+
Species
+
{{:data.general.species}}
+
+
+
Age
+
{{:data.general.age}}
+
+
+
Rank
+
{{:data.general.rank}}
+
+
+
Fingerprint
+
{{:data.general.fingerprint}}
+
+
+
Physical Status
+
{{:data.general.p_stat}}
+
+
+
Mental Status
+
{{:data.general.m_stat}}
+
+{{/if}} +{{if data.security}} +
+
Criminal Status
+
{{:data.security.criminal}}
+
+
+
Minor Crimes
+
{{:data.security.mi_crim}}
+
{{:data.security.mi_crim_d}}
+
+
+
Major Crimes
+
{{:data.security.ma_crim}}
+
{{:data.security.ma_crim_d}}
+
+
+
Important Notes
+
{{:data.security.notes}}
+
+{{/if}} + +{{if data.could_not_find}} +
+ Failed to find some records; the information above may be incomplete or missing. +
+{{/if}} \ No newline at end of file diff --git a/nano/templates/pai_signaller.tmpl b/nano/templates/pai_signaller.tmpl new file mode 100644 index 00000000000..20c2781bc9b --- /dev/null +++ b/nano/templates/pai_signaller.tmpl @@ -0,0 +1,33 @@ + + +
+
+ Frequency: +
+
+ {{:helper.link("--", '', {"stopic":"signaller", "freq":-10})}} + {{:helper.link("-", '', {"stopic":"signaller", "freq": -2})}} +
{{:data.frequency}}
+ {{:helper.link("+", '', {"stopic":"signaller", "freq": 2})}} + {{:helper.link("++", '', {"stopic":"signaller", "freq": 10})}} +
+
+ +
+
+ Code: +
+
+ {{:helper.link("--", '', {"stopic":"signaller", "code":-10})}} + {{:helper.link("-", '', {"stopic":"signaller", "code": -1})}} +
{{:data.code}}
+ {{:helper.link("+", '', {"stopic":"signaller", "code": 1})}} + {{:helper.link("++", '', {"stopic":"signaller", "code": 10})}} +
+
+ +
+ {{:helper.link("Send", '', {"stopic":"signaller", "send":1})}} +
\ No newline at end of file diff --git a/paradise.dme b/paradise.dme index c5f54875ab0..32726c010dd 100644 --- a/paradise.dme +++ b/paradise.dme @@ -1338,6 +1338,7 @@ #include "code\modules\mob\living\silicon\pai\recruit.dm" #include "code\modules\mob\living\silicon\pai\say.dm" #include "code\modules\mob\living\silicon\pai\software.dm" +#include "code\modules\mob\living\silicon\pai\software_modules.dm" #include "code\modules\mob\living\silicon\robot\component.dm" #include "code\modules\mob\living\silicon\robot\death.dm" #include "code\modules\mob\living\silicon\robot\emote.dm"