diff --git a/code/__HELPERS/global_lists.dm b/code/__HELPERS/global_lists.dm index cbc691e3c2a..faf94d8c177 100644 --- a/code/__HELPERS/global_lists.dm +++ b/code/__HELPERS/global_lists.dm @@ -72,8 +72,6 @@ to_chat(world, "pAI software module [P.name] has the same key as [O.name]!") continue GLOB.pai_software_by_key[P.id] = P - if(P.default) - GLOB.default_pai_software[P.id] = P // Setup loadout gear for(var/geartype in subtypesof(/datum/gear)) diff --git a/code/modules/mob/living/silicon/pai/life.dm b/code/modules/mob/living/silicon/pai/life.dm index 321362217a1..5520afe0cea 100644 --- a/code/modules/mob/living/silicon/pai/life.dm +++ b/code/modules/mob/living/silicon/pai/life.dm @@ -7,12 +7,12 @@ silence_time = null to_chat(src, "Communication circuit reinitialized. Speech and messaging functionality restored.") - if(cable) - if(get_dist(src, cable) > 1) - var/turf/T = get_turf_or_move(loc) - for(var/mob/M in viewers(T)) - M.show_message("The data cable rapidly retracts back into its spool.", 3, "You hear a click and the sound of wire spooling rapidly.", 2) - QDEL_NULL(cable) + if(installed_software["doorjack"]) + var/datum/pai_software/door_jack/DJ = installed_software["doorjack"] + if(DJ.cable) + if(get_dist(src, DJ.cable) > 1) + visible_message("The data cable connected to [src] rapidly retracts back into its spool!") + QDEL_NULL(DJ.cable) /mob/living/silicon/pai/updatehealth(reason = "none given") if(status_flags & GODMODE) diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm index 0a4e4cee898..a0edbc01c07 100644 --- a/code/modules/mob/living/silicon/pai/pai.dm +++ b/code/modules/mob/living/silicon/pai/pai.dm @@ -9,11 +9,8 @@ pass_flags = PASSTABLE density = 0 holder_type = /obj/item/holder/pai - var/network = "SS13" - var/obj/machinery/camera/current = null var/ram = 100 // Used as currency to purchase different abilities - var/list/software = list() var/userDNA // The DNA string of our assigned user var/obj/item/paicard/card // The card we inhabit var/obj/item/radio/radio // Our primary radio @@ -42,9 +39,6 @@ ) - - var/obj/item/pai_cable/cable // The cable we produce and use when door or camera jacking - var/master // Name of the one who commands us var/master_dna // DNA string for owner verification // Keeping this separate from the laws var, it should be much more difficult to modify @@ -64,17 +58,11 @@ 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 + /// Currently active software + var/datum/pai_software/active_software - 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 + /// List of all installed software + var/list/datum/pai_software/installed_software = list() var/obj/item/integrated_radio/signal/sradio // AI's signaller @@ -109,20 +97,20 @@ //PDA pda = new(src) - spawn(5) - pda.ownjob = "Personal Assistant" - pda.owner = text("[]", src) - pda.name = pda.owner + " (" + pda.ownjob + ")" - var/datum/data/pda/app/messenger/M = pda.find_program(/datum/data/pda/app/messenger) - M.toff = 1 - ..() + pda.ownjob = "Personal Assistant" + pda.owner = "[src]" + pda.name = "[pda.owner] ([pda.ownjob])" + var/datum/data/pda/app/messenger/M = pda.find_program(/datum/data/pda/app/messenger) + M.toff = TRUE -/mob/living/silicon/pai/Destroy() - medicalActive1 = null - medicalActive2 = null - securityActive1 = null - securityActive2 = null - return ..() + // Software modules. No these var names have nothing to do with photoshop + for(var/PS in subtypesof(/datum/pai_software)) + var/datum/pai_software/PSD = new PS(src) + if(PSD.default) + installed_software[PSD.id] = PSD + + active_software = installed_software["mainmenu"] // Default us to the main menu + ..() /mob/living/silicon/pai/can_unbuckle() return FALSE @@ -159,12 +147,6 @@ for(var/obj/effect/proc_holder/P in proc_holder_list) statpanel("[P.panel]","",P) -/mob/living/silicon/pai/check_eye(var/mob/user as mob) - if(!current) - return null - user.reset_perspective(current) - return 1 - /mob/living/silicon/pai/blob_act() if(stat != DEAD) adjustBruteLoss(60) @@ -229,7 +211,7 @@ return -// See software.dm for Topic() +// See software.dm for tgui_act() /mob/living/silicon/pai/attack_animal(mob/living/simple_animal/M) . = ..() @@ -238,78 +220,6 @@ add_attack_logs(M, src, "Animal attacked for [damage] damage") adjustBruteLoss(damage) -/mob/living/silicon/pai/proc/switchCamera(var/obj/machinery/camera/C) - usr:cameraFollow = null - if(!C) - unset_machine() - reset_perspective(null) - return 0 - if(stat == 2 || !C.status || !(network in C.network)) return 0 - - // ok, we're alive, camera is good and in our network... - - set_machine(src) - src:current = C - reset_perspective(C) - return 1 - -/mob/living/silicon/pai/verb/reset_record_view() - set category = "pAI Commands" - set name = "Reset Records Software" - - securityActive1 = null - securityActive2 = null - security_cannotfind = 0 - medicalActive1 = null - medicalActive2 = null - medical_cannotfind = 0 - SSnanoui.update_uis(src) - to_chat(usr, "You reset your record-viewing software.") - -/mob/living/silicon/pai/cancel_camera() - set category = "pAI Commands" - set name = "Cancel Camera View" - reset_perspective(null) - unset_machine() - src:cameraFollow = null - -//Addition by Mord_Sith to define AI's network change ability -/* -/mob/living/silicon/pai/proc/pai_network_change() - set category = "pAI Commands" - set name = "Change Camera Network" - reset_perspective(null) - unset_machine() - src:cameraFollow = null - var/cameralist[0] - - if(usr.stat == 2) - to_chat(usr, "You can't change your camera network because you are dead!") - return - - for(var/obj/machinery/camera/C in Cameras) - if(!C.status) - continue - else - if(C.network != "CREED" && C.network != "thunder" && C.network != "RD" && C.network != "toxins" && C.network != "Prison") COMPILE ERROR! This will have to be updated as camera.network is no longer a string, but a list instead - cameralist[C.network] = C.network - - network = input(usr, "Which network would you like to view?") as null|anything in cameralist - to_chat(src, "Switched to [network] camera network.") -//End of code by Mord_Sith -*/ - - -/* -// Debug command - Maybe should be added to admin verbs later -/mob/verb/makePAI(var/turf/t in view()) - var/obj/item/paicard/card = new(t) - var/mob/living/silicon/pai/pai = new(card) - pai.key = key - card.setPersonality(pai) - -*/ - // Procs/code after this point is used to convert the stationary pai item into a // mobile pai mob. This also includes handling some of the general shit that can occur // to it. Really this deserves its own file, but for the moment it can sit here. ~ Z diff --git a/code/modules/mob/living/silicon/pai/software.dm b/code/modules/mob/living/silicon/pai/software.dm index 574dcda3230..ecc177a641c 100644 --- a/code/modules/mob/living/silicon/pai/software.dm +++ b/code/modules/mob/living/silicon/pai/software.dm @@ -10,113 +10,41 @@ GLOBAL_LIST_INIT(pai_emotions, list( "What" = 9 )) - GLOBAL_LIST_EMPTY(pai_software_by_key) -GLOBAL_LIST_EMPTY(default_pai_software) - -/mob/living/silicon/pai/New() - ..() - software = GLOB.default_pai_software.Copy() /mob/living/silicon/pai/verb/paiInterface() set category = "pAI Commands" set name = "Software Interface" - ui_interact(src) + tgui_interact(src) -/mob/living/silicon/pai/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = 1, datum/topic_state/state = GLOB.self_state) - if(ui_key != "main") - var/datum/pai_software/S = software[ui_key] - if(S && !S.toggle) - ui = SSnanoui.try_update_ui(user, src, S.id, ui, force_open) - if(!ui) - ui = new(user, src, S.id, S.template_file, S.ui_title, S.ui_width, S.ui_height, state = state) - ui.open() - if(S.autoupdate) - ui.set_auto_update(1) - else - if(ui) - ui.set_status(STATUS_CLOSE, 0) - return - - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) +/mob/living/silicon/pai/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_self_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "pai_interface.tmpl", "pAI Software Interface", 450, 600, state = state) + ui = new(user, src, ui_key, "PAI", name, 600, 650, master_ui, state) ui.open() - ui.set_auto_update(1) -/mob/living/silicon/pai/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.self_state) - var/data[0] - - if(ui_key != "main") - var/datum/pai_software/S = software[ui_key] - if(S && !S.toggle) - return S.on_ui_data(user, state) - log_runtime(EXCEPTION("Unrecognized/invalid pAI UI state '[ui_key]'"), src) - return - // Software we have bought - var/bought_software[0] - // Software we have not bought - var/not_bought_software[0] - - for(var/key in GLOB.pai_software_by_key) - var/datum/pai_software/S = GLOB.pai_software_by_key[key] - var/software_data[0] - software_data["name"] = S.name - software_data["id"] = S.id - if(key in software) - software_data["on"] = S.is_active(src) - bought_software[++bought_software.len] = software_data - else - software_data["ram"] = S.ram_cost - not_bought_software[++not_bought_software.len] = software_data - - data["bought"] = bought_software - data["not_bought"] = not_bought_software - data["available_ram"] = ram - - // Emotions - var/emotions[0] - for(var/name in GLOB.pai_emotions) - var/emote[0] - emote["name"] = name - emote["id"] = GLOB.pai_emotions[name] - emotions[++emotions.len] = emote - - data["emotions"] = emotions - data["current_emotion"] = card.current_emotion +/mob/living/silicon/pai/tgui_data(mob/user) + var/list/data = list() + data["app_template"] = active_software.template_file + data["app_icon"] = active_software.ui_icon + data["app_title"] = active_software.name + data["app_data"] = active_software.get_app_data(user) return data -/mob/living/silicon/pai/Topic(href, href_list) +// Yes the stupid amount of args here is important, so we can proxy stuff to child UIs +/mob/living/silicon/pai/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state) if(..()) - return 1 + return - if(href_list["software"]) - var/soft = href_list["software"] - var/datum/pai_software/S = software[soft] - if(S.toggle) - S.toggle(src) + . = TRUE + + switch(action) + // This call is global to all templates, hence the prefix + if("MASTER_back") + active_software = installed_software["mainmenu"] + // Bail early + return else - 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) - - else if(href_list["purchase"]) - var/soft = href_list["purchase"] - var/datum/pai_software/S = GLOB.pai_software_by_key[soft] - if(S && (ram >= S.ram_cost)) - ram -= S.ram_cost - software[S.id] = S - return 1 - - else if(href_list["image"]) - var/img = text2num(href_list["image"]) - if(1 <= img && img <= 9) - card.setEmotion(img) - return 1 + active_software.tgui_act(action, params, ui, state) diff --git a/code/modules/mob/living/silicon/pai/software/_base.dm b/code/modules/mob/living/silicon/pai/software/_base.dm new file mode 100644 index 00000000000..c761e900f46 --- /dev/null +++ b/code/modules/mob/living/silicon/pai/software/_base.dm @@ -0,0 +1,115 @@ +/** + * # pAI Software + * + * Datum module for pAI software + * + * Very similar to the PDA app datum, this determines what UI sub-template to use, + * as well as the RAM cost, and if it is toggle software (not a UI app) + * + */ +/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 + // Toggled software should override toggle() and is_active() + // Non-toggled software should override get_app_data() and Topic() + /// Whether this software is a toggle or not + var/toggle_software = FALSE + /// Do we have this software installed by default + var/default = FALSE + /// Template for the TGUI file + var/template_file = "oops" + /// Icon for inside the UI + var/ui_icon = "file-code" + /// pAI which holds this software + var/mob/living/silicon/pai/pai_holder + +/** + * New handler + * + * Ensures that the pai_holder var is set to the pAI itself + * Arguments: + * * user - The pAI that this softawre is held by + */ +/datum/pai_software/New(mob/living/silicon/pai/user) + pai_holder = user + ..() + +/** + * Handler for the app's UI data + * + * This returns the list of the current app's data for the UI + * This will then be injected as a variable on the TGUI data called "app_data" + * + * Arguments: + * * user - The pAI that is using this app + */ +/datum/pai_software/proc/get_app_data(mob/living/silicon/pai/user) + return list() + +/** + * Handler for toggling toggle apps on and off + * + * This is invoked whenever you toggle a toggleable function + * Put your toggleable work in here + * + * Arguments: + * * user - The pAI that is using this toggle + */ +/datum/pai_software/proc/toggle(mob/living/silicon/pai/user) + return + +/** + * Helper for checking if a toggle is enabled or not + * + * Returns TRUE if the toggle software is active, FALSE if not + * + * Its like this instead of a simple `is_toggled` var because some toggles override eachother and this is easier + * + * Arguments: + * * user - The pAI that is using this app + */ +/datum/pai_software/proc/is_active(mob/living/silicon/pai/user) + return FALSE + +/** + * Helper proc so that pAIs can get the mob holding them + * + * This needs to exist because pAIs have many different locs + * (Held card, mob itself, in pocket, etc) + * + * Arguments: + * * inform - Boolean, should we inform the pAI if they fail to find a carrier + */ +/datum/pai_software/proc/get_holding_mob(inform = FALSE) + var/mob/living/M = pai_holder.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) + if(inform) + to_chat(usr, "You are not being carried by anyone!") + return null + M = M.loc + count++ + + return M + +/** + * tgui_act sanity check helper + * + * Basically checks the existing href exploit stuff, as well as making sure the user using the UI is the pAI itself + */ +/datum/pai_software/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state) + if(..()) + return TRUE + + if(usr != pai_holder) + return TRUE + + return FALSE diff --git a/code/modules/mob/living/silicon/pai/software/pai_apps.dm b/code/modules/mob/living/silicon/pai/software/pai_apps.dm new file mode 100644 index 00000000000..e5c7b7ac725 --- /dev/null +++ b/code/modules/mob/living/silicon/pai/software/pai_apps.dm @@ -0,0 +1,388 @@ +// Main Menu // +/datum/pai_software/main_menu + name = "Main Menu" + id = "mainmenu" + default = TRUE + template_file = "pai_main_menu" + ui_icon = "home" + +/datum/pai_software/main_menu/get_app_data(mob/living/silicon/pai/user) + var/list/data = list() + data["available_ram"] = user.ram + + // Emotions + var/list/emotions = list() + for(var/name in GLOB.pai_emotions) + var/list/emote = list() + emote["name"] = name + emote["id"] = GLOB.pai_emotions[name] + emotions[++emotions.len] = emote + + data["emotions"] = emotions + data["current_emotion"] = user.card.current_emotion + + var/list/available_s = list() + for(var/s in GLOB.pai_software_by_key) + var/datum/pai_software/PS = GLOB.pai_software_by_key[s] + available_s += list(list("name" = PS.name, "key" = PS.id, "icon" = PS.ui_icon, "cost" = PS.ram_cost)) + + // Split to installed software and toggles for the UI + var/list/installed_s = list() + var/list/installed_t = list() + for(var/s in pai_holder.installed_software) + var/datum/pai_software/PS = pai_holder.installed_software[s] + if(PS.toggle_software) + installed_t += list(list("name" = PS.name, "key" = PS.id, "icon" = PS.ui_icon, "active" = PS.is_active(user))) + else + installed_s += list(list("name" = PS.name, "key" = PS.id, "icon" = PS.ui_icon)) + + data["available_software"] = available_s + data["installed_software"] = installed_s + data["installed_toggles"] = installed_t + + return data + +/datum/pai_software/main_menu/tgui_act(action, list/params) + if(..()) + return + + switch(action) + if("purchaseSoftware") + var/datum/pai_software/S = GLOB.pai_software_by_key[params["key"]] + if(S && (pai_holder.ram >= S.ram_cost)) + var/datum/pai_software/newPS = new S.type(pai_holder) + pai_holder.ram -= newPS.ram_cost + pai_holder.installed_software[newPS.id] = newPS + if("setEmotion") + var/emotion = clamp(text2num(params["emotion"]), 1, 9) + pai_holder.card.setEmotion(emotion) + if("startSoftware") + var/software_key = params["software_key"] + if(pai_holder.installed_software[software_key]) + pai_holder.active_software = pai_holder.installed_software[software_key] + if("setToggle") + var/toggle_key = params["toggle_key"] + if(pai_holder.installed_software[toggle_key]) + pai_holder.installed_software[toggle_key].toggle(pai_holder) + +// Directives // +/datum/pai_software/directives + name = "Directives" + id = "directives" + default = TRUE + template_file = "pai_directives" + ui_icon = "clipboard-list" + +/datum/pai_software/directives/get_app_data(mob/living/silicon/pai/user) + var/list/data = list() + + data["master"] = user.master + data["dna"] = user.master_dna + data["prime"] = user.pai_law0 + data["supplemental"] = user.pai_laws + + return data + +/datum/pai_software/directives/tgui_act(action, list/params) + if(..()) + return + + . = TRUE + + switch(action) + if("getdna") + var/mob/living/M = get_holding_mob() + if(!istype(M)) + return + + // Check the carrier + var/answer = alert(M, "[pai_holder] is requesting a DNA sample from you. Will you allow it to confirm your identity?", "[pai_holder] Check DNA", "Yes", "No") + if(answer == "Yes") + M.visible_message("[M] presses [M.p_their()] thumb against [pai_holder].", "You press your thumb against [pai_holder].") + var/datum/dna/dna = M.dna + to_chat(usr, "[M]'s UE string: [dna.unique_enzymes]") + if(dna.unique_enzymes == pai_holder.master_dna) + to_chat(usr, "DNA is a match to stored Master DNA.") + else + to_chat(usr, "DNA does not match stored Master DNA.") + else + to_chat(usr, "[M] does not seem like [M.p_they()] [M.p_are()] going to provide a DNA sample willingly.") + +// Crew Manifest // +/datum/pai_software/crew_manifest + name = "Crew Manifest" + ram_cost = 5 + id = "manifest" + template_file = "pai_manifest" + ui_icon = "users" + +/datum/pai_software/crew_manifest/get_app_data(mob/living/silicon/pai/user) + var/list/data = list() + + GLOB.data_core.get_manifest_json() + data["manifest"] = GLOB.PDA_Manifest + + return data + +// Med Records // +/datum/pai_software/med_records + name = "Medical Records" + ram_cost = 15 + id = "med_records" + template_file = "pai_medrecords" + ui_icon = "heartbeat" + /// Integrated medical records module to reduce duplicated code + var/datum/data/pda/app/crew_records/medical/integrated_records = new + +/datum/pai_software/med_records/get_app_data(mob/living/silicon/pai/user) + var/list/data = list() + // Just grab the stuff internally + integrated_records.update_ui(user, data) + return data + +/datum/pai_software/med_records/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state) + if(..()) + return + // Double proxy here + integrated_records.tgui_act(action, params, ui, state) + +// Sec Records // +/datum/pai_software/sec_records + name = "Security Records" + ram_cost = 15 + id = "sec_records" + template_file = "pai_secrecords" + ui_icon = "id-badge" + /// Integrated security records module to reduce duplicated code + var/datum/data/pda/app/crew_records/security/integrated_records = new + +/datum/pai_software/sec_records/get_app_data(mob/living/silicon/pai/user) + var/list/data = list() + // Just grab the stuff internally + integrated_records.update_ui(user, data) + return data + +/datum/pai_software/sec_records/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state) + if(..()) + return + // Double proxy here + integrated_records.tgui_act(action, params, ui, state) + +// Atmos Scan // +/datum/pai_software/atmosphere_sensor + name = "Atmosphere Sensor" + ram_cost = 5 + id = "atmos_sense" + template_file = "pai_atmosphere" + ui_icon = "fire" + /// Integrated PDA atmos scan module to reduce duplicated code + var/datum/data/pda/app/atmos_scanner/scanner = new + +/datum/pai_software/atmosphere_sensor/get_app_data(mob/living/silicon/pai/user) + var/list/data = list() + // Just grab the stuff internally + scanner.update_ui(user, data) + return data + +// Messenger // +/datum/pai_software/messenger + name = "Digital Messenger" + ram_cost = 5 + id = "messenger" + template_file = "pai_messenger" + ui_icon = "envelope" + +/datum/pai_software/messenger/get_app_data(mob/living/silicon/pai/user) + var/list/data = list() + + // Some safety checks + if(!user.pda) + CRASH("pAI found without PDA.") + + var/datum/data/pda/app/messenger/PM = user.pda.find_program(/datum/data/pda/app/messenger) + if(!PM) + CRASH("pAI PDA lacks a messenger program") + + // Grab the internal data + PM.update_ui(user, data) + + return data + +/datum/pai_software/messenger/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state) + if(..()) + return + + // Grab their messenger + var/datum/data/pda/app/messenger/PM = pai_holder.pda.find_program(/datum/data/pda/app/messenger) + // Double proxy here + PM.tgui_act(action, params, ui, state) + +// Radio +/datum/pai_software/radio_config + name = "Radio Configuration" + id = "radio" + default = TRUE + template_file = "pai_radio" + ui_icon = "broadcast-tower" + +/datum/pai_software/radio_config/get_app_data(mob/living/silicon/pai/user) + var/list/data = list() + data["frequency"] = user.radio.frequency + data["minFrequency"] = PUBLIC_LOW_FREQ + data["maxFrequency"] = PUBLIC_HIGH_FREQ + data["broadcasting"] = user.radio.broadcasting + return data + +/datum/pai_software/radio_config/tgui_act(action, list/params) + if(..()) + return + + switch(action) + if("toggleBroadcast") + // Just toggle it + pai_holder.radio.broadcasting = !pai_holder.radio.broadcasting + + if("freq") + var/new_frequency = sanitize_frequency(text2num(params["freq"]) * 10) + pai_holder.radio.set_frequency(new_frequency) + +// Signaler // +/datum/pai_software/signaler + name = "Remote Signaler" + ram_cost = 5 + id = "signaler" + template_file = "pai_signaler" + ui_icon = "rss" + +/datum/pai_software/signaler/get_app_data(mob/living/silicon/pai/user) + var/list/data = list() + + data["frequency"] = user.sradio.frequency + data["code"] = user.sradio.code + data["minFrequency"] = PUBLIC_LOW_FREQ + data["maxFrequency"] = PUBLIC_HIGH_FREQ + + return data + +/datum/pai_software/signaler/tgui_act(action, list/params) + if(..()) + return + + switch(action) + if("signal") + pai_holder.sradio.send_signal("ACTIVATE") + + if("freq") + var/new_frequency = sanitize_frequency(text2num(params["freq"]) * 10) + pai_holder.sradio.set_frequency(new_frequency) + + if("code") + pai_holder.sradio.code = clamp(text2num(params["code"]), 1, 100) + +// Door Jack // +/datum/pai_software/door_jack + name = "Door Jack" + ram_cost = 30 + id = "door_jack" + template_file = "pai_doorjack" + ui_icon = "door-open" + /// Progress on hacking the door + var/progress = 0 + /// Are we hacking? + var/hacking = FALSE + /// The cable being plugged into a door + var/obj/item/pai_cable/cable + /// The door being hacked + var/obj/machinery/door/hackdoor + +/datum/pai_software/door_jack/get_app_data(mob/living/silicon/pai/user) + var/list/data = list() + + data["cable"] = (cable != null) + data["machine"] = (cable?.machine != null) + data["inprogress"] = (hackdoor != null) + data["progress"] = progress + + return data + +/datum/pai_software/door_jack/tgui_act(action, list/params) + if(..()) + return + + switch(action) + if("jack") + if(cable && cable.machine) + hackdoor = cable.machine + if(hacking) + to_chat(usr, "You are already hacking that door!") + else + hacking = TRUE + INVOKE_ASYNC(src, /datum/pai_software/door_jack/.proc/hackloop) + if("cancel") + hackdoor = null + if("cable") + if(cable) + to_chat(usr, "You already have a cable deployed!") + return + var/turf/T = get_turf(pai_holder) + cable = new /obj/item/pai_cable(T) + pai_holder.visible_message("A port on [pai_holder] opens to reveal [cable], which promptly falls to the floor.") + +/** + * Door jack hack loop + * + * Self-contained proc for handling the hacking of a door. + * Invoked asyncly, but will only allow one instance at a time + */ +/datum/pai_software/door_jack/proc/hackloop() + var/obj/machinery/door/D = cable.machine + if(!istype(D)) + cleanup_hack() + return + while(progress < 100) + if(cable && cable.machine == D && cable.machine == hackdoor && get_dist(pai_holder, hackdoor) <= 1) + progress = min(progress + rand(1, 20), 100) + else + cleanup_hack() + return + if(progress >= 100) + D.open() + cleanup_hack() + return + sleep(1 SECONDS) // Update every second + +/** + * Door jack cleanup proc + * + * Self-contained proc for cleaning up failed hack attempts + */ +/datum/pai_software/door_jack/proc/cleanup_hack() + progress = 0 + hackdoor = null + cable.machine = null + QDEL_NULL(cable) + hacking = FALSE + +// Host Bioscan // +/datum/pai_software/host_scan + name = "Host Bioscan" + ram_cost = 5 + id = "bioscan" + template_file = "pai_bioscan" + ui_icon = "heartbeat" + +/datum/pai_software/host_scan/get_app_data(mob/living/silicon/pai/user) + var/list/data = list() + + var/mob/living/held = get_holding_mob(FALSE) + + if(isliving(held)) + data["holder"] = held.name + data["dead"] = (held.stat > UNCONSCIOUS) + data["health"] = held.health + data["brute"] = held.getBruteLoss() + data["oxy"] = held.getOxyLoss() + data["tox"] = held.getToxLoss() + data["burn"] = held.getFireLoss() + + return data diff --git a/code/modules/mob/living/silicon/pai/software/pai_toggles.dm b/code/modules/mob/living/silicon/pai/software/pai_toggles.dm new file mode 100644 index 00000000000..138fb06d710 --- /dev/null +++ b/code/modules/mob/living/silicon/pai/software/pai_toggles.dm @@ -0,0 +1,95 @@ +// Security Hud // +/datum/pai_software/sec_hud + name = "Security HUD" + ram_cost = 20 + id = "sec_hud" + ui_icon = "exclamation-triangle" + toggle_software = TRUE + +/datum/pai_software/sec_hud/toggle(mob/living/silicon/pai/user) + user.secHUD = !user.secHUD + user.remove_med_sec_hud() + if(user.secHUD) + user.add_sec_hud() + +/datum/pai_software/sec_hud/is_active(mob/living/silicon/pai/user) + return user.secHUD + +// Medical Hud // +/datum/pai_software/med_hud + name = "Medical HUD" + ram_cost = 20 + id = "med_hud" + ui_icon = "first-aid" + toggle_software = TRUE + +/datum/pai_software/med_hud/toggle(mob/living/silicon/pai/user) + user.medHUD = !user.medHUD + user.remove_med_sec_hud() + if(user.medHUD) + user.add_med_hud() + +/datum/pai_software/med_hud/is_active(mob/living/silicon/pai/user) + return user.medHUD + +// Universal Translator // +/datum/pai_software/translator + name = "Universal Translator" + ram_cost = 35 + id = "translator" + ui_icon = "language" + toggle_software = TRUE + +/datum/pai_software/translator/toggle(mob/living/silicon/pai/user) + // Galactic Common, Sol Common, Tradeband, Gutter and Trinary 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("Canilunzt") + user.add_language("Skrellian") + user.add_language("Vox-pidgin") + user.add_language("Rootspeak") + user.add_language("Chittin") + user.add_language("Bubblish") + user.add_language("Orluum") + user.add_language("Clownish") + user.add_language("Neo-Russkiya") + else + user.remove_language("Sinta'unathi") + user.remove_language("Siik'tajr") + user.remove_language("Canilunzt") + user.remove_language("Skrellian") + user.remove_language("Vox-pidgin") + user.remove_language("Rootspeak") + user.remove_language("Chittin") + user.remove_language("Bubblish") + user.remove_language("Orluum") + user.remove_language("Clownish") + user.remove_language("Neo-Russkiya") + +/datum/pai_software/translator/is_active(mob/living/silicon/pai/user) + return user.translator_on + +// FLashlight // +/datum/pai_software/flashlight + name = "Flashlight" + ram_cost = 5 + id = "flashlight" + ui_icon = "lightbulb" + toggle_software = TRUE + +/datum/pai_software/flashlight/toggle(mob/living/silicon/pai/user) + var/atom/movable/actual_location = istype(user.loc, /obj/item/paicard) ? user.loc : user + if(!user.flashlight_on) + actual_location.set_light(2) + user.card.set_light(2) + else + actual_location.set_light(0) + user.card.set_light(0) + + user.flashlight_on = !user.flashlight_on + +/datum/pai_software/flashlight/is_active(mob/living/silicon/pai/user) + return user.flashlight_on + diff --git a/code/modules/mob/living/silicon/pai/software_modules.dm b/code/modules/mob/living/silicon/pai/software_modules.dm deleted file mode 100644 index 7161919df7f..00000000000 --- a/code/modules/mob/living/silicon/pai/software_modules.dm +++ /dev/null @@ -1,624 +0,0 @@ -/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_data() and Topic() - var/toggle = 1 - // Whether pAIs should automatically receive this module at no cost - var/default = 0 - - // Vars for pAI nanoUI handling - var/autoupdate = 0 - var/template_file = "oops" - var/ui_title = "somebody forgot to set this" - var/ui_width = 450 - var/ui_height = 600 - -/datum/pai_software/proc/on_ui_data(mob/living/silicon/pai/user, datum/topic_state/state = GLOB.self_state) - return list() - -/datum/pai_software/proc/toggle(mob/living/silicon/pai/user) - return - -/datum/pai_software/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 - - template_file = "pai_directives.tmpl" - ui_title = "pAI Directives" - autoupdate = 1 - -/datum/pai_software/directives/on_ui_data(mob/living/silicon/pai/user, datum/topic_state/state = GLOB.self_state) - var/data[0] - - data["master"] = user.master - data["dna"] = user.master_dna - data["prime"] = user.pai_law0 - data["supplemental"] = user.pai_laws - - return data - -/datum/pai_software/directives/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) - to_chat(P, "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 [M.p_their()] thumb against [P].", 3, "[P] makes a sharp clicking sound as it extracts DNA material from [M].", 2) - var/datum/dna/dna = M.dna - to_chat(P, "

