diff --git a/code/__defines/pda.dm b/code/__defines/pda.dm new file mode 100644 index 00000000000..c5d32f03ba8 --- /dev/null +++ b/code/__defines/pda.dm @@ -0,0 +1,3 @@ +#define PDA_APP_UPDATE 0 +#define PDA_APP_NOUPDATE 1 +#define PDA_APP_UPDATE_SLOW 2 diff --git a/code/_helpers/text.dm b/code/_helpers/text.dm index 02a8806089d..a9aac1ffcf5 100644 --- a/code/_helpers/text.dm +++ b/code/_helpers/text.dm @@ -21,6 +21,27 @@ /* * Text sanitization */ +// Can be used almost the same way as normal input for text +/proc/clean_input(Message, Title, Default, mob/user=usr) + var/txt = input(user, Message, Title, Default) as text | null + if(txt) + return html_encode(txt) + +//Simply removes < and > and limits the length of the message +/proc/strip_html_simple(var/t,var/limit=MAX_MESSAGE_LEN) + var/list/strip_chars = list("<",">") + t = copytext(t,1,limit) + for(var/char in strip_chars) + var/index = findtext(t, char) + while(index) + t = copytext(t, 1, index) + copytext(t, index+1) + index = findtext(t, char) + return t + +//Runs byond's sanitization proc along-side strip_html_simple +//I believe strip_html_simple() is required to run first to prevent '<' from displaying as '<' that html_encode() would cause +/proc/adminscrub(var/t,var/limit=MAX_MESSAGE_LEN) + return copytext((html_encode(strip_html_simple(t))),1,limit) //Used for preprocessing entered text /proc/sanitize(var/input, var/max_length = MAX_MESSAGE_LEN, var/encode = 1, var/trim = 1, var/extra = 1) diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm index 73b78cb148a..955ccf596a7 100644 --- a/code/game/machinery/camera/camera.dm +++ b/code/game/machinery/camera/camera.dm @@ -204,7 +204,9 @@ else P = W itemname = P.name - info = P.notehtml + var/datum/data/pda/app/notekeeper/N = P.find_program(/datum/data/pda/app/notekeeper) + if(N) + info = N.notehtml to_chat(U, "You hold \a [itemname] up to the camera ...") for(var/mob/living/silicon/ai/O in living_mob_list) if(!O.client) diff --git a/code/game/machinery/computer/message.dm b/code/game/machinery/computer/message.dm index 1dc0709ed35..1caa7b61a4d 100644 --- a/code/game/machinery/computer/message.dm +++ b/code/game/machinery/computer/message.dm @@ -131,7 +131,10 @@ //Get out list of viable PDAs var/list/obj/item/device/pda/sendPDAs = list() for(var/obj/item/device/pda/P in PDAs) - if(!P.owner || P.toff || P.hidden) + if(!P.owner || P.hidden) + continue + var/datum/data/pda/app/messenger/M = P.find_program(/datum/data/pda/app/messenger) + if(!M || M.toff) continue sendPDAs["[P.name]"] = "\ref[P]" data["possibleRecipients"] = sendPDAs @@ -265,7 +268,11 @@ if("set_recipient") var/ref = params["val"] var/obj/item/device/pda/P = locate(ref) - if(!istype(P) || !P.owner || P.toff || P.hidden) + if(!istype(P) || !P.owner || P.hidden) + return FALSE + + var/datum/data/pda/app/messenger/M = P.find_program(/datum/data/pda/app/messenger) + if(!M || M.toff) return FALSE customrecepient = P . = TRUE @@ -286,22 +293,26 @@ var/obj/item/device/pda/PDARec = null for(var/obj/item/device/pda/P in PDAs) - if(!P.owner || P.toff || P.hidden) continue + if(!P.owner || P.hidden) + continue + var/datum/data/pda/app/messenger/M = P.find_program(/datum/data/pda/app/messenger) + if(!M || M.toff) + continue if(P.owner == customsender) PDARec = P //Sender isn't faking as someone who exists if(isnull(PDARec)) linkedServer.send_pda_message("[customrecepient.owner]", "[customsender]","[custommessage]") - customrecepient.new_message(customsender, customsender, customjob, custommessage) + var/datum/data/pda/app/messenger/M = customrecepient.find_program(/datum/data/pda/app/messenger) + if(M) + M.receive_message(list("sent" = 0, "owner" = customsender, "job" = customjob, "message" = custommessage), null) //Sender is faking as someone who exists else linkedServer.send_pda_message("[customrecepient.owner]", "[PDARec.owner]","[custommessage]") - customrecepient.tnote.Add(list(list("sent" = 0, "owner" = "[PDARec.owner]", "job" = "[customjob]", "message" = "[custommessage]", "target" ="\ref[PDARec]"))) - - if(!customrecepient.conversations.Find("\ref[PDARec]")) - customrecepient.conversations.Add("\ref[PDARec]") - - customrecepient.new_message(PDARec, custommessage) + + var/datum/data/pda/app/messenger/M = customrecepient.find_program(/datum/data/pda/app/messenger) + if(M) + M.receive_message(list("sent" = 0, "owner" = "[PDARec.owner]", "job" = "[customjob]", "message" = "[custommessage]", "target" = "\ref[PDARec]"), "\ref[PDARec]") //Finally.. ResetMessage() . = TRUE diff --git a/code/game/machinery/newscaster.dm b/code/game/machinery/newscaster.dm index 05e2256d9d0..de85f95e9ac 100644 --- a/code/game/machinery/newscaster.dm +++ b/code/game/machinery/newscaster.dm @@ -101,22 +101,22 @@ NEWSCASTER.newsAlert(annoncement) NEWSCASTER.update_icon() - var/list/receiving_pdas = new - for (var/obj/item/device/pda/P in PDAs) - if(!P.owner) - continue - if(P.toff) - continue - receiving_pdas += P + // var/list/receiving_pdas = new + // for (var/obj/item/device/pda/P in PDAs) + // if(!P.owner) + // continue + // if(P.toff) + // continue + // receiving_pdas += P - spawn(0) // get_receptions sleeps further down the line, spawn of elsewhere - var/datum/receptions/receptions = get_receptions(null, receiving_pdas) // datums are not atoms, thus we have to assume the newscast network always has reception + // spawn(0) // get_receptions sleeps further down the line, spawn of elsewhere + // var/datum/receptions/receptions = get_receptions(null, receiving_pdas) // datums are not atoms, thus we have to assume the newscast network always has reception - for(var/obj/item/device/pda/PDA in receiving_pdas) - if(!(receptions.receiver_reception[PDA] & TELECOMMS_RECEPTION_RECEIVER)) - continue + // for(var/obj/item/device/pda/PDA in receiving_pdas) + // if(!(receptions.receiver_reception[PDA] & TELECOMMS_RECEPTION_RECEIVER)) + // continue - PDA.new_news(annoncement) + // PDA.new_news(annoncement) var/datum/feed_network/news_network = new /datum/feed_network //The global news-network, which is coincidentally a global list. diff --git a/code/game/machinery/pda_multicaster.dm b/code/game/machinery/pda_multicaster.dm index 20e870d3d02..a70f1ede372 100644 --- a/code/game/machinery/pda_multicaster.dm +++ b/code/game/machinery/pda_multicaster.dm @@ -63,7 +63,9 @@ /obj/machinery/pda_multicaster/proc/update_PDAs(var/turn_off) for(var/obj/item/device/pda/pda in contents) - pda.toff = turn_off + var/datum/data/pda/app/messenger/M = pda.find_program(/datum/data/pda/app/messenger/multicast) + if(M) + M.toff = turn_off /obj/machinery/pda_multicaster/proc/update_power() if(toggle) diff --git a/code/game/objects/items/devices/PDA/PDA.dm b/code/game/objects/items/devices/PDA/PDA.dm deleted file mode 100644 index 91784b5de6d..00000000000 --- a/code/game/objects/items/devices/PDA/PDA.dm +++ /dev/null @@ -1,1608 +0,0 @@ - -//The advanced pea-green monochrome lcd of tomorrow. - -var/global/list/obj/item/device/pda/PDAs = list() - -/obj/item/device/pda - name = "\improper PDA" - desc = "A portable microcomputer by Thinktronic Systems, LTD. Functionality determined by a preprogrammed ROM cartridge." - icon = 'icons/obj/pda.dmi' - icon_state = "pda" - item_state = "electronic" - w_class = ITEMSIZE_SMALL - slot_flags = SLOT_ID | SLOT_BELT - sprite_sheets = list(SPECIES_TESHARI = 'icons/mob/species/seromi/id.dmi') - - //Main variables - var/pdachoice = 1 - var/owner = null - var/default_cartridge = 0 // Access level defined by cartridge - var/obj/item/weapon/cartridge/cartridge = null //current cartridge - var/mode = 0 //Controls what menu the PDA will display. 0 is hub; the rest are either built in or based on cartridge. - - var/lastmode = 0 - var/ui_tick = 0 - var/nanoUI[0] - - //Secondary variables - var/scanmode = 0 //1 is medical scanner, 2 is forensics, 3 is reagent scanner. - var/fon = 0 //Is the flashlight function on? - var/f_lum = 2 //Luminosity for the flashlight function - var/message_silent = 0 //To beep or not to beep, that is the question - var/news_silent = 1 //To beep or not to beep, that is the question. The answer is No. - var/toff = 0 //If 1, messenger disabled - var/tnote[0] //Current Texts - var/last_text //No text spamming - var/last_honk //Also no honk spamming that's bad too - var/ttone = "beep" //The PDA ringtone! - var/newstone = "beep, beep" //The news ringtone! - var/lock_code = "" // Lockcode to unlock uplink - var/honkamt = 0 //How many honks left when infected with honk.exe - var/mimeamt = 0 //How many silence left when infected with mime.exe - var/note = "Congratulations, your station has chosen the Thinktronic 5230 Personal Data Assistant!" //Current note in the notepad function - var/notehtml = "" - var/cart = "" //A place to stick cartridge menu information - var/detonate = 1 // Can the PDA be blown up? - var/hidden = 0 // Is the PDA hidden from the PDA list? - var/active_conversation = null // New variable that allows us to only view a single conversation. - var/list/conversations = list() // For keeping up with who we have PDA messsages from. - var/new_message = 0 //To remove hackish overlay check - var/new_news = 0 - var/touch_silent = 0 //If 1, no beeps on interacting. - - var/active_feed // The selected feed - var/list/warrant // The warrant as we last knew it - var/list/feeds = list() // The list of feeds as we last knew them - var/list/feed_info = list() // The data and contents of each feed as we last knew them - - var/list/cartmodes = list(40, 42, 43, 433, 44, 441, 45, 451, 46, 48, 47, 49) // If you add more cartridge modes add them to this list as well. - var/list/no_auto_update = list(1, 40, 43, 44, 441, 45, 451) // These modes we turn off autoupdate - var/list/update_every_five = list(3, 41, 433, 46, 47, 48, 49) // These we update every 5 ticks - - var/obj/item/weapon/card/id/id = null //Making it possible to slot an ID card into the PDA so it can function as both. - var/ownjob = null //related to above - this is assignment (potentially alt title) - var/ownrank = null // this one is rank, never alt title - - var/obj/item/device/paicard/pai = null // A slot for a personal AI device - - var/spam_proof = FALSE // If true, it can't be spammed by random events. - -/obj/item/device/pda/examine(mob/user) - . = ..() - if(Adjacent(user)) - . += "The time [stationtime2text()] is displayed in the corner of the screen." - -/obj/item/device/pda/CtrlClick() - if(issilicon(usr)) - return - - if(can_use(usr)) - remove_pen() - return - ..() - -/obj/item/device/pda/AltClick() - if(issilicon(usr)) - return - - if ( can_use(usr) ) - if(id) - remove_id() - else - to_chat(usr, "This PDA does not have an ID in it.") - -//Bloop when using: -/obj/item/device/pda/CouldUseTopic(var/mob/user) - ..() - if(iscarbon(user) && !touch_silent) - playsound(src, 'sound/machines/pda_click.ogg', 20) - -/obj/item/device/pda/medical - default_cartridge = /obj/item/weapon/cartridge/medical - icon_state = "pda-m" - -/obj/item/device/pda/viro - default_cartridge = /obj/item/weapon/cartridge/medical - icon_state = "pda-v" - -/obj/item/device/pda/engineering - default_cartridge = /obj/item/weapon/cartridge/engineering - icon_state = "pda-e" - -/obj/item/device/pda/security - default_cartridge = /obj/item/weapon/cartridge/security - icon_state = "pda-s" - -/obj/item/device/pda/detective - default_cartridge = /obj/item/weapon/cartridge/detective - icon_state = "pda-det" - -/obj/item/device/pda/warden - default_cartridge = /obj/item/weapon/cartridge/security - icon_state = "pda-warden" - -/obj/item/device/pda/janitor - default_cartridge = /obj/item/weapon/cartridge/janitor - icon_state = "pda-j" - ttone = "slip" - -/obj/item/device/pda/science - default_cartridge = /obj/item/weapon/cartridge/signal/science - icon_state = "pda-tox" - ttone = "boom" - -/obj/item/device/pda/clown - default_cartridge = /obj/item/weapon/cartridge/clown - icon_state = "pda-clown" - desc = "A portable microcomputer by Thinktronic Systems, LTD. The surface is coated with polytetrafluoroethylene and banana drippings." - ttone = "honk" - -/obj/item/device/pda/mime - default_cartridge = /obj/item/weapon/cartridge/mime - icon_state = "pda-mime" - message_silent = 1 - news_silent = 1 - ttone = "silence" - newstone = "silence" - -/obj/item/device/pda/heads - default_cartridge = /obj/item/weapon/cartridge/head - icon_state = "pda-h" - news_silent = 1 - -/obj/item/device/pda/heads/hop - default_cartridge = /obj/item/weapon/cartridge/hop - icon_state = "pda-hop" - -/obj/item/device/pda/heads/hos - default_cartridge = /obj/item/weapon/cartridge/hos - icon_state = "pda-hos" - -/obj/item/device/pda/heads/ce - default_cartridge = /obj/item/weapon/cartridge/ce - icon_state = "pda-ce" - -/obj/item/device/pda/heads/cmo - default_cartridge = /obj/item/weapon/cartridge/cmo - icon_state = "pda-cmo" - -/obj/item/device/pda/heads/rd - default_cartridge = /obj/item/weapon/cartridge/rd - icon_state = "pda-rd" - -/obj/item/device/pda/captain - default_cartridge = /obj/item/weapon/cartridge/captain - icon_state = "pda-c" - detonate = 0 - //toff = 1 - -/obj/item/device/pda/ert - default_cartridge = /obj/item/weapon/cartridge/captain - icon_state = "pda-h" - detonate = 0 -// hidden = 1 - -/obj/item/device/pda/cargo - default_cartridge = /obj/item/weapon/cartridge/quartermaster - icon_state = "pda-cargo" - -/obj/item/device/pda/quartermaster - default_cartridge = /obj/item/weapon/cartridge/quartermaster - icon_state = "pda-q" - -/obj/item/device/pda/shaftminer - icon_state = "pda-miner" - default_cartridge = /obj/item/weapon/cartridge/miner - -/obj/item/device/pda/syndicate - default_cartridge = /obj/item/weapon/cartridge/syndicate - icon_state = "pda-syn" -// name = "Military PDA" // Vorestation Edit -// owner = "John Doe" - hidden = 1 - -/obj/item/device/pda/chaplain - default_cartridge = /obj/item/weapon/cartridge/service - icon_state = "pda-holy" - ttone = "holy" - -/obj/item/device/pda/lawyer - default_cartridge = /obj/item/weapon/cartridge/lawyer - icon_state = "pda-lawyer" - ttone = "..." - -/obj/item/device/pda/botanist - default_cartridge = /obj/item/weapon/cartridge/service - icon_state = "pda-hydro" - -/obj/item/device/pda/roboticist - default_cartridge = /obj/item/weapon/cartridge/signal/science - icon_state = "pda-robot" - -/obj/item/device/pda/librarian - default_cartridge = /obj/item/weapon/cartridge/service - icon_state = "pda-libb" - desc = "A portable microcomputer by Thinktronic Systems, LTD. This is model is a WGW-11 series e-reader." - note = "Congratulations, your station has chosen the Thinktronic 5290 WGW-11 Series E-reader and Personal Data Assistant!" - message_silent = 1 //Quiet in the library! - news_silent = 0 // Librarian is above the law! (That and alt job title is reporter) - -/obj/item/device/pda/clear - icon_state = "pda-transp" - desc = "A portable microcomputer by Thinktronic Systems, LTD. This is model is a special edition with a transparent case." - note = "Congratulations, you have chosen the Thinktronic 5230 Personal Data Assistant Deluxe Special Max Turbo Limited Edition!" - -/obj/item/device/pda/chef - default_cartridge = /obj/item/weapon/cartridge/service - icon_state = "pda-chef" - -/obj/item/device/pda/bar - default_cartridge = /obj/item/weapon/cartridge/service - icon_state = "pda-bar" - -/obj/item/device/pda/atmos - default_cartridge = /obj/item/weapon/cartridge/atmos - icon_state = "pda-atmo" - -/obj/item/device/pda/chemist - default_cartridge = /obj/item/weapon/cartridge/chemistry - icon_state = "pda-chem" - -/obj/item/device/pda/geneticist - default_cartridge = /obj/item/weapon/cartridge/medical - icon_state = "pda-gene" - - -// Special AI/pAI PDAs that cannot explode. -/obj/item/device/pda/ai - icon_state = "NONE" - ttone = "data" - newstone = "news" - detonate = 0 - - -/obj/item/device/pda/ai/proc/set_name_and_job(newname as text, newjob as text, newrank as null|text) - owner = newname - ownjob = newjob - if(newrank) - ownrank = newrank - else - ownrank = ownjob - name = newname + " (" + ownjob + ")" - -//AI verb and proc for sending PDA messages. -/obj/item/device/pda/ai/verb/cmd_send_pdamesg() - set category = "AI IM" - set name = "Send Message" - set src in usr - if(usr.stat == 2) - to_chat(usr, "You can't send PDA messages because you are dead!") - return - var/list/plist = available_pdas() - if (plist) - var/c = input(usr, "Please select a PDA") as null|anything in sortList(plist) - if (!c) // if the user hasn't selected a PDA file we can't send a message - return - var/selected = plist[c] - create_message(usr, selected, 0) - -/obj/item/device/pda/ai/verb/cmd_toggle_pda_receiver() - set category = "AI IM" - set name = "Toggle Sender/Receiver" - set src in usr - if(usr.stat == 2) - to_chat(usr, "You can't send PDA messages because you are dead!") - return - toff = !toff - to_chat(usr, "PDA sender/receiver toggled [(toff ? "Off" : "On")]!") - -/obj/item/device/pda/ai/verb/cmd_toggle_pda_silent() - set category = "AI IM" - set name = "Toggle Ringer" - set src in usr - if(usr.stat == 2) - to_chat(usr, "You can't send PDA messages because you are dead!") - return - message_silent=!message_silent - to_chat(usr, "PDA ringer toggled [(message_silent ? "Off" : "On")]!") - -/obj/item/device/pda/ai/verb/cmd_show_message_log() - set category = "AI IM" - set name = "Show Message Log" - set src in usr - if(usr.stat == 2) - to_chat(usr, "You can't send PDA messages because you are dead!") - return - var/HTML = "AI PDA Message Log" - for(var/index in tnote) - if(index["sent"]) - HTML += addtext("→ To ", index["owner"],":
", index["message"], "
") - else - HTML += addtext("← From ", index["owner"],":
", index["message"], "
") - HTML +="" - usr << browse(HTML, "window=log;size=400x444;border=1;can_resize=1;can_close=1;can_minimize=0") - - -/obj/item/device/pda/ai/can_use() - return 1 - - -/obj/item/device/pda/ai/attack_self(mob/user as mob) - if ((honkamt > 0) && (prob(60)))//For clown virus. - honkamt-- - playsound(src, 'sound/items/bikehorn.ogg', 30, 1) - return - - -/obj/item/device/pda/ai/pai - ttone = "assist" - -/obj/item/device/pda/ai/shell - spam_proof = TRUE // Since empty shells get a functional PDA. - -// Used for the PDA multicaster, which mirrors messages sent to it to a specific department, -/obj/item/device/pda/multicaster - ownjob = "Relay" - icon_state = "NONE" - ttone = "data" - detonate = 0 - news_silent = 1 - spam_proof = TRUE // Spam messages don't actually work and its difficult to disable these. - var/list/cartridges_to_send_to = list() - -// This is what actually mirrors the message, -/obj/item/device/pda/multicaster/new_message(var/sending_unit, var/sender, var/sender_job, var/message) - if(sender) - var/list/targets = list() - for(var/obj/item/device/pda/pda in PDAs) - if(pda.cartridge && pda.owner && is_type_in_list(pda.cartridge, cartridges_to_send_to)) - targets |= pda - if(targets.len) - for(var/obj/item/device/pda/target in targets) - create_message(target, sender, sender_job, message) - -// This has so much copypasta, -/obj/item/device/pda/multicaster/create_message(var/obj/item/device/pda/P, var/original_sender, var/original_job, var/t) - t = sanitize(t, MAX_MESSAGE_LEN, 0) - t = replace_characters(t, list(""" = "\"")) - if (!t || !istype(P)) - return - - if (isnull(P)||P.toff || toff) - return - - last_text = world.time - var/datum/reception/reception = get_reception(src, P, t) - t = reception.message - - if(reception.message_server && (reception.telecomms_reception & TELECOMMS_RECEPTION_SENDER)) // only send the message if it's stable, - if(reception.telecomms_reception & TELECOMMS_RECEPTION_RECEIVER == 0) // Does our recipient have a broadcaster on their level?, - return - var/send_result = reception.message_server.send_pda_message("[P.owner]","[owner]","[t]") - if (send_result) - return - - P.tnote.Add(list(list("sent" = 0, "owner" = "[owner]", "job" = "[ownjob]", "message" = "[t]", "target" = "\ref[src]"))) - - if(!P.conversations.Find("\ref[src]")) - P.conversations.Add("\ref[src]") - - P.new_message(src, "[original_sender] \[Relayed\]", original_job, t, 0) - - else - return - -/obj/item/device/pda/multicaster/command/New() - ..() - owner = "Command Department" - name = "Command Department (Relay)" - cartridges_to_send_to = command_cartridges - -/obj/item/device/pda/multicaster/security/New() - ..() - owner = "Security Department" - name = "Security Department (Relay)" - cartridges_to_send_to = security_cartridges - -/obj/item/device/pda/multicaster/engineering/New() - ..() - owner = "Engineering Department" - name = "Engineering Department (Relay)" - cartridges_to_send_to = engineering_cartridges - -/obj/item/device/pda/multicaster/medical/New() - ..() - owner = "Medical Department" - name = "Medical Department (Relay)" - cartridges_to_send_to = medical_cartridges - -/obj/item/device/pda/multicaster/research/New() - ..() - owner = "Research Department" - name = "Research Department (Relay)" - cartridges_to_send_to = research_cartridges - -/obj/item/device/pda/multicaster/cargo/New() - ..() - owner = "Cargo Department" - name = "Cargo Department (Relay)" - cartridges_to_send_to = cargo_cartridges - -/obj/item/device/pda/multicaster/civilian/New() - ..() - owner = "Civilian Services Department" - name = "Civilian Services Department (Relay)" - cartridges_to_send_to = civilian_cartridges - -/* - * The Actual PDA - */ - -/obj/item/device/pda/New(var/mob/living/carbon/human/H) - ..() - PDAs += src - PDAs = sortAtom(PDAs) - if(default_cartridge) - cartridge = new default_cartridge(src) - new /obj/item/weapon/pen(src) - pdachoice = isnull(H) ? 1 : (ishuman(H) ? H.pdachoice : 1) - switch(pdachoice) - if(1) icon = 'icons/obj/pda.dmi' - if(2) icon = 'icons/obj/pda_slim.dmi' - if(3) icon = 'icons/obj/pda_old.dmi' - if(4) icon = 'icons/obj/pda_rugged.dmi' - if(5) icon = 'icons/obj/pda_holo.dmi' - if(6) - icon = 'icons/obj/pda_wrist.dmi' - item_state = icon_state - item_icons = list( - slot_belt_str = 'icons/mob/pda_wrist.dmi', - slot_wear_id_str = 'icons/mob/pda_wrist.dmi', - slot_gloves_str = 'icons/mob/pda_wrist.dmi' - ) - desc = "A portable microcomputer by Thinktronic Systems, LTD. This model is a wrist-bound version." - slot_flags = SLOT_ID | SLOT_BELT | SLOT_GLOVES - sprite_sheets = list( - SPECIES_TESHARI = 'icons/mob/species/seromi/pda_wrist.dmi', - SPECIES_VR_TESHARI = 'icons/mob/species/seromi/pda_wrist.dmi', - ) - else - icon = 'icons/obj/pda_old.dmi' - log_debug("Invalid switch for PDA, defaulting to old PDA icons. [pdachoice] chosen.") - - -/obj/item/device/pda/proc/can_use() - - if(!ismob(loc)) - return 0 - - var/mob/M = loc - if(M.stat || M.restrained() || M.paralysis || M.stunned || M.weakened) - return 0 - if((src in M.contents) || ( istype(loc, /turf) && in_range(src, M) )) - return 1 - else - return 0 - -/obj/item/device/pda/GetAccess() - if(id) - return id.GetAccess() - else - return ..() - -/obj/item/device/pda/GetID() - return id - -/obj/item/device/pda/MouseDrop(obj/over_object as obj, src_location, over_location) - var/mob/M = usr - if((!istype(over_object, /obj/screen)) && can_use()) - return attack_self(M) - return - - -/obj/item/device/pda/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - ui_tick++ - var/datum/nanoui/old_ui = SSnanoui.get_open_ui(user, src, "main") - var/auto_update = 1 - if(mode in no_auto_update) - auto_update = 0 - if(old_ui && (mode == lastmode && ui_tick % 5 && mode in update_every_five)) - return - - lastmode = mode - - var/title = "Personal Data Assistant" - - var/data[0] // This is the data that will be sent to the PDA - - data["owner"] = owner // Who is your daddy... - data["ownjob"] = ownjob // ...and what does he do? - - data["mode"] = mode // The current view - data["scanmode"] = scanmode // Scanners - data["fon"] = fon // Flashlight on? - data["pai"] = (isnull(pai) ? 0 : 1) // pAI inserted? - data["note"] = note // current pda notes - data["message_silent"] = message_silent // does the pda make noise when it receives a message? - data["news_silent"] = news_silent // does the pda make noise when it receives news? - data["touch_silent"] = touch_silent // does the pda make noise when it receives news? - data["toff"] = toff // is the messenger function turned off? - data["active_conversation"] = active_conversation // Which conversation are we following right now? - - - data["idInserted"] = (id ? 1 : 0) - data["idLink"] = (id ? text("[id.registered_name], [id.assignment]") : "--------") - - data["cart_loaded"] = cartridge ? 1:0 - if(cartridge) - var/cartdata[0] - cartdata["access"] = list(\ - "access_security" = cartridge.access_security,\ - "access_engine" = cartridge.access_engine,\ - "access_atmos" = cartridge.access_atmos,\ - "access_medical" = cartridge.access_medical,\ - "access_clown" = cartridge.access_clown,\ - "access_mime" = cartridge.access_mime,\ - "access_janitor" = cartridge.access_janitor,\ - "access_quartermaster" = cartridge.access_quartermaster,\ - "access_hydroponics" = cartridge.access_hydroponics,\ - "access_reagent_scanner" = cartridge.access_reagent_scanner,\ - "access_remote_door" = cartridge.access_remote_door,\ - "access_status_display" = cartridge.access_status_display,\ - "access_detonate_pda" = cartridge.access_detonate_pda\ - ) - - if(mode in cartmodes) - data["records"] = cartridge.create_NanoUI_values() - - if(mode == 0) - cartdata["name"] = cartridge.name - if(isnull(cartridge.radio)) - cartdata["radio"] = 0 - else - if(istype(cartridge.radio, /obj/item/radio/integrated/beepsky)) - cartdata["radio"] = 1 - if(istype(cartridge.radio, /obj/item/radio/integrated/signal)) - cartdata["radio"] = 2 - //if(istype(cartridge.radio, /obj/item/radio/integrated/mule)) - // cartdata["radio"] = 3 - - if(mode == 2) - cartdata["charges"] = cartridge.charges ? cartridge.charges : 0 - data["cartridge"] = cartdata - - data["stationTime"] = stationtime2text() - data["new_Message"] = new_message - data["new_News"] = new_news - - var/datum/reception/reception = get_reception(src, do_sleep = 0) - var/has_reception = reception.telecomms_reception & TELECOMMS_RECEPTION_SENDER - data["reception"] = has_reception - - if(mode==2) - var/convopdas[0] - var/pdas[0] - var/count = 0 - for (var/obj/item/device/pda/P in PDAs) - if (!P.owner||P.toff||P == src||P.hidden) continue - if(conversations.Find("\ref[P]")) - convopdas.Add(list(list("Name" = "[P]", "Reference" = "\ref[P]", "Detonate" = "[P.detonate]", "inconvo" = "1"))) - else - pdas.Add(list(list("Name" = "[P]", "Reference" = "\ref[P]", "Detonate" = "[P.detonate]", "inconvo" = "0"))) - count++ - - data["convopdas"] = convopdas - data["pdas"] = pdas - data["pda_count"] = count - - if(mode==21) - data["messagescount"] = tnote.len - data["messages"] = tnote - else - data["messagescount"] = null - data["messages"] = null - - if(active_conversation) - for(var/c in tnote) - if(c["target"] == active_conversation) - data["convo_name"] = sanitize(c["owner"]) - data["convo_job"] = sanitize(c["job"]) - break - if(mode==41) - data_core.get_manifest_list() - - - if(mode==3) - data["aircontents"] = src.analyze_air() - if(mode==6) - if(has_reception) - feeds.Cut() - for(var/datum/feed_channel/channel in news_network.network_channels) - feeds[++feeds.len] = list("name" = channel.channel_name, "censored" = channel.censored) - data["feedChannels"] = feeds - if(mode==61) - var/datum/feed_channel/FC - for(FC in news_network.network_channels) - if(FC.channel_name == active_feed["name"]) - break - - var/list/feed = feed_info[active_feed] - if(!feed) - feed = list() - feed["channel"] = FC.channel_name - feed["author"] = "Unknown" - feed["censored"]= 0 - feed["updated"] = -1 - feed_info[active_feed] = feed - - if(FC.updated > feed["updated"] && has_reception) - feed["author"] = FC.author - feed["updated"] = FC.updated - feed["censored"] = FC.censored - - var/list/messages = list() - if(!FC.censored) - var/index = 0 - for(var/datum/feed_message/FM in FC.messages) - index++ - if(FM.img) - usr << browse_rsc(FM.img, "pda_news_tmp_photo_[feed["channel"]]_[index].png") - // News stories are HTML-stripped but require newline replacement to be properly displayed in NanoUI - var/body = replacetext(FM.body, "\n", "
") - messages[++messages.len] = list("author" = FM.author, "body" = body, "message_type" = FM.message_type, "time_stamp" = FM.time_stamp, "has_image" = (FM.img != null), "caption" = FM.caption, "index" = index) - feed["messages"] = messages - - data["feed"] = feed - - data["manifest"] = PDA_Manifest - - nanoUI = data - // update the ui if it exists, returns null if no ui is passed/found - - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open) - - if (!ui) - // the ui does not exist, so we'll create a new() one - // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm - ui = new(user, src, ui_key, "pda.tmpl", title, 520, 400, state = inventory_state) - // add templates for screens in common with communicator. - ui.add_template("atmosphericScan", "atmospheric_scan.tmpl") - ui.add_template("crewManifest", "crew_manifest.tmpl") - // when the ui is first opened this is the data it will use - ui.set_initial_data(data) - // open the new ui window - ui.open() - // auto update every Master Controller tick - ui.set_auto_update(auto_update) - -/obj/item/device/pda/attack_self(mob/user as mob) - user.set_machine(src) - - if(active_uplink_check(user)) - return - - ui_interact(user) //NanoUI requires this proc - return - -/obj/item/device/pda/Topic(href, href_list) - if(href_list["cartmenu"] && !isnull(cartridge)) - cartridge.Topic(href, href_list) - return 1 - if(href_list["radiomenu"] && !isnull(cartridge) && !isnull(cartridge.radio)) - cartridge.radio.Topic(href, href_list) - return 1 - - - ..() - var/mob/user = usr - var/datum/nanoui/ui = SSnanoui.get_open_ui(user, src, "main") - var/mob/living/U = usr - //Looking for master was kind of pointless since PDAs don't appear to have one. - //if ((src in U.contents) || ( istype(loc, /turf) && in_range(src, U) ) ) - if (usr.stat == DEAD) - return 0 - if(!can_use()) //Why reinvent the wheel? There's a proc that does exactly that. - U.unset_machine() - if(ui) - ui.close() - return 0 - - add_fingerprint(U) - U.set_machine(src) - - switch(href_list["choice"]) - -//BASIC FUNCTIONS=================================== - - if("Close")//Self explanatory - U.unset_machine() - ui.close() - return 0 - if("Refresh")//Refresh, goes to the end of the proc. - if("Return")//Return - if(mode<=9) - mode = 0 - else - mode = round(mode/10) - if(mode==2) - active_conversation = null - if(mode==4)//Fix for cartridges. Redirects to hub. - mode = 0 - else if(mode >= 40 && mode <= 49)//Fix for cartridges. Redirects to refresh the menu. - cartridge.mode = mode - if ("Authenticate")//Checks for ID - id_check(U, 1) - if("UpdateInfo") - ownjob = id.assignment - ownrank = id.rank - name = "PDA-[owner] ([ownjob])" - if("Eject")//Ejects the cart, only done from hub. - verb_remove_cartridge() - -//MENU FUNCTIONS=================================== - - if("0")//Hub - mode = 0 - if("1")//Notes - mode = 1 - if("2")//Messenger - mode = 2 - if("21")//Read messages - mode = 21 - if("3")//Atmos scan - mode = 3 - if("4")//Redirects to hub - mode = 0 - if("chatroom") // chatroom hub - mode = 5 - if("41") //Manifest - mode = 41 - - -//MAIN FUNCTIONS=================================== - - if("Light") - if(fon) - fon = 0 - set_light(0) - else - fon = 1 - set_light(f_lum) - if("Medical Scan") - if(scanmode == 1) - scanmode = 0 - else if((!isnull(cartridge)) && (cartridge.access_medical)) - scanmode = 1 - if("Reagent Scan") - if(scanmode == 3) - scanmode = 0 - else if((!isnull(cartridge)) && (cartridge.access_reagent_scanner)) - scanmode = 3 - if("Halogen Counter") - if(scanmode == 4) - scanmode = 0 - else if((!isnull(cartridge)) && (cartridge.access_engine)) - scanmode = 4 - if("Honk") - if ( !(last_honk && world.time < last_honk + 20) ) - playsound(src, 'sound/items/bikehorn.ogg', 50, 1) - last_honk = world.time - if("Gas Scan") - if(scanmode == 5) - scanmode = 0 - else if((!isnull(cartridge)) && (cartridge.access_atmos)) - scanmode = 5 - if("Toggle Beeping") - touch_silent = !touch_silent - -//MESSENGER/NOTE FUNCTIONS=================================== - - if ("Edit") - var/n = input(U, "Please enter message", name, notehtml) as message - if (in_range(src, U) && loc == U) - n = sanitizeSafe(n, extra = 0) - if (mode == 1) - note = html_decode(n) - notehtml = note - note = replacetext(note, "\n", "
") - else - ui.close() - if("Toggle Messenger") - toff = !toff - if("Toggle Ringer")//If viewing texts then erase them, if not then toggle silent status - message_silent = !message_silent - if("Toggle News") - news_silent = !news_silent - if("Clear")//Clears messages - if(href_list["option"] == "All") - tnote.Cut() - conversations.Cut() - if(href_list["option"] == "Convo") - var/new_tnote[0] - for(var/i in tnote) - if(i["target"] != active_conversation) - new_tnote[++new_tnote.len] = i - tnote = new_tnote - conversations.Remove(active_conversation) - - active_conversation = null - if(mode==21) - mode=2 - - if("Ringtone") - var/t = input(U, "Please enter new ringtone", name, ttone) as text - if (in_range(src, U) && loc == U) - if (t) - if(src.hidden_uplink && hidden_uplink.check_trigger(U, lowertext(t), lowertext(lock_code))) - to_chat(U, "The PDA softly beeps.") - ui.close() - else - t = sanitize(t, 20) - ttone = t - else - ui.close() - return 0 - if("Newstone") - var/t = input(U, "Please enter new news tone", name, newstone) as text - if (in_range(src, U) && loc == U) - if (t) - t = sanitize(t, 20) - newstone = t - else - ui.close() - return 0 - if("Message") - - var/obj/item/device/pda/P = locate(href_list["target"]) - src.create_message(U, P, !href_list["notap"]) - if(mode == 2) - if(href_list["target"] in conversations) // Need to make sure the message went through, if not welp. - active_conversation = href_list["target"] - mode = 21 - - if("Select Conversation") - var/P = href_list["convo"] - for(var/n in conversations) - if(P == n) - active_conversation=P - mode=21 - if("Select Feed") - var/n = href_list["name"] - for(var/f in feeds) - if(f["name"] == n) - active_feed = f - mode=61 - if("Send Honk")//Honk virus - if(cartridge && cartridge.access_clown)//Cartridge checks are kind of unnecessary since everything is done through switch. - var/obj/item/device/pda/P = locate(href_list["target"])//Leaving it alone in case it may do something useful, I guess. - if(!isnull(P)) - if (!P.toff && cartridge.charges > 0) - cartridge.charges-- - U.show_message("Virus sent!", 1) - P.honkamt = (rand(15,20)) - else - to_chat(U, "PDA not found.") - else - ui.close() - return 0 - if("Send Silence")//Silent virus - if(cartridge && cartridge.access_mime) - var/obj/item/device/pda/P = locate(href_list["target"]) - if(!isnull(P)) - if (!P.toff && cartridge.charges > 0) - cartridge.charges-- - U.show_message("Virus sent!", 1) - P.message_silent = 1 - P.news_silent = 1 - P.ttone = "silence" - P.newstone = "silence" - else - to_chat(U, "PDA not found.") - else - ui.close() - return 0 - - -//SYNDICATE FUNCTIONS=================================== - - if("Toggle Door") - if(cartridge && cartridge.access_remote_door) - for(var/obj/machinery/door/blast/M in machines) - if(M.id == cartridge.remote_door_id) - if(M.density) - M.open() - else - M.close() - - if("Detonate")//Detonate PDA... maybe - if(cartridge && cartridge.access_detonate_pda) - var/obj/item/device/pda/P = locate(href_list["target"]) - var/datum/reception/reception = get_reception(src, P, "", do_sleep = 0) - if(!(reception.message_server && reception.telecomms_reception & TELECOMMS_RECEPTION_SENDER)) - U.show_message("An error flashes on your [src]: Connection unavailable", 1) - return - if(reception.telecomms_reception & TELECOMMS_RECEPTION_RECEIVER == 0) // Does our recepient have a broadcaster on their level? - U.show_message("An error flashes on your [src]: Recipient unavailable", 1) - return - if(!isnull(P)) - if (!P.toff && cartridge.charges > 0) - cartridge.charges-- - - var/difficulty = 2 - - if(P.cartridge) - difficulty += P.cartridge.access_medical - difficulty += P.cartridge.access_security - difficulty += P.cartridge.access_engine - difficulty += P.cartridge.access_clown - difficulty += P.cartridge.access_janitor - if(P.hidden_uplink) - difficulty += 3 - - if(prob(difficulty)) - U.show_message("An error flashes on your [src].", 1) - else if (prob(difficulty * 7)) - U.show_message("Energy feeds back into your [src]!", 1) - ui.close() - detonate_act(src) - log_admin("[key_name(U)] just attempted to blow up [P] with the Detomatix cartridge but failed, blowing themselves up") - message_admins("[key_name_admin(U)] just attempted to blow up [P] with the Detomatix cartridge but failed.", 1) - else - U.show_message("Success!", 1) - log_admin("[key_name(U)] just attempted to blow up [P] with the Detomatix cartridge and succeeded") - message_admins("[key_name_admin(U)] just attempted to blow up [P] with the Detomatix cartridge and succeeded.", 1) - detonate_act(P) - else - to_chat(U, "No charges left.") - - else - to_chat(U, "PDA not found.") - else - U.unset_machine() - ui.close() - return 0 - -//pAI FUNCTIONS=================================== - if("pai") - if(pai) - if(pai.loc != src) - pai = null - else - switch(href_list["option"]) - if("1") // Configure pAI device - pai.attack_self(U) - if("2") // Eject pAI device - var/turf/T = get_turf_or_move(src.loc) - if(T) - pai.loc = T - pai = null - - else - mode = text2num(href_list["choice"]) - if(cartridge) - cartridge.mode = mode - -//EXTRA FUNCTIONS=================================== - - if (mode == 2||mode == 21)//To clear message overlays. - new_message = 0 - update_icon() - - if (mode == 6||mode == 61)//To clear news overlays. - new_news = 0 - update_icon() - - if ((honkamt > 0) && (prob(60)))//For clown virus. - honkamt-- - playsound(src, 'sound/items/bikehorn.ogg', 30, 1) - - return 1 // return 1 tells it to refresh the UI in NanoUI - -/obj/item/device/pda/update_icon() - ..() - - overlays.Cut() - if(new_message || new_news) - overlays += image(icon, "pda-r") - -/obj/item/device/pda/proc/detonate_act(var/obj/item/device/pda/P) - //TODO: sometimes these attacks show up on the message server - var/i = rand(1,100) - var/j = rand(0,1) //Possibility of losing the PDA after the detonation - var/message = "" - var/mob/living/M = null - if(ismob(P.loc)) - M = P.loc - - //switch(i) //Yes, the overlapping cases are intended. - if(i<=10) //The traditional explosion - P.explode() - j=1 - message += "Your [P] suddenly explodes!" - if(i>=10 && i<= 20) //The PDA burns a hole in the holder. - j=1 - if(M && isliving(M)) - M.apply_damage( rand(30,60) , BURN) - message += "You feel a searing heat! Your [P] is burning!" - if(i>=20 && i<=25) //EMP - empulse(P.loc, 1, 2, 4, 6, 1) - message += "Your [P] emits a wave of electromagnetic energy!" - if(i>=25 && i<=40) //Smoke - var/datum/effect/effect/system/smoke_spread/chem/S = new /datum/effect/effect/system/smoke_spread/chem - S.attach(P.loc) - S.set_up(P, 10, 0, P.loc) - playsound(P, 'sound/effects/smoke.ogg', 50, 1, -3) - S.start() - message += "Large clouds of smoke billow forth from your [P]!" - if(i>=40 && i<=45) //Bad smoke - var/datum/effect/effect/system/smoke_spread/bad/B = new /datum/effect/effect/system/smoke_spread/bad - B.attach(P.loc) - B.set_up(P, 10, 0, P.loc) - playsound(P, 'sound/effects/smoke.ogg', 50, 1, -3) - B.start() - message += "Large clouds of noxious smoke billow forth from your [P]!" - if(i>=65 && i<=75) //Weaken - if(M && isliving(M)) - M.apply_effects(0,1) - message += "Your [P] flashes with a blinding white light! You feel weaker." - if(i>=75 && i<=85) //Stun and stutter - if(M && isliving(M)) - M.apply_effects(1,0,0,0,1) - message += "Your [P] flashes with a blinding white light! You feel weaker." - if(i>=85) //Sparks - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(2, 1, P.loc) - s.start() - message += "Your [P] begins to spark violently!" - if(i>45 && i<65 && prob(50)) //Nothing happens - message += "Your [P] bleeps loudly." - j = prob(10) - - if(j && detonate) //This kills the PDA - qdel(P) - if(message) - message += "It melts in a puddle of plastic." - else - message += "Your [P] shatters in a thousand pieces!" - - if(M && isliving(M)) - message = "[message]" - M.show_message(message, 1) - -/obj/item/device/pda/proc/remove_id() - if (id) - if (ismob(loc)) - var/mob/M = loc - M.put_in_hands(id) - to_chat(usr, "You remove the ID from the [name].") - playsound(src, 'sound/machines/id_swipe.ogg', 100, 1) - else - id.loc = get_turf(src) - id = null - -/obj/item/device/pda/proc/remove_pen() - var/obj/item/weapon/pen/O = locate() in src - if(O) - if(istype(loc, /mob)) - var/mob/M = loc - if(M.get_active_hand() == null) - M.put_in_hands(O) - to_chat(usr, "You remove \the [O] from \the [src].") - return - O.loc = get_turf(src) - else - to_chat(usr, "This PDA does not have a pen in it.") - -/obj/item/device/pda/proc/create_message(var/mob/living/U = usr, var/obj/item/device/pda/P, var/tap = 1) - if(tap) - U.visible_message("\The [U] taps on their PDA's screen.") - var/t = input(U, "Please enter message", P.name, null) as text - t = sanitize(t) - //t = readd_quotes(t) - t = replace_characters(t, list(""" = "\"")) - if (!t || !istype(P)) - return - if (!in_range(src, U) && loc != U) - return - - if (isnull(P)||P.toff || toff) - return - - if (last_text && world.time < last_text + 5) - return - - if (!can_use()) - return - - if (is_jammed(src)) - return - - last_text = world.time - var/datum/reception/reception = get_reception(src, P, t) - t = reception.message - - if(reception.message_server && (reception.telecomms_reception & TELECOMMS_RECEPTION_SENDER)) // only send the message if it's stable - if(reception.telecomms_reception & TELECOMMS_RECEPTION_RECEIVER == 0) // Does our recipient have a broadcaster on their level? - to_chat(U, "ERROR: Cannot reach recipient.") - return - var/send_result = reception.message_server.send_pda_message("[P.owner]","[owner]","[t]") - if (send_result) - to_chat(U, "ERROR: Messaging server rejected your message. Reason: contains '[send_result]'.") - return - - tnote.Add(list(list("sent" = 1, "owner" = "[P.owner]", "job" = "[P.ownjob]", "message" = "[t]", "target" = "\ref[P]"))) - P.tnote.Add(list(list("sent" = 0, "owner" = "[owner]", "job" = "[ownjob]", "message" = "[t]", "target" = "\ref[src]"))) - for(var/mob/M in player_list) - if(M.stat == DEAD && M.client && (M.is_preference_enabled(/datum/client_preference/ghost_ears))) // src.client is so that ghosts don't have to listen to mice - if(istype(M, /mob/new_player)) - continue - if(M.forbid_seeing_deadchat) - continue - M.show_message("PDA Message - [owner] -> [P.owner]: [t]") - - if(!conversations.Find("\ref[P]")) - conversations.Add("\ref[P]") - if(!P.conversations.Find("\ref[src]")) - P.conversations.Add("\ref[src]") - to_chat(U, "[bicon(src)] Sent message to [P.owner] ([P.ownjob]), \"[t]\"") - - if (prob(5) && security_level >= SEC_LEVEL_BLUE) //Give the AI a chance of intercepting the message //VOREStation Edit: no spam interception on lower codes + lower interception chance - var/who = src.owner - if(prob(50)) - who = P.owner - for(var/mob/living/silicon/ai/ai in mob_list) - // Allows other AIs to intercept the message but the AI won't intercept their own message. - if(ai.aiPDA != P && ai.aiPDA != src) - ai.show_message("Intercepted message from [who]: [t]") - - P.new_message_from_pda(src, t) - SSnanoui.update_user_uis(U, src) // Update the sending user's PDA UI so that they can see the new message - else - to_chat(U, "ERROR: Messaging server is not responding.") - -/obj/item/device/pda/proc/new_info(var/beep_silent, var/message_tone, var/reception_message) - if (!beep_silent) - playsound(src, 'sound/machines/twobeep.ogg', 50, 1) - for (var/mob/O in hearers(2, loc)) - O.show_message(text("[bicon(src)] *[message_tone]*")) - //Search for holder of the PDA. - var/mob/living/L = null - if(loc && isliving(loc)) - L = loc - //Maybe they are a pAI! - else - L = get(src, /mob/living/silicon) - - if(L) - if(reception_message) - to_chat(L,reception_message) - SSnanoui.update_user_uis(L, src) // Update the receiving user's PDA UI so that they can see the new message - -/obj/item/device/pda/proc/new_news(var/message) - new_info(news_silent, newstone, news_silent ? "" : "[bicon(src)] [message]") - - if(!news_silent) - new_news = 1 - update_icon() - -/obj/item/device/pda/ai/new_news(var/message) - // Do nothing - -/obj/item/device/pda/proc/new_message_from_pda(var/obj/item/device/pda/sending_device, var/message) - if (is_jammed(src)) - return - new_message(sending_device, sending_device.owner, sending_device.ownjob, message) - -/obj/item/device/pda/proc/new_message(var/sending_unit, var/sender, var/sender_job, var/message, var/reply = 1) - var/reception_message = "[bicon(src)] Message from [sender] ([sender_job]), \"[message]\" ([reply ? "Reply" : "Unable to Reply"])" - new_info(message_silent, ttone, reception_message) - - log_pda("(PDA: [sending_unit]) sent \"[message]\" to [name]", usr) - new_message = 1 - update_icon() - -/obj/item/device/pda/ai/new_message(var/atom/movable/sending_unit, var/sender, var/sender_job, var/message) - var/track = "" - if(ismob(sending_unit.loc) && isAI(loc)) - track = "(Follow)" - - var/reception_message = "[bicon(src)] Message from [sender] ([sender_job]), \"[message]\" (Reply) [track]" - new_info(message_silent, newstone, reception_message) - - log_pda("(PDA: [sending_unit]) sent \"[message]\" to [name]",usr) - new_message = 1 - -/obj/item/device/pda/proc/spam_message(sender, message) - var/reception_message = "\icon[src] Message from [sender] (Unknown / spam?), \"[message]\" (Unable to Reply)" - new_info(message_silent, ttone, reception_message) - - if(prob(50)) // Give the AI an increased chance to intercept the message - for(var/mob/living/silicon/ai/ai in mob_list) - if(ai.aiPDA != src) - ai.show_message("Intercepted message from [sender] (Unknown / spam?) to [owner]: [message]") - -/obj/item/device/pda/verb/verb_reset_pda() - set category = "Object" - set name = "Reset PDA" - set src in usr - - if(issilicon(usr)) - return - - if(can_use(usr)) - mode = 0 - SSnanoui.update_uis(src) - to_chat(usr, "You press the reset button on \the [src].") - else - to_chat(usr, "You cannot do this while restrained.") - -/obj/item/device/pda/verb/verb_remove_id() - set category = "Object" - set name = "Remove id" - set src in usr - - if(issilicon(usr)) - return - - if ( can_use(usr) ) - if(id) - remove_id() - else - to_chat(usr, "This PDA does not have an ID in it.") - else - to_chat(usr, "You cannot do this while restrained.") - - -/obj/item/device/pda/verb/verb_remove_pen() - set category = "Object" - set name = "Remove pen" - set src in usr - - if(issilicon(usr)) - return - - if ( can_use(usr) ) - remove_pen() - else - to_chat(usr, "You cannot do this while restrained.") - -/obj/item/device/pda/verb/verb_remove_cartridge() - set category = "Object" - set name = "Remove cartridge" - set src in usr - - if(issilicon(usr)) - return - - if(!can_use(usr)) - to_chat(usr, "You cannot do this while restrained.") - return - - if(isnull(cartridge)) - to_chat(usr, "There's no cartridge to eject.") - return - - cartridge.forceMove(get_turf(src)) - if(ismob(loc)) - var/mob/M = loc - M.put_in_hands(cartridge) - mode = 0 - scanmode = 0 - if (cartridge.radio) - cartridge.radio.hostpda = null - to_chat(usr, "You remove \the [cartridge] from the [name].") - playsound(src, 'sound/machines/id_swipe.ogg', 100, 1) - cartridge = null - -/obj/item/device/pda/proc/id_check(mob/user as mob, choice as num)//To check for IDs; 1 for in-pda use, 2 for out of pda use. - if(choice == 1) - if (id) - remove_id() - return 1 - else - var/obj/item/I = user.get_active_hand() - if (istype(I, /obj/item/weapon/card/id) && user.unEquip(I)) - I.loc = src - id = I - return 1 - else - var/obj/item/weapon/card/I = user.get_active_hand() - if (istype(I, /obj/item/weapon/card/id) && I:registered_name && user.unEquip(I)) - var/obj/old_id = id - I.loc = src - id = I - user.put_in_hands(old_id) - return 1 - return 0 - -// access to status display signals -/obj/item/device/pda/attackby(obj/item/C as obj, mob/user as mob) - ..() - if(istype(C, /obj/item/weapon/cartridge) && !cartridge) - cartridge = C - user.drop_item() - cartridge.loc = src - to_chat(usr, "You insert [cartridge] into [src].") - SSnanoui.update_uis(src) // update all UIs attached to src - if(cartridge.radio) - cartridge.radio.hostpda = src - - else if(istype(C, /obj/item/weapon/card/id)) - var/obj/item/weapon/card/id/idcard = C - if(!idcard.registered_name) - to_chat(user, "\The [src] rejects the ID.") - return - if(!owner) - owner = idcard.registered_name - ownjob = idcard.assignment - ownrank = idcard.rank - name = "PDA-[owner] ([ownjob])" - to_chat(user, "Card scanned.") - else - //Basic safety check. If either both objects are held by user or PDA is on ground and card is in hand. - if(((src in user.contents) && (C in user.contents)) || (istype(loc, /turf) && in_range(src, user) && (C in user.contents)) ) - if(id_check(user, 2)) - to_chat(user, "You put the ID into \the [src]'s slot.") - updateSelfDialog()//Update self dialog on success. - return //Return in case of failed check or when successful. - updateSelfDialog()//For the non-input related code. - else if(istype(C, /obj/item/device/paicard) && !src.pai) - user.drop_item() - C.loc = src - pai = C - to_chat(user, "You slot \the [C] into \the [src].") - SSnanoui.update_uis(src) // update all UIs attached to src - else if(istype(C, /obj/item/weapon/pen)) - var/obj/item/weapon/pen/O = locate() in src - if(O) - to_chat(user, "There is already a pen in \the [src].") - else - user.drop_item() - C.loc = src - to_chat(user, "You slot \the [C] into \the [src].") - return - -/obj/item/device/pda/attack(mob/living/C as mob, mob/living/user as mob) - if (istype(C, /mob/living/carbon)) - switch(scanmode) - if(1) - - for (var/mob/O in viewers(C, null)) - O.show_message("\The [user] has analyzed [C]'s vitals!", 1) - - user.show_message("Analyzing Results for [C]:") - user.show_message(" Overall Status: [C.stat > 1 ? "dead" : "[C.health - C.halloss]% healthy"]", 1) - user.show_message(text(" Damage Specifics: []-[]-[]-[]", - (C.getOxyLoss() > 50) ? "warning" : "", C.getOxyLoss(), - (C.getToxLoss() > 50) ? "warning" : "", C.getToxLoss(), - (C.getFireLoss() > 50) ? "warning" : "", C.getFireLoss(), - (C.getBruteLoss() > 50) ? "warning" : "", C.getBruteLoss() - ), 1) - user.show_message(" Key: Suffocation/Toxin/Burns/Brute", 1) - user.show_message(" Body Temperature: [C.bodytemperature-T0C]°C ([C.bodytemperature*1.8-459.67]°F)", 1) - if(C.tod && (C.stat == DEAD || (C.status_flags & FAKEDEATH))) - user.show_message(" Time of Death: [C.tod]") - if(istype(C, /mob/living/carbon/human)) - var/mob/living/carbon/human/H = C - var/list/damaged = H.get_damaged_organs(1,1) - user.show_message("Localized Damage, Brute/Burn:",1) - if(length(damaged)>0) - for(var/obj/item/organ/external/org in damaged) - user.show_message(text(" []: []-[]", - capitalize(org.name), (org.brute_dam > 0) ? "warning" : "notice", org.brute_dam, (org.burn_dam > 0) ? "warning" : "notice", org.burn_dam),1) - else - user.show_message(" Limbs are OK.",1) - - if(2) - if (!istype(C:dna, /datum/dna)) - to_chat(user, "No fingerprints found on [C]") - else - to_chat(user, text("\The [C]'s Fingerprints: [md5(C:dna.uni_identity)]")) - if ( !(C:blood_DNA) ) - to_chat(user, "No blood found on [C]") - if(C:blood_DNA) - qdel(C:blood_DNA) - else - to_chat(user, "Blood found on [C]. Analysing...") - spawn(15) - for(var/blood in C:blood_DNA) - to_chat(user, "Blood type: [C:blood_DNA[blood]]\nDNA: [blood]") - - if(4) - user.visible_message("\The [user] has analyzed [C]'s radiation levels!", "You have analyzed [C]'s radiation levels!") - to_chat(user, "Analyzing Results for [C]:") - if(C.radiation) - to_chat(user, "Radiation Level: [C.radiation]") - else - to_chat(user, "No radiation detected.") - -/obj/item/device/pda/afterattack(atom/A as mob|obj|turf|area, mob/user as mob, proximity) - if(!proximity) return - switch(scanmode) - - if(3) - if(!isobj(A)) - return - if(!isnull(A.reagents)) - if(A.reagents.reagent_list.len > 0) - var/reagents_length = A.reagents.reagent_list.len - to_chat(user, "[reagents_length] chemical agent[reagents_length > 1 ? "s" : ""] found.") - for (var/re in A.reagents.reagent_list) - to_chat(user, " [re]") - else - to_chat(user, "No active chemical agents found in [A].") - else - to_chat(user, "No significantchemical agents found in [A].") - - if(5) - analyze_gases(A, user) - - if (!scanmode && istype(A, /obj/item/weapon/paper) && owner) - // JMO 20140705: Makes scanned document show up properly in the notes. Not pretty for formatted documents, - // as this will clobber the HTML, but at least it lets you scan a document. You can restore the original - // notes by editing the note again. (Was going to allow you to edit, but scanned documents are too long.) - var/raw_scan = (A:info) - var/formatted_scan = "" - // Scrub out the tags (replacing a few formatting ones along the way) - - // Find the beginning and end of the first tag. - var/tag_start = findtext(raw_scan,"<") - var/tag_stop = findtext(raw_scan,">") - - // Until we run out of complete tags... - while(tag_start&&tag_stop) - var/pre = copytext(raw_scan,1,tag_start) // Get the stuff that comes before the tag - var/tag = lowertext(copytext(raw_scan,tag_start+1,tag_stop)) // Get the tag so we can do intellegent replacement - var/tagend = findtext(tag," ") // Find the first space in the tag if there is one. - - // Anything that's before the tag can just be added as is. - formatted_scan = formatted_scan+pre - - // If we have a space after the tag (and presumably attributes) just crop that off. - if (tagend) - tag=copytext(tag,1,tagend) - - if (tag=="p"||tag=="/p"||tag=="br") // Check if it's I vertical space tag. - formatted_scan=formatted_scan+"
" // If so, add some padding in. - - raw_scan = copytext(raw_scan,tag_stop+1) // continue on with the stuff after the tag - - // Look for the next tag in what's left - tag_start = findtext(raw_scan,"<") - tag_stop = findtext(raw_scan,">") - - // Anything that is left in the page. just tack it on to the end as is - formatted_scan=formatted_scan+raw_scan - - // If there is something in there already, pad it out. - if (length(note)>0) - note = note + "

