/obj/item/paicard name = "personal AI device" icon = 'icons/obj/pai.dmi' icon_state = "pai" item_state = "electronic" w_class = WEIGHT_CLASS_SMALL slot_flags = SLOT_BELT origin_tech = list(TECH_DATA = 2) var/list/installed_encryptionkeys = list() var/obj/item/radio/radio var/looking_for_personality = 0 var/mob/living/silicon/pai/pai var/move_delay = 0 light_power = 1 light_range = 1 light_color = COLOR_BRIGHT_GREEN /obj/item/paicard/relaymove(mob/living/user, direction) . = ..() if(user.stat || user.stunned) return if(istype(loc, /mob/living/bot)) if(world.time < move_delay) return var/mob/living/bot/B = loc move_delay = world.time + 1 SECOND if(!B.on) to_chat(pai, SPAN_WARNING("\The [B] isn't turned on!")) return step_towards(B, get_step(B, direction)) var/obj/item/rig/rig = src.get_rig() if(istype(rig)) rig.forced_move(direction, user) /obj/item/paicard/Initialize() . = ..() AddOverlays("pai_off") SSpai.all_pai_devices += src update_light() /obj/item/paicard/Destroy() SSpai.all_pai_devices -= src //Will stop people throwing friend pAIs into the singularity so they can respawn if(pai) pai.death(0) return ..() /obj/item/paicard/attackby(obj/item/attacking_item, mob/user) if(istype(attacking_item, /obj/item/card/id)) scan_ID(attacking_item, user) return TRUE else if(istype(attacking_item, /obj/item/encryptionkey)) if(length(installed_encryptionkeys) > 2) to_chat(user, SPAN_WARNING("\The [src] already has the full number of possible encryption keys installed!")) return TRUE var/obj/item/encryptionkey/EK = attacking_item var/added_channels = FALSE for(var/thing in (EK.channels | EK.additional_channels)) if(!radio.channels[thing]) added_channels = TRUE break if(added_channels) installed_encryptionkeys += EK user.drop_from_inventory(EK, src) user.visible_message("[user] slides \the [EK] into \the [src]'s encryption key slot.", SPAN_NOTICE("You slide \the [EK] into \the [src]'s encryption key slot, granting it access to the radio channels.")) recalculateChannels() if(pai) to_chat(pai, SPAN_NOTICE("You now have access to these radio channels: [english_list(radio.channels)].")) else to_chat(user, SPAN_WARNING("\The [src] would not gain any new channels from \the [EK].")) return TRUE else if(attacking_item.tool_behaviour == TOOL_SCREWDRIVER) if(!length(installed_encryptionkeys)) to_chat(user, SPAN_WARNING("There are no installed encryption keys to remove!")) return user.visible_message("[user] uses \the [attacking_item] to pop the encryption key[length(installed_encryptionkeys) > 1 ? "s" : ""] out of \the [src].", SPAN_NOTICE("You use \the [attacking_item] to pop the encryption key[length(installed_encryptionkeys) > 1 ? "s" : ""] out of \the [src].")) for(var/key in installed_encryptionkeys) var/obj/item/encryptionkey/EK = key EK.forceMove(get_turf(src)) installed_encryptionkeys -= EK recalculateChannels() return TRUE else if(istype(attacking_item, /obj/item/stack/nanopaste)) if(!pai) to_chat(user, SPAN_WARNING("You cannot repair a pAI device if there's no active pAI personality installed.")) return TRUE pai.attackby(attacking_item, user) /obj/item/paicard/proc/recalculateChannels() radio.channels = list("Common" = radio.FREQ_LISTENING, "Entertainment" = radio.FREQ_LISTENING) for(var/keyslot in installed_encryptionkeys) var/obj/item/encryptionkey/EK = keyslot for(var/ch_name in (EK.channels | EK.additional_channels)) radio.channels[ch_name] = radio.FREQ_LISTENING for(var/ch_name in radio.channels) if(!SSradio) sleep(30) // Waiting for the SSradio to be created. if(!SSradio) radio.name = "broken radio headset" return radio.secure_radio_connections[ch_name] = SSradio.add_object(radio, radiochannels[ch_name], RADIO_CHAT) //This proc is called when the user scans their ID on the pAI card. //It registers their ID and copies their access to the pai, allowing it to use airlocks the owner can //Scanning an ID replaces any previously stored access with the new set. //Only cards that match the imprinted DNA can be used, it's not a free Agent ID card. //Possible TODO in future, allow emagging a paicard to let it work like an agent ID, accumulating access from any ID /obj/item/paicard/proc/scan_ID(var/obj/item/card/id/card, var/mob/user) if (!pai) to_chat(user, SPAN_WARNING("Error: ID Registration failed. No pAI personality installed.")) playsound(src.loc, 'sound/machines/buzz-two.ogg', 20, 0) return 0 if (!pai.master_dna) to_chat(user, SPAN_WARNING("Error: ID Registration failed. User not registered as owner. Please complete imprinting process first.")) playsound(src.loc, 'sound/machines/buzz-two.ogg', 20, 0) return 0 if (pai.master_dna != card.dna_hash) to_chat(user, SPAN_WARNING("Error: ID Registration failed. Biometric data on ID card does not match DNA sample of registered owner.")) playsound(src.loc, 'sound/machines/buzz-two.ogg', 20, 0) return 0 pai.id_card.access.Cut() pai.id_card.access = card.access.Copy() pai.id_card.registered_name = card.registered_name playsound(src.loc, 'sound/machines/ping.ogg', 50, 0) to_chat(user, SPAN_NOTICE("ID Registration for [pai.id_card.registered_name] is a success. PAI access updated!")) return 1 /obj/item/paicard/proc/ID_readout() if (pai.id_card && pai.id_card.registered_name) return SPAN_NOTICE("Identity of owner: [pai.id_card.registered_name] registered.") else return SPAN_WARNING("No ID card registered! Please scan your ID to share access.") /obj/item/paicard/attack_self(mob/user) if (!in_range(src, user)) return user.set_machine(src) var/dat = {" "} if(pai) dat += {" Personal AI Device