[M]'s UE string : [dna.unique_enzymes]

") - if(dna.unique_enzymes == P.master_dna) - to_chat(P, "DNA is a match to stored Master DNA.") - else - to_chat(P, "DNA does not match stored Master DNA.") - else - to_chat(P, "[M] does not seem like [M.p_they()] [M.p_are()] 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 - - template_file = "pai_radio.tmpl" - ui_title = "Radio Configuration" - ui_width = 300 - ui_height = 150 - -/datum/pai_software/radio_config/on_ui_data(mob/living/silicon/pai/user, datum/topic_state/state = GLOB.self_state) - 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/radio - ch_dat["listening"] = !!(ch_stat & user.radio.FREQ_LISTENING) - channels[++channels.len] = ch_dat - - data["channels"] = channels - - return data - -/datum/pai_software/radio_config/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 - - autoupdate = 1 - template_file = "pai_manifest.tmpl" - ui_title = "Crew Manifest" - -/datum/pai_software/crew_manifest/on_ui_data(mob/living/silicon/pai/user, datum/topic_state/state = GLOB.self_state) - var/data[0] - - GLOB.data_core.get_manifest_json() - data["manifest"] = GLOB.PDA_Manifest - - return data - -/datum/pai_software/messenger - name = "Digital Messenger" - ram_cost = 5 - id = "messenger" - toggle = 0 - - autoupdate = 1 - template_file = "pai_messenger.tmpl" - ui_title = "Digital Messenger" - -/datum/pai_software/messenger/on_ui_data(mob/living/silicon/pai/user, datum/topic_state/state = GLOB.self_state) - var/data[0] - - if(!user.pda) - log_runtime(EXCEPTION("pAI found without PDA."), user) - return data - var/datum/data/pda/app/messenger/M = user.pda.find_program(/datum/data/pda/app/messenger) - if(!M) - log_runtime(EXCEPTION("pAI PDA lacks a messenger program"), user) - return data - - data["receiver_off"] = M.toff - data["ringer_off"] = M.notify_silent - data["current_ref"] = null - data["current_name"] = user.current_pda_messaging - - var/pdas[0] - if(!M.toff) - for(var/obj/item/pda/P in GLOB.PDAs) - var/datum/data/pda/app/messenger/PM = P.find_program(/datum/data/pda/app/messenger) - - if(P == user.pda || !PM || !PM.can_receive()) - 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 M.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 - - return data - -/datum/pai_software/messenger/Topic(href, href_list) - var/mob/living/silicon/pai/P = usr - if(!istype(P)) return - - if(!isnull(P.pda)) - var/datum/data/pda/app/messenger/M = P.pda.find_program(/datum/data/pda/app/messenger) - if(!M) - return - - if(href_list["toggler"]) - M.toff = href_list["toggler"] != "1" - return 1 - else if(href_list["ringer"]) - M.notify_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"]) - M.create_message(P, target, 1) - return 1 - -/datum/pai_software/med_records - name = "Medical Records" - ram_cost = 15 - id = "med_records" - toggle = 0 - - autoupdate = 1 - template_file = "pai_medrecords.tmpl" - ui_title = "Medical Records" - - -/datum/pai_software/med_records/on_ui_data(mob/living/silicon/pai/user, datum/topic_state/state = GLOB.self_state) - var/data[0] - - var/records[0] - for(var/datum/data/record/general in sortRecord(GLOB.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 - - return data - -/datum/pai_software/med_records/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(!( GLOB.data_core.general.Find(R) )) - P.medical_cannotfind = 1 - else - P.medical_cannotfind = 0 - for(var/datum/data/record/E in GLOB.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 - - autoupdate = 1 - template_file = "pai_secrecords.tmpl" - ui_title = "Security Records" - - -/datum/pai_software/sec_records/on_ui_data(mob/living/silicon/pai/user, datum/topic_state/state = GLOB.self_state) - var/data[0] - - var/records[0] - for(var/datum/data/record/general in sortRecord(GLOB.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 - - return data - -/datum/pai_software/sec_records/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(!( GLOB.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 GLOB.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 - - autoupdate = 1 - template_file = "pai_doorjack.tmpl" - ui_title = "Door Jack" - ui_width = 300 - ui_height = 150 - -/datum/pai_software/door_jack/on_ui_data(mob/living/silicon/pai/user, datum/topic_state/state = GLOB.self_state) - 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 - - return data - -/datum/pai_software/door_jack/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/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/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 - - template_file = "pai_atmosphere.tmpl" - ui_title = "Atmosphere Sensor" - ui_width = 350 - ui_height = 300 - - -/datum/pai_software/atmosphere_sensor/on_ui_data(mob/living/silicon/pai/user, datum/topic_state/state = GLOB.self_state) - 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 - data["pressure"] = env.return_pressure() - 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 - - return data - -/datum/pai_software/sec_hud - name = "Security HUD" - ram_cost = 20 - id = "sec_hud" - -/datum/pai_software/sec_hud/toggle(mob/living/silicon/pai/user) - user.secHUD = !user.secHUD - user.remove_med_sec_hud() - if(user.secHUD) - user.add_sec_hud() - -/datum/pai_software/sec_hud/is_active(mob/living/silicon/pai/user) - return user.secHUD - -/datum/pai_software/med_hud - name = "Medical HUD" - ram_cost = 20 - id = "med_hud" - -/datum/pai_software/med_hud/toggle(mob/living/silicon/pai/user) - user.medHUD = !user.medHUD - user.remove_med_sec_hud() - if(user.medHUD) - user.add_med_hud() - -/datum/pai_software/med_hud/is_active(mob/living/silicon/pai/user) - return user.medHUD - -/datum/pai_software/translator - name = "Universal Translator" - ram_cost = 35 - id = "translator" - -/datum/pai_software/translator/toggle(mob/living/silicon/pai/user) - // Galactic Common, Sol Common, Tradeband, Gutter and Trinary 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("Canilunzt") - user.add_language("Skrellian") - user.add_language("Vox-pidgin") - user.add_language("Rootspeak") - user.add_language("Chittin") - user.add_language("Bubblish") - user.add_language("Orluum") - user.add_language("Clownish") - user.add_language("Neo-Russkiya") - else - user.remove_language("Sinta'unathi") - user.remove_language("Siik'tajr") - user.remove_language("Canilunzt") - user.remove_language("Skrellian") - user.remove_language("Vox-pidgin") - user.remove_language("Rootspeak") - user.remove_language("Chittin") - user.remove_language("Bubblish") - user.remove_language("Orluum") - user.remove_language("Clownish") - user.remove_language("Neo-Russkiya") - -/datum/pai_software/translator/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 - - template_file = "pai_signaller.tmpl" - ui_title = "Signaller" - ui_width = 320 - ui_height = 150 - -/datum/pai_software/signaller/on_ui_data(mob/living/silicon/pai/user, datum/topic_state/state = GLOB.self_state) - var/data[0] - - data["frequency"] = format_frequency(user.sradio.frequency) - data["code"] = user.sradio.code - - return data - -/datum/pai_software/signaller/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("[bicon(P)] *beep* *beep*", 3, "*beep* *beep*", 2) - return 1 - - else if(href_list["freq"]) - var/new_frequency = (P.sradio.frequency + text2num(href_list["freq"])) - if(new_frequency < PUBLIC_LOW_FREQ || new_frequency > PUBLIC_HIGH_FREQ) - 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 - -/datum/pai_software/host_scan - name = "Host Bioscan" - ram_cost = 5 - id = "bioscan" - toggle = 0 - - template_file = "pai_bioscan.tmpl" - ui_title = "Host Bioscan" - ui_width = 400 - ui_height = 350 - -/datum/pai_software/host_scan/on_ui_data(mob/living/silicon/pai/user, datum/topic_state/state = GLOB.self_state) - var/data[0] - var/mob/living/held = user.loc - var/count = 0 - - // Find the carrier - while(!isliving(held)) - if(!held || !held.loc || count > 6) - //For a runtime where M ends up in nullspace (similar to bluespace but less colourful) - to_chat(user, "You are not being carried by anyone!") - return 0 - held = held.loc - count++ - if(isliving(held)) - data["holder"] = held - data["health"] = "[held.stat > 1 ? "dead" : "[held.health]% healthy"]" - data["brute"] = "[held.getBruteLoss() > 50 ? "" : ""][held.getBruteLoss()]" - data["oxy"] = "[held.getOxyLoss() > 50 ? "" : ""][held.getOxyLoss()]" - data["tox"] = "[held.getToxLoss() > 50 ? "" : ""][held.getToxLoss()]" - data["burn"] = "[held.getFireLoss() > 50 ? "" : ""][held.getFireLoss()]" - data["temp"] = "[held.bodytemperature-T0C]°C ([held.bodytemperature*1.8-459.67]°F)" - else - data["holder"] = 0 - - return data - -/datum/pai_software/flashlight - name = "Flashlight" - ram_cost = 5 - id = "flashlight" - -/datum/pai_software/flashlight/toggle(mob/living/silicon/pai/user) - var/atom/movable/actual_location = istype(user.loc, /obj/item/paicard) ? user.loc : user - if(!user.flashlight_on) - actual_location.set_light(2) - user.card.set_light(2) - else - actual_location.set_light(0) - user.card.set_light(0) - - user.flashlight_on = !user.flashlight_on - -/datum/pai_software/flashlight/is_active(mob/living/silicon/pai/user) - return user.flashlight_on diff --git a/code/modules/pda/ai.dm b/code/modules/pda/ai.dm index ac4e6c9fd6b..aece49b6b83 100644 --- a/code/modules/pda/ai.dm +++ b/code/modules/pda/ai.dm @@ -97,8 +97,8 @@ /obj/item/pda/silicon/pai/can_use() var/mob/living/silicon/pai/pAI = usr if(!istype(pAI)) - return 0 - if(!pAI.software["messenger"]) + return FALSE + if(!pAI.installed_software["messenger"]) to_chat(usr, "You have not purchased the digital messenger!") - return 0 + return FALSE return ..() && !pAI.silence_time diff --git a/nano/templates/ai_restorer.tmpl b/nano/templates/ai_restorer.tmpl deleted file mode 100644 index eb4c20a74f8..00000000000 --- a/nano/templates/ai_restorer.tmpl +++ /dev/null @@ -1,45 +0,0 @@ -{{if data.restoring}} -
Reconstruction in progress!
-{{/if}} - -
-
Inserted AI:
-
{{:helper.link(data.name ? data.name : "-----", 'eject', {'action' : 'PRG_eject'}, data.nocard ? 'disabled' : null)}}
-
- -{{if data.error}} - ERROR: {{:data.error}} -{{else}} -