" - - // Store the scanned document to the notes - note = "Scanned Document. Edit to restore previous notes/delete scan.
----------
" + formatted_scan + "
" - // notehtml ISN'T set to allow user to get their old notes back. A better implementation would add a "scanned documents" - // feature to the PDA, which would better convey the availability of the feature, but this will work for now. - - // Inform the user - to_chat(user, "Paper scanned and OCRed to notekeeper.") //concept of scanning paper copyright brainoblivion 2009 - - -/obj/item/device/pda/proc/explode() //This needs tuning. //Sure did. - if(!src.detonate) return - var/turf/T = get_turf(src.loc) - if(T) - T.hotspot_expose(700,125) - explosion(T, 0, 0, 1, rand(1,2)) - return - -/obj/item/device/pda/Destroy() - PDAs -= src - if (src.id && prob(100) && !delete_id) //IDs are kept in 90% of the cases //VOREStation Edit - 100% of the cases, excpet when specified otherwise - src.id.forceMove(get_turf(src.loc)) - else - QDEL_NULL(src.id) - QDEL_NULL(src.cartridge) - QDEL_NULL(src.pai) - return ..() - -/obj/item/device/pda/clown/Crossed(atom/movable/AM as mob|obj) //Clown PDA is slippery. - if(AM.is_incorporeal()) - return - if (istype(AM, /mob/living)) - var/mob/living/M = AM - - if(M.slip("the PDA",8) && M.real_name != src.owner && istype(src.cartridge, /obj/item/weapon/cartridge/clown)) - if(src.cartridge.charges < 5) - src.cartridge.charges++ - -/obj/item/device/pda/proc/available_pdas() - var/list/names = list() - var/list/plist = list() - var/list/namecounts = list() - - if (toff) - to_chat(usr, "Turn on your receiver in order to send messages.") - return - - for (var/obj/item/device/pda/P in PDAs) - if (!P.owner) - continue - else if(P.hidden) - continue - else if (P == src) - continue - else if (P.toff) - continue - - var/name = P.owner - if (name in names) - namecounts[name]++ - name = text("[name] ([namecounts[name]])") - else - names.Add(name) - namecounts[name] = 1 - - plist[text("[name]")] = P - return plist - - -//Some spare PDAs in a box -/obj/item/weapon/storage/box/PDAs - name = "box of spare PDAs" - desc = "A box of spare PDA microcomputers." - icon = 'icons/obj/pda.dmi' - icon_state = "pdabox" - - New() - ..() - new /obj/item/device/pda(src) - new /obj/item/device/pda(src) - new /obj/item/device/pda(src) - new /obj/item/device/pda(src) - new /obj/item/weapon/cartridge/head(src) - - var/newcart = pick( /obj/item/weapon/cartridge/engineering, - /obj/item/weapon/cartridge/security, - /obj/item/weapon/cartridge/medical, - /obj/item/weapon/cartridge/signal/science, - /obj/item/weapon/cartridge/quartermaster) - new newcart(src) - -// Pass along the pulse to atoms in contents, largely added so pAIs are vulnerable to EMP -/obj/item/device/pda/emp_act(severity) - for(var/atom/A in src) - A.emp_act(severity) - -/obj/item/device/pda/proc/analyze_air() - var/list/results = list() - var/turf/T = get_turf(src.loc) - if(!isnull(T)) - var/datum/gas_mixture/environment = T.return_air() - var/pressure = environment.return_pressure() - var/total_moles = environment.total_moles - if (total_moles) - var/o2_level = environment.gas["oxygen"]/total_moles - var/n2_level = environment.gas["nitrogen"]/total_moles - var/co2_level = environment.gas["carbon_dioxide"]/total_moles - var/phoron_level = environment.gas["phoron"]/total_moles - var/unknown_level = 1-(o2_level+n2_level+co2_level+phoron_level) - - // entry is what the element is describing - // Type identifies which unit or other special characters to use - // Val is the information reported - // Bad_high/_low are the values outside of which the entry reports as dangerous - // Poor_high/_low are the values outside of which the entry reports as unideal - // Values were extracted from the template itself - results = list( - list("entry" = "Pressure", "units" = "kPa", "val" = "[round(pressure,0.1)]", "bad_high" = 120, "poor_high" = 110, "poor_low" = 95, "bad_low" = 80), - list("entry" = "Temperature", "units" = "°C", "val" = "[round(environment.temperature-T0C,0.1)]", "bad_high" = 35, "poor_high" = 25, "poor_low" = 15, "bad_low" = 5), - list("entry" = "Oxygen", "units" = "kPa", "val" = "[round(o2_level*100,0.1)]", "bad_high" = 140, "poor_high" = 135, "poor_low" = 19, "bad_low" = 17), - list("entry" = "Nitrogen", "units" = "kPa", "val" = "[round(n2_level*100,0.1)]", "bad_high" = 105, "poor_high" = 85, "poor_low" = 50, "bad_low" = 40), - list("entry" = "Carbon Dioxide", "units" = "kPa", "val" = "[round(co2_level*100,0.1)]", "bad_high" = 10, "poor_high" = 5, "poor_low" = 0, "bad_low" = 0), - list("entry" = "Phoron", "units" = "kPa", "val" = "[round(phoron_level*100,0.01)]", "bad_high" = 0.5, "poor_high" = 0, "poor_low" = 0, "bad_low" = 0), - list("entry" = "Other", "units" = "kPa", "val" = "[round(unknown_level, 0.01)]", "bad_high" = 1, "poor_high" = 0.5, "poor_low" = 0, "bad_low" = 0) - ) - - if(isnull(results)) - results = list(list("entry" = "pressure", "units" = "kPa", "val" = "0", "bad_high" = 120, "poor_high" = 110, "poor_low" = 95, "bad_low" = 80)) - return results diff --git a/code/game/objects/items/devices/PDA/cart.dm b/code/game/objects/items/devices/PDA/cart.dm deleted file mode 100644 index 5a404288e31..00000000000 --- a/code/game/objects/items/devices/PDA/cart.dm +++ /dev/null @@ -1,630 +0,0 @@ -var/list/command_cartridges = list( - /obj/item/weapon/cartridge/captain, - /obj/item/weapon/cartridge/hop, - /obj/item/weapon/cartridge/hos, - /obj/item/weapon/cartridge/ce, - /obj/item/weapon/cartridge/rd, - /obj/item/weapon/cartridge/cmo, - /obj/item/weapon/cartridge/head, - /obj/item/weapon/cartridge/lawyer // Internal Affaris, - ) - -var/list/security_cartridges = list( - /obj/item/weapon/cartridge/security, - /obj/item/weapon/cartridge/detective, - /obj/item/weapon/cartridge/hos - ) - -var/list/engineering_cartridges = list( - /obj/item/weapon/cartridge/engineering, - /obj/item/weapon/cartridge/atmos, - /obj/item/weapon/cartridge/ce - ) - -var/list/medical_cartridges = list( - /obj/item/weapon/cartridge/medical, - /obj/item/weapon/cartridge/chemistry, - /obj/item/weapon/cartridge/cmo - ) - -var/list/research_cartridges = list( - /obj/item/weapon/cartridge/signal/science, - /obj/item/weapon/cartridge/rd - ) - -var/list/cargo_cartridges = list( - /obj/item/weapon/cartridge/quartermaster, // This also covers cargo-techs, apparently, - /obj/item/weapon/cartridge/miner, - /obj/item/weapon/cartridge/hop - ) - -var/list/civilian_cartridges = list( - /obj/item/weapon/cartridge/janitor, - /obj/item/weapon/cartridge/service, - /obj/item/weapon/cartridge/hop - ) - -/obj/item/weapon/cartridge - name = "generic cartridge" - desc = "A data cartridge for portable microcomputers." - icon = 'icons/obj/pda.dmi' - icon_state = "cart" - item_state = "electronic" - w_class = ITEMSIZE_TINY - drop_sound = 'sound/items/drop/component.ogg' - pickup_sound = 'sound/items/pickup/component.ogg' - - var/obj/item/radio/integrated/radio = null - var/access_security = 0 - var/access_engine = 0 - var/access_atmos = 0 - var/access_medical = 0 - var/access_clown = 0 - var/access_mime = 0 - var/access_janitor = 0 -// var/access_flora = 0 - var/access_reagent_scanner = 0 - var/access_remote_door = 0 // Control some blast doors remotely!! - var/remote_door_id = "" - var/access_status_display = 0 - var/access_quartermaster = 0 - var/access_detonate_pda = 0 - var/access_hydroponics = 0 - var/charges = 0 - var/mode = null - var/menu - var/datum/data/record/active1 = null //General - var/datum/data/record/active2 = null //Medical - var/datum/data/record/active3 = null //Security - var/selected_sensor = null // Power Sensor - var/message1 // used for status_displays - var/message2 - var/list/stored_data = list() - -/obj/item/weapon/cartridge/Destroy() - QDEL_NULL(radio) - return ..() - -/obj/item/weapon/cartridge/engineering - name = "\improper Power-ON cartridge" - icon_state = "cart-e" - access_engine = 1 - -/obj/item/weapon/cartridge/atmos - name = "\improper BreatheDeep cartridge" - icon_state = "cart-a" - access_atmos = 1 - -/obj/item/weapon/cartridge/medical - name = "\improper Med-U cartridge" - icon_state = "cart-m" - access_medical = 1 - -/obj/item/weapon/cartridge/chemistry - name = "\improper ChemWhiz cartridge" - icon_state = "cart-chem" - access_reagent_scanner = 1 - access_medical = 1 - -/obj/item/weapon/cartridge/security - name = "\improper R.O.B.U.S.T. cartridge" - icon_state = "cart-s" - access_security = 1 - -/obj/item/weapon/cartridge/security/Initialize() - radio = new /obj/item/radio/integrated/beepsky(src) - . = ..() - -/obj/item/weapon/cartridge/detective - name = "\improper D.E.T.E.C.T. cartridge" - icon_state = "cart-s" - access_security = 1 - access_medical = 1 - - -/obj/item/weapon/cartridge/janitor - name = "\improper CustodiPRO cartridge" - desc = "The ultimate in clean-room design." - icon_state = "cart-j" - access_janitor = 1 - -/obj/item/weapon/cartridge/lawyer - name = "\improper P.R.O.V.E. cartridge" - icon_state = "cart-s" - access_security = 1 - -/obj/item/weapon/cartridge/clown - name = "\improper Honkworks 5.0 cartridge" - icon_state = "cart-clown" - access_clown = 1 - charges = 5 - -/obj/item/weapon/cartridge/mime - name = "\improper Gestur-O 1000 cartridge" - icon_state = "cart-mi" - access_mime = 1 - charges = 5 -/* -/obj/item/weapon/cartridge/botanist - name = "Green Thumb v4.20" - icon_state = "cart-b" - access_flora = 1 -*/ - -/obj/item/weapon/cartridge/service - name = "\improper Serv-U Pro cartridge" - desc = "A data cartridge designed to serve YOU!" - -/obj/item/weapon/cartridge/signal - name = "generic signaler cartridge" - desc = "A data cartridge with an integrated radio signaler module." - var/qdeled = 0 - -/obj/item/weapon/cartridge/signal/science - name = "\improper Signal Ace 2 cartridge" - desc = "Complete with integrated radio signaler!" - icon_state = "cart-tox" - access_reagent_scanner = 1 - access_atmos = 1 - -/obj/item/weapon/cartridge/signal/Initialize() - radio = new /obj/item/radio/integrated/signal(src) - . = ..() - -/obj/item/weapon/cartridge/quartermaster - name = "\improper Space Parts & Space Vendors cartridge" - desc = "Perfect for the Quartermaster on the go!" - icon_state = "cart-q" - access_quartermaster = 1 - -/obj/item/weapon/cartridge/miner - name = "\improper Drill-Jockey 4.5 cartridge" - desc = "It's covered in some sort of sand." - icon_state = "cart-q" - -/obj/item/weapon/cartridge/head - name = "\improper Easy-Record DELUXE cartridge" - icon_state = "cart-h" - access_status_display = 1 - -/obj/item/weapon/cartridge/hop - name = "\improper HumanResources9001 cartridge" - icon_state = "cart-h" - access_status_display = 1 - access_quartermaster = 1 - access_janitor = 1 - access_security = 1 - -/obj/item/weapon/cartridge/hos - name = "\improper R.O.B.U.S.T. DELUXE cartridge" - icon_state = "cart-hos" - access_status_display = 1 - access_security = 1 - -/obj/item/weapon/cartridge/hos/Initialize() - radio = new /obj/item/radio/integrated/beepsky(src) - . = ..() - -/obj/item/weapon/cartridge/ce - name = "\improper Power-On DELUXE cartridge" - icon_state = "cart-ce" - access_status_display = 1 - access_engine = 1 - access_atmos = 1 - -/obj/item/weapon/cartridge/cmo - name = "\improper Med-U DELUXE cartridge" - icon_state = "cart-cmo" - access_status_display = 1 - access_reagent_scanner = 1 - access_medical = 1 - -/obj/item/weapon/cartridge/rd - name = "\improper Signal Ace DELUXE cartridge" - icon_state = "cart-rd" - access_status_display = 1 - access_reagent_scanner = 1 - access_atmos = 1 - -/obj/item/weapon/cartridge/rd/Initialize() - radio = new /obj/item/radio/integrated/signal(src) - . = ..() - -/obj/item/weapon/cartridge/captain - name = "\improper Value-PAK cartridge" - desc = "Now with 200% more value!" - icon_state = "cart-c" - access_quartermaster = 1 - access_janitor = 1 - access_engine = 1 - access_security = 1 - access_medical = 1 - access_reagent_scanner = 1 - access_status_display = 1 - access_atmos = 1 - -/obj/item/weapon/cartridge/syndicate - name = "\improper Detomatix cartridge" - icon_state = "cart" - access_remote_door = 1 - access_detonate_pda = 1 - remote_door_id = "smindicate" //Make sure this matches the syndicate shuttle's shield/door id!! //don't ask about the name, testing. - charges = 4 - -/obj/item/weapon/cartridge/proc/post_status(var/command, var/data1, var/data2) - - var/datum/radio_frequency/frequency = radio_controller.return_frequency(1435) - if(!frequency) return - - var/datum/signal/status_signal = new - status_signal.source = src - status_signal.transmission_method = TRANSMISSION_RADIO - status_signal.data["command"] = command - - switch(command) - if("message") - status_signal.data["msg1"] = data1 - status_signal.data["msg2"] = data2 - if(loc) - var/obj/item/PDA = loc - var/mob/user = PDA.fingerprintslast - log_admin("STATUS: [user] set status screen with [PDA]. Message: [data1] [data2]") - message_admins("STATUS: [user] set status screen with [PDA]. Message: [data1] [data2]") - - if("alert") - status_signal.data["picture_state"] = data1 - - frequency.post_signal(src, status_signal) - - -/* - This generates the nano values of the cart menus. - Because we close the UI when we insert a new cart - we don't have to worry about null values on items - the user can't access. Well, unless they are href hacking. - But in that case their UI will just lock up. -*/ - - -/obj/item/weapon/cartridge/proc/create_NanoUI_values(mob/user as mob) - var/values[0] - - /* Signaler (Mode: 40) */ - - - if(istype(radio,/obj/item/radio/integrated/signal) && (mode==40)) - var/obj/item/radio/integrated/signal/R = radio - values["signal_freq"] = format_frequency(R.frequency) - values["signal_code"] = R.code - - - /* Station Display (Mode: 42) */ - - if(mode==42) - values["message1"] = message1 ? message1 : "(none)" - values["message2"] = message2 ? message2 : "(none)" - - - - /* Power Monitor (Mode: 43 / 433) */ - - if(mode==43 || mode==433) - var/list/sensors = list() - var/obj/machinery/power/sensor/MS = null - var/my_z = get_z(user) - var/list/levels = using_map.get_map_levels(my_z) - - for(var/obj/machinery/power/sensor/S in machines) - if(!(get_z(S) in levels)) - continue - sensors.Add(list(list("name_tag" = S.name_tag))) - if(S.name_tag == selected_sensor) - MS = S - values["power_sensors"] = sensors - if(selected_sensor && MS) - values["sensor_reading"] = MS.return_reading_data() - - - /* General Records (Mode: 44 / 441 / 45 / 451) */ - if(mode == 44 || mode == 441 || mode == 45 || mode ==451) - if(istype(active1, /datum/data/record) && (active1 in data_core.general)) - values["general"] = active1.fields - values["general_exists"] = 1 - - else - values["general_exists"] = 0 - - - - /* Medical Records (Mode: 44 / 441) */ - - if(mode == 44 || mode == 441) - var/medData[0] - for(var/datum/data/record/R in sortRecord(data_core.general)) - medData[++medData.len] = list(Name = R.fields["name"],"ref" = "\ref[R]") - values["medical_records"] = medData - - if(istype(active2, /datum/data/record) && (active2 in data_core.medical)) - values["medical"] = active2.fields - values["medical_exists"] = 1 - else - values["medical_exists"] = 0 - - /* Security Records (Mode:45 / 451) */ - - if(mode == 45 || mode == 451) - var/secData[0] - for (var/datum/data/record/R in sortRecord(data_core.general)) - secData[++secData.len] = list(Name = R.fields["name"], "ref" = "\ref[R]") - values["security_records"] = secData - - if(istype(active3, /datum/data/record) && (active3 in data_core.security)) - values["security"] = active3.fields - values["security_exists"] = 1 - else - values["security_exists"] = 0 - - /* Security Bot Control (Mode: 46) */ - - if(mode==46) - var/botsData[0] - var/beepskyData[0] - if(istype(radio,/obj/item/radio/integrated/beepsky)) - var/obj/item/radio/integrated/beepsky/SC = radio - beepskyData["active"] = SC.active - if(SC.active && !isnull(SC.botstatus)) - var/area/loca = SC.botstatus["loca"] - var/loca_name = sanitize(loca.name) - beepskyData["botstatus"] = list("loca" = loca_name, "mode" = SC.botstatus["mode"]) - else - beepskyData["botstatus"] = list("loca" = null, "mode" = -1) - var/botsCount=0 - if(SC.botlist && SC.botlist.len) - for(var/mob/living/bot/B in SC.botlist) - botsCount++ - if(B.loc) - botsData[++botsData.len] = list("Name" = sanitize(B.name), "Location" = sanitize(B.loc.loc.name), "ref" = "\ref[B]") - - if(!botsData.len) - botsData[++botsData.len] = list("Name" = "No bots found", "Location" = "Invalid", "ref"= null) - - beepskyData["bots"] = botsData - beepskyData["count"] = botsCount - - else - beepskyData["active"] = 0 - botsData[++botsData.len] = list("Name" = "No bots found", "Location" = "Invalid", "ref"= null) - beepskyData["botstatus"] = list("loca" = null, "mode" = null) - beepskyData["bots"] = botsData - beepskyData["count"] = 0 - - values["beepsky"] = beepskyData - - - /* MULEBOT Control (Mode: 48) */ - - if(mode==48) - var/mulebotsData[0] - var/count = 0 - - for(var/mob/living/bot/mulebot/M in living_mob_list) - if(!M.on) - continue - ++count - var/muleData[0] - muleData["name"] = M.suffix - muleData["location"] = get_area(M) - muleData["paused"] = M.paused - muleData["home"] = M.homeName - muleData["target"] = M.targetName - muleData["ref"] = "\ref[M]" - muleData["load"] = M.load ? M.load.name : "Nothing" - - mulebotsData[++mulebotsData.len] = muleData.Copy() - - values["mulebotcount"] = count - values["mulebots"] = mulebotsData - - - - /* Supply Shuttle Requests Menu (Mode: 47) */ - - if(mode==47) - var/supplyData[0] - var/datum/shuttle/autodock/ferry/supply/shuttle = SSsupply.shuttle - if (shuttle) - supplyData["shuttle_moving"] = shuttle.has_arrive_time() - supplyData["shuttle_eta"] = shuttle.eta_minutes() - supplyData["shuttle_loc"] = shuttle.at_station() ? "Station" : "Dock" - var/supplyOrderCount = 0 - var/supplyOrderData[0] - for(var/S in SSsupply.shoppinglist) - var/datum/supply_order/SO = S - - supplyOrderData[++supplyOrderData.len] = list("Number" = SO.ordernum, "Name" = html_encode(SO.object.name), "ApprovedBy" = SO.ordered_by, "Comment" = html_encode(SO.comment)) - if(!supplyOrderData.len) - supplyOrderData[++supplyOrderData.len] = list("Number" = null, "Name" = null, "OrderedBy"=null) - - supplyData["approved"] = supplyOrderData - supplyData["approved_count"] = supplyOrderCount - - var/requestCount = 0 - var/requestData[0] - for(var/S in SSsupply.order_history) - var/datum/supply_order/SO = S - if(SO.status != SUP_ORDER_REQUESTED) - continue - - requestCount++ - requestData[++requestData.len] = list("Number" = SO.ordernum, "Name" = html_encode(SO.object.name), "OrderedBy" = SO.ordered_by, "Comment" = html_encode(SO.comment)) - if(!requestData.len) - requestData[++requestData.len] = list("Number" = null, "Name" = null, "orderedBy" = null, "Comment" = null) - - supplyData["requests"] = requestData - supplyData["requests_count"] = requestCount - - - values["supply"] = supplyData - - - - /* Janitor Supplies Locator (Mode: 49) */ - if(mode==49) - var/JaniData[0] - var/turf/cl = get_turf(src) - - if(cl) - JaniData["user_loc"] = list("x" = cl.x, "y" = cl.y) - else - JaniData["user_loc"] = list("x" = 0, "y" = 0) - var/MopData[0] - for(var/obj/item/weapon/mop/M in all_mops) - var/turf/ml = get_turf(M) - if(ml) - if(ml.z != cl.z) - continue - var/direction = get_dir(src, M) - MopData[++MopData.len] = list ("x" = ml.x, "y" = ml.y, "dir" = uppertext(dir2text(direction)), "status" = M.reagents.total_volume ? "Wet" : "Dry") - - if(!MopData.len) - MopData[++MopData.len] = list("x" = 0, "y" = 0, dir=null, status = null) - - - var/BucketData[0] - for(var/obj/structure/mopbucket/B in all_mopbuckets) - var/turf/bl = get_turf(B) - if(bl) - if(bl.z != cl.z) - continue - var/direction = get_dir(src,B) - BucketData[++BucketData.len] = list ("x" = bl.x, "y" = bl.y, "dir" = uppertext(dir2text(direction)), "status" = B.reagents.total_volume/100) - - if(!BucketData.len) - BucketData[++BucketData.len] = list("x" = 0, "y" = 0, dir=null, status = null) - - var/CbotData[0] - for(var/mob/living/bot/cleanbot/B in mob_list) - var/turf/bl = get_turf(B) - if(bl) - if(bl.z != cl.z) - continue - var/direction = get_dir(src,B) - CbotData[++CbotData.len] = list("x" = bl.x, "y" = bl.y, "dir" = uppertext(dir2text(direction)), "status" = B.on ? "Online" : "Offline") - - - if(!CbotData.len) - CbotData[++CbotData.len] = list("x" = 0, "y" = 0, dir=null, status = null) - var/CartData[0] - for(var/obj/structure/janitorialcart/B in all_janitorial_carts) - var/turf/bl = get_turf(B) - if(bl) - if(bl.z != cl.z) - continue - var/direction = get_dir(src,B) - var/status = "No Bucket" - if(B.mybucket) - status = B.mybucket.reagents.total_volume / 100 - CartData[++CartData.len] = list("x" = bl.x, "y" = bl.y, "dir" = uppertext(dir2text(direction)), "status" = status) - if(!CartData.len) - CartData[++CartData.len] = list("x" = 0, "y" = 0, dir=null, status = null) - - - - - JaniData["mops"] = MopData - JaniData["buckets"] = BucketData - JaniData["cleanbots"] = CbotData - JaniData["carts"] = CartData - values["janitor"] = JaniData - - return values - - - - - -/obj/item/weapon/cartridge/Topic(href, href_list) - ..() - - if (!usr.canmove || usr.stat || usr.restrained() || !in_range(loc, usr)) - usr.unset_machine() - usr << browse(null, "window=pda") - return - - - - - switch(href_list["choice"]) - if("Medical Records") - var/datum/data/record/R = locate(href_list["target"]) - var/datum/data/record/M = locate(href_list["target"]) - loc:mode = 441 - mode = 441 - if (R in data_core.general) - 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 - break - active1 = R - active2 = M - - if("Security Records") - var/datum/data/record/R = locate(href_list["target"]) - var/datum/data/record/S = locate(href_list["target"]) - loc:mode = 451 - mode = 451 - if (R in data_core.general) - 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 - break - active1 = R - active3 = S - - if("Send Signal") - if(is_jammed(src)) - return - spawn( 0 ) - radio:send_signal("ACTIVATE") - return - - if("Signal Frequency") - var/new_frequency = sanitize_frequency(radio:frequency + text2num(href_list["sfreq"])) - radio:set_frequency(new_frequency) - - if("Signal Code") - radio:code += text2num(href_list["scode"]) - radio:code = round(radio:code) - radio:code = min(100, radio:code) - radio:code = max(1, radio:code) - - if("Status") - switch(href_list["statdisp"]) - if("message") - post_status("message", message1, message2) - if("alert") - post_status("alert", href_list["alert"]) - if("setmsg1") - message1 = reject_bad_text(sanitize(input("Line 1", "Enter Message Text", message1) as text|null, 40), 40) - updateSelfDialog() - if("setmsg2") - message2 = reject_bad_text(sanitize(input("Line 2", "Enter Message Text", message2) as text|null, 40), 40) - updateSelfDialog() - else - post_status(href_list["statdisp"]) - - if("Power Select") - selected_sensor = href_list["target"] - loc:mode = 433 - mode = 433 - if("Power Clear") - selected_sensor = null - loc:mode = 43 - mode = 43 - - if("MULEbot") - var/mob/living/bot/mulebot/M = locate(href_list["ref"]) - if(istype(M)) - M.obeyCommand(href_list["command"]) - - return 1 diff --git a/code/game/objects/items/devices/PDA/cart_vr.dm b/code/game/objects/items/devices/PDA/cart_vr.dm deleted file mode 100644 index fc4e3d098a4..00000000000 --- a/code/game/objects/items/devices/PDA/cart_vr.dm +++ /dev/null @@ -1,17 +0,0 @@ -var/list/exploration_cartridges = list( - /obj/item/weapon/cartridge/explorer, - /obj/item/weapon/cartridge/sar - ) - -/obj/item/weapon/cartridge/explorer - name = "\improper Explorator cartridge" - icon_state = "cart-e" - access_reagent_scanner = 1 - access_atmos = 1 - -/obj/item/weapon/cartridge/sar - name = "\improper Med-Exp cartridge" - icon_state = "cart-m" - access_medical = 1 - access_reagent_scanner = 1 - access_atmos = 1 diff --git a/code/game/objects/items/devices/PDA/chatroom.dm b/code/game/objects/items/devices/PDA/chatroom.dm deleted file mode 100644 index 008dab64149..00000000000 --- a/code/game/objects/items/devices/PDA/chatroom.dm +++ /dev/null @@ -1,16 +0,0 @@ -var/list/chatrooms = list() - -/datum/chatroom - var/name = "Generic Chatroom" - var/list/logged_in = list() - var/list/logs = list() // chat logs - var/list/banned = list() // banned users - var/list/whitelist = list() // whitelisted users - var/list/muted = list() - var/topic = "" // topic message for the chatroom - var/password = "" // blank for no password. - var/operator = "" // name of the operator - -/datum/chatroom/proc/attempt_connect(var/obj/item/device/pda/device, var/obj/password) - if(!device) - return diff --git a/code/game/objects/items/devices/PDA/radio.dm b/code/game/objects/items/devices/PDA/radio.dm deleted file mode 100644 index 133798e4b18..00000000000 --- a/code/game/objects/items/devices/PDA/radio.dm +++ /dev/null @@ -1,153 +0,0 @@ -/obj/item/radio/integrated - name = "\improper PDA radio module" - desc = "An electronic radio system." - icon = 'icons/obj/module.dmi' - icon_state = "power_mod" - var/obj/item/device/pda/hostpda = null - - var/on = 0 //Are we currently active?? - var/menu_message = "" - - New() - ..() - if (istype(loc.loc, /obj/item/device/pda)) - hostpda = loc.loc - - proc/post_signal(var/freq, var/key, var/value, var/key2, var/value2, var/key3, var/value3, s_filter) - - //to_world("Post: [freq]: [key]=[value], [key2]=[value2]") - var/datum/radio_frequency/frequency = radio_controller.return_frequency(freq) - - if(!frequency) return - - var/datum/signal/signal = new() - signal.source = src - signal.transmission_method = TRANSMISSION_RADIO - signal.data[key] = value - if(key2) - signal.data[key2] = value2 - if(key3) - signal.data[key3] = value3 - - frequency.post_signal(src, signal, radio_filter = s_filter) - - return - - proc/generate_menu() - -/obj/item/radio/integrated/beepsky - var/list/botlist = null // list of bots - var/mob/living/bot/secbot/active // the active bot; if null, show bot list - var/list/botstatus // the status signal sent by the bot - - var/control_freq = BOT_FREQ - - // create a new QM cartridge, and register to receive bot control & beacon message - New() - ..() - spawn(5) - if(radio_controller) - radio_controller.add_object(src, control_freq, radio_filter = RADIO_SECBOT) - - // receive radio signals - // can detect bot status signals - // create/populate list as they are recvd - - receive_signal(datum/signal/signal) -// var/obj/item/device/pda/P = src.loc - - /* - to_world("recvd:[P] : [signal.source]") - for(var/d in signal.data) - to_world("- [d] = [signal.data[d]]") - */ - if (signal.data["type"] == "secbot") - if(!botlist) - botlist = new() - - if(!(signal.source in botlist)) - botlist += signal.source - - if(active == signal.source) - var/list/b = signal.data - botstatus = b.Copy() - -// if (istype(P)) P.updateSelfDialog() - - Topic(href, href_list) - ..() - var/obj/item/device/pda/PDA = src.hostpda - - switch(href_list["op"]) - - if("control") - active = locate(href_list["bot"]) - post_signal(control_freq, "command", "bot_status", "active", active, s_filter = RADIO_SECBOT) - - if("scanbots") // find all bots - botlist = null - post_signal(control_freq, "command", "bot_status", s_filter = RADIO_SECBOT) - - if("botlist") - active = null - - if("stop", "go") - post_signal(control_freq, "command", href_list["op"], "active", active, s_filter = RADIO_SECBOT) - post_signal(control_freq, "command", "bot_status", "active", active, s_filter = RADIO_SECBOT) - - if("summon") - post_signal(control_freq, "command", "summon", "active", active, "target", get_turf(PDA) , s_filter = RADIO_SECBOT) - post_signal(control_freq, "command", "bot_status", "active", active, s_filter = RADIO_SECBOT) - - -/obj/item/radio/integrated/beepsky/Destroy() - if(radio_controller) - radio_controller.remove_object(src, control_freq) - return ..() - -/* - * Radio Cartridge, essentially a signaler. - */ - - -/obj/item/radio/integrated/signal - var/frequency = 1457 - var/code = 30.0 - var/last_transmission - var/datum/radio_frequency/radio_connection - -/obj/item/radio/integrated/signal/Initialize() - if(!radio_controller) - return - - if (src.frequency < PUBLIC_LOW_FREQ || src.frequency > PUBLIC_HIGH_FREQ) - src.frequency = sanitize_frequency(src.frequency) - - set_frequency(frequency) - -/obj/item/radio/integrated/signal/proc/set_frequency(new_frequency) - radio_controller.remove_object(src, frequency) - frequency = new_frequency - radio_connection = radio_controller.add_object(src, frequency) - -/obj/item/radio/integrated/signal/proc/send_signal(message="ACTIVATE") - - if(last_transmission && world.time < (last_transmission + 5)) - return - last_transmission = world.time - - var/time = time2text(world.realtime,"hh:mm:ss") - var/turf/T = get_turf(src) - lastsignalers.Add("[time] : [usr.key] used [src] @ location ([T.x],[T.y],[T.z]) : [format_frequency(frequency)]/[code]") - - var/datum/signal/signal = new - signal.source = src - signal.encryption = code - signal.data["message"] = message - - radio_connection.post_signal(src, signal) - -/obj/item/radio/integrated/signal/Destroy() - if(radio_controller) - radio_controller.remove_object(src, frequency) - return ..() diff --git a/code/game/objects/items/devices/paicard.dm b/code/game/objects/items/devices/paicard.dm index 8b7f55e0a9f..77e463c0d0d 100644 --- a/code/game/objects/items/devices/paicard.dm +++ b/code/game/objects/items/devices/paicard.dm @@ -33,6 +33,24 @@ GLOBAL_LIST_BOILERPLATE(all_pai_cards, /obj/item/device/paicard) QDEL_NULL(radio) return ..() +/obj/item/device/paicard/attack_ghost(mob/observer/dead/user) + if(istype(user) && user.can_admin_interact()) + switch(alert(user, "Would you like to become a pAI by force? (Admin)", "pAI Creation", "Yes", "No")) + if("Yes") + // Copied from paiController/Topic + var/mob/living/silicon/pai/pai = new(src) + pai.name = user.name + pai.real_name = pai.name + pai.key = user.key + + setPersonality(pai) + looking_for_personality = FALSE + + if(pai.mind) + update_antag_icons(pai.mind) + return ..() + + /obj/item/device/paicard/attack_self(mob/user) if (!in_range(src, user)) return diff --git a/code/game/objects/items/devices/uplink.dm b/code/game/objects/items/devices/uplink.dm index 5a19c72742b..5b6e55290e9 100644 --- a/code/game/objects/items/devices/uplink.dm +++ b/code/game/objects/items/devices/uplink.dm @@ -1,13 +1,23 @@ GLOBAL_LIST_BOILERPLATE(world_uplinks, /obj/item/device/uplink) +// I placed this here because of how relevant it is. +// You place this in your uplinkable item to check if an uplink is active or not. +// If it is, it will display the uplink menu and return 1, else it'll return false. +// If it returns true, I recommend closing the item's normal menu with "user << browse(null, "window=name")" +/obj/item/proc/active_uplink_check(mob/user as mob) + // Activates the uplink if it's active + if(hidden_uplink) + if(hidden_uplink.active) + hidden_uplink.trigger(user) + return TRUE + return FALSE + /obj/item/device/uplink var/welcome = "Welcome, Operative" // Welcoming menu message var/uses // Numbers of crystals var/list/ItemsCategory // List of categories with lists of items var/list/ItemsReference // List of references with an associated item var/list/nanoui_items // List of items for NanoUI use - var/nanoui_menu = 0 // The current menu we are in - var/list/nanoui_data = new // Additional data for NanoUI use var/faction = "" //Antag faction holder. var/list/purchase_log = new @@ -17,9 +27,7 @@ GLOBAL_LIST_BOILERPLATE(world_uplinks, /obj/item/device/uplink) var/next_offer_time var/datum/uplink_item/discount_item //The item to be discounted var/discount_amount //The amount as a percent the item will be discounted by - -/obj/item/device/uplink/nano_host() - return loc + var/compact_mode = FALSE /obj/item/device/uplink/Initialize(var/mapload, var/datum/mind/owner = null, var/telecrystals = DEFAULT_TELECRYSTAL_AMOUNT) . = ..() @@ -53,23 +61,20 @@ GLOBAL_LIST_BOILERPLATE(world_uplinks, /obj/item/device/uplink) name = "hidden uplink" desc = "There is something wrong if you're examining this." var/active = 0 - var/datum/uplink_category/category = 0 // The current category we are in var/exploit_id // Id of the current exploit record we are viewing + var/selected_cat // The hidden uplink MUST be inside an obj/item's contents. /obj/item/device/uplink/hidden/Initialize() . = ..() if(!isitem(loc)) return INITIALIZE_HINT_QDEL - nanoui_data = list() - update_nano_data() /obj/item/device/uplink/hidden/next_offer() discount_item = default_uplink_selection.get_random_item(INFINITY) discount_amount = pick(90;0.9, 80;0.8, 70;0.7, 60;0.6, 50;0.5, 40;0.4, 30;0.3, 20;0.2, 10;0.1) - update_nano_data() - SSnanoui.update_uis(src) next_offer_time = world.time + offer_time + SStgui.update_uis(src) addtimer(CALLBACK(src, .proc/next_offer), offer_time) // Toggles the uplink on and off. Normally this will bypass the item's normal functions and go to the uplink menu, if activated. @@ -91,136 +96,137 @@ GLOBAL_LIST_BOILERPLATE(world_uplinks, /obj/item/device/uplink) return 1 return 0 -/* - NANO UI FOR UPLINK WOOP WOOP -*/ -/obj/item/device/uplink/hidden/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - var/title = "Remote Uplink" - var/data[0] - uses = user.mind.tcrystals - if(ishuman(user)) - var/mob/living/carbon/human/H = user - faction = H.antag_faction +// Legacy +/obj/item/device/uplink/hidden/interact(mob/user) + tgui_interact(user) - data["welcome"] = welcome - data["crystals"] = uses - data["menu"] = nanoui_menu - data += nanoui_data +/***************** + * Uplink TGUI + *****************/ +/obj/item/device/uplink/tgui_host() + return loc - // update the ui if it exists, returns null if no ui is passed/found - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open) - if (!ui) // No auto-refresh - ui = new(user, src, ui_key, "uplink.tmpl", title, 630, 700, state = inventory_state) - data["menu"] = 0 - ui.set_initial_data(data) +/obj/item/device/uplink/hidden/tgui_state(mob/user) + return GLOB.tgui_inventory_state + +/obj/item/device/uplink/hidden/tgui_interact(mob/user, datum/tgui/ui, datum/tgui/parent_ui) + if(!active) + toggle() + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Uplink", "Remote Uplink") + // This UI is only ever opened by one person, + // and never is updated outside of user input. + ui.set_autoupdate(FALSE) ui.open() +/obj/item/device/uplink/hidden/tgui_data(mob/user, datum/tgui/ui, datum/tgui_state/state) + if(!user.mind) + return -// Interaction code. Gathers a list of items purchasable from the paren't uplink and displays it. It also adds a lock button. -/obj/item/device/uplink/hidden/interact(mob/user) - ui_interact(user) + var/list/data = ..() -/obj/item/device/uplink/hidden/CanUseTopic() + data["telecrystals"] = uses + data["lockable"] = TRUE + data["compactMode"] = compact_mode + + data["discount_name"] = discount_item ? discount_item.name : "" + data["discount_amount"] = (1-discount_amount)*100 + data["offer_expiry"] = worldtime2stationtime(next_offer_time) + + data["exploit"] = null + data["locked_records"] = null + + if(exploit_id) + for(var/datum/data/record/L in data_core.locked) + if(L.fields["id"] == exploit_id) + data["exploit"] = list() // Setting this to equal L.fields passes it's variables that are lists as reference instead of value. + // We trade off being able to automatically add shit for more control over what gets passed to json + // and if it's sanitized for html. + data["exploit"]["nanoui_exploit_record"] = html_encode(L.fields["exploit_record"]) // Change stuff into html + data["exploit"]["nanoui_exploit_record"] = replacetext(data["exploit"]["nanoui_exploit_record"], "\n", "
") // change line breaks into
+ data["exploit"]["name"] = html_encode(L.fields["name"]) + data["exploit"]["sex"] = html_encode(L.fields["sex"]) + data["exploit"]["age"] = html_encode(L.fields["age"]) + data["exploit"]["species"] = html_encode(L.fields["species"]) + data["exploit"]["rank"] = html_encode(L.fields["rank"]) + data["exploit"]["home_system"] = html_encode(L.fields["home_system"]) + data["exploit"]["citizenship"] = html_encode(L.fields["citizenship"]) + data["exploit"]["faction"] = html_encode(L.fields["faction"]) + data["exploit"]["religion"] = html_encode(L.fields["religion"]) + data["exploit"]["fingerprint"] = html_encode(L.fields["fingerprint"]) + if(L.fields["antagvis"] == ANTAG_KNOWN || (faction == L.fields["antagfac"] && (L.fields["antagvis"] == ANTAG_SHARED))) + data["exploit"]["antagfaction"] = html_encode(L.fields["antagfac"]) + else + data["exploit"]["antagfaction"] = html_encode("None") + break + else + var/list/permanentData = list() + for(var/datum/data/record/L in sortRecord(data_core.locked)) + permanentData.Add(list(list( + "name" = L.fields["name"], + "id" = L.fields["id"] + ))) + data["locked_records"] = permanentData + + return data + +/obj/item/device/uplink/hidden/tgui_static_data(mob/user) + var/list/data = ..() + + data["categories"] = list() + for(var/datum/uplink_category/category in uplink.categories) + if(category.can_view(src)) + var/list/cat = list( + "name" = category.name, + "items" = (category == selected_cat ? list() : null) + ) + for(var/datum/uplink_item/item in category.items) + if(!item.can_view(src)) + continue + var/cost = item.cost(uses, src) || "???" + cat["items"] += list(list( + "name" = item.name, + "cost" = cost, + "desc" = item.description(), + "ref" = REF(item), + )) + data["categories"] += list(cat) + + return data + +/obj/item/device/uplink/hidden/tgui_status(mob/user, datum/tgui_state/state) if(!active) return STATUS_CLOSE return ..() -// The purchasing code. -/obj/item/device/uplink/hidden/Topic(href, href_list) +/obj/item/device/uplink/hidden/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state) if(..()) - return 1 + return TRUE - var/mob/user = usr - if(href_list["buy_item"]) - var/datum/uplink_item/UI = (locate(href_list["buy_item"]) in uplink.items) - UI.buy(src, usr) - else if(href_list["lock"]) - toggle() - var/datum/nanoui/ui = SSnanoui.get_open_ui(user, src, "main") - ui.close() - else if(href_list["return"]) - nanoui_menu = round(nanoui_menu/10) - else if(href_list["menu"]) - nanoui_menu = text2num(href_list["menu"]) - if(href_list["id"]) - exploit_id = href_list["id"] - else if(href_list["category"]) - category = locate(href_list["category"]) in uplink.categories - - update_nano_data() - return 1 - -/obj/item/device/uplink/hidden/proc/update_nano_data() - if(nanoui_menu == 0) - var/categories[0] - for(var/datum/uplink_category/category in uplink.categories) - if(category.can_view(src)) - categories[++categories.len] = list("name" = category.name, "ref" = "\ref[category]") - nanoui_data["categories"] = categories - nanoui_data["discount_name"] = discount_item ? discount_item.name : "" - nanoui_data["discount_amount"] = (1-discount_amount)*100 - nanoui_data["offer_expiry"] = worldtime2stationtime(next_offer_time) - - if(category) - nanoui_data["current_category"] = category.name - var/items[0] - for(var/datum/uplink_item/item in category.items) - if(item.can_view(src)) - var/cost = item.cost(uses, src) - if(!cost) cost = "???" - items[++items.len] = list("name" = item.name, "description" = replacetext(item.description(), "\n", "
"), "can_buy" = item.can_buy(src), "cost" = cost, "ref" = "\ref[item]") - nanoui_data["items"] = items - - else if(nanoui_menu == 2) - var/permanentData[0] - for(var/datum/data/record/L in sortRecord(data_core.locked)) - permanentData[++permanentData.len] = list(Name = L.fields["name"],"id" = L.fields["id"]) - nanoui_data["exploit_records"] = permanentData - else if(nanoui_menu == 21) - nanoui_data["exploit_exists"] = 0 - - for(var/datum/data/record/L in data_core.locked) - if(L.fields["id"] == exploit_id) - nanoui_data["exploit"] = list() // Setting this to equal L.fields passes it's variables that are lists as reference instead of value. - // We trade off being able to automatically add shit for more control over what gets passed to json - // and if it's sanitized for html. - nanoui_data["exploit"]["nanoui_exploit_record"] = html_encode(L.fields["exploit_record"]) // Change stuff into html - nanoui_data["exploit"]["nanoui_exploit_record"] = replacetext(nanoui_data["exploit"]["nanoui_exploit_record"], "\n", "
") // change line breaks into
- nanoui_data["exploit"]["name"] = html_encode(L.fields["name"]) - nanoui_data["exploit"]["sex"] = html_encode(L.fields["sex"]) - nanoui_data["exploit"]["age"] = html_encode(L.fields["age"]) - nanoui_data["exploit"]["species"] = html_encode(L.fields["species"]) - nanoui_data["exploit"]["rank"] = html_encode(L.fields["rank"]) - nanoui_data["exploit"]["home_system"] = html_encode(L.fields["home_system"]) - nanoui_data["exploit"]["citizenship"] = html_encode(L.fields["citizenship"]) - nanoui_data["exploit"]["faction"] = html_encode(L.fields["faction"]) - nanoui_data["exploit"]["religion"] = html_encode(L.fields["religion"]) - nanoui_data["exploit"]["fingerprint"] = html_encode(L.fields["fingerprint"]) - if(L.fields["antagvis"] == ANTAG_KNOWN || (faction == L.fields["antagfac"] && (L.fields["antagvis"] == ANTAG_SHARED))) - nanoui_data["exploit"]["antagfaction"] = html_encode(L.fields["antagfac"]) - else - nanoui_data["exploit"]["antagfaction"] = html_encode("None") - nanoui_data["exploit_exists"] = 1 - break - -// I placed this here because of how relevant it is. -// You place this in your uplinkable item to check if an uplink is active or not. -// If it is, it will display the uplink menu and return 1, else it'll return false. -// If it returns true, I recommend closing the item's normal menu with "user << browse(null, "window=name")" -/obj/item/proc/active_uplink_check(mob/user as mob) - // Activates the uplink if it's active - if(src.hidden_uplink) - if(src.hidden_uplink.active) - src.hidden_uplink.trigger(user) - return 1 - return 0 + switch(action) + if("buy") + var/datum/uplink_item/UI = (locate(params["ref"]) in uplink.items) + UI.buy(src, usr) + return TRUE + if("lock") + toggle() + SStgui.close_uis(src) + if("select") + selected_cat = params["category"] + return TRUE + if("compact_toggle") + compact_mode = !compact_mode + return TRUE + if("view_exploits") + exploit_id = params["id"] + return TRUE // PRESET UPLINKS // A collection of preset uplinks. // // Includes normal radio uplink, multitool uplink, // implant uplink (not the implant tool) and a preset headset uplink. - /obj/item/device/radio/uplink/New(atom/loc, datum/mind/target_mind, telecrystals) ..(loc) hidden_uplink = new(src, target_mind, telecrystals) diff --git a/code/modules/events/money_spam.dm b/code/modules/events/money_spam.dm index 0b877cef3d7..ad8a1bbc3f1 100644 --- a/code/modules/events/money_spam.dm +++ b/code/modules/events/money_spam.dm @@ -36,7 +36,11 @@ var/obj/item/device/pda/P var/list/viables = list() for(var/obj/item/device/pda/check_pda in sortAtom(PDAs)) - if (!check_pda.owner||check_pda.toff||check_pda == src||check_pda.hidden) + if (!check_pda.owner || check_pda == src || check_pda.hidden) + continue + + var/datum/data/pda/app/messenger/M = check_pda.find_program(/datum/data/pda/app/messenger) + if(!M || M.toff) continue viables.Add(check_pda) @@ -112,17 +116,5 @@ //Commented out because we don't send messages like this anymore. Instead it will just popup in their chat window. //P.tnote += "← From [sender] (Unknown / spam?):
[message]
" - if (!P.message_silent) - playsound(P, 'sound/machines/twobeep.ogg', 50, 1) - for (var/mob/O in hearers(3, P.loc)) - if(!P.message_silent) O.show_message(text("[bicon(P)] *[P.ttone]*")) - //Search for holder of the PDA. - var/mob/living/L = null - if(P.loc && isliving(P.loc)) - L = P.loc - //Maybe they are a pAI! - else - L = get(P, /mob/living/silicon) - - if(L) - to_chat(L, "[bicon(P)] Message from [sender] (Unknown / spam?), \"[message]\" (Unable to Reply)") + var/datum/data/pda/app/messenger/PM = P.find_program(/datum/data/pda/app/messenger) + PM.notify("Message from [sender] (Unknown / spam?), \"[message]\" (Unable to Reply)", 0) diff --git a/code/modules/gamemaster/event2/events/everyone/pda_spam.dm b/code/modules/gamemaster/event2/events/everyone/pda_spam.dm index b56687283c4..c6a1532d0ed 100644 --- a/code/modules/gamemaster/event2/events/everyone/pda_spam.dm +++ b/code/modules/gamemaster/event2/events/everyone/pda_spam.dm @@ -35,7 +35,11 @@ var/list/viables = list() for(var/obj/item/device/pda/check_pda in sortAtom(PDAs)) - if(!check_pda.owner || check_pda.toff || check_pda.hidden || check_pda.spam_proof) + if (!check_pda.owner || check_pda == src || check_pda.hidden) + continue + + var/datum/data/pda/app/messenger/M = check_pda.find_program(/datum/data/pda/app/messenger) + if(!M || M.toff) continue viables += check_pda @@ -93,7 +97,7 @@ message = pick("Luxury watches for Blowout sale prices!",\ "Watches, Jewelry & Accessories, Bags & Wallets !",\ "Deposit 100$ and get 300$ totally free!",\ - " 100K NT.|WOWGOLD õnly $89 ",\ + " 100K NT.|WOWGOLD �nly $89 ",\ "We have been filed with a complaint from one of your customers in respect of their business relations with you.",\ "We kindly ask you to open the COMPLAINT REPORT (attached) to reply on this complaint..") if(4) @@ -127,7 +131,8 @@ /datum/event2/event/pda_spam/proc/send_spam(obj/item/device/pda/P, sender, message) last_spam_time = world.time - P.spam_message(sender, message) + var/datum/data/pda/app/messenger/PM = P.find_program(/datum/data/pda/app/messenger) + PM.notify("Message from [sender] (Unknown / spam?), \"[message]\" (Unable to Reply)", 0) if(spam_debug) log_debug("PDA Spam event sent spam to \the [P].") diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm index 3a995ba19df..6e4cd0a4010 100644 --- a/code/modules/mob/living/silicon/pai/pai.dm +++ b/code/modules/mob/living/silicon/pai/pai.dm @@ -122,7 +122,10 @@ pda.ownjob = "Personal Assistant" pda.owner = text("[]", src) pda.name = pda.owner + " (" + pda.ownjob + ")" - pda.toff = 1 + + var/datum/data/pda/app/messenger/M = pda.find_program(/datum/data/pda/app/messenger) + if(M) + M.toff = TRUE ..() /mob/living/silicon/pai/Login() diff --git a/code/modules/mob/living/silicon/pai/software.dm b/code/modules/mob/living/silicon/pai/software.dm index bbb038deada..342e8d7f5c8 100644 --- a/code/modules/mob/living/silicon/pai/software.dm +++ b/code/modules/mob/living/silicon/pai/software.dm @@ -108,6 +108,7 @@ var/global/list/default_pai_software = list() S.toggle(src) else ui_interact(src, ui_key = soft) + S.tgui_interact(src) return 1 else if(href_list["stopic"]) diff --git a/code/modules/mob/living/silicon/pai/software_modules.dm b/code/modules/mob/living/silicon/pai/software_modules.dm index 8f7e8462de3..dd92edfdab1 100644 --- a/code/modules/mob/living/silicon/pai/software_modules.dm +++ b/code/modules/mob/living/silicon/pai/software_modules.dm @@ -21,6 +21,14 @@ proc/is_active(mob/living/silicon/pai/user) return 0 +/datum/pai_software/tgui_state(mob/user) + return GLOB.tgui_always_state + +/datum/pai_software/tgui_status(mob/user) + if(!istype(user, /mob/living/silicon/pai)) + return STATUS_CLOSE + return ..() + /datum/pai_software/directives name = "Directives" ram_cost = 0 @@ -142,75 +150,8 @@ 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.message_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 = SSnanoui.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.message_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/messenger/tgui_interact(mob/living/silicon/pai/user, datum/tgui/ui, datum/tgui/parent_ui) + return user.pda.tgui_interact(user) /datum/pai_software/med_records name = "Medical Records" diff --git a/code/modules/nano/nanoui.dm b/code/modules/nano/nanoui.dm index 900f5b9d336..29863f56170 100644 --- a/code/modules/nano/nanoui.dm +++ b/code/modules/nano/nanoui.dm @@ -109,6 +109,7 @@ nanoui is used to open and update nano browser uis add_script("nano_state_manager.js") // The NanoStateManager JS, it handles updates from the server and passes data to the current state add_script("nano_state.js") // The NanoState JS, this is the base state which all states must inherit from add_script("nano_state_default.js") // The NanoStateDefault JS, this is the "default" state (used by all UIs by default), which inherits from NanoState + add_script("nano_state_pda.js") // The NanoStatePDA JS, this is the state used for PDAs. add_script("nano_base_callbacks.js") // The NanoBaseCallbacks JS, this is used to set up (before and after update) callbacks which are common to all UIs add_script("nano_base_helpers.js") // The NanoBaseHelpers JS, this is used to set up template helpers which are common to all UIs add_stylesheet("shared.css") // this CSS sheet is common to all UIs diff --git a/code/modules/pda/ai.dm b/code/modules/pda/ai.dm new file mode 100644 index 00000000000..b8a0d99a390 --- /dev/null +++ b/code/modules/pda/ai.dm @@ -0,0 +1,110 @@ + +// Special AI/pAI PDAs that cannot explode. +/obj/item/device/pda/ai + icon_state = "NONE" + ttone = "data" + detonate = 0 + + +/obj/item/device/pda/ai/proc/set_name_and_job(newname as text, newjob as text, newrank as null|text) + owner = newname + ownjob = newjob + if(newrank) + ownrank = newrank + else + ownrank = ownjob + name = newname + " (" + ownjob + ")" + +//AI verb and proc for sending PDA messages. +/obj/item/device/pda/ai/verb/cmd_send_pdamesg() + set category = "AI IM" + set name = "Send PDA Message" + set src in usr + + if(!can_use()) + return + var/datum/data/pda/app/messenger/M = find_program(/datum/data/pda/app/messenger) + if(!M) + to_chat(usr, "Cannot use messenger!") + var/list/plist = M.available_pdas() + if(plist) + var/c = input(usr, "Please select a PDA") as null|anything in sortList(plist) + if(!c) // if the user hasn't selected a PDA file we can't send a message + return + var/selected = plist[c] + M.create_message(usr, selected) + +/obj/item/device/pda/ai/verb/cmd_toggle_pda_receiver() + set category = "AI IM" + set name = "Toggle Sender/Receiver" + set src in usr + + if(!can_use()) + return + var/datum/data/pda/app/messenger/M = find_program(/datum/data/pda/app/messenger) + M.toff = !M.toff + to_chat(usr, "PDA sender/receiver toggled [(M.toff ? "Off" : "On")]!") + +/obj/item/device/pda/ai/verb/cmd_toggle_pda_silent() + set category = "AI IM" + set name = "Toggle Ringer" + set src in usr + + if(!can_use()) + return + var/datum/data/pda/app/messenger/M = find_program(/datum/data/pda/app/messenger) + M.notify_silent = !M.notify_silent + to_chat(usr, "PDA ringer toggled [(M.notify_silent ? "Off" : "On")]!") + +/obj/item/device/pda/ai/verb/cmd_show_message_log() + set category = "AI IM" + set name = "Show Message Log" + set src in usr + + if(!can_use()) + return + var/datum/data/pda/app/messenger/M = find_program(/datum/data/pda/app/messenger) + if(!M) + to_chat(usr, "Cannot use messenger!") + var/HTML = "AI PDA Message Log" + for(var/index in M.tnote) + if(index["sent"]) + HTML += addtext("→ To ", index["owner"],":
", index["message"], "
") + else + HTML += addtext("← From ", index["owner"],":
", index["message"], "
") + HTML +="" + usr << browse(HTML, "window=log;size=400x444;border=1;can_resize=1;can_close=1;can_minimize=0") + + +/obj/item/device/pda/ai/can_use() + return 1 + + +/obj/item/device/pda/ai/attack_self(mob/user as mob) + if ((honkamt > 0) && (prob(60)))//For clown virus. + honkamt-- + playsound(src, 'sound/items/bikehorn.ogg', 30, 1) + return + + +/obj/item/device/pda/ai/pai + ttone = "assist" + var/our_owner = null // Ref to a pAI + touch_silent = TRUE + programs = list( + new/datum/data/pda/app/main_menu, + new/datum/data/pda/app/notekeeper, + new/datum/data/pda/app/messenger) + +/obj/item/device/pda/ai/pai/New(mob/living/silicon/pai/P) + if(istype(P)) + our_owner = REF(P) + return ..() + +/obj/item/device/pda/ai/pai/tgui_status(mob/living/silicon/pai/user, datum/tgui_state/state) + if(!istype(user) || REF(user) != our_owner) // Only allow our pAI to interface with us + return STATUS_CLOSE + return ..() + +/obj/item/device/pda/ai/shell + spam_proof = TRUE // Since empty shells get a functional PDA. diff --git a/code/modules/pda/app.dm b/code/modules/pda/app.dm new file mode 100644 index 00000000000..eb64125ee9b --- /dev/null +++ b/code/modules/pda/app.dm @@ -0,0 +1,109 @@ +// Base class for anything that can show up on home screen +/datum/data/pda + var/icon = "tasks" //options comes from http://fontawesome.io/icons/ + var/notify_icon = "exclamation-circle" + var/notify_silent = 0 + var/hidden = 0 // program not displayed in main menu + var/category = "General" // the category to list it in on the main menu + var/obj/item/device/pda/pda // if this is null, and the app is running code, something's gone wrong + +/datum/data/pda/Destroy() + pda = null + return ..() + +/datum/data/pda/proc/start() + return + +/datum/data/pda/proc/stop() + return + +/datum/data/pda/proc/program_process() + return + +/datum/data/pda/proc/program_hit_check() + return + +/datum/data/pda/proc/notify(message, blink = 1) + if(message) + //Search for holder of the PDA. + var/mob/living/L = null + if(pda.loc && isliving(pda.loc)) + L = pda.loc + //Maybe they are a pAI! + else + L = get(pda, /mob/living/silicon) + + if(L) + to_chat(L, "[bicon(pda)] [message]") + SStgui.update_user_uis(L, pda) // Update the receiving user's PDA UI so that they can see the new message + + if(!notify_silent) + pda.play_ringtone() + + if(blink && !(src in pda.notifying_programs)) + pda.overlays += image('icons/obj/pda.dmi', "pda-r") + pda.notifying_programs |= src + +/datum/data/pda/proc/unnotify() + if(src in pda.notifying_programs) + pda.notifying_programs -= src + if(!pda.notifying_programs.len) + pda.overlays -= image('icons/obj/pda.dmi', "pda-r") + +// An app has a button on the home screen and its own UI +/datum/data/pda/app + name = "App" + size = 3 + var/title = null // what is displayed in the title bar when this is the current app + var/template = "" + var/update = PDA_APP_UPDATE + var/has_back = 0 + +/datum/data/pda/app/tgui_host(mob/user) + return pda || src + +/datum/data/pda/app/New() + if(!title) + title = name + +/datum/data/pda/app/start() + if(pda.current_app) + pda.current_app.stop() + pda.current_app = src + return 1 + +/datum/data/pda/app/proc/update_ui(mob/user as mob, list/data) + + +// Utilities just have a button on the home screen, but custom code when clicked +/datum/data/pda/utility + name = "Utility" + icon = "gear" + size = 1 + category = "Utilities" + + +/datum/data/pda/utility/scanmode + var/base_name + category = "Scanners" + +/datum/data/pda/utility/scanmode/New(obj/item/weapon/cartridge/C) + ..(C) + name = "Enable [base_name]" + +/datum/data/pda/utility/scanmode/start() + if(pda.scanmode) + pda.scanmode.name = "Enable [pda.scanmode.base_name]" + + if(pda.scanmode == src) + pda.scanmode = null + else + pda.scanmode = src + name = "Disable [base_name]" + + pda.update_shortcuts() + return 1 + +/datum/data/pda/utility/scanmode/proc/scan_mob(mob/living/C as mob, mob/living/user as mob) + +/datum/data/pda/utility/scanmode/proc/scan_atom(atom/A as mob|obj|turf|area, mob/user as mob) diff --git a/code/modules/pda/cart.dm b/code/modules/pda/cart.dm new file mode 100644 index 00000000000..2ec6a4e5366 --- /dev/null +++ b/code/modules/pda/cart.dm @@ -0,0 +1,311 @@ +var/list/command_cartridges = list( + /obj/item/weapon/cartridge/captain, + /obj/item/weapon/cartridge/hop, + /obj/item/weapon/cartridge/hos, + /obj/item/weapon/cartridge/ce, + /obj/item/weapon/cartridge/rd, + /obj/item/weapon/cartridge/cmo, + /obj/item/weapon/cartridge/head, + /obj/item/weapon/cartridge/lawyer // Internal Affaris, + ) + +var/list/security_cartridges = list( + /obj/item/weapon/cartridge/security, + /obj/item/weapon/cartridge/detective, + /obj/item/weapon/cartridge/hos + ) + +var/list/engineering_cartridges = list( + /obj/item/weapon/cartridge/engineering, + /obj/item/weapon/cartridge/atmos, + /obj/item/weapon/cartridge/ce + ) + +var/list/medical_cartridges = list( + /obj/item/weapon/cartridge/medical, + /obj/item/weapon/cartridge/chemistry, + /obj/item/weapon/cartridge/cmo + ) + +var/list/research_cartridges = list( + /obj/item/weapon/cartridge/signal/science, + /obj/item/weapon/cartridge/rd + ) + +var/list/cargo_cartridges = list( + /obj/item/weapon/cartridge/quartermaster, // This also covers cargo-techs, apparently, + /obj/item/weapon/cartridge/miner, + /obj/item/weapon/cartridge/hop + ) + +var/list/civilian_cartridges = list( + /obj/item/weapon/cartridge/janitor, + /obj/item/weapon/cartridge/service, + /obj/item/weapon/cartridge/hop + ) + +/obj/item/weapon/cartridge + name = "generic cartridge" + desc = "A data cartridge for portable microcomputers." + icon = 'icons/obj/pda.dmi' + icon_state = "cart" + item_state = "electronic" + w_class = ITEMSIZE_TINY + drop_sound = 'sound/items/drop/component.ogg' + pickup_sound = 'sound/items/pickup/component.ogg' + + var/obj/item/radio/integrated/radio = null + + var/charges = 0 + + var/list/stored_data = list() + var/list/programs = list() + var/list/messenger_plugins = list() + +/obj/item/weapon/cartridge/Destroy() + QDEL_NULL(radio) + QDEL_LIST(programs) + QDEL_LIST(messenger_plugins) + return ..() + +/obj/item/weapon/cartridge/proc/update_programs(obj/item/device/pda/pda) + for(var/A in programs) + var/datum/data/pda/P = A + P.pda = pda + for(var/A in messenger_plugins) + var/datum/data/pda/messenger_plugin/P = A + P.pda = pda + +/obj/item/weapon/cartridge/engineering + name = "\improper Power-ON cartridge" + icon_state = "cart-e" + programs = list( + new/datum/data/pda/app/power, + new/datum/data/pda/utility/scanmode/halogen) + +/obj/item/weapon/cartridge/atmos + name = "\improper BreatheDeep cartridge" + icon_state = "cart-a" + programs = list(new/datum/data/pda/utility/scanmode/gas) + +/obj/item/weapon/cartridge/medical + name = "\improper Med-U cartridge" + icon_state = "cart-m" + programs = list( + new/datum/data/pda/app/crew_records/medical, + new/datum/data/pda/utility/scanmode/medical) + +/obj/item/weapon/cartridge/chemistry + name = "\improper ChemWhiz cartridge" + icon_state = "cart-chem" + programs = list( + new/datum/data/pda/app/crew_records/medical, + new/datum/data/pda/utility/scanmode/medical, + new/datum/data/pda/utility/scanmode/reagent) + +/obj/item/weapon/cartridge/security + name = "\improper R.O.B.U.S.T. cartridge" + icon_state = "cart-s" + programs = list( + new/datum/data/pda/app/crew_records/security) + +/obj/item/weapon/cartridge/detective + name = "\improper D.E.T.E.C.T. cartridge" + icon_state = "cart-s" + programs = list( + new/datum/data/pda/app/crew_records/medical, + new/datum/data/pda/utility/scanmode/medical, + + new/datum/data/pda/app/crew_records/security) + + +/obj/item/weapon/cartridge/janitor + name = "\improper CustodiPRO cartridge" + desc = "The ultimate in clean-room design." + icon_state = "cart-j" + programs = list(new/datum/data/pda/app/janitor) + +/obj/item/weapon/cartridge/lawyer + name = "\improper P.R.O.V.E. cartridge" + icon_state = "cart-s" + programs = list(new/datum/data/pda/app/crew_records/security) + +/obj/item/weapon/cartridge/clown + name = "\improper Honkworks 5.0 cartridge" + icon_state = "cart-clown" + charges = 5 + programs = list(new/datum/data/pda/utility/honk) + messenger_plugins = list(new/datum/data/pda/messenger_plugin/virus/clown) + +/obj/item/weapon/cartridge/mime + name = "\improper Gestur-O 1000 cartridge" + icon_state = "cart-mi" + charges = 5 + messenger_plugins = list(new/datum/data/pda/messenger_plugin/virus/mime) + +/obj/item/weapon/cartridge/service + name = "\improper Serv-U Pro cartridge" + desc = "A data cartridge designed to serve YOU!" + +/obj/item/weapon/cartridge/signal + name = "generic signaler cartridge" + desc = "A data cartridge with an integrated radio signaler module." + programs = list(new/datum/data/pda/app/signaller) + +/obj/item/weapon/cartridge/signal/Initialize() + radio = new /obj/item/radio/integrated/signal(src) + ..() + +/obj/item/weapon/cartridge/signal/science + name = "\improper Signal Ace 2 cartridge" + desc = "Complete with integrated radio signaler!" + icon_state = "cart-tox" + programs = list( + new/datum/data/pda/utility/scanmode/gas, + + new/datum/data/pda/utility/scanmode/reagent, + + new/datum/data/pda/app/signaller) + +/obj/item/weapon/cartridge/quartermaster + name = "\improper Space Parts & Space Vendors cartridge" + desc = "Perfect for the Quartermaster on the go!" + icon_state = "cart-q" + programs = list( + new/datum/data/pda/app/supply) + +/obj/item/weapon/cartridge/miner + name = "\improper Drill-Jockey 4.5 cartridge" + desc = "It's covered in some sort of sand." + icon_state = "cart-q" + +/obj/item/weapon/cartridge/head + name = "\improper Easy-Record DELUXE cartridge" + icon_state = "cart-h" + programs = list(new/datum/data/pda/app/status_display) + +/obj/item/weapon/cartridge/hop + name = "\improper HumanResources9001 cartridge" + icon_state = "cart-h" + programs = list( + new/datum/data/pda/app/crew_records/security, + + new/datum/data/pda/app/janitor, + + new/datum/data/pda/app/supply, + + new/datum/data/pda/app/status_display) + +/obj/item/weapon/cartridge/hos + name = "\improper R.O.B.U.S.T. DELUXE cartridge" + icon_state = "cart-hos" + programs = list( + new/datum/data/pda/app/crew_records/security, + + new/datum/data/pda/app/status_display) + +/obj/item/weapon/cartridge/ce + name = "\improper Power-On DELUXE cartridge" + icon_state = "cart-ce" + programs = list( + new/datum/data/pda/app/power, + new/datum/data/pda/utility/scanmode/halogen, + + new/datum/data/pda/utility/scanmode/gas, + + new/datum/data/pda/app/status_display) + +/obj/item/weapon/cartridge/cmo + name = "\improper Med-U DELUXE cartridge" + icon_state = "cart-cmo" + programs = list( + new/datum/data/pda/app/crew_records/medical, + new/datum/data/pda/utility/scanmode/medical, + + new/datum/data/pda/utility/scanmode/reagent, + + new/datum/data/pda/app/status_display) + +/obj/item/weapon/cartridge/rd + name = "\improper Signal Ace DELUXE cartridge" + icon_state = "cart-rd" + programs = list( + new/datum/data/pda/utility/scanmode/gas, + + new/datum/data/pda/utility/scanmode/reagent, + + new/datum/data/pda/app/signaller, + + new/datum/data/pda/app/status_display) + +/obj/item/weapon/cartridge/rd/Initialize() + radio = new /obj/item/radio/integrated/signal(src) + . = ..() + +/obj/item/weapon/cartridge/captain + name = "\improper Value-PAK cartridge" + desc = "Now with 200% more value!" + icon_state = "cart-c" + programs = list( + new/datum/data/pda/app/power, + new/datum/data/pda/utility/scanmode/halogen, + + new/datum/data/pda/utility/scanmode/gas, + + new/datum/data/pda/app/crew_records/medical, + new/datum/data/pda/utility/scanmode/medical, + + new/datum/data/pda/utility/scanmode/reagent, + + new/datum/data/pda/app/crew_records/security, + + new/datum/data/pda/app/janitor, + + new/datum/data/pda/app/supply, + + new/datum/data/pda/app/status_display) + +/obj/item/weapon/cartridge/syndicate + name = "\improper Detomatix cartridge" + icon_state = "cart" + var/initial_remote_door_id = "smindicate" //Make sure this matches the syndicate shuttle's shield/door id!! //don't ask about the name, testing. + charges = 4 + programs = list(new/datum/data/pda/utility/toggle_door) + messenger_plugins = list(new/datum/data/pda/messenger_plugin/virus/detonate) + +/obj/item/weapon/cartridge/syndicate/New() + var/datum/data/pda/utility/toggle_door/D = programs[1] + if(istype(D)) + D.remote_door_id = initial_remote_door_id + +/obj/item/weapon/cartridge/proc/post_status(var/command, var/data1, var/data2) + + var/datum/radio_frequency/frequency = radio_controller.return_frequency(1435) + if(!frequency) return + + var/datum/signal/status_signal = new + status_signal.source = src + status_signal.transmission_method = TRANSMISSION_RADIO + status_signal.data["command"] = command + + switch(command) + if("message") + status_signal.data["msg1"] = data1 + status_signal.data["msg2"] = data2 + if(loc) + var/obj/item/PDA = loc + var/mob/user = PDA.fingerprintslast + log_admin("STATUS: [user] set status screen with [PDA]. Message: [data1] [data2]") + message_admins("STATUS: [user] set status screen with [PDA]. Message: [data1] [data2]") + + if("alert") + status_signal.data["picture_state"] = data1 + + frequency.post_signal(src, status_signal) + +/obj/item/weapon/cartridge/frame + name = "F.R.A.M.E. cartridge" + icon_state = "cart" + charges = 5 + var/telecrystals = 0 + messenger_plugins = list(new/datum/data/pda/messenger_plugin/virus/frame) diff --git a/code/modules/pda/cart_apps.dm b/code/modules/pda/cart_apps.dm new file mode 100644 index 00000000000..e3e0166f9b6 --- /dev/null +++ b/code/modules/pda/cart_apps.dm @@ -0,0 +1,311 @@ +/datum/data/pda/app/status_display + name = "Status Display" + icon = "list-alt" + template = "pda_status_display" + category = "Utilities" + + var/message1 // used for status_displays + var/message2 + +/datum/data/pda/app/status_display/update_ui(mob/user as mob, list/data) + data["records"] = list( + "message1" = message1 ? message1 : "(none)", + "message2" = message2 ? message2 : "(none)") + +/datum/data/pda/app/status_display/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state) + if(..()) + return TRUE + switch(action) + if("Status") + switch(params["statdisp"]) + if("message") + post_status("message", message1, message2) + if("alert") + post_status("alert", params["alert"]) + if("setmsg1") + message1 = clean_input("Line 1", "Enter Message Text", message1) + if("setmsg2") + message2 = clean_input("Line 2", "Enter Message Text", message2) + else + post_status(params["statdisp"]) + return TRUE + +/datum/data/pda/app/status_display/proc/post_status(var/command, var/data1, var/data2) + var/datum/radio_frequency/frequency = radio_controller.return_frequency(1435) + if(!frequency) + return + + var/datum/signal/status_signal = new + status_signal.source = src + status_signal.transmission_method = 1 + status_signal.data["command"] = command + + switch(command) + if("message") + status_signal.data["msg1"] = data1 + status_signal.data["msg2"] = data2 + var/mob/user = pda.fingerprintslast + if(istype(pda.loc, /mob/living)) + user = pda.loc + log_admin("STATUS: [user] set status screen with [pda]. Message: [data1] [data2]") + message_admins("STATUS: [user] set status screen with [pda]. Message: [data1] [data2]") + + if("alert") + status_signal.data["picture_state"] = data1 + + spawn(0) + frequency.post_signal(src, status_signal) + + +/datum/data/pda/app/signaller + name = "Signaler System" + icon = "rss" + template = "pda_signaller" + category = "Utilities" + +/datum/data/pda/app/signaller/update_ui(mob/user as mob, list/data) + if(pda.cartridge && istype(pda.cartridge.radio, /obj/item/radio/integrated/signal)) + var/obj/item/radio/integrated/signal/R = pda.cartridge.radio + data["frequency"] = R.frequency + data["minFrequency"] = RADIO_LOW_FREQ + data["maxFrequency"] = RADIO_HIGH_FREQ + data["code"] = R.code + +/datum/data/pda/app/signaller/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state) + if(..()) + return TRUE + if(pda.cartridge && istype(pda.cartridge.radio, /obj/item/radio/integrated/signal)) + var/obj/item/radio/integrated/signal/R = pda.cartridge.radio + + switch(action) + if("signal") + spawn(0) + R.send_signal("ACTIVATE") + if("freq") + var/frequency = unformat_frequency(params["freq"]) + frequency = sanitize_frequency(frequency, RADIO_LOW_FREQ, RADIO_HIGH_FREQ) + R.set_frequency(frequency) + . = TRUE + if("code") + R.code = clamp(round(text2num(params["code"])), 1, 100) + . = TRUE + if("reset") + if(params["reset"] == "freq") + R.set_frequency(initial(R.frequency)) + else + R.code = initial(R.code) + . = TRUE + +/datum/data/pda/app/power + name = "Power Monitor" + icon = "exclamation-triangle" + template = "pda_power" + category = "Engineering" + + var/datum/tgui_module/power_monitor/power_monitor + +/datum/data/pda/app/power/New() + power_monitor = new(src) + . = ..() + +/datum/data/pda/app/power/Destroy() + QDEL_NULL(power_monitor) + return ..() + +/datum/data/pda/app/power/update_ui(mob/user as mob, list/data) + data.Add(power_monitor.tgui_data(user)) + +/datum/data/pda/app/power/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state) + if(..()) + return TRUE + if(power_monitor.tgui_act(action, params, ui, state)) + return TRUE + switch(action) + if("Back") + power_monitor.active_sensor = null + return TRUE + +/datum/data/pda/app/crew_records + var/datum/data/record/general_records = null + +/datum/data/pda/app/crew_records/update_ui(mob/user as mob, list/data) + var/list/records[0] + + if(general_records && (general_records in data_core.general)) + data["records"] = records + records["general"] = general_records.fields + return records + else + for(var/A in sortRecord(data_core.general)) + var/datum/data/record/R = A + if(R) + records += list(list(Name = R.fields["name"], "ref" = "\ref[R]")) + data["recordsList"] = records + data["records"] = null + return null + +/datum/data/pda/app/crew_records/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state) + if(..()) + return TRUE + switch(action) + if("Records") + var/datum/data/record/R = locate(params["target"]) + if(R && (R in data_core.general)) + load_records(R) + return TRUE + if("Back") + general_records = null + has_back = 0 + return TRUE + +/datum/data/pda/app/crew_records/proc/load_records(datum/data/record/R) + general_records = R + has_back = 1 + +/datum/data/pda/app/crew_records/medical + name = "Medical Records" + icon = "heartbeat" + template = "pda_medical" + category = "Medical" + + var/datum/data/record/medical_records = null + +/datum/data/pda/app/crew_records/medical/update_ui(mob/user as mob, list/data) + var/list/records = ..() + if(!records) + return + + if(medical_records && (medical_records in data_core.medical)) + records["medical"] = medical_records.fields + + return records + +/datum/data/pda/app/crew_records/medical/load_records(datum/data/record/R) + ..(R) + for(var/A in data_core.medical) + var/datum/data/record/E = A + if(E && (E.fields["name"] == R.fields["name"] || E.fields["id"] == R.fields["id"])) + medical_records = E + break + +/datum/data/pda/app/crew_records/security + name = "Security Records" + icon = "tags" + template = "pda_security" + category = "Security" + + var/datum/data/record/security_records = null + +/datum/data/pda/app/crew_records/security/update_ui(mob/user as mob, list/data) + var/list/records = ..() + if(!records) + return + + if(security_records && (security_records in data_core.security)) + records["security"] = security_records.fields + + return records + +/datum/data/pda/app/crew_records/security/load_records(datum/data/record/R) + ..(R) + for(var/A in data_core.security) + var/datum/data/record/E = A + if(E && (E.fields["name"] == R.fields["name"] || E.fields["id"] == R.fields["id"])) + security_records = E + break + +/datum/data/pda/app/supply + name = "Supply Records" + icon = "file-word-o" + template = "pda_supply" + category = "Quartermaster" + +/datum/data/pda/app/supply/update_ui(mob/user as mob, list/data) + var/supplyData[0] + var/datum/shuttle/autodock/ferry/supply/shuttle = SSsupply.shuttle + if (shuttle) + supplyData["shuttle_moving"] = shuttle.has_arrive_time() + supplyData["shuttle_eta"] = shuttle.eta_minutes() + supplyData["shuttle_loc"] = shuttle.at_station() ? "Station" : "Dock" + var/supplyOrderCount = 0 + var/supplyOrderData[0] + for(var/S in SSsupply.shoppinglist) + var/datum/supply_order/SO = S + + supplyOrderCount++ + supplyOrderData[++supplyOrderData.len] = list("Number" = SO.ordernum, "Name" = html_encode(SO.object.name), "ApprovedBy" = SO.ordered_by, "Comment" = html_encode(SO.comment)) + + supplyData["approved"] = supplyOrderData + supplyData["approved_count"] = supplyOrderCount + + var/requestCount = 0 + var/requestData[0] + for(var/S in SSsupply.order_history) + var/datum/supply_order/SO = S + if(SO.status != SUP_ORDER_REQUESTED) + continue + + requestCount++ + requestData[++requestData.len] = list("Number" = SO.ordernum, "Name" = html_encode(SO.object.name), "OrderedBy" = SO.ordered_by, "Comment" = html_encode(SO.comment)) + + supplyData["requests"] = requestData + supplyData["requests_count"] = requestCount + + data["supply"] = supplyData + +/datum/data/pda/app/janitor + name = "Custodial Locator" + icon = "trash-alt-o" + template = "pda_janitor" + category = "Utilities" + +/datum/data/pda/app/janitor/update_ui(mob/user as mob, list/data) + var/JaniData[0] + var/turf/cl = get_turf(pda) + + if(cl) + JaniData["user_loc"] = list("x" = cl.x, "y" = cl.y) + else + JaniData["user_loc"] = list("x" = 0, "y" = 0) + + var/MopData[0] + for(var/obj/item/weapon/mop/M in all_mops)//GLOB.janitorial_equipment) + var/turf/ml = get_turf(M) + if(ml) + if(ml.z != cl.z) + continue + var/direction = get_dir(pda, M) + MopData[++MopData.len] = list ("x" = ml.x, "y" = ml.y, "dir" = uppertext(dir2text(direction)), "status" = M.reagents.total_volume ? "Wet" : "Dry") + + var/BucketData[0] + for(var/obj/structure/mopbucket/B in all_mopbuckets)//GLOB.janitorial_equipment) + var/turf/bl = get_turf(B) + if(bl) + if(bl.z != cl.z) + continue + var/direction = get_dir(pda,B) + BucketData[++BucketData.len] = list ("x" = bl.x, "y" = bl.y, "dir" = uppertext(dir2text(direction)), "volume" = B.reagents.total_volume, "max_volume" = B.reagents.maximum_volume) + + var/CbotData[0] + for(var/mob/living/bot/cleanbot/B in mob_list) + var/turf/bl = get_turf(B) + if(bl) + if(bl.z != cl.z) + continue + var/direction = get_dir(pda,B) + CbotData[++CbotData.len] = list("x" = bl.x, "y" = bl.y, "dir" = uppertext(dir2text(direction)), "status" = B.on ? "Online" : "Offline") + + var/CartData[0] + for(var/obj/structure/janitorialcart/B in all_janitorial_carts)//GLOB.janitorial_equipment) + var/turf/bl = get_turf(B) + if(bl) + if(bl.z != cl.z) + continue + var/direction = get_dir(pda,B) + CartData[++CartData.len] = list("x" = bl.x, "y" = bl.y, "dir" = uppertext(dir2text(direction)), "volume" = B.reagents.total_volume, "max_volume" = B.reagents.maximum_volume) + + JaniData["mops"] = MopData.len ? MopData : null + JaniData["buckets"] = BucketData.len ? BucketData : null + JaniData["cleanbots"] = CbotData.len ? CbotData : null + JaniData["carts"] = CartData.len ? CartData : null + data["janitor"] = JaniData diff --git a/code/modules/pda/cart_vr.dm b/code/modules/pda/cart_vr.dm new file mode 100644 index 00000000000..1f1a1cbe520 --- /dev/null +++ b/code/modules/pda/cart_vr.dm @@ -0,0 +1,20 @@ +var/list/exploration_cartridges = list( + /obj/item/weapon/cartridge/explorer, + /obj/item/weapon/cartridge/sar + ) + +/obj/item/weapon/cartridge/explorer + name = "\improper Explorator cartridge" + icon_state = "cart-e" + programs = list( + new/datum/data/pda/utility/scanmode/reagent, + new/datum/data/pda/utility/scanmode/gas) + +/obj/item/weapon/cartridge/sar + name = "\improper Med-Exp cartridge" + icon_state = "cart-m" + programs = list( + new/datum/data/pda/app/crew_records/medical, + new/datum/data/pda/utility/scanmode/medical, + new/datum/data/pda/utility/scanmode/reagent, + new/datum/data/pda/utility/scanmode/gas) diff --git a/code/modules/pda/core_apps.dm b/code/modules/pda/core_apps.dm new file mode 100644 index 00000000000..4b561c98444 --- /dev/null +++ b/code/modules/pda/core_apps.dm @@ -0,0 +1,206 @@ +/datum/data/pda/app/main_menu + icon = "home" + template = "pda_main_menu" + hidden = 1 + +/datum/data/pda/app/main_menu/update_ui(mob/user as mob, list/data) + title = pda.name + + data["app"]["is_home"] = 1 + + data["apps"] = pda.shortcut_cache + data["categories"] = pda.shortcut_cat_order + data["pai"] = !isnull(pda.pai) // pAI inserted? + + var/list/notifying[0] + for(var/P in pda.notifying_programs) + notifying["\ref[P]"] = 1 + data["notifying"] = notifying + +/datum/data/pda/app/main_menu/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state) + if(..()) + return TRUE + switch(action) + if("UpdateInfo") + pda.ownjob = pda.id.assignment + pda.ownrank = pda.id.rank + pda.name = "PDA-[pda.owner] ([pda.ownjob])" + return TRUE + if("pai") + if(pda.pai) + if(pda.pai.loc != pda) + pda.pai = null + else + switch(text2num(params["option"])) + if(1) // Configure pAI device + pda.pai.attack_self(usr) + if(2) // Eject pAI device + var/turf/T = get_turf_or_move(pda.loc) + if(T) + pda.pai.loc = T + pda.pai = null + return TRUE + +/datum/data/pda/app/notekeeper + name = "Notekeeper" + icon = "sticky-note-o" + template = "pda_notekeeper" + + var/note = null + var/notehtml = "" + +/datum/data/pda/app/notekeeper/start() + . = ..() + if(!note) + note = "Congratulations, your station has chosen the [pda.model_name]!" + +/datum/data/pda/app/notekeeper/update_ui(mob/user as mob, list/data) + data["note"] = note // current pda notes + +/datum/data/pda/app/notekeeper/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state) + if(..()) + return TRUE + switch(action) + if("Edit") + var/n = input("Please enter message", name, notehtml) as message + if(pda.loc == usr) + note = adminscrub(n) + notehtml = html_decode(note) + note = replacetext(note, "\n", "
") + else + pda.close(usr) + return TRUE + +/datum/data/pda/app/manifest + name = "Crew Manifest" + icon = "user" + template = "pda_manifest" + +/datum/data/pda/app/manifest/update_ui(mob/user as mob, list/data) + if(data_core) + data_core.get_manifest_list() + data["manifest"] = PDA_Manifest + +/datum/data/pda/app/manifest/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state) + if(..()) + return TRUE + +/datum/data/pda/app/atmos_scanner + name = "Atmospheric Scan" + icon = "fire" + template = "pda_atmos_scan" + category = "Utilities" + +/datum/data/pda/app/atmos_scanner/update_ui(mob/user as mob, list/data) + var/list/results = list() + var/turf/T = get_turf(user) + if(!isnull(T)) + var/datum/gas_mixture/environment = T.return_air() + var/pressure = environment.return_pressure() + var/total_moles = environment.total_moles + if (total_moles) + var/o2_level = environment.gas["oxygen"]/total_moles + var/n2_level = environment.gas["nitrogen"]/total_moles + var/co2_level = environment.gas["carbon_dioxide"]/total_moles + var/phoron_level = environment.gas["phoron"]/total_moles + var/unknown_level = 1-(o2_level+n2_level+co2_level+phoron_level) + + // entry is what the element is describing + // Type identifies which unit or other special characters to use + // Val is the information reported + // Bad_high/_low are the values outside of which the entry reports as dangerous + // Poor_high/_low are the values outside of which the entry reports as unideal + // Values were extracted from the template itself + results = list( + list("entry" = "Pressure", "units" = "kPa", "val" = "[round(pressure,0.1)]", "bad_high" = 120, "poor_high" = 110, "poor_low" = 95, "bad_low" = 80), + list("entry" = "Temperature", "units" = "°C", "val" = "[round(environment.temperature-T0C,0.1)]", "bad_high" = 35, "poor_high" = 25, "poor_low" = 15, "bad_low" = 5), + list("entry" = "Oxygen", "units" = "kPa", "val" = "[round(o2_level*100,0.1)]", "bad_high" = 140, "poor_high" = 135, "poor_low" = 19, "bad_low" = 17), + list("entry" = "Nitrogen", "units" = "kPa", "val" = "[round(n2_level*100,0.1)]", "bad_high" = 105, "poor_high" = 85, "poor_low" = 50, "bad_low" = 40), + list("entry" = "Carbon Dioxide", "units" = "kPa", "val" = "[round(co2_level*100,0.1)]", "bad_high" = 10, "poor_high" = 5, "poor_low" = 0, "bad_low" = 0), + list("entry" = "Phoron", "units" = "kPa", "val" = "[round(phoron_level*100,0.01)]", "bad_high" = 0.5, "poor_high" = 0, "poor_low" = 0, "bad_low" = 0), + list("entry" = "Other", "units" = "kPa", "val" = "[round(unknown_level, 0.01)]", "bad_high" = 1, "poor_high" = 0.5, "poor_low" = 0, "bad_low" = 0) + ) + + if(isnull(results)) + results = list(list("entry" = "pressure", "units" = "kPa", "val" = "0", "bad_high" = 120, "poor_high" = 110, "poor_low" = 95, "bad_low" = 80)) + + data["aircontents"] = results + +/datum/data/pda/app/news + name = "News" + icon = "newspaper" + template = "pda_news" + + var/newsfeed_channel + +/datum/data/pda/app/news/update_ui(mob/user as mob, list/data) + data["feeds"] = compile_news() + data["latest_news"] = get_recent_news() + if(newsfeed_channel) + data["target_feed"] = data["feeds"][newsfeed_channel] + else + data["target_feed"] = null + +/datum/data/pda/app/news/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state) + if(..()) + return TRUE + switch(action) + if("newsfeed") + newsfeed_channel = text2num(params["newsfeed"]) + +/datum/data/pda/app/news/proc/compile_news() + var/list/feeds = list() + for(var/datum/feed_channel/channel in news_network.network_channels) + var/list/messages = list() + if(!channel.censored) + var/index = 0 + for(var/datum/feed_message/FM in channel.messages) + index++ + var/list/msgdata = list( + "author" = FM.author, + "body" = FM.body, + "img" = null, + "message_type" = FM.message_type, + "time_stamp" = FM.time_stamp, + "caption" = FM.caption, + "index" = index + ) + if(FM.img) + msgdata["img"] = icon2base64(FM.img) + messages[++messages.len] = msgdata + + feeds[++feeds.len] = list( + "name" = channel.channel_name, + "censored" = channel.censored, + "author" = channel.author, + "messages" = messages, + "index" = feeds.len + 1 + ) + return feeds + +/datum/data/pda/app/news/proc/get_recent_news() + var/list/news = list() + + // Compile all the newscasts + for(var/datum/feed_channel/channel in news_network.network_channels) + if(!channel.censored) + for(var/datum/feed_message/FM in channel.messages) + var/body = replacetext(FM.body, "\n", "
") + news[++news.len] = list( + "channel" = channel.channel_name, + "author" = FM.author, + "body" = body, + "message_type" = FM.message_type, + "time_stamp" = FM.time_stamp, + "has_image" = (FM.img != null), + "caption" = FM.caption, + "time" = FM.post_time + ) + + // Cut out all but the youngest three + if(news.len > 3) + sortByKey(news, "time") + news.Cut(1, news.len - 2) // Last three have largest timestamps, youngest posts + news.Swap(1, 3) // List is sorted in ascending order of timestamp, we want descending + + return news \ No newline at end of file diff --git a/code/modules/pda/messenger.dm b/code/modules/pda/messenger.dm new file mode 100644 index 00000000000..38360e127a7 --- /dev/null +++ b/code/modules/pda/messenger.dm @@ -0,0 +1,247 @@ +/datum/data/pda/app/messenger + name = "Messenger" + icon = "comments-o" + notify_icon = "comments" + title = "SpaceMessenger V4.1.0" + template = "pda_messenger" + + var/toff = 0 //If 1, messenger disabled + var/list/tnote[0] //Current Texts + var/last_text //No text spamming + + var/m_hidden = 0 // Is the PDA hidden from the PDA list? + var/active_conversation = null // New variable that allows us to only view a single conversation. + var/list/conversations = list() // For keeping up with who we have PDA messsages from. + +/datum/data/pda/app/messenger/start() + . = ..() + unnotify() + +/datum/data/pda/app/messenger/update_ui(mob/user as mob, list/data) + data["silent"] = notify_silent // does the pda make noise when it receives a message? + data["toff"] = toff // is the messenger function turned off? + data["active_conversation"] = active_conversation // Which conversation are we following right now? + + has_back = active_conversation + if(active_conversation) + data["messages"] = tnote + for(var/c in tnote) + if(c["target"] == active_conversation) + data["convo_name"] = sanitize(c["owner"]) + data["convo_job"] = sanitize(c["job"]) + break + else + var/convopdas[0] + var/pdas[0] + for(var/A in PDAs) + var/obj/item/device/pda/P = A + var/datum/data/pda/app/messenger/PM = P.find_program(/datum/data/pda/app/messenger) + + if(!P.owner || PM.toff || P == pda || PM.m_hidden) + continue + if(conversations.Find("\ref[P]")) + convopdas.Add(list(list("Name" = "[P]", "Reference" = "\ref[P]", "Detonate" = "[P.detonate]", "inconvo" = "1"))) + else + pdas.Add(list(list("Name" = "[P]", "Reference" = "\ref[P]", "Detonate" = "[P.detonate]", "inconvo" = "0"))) + + data["convopdas"] = convopdas + data["pdas"] = pdas + + var/list/plugins = list() + if(pda.cartridge) + for(var/A in pda.cartridge.messenger_plugins) + var/datum/data/pda/messenger_plugin/P = A + plugins += list(list(name = P.name, icon = P.icon, ref = "\ref[P]")) + data["plugins"] = plugins + + if(pda.cartridge) + data["charges"] = pda.cartridge.charges ? pda.cartridge.charges : 0 + +/datum/data/pda/app/messenger/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state) + if(..()) + return TRUE + + unnotify() + + . = TRUE + switch(action) + if("Toggle Messenger") + toff = !toff + if("Toggle Ringer")//If viewing texts then erase them, if not then toggle silent status + notify_silent = !notify_silent + if("Clear")//Clears messages + if(params["option"] == "All") + tnote.Cut() + conversations.Cut() + if(params["option"] == "Convo") + var/new_tnote[0] + for(var/i in tnote) + if(i["target"] != active_conversation) + new_tnote[++new_tnote.len] = i + tnote = new_tnote + conversations.Remove(active_conversation) + + active_conversation = null + if("Message") + var/obj/item/device/pda/P = locate(params["target"]) + create_message(usr, P) + if(params["target"] in conversations) // Need to make sure the message went through, if not welp. + active_conversation = params["target"] + if("Select Conversation") + var/P = params["target"] + for(var/n in conversations) + if(P == n) + active_conversation = P + if("Messenger Plugin") + if(!params["target"] || !params["plugin"]) + return + + var/obj/item/device/pda/P = locate(params["target"]) + if(!P) + to_chat(usr, "PDA not found.") + + var/datum/data/pda/messenger_plugin/plugin = locate(params["plugin"]) + if(plugin && (plugin in pda.cartridge.messenger_plugins)) + plugin.messenger = src + plugin.user_act(usr, P) + if("Back") + active_conversation = null + +// Specifically here for the chat message. +/datum/data/pda/app/messenger/Topic(href, href_list) + if(!pda.can_use()) + return + unnotify() + + switch(href_list["choice"]) + if("Message") + var/obj/item/device/pda/P = locate(href_list["target"]) + create_message(usr, P) + if(href_list["target"] in conversations) // Need to make sure the message went through, if not welp. + active_conversation = href_list["target"] + + +/datum/data/pda/app/messenger/proc/create_message(var/mob/living/U, var/obj/item/device/pda/P) + var/t = input(U, "Please enter message", name, null) as text|null + if(!t) + return + t = sanitize(copytext(t, 1, MAX_MESSAGE_LEN)) + t = readd_quotes(t) + if(!t || !istype(P)) + return + if(!in_range(pda, U) && pda.loc != U) + return + + var/datum/data/pda/app/messenger/PM = P.find_program(/datum/data/pda/app/messenger) + + if(!PM || PM.toff || toff) + return + + if(last_text && world.time < last_text + 5) + return + + if(!pda.can_use()) + return + + last_text = world.time + // check if telecomms I/O route 1459 is stable + //var/telecomms_intact = telecomms_process(P.owner, owner, t) + var/obj/machinery/message_server/useMS = null + if(message_servers) + for(var/A in message_servers) + var/obj/machinery/message_server/MS = A + //PDAs are now dependent on the Message Server. + if(MS.active) + useMS = MS + break + + var/datum/signal/signal = pda.telecomms_process() + + var/useTC = 0 + if(signal) + if(signal.data["done"]) + useTC = 1 + var/turf/pos = get_turf(P) + // TODO: Make the radio system cooperate with the space manager + if(pos.z in signal.data["level"]) + useTC = 2 + //Let's make this barely readable + if(signal.data["compression"] > 0) + t = Gibberish(t, signal.data["compression"] + 50) + + if(useMS && useTC) // only send the message if it's stable + if(useTC != 2) // Does our recipient have a broadcaster on their level? + to_chat(U, "ERROR: Cannot reach recipient.") + return + useMS.send_pda_message("[P.owner]","[pda.owner]","[t]") + pda.investigate_log("PDA Message - [U.key] - [pda.owner] -> [P.owner]: [t]", "pda") + + receive_message(list("sent" = 1, "owner" = "[P.owner]", "job" = "[P.ownjob]", "message" = "[t]", "target" = "\ref[P]"), "\ref[P]") + PM.receive_message(list("sent" = 0, "owner" = "[pda.owner]", "job" = "[pda.ownjob]", "message" = "[t]", "target" = "\ref[pda]"), "\ref[pda]") + + SStgui.update_user_uis(U, P) // Update the sending user's PDA UI so that they can see the new message + log_pda("(PDA: [src.name]) sent \"[t]\" to [P.name]", usr) + else + to_chat(U, "ERROR: Messaging server is not responding.") + +/datum/data/pda/app/messenger/proc/available_pdas() + var/list/names = list() + var/list/plist = list() + var/list/namecounts = list() + + if(toff) + to_chat(usr, "Turn on your receiver in order to send messages.") + return + + for(var/A in PDAs) + var/obj/item/device/pda/P = A + var/datum/data/pda/app/messenger/PM = P.find_program(/datum/data/pda/app/messenger) + + if(!P.owner || !PM || PM.hidden || P == pda || PM.toff) + continue + + var/name = P.owner + if(name in names) + namecounts[name]++ + name = text("[name] ([namecounts[name]])") + else + names.Add(name) + namecounts[name] = 1 + + plist[text("[name]")] = P + return plist + +/datum/data/pda/app/messenger/proc/can_receive() + return pda.owner && !toff && !hidden + +/datum/data/pda/app/messenger/proc/receive_message(list/data, ref) + tnote.Add(list(data)) + if(!conversations.Find(ref)) + conversations.Add(ref) + if(!data["sent"]) + var/owner = data["owner"] + var/job = data["job"] + var/message = data["message"] + notify("Message from [owner] ([job]), \"[message]\" (Reply)") + +/datum/data/pda/app/messenger/multicast +/datum/data/pda/app/messenger/multicast/receive_message(list/data, ref) + . = ..() + + var/obj/item/device/pda/multicaster/M = pda + if(!istype(M)) + return + + var/list/modified_message = data.Copy() + modified_message["owner"] = modified_message["owner"] + " \[Relayed]" + modified_message["target"] = "\ref[M]" + + var/list/targets = list() + for(var/obj/item/device/pda/pda in PDAs) + if(pda.cartridge && pda.owner && is_type_in_list(pda.cartridge, M.cartridges_to_send_to)) + targets |= pda + if(targets.len) + for(var/obj/item/device/pda/target in targets) + var/datum/data/pda/app/messenger/P = target.find_program(/datum/data/pda/app/messenger) + if(P) + P.receive_message(modified_message, "\ref[M]") \ No newline at end of file diff --git a/code/modules/pda/messenger_plugins.dm b/code/modules/pda/messenger_plugins.dm new file mode 100644 index 00000000000..90cb9460a48 --- /dev/null +++ b/code/modules/pda/messenger_plugins.dm @@ -0,0 +1,91 @@ +/datum/data/pda/messenger_plugin + var/datum/data/pda/app/messenger/messenger + +/datum/data/pda/messenger_plugin/proc/user_act(mob/user as mob, obj/item/device/pda/P) + + +/datum/data/pda/messenger_plugin/virus + name = "*Send Virus*" + +/datum/data/pda/messenger_plugin/virus/user_act(mob/user as mob, obj/item/device/pda/P) + var/datum/data/pda/app/messenger/M = P.find_program(/datum/data/pda/app/messenger) + + if(M && !M.toff && pda.cartridge.charges > 0) + pda.cartridge.charges-- + return 1 + return 0 + + +/datum/data/pda/messenger_plugin/virus/clown + icon = "star" + +/datum/data/pda/messenger_plugin/virus/clown/user_act(mob/user as mob, obj/item/device/pda/P) + . = ..(user, P) + if(.) + user.show_message("Virus sent!", 1) + P.honkamt = (rand(15,20)) + P.ttone = "honk" + + +/datum/data/pda/messenger_plugin/virus/mime + icon = "arrow-circle-down" + +/datum/data/pda/messenger_plugin/virus/mime/user_act(mob/user as mob, obj/item/device/pda/P) + . = ..(user, P) + if(.) + user.show_message("Virus sent!", 1) + var/datum/data/pda/app/M = P.find_program(/datum/data/pda/app/messenger) + if(M) + M.notify_silent = 1 + P.ttone = "silence" + + +/datum/data/pda/messenger_plugin/virus/detonate + name = "*Detonate*" + icon = "exclamation-circle" + +/datum/data/pda/messenger_plugin/virus/detonate/user_act(mob/user as mob, obj/item/device/pda/P) + . = ..(user, P) + if(.) + var/difficulty = 0 + + if(pda.cartridge) + difficulty += pda.cartridge.programs.len / 2 + else + difficulty += 2 + + if(!P.detonate || P.hidden_uplink) + user.show_message("The target PDA does not seem to respond to the detonation command.", 1) + pda.cartridge.charges++ + else if(prob(difficulty * 12)) + user.show_message("An error flashes on your [pda].", 1) + else if(prob(difficulty * 3)) + user.show_message("Energy feeds back into your [pda]!", 1) + pda.close(user) + pda.explode() + log_admin("[key_name(user)] just attempted to blow up [P] with the Detomatix cartridge but failed, blowing themselves up") + message_admins("[key_name_admin(user)] just attempted to blow up [P] with the Detomatix cartridge but failed, blowing themselves up", 1) + else + user.show_message("Success!", 1) + log_admin("[key_name(user)] just attempted to blow up [P] with the Detomatix cartridge and succeded") + message_admins("[key_name_admin(user)] just attempted to blow up [P] with the Detomatix cartridge and succeded", 1) + P.explode() + +/datum/data/pda/messenger_plugin/virus/frame + icon = "exclamation-circle" + +/datum/data/pda/messenger_plugin/virus/frame/user_act(mob/user, obj/item/device/pda/P) + . = ..(user, P) + if(.) + var/lock_code = "[rand(100,999)] [pick("Alpha","Bravo","Charlie","Delta","Echo","Foxtrot","Golf","Hotel","India","Juliet","Kilo","Lima","Mike","November","Oscar","Papa","Quebec","Romeo","Sierra","Tango","Uniform","Victor","Whiskey","X-ray","Yankee","Zulu")]" + user.show_message("Virus Sent! The unlock code to the target is: [lock_code]") + if(!P.hidden_uplink) + var/obj/item/device/uplink/hidden/uplink = new(P) + P.hidden_uplink = uplink + P.lock_code = lock_code + // else + // P.hidden_uplink.hidden_crystals += P.hidden_uplink.uses //Temporarially hide the PDA's crystals, so you can't steal telecrystals. + var/obj/item/weapon/cartridge/frame/parent_cart = pda.cartridge + P.hidden_uplink.uses = parent_cart.telecrystals + parent_cart.telecrystals = 0 + P.hidden_uplink.active = TRUE diff --git a/code/modules/pda/pda.dm b/code/modules/pda/pda.dm new file mode 100644 index 00000000000..3c9b03ce1ed --- /dev/null +++ b/code/modules/pda/pda.dm @@ -0,0 +1,502 @@ + +//The advanced pea-green monochrome lcd of tomorrow. + +var/global/list/obj/item/device/pda/PDAs = list() + +/obj/item/device/pda + name = "\improper PDA" + desc = "A portable microcomputer by Thinktronic Systems, LTD. Functionality determined by a preprogrammed ROM cartridge." + icon = 'icons/obj/pda.dmi' + icon_state = "pda" + item_state = "electronic" + w_class = ITEMSIZE_SMALL + slot_flags = SLOT_ID | SLOT_BELT + sprite_sheets = list(SPECIES_TESHARI = 'icons/mob/species/seromi/id.dmi') + + //Main variables + var/pdachoice = 1 + var/owner = null + var/default_cartridge = 0 // Access level defined by cartridge + var/obj/item/weapon/cartridge/cartridge = null //current cartridge + + //Secondary variables + var/model_name = "Thinktronic 5230 Personal Data Assistant" + var/datum/data/pda/utility/scanmode/scanmode = null + + var/lock_code = "" // Lockcode to unlock uplink + var/honkamt = 0 //How many honks left when infected with honk.exe + var/mimeamt = 0 //How many silence left when infected with mime.exe + var/detonate = 1 // Can the PDA be blown up? + var/ttone = "beep" //The ringtone! + var/list/ttone_sound = list("beep" = 'sound/machines/twobeep.ogg', + "boom" = 'sound/effects/explosionfar.ogg', + "slip" = 'sound/misc/slip.ogg', + "honk" = 'sound/items/bikehorn.ogg', + "SKREE" = 'sound/voice/shriek1.ogg', + // "holy" = 'sound/items/PDA/ambicha4-short.ogg', + "xeno" = 'sound/voice/hiss1.ogg') + var/hidden = 0 // Is the PDA hidden from the PDA list? + var/touch_silent = 0 //If 1, no beeps on interacting. + + var/obj/item/weapon/card/id/id = null //Making it possible to slot an ID card into the PDA so it can function as both. + var/ownjob = null //related to above - this is assignment (potentially alt title) + var/ownrank = null // this one is rank, never alt title + + var/obj/item/device/paicard/pai = null // A slot for a personal AI device + + var/spam_proof = FALSE // If true, it can't be spammed by random events. + + var/datum/data/pda/app/current_app = null + var/datum/data/pda/app/lastapp = null + var/list/programs = list( + new/datum/data/pda/app/main_menu, + new/datum/data/pda/app/notekeeper, + new/datum/data/pda/app/news, + new/datum/data/pda/app/messenger, + new/datum/data/pda/app/manifest, + new/datum/data/pda/app/atmos_scanner, + new/datum/data/pda/utility/scanmode/notes, + new/datum/data/pda/utility/flashlight) + var/list/shortcut_cache = list() + var/list/shortcut_cat_order = list() + var/list/notifying_programs = list() + var/retro_mode = 0 + +/obj/item/device/pda/examine(mob/user) + . = ..() + if(Adjacent(user)) + . += "The time [stationtime2text()] is displayed in the corner of the screen." + +/obj/item/device/pda/CtrlClick() + if(issilicon(usr)) + return + + if(can_use(usr)) + remove_pen() + return + ..() + +/obj/item/device/pda/AltClick() + if(issilicon(usr)) + return + + if ( can_use(usr) ) + if(id) + remove_id() + else + to_chat(usr, "This PDA does not have an ID in it.") + +/obj/item/device/pda/proc/play_ringtone() + var/S + + if(ttone in ttone_sound) + S = ttone_sound[ttone] + else + S = 'sound/machines/twobeep.ogg' + playsound(loc, S, 50, 1) + for(var/mob/O in hearers(3, loc)) + O.show_message(text("[bicon(src)] *[ttone]*")) + +/obj/item/device/pda/proc/set_ringtone() + var/t = input("Please enter new ringtone", name, ttone) as text + if(in_range(src, usr) && loc == usr) + if(t) + if(hidden_uplink && hidden_uplink.check_trigger(usr, lowertext(t), lowertext(lock_code))) + to_chat(usr, "The PDA softly beeps.") + close(usr) + else + t = sanitize(copytext(t, 1, 20)) + ttone = t + return 1 + else + close(usr) + return 0 + +/obj/item/device/pda/New(var/mob/living/carbon/human/H) + ..() + PDAs += src + PDAs = sortAtom(PDAs) + update_programs() + if(default_cartridge) + cartridge = new default_cartridge(src) + cartridge.update_programs(src) + new /obj/item/weapon/pen(src) + pdachoice = isnull(H) ? 1 : (ishuman(H) ? H.pdachoice : 1) + switch(pdachoice) + if(1) icon = 'icons/obj/pda.dmi' + if(2) icon = 'icons/obj/pda_slim.dmi' + if(3) icon = 'icons/obj/pda_old.dmi' + if(4) icon = 'icons/obj/pda_rugged.dmi' + if(5) icon = 'icons/obj/pda_holo.dmi' + if(6) + icon = 'icons/obj/pda_wrist.dmi' + item_state = icon_state + item_icons = list( + slot_belt_str = 'icons/mob/pda_wrist.dmi', + slot_wear_id_str = 'icons/mob/pda_wrist.dmi', + slot_gloves_str = 'icons/mob/pda_wrist.dmi' + ) + desc = "A portable microcomputer by Thinktronic Systems, LTD. This model is a wrist-bound version." + slot_flags = SLOT_ID | SLOT_BELT | SLOT_GLOVES + sprite_sheets = list( + SPECIES_TESHARI = 'icons/mob/species/seromi/pda_wrist.dmi', + SPECIES_VR_TESHARI = 'icons/mob/species/seromi/pda_wrist.dmi', + ) + else + icon = 'icons/obj/pda_old.dmi' + log_debug("Invalid switch for PDA, defaulting to old PDA icons. [pdachoice] chosen.") + start_program(find_program(/datum/data/pda/app/main_menu)) + +/obj/item/device/pda/proc/can_use() + if(!ismob(loc)) + return FALSE + + var/mob/M = loc + if(M.incapacitated(INCAPACITATION_ALL)) + return FALSE + if(src in M.contents) + return TRUE + return FALSE + +/obj/item/device/pda/GetAccess() + if(id) + return id.GetAccess() + else + return ..() + +/obj/item/device/pda/GetID() + return id + +/obj/item/device/pda/MouseDrop(obj/over_object as obj, src_location, over_location) + var/mob/M = usr + if((!istype(over_object, /obj/screen)) && can_use()) + return attack_self(M) + return + +/obj/item/device/pda/proc/close(mob/user) + SStgui.close_uis(src) + +/obj/item/device/pda/attack_self(mob/user as mob) + user.set_machine(src) + + if(active_uplink_check(user)) + return + + tgui_interact(user) + return + +/obj/item/device/pda/proc/start_program(datum/data/pda/P) + if(P && ((P in programs) || (cartridge && (P in cartridge.programs)))) + return P.start() + return 0 + +/obj/item/device/pda/proc/find_program(type) + var/datum/data/pda/A = locate(type) in programs + if(A) + return A + if(cartridge) + A = locate(type) in cartridge.programs + if(A) + return A + return null + +// force the cache to rebuild on update_ui +/obj/item/device/pda/proc/update_shortcuts() + shortcut_cache.Cut() + +/obj/item/device/pda/proc/update_programs() + for(var/A in programs) + var/datum/data/pda/P = A + P.pda = src + +/obj/item/device/pda/proc/detonate_act(var/obj/item/device/pda/P) + //TODO: sometimes these attacks show up on the message server + var/i = rand(1,100) + var/j = rand(0,1) //Possibility of losing the PDA after the detonation + var/message = "" + var/mob/living/M = null + if(ismob(P.loc)) + M = P.loc + + //switch(i) //Yes, the overlapping cases are intended. + if(i<=10) //The traditional explosion + P.explode() + j=1 + message += "Your [P] suddenly explodes!" + if(i>=10 && i<= 20) //The PDA burns a hole in the holder. + j=1 + if(M && isliving(M)) + M.apply_damage( rand(30,60) , BURN) + message += "You feel a searing heat! Your [P] is burning!" + if(i>=20 && i<=25) //EMP + empulse(P.loc, 1, 2, 4, 6, 1) + message += "Your [P] emits a wave of electromagnetic energy!" + if(i>=25 && i<=40) //Smoke + var/datum/effect/effect/system/smoke_spread/chem/S = new /datum/effect/effect/system/smoke_spread/chem + S.attach(P.loc) + S.set_up(P, 10, 0, P.loc) + playsound(P, 'sound/effects/smoke.ogg', 50, 1, -3) + S.start() + message += "Large clouds of smoke billow forth from your [P]!" + if(i>=40 && i<=45) //Bad smoke + var/datum/effect/effect/system/smoke_spread/bad/B = new /datum/effect/effect/system/smoke_spread/bad + B.attach(P.loc) + B.set_up(P, 10, 0, P.loc) + playsound(P, 'sound/effects/smoke.ogg', 50, 1, -3) + B.start() + message += "Large clouds of noxious smoke billow forth from your [P]!" + if(i>=65 && i<=75) //Weaken + if(M && isliving(M)) + M.apply_effects(0,1) + message += "Your [P] flashes with a blinding white light! You feel weaker." + if(i>=75 && i<=85) //Stun and stutter + if(M && isliving(M)) + M.apply_effects(1,0,0,0,1) + message += "Your [P] flashes with a blinding white light! You feel weaker." + if(i>=85) //Sparks + var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread + s.set_up(2, 1, P.loc) + s.start() + message += "Your [P] begins to spark violently!" + if(i>45 && i<65 && prob(50)) //Nothing happens + message += "Your [P] bleeps loudly." + j = prob(10) + + if(j && detonate) //This kills the PDA + qdel(P) + if(message) + message += "It melts in a puddle of plastic." + else + message += "Your [P] shatters in a thousand pieces!" + + if(M && isliving(M)) + message = "[message]" + M.show_message(message, 1) + +/obj/item/device/pda/proc/remove_id() + if (id) + if (ismob(loc)) + var/mob/M = loc + M.put_in_hands(id) + to_chat(usr, "You remove the ID from the [name].") + playsound(src, 'sound/machines/id_swipe.ogg', 100, 1) + else + id.loc = get_turf(src) + id = null + +/obj/item/device/pda/proc/remove_pen() + var/obj/item/weapon/pen/O = locate() in src + if(O) + if(istype(loc, /mob)) + var/mob/M = loc + if(M.get_active_hand() == null) + M.put_in_hands(O) + to_chat(usr, "You remove \the [O] from \the [src].") + return + O.loc = get_turf(src) + else + to_chat(usr, "This PDA does not have a pen in it.") + +/obj/item/device/pda/verb/verb_reset_pda() + set category = "Object" + set name = "Reset PDA" + set src in usr + + if(issilicon(usr)) + return + + if(can_use(usr)) + start_program(find_program(/datum/data/pda/app/main_menu)) + notifying_programs.Cut() + overlays -= image('icons/obj/pda.dmi', "pda-r") + to_chat(usr, "You press the reset button on \the [src].") + else + to_chat(usr, "You cannot do this while restrained.") + +/obj/item/device/pda/verb/verb_remove_id() + set category = "Object" + set name = "Remove id" + set src in usr + + if(issilicon(usr)) + return + + if ( can_use(usr) ) + if(id) + remove_id() + else + to_chat(usr, "This PDA does not have an ID in it.") + else + to_chat(usr, "You cannot do this while restrained.") + + +/obj/item/device/pda/verb/verb_remove_pen() + set category = "Object" + set name = "Remove pen" + set src in usr + + if(issilicon(usr)) + return + + if ( can_use(usr) ) + remove_pen() + else + to_chat(usr, "You cannot do this while restrained.") + +/obj/item/device/pda/verb/verb_remove_cartridge() + set category = "Object" + set name = "Remove cartridge" + set src in usr + + if(issilicon(usr)) + return + + if(!can_use(usr)) + to_chat(usr, "You cannot do this while restrained.") + return + + if(isnull(cartridge)) + to_chat(usr, "There's no cartridge to eject.") + return + + cartridge.forceMove(get_turf(src)) + if(ismob(loc)) + var/mob/M = loc + M.put_in_hands(cartridge) + // mode = 0 + // scanmode = 0 + if (cartridge.radio) + cartridge.radio.hostpda = null + to_chat(usr, "You remove \the [cartridge] from the [name].") + playsound(src, 'sound/machines/id_swipe.ogg', 100, 1) + cartridge = null + update_programs() + update_shortcuts() + start_program(find_program(/datum/data/pda/app/main_menu)) + + +/obj/item/device/pda/proc/id_check(mob/user as mob, choice as num)//To check for IDs; 1 for in-pda use, 2 for out of pda use. + if(choice == 1) + if (id) + remove_id() + return 1 + else + var/obj/item/I = user.get_active_hand() + if (istype(I, /obj/item/weapon/card/id) && user.unEquip(I)) + I.loc = src + id = I + return 1 + else + var/obj/item/weapon/card/I = user.get_active_hand() + if (istype(I, /obj/item/weapon/card/id) && I:registered_name && user.unEquip(I)) + var/obj/old_id = id + I.loc = src + id = I + user.put_in_hands(old_id) + return 1 + return 0 + +// access to status display signals +/obj/item/device/pda/attackby(obj/item/C as obj, mob/user as mob) + ..() + if(istype(C, /obj/item/weapon/cartridge) && !cartridge) + cartridge = C + user.drop_item() + cartridge.loc = src + cartridge.update_programs(src) + update_shortcuts() + to_chat(usr, "You insert [cartridge] into [src].") + if(cartridge.radio) + cartridge.radio.hostpda = src + + else if(istype(C, /obj/item/weapon/card/id)) + var/obj/item/weapon/card/id/idcard = C + if(!idcard.registered_name) + to_chat(user, "\The [src] rejects the ID.") + return + if(!owner) + owner = idcard.registered_name + ownjob = idcard.assignment + ownrank = idcard.rank + name = "PDA-[owner] ([ownjob])" + to_chat(user, "Card scanned.") + else + //Basic safety check. If either both objects are held by user or PDA is on ground and card is in hand. + if(((src in user.contents) && (C in user.contents)) || (istype(loc, /turf) && in_range(src, user) && (C in user.contents)) ) + if(id_check(user, 2)) + to_chat(user, "You put the ID into \the [src]'s slot.") + updateSelfDialog()//Update self dialog on success. + return //Return in case of failed check or when successful. + updateSelfDialog()//For the non-input related code. + else if(istype(C, /obj/item/device/paicard) && !src.pai) + user.drop_item() + C.loc = src + pai = C + to_chat(user, "You slot \the [C] into \the [src].") + SStgui.update_uis(src) // update all UIs attached to src + else if(istype(C, /obj/item/weapon/pen)) + var/obj/item/weapon/pen/O = locate() in src + if(O) + to_chat(user, "There is already a pen in \the [src].") + else + user.drop_item() + C.loc = src + to_chat(user, "You slot \the [C] into \the [src].") + return + +/obj/item/device/pda/attack(mob/living/C as mob, mob/living/user as mob) + if (istype(C, /mob/living/carbon) && scanmode) + scanmode.scan_mob(C, user) + +/obj/item/device/pda/afterattack(atom/A as mob|obj|turf|area, mob/user as mob, proximity) + if(proximity && scanmode) + scanmode.scan_atom(A, user) + +/obj/item/device/pda/proc/explode() //This needs tuning. //Sure did. + if(!src.detonate) return + var/turf/T = get_turf(src.loc) + if(T) + T.hotspot_expose(700,125) + explosion(T, 0, 0, 1, rand(1,2)) + return + +/obj/item/device/pda/Destroy() + PDAs -= src + if (src.id && prob(100) && !delete_id) //IDs are kept in 90% of the cases //VOREStation Edit - 100% of the cases, excpet when specified otherwise + src.id.forceMove(get_turf(src.loc)) + else + QDEL_NULL(src.id) + + current_app = null + scanmode = null + QDEL_NULL(pai) + QDEL_LIST(programs) + QDEL_NULL(cartridge) + return ..() + +//Some spare PDAs in a box +/obj/item/weapon/storage/box/PDAs + name = "box of spare PDAs" + desc = "A box of spare PDA microcomputers." + icon = 'icons/obj/pda.dmi' + icon_state = "pdabox" + +/obj/item/weapon/storage/box/PDAs/New() + ..() + new /obj/item/device/pda(src) + new /obj/item/device/pda(src) + new /obj/item/device/pda(src) + new /obj/item/device/pda(src) + new /obj/item/weapon/cartridge/head(src) + + var/newcart = pick( /obj/item/weapon/cartridge/engineering, + /obj/item/weapon/cartridge/security, + /obj/item/weapon/cartridge/medical, + /obj/item/weapon/cartridge/signal/science, + /obj/item/weapon/cartridge/quartermaster) + new newcart(src) + +// Pass along the pulse to atoms in contents, largely added so pAIs are vulnerable to EMP +/obj/item/device/pda/emp_act(severity) + for(var/atom/A in src) + A.emp_act(severity) diff --git a/code/modules/pda/pda_subtypes.dm b/code/modules/pda/pda_subtypes.dm new file mode 100644 index 00000000000..11b684b6782 --- /dev/null +++ b/code/modules/pda/pda_subtypes.dm @@ -0,0 +1,246 @@ + +/obj/item/device/pda/medical + default_cartridge = /obj/item/weapon/cartridge/medical + icon_state = "pda-m" + +/obj/item/device/pda/viro + default_cartridge = /obj/item/weapon/cartridge/medical + icon_state = "pda-v" + +/obj/item/device/pda/engineering + default_cartridge = /obj/item/weapon/cartridge/engineering + icon_state = "pda-e" + +/obj/item/device/pda/security + default_cartridge = /obj/item/weapon/cartridge/security + icon_state = "pda-s" + +/obj/item/device/pda/detective + default_cartridge = /obj/item/weapon/cartridge/detective + icon_state = "pda-det" + +/obj/item/device/pda/warden + default_cartridge = /obj/item/weapon/cartridge/security + icon_state = "pda-warden" + +/obj/item/device/pda/janitor + default_cartridge = /obj/item/weapon/cartridge/janitor + icon_state = "pda-j" + ttone = "slip" + +/obj/item/device/pda/science + default_cartridge = /obj/item/weapon/cartridge/signal/science + icon_state = "pda-tox" + ttone = "boom" + +/obj/item/device/pda/clown + default_cartridge = /obj/item/weapon/cartridge/clown + icon_state = "pda-clown" + desc = "A portable microcomputer by Thinktronic Systems, LTD. The surface is coated with polytetrafluoroethylene and banana drippings." + ttone = "honk" + +/obj/item/device/pda/mime + default_cartridge = /obj/item/weapon/cartridge/mime + icon_state = "pda-mime" + +/obj/item/device/pda/mime/New() + . = ..() + var/datum/data/pda/app/M = find_program(/datum/data/pda/app/messenger) + if(M) + M.notify_silent = TRUE + +/obj/item/device/pda/heads + default_cartridge = /obj/item/weapon/cartridge/head + icon_state = "pda-h" + +/obj/item/device/pda/heads/hop + default_cartridge = /obj/item/weapon/cartridge/hop + icon_state = "pda-hop" + +/obj/item/device/pda/heads/hos + default_cartridge = /obj/item/weapon/cartridge/hos + icon_state = "pda-hos" + +/obj/item/device/pda/heads/ce + default_cartridge = /obj/item/weapon/cartridge/ce + icon_state = "pda-ce" + +/obj/item/device/pda/heads/cmo + default_cartridge = /obj/item/weapon/cartridge/cmo + icon_state = "pda-cmo" + +/obj/item/device/pda/heads/rd + default_cartridge = /obj/item/weapon/cartridge/rd + icon_state = "pda-rd" + +/obj/item/device/pda/captain + default_cartridge = /obj/item/weapon/cartridge/captain + icon_state = "pda-c" + detonate = 0 + //toff = 1 + +/obj/item/device/pda/ert + default_cartridge = /obj/item/weapon/cartridge/captain + icon_state = "pda-h" + detonate = 0 +// hidden = 1 + +/obj/item/device/pda/cargo + default_cartridge = /obj/item/weapon/cartridge/quartermaster + icon_state = "pda-cargo" + +/obj/item/device/pda/quartermaster + default_cartridge = /obj/item/weapon/cartridge/quartermaster + icon_state = "pda-q" + +/obj/item/device/pda/shaftminer + icon_state = "pda-miner" + default_cartridge = /obj/item/weapon/cartridge/miner + +/obj/item/device/pda/syndicate + default_cartridge = /obj/item/weapon/cartridge/syndicate + icon_state = "pda-syn" +// name = "Military PDA" // Vorestation Edit +// owner = "John Doe" + hidden = 1 + +/obj/item/device/pda/chaplain + default_cartridge = /obj/item/weapon/cartridge/service + icon_state = "pda-holy" + ttone = "holy" + +/obj/item/device/pda/lawyer + default_cartridge = /obj/item/weapon/cartridge/lawyer + icon_state = "pda-lawyer" + ttone = "..." + +/obj/item/device/pda/botanist + default_cartridge = /obj/item/weapon/cartridge/service + icon_state = "pda-hydro" + +/obj/item/device/pda/roboticist + default_cartridge = /obj/item/weapon/cartridge/signal/science + icon_state = "pda-robot" + +/obj/item/device/pda/librarian + default_cartridge = /obj/item/weapon/cartridge/service + icon_state = "pda-libb" + desc = "A portable microcomputer by Thinktronic Systems, LTD. This is model is a WGW-11 series e-reader." + model_name = "Thinktronic 5290 WGW-11 Series E-reader and Personal Data Assistant" + +/obj/item/device/pda/librarian/New() + . = ..() + var/datum/data/pda/app/M = find_program(/datum/data/pda/app/messenger) + if(M) + M.notify_silent = TRUE //Quiet in the library! + +/obj/item/device/pda/clear + icon_state = "pda-transp" + desc = "A portable microcomputer by Thinktronic Systems, LTD. This is model is a special edition with a transparent case." + model_name = "Thinktronic 5230 Personal Data Assistant Deluxe Special Max Turbo Limited Edition" + +/obj/item/device/pda/chef + default_cartridge = /obj/item/weapon/cartridge/service + icon_state = "pda-chef" + +/obj/item/device/pda/bar + default_cartridge = /obj/item/weapon/cartridge/service + icon_state = "pda-bar" + +/obj/item/device/pda/atmos + default_cartridge = /obj/item/weapon/cartridge/atmos + icon_state = "pda-atmo" + +/obj/item/device/pda/chemist + default_cartridge = /obj/item/weapon/cartridge/chemistry + icon_state = "pda-chem" + +/obj/item/device/pda/geneticist + default_cartridge = /obj/item/weapon/cartridge/medical + icon_state = "pda-gene" + + +// Used for the PDA multicaster, which mirrors messages sent to it to a specific department, +/obj/item/device/pda/multicaster + ownjob = "Relay" + icon_state = "NONE" + detonate = 0 + spam_proof = TRUE // Spam messages don't actually work and its difficult to disable these. + programs = list( + new/datum/data/pda/app/messenger/multicast + ) + var/list/cartridges_to_send_to = list() + +/obj/item/device/pda/multicaster/command/New() + ..() + owner = "Command Department" + name = "Command Department (Relay)" + cartridges_to_send_to = command_cartridges + +/obj/item/device/pda/multicaster/security/New() + ..() + owner = "Security Department" + name = "Security Department (Relay)" + cartridges_to_send_to = security_cartridges + +/obj/item/device/pda/multicaster/engineering/New() + ..() + owner = "Engineering Department" + name = "Engineering Department (Relay)" + cartridges_to_send_to = engineering_cartridges + +/obj/item/device/pda/multicaster/medical/New() + ..() + owner = "Medical Department" + name = "Medical Department (Relay)" + cartridges_to_send_to = medical_cartridges + +/obj/item/device/pda/multicaster/research/New() + ..() + owner = "Research Department" + name = "Research Department (Relay)" + cartridges_to_send_to = research_cartridges + +/obj/item/device/pda/multicaster/cargo/New() + ..() + owner = "Cargo Department" + name = "Cargo Department (Relay)" + cartridges_to_send_to = cargo_cartridges + +/obj/item/device/pda/multicaster/civilian/New() + ..() + owner = "Civilian Services Department" + name = "Civilian Services Department (Relay)" + cartridges_to_send_to = civilian_cartridges + +/obj/item/device/pda/clown/Crossed(atom/movable/AM as mob|obj) //Clown PDA is slippery. + if(AM.is_incorporeal()) + return + if (istype(AM, /mob/living)) + var/mob/living/M = AM + + if(M.slip("the PDA",8) && M.real_name != src.owner && istype(src.cartridge, /obj/item/weapon/cartridge/clown)) + if(src.cartridge.charges < 5) + src.cartridge.charges++ + +//Some spare PDAs in a box +/obj/item/weapon/storage/box/PDAs + name = "box of spare PDAs" + desc = "A box of spare PDA microcomputers." + icon = 'icons/obj/pda.dmi' + icon_state = "pdabox" + +/obj/item/weapon/storage/box/PDAs/New() + ..() + new /obj/item/device/pda(src) + new /obj/item/device/pda(src) + new /obj/item/device/pda(src) + new /obj/item/device/pda(src) + new /obj/item/weapon/cartridge/head(src) + + var/newcart = pick( /obj/item/weapon/cartridge/engineering, + /obj/item/weapon/cartridge/security, + /obj/item/weapon/cartridge/medical, + /obj/item/weapon/cartridge/signal/science, + /obj/item/weapon/cartridge/quartermaster) + new newcart(src) diff --git a/code/modules/pda/pda_tgui.dm b/code/modules/pda/pda_tgui.dm new file mode 100644 index 00000000000..4937d423252 --- /dev/null +++ b/code/modules/pda/pda_tgui.dm @@ -0,0 +1,124 @@ +// Self contained file for all things TGUI +/obj/item/device/pda/tgui_state(mob/user) + return GLOB.tgui_inventory_state + +/obj/item/device/pda/tgui_status(mob/user, datum/tgui_state/state) + . = ..() + if(!can_use()) + . = min(., STATUS_UPDATE) + +/obj/item/device/pda/tgui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Pda", "Personal Data Assistant") + ui.open() + +/obj/item/device/pda/tgui_data(mob/user, datum/tgui/ui, datum/tgui_state/state) + var/list/data = ..() + + data["owner"] = owner // Who is your daddy... + data["ownjob"] = ownjob // ...and what does he do? + + // update list of shortcuts, only if they changed + if(!shortcut_cache.len) + shortcut_cache = list() + shortcut_cat_order = list() + var/prog_list = programs.Copy() + if(cartridge) + prog_list |= cartridge.programs + + for(var/A in prog_list) + var/datum/data/pda/P = A + + if(P.hidden) + continue + var/list/cat + if(P.category in shortcut_cache) + cat = shortcut_cache[P.category] + else + cat = list() + shortcut_cache[P.category] = cat + shortcut_cat_order += P.category + cat |= list(list(name = P.name, icon = P.icon, notify_icon = P.notify_icon, ref = "\ref[P]")) + + // force the order of a few core categories + shortcut_cat_order = list("General") \ + + sortList(shortcut_cat_order - list("General", "Scanners", "Utilities")) \ + + list("Scanners", "Utilities") + + data["idInserted"] = (id ? 1 : 0) + data["idLink"] = (id ? text("[id.registered_name], [id.assignment]") : "--------") + + data["useRetro"] = retro_mode + + data["cartridge_name"] = cartridge ? cartridge.name : "" + data["stationTime"] = stationtime2text() //worldtime2stationtime(world.time) // Aaa which fucking one is canonical there's SO MANY + + data["app"] = list( + "name" = current_app.title, + "icon" = current_app.icon, + "template" = current_app.template, + "has_back" = current_app.has_back) + + current_app.update_ui(user, data) + + return data + +/obj/item/device/pda/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state) + if(..()) + return TRUE + + if(!can_use()) + usr.unset_machine() + if(ui) + ui.close() + return FALSE + + add_fingerprint(usr) + usr.set_machine(src) + + if(!touch_silent) + playsound(src, 'sound/machines/pda_click.ogg', 20) + + . = TRUE + switch(action) + if("Home") //Go home, largely replaces the old Return + var/datum/data/pda/app/main_menu/A = find_program(/datum/data/pda/app/main_menu) + if(A) + start_program(A) + if("StartProgram") + if(params["program"]) + var/datum/data/pda/app/A = locate(params["program"]) + if(A) + start_program(A) + if("Eject")//Ejects the cart, only done from hub. + if(!isnull(cartridge)) + var/turf/T = loc + if(ismob(T)) + T = T.loc + var/obj/item/weapon/cartridge/C = cartridge + C.forceMove(T) + if(scanmode in C.programs) + scanmode = null + if(current_app in C.programs) + start_program(find_program(/datum/data/pda/app/main_menu)) + if(C.radio) + C.radio.hostpda = null + for(var/datum/data/pda/P in notifying_programs) + if(P in C.programs) + P.unnotify() + cartridge = null + update_shortcuts() + if("Authenticate")//Checks for ID + id_check(usr, 1) + if("Retro") + retro_mode = !retro_mode + if("Ringtone") + return set_ringtone() + else + if(current_app) + . = current_app.tgui_act(action, params, ui, state) + + if((honkamt > 0) && (prob(60)))//For clown virus. + honkamt-- + playsound(loc, 'sound/items/bikehorn.ogg', 30, 1) diff --git a/code/game/objects/items/devices/PDA/PDA_vr.dm b/code/modules/pda/pda_vr.dm similarity index 100% rename from code/game/objects/items/devices/PDA/PDA_vr.dm rename to code/modules/pda/pda_vr.dm diff --git a/code/modules/pda/radio.dm b/code/modules/pda/radio.dm new file mode 100644 index 00000000000..0d3b115a134 --- /dev/null +++ b/code/modules/pda/radio.dm @@ -0,0 +1,138 @@ +/obj/item/radio/integrated + name = "\improper PDA radio module" + desc = "An electronic radio system." + icon = 'icons/obj/module.dmi' + icon_state = "power_mod" + var/obj/item/device/pda/hostpda = null + + var/list/botlist = null // list of bots + var/mob/living/bot/active // the active bot; if null, show bot list + var/list/botstatus // the status signal sent by the bot + + var/bot_type //The type of bot it is. + var/bot_filter //Determines which radio filter to use. + + var/control_freq = BOT_FREQ + + var/on = 0 //Are we currently active?? + var/menu_message = "" + +/obj/item/radio/integrated/New() + ..() + if(istype(loc.loc, /obj/item/device/pda)) + hostpda = loc.loc + if(bot_filter) + spawn(5) + add_to_radio(bot_filter) + +/obj/item/radio/integrated/Destroy() + if(radio_controller) + radio_controller.remove_object(src, control_freq) + hostpda = null + return ..() + +/obj/item/radio/integrated/proc/post_signal(var/freq, var/key, var/value, var/key2, var/value2, var/key3, var/value3, s_filter) + + //to_world("Post: [freq]: [key]=[value], [key2]=[value2]") + var/datum/radio_frequency/frequency = radio_controller.return_frequency(freq) + + if(!frequency) + return + + var/datum/signal/signal = new() + signal.source = src + signal.transmission_method = TRANSMISSION_RADIO + signal.data[key] = value + if(key2) + signal.data[key2] = value2 + if(key3) + signal.data[key3] = value3 + + frequency.post_signal(src, signal, radio_filter = s_filter) + +/obj/item/radio/integrated/Topic(href, href_list) + ..() + switch(href_list["op"]) + if("control") + active = locate(href_list["bot"]) + spawn(0) + post_signal(control_freq, "command", "bot_status", "active", active, s_filter = bot_filter) + + if("scanbots") // find all bots + botlist = null + spawn(0) + post_signal(control_freq, "command", "bot_status", s_filter = bot_filter) + + if("botlist") + active = null + + if("stop", "go", "home") + spawn(0) + post_signal(control_freq, "command", href_list["op"], "active", active, s_filter = bot_filter) + post_signal(control_freq, "command", "bot_status", "active", active, s_filter = bot_filter) + + if("summon") + spawn(0) + post_signal(control_freq, "command", "summon", "active", active, "target", get_turf(hostpda), "useraccess", hostpda.GetAccess(), "user", usr, s_filter = bot_filter) + post_signal(control_freq, "command", "bot_status", "active", active, s_filter = bot_filter) + +/obj/item/radio/integrated/receive_signal(datum/signal/signal) + if(bot_type && istype(signal.source, /mob/living/bot) && signal.data["type"] == bot_type) + if(!botlist) + botlist = new() + + botlist |= signal.source + + if(active == signal.source) + var/list/b = signal.data + botstatus = b.Copy() + +/obj/item/radio/integrated/proc/add_to_radio(bot_filter) //Master filter control for bots. Must be placed in the bot's local New() to support map spawned bots. + if(radio_controller) + radio_controller.add_object(src, control_freq, radio_filter = bot_filter) + +/* + * Radio Cartridge, essentially a signaler. + */ +/obj/item/radio/integrated/signal + var/frequency = 1457 + var/code = 30.0 + var/last_transmission + var/datum/radio_frequency/radio_connection + +/obj/item/radio/integrated/signal/Destroy() + if(radio_controller) + radio_controller.remove_object(src, frequency) + radio_connection = null + return ..() + +/obj/item/radio/integrated/signal/Initialize() + if(!radio_controller) + return + + if(src.frequency < PUBLIC_LOW_FREQ || src.frequency > PUBLIC_HIGH_FREQ) + src.frequency = sanitize_frequency(src.frequency) + + set_frequency(frequency) + +/obj/item/radio/integrated/signal/proc/set_frequency(new_frequency) + radio_controller.remove_object(src, frequency) + frequency = new_frequency + radio_connection = radio_controller.add_object(src, frequency) + +/obj/item/radio/integrated/signal/proc/send_signal(message="ACTIVATE") + if(last_transmission && world.time < (last_transmission + 5)) + return + last_transmission = world.time + + var/time = time2text(world.realtime,"hh:mm:ss") + var/turf/T = get_turf(src) + lastsignalers.Add("[time] : [usr.key] used [src] @ location ([T.x],[T.y],[T.z]) : [format_frequency(frequency)]/[code]") + + var/datum/signal/signal = new + signal.source = src + signal.encryption = code + signal.data["message"] = message + + spawn(0) + radio_connection.post_signal(src, signal) diff --git a/code/modules/pda/utilities.dm b/code/modules/pda/utilities.dm new file mode 100644 index 00000000000..be8bb8f4445 --- /dev/null +++ b/code/modules/pda/utilities.dm @@ -0,0 +1,184 @@ +/datum/data/pda/utility/flashlight + name = "Enable Flashlight" + icon = "lightbulb-o" + + var/fon = 0 //Is the flashlight function on? + var/f_lum = 2 //Luminosity for the flashlight function + +/datum/data/pda/utility/flashlight/start() + fon = !fon + name = fon ? "Disable Flashlight" : "Enable Flashlight" + pda.update_shortcuts() + pda.set_light(fon ? f_lum : 0) + +/datum/data/pda/utility/honk + name = "Honk Synthesizer" + icon = "smile-o" + category = "Clown" + + var/last_honk //Also no honk spamming that's bad too + +/datum/data/pda/utility/honk/start() + if(!(last_honk && world.time < last_honk + 20)) + playsound(pda.loc, 'sound/items/bikehorn.ogg', 50, 1) + last_honk = world.time + +/datum/data/pda/utility/toggle_door + name = "Toggle Door" + icon = "external-link" + var/remote_door_id = "" + +// /datum/data/pda/utility/toggle_door/start() +// for(var/obj/machinery/door/poddoor/M in airlocks) +// if(M.id_tag == remote_door_id) +// if(M.density) +// M.open() +// else +// M.close() + +/datum/data/pda/utility/scanmode/medical + base_name = "Med Scanner" + icon = "heart-o" + +/datum/data/pda/utility/scanmode/medical/scan_mob(mob/living/C as mob, mob/living/user as mob) + C.visible_message("[user] has analyzed [C]'s vitals!") + + user.show_message("Analyzing Results for [C]:") + user.show_message(" Overall Status: [C.stat > 1 ? "dead" : "[C.health - C.halloss]% healthy"]", 1) + user.show_message(text(" Damage Specifics: []-[]-[]-[]", + (C.getOxyLoss() > 50) ? "warning" : "", C.getOxyLoss(), + (C.getToxLoss() > 50) ? "warning" : "", C.getToxLoss(), + (C.getFireLoss() > 50) ? "warning" : "", C.getFireLoss(), + (C.getBruteLoss() > 50) ? "warning" : "", C.getBruteLoss() + ), 1) + user.show_message(" Key: Suffocation/Toxin/Burns/Brute", 1) + user.show_message(" Body Temperature: [C.bodytemperature-T0C]°C ([C.bodytemperature*1.8-459.67]°F)", 1) + if(C.tod && (C.stat == DEAD || (C.status_flags & FAKEDEATH))) + user.show_message(" Time of Death: [C.tod]") + if(istype(C, /mob/living/carbon/human)) + var/mob/living/carbon/human/H = C + var/list/damaged = H.get_damaged_organs(1,1) + user.show_message("Localized Damage, Brute/Burn:",1) + if(length(damaged)>0) + for(var/obj/item/organ/external/org in damaged) + user.show_message(text(" []: []-[]", + capitalize(org.name), (org.brute_dam > 0) ? "warning" : "notice", org.brute_dam, (org.burn_dam > 0) ? "warning" : "notice", org.burn_dam),1) + else + user.show_message(" Limbs are OK.",1) + +/datum/data/pda/utility/scanmode/dna + base_name = "DNA Scanner" + icon = "link" + +/datum/data/pda/utility/scanmode/dna/scan_mob(mob/living/C as mob, mob/living/user as mob) + if(istype(C, /mob/living/carbon/human)) + var/mob/living/carbon/human/H = C + if(!istype(H.dna, /datum/dna)) + to_chat(user, "No fingerprints found on [H]") + else + to_chat(user, "[H]'s Fingerprints: [md5(H.dna.uni_identity)]") + scan_blood(C, user) + +/datum/data/pda/utility/scanmode/dna/scan_atom(atom/A as mob|obj|turf|area, mob/user as mob) + scan_blood(A, user) + +/datum/data/pda/utility/scanmode/dna/proc/scan_blood(atom/A, mob/user) + if(!A.blood_DNA) + to_chat(user, "No blood found on [A]") + if(A.blood_DNA) + qdel(A.blood_DNA) + else + to_chat(user, "Blood found on [A]. Analysing...") + spawn(15) + for(var/blood in A.blood_DNA) + to_chat(user, "Blood type: [A.blood_DNA[blood]]\nDNA: [blood]") + +/datum/data/pda/utility/scanmode/halogen + base_name = "Halogen Counter" + icon = "exclamation-circle" + +/datum/data/pda/utility/scanmode/halogen/scan_mob(mob/living/C as mob, mob/living/user as mob) + C.visible_message("[user] has analyzed [C]'s radiation levels!") + + user.show_message("Analyzing Results for [C]:") + if(C.radiation) + user.show_message("Radiation Level: [C.radiation > 0 ? "[C.radiation]" : "0"]") + else + user.show_message("No radiation detected.") + +/datum/data/pda/utility/scanmode/reagent + base_name = "Reagent Scanner" + icon = "flask" + +/datum/data/pda/utility/scanmode/reagent/scan_atom(atom/A as mob|obj|turf|area, mob/user as mob) + if(!isnull(A.reagents)) + if(A.reagents.reagent_list.len > 0) + var/reagents_length = A.reagents.reagent_list.len + to_chat(user, "[reagents_length] chemical agent[reagents_length > 1 ? "s" : ""] found.") + for(var/re in A.reagents.reagent_list) + to_chat(user, "\t [re]") + else + to_chat(user, "No active chemical agents found in [A].") + else + to_chat(user, "No significant chemical agents found in [A].") + +/datum/data/pda/utility/scanmode/gas + base_name = "Gas Scanner" + icon = "tachometer-alt" + +/datum/data/pda/utility/scanmode/gas/scan_atom(atom/A as mob|obj|turf|area, mob/user as mob) + pda.analyze_gases(A, user) + +/datum/data/pda/utility/scanmode/notes + base_name = "Note Scanner" + icon = "clipboard" + var/datum/data/pda/app/notekeeper/notes + +/datum/data/pda/utility/scanmode/notes/start() + . = ..() + notes = pda.find_program(/datum/data/pda/app/notekeeper) + +/datum/data/pda/utility/scanmode/notes/scan_atom(atom/A as mob|obj|turf|area, mob/user as mob) + if(notes && istype(A, /obj/item/weapon/paper)) + var/obj/item/weapon/paper/P = A + var/list/brlist = list("p", "/p", "br", "hr", "h1", "h2", "h3", "h4", "/h1", "/h2", "/h3", "/h4") + + // JMO 20140705: Makes scanned document show up properly in the notes. Not pretty for formatted documents, + // as this will clobber the HTML, but at least it lets you scan a document. You can restore the original + // notes by editing the note again. (Was going to allow you to edit, but scanned documents are too long.) + var/raw_scan = sanitize_simple(P.info, list("\t" = "", "ÿ" = "")) + var/formatted_scan = "" + // Scrub out the tags (replacing a few formatting ones along the way) + // Find the beginning and end of the first tag. + var/tag_start = findtext(raw_scan, "<") + var/tag_stop = findtext(raw_scan, ">") + // Until we run out of complete tags... + while(tag_start && tag_stop) + var/pre = copytext(raw_scan, 1, tag_start) // Get the stuff that comes before the tag + var/tag = lowertext(copytext(raw_scan, tag_start + 1, tag_stop)) // Get the tag so we can do intellegent replacement + var/tagend = findtext(tag, " ") // Find the first space in the tag if there is one. + // Anything that's before the tag can just be added as is. + formatted_scan = formatted_scan + pre + // If we have a space after the tag (and presumably attributes) just crop that off. + if(tagend) + tag = copytext(tag, 1, tagend) + if(tag in brlist) // Check if it's I vertical space tag. + formatted_scan = formatted_scan + "
" // If so, add some padding in. + raw_scan = copytext(raw_scan, tag_stop + 1) // continue on with the stuff after the tag + // Look for the next tag in what's left + tag_start = findtext(raw_scan, "<") + tag_stop = findtext(raw_scan, ">") + // Anything that is left in the page. just tack it on to the end as is + formatted_scan = formatted_scan + raw_scan + // If there is something in there already, pad it out. + if(length(notes.note) > 0) + notes.note += "