Installed Personality: [pai.name]
Prime directive: [pai.pai_law0]
Additional directives: [pai.pai_laws]
ID: [ID_readout()]

"} dat += {"
Configure Directives
"} if(pai && (!pai.master_dna || !pai.master)) dat += {"
Imprint Master DNA
"} dat += "
" if(radio) dat += "Radio Uplink" dat += {"
Transmit: [radio.get_broadcasting() ? "En" : "Dis" ]abled
Receive: [radio.get_listening() ? "En" : "Dis" ]abled

"} else // dat += "Radio Uplink
" dat += "Radio firmware not loaded. Please install a pAI personality to load firmware.
" dat += {"
Wipe current pAI personality
"} else if(looking_for_personality) dat += {" pAI Request Module

Requesting AI personalities from central database... If there are no entries, or if a suitable entry is not listed, check again later as more personalities may be added.

Searching for personalities

Refresh available personalities

"} else dat += {" pAI Request Module

No personality is installed.

Request personality

Each time this button is pressed, a request will be sent out to any available personalities. Check back often give plenty of time for personalities to respond. This process could take anywhere from 15 seconds to several minutes, depending on the available personalities' timelines.

"} user << browse(dat, "window=paicard") onclose(user, "paicard") return /obj/item/paicard/Topic(href, href_list) if(!usr || usr.stat) return if(href_list["setdna"]) if(pai.master_dna) return var/mob/M = usr if(!istype(M, /mob/living/carbon)) to_chat(usr, "You don't have any DNA, or your DNA is incompatible with this device.") else var/datum/dna/dna = usr.dna pai.master = M.real_name pai.master_dna = dna.unique_enzymes to_chat(pai, "

You have been bound to a new master.