System Status

-
-
-
Current AI:
-
{{:data.name}}
-
-
-
Status:
-
{{if data.isDead}}Nonfunctional{{else}}Functional{{/if}}
-
-
-
System Integrity:
- {{:helper.displayBar(data.health, 0, 100, (data.health > 75) ? 'good' : (data.health > 50) ? 'average' : 'bad')}} -
- {{:helper.smoothRound(data.health)}}% -
-
-
-
- Active Laws: -
-
- - {{for data.ai_laws}} -
{{:value.index}}: {{:value.text}} - {{/for}} -
-
-
-
-

Operations

- {{:helper.link('Begin Reconstruction', 'plus', {'action' : 'PRG_beginReconstruction'}, data.restoring ? 'disabled' : null)}} -{{/if}} \ No newline at end of file diff --git a/nano/templates/exosuit_control.tmpl b/nano/templates/exosuit_control.tmpl deleted file mode 100644 index 111b63040ff..00000000000 --- a/nano/templates/exosuit_control.tmpl +++ /dev/null @@ -1,81 +0,0 @@ - -{{if data.screen == 0}} - {{for data.mechas}} -
-
-

{{:value.name}}

-

Information

-
- Pilot: -
-
- {{:value.pilot}} -
-
- Integrity: -
-
- {{:helper.smoothRound(value.integrity)}}% -
-
- Location: -
-
- {{:value.location}} -
-
- Airtank: -
-
- {{:helper.smoothRound(value.airtank)}}kPa -
-
- Active Equipment: -
-
- {{:value.equipment}} -
- {{if value.hascargo}} -
- Cargo Used: -
-
- {{:value.cargo}}% -
- {{/if}} - -