" + // Store the scanned document to the notes + notes.note += "Scanned Document. Edit to restore previous notes/delete scan.
----------
" + formatted_scan + "
" + // notehtml ISN'T set to allow user to get their old notes back. A better implementation would add a "scanned documents" + // feature to the PDA, which would better convey the availability of the feature, but this will work for now. + // Inform the user + to_chat(user, "Paper scanned and OCRed to notekeeper.")//concept of scanning paper copyright brainoblivion 2009 + + else + to_chat(user, "Error scanning [A].") diff --git a/code/modules/resleeving/infomorph.dm b/code/modules/resleeving/infomorph.dm index 7fd716bc96f..248444f1dff 100644 --- a/code/modules/resleeving/infomorph.dm +++ b/code/modules/resleeving/infomorph.dm @@ -112,7 +112,10 @@ var/list/infomorph_emotions = list( pda.ownjob = "Sleevecard" pda.owner = text("[]", src) pda.name = pda.owner + " (" + pda.ownjob + ")" - pda.toff = 1 + + var/datum/data/pda/app/messenger/M = pda.find_program(/datum/data/pda/app/messenger) + if(M) + M.toff = TRUE ..() 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/pda.tmpl b/nano/templates/pda.tmpl deleted file mode 100644 index 3177450ba64..00000000000 --- a/nano/templates/pda.tmpl +++ /dev/null @@ -1,784 +0,0 @@ - - -{{if data.owner}} -
-
- Functions: -
-
- - {{:helper.link('Close', 'gear', {'choice' : "Close"}, null, 'fixedLeft')}} - {{if data.idInserted}} {{:helper.link('Update PDA Info', 'eject', {'choice' : "UpdateInfo"}, null, 'fixedLeftWide')}} {{/if}} - {{if data.mode != 0}} {{:helper.link('Return', 'arrowreturn-1-w', {'choice' : "Return"}, null, 'fixedLeft')}} {{/if}} -
-
-
-
-
- Station Time: -
-
- {{:data.stationTime}} -
-
-
- - - {{if data.mode == 0}} -
-
- Owner: -
-
- {{:data.owner}}, {{:data.ownjob}} -
-
-
-
-
- ID: -
-
- {{:helper.link(data.idLink, 'eject', {'choice' : "Authenticate"}, data.idInserted ? null : 'disabled', data.idInserted ? 'floatright' : 'fixedLeft')}} -
-
-
-
-
- Cartridge: -
-
- {{if data.cart_loaded==1}} - {{:helper.link(data.cartridge.name, 'eject', {'choice' : "Eject"},null,null)}} - {{else}} - {{:helper.link('None', 'eject', {'choice' : "Eject"},'disabled',null)}} - {{/if}} -
-
-
-