") if(href_list["request"]) src.looking_for_personality = 1 SSpai.findPAI(src, usr) if(href_list["wipe"]) var/confirm = tgui_alert(usr, "Are you CERTAIN you wish to delete the current personality? This action cannot be undone.", "Personality Wipe", list("Yes", "No")) if(confirm == "Yes") for(var/mob/M in src) to_chat(M, "

You feel yourself slipping away from reality.

") sleep(30) to_chat(M, "

Byte by byte you lose your sense of self.

") sleep(20) to_chat(M, "

Your mental faculties leave you.

") sleep(30) to_chat(M, "
oblivion...
") M.death(0) removePersonality() if(href_list["wires"]) var/t1 = text2num(href_list["wires"]) switch(t1) if(4) radio.set_broadcasting(!radio.get_broadcasting()) if(2) radio.set_listening(!radio.get_listening()) if(href_list["setlaws"]) var/newlaws = sanitize(input("Enter any additional directives you would like your pAI personality to follow. Note that these directives will not override the personality's allegiance to its imprinted master. Conflicting directives will be ignored.", "pAI Directive Configuration", pai.pai_laws) as message) if(newlaws) pai.pai_laws = newlaws to_chat(pai, "Your supplemental directives have been updated. Your new directives are:") to_chat(pai, "Prime Directive:
[pai.pai_law0]") to_chat(pai, "Supplemental Directives:
[pai.pai_laws]") attack_self(usr) // WIRE_SIGNAL = 1 // WIRE_RECEIVE = 2 // WIRE_TRANSMIT = 4 /obj/item/paicard/proc/setPersonality(mob/living/silicon/pai/personality) src.pai = personality AddOverlays("pai-happy") playsound(src, 'sound/effects/pai/pai_restore.ogg', 75) /obj/item/paicard/proc/removePersonality() src.pai = null ClearOverlays() AddOverlays("pai-off") /obj/item/paicard var/current_emotion = 1 /obj/item/paicard/proc/setEmotion(var/emotion) if(pai) ClearOverlays() var/new_state switch(emotion) if(1) new_state = "pai-happy" if(2) new_state = "pai-cat" if(3) new_state = "pai-extremely-happy" if(4) new_state = "pai-face" if(5) new_state = "pai-laugh" if(6) new_state = "pai-off" if(7) new_state = "pai-sad" if(8) new_state = "pai-angry" if(9) new_state = "pai-what" if(10) new_state = "pai-neutral" if(11) new_state = "pai-silly" if(12) new_state = "pai-nose" if(13) new_state = "pai-smirk" if(14) new_state = "pai-exclamation" if(15) new_state = "pai-question" if (new_state) AddOverlays(new_state) current_emotion = emotion /obj/item/paicard/proc/alertUpdate() var/turf/T = get_turf_or_move(src.loc) for (var/mob/M in viewers(T)) M.show_message(SPAN_NOTICE("\The [src] flashes a message across its screen, \"Additional personalities available for download.\""), 3, SPAN_NOTICE("\The [src] bleeps electronically."), 2) /obj/item/paicard/emp_act(severity) . = ..() for(var/mob/M in src) M.emp_act(severity) /obj/item/paicard/ex_act(severity) if(pai) pai.ex_act(severity) else qdel(src) /obj/item/paicard/see_emote(mob/living/M, text) if(pai && pai.client && !pai.canmove) var/rendered = span("message", "[text]") pai.show_message(rendered, 2) ..() /obj/item/paicard/dropped(mob/user) . = ..() ///When an object is put into a container, drop fires twice. //once with it on the floor, and then once in the container //We only care about the second one if (istype(loc, /obj/item/storage)) //The second drop reads the container its placed into as the location update_location() /obj/item/paicard/equipped(var/mob/user, var/slot) ..() update_location(slot) /obj/item/paicard/proc/update_location(var/slotnumber = null) if (!pai) return if (!slotnumber) if (istype(loc, /mob)) slotnumber = get_equip_slot() report_onmob_location(1, slotnumber, pai) /obj/item/paicard/show_message(msg, type, alt, alt_type) if(pai && pai.client) var/rendered = span("message", "[msg]") pai.show_message(rendered, type) ..()