Power Cell

- {{if value.cell}} - - Rating: - - - {{:value.cell_capacity}} - - {{:helper.displayBar(value.cell_percentage, 0, 100, (value.cell_percentage >= 50) ? 'good' : (value.cell_percentage >= 25) ? 'average' : 'bad')}} - {{:helper.smoothRound(value.cell_percentage)}}% - {{else}} - Not Installed - {{:helper.displayBar(100, 0, 100, 'bad')}} - {{/if}} - - -

Actions

- {{:helper.link('Send Message', 'envelope', {'send_message' : value.reference})}} - {{:helper.link('Exosuit Log', 'search', {'get_log' : value.reference})}} - {{:helper.link('EMP Pulse', 'exclamation-triangle', {'shock' : value.reference}, null, 'redButton')}} -
- {{empty}} -
-

No mecha trackers were found.

-
- {{/for}} -{{else}} -
- {{:helper.link('Return', 'close', {'return' : 1})}} -
- {{:data.log}} -{{/if}} \ No newline at end of file diff --git a/nano/templates/pai_atmosphere.tmpl b/nano/templates/pai_atmosphere.tmpl deleted file mode 100644 index f8764f5a3a7..00000000000 --- a/nano/templates/pai_atmosphere.tmpl +++ /dev/null @@ -1,36 +0,0 @@ - - -{{if data.reading}} -
-
- Pressure: -
-
- {{:helper.smoothRound(data.pressure, 1)}} kPa -
-
- -
-
- Temperature: -
-
- {{:helper.smoothRound(data.temperature)}}K ({{:helper.smoothRound(data.temperatureC)}}°C) -
-
- - {{for data.gas}} -
-
- {{:value.name}}: -
-
- {{:helper.smoothRound(value.percent)}}% -
-
- {{/for}} -{{else}} - Unable to obtain air reading! -{{/if}} diff --git a/nano/templates/pai_bioscan.tmpl b/nano/templates/pai_bioscan.tmpl deleted file mode 100644 index 921588045a1..00000000000 --- a/nano/templates/pai_bioscan.tmpl +++ /dev/null @@ -1,30 +0,0 @@ - - -{{if data.holder}} -
-
- Bioscan Results for {{:data.holder}} : -
-
- Health Status : {{:data.health}} -
- -
- Brute Damage : {{:data.brute}} -
- Oxygen Content : {{:data.oxy}} -
- Toxin Content : {{:data.tox}} -
-
- Burn Status : {{:data.burn}} -
-
- User Temperature : {{:data.temp}} -
-
-{{else}} - Error: No biological host found! -{{/if}} diff --git a/nano/templates/pai_chatroom.tmpl b/nano/templates/pai_chatroom.tmpl deleted file mode 100644 index d669a375d86..00000000000 --- a/nano/templates/pai_chatroom.tmpl +++ /dev/null @@ -1,76 +0,0 @@ - - -
-
- Receiver: -
-
- {{if data.receiver_off}} - {{:helper.link("On", '', {"stopic":"chatroom", "toggler":1})}} - {{:helper.link("Off", '', {"stopic":"chatroom", "toggler":2}, 'selected')}} - {{else}} - {{:helper.link("On", '', {"stopic":"chatroom", "toggler":1}, 'selected')}} - {{:helper.link("Off", '', {"stopic":"chatroom", "toggler":2})}} - {{/if}} -
-
- -
-
- Ringer: -
-
- {{if data.ringer_off}} - {{:helper.link("On", '', {"stopic":"chatroom", "ringer":1})}} - {{:helper.link("Off", '', {"stopic":"chatroom", "ringer":2}, 'selected')}} - {{else}} - {{:helper.link("On", '', {"stopic":"chatroom", "ringer":1}, 'selected')}} - {{:helper.link("Off", '', {"stopic":"chatroom", "ringer":2})}} - {{/if}} -
-
- -{{for data.rooms}} -
- {{:helper.link(value.name, '', {"stopic":"chatroom", "select":value.ref})}} - {{:helper.link("Quick Message", '', {"stopic":"chatroom", "target":value.ref})}} -
-{{/for}} -
-{{if data.disconnected}} -