Functions

-
-
-
- General: -
-
- {{:helper.link('Notekeeper', 'note', {'choice' : "1"}, null, 'fixedLeftWide')}} - {{:helper.link('Messenger', data.new_Message ? 'mail-closed' : 'mail-open', {'choice' : "2"}, null, 'fixedLeftWide')}} - {{:helper.link('Crew Manifest', 'contact', {'choice' : "41"}, null, 'fixedLeftWide')}} - {{:helper.link('News', data.new_News ? 'mail-closed' : 'mail-open', {'choice' : "6"}, null, 'fixedLeftWide')}} -
-
-
- {{if data.cartridge}} - {{if data.cartridge.access.access_clown == 1}} -
-
- Clown: -
-
- {{:helper.link('Honk Synthesizer', 'gear', {'choice' : "Honk"}, null, 'fixedLeftWide')}} -
-
-
- {{/if}} - {{if data.cartridge.access.access_engine == 1}} -
-
- Engineering: -
-
- {{:helper.link('Power Monitor', 'alert', {'choice' : "43"}, null, 'fixedLeftWide')}} -
-
-
- {{/if}} - {{if data.cartridge.access.access_medical == 1}} -
-
- Medical: -
-
- {{:helper.link('Medical Records', 'gear', {'choice' : "44"}, null, 'fixedLeftWide')}} - {{:helper.link(data.scanmode == 1 ? 'Disable Med Scanner' : 'Enable Med Scanner', 'gear', {'choice' : "Medical Scan"}, null , 'fixedLeftWide')}} -
-
-
- {{/if}} - {{if data.cartridge.access.access_security == 1}} -
-
- Security: -
-
- {{:helper.link('Security Records', 'gear', {'choice' : "45"}, null, 'fixedLeftWide')}} - {{if data.cartridge.radio ==1}} {{:helper.link('Security Bot Access', 'gear', {'choice' : "46"}, null, 'fixedLeftWide')}} {{/if}} -
-
-
-
- {{/if}} - {{if data.cartridge.access.access_quartermaster == 1}} -
-
- Quartermaster: -
-
- {{:helper.link('Supply Records', 'gear', {'choice' : "47"}, null, 'fixedLeftWide')}} - {{:helper.link('Delivery Bot Control', 'gear', {'choice' : "48"}, null, 'fixedLeftWide')}} -
-
-
-
- {{/if}} - {{/if}} -
-
-
- Utilities: -
-
- {{if data.cartridge}} - {{if data.cartridge.access.access_status_display == 1}} - {{:helper.link('Status Display', 'gear', {'choice' : "42"}, null, 'fixedLeftWide')}} - {{/if}} - {{if data.cartridge.access.access_janitor==1}} - {{:helper.link('Custodial Locator', 'gear', {'choice' : "49"}, null, 'fixedLeftWide')}} - {{/if}} - {{if data.cartridge.radio == 2}} - {{:helper.link('Signaler System', 'gear', {'choice' : "40"}, null, 'fixedLeftWide')}} - {{/if}} - {{if data.cartridge.access.access_reagent_scanner==1}} - {{:helper.link(data.scanmode == 3 ? 'Disable Reagent Scanner' : 'Enable Reagent Scanner', 'gear', {'choice' : "Reagent Scan"}, null, 'fixedLeftWider')}} - {{/if}} - {{if data.cartridge.access.access_engine==1}} - {{:helper.link(data.scanmode == 4 ? 'Disable Halogen Counter' : 'Enable Halogen Counter', 'gear', {'choice' : "Halogen Counter"}, null, 'fixedLeftWider')}} - {{/if}} - {{if data.cartridge.access.access_atmos==1}} - {{:helper.link(data.scanmode == 5 ? 'Disable Gas Scanner' : 'Enable Gas Scanner', 'gear', {'choice' : "Gas Scan"}, null, 'fixedLeftWide')}} - {{/if}} - {{if data.cartridge.access.access_remote_door==1}} - {{:helper.link('Toggle Door', 'gear', {'choice' : "Toggle Door"}, null, 'fixedLeftWide')}} - {{/if}} - {{/if}} - {{:helper.link('Atmospheric Scan', 'gear', {'choice' : "3"}, null, 'fixedLeftWide')}} - {{:helper.link(data.touch_silent==1 ? 'Enable Beeping' : 'Disable Beeping', 'gear', {'choice' : "Toggle Beeping"}, null,'fixedLeftWide')}} - {{:helper.link(data.fon==1 ? 'Disable Flashlight' : 'Enable Flashlight', 'lightbulb', {'choice' : "Light"}, null,'fixedLeftWide')}} -
-
- {{if data.pai}} -
-
- PAI Utilities: -
-
- {{:helper.link('Configuration', 'gear', {'choice' : "pai", 'option' : "1"}, null, 'fixedLeft')}} - {{:helper.link('Eject pAI', 'eject', {'choice' : "pai", 'option' : "2"}, null, 'fixedLeft')}} -
-
- {{/if}} - - - {{else data.mode == 1}} -
-
- Notes: -
-
-
-
-
- {{:data.note}} -
-
-
-
-
- {{:helper.link('Edit Notes', 'gear', {'choice' : "Edit"}, null, 'fixedLeft')}} -
-
- - - {{else data.mode == 2}} -

SpaceMessenger V4.0.1

-
-
- Messenger Functions: -
-
- {{:helper.link(data.message_silent==1 ? 'Ringer: Off' : 'Ringer: On', data.message_silent==1 ? 'volume-off' : 'volume-on', {'choice' : "Toggle Ringer"}, null, 'fixedLeftWide')}} - {{:helper.link(data.toff==1 ? 'Messenger: Off' : 'Messenger: On',data.toff==1 ? 'close':'check', {'choice' : "Toggle Messenger"}, null, 'fixedLeftWide')}} - {{:helper.link('Set Ringtone', 'comment', {'choice' : "Ringtone"}, null, 'fixedLeftWide')}} - {{:helper.link('Delete all Conversations', 'trash', {'choice' : "Clear", 'option' : "All"}, null, 'fixedLeftWider')}} -
-
- {{if data.toff == 0}} -

- {{if data.cartridge}} - {{if data.cartridge.charges}} -
- {{:data.cartridge.charges}} - {{if data.cartridge.access.access_detonate_pda}} detonation charges left. {{/if}} - {{if data.cartridge.access.access_clown || data.cartridge.access.access_mime}} viral files left. {{/if}} - -

-
- {{/if}} - {{/if}} - - {{if data.pda_count == 0}} - No other PDAS located - {{else}} -