Connection Lost

- {{:helper.link("Reconnect", 'refresh', {"stopic":"chatroom", "reconnect":1})}} -{{else data.current_room}} -

#{{:data.current_room_name}}

-
- Topic:
-
{{:helper.link(data.current_room_topic, '', {"stopic":"chatroom", "topic":1})}}
-
-
-
Users:
-
- {{var firstUser = true;}} - {{for data.users}} - {{:firstUser ? "" : ", "}}{{:value}} - {{firstUser = false;}} - {{/for}} -
-
- {{var prevName = "";}} - {{for data.messages}} -
-
{{:value.username!=prevName ? value.username : " "}}
-
- {{:value.message}} -
-
- {{prevName = value.username;}} - {{/for}} - {{:helper.link("Send Message", '', {"stopic":"chatroom", "target":data.current_room})}} -{{/if}} - -{{if data.current_name}} - {{:helper.link("Clear Screen", '', {"stopic":"chatroom", "select":"*NONE*"})}} -{{/if}} diff --git a/nano/templates/pai_directives.tmpl b/nano/templates/pai_directives.tmpl deleted file mode 100644 index 2caff849a26..00000000000 --- a/nano/templates/pai_directives.tmpl +++ /dev/null @@ -1,50 +0,0 @@ - - -
-
- 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 deleted file mode 100644 index 69abd861e80..00000000000 --- a/nano/templates/pai_doorjack.tmpl +++ /dev/null @@ -1,44 +0,0 @@ - - -
-
- 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}} -
{{:helper.smoothRound(data.progress_a)}}.{{:helper.smoothRound(data.progress_b)}}%
- {{else data.progress_a <= 67}} -
{{:helper.smoothRound(data.progress_a)}}.{{:helper.smoothRound(data.progress_b)}}%
- {{else}} -
{{:helper.smoothRound(data.progress_a)}}.{{:helper.smoothRound(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 deleted file mode 100644 index e6441932665..00000000000 --- a/nano/templates/pai_interface.tmpl +++ /dev/null @@ -1,45 +0,0 @@ - - - - - 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 deleted file mode 100644 index d6827594e35..00000000000 --- a/nano/templates/pai_manifest.tmpl +++ /dev/null @@ -1,98 +0,0 @@ - - -
-
- {{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.ser}} - - {{for data.manifest["ser"]}} - {{if value.rank == "Head of Personnel"}} - - {{else}} - - {{/if}} - {{/for}} - {{/if}} - {{if data.manifest.sup}} - - {{for data.manifest["sup"]}} - {{if value.rank == "Head of Personnel"}} - - {{else value.rank == "Quartermaster"}} - - {{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}}
Service
{{:value.name}}{{:value.rank}}{{:value.active}}
{{:value.name}}{{:value.rank}}{{:value.active}}
Supply
{{:value.name}}{{:value.rank}}{{:value.active}}
{{: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}}
-
diff --git a/nano/templates/pai_medrecords.tmpl b/nano/templates/pai_medrecords.tmpl deleted file mode 100644 index 37b1d7061ca..00000000000 --- a/nano/templates/pai_medrecords.tmpl +++ /dev/null @@ -1,86 +0,0 @@ - - -{{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 deleted file mode 100644 index fb20f826f27..00000000000 --- a/nano/templates/pai_messenger.tmpl +++ /dev/null @@ -1,80 +0,0 @@ - - -
-
- 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 deleted file mode 100644 index bcdacdb48f1..00000000000 --- a/nano/templates/pai_radio.tmpl +++ /dev/null @@ -1,48 +0,0 @@ - - -
-
- 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 deleted file mode 100644 index e139034c0de..00000000000 --- a/nano/templates/pai_secrecords.tmpl +++ /dev/null @@ -1,76 +0,0 @@ - - -{{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 deleted file mode 100644 index 20c2781bc9b..00000000000 --- a/nano/templates/pai_signaller.tmpl +++ /dev/null @@ -1,33 +0,0 @@ - - -
-
- 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 2ba11f3a87f..4d1572bfa7a 100644 --- a/paradise.dme +++ b/paradise.dme @@ -1893,8 +1893,10 @@ #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\pai\update_status.dm" +#include "code\modules\mob\living\silicon\pai\software\_base.dm" +#include "code\modules\mob\living\silicon\pai\software\pai_apps.dm" +#include "code\modules\mob\living\silicon\pai\software\pai_toggles.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" diff --git a/tgui/packages/tgui/interfaces/PAI.js b/tgui/packages/tgui/interfaces/PAI.js new file mode 100644 index 00000000000..1025820ed31 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PAI.js @@ -0,0 +1,58 @@ +import { useBackend } from "../backend"; +import { Button, Box, Section, Flex, Icon } from "../components"; +import { Window } from "../layouts"; + +/* This is all basically stolen from routes.js. */ +import { routingError } from "../routes"; + +const RequirePAIInterface = require.context('./pai', false, /\.js$/); + +const GetApp = name => { + let appModule; + try { + appModule = RequirePAIInterface(`./${name}.js`); + } catch (err) { + if (err.code === 'MODULE_NOT_FOUND') { + return routingError('notFound', name); + } + throw err; + } + const Component = appModule[name]; + if (!Component) { + return routingError('missingExport', name); + } + return Component; +}; + +export const PAI = (props, context) => { + const { act, data } = useBackend(context); + const { + app_template, + app_icon, + app_title, + } = data; + + const App = GetApp(app_template); + + return ( + + +
+ + {app_title} + {app_template !== "pai_main_menu" && ( +
+
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/common/AtmosScan.js b/tgui/packages/tgui/interfaces/common/AtmosScan.js new file mode 100644 index 00000000000..35e9acec031 --- /dev/null +++ b/tgui/packages/tgui/interfaces/common/AtmosScan.js @@ -0,0 +1,46 @@ +import { filter } from 'common/collections'; +import { useBackend } from "../../backend"; +import { Box, LabeledList } from "../../components"; + +const getItemColor = (value, min2, min1, max1, max2) => { + if (value < min2) { + return "bad"; + } else if (value < min1) { + return "average"; + } else if (value > max1) { + return "average"; + } else if (value > max2) { + return "bad"; + } + return "good"; +}; + +export const AtmosScan = (props, context) => { + const { + aircontents, + } = props.data; + + return ( + + + {filter( + i => (i.val !== "0") + || i.entry === "Pressure" + || i.entry === "Temperature" + )(aircontents) + .map(item => ( + + {item.val}{item.units} + + ))} + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/common/CrewManifest.js b/tgui/packages/tgui/interfaces/common/CrewManifest.js index 403487c9e24..80b05ebb72b 100644 --- a/tgui/packages/tgui/interfaces/common/CrewManifest.js +++ b/tgui/packages/tgui/interfaces/common/CrewManifest.js @@ -67,7 +67,14 @@ const ManifestTable = group => { }; export const CrewManifest = (props, context) => { - const { act, data } = useBackend(context); + const { act } = useBackend(context); + let finalData; + if (props.data) { + finalData = props.data; + } else { + let { data } = useBackend(context); + finalData = data; + } // HOW TO USE THIS THING /* GLOB.data_core.get_manifest_json() @@ -78,7 +85,7 @@ export const CrewManifest = (props, context) => { const { manifest, - } = data; + } = finalData; const { heads, diff --git a/tgui/packages/tgui/interfaces/common/Signaler.js b/tgui/packages/tgui/interfaces/common/Signaler.js new file mode 100644 index 00000000000..c1abb23ed1b --- /dev/null +++ b/tgui/packages/tgui/interfaces/common/Signaler.js @@ -0,0 +1,53 @@ +import { toFixed } from 'common/math'; +import { useBackend } from "../../backend"; +import { Button, LabeledList, NumberInput, Section } from "../../components"; + +export const Signaler = (props, context) => { + const { act } = useBackend(context); + const { + code, + frequency, + minFrequency, + maxFrequency, + } = props.data; + return ( +
+ + + toFixed(value, 1)} + width="80px" + onDrag={(e, value) => act('freq', { + freq: value, + })} /> + + + act('code', { + code: value, + })} /> + + +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/common/SimpleRecords.js b/tgui/packages/tgui/interfaces/common/SimpleRecords.js new file mode 100644 index 00000000000..6d40197965b --- /dev/null +++ b/tgui/packages/tgui/interfaces/common/SimpleRecords.js @@ -0,0 +1,205 @@ +import { useBackend, useLocalState } from "../../backend"; +import { createSearch } from 'common/string'; +import { flow } from 'common/fp'; +import { filter, sortBy } from 'common/collections'; +import { Box, Input, Button, Section, LabeledList } from "../../components"; + +export const SimpleRecords = (props, context) => { + const { + records, + } = props.data; + + return ( + + {!records ? ( + + ) : ( + + )} + + ); +}; + +const SelectionView = (props, context) => { + const { act } = useBackend(context); + const { + recordsList, + } = props.data; + + const [ + searchText, + setSearchText, + ] = useLocalState(context, 'searchText', ''); + + // Search for peeps + const SelectMembers = (people, searchText = '') => { + const MemberSearch = createSearch(searchText, member => member.Name); + return flow([ + // Null member filter + filter(member => member?.Name), + // Optional search term + searchText && filter(MemberSearch), + // Slightly expensive, but way better than sorting in BYOND + sortBy(member => member.Name), + ])(recordsList); + }; + + const formattedRecords = SelectMembers(recordsList, searchText); + + return ( + + setSearchText(value)} /> + {formattedRecords.map(r => ( + +