Current Conversations

- {{for data.convopdas}} -
- {{:helper.link(value.Name, 'circle-arrow-s', {'choice' : "Select Conversation", 'convo' : value.Reference } , null, value.fixedLeftWider)}} - {{if data.cartridge}} - {{if data.cartridge.access.access_detonate_pda && value.Detonate}} - {{:helper.link('*Detonate*', 'radiation', {'choice' : "Detonate", 'target' : value.Reference}, null, 'fixedLeft')}} - {{/if}} - {{if data.cartridge.access.access_clown}} - {{:helper.link('*Send Virus*', 'star', {'choice' : "Send Honk", 'target' : value.Reference}, null, 'fixedLeft')}} - {{/if}} - {{if data.cartridge.access.access_mime}} - {{:helper.link('*Send Virus*', 'circle-arrow-s', {'choice' : "Send Silence", 'target' : value.Reference}, null, 'fixedLeft')}} - {{/if}} - {{/if}} -
- {{/for}} -

Other PDAs

- {{for data.pdas}} -
- {{:helper.link(value.Name, 'circle-arrow-s', {'choice' : "Message", 'target' : value.Reference}, null, value.fixedLeftWider)}} - {{if data.cartridge}} - {{if data.cartridge.access.access_detonate_pda && value.Detonate}} {{:helper.link('*Detonate*', 'radiation', {'choice' : "Detonate", 'target' : value.Reference}, null, 'fixedLeft')}} {{/if}} - {{if data.cartridge.access.access_clown}} {{:helper.link('*Send Virus*', 'star', {'choice' : "Send Honk", 'target' : value.Reference}, null, 'fixedLeft')}} {{/if}} - {{if data.cartridge.access.access_mime}} {{:helper.link('*Send Virus*', 'circle-arrow-s', {'choice' : "Send Silence", 'target' : value.Reference}, null, 'fixedLeft')}} {{/if}} - {{/if}} -
- {{/for}} - {{/if}} - {{/if}} - - - {{else data.mode == 21}} -

SpaceMessenger V4.0.1

-
-
- Messenger Functions: -
-
- {{:helper.link('Delete Conversation', 'trash', {'choice' : "Clear", 'option' : "Convo"}, null, 'fixedLeftWide')}} -
-
-
-
-

Conversation with: {{:data.convo_name}} ({{:data.convo_job}})

-
-
-
- {{for data.messages}} - {{if data.active_conversation == value.target}} - {{if value.sent==0}} - Them: {{:value.message}}
- {{else}} - You: {{:value.message}}
- {{/if}} - {{/if}} - {{/for}} -
-
-
- {{:helper.link('Reply', 'comment', {'choice' : "Message", 'target': data.active_conversation}, null, 'fixedLeft')}} - - - {{else data.mode== 41}} - {{#def.crewManifest}} - - - {{else data.mode == 3}} -

Atmospheric Scan

- {{#def.atmosphericScan}} - - {{else data.mode == 40}} -

Remote Signaling System

-
-
- Frequency: -
-
- {{:data.records.signal_freq}} -
-   - {{:helper.link('-1', null, {'cartmenu' : "1", 'choice' : "Signal Frequency", 'sfreq' : "-10"}, null, null)}}  - {{:helper.link('-.2', null, {'cartmenu' : "1", 'choice' : "Signal Frequency", 'sfreq' : "-2"}, null, null)}}  - - {{:helper.link('+.2', null, {'cartmenu' : "1", 'choice' : "Signal Frequency", 'sfreq' : "2"}, null, null)}}  - {{:helper.link('+1', null, {'cartmenu' : "1", 'choice' : "Signal Frequency", 'sfreq' : "10"}, null, null)}} -
-
-
-
-
-
- Code: -
-
- - {{:data.records.signal_code}}
-
- {{:helper.link('-5', null, {'cartmenu' : "1", 'choice' : "Signal Code", 'scode' : "-5"}, null, null)}} - {{:helper.link('-1', null, {'cartmenu' : "1", 'choice' : "Signal Code", 'scode' : "-1"}, null, null)}} - {{:helper.link('+1', null, {'cartmenu' : "1", 'choice' : "Signal Code", 'scode' : "1"}, null, null)}} - {{:helper.link('+5', null, {'cartmenu' : "1", 'choice' : "Signal Code", 'scode' : "5"}, null, null)}} -
-
-
- {{:helper.link('Send Signal', 'radiation', {'cartmenu' : "1", 'choice' : "Send Signal"}, null, null)}} -
- - - {{else data.mode == 42}} -

Station Status Displays Interlink

-
-
- Code: -
-
- {{:helper.link('Clear', 'trash', {'cartmenu' : "1", 'choice' : "Status", 'statdisp' : "blank"}, null, null)}} - - {{:helper.link('Tram ETA', 'gear', {'cartmenu' : "1", 'choice' : "Status",'statdisp' : "shuttle"}, null, null)}} - {{:helper.link('Message', 'gear', {'cartmenu' : "1", 'choice' : "Status",'statdisp' : "message"}, null, null)}} -
-
-
-
-
- Message line 1 -
-
- {{:helper.link(data.records.message1 + ' (set)', 'pencil', {'cartmenu' : "1", 'choice' : "Status",'statdisp' : "setmsg1"}, null, null)}} -
-
-
-
- Message line 2 -
-
- {{:helper.link(data.records.message2 + ' (set)', 'pencil', {'cartmenu' : "1", 'choice' : "Status",'statdisp' : "setmsg2"}, null, null)}} -
-
- -
-
-
- ALERT!: -
-
- {{:helper.link('None', 'alert', {'cartmenu' : "1", 'choice' : "Status",'statdisp' : "alert", 'alert' : "default"}, null, null)}} - {{:helper.link('Red Alert', 'alert', {'cartmenu' : "1", 'choice' : "Status",'statdisp' : "alert", 'alert' : "redalert"}, null, null)}} - {{:helper.link('Lockdown', 'caution', {'cartmenu' : "1", 'choice' : "Status",'statdisp' : "alert", 'alert' : "lockdown"}, null, null)}} - {{:helper.link('Biohazard', 'radiation', {'cartmenu' : "1", 'choice' : "Status",'statdisp' : "alert", 'alert' : "biohazard"}, null, null)}} -
-
- - {{else data.mode == 43}} -

Sensor Selection

-
- Available Sensors: -
- {{for data.records.power_sensors}} -
- {{:helper.link(value.name_tag, 'plus', {'cartmenu' : "1", 'choice' : "Power Select",'target' : value.name_tag})}}
-
- {{/for}} - - - {{else data.mode == 433}} -

Sensor Reading(Simplified View)

- {{if data.records.sensor_reading}} -
AreaCell %Load - {{for data.records.sensor_reading.apc_data}} -
{{:value.name}} - {{:value.cell_charge}}% - {{:value.total_load}} - {{empty}} -
No APCs found! - {{/for}} -
-
Available: {{:data.records.sensor_reading.total_avail}} -
Load: {{:data.records.sensor_reading.total_used_all}} - {{else}} - Unable to contact sensor controller! Please retry and contact tech support if problem persists. - {{/if}} - - {{else data.mode == 44}} -

Medical Record List

-
- Select A record -
-
- {{for data.records.medical_records}} -
- {{:helper.link(value.Name, 'gear', {'cartmenu' : "1", 'choice' : "Medical Records",'target' : value.ref}, null, null)}} -
- {{/for}} - - - {{else data.mode == 441}} -

Medical Record

-
-
-
- {{if data.records.general_exists == 1}} - Name: {{:data.records.general.name}}
- Entity Class: {{:data.records.general.brain_type}}
- Sex: {{:data.records.general.sex}}
- Species: {{:data.records.general.species}}
- Age: {{:data.records.general.age}}
- Rank: {{:data.records.general.rank}}
- Fingerprint: {{:data.records.general.fingerprint}}
- Physical Status: {{:data.records.general.p_stat}}
- Mental Status: {{:data.records.general.m_stat}}

- {{else}} - - General Record Lost!

-
- {{/if}} - {{if data.records.medical_exists == 1}} - Medical Data:
- Blood Type: {{:data.records.medical.b_type}}

- Minor Disabilities: {{:data.records.medical.mi_dis}}
- Details: {{:data.records.medical.mi_dis_d}}

- Major Disabilities: {{:data.records.medical.ma_dis}}
- Details: {{:data.records.medical.ma_dis_d}}

- Allergies: {{:data.records.medical.alg}}
- Details: {{:data.records.medical.alg_d}}

- Current Disease: {{:data.records.medical.cdi}}
- Details: {{:data.records.medical.alg_d}}

- Important Notes: {{:data.records.medical.notes}} - {{else}} - - Medical Record Lost! -
-
-
- {{/if}} -
-
-
- - - {{else data.mode == 45}} -

Security Record List

-
- Select A record -
-
- {{for data.records.security_records}} -
- {{:helper.link(value.Name, 'gear', {'cartmenu' : "1", 'choice' : "Security Records",'target' : value.ref}, null, null)}} -
- {{/for}} - - - {{else data.mode == 451}} -

Security Record

-
-
-
- {{if data.records.general_exists == 1}} - Name: {{:data.records.general.name}}
- Sex: {{:data.records.general.sex}}
- Species: {{:data.records.general.species}}
- Age: {{:data.records.general.age}}
- Rank: {{:data.records.general.rank}}
- Fingerprint: {{:data.records.general.fingerprint}}
- Physical Status: {{:data.records.general.p_stat}}
- Mental Status: {{:data.records.general.m_stat}}

- {{else}} - - General Record Lost!

-
- {{/if}} - {{if data.records.security_exists == 1}} - Security Data:
- Criminal Status: {{:data.records.security.criminal}}

- Minor Crimes: {{:data.records.security.mi_crim}}
- Details: {{:data.records.security.mi_crim_d}}

- Major Crimes: {{:data.records.security.ma_crim}}
- Details: {{:data.records.security.ma_crim_d}}

- Important Notes: {{:data.records.security.notes}} - {{else}} - - Security Record Lost!

-
- {{/if}} -
-
-
- - - {{else data.mode == 46}} -

Security Bot Control

- {{if data.records.beepsky.active == null || data.records.beepsky.active == 0}} - {{if data.records.beepsky.count == 0}} -

No bots found.

- {{else}} -
- Select A Bot. -
-
- {{for data.records.beepsky.bots}} -
- {{:helper.link(value.Name, 'gear', {'radiomenu' : "1", 'op' : "control",'bot' : value.ref}, null, null)}} (Location: {{:value.Location}}) -
- {{/for}} - {{/if}} -
- {{:helper.link('Scan for Bots','gear', {'radiomenu' : "1", 'op' : "scanbots"}, null, null)}} - {{else}} -

{{:data.records.beepsky.active}}

-

- {{if data.records.beepsky.botstatus.mode == -1}} -

Waiting for response...

- {{else}} -

Status:

-
-
-
- Location: -
-
- {{:data.records.beepsky.botstatus.loca}} -
-
-
-
- Mode: -
-
- - {{if data.records.beepsky.botstatus.mode ==0}} - Ready - {{else data.records.beepsky.botstatus.mode == 1}} - Apprehending target - {{else data.records.beepsky.botstatus.mode ==2 || data.records.beepsky.botstatus.mode == 3}} - Arresting target - {{else data.records.beepsky.botstatus.mode ==4}} - Starting patrol - {{else data.records.beepsky.botstatus.mode ==5}} - On Patrol - {{else data.records.beepsky.botstatus.mode ==6}} - Responding to summons - {{/if}} - -
-
-
- {{:helper.link('Stop Patrol', 'gear', {'radiomenu' : "1", 'op' : "stop"}, null, null)}} - {{:helper.link('Start Patrol', 'gear', {'radiomenu' : "1", 'op' : "go"}, null, null)}} - {{:helper.link('Summon Bot', 'gear', {'radiomenu' : "1", 'op' : "summon"}, null, null)}} -
- {{/if}} - {{:helper.link('Return to Bot list', 'gear', {'radiomenu' : "1", 'op' : "botlist"}, null, null)}} - {{/if}} - - - {{else data.mode == 47}} -

Supply Record Interlink

-
-
- Location: -
-
- - {{if data.records.supply.shuttle_moving}} - Moving to station ({{:data.records.supply.shuttle_eta}}) - {{else}} - Shuttle at {{:data.records.supply.shuttle_loc}} - {{/if}} - -
-
-
-
-
- Current Approved Orders
- {{if data.records.supply.approved_count == 0}} - No current approved orders

- {{else}} - {{for data.records.supply.approved}} - #{{:value.Number}} - {{:value.Name}} approved by {{:value.OrderedBy}}
{{if value.Comment != ""}} {{:value.Comment}}
{{/if}}
- {{/for}} - {{/if}} -

- Current Requested Orders
- {{if data.records.supply.requests_count == 0}} - No current requested orders

- {{else}} - {{for data.records.supply.requests}} - #{{:value.Number}} - {{:value.Name}} requested by {{:value.OrderedBy}}
{{if value.Comment != ""}} {{:value.Comment}}
{{/if}}
- {{/for}} - {{/if}} -
-
-
- - - {{else data.mode == 48}} -

Mule Control

- {{if data.records.mulebotcount == 0}} -

No bots found.

- {{else}} -

Mule List

- {{for data.records.mulebots}} -
-
Mulebot #{{:value.name}}
-
Location: {{:value.location}}
Home: {{:value.home}}
Target: {{:value.target}}
Load: {{:value.load}}
-
-
-
Status:
-
- {{if value.paused == 0}} - Nominal - {{else value.paused == 1}} - Paused - {{/if}} -
-
-
- {{:helper.link('Go home', null, {'cartmenu' : "1", 'choice' : "MULEbot", 'ref' : value.ref, 'command' : "Home"})}} - {{:helper.link('Set destination', null, {'cartmenu' : "1", 'choice' : "MULEbot", 'ref' : value.ref, 'command' : "SetD"})}} - {{:helper.link('Go', null, {'cartmenu' : "1", 'choice' : "MULEbot", 'ref' : value.ref, 'command' : "GoTD"})}} - {{:helper.link('Stop', null, {'cartmenu' : "1", 'choice' : "MULEbot", 'ref' : value.ref, 'command' : "Stop"})}} -
- {{/for}} - {{/if}} - - - {{else data.mode == 49}} -

Janatorial Supplies Locator

-
- Current Location: - {{if data.records.janitor.user_loc.x == 0}} - Unknown - {{else}} - {{:data.records.janitor.user_loc.x}} / {{:data.records.janitor.user_loc.y}} - {{/if}} -
-
- {{for data.records.janitor.mops}} - {{if value.x==0}} - Unable to locate Mop - {{else}} - Mop Location: - ({{:value.x}} / {{:value.y}}) - {{:value.dir}} - Status: {{:value.status}}
- {{/if}} - {{/for}} -
-
- {{for data.records.janitor.buckets}} - {{if value.x==0}} - Unable to locate Water Buckets - {{else}} - Water Buckets Location: - ({{:value.x}} / {{:value.y}}) - {{:value.dir}} - Water Level: {{:value.status}}
- {{/if}} - {{/for}} -
-
- {{for data.records.janitor.cleanbots}} - {{if value.x==0}} - Unable to locate Clean Bots - {{else}} - Clean Bots Location: - ({{:value.x}} / {{:value.y}}) - {{:value.dir}} - Status: {{:value.status}}
- {{/if}} - {{/for}} -
-
- {{for data.records.janitor.carts}} - {{if value.x==0}} - Unable to locate Janitorial Cart - {{else}} - Janitorial cart Location: - ({{:value.x}} / {{:value.y}}) - {{:value.dir}} - Status: {{:value.status}}
- {{/if}} - {{/for}} - - {{else data.mode == 6}} -

InstaNews ED 2.0.9

- -
-
- Functions: -
-
- {{:helper.link(data.news_silent==1 ? 'Ringer: Off' : 'Ringer: On', data.news_silent==1 ? 'volume-off' : 'volume-on', {'choice' : "Toggle News"}, null, 'fixedLeftWide')}} - {{:helper.link('Set news tone', 'comment', {'choice' : "Newstone"}, null, 'fixedLeftWide')}} -
-
- - {{if data.reception != 1}} - No reception with newscaster network. - {{/if}} - -
-
- {{for data.feedChannels}} - {{if value.censored}} - {{:helper.link(value.name, 'circle-arrow-s', {'choice' : "Select Feed", 'feed' : value.feed, 'name' : value.name } , null, 'fixedLeftWiderRed')}} - {{else}} - {{:helper.link(value.name, 'circle-arrow-s', {'choice' : "Select Feed", 'feed' : value.feed, 'name' : value.name } , null, 'fixedLeftWider')}} - {{/if}} - {{empty}} - No active channels found... - {{/for}} -
-
- - {{else data.mode == 61}} -

{{:data.feed.channel}}

- Created by: {{:data.feed.author}}
- - {{if data.reception != 1}} - No reception with newscaster network. - {{/if}} - -
-
-
- {{if data.feed.censored}} -

Attention

- This channel has been deemed as threatening to the welfare of the station, and marked with a Nanotrasen D-Notice.
- No further feed story additions are allowed while the D-Notice is in effect.
- {{else}} - {{for data.feed.messages}} - -{{:value.body}}
- {{if value.has_image}} -
- {{if value.caption}} - {{:value.caption}}
- {{/if}} - {{/if}} - [{{:value.message_type}} by {{:value.author}} - {{:value.time_stamp}}]
-
- {{empty}} - No feed messages found in channel... - {{/for}} - {{/if}} -
-
-
- - {{/if}} -{{else}} -
-






No Owner information found, please swipe ID -
-{{/if}} - diff --git a/nano/templates/uplink.tmpl b/nano/templates/uplink.tmpl deleted file mode 100644 index 0773883d703..00000000000 --- a/nano/templates/uplink.tmpl +++ /dev/null @@ -1,177 +0,0 @@ - - -{{:helper.syndicateMode()}} - - - -

{{:data.welcome}}

-
-
-
- Functions: -
-
-
- {{:helper.link('Request Items', 'gear', {'menu' : 0}, null, 'fixedLeftWider')}} - {{:helper.link('Exploitable Information', 'gear', {'menu' : 2}, null, 'fixedLeftWider')}} -
-
- {{:helper.link('Return', 'arrowreturn-1-w', {'return' : 1}, null, 'fixedLeft')}} - {{:helper.link('Close', 'gear', {'lock' : "1"}, null, 'fixedLeft')}} -
-
-
-
- -
-
- Tele-Crystals: -
-
- {{:data.crystals}} -
-
- - -{{if data.menu == 0}} - {{if data.discount_amount < 100}} -
-
- Currently discounted: -
-
- {{:data.discount_name}} - {{:data.discount_amount}}% off. Offer will expire at: {{:data.offer_expiry}} -
-
- {{/if}} - -
-
Categories
- {{for data.categories}} -
- {{:helper.link(value.name, 'gear', {'category' : value.ref}, (value.name == data.current_category) ? 'categoryActive' : '', 'category')}} -
- {{/for}} -
- -
-
- Request Items -
-
-
-
- {{for data.items}} -
-
- -
{{:value.cost}} points
-
-
- {{/for}} -
-
-
-
- -{{else data.menu == 2}} -

Information Record List:

-
-
- Select a Record -
-
- {{for data.exploit_records}} -
- {{:helper.link(value.Name, 'gear', {'menu' : 21, 'id' : value.id}, null, null)}} -
- {{/for}} -{{else data.menu == 21}} -

Information Record:

-
-
-
-
- {{if data.exploit_exists == 1}} - Name: {{:data.exploit.name}}
- Sex: {{:data.exploit.sex}}
- Species: {{:data.exploit.species}}
- Age: {{:data.exploit.age}}
- Rank: {{:data.exploit.rank}}
- Home System: {{:data.exploit.home_system}}
- Citizenship: {{:data.exploit.citizenship}}
- Faction: {{:data.exploit.faction}}
- Religion: {{:data.exploit.religion}}
- Fingerprint: {{:data.exploit.fingerprint}}
- Other Affiliation: {{:data.exploit.antagfaction}}
- -
Acquired Information:
- Notes:
{{:data.exploit.nanoui_exploit_record}}

- {{else}} - - No exploitative information acquired! -
-
-
- {{/if}} -
-
-
-{{/if}} diff --git a/tgui/packages/common/string.js b/tgui/packages/common/string.js index a3da0885a19..405693a0d2f 100644 --- a/tgui/packages/common/string.js +++ b/tgui/packages/common/string.js @@ -120,10 +120,11 @@ export const decodeHtmlEntities = str => { if (!str) { return str; } - const translate_re = /&(nbsp|amp|quot|lt|gt|apos|trade);/g; + const translate_re = /&(nbsp|amp|deg|quot|lt|gt|apos|trade);/g; const translate = { nbsp: ' ', amp: '&', + deg: '°', quot: '"', lt: '<', gt: '>', diff --git a/tgui/packages/tgui/index.js b/tgui/packages/tgui/index.js index 16720fa2a6e..462934fd245 100644 --- a/tgui/packages/tgui/index.js +++ b/tgui/packages/tgui/index.js @@ -24,6 +24,7 @@ import './styles/themes/hackerman.scss'; import './styles/themes/malfunction.scss'; import './styles/themes/ntos.scss'; import './styles/themes/paper.scss'; +import './styles/themes/pda-retro.scss'; import './styles/themes/retro.scss'; import './styles/themes/syndicate.scss'; diff --git a/tgui/packages/tgui/interfaces/Pda.js b/tgui/packages/tgui/interfaces/Pda.js new file mode 100644 index 00000000000..a1f52b283ac --- /dev/null +++ b/tgui/packages/tgui/interfaces/Pda.js @@ -0,0 +1,205 @@ +import { round } from 'common/math'; +import { Fragment } from 'inferno'; +import { useBackend, useLocalState } from "../backend"; +import { Box, Button, Flex, Icon, LabeledList, Modal, ProgressBar, Section } from "../components"; +import { Window } from "../layouts"; + +/* This is all basically stolen from routes.js. */ +import { routingError } from "../routes"; + +const requirePdaInterface = require.context('./pda', false, /\.js$/); + +const getPdaApp = name => { + let appModule; + try { + appModule = requirePdaInterface(`./${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 Pda = (props, context) => { + const { act, data } = useBackend(context); + + const { + app, + owner, + useRetro, + } = data; + + if (!owner) { + return ( + + +
+ Warning: No ID information found! Please swipe ID! +
+
+
+ ); + } + + let App = getPdaApp(app.template); + + const [settingsMode, setSettingsMode] = useLocalState(context, 'settingsMode', false); + + return ( + + + + {settingsMode && ( + + ) || ( +
+ + {app.name} + + } + p={1}> + +
+ )} + + +
+
+ ); +}; + +const PDAHeader = (props, context) => { + const { act, data } = useBackend(context); + + const { + settingsMode, + setSettingsMode, + } = props; + + const { + idInserted, + idLink, + cartridge_name, + stationTime, + } = data; + + return ( + + + {!!idInserted && ( + + + + + {body} + + ); +}; + +const MessengerList = (props, context) => { + const { act, data } = useBackend(context); + + const { + auto_scroll, + convopdas, + pdas, + charges, + plugins, + silent, + toff, + } = data; + + return ( + + + + + + + + + + {!toff && ( + + {!!charges && ( + + {charges} charges left. + + )} + {!convopdas.length && !pdas.length && ( + + No other PDAs located. + + ) || ( + + + + + )} + + ) || ( + + Messenger Offline. + + )} + + ); +}; + +const PDAList = (props, context) => { + const { act, data } = useBackend(context); + + const { + pdas, + title, + msgAct, + } = props; + + const { + charges, + plugins, + } = data; + + if (!pdas || !pdas.length) { + return ( +
+ No PDAs found. +
+ ); + } + + return ( +
+ {pdas.map(pda => ( + +
+ ); +}; \ No newline at end of file diff --git a/tgui/packages/tgui/interfaces/pda/pda_news.js b/tgui/packages/tgui/interfaces/pda/pda_news.js new file mode 100644 index 00000000000..85c2041c75b --- /dev/null +++ b/tgui/packages/tgui/interfaces/pda/pda_news.js @@ -0,0 +1,125 @@ +import { filter } from 'common/collections'; +import { decodeHtmlEntities, toTitleCase } from 'common/string'; +import { Fragment } from 'inferno'; +import { useBackend, useLocalState } from "../../backend"; +import { Box, Button, Flex, Icon, LabeledList, ProgressBar, Section } from "../../components"; + +// Stolen wholesale from communicators. TGUITODO: Merge PDA & Communicator shared code once both are in +/* News */ +export const pda_news = (props, context) => { + const { act, data } = useBackend(context); + + const { + feeds, + target_feed, + } = data; + + return ( + + {!feeds.length && ( + + Error: No newsfeeds available. Please try again later. + + ) || target_feed && ( + + ) || ( + + )} + + ); +}; + +const NewsTargetFeed = (props, context) => { + const { act, data } = useBackend(context); + + const { + target_feed, + } = data; + + return ( +
act("newsfeed", { newsfeed: null })} /> + }> + {target_feed.messages.length && target_feed.messages.map(message => ( +
+ - {decodeHtmlEntities(message.body)} + {!!message.img && ( + + + {decodeHtmlEntities(message.caption) || null} + + )} + + [{message.message_type} by {decodeHtmlEntities(message.author)} - {message.time_stamp}] + +
+ )) || ( + + No stories found in {target_feed.name}. + + )} +
+ ); +}; + +const NewsFeed = (props, context) => { + const { act, data } = useBackend(context); + + const { + feeds, + latest_news, + } = data; + + return ( + +
+ {latest_news.length && ( +
+ {latest_news.map(news => ( + +
+ {decodeHtmlEntities(news.channel)} +
+ - {decodeHtmlEntities(news.body)} + {!!news.img && ( + + [image omitted, view story for more details] + {news.caption || null} + + )} + + [{news.message_type} by {news.author} - {news.time_stamp}] + +
+ ))} +
+ ) || ( + + No recent stories found. + + )} +
+
+ {feeds.map(feed => ( +
+
+ ); +}; \ No newline at end of file diff --git a/tgui/packages/tgui/interfaces/pda/pda_notekeeper.js b/tgui/packages/tgui/interfaces/pda/pda_notekeeper.js new file mode 100644 index 00000000000..5accbfcb3b1 --- /dev/null +++ b/tgui/packages/tgui/interfaces/pda/pda_notekeeper.js @@ -0,0 +1,26 @@ +/* eslint react/no-danger: "off" */ +import { useBackend } from "../../backend"; +import { Box, Button, Flex, Icon, LabeledList, ProgressBar, Section } from "../../components"; + +export const pda_notekeeper = (props, context) => { + const { act, data } = useBackend(context); + + const { + note, + } = data; + + return ( + +
+ { /* As usual with dangerouslySetInnerHTML, + this notekeeper was designed to use HTML injection. + Fix when markdown is easier. */ } +
+
+