// Communicators // // Allows ghosts to roleplay with crewmembers without having to commit to joining the round, and also allows communications between two communicators. var/global/list/obj/item/device/communicator/all_communicators = list() /obj/item/device/communicator name = "communicator" desc = "A personal device used to enable long range dialog between two people, utilizing existing telecommunications infrastructure to allow \ communications across different stations, planets, or even star systems." icon = 'icons/obj/device.dmi' icon_state = "communicator" w_class = 2.0 slot_flags = SLOT_ID | SLOT_BELT show_messages = 1 origin_tech = list(TECH_ENGINEERING = 2, TECH_MAGNET = 2, TECH_BLUESPACE = 2, TECH_DATA = 2) matter = list(DEFAULT_WALL_MATERIAL = 30,"glass" = 10) var/video_range = 4 var/obj/machinery/camera/communicator/video_source // Their camera var/obj/machinery/camera/communicator/camera // Our camera var/list/voice_mobs = list() var/list/voice_requests = list() var/list/voice_invites = list() var/list/im_contacts = list() var/list/im_list = list() var/note = "Thank you for choosing the T-14.2 Communicator, this is your notepad!" //Current note in the notepad function var/notehtml = "" var/obj/item/weapon/cartridge/cartridge = null //current cartridge var/list/modules = list( list("module" = "Phone", "icon" = "phone64", "number" = 2), list("module" = "Contacts", "icon" = "person64", "number" = 3), list("module" = "Messaging", "icon" = "comment64", "number" = 4), list("module" = "Note", "icon" = "note64", "number" = 5), list("module" = "Settings", "icon" = "gear64", "number" = 6) ) //list("module" = "Name of Module", "icon" = "icon name64", "number" = "what tab is the module") var/selected_tab = 1 var/owner = "" var/occupation = "" var/alert_called = 0 var/obj/machinery/exonet_node/node = null //Reference to the Exonet node, to avoid having to look it up so often. var/target_address = "" var/target_address_name = "" var/network_visibility = 1 var/ringer = 1 var/list/known_devices = list() var/datum/exonet_protocol/exonet = null var/list/communicating = list() var/update_ticks = 0 // Proc: New() // Parameters: None // Description: Adds the new communicator to the global list of all communicators, sorts the list, obtains a reference to the Exonet node, then tries to // assign the device to the holder's name automatically in a spectacularly shitty way. /obj/item/device/communicator/New() ..() all_communicators += src all_communicators = sortAtom(all_communicators) node = get_exonet_node() processing_objects |= src camera = new(src) camera.name = "[src] #[rand(100,999)]" camera.c_tag = camera.name //This is a pretty terrible way of doing this. spawn(5 SECONDS) //Wait for our mob to finish spawning. if(ismob(loc)) register_device(loc) initialize_exonet(loc) else if(istype(loc, /obj/item/weapon/storage)) var/obj/item/weapon/storage/S = loc if(ismob(S.loc)) register_device(S.loc) initialize_exonet(S.loc) // Proc: examine() // Parameters: user - the user doing the examining // Description: Allows the user to click a link when examining to look at video if one is going. /obj/item/device/communicator/examine(mob/user) . = ..(user, 1) if(. && video_source) user << "It looks like it's on a video call: \[view\]" // Proc: initialize_exonet() // Parameters: 1 (user - the person the communicator belongs to) // Description: Sets up the exonet datum, gives the device an address, and then gets a node reference. Afterwards, populates the device // list. /obj/item/device/communicator/proc/initialize_exonet(mob/user) if(!user || !istype(user, /mob/living)) return if(!exonet) exonet = new(src) if(!exonet.address) exonet.make_address("communicator-[user.client]-[user.name]") if(!node) node = get_exonet_node() populate_known_devices() // Proc: examine() // Parameters: 1 (user - the person examining the device) // Description: Shows all the voice mobs inside the device, and their status. /obj/item/device/communicator/examine(mob/user) if(!..(user)) return var/msg = "" for(var/mob/living/voice/voice in contents) msg += "On the screen, you can see a image feed of [voice].\n" msg += "" if(voice && voice.key) switch(voice.stat) if(CONSCIOUS) if(!voice.client) msg += "[voice] appears to be asleep.\n" //afk if(UNCONSCIOUS) msg += "[voice] doesn't appear to be conscious.\n" if(DEAD) msg += "[voice] appears to have died...\n" //Hopefully this never has to be used. else msg += "The device doesn't appear to be transmitting any data.\n" msg += "" user << msg return // Proc: emp_act() // Parameters: None // Description: Drops all calls when EMPed, so the holder can then get murdered by the antagonist. /obj/item/device/communicator/emp_act() close_connection(reason = "Hardware error de%#_^@%-BZZZZZZZT") // Proc: add_to_EPv2() // Parameters: 1 (hex - a single hexadecimal character) // Description: Called when someone is manually dialing with nanoUI. Adds colons when appropiate. /obj/item/device/communicator/proc/add_to_EPv2(var/hex) var/length = length(target_address) if(length >= 24) return if(length == 4 || length == 9 || length == 14 || length == 19 || length == 24 || length == 29) target_address += ":[hex]" return target_address += hex // Proc: populate_known_devices() // Parameters: 1 (user - the person using the device) // Description: Searches all communicators and ghosts in the world, and adds them to the known_devices list if they are 'visible'. /obj/item/device/communicator/proc/populate_known_devices(mob/user) if(!exonet) exonet = new(src) src.known_devices.Cut() if(!get_connection_to_tcomms()) //If the network's down, we can't see anything. return for(var/obj/item/device/communicator/comm in all_communicators) if(!comm || !comm.exonet || !comm.exonet.address || comm.exonet.address == src.exonet.address) //Don't add addressless devices, and don't add ourselves. continue src.known_devices |= comm for(var/mob/observer/dead/O in dead_mob_list) if(!O.client || O.client.prefs.communicator_visibility == 0) continue src.known_devices |= O // Proc: get_connection_to_tcomms() // Parameters: None // Description: Simple check to see if the exonet node is active. /obj/item/device/communicator/proc/get_connection_to_tcomms() if(node) if(node.on && node.allow_external_communicators) return 1 return 0 // Proc: process() // Parameters: None // Description: Ticks the update_ticks variable, and checks to see if it needs to disconnect communicators every five ticks.. /obj/item/device/communicator/process() update_ticks++ if(update_ticks % 5) if(!node) node = get_exonet_node() if(!node || !node.on || !node.allow_external_communicators) close_connection(reason = "Connection timed out") // Proc: attackby() // Parameters: 2 (C - what is used on the communicator. user - the mob that has the communicator) // Description: When an ID is swiped on the communicator, the communicator reads the job and checks it against the Owner name, if success, the occupation is added. /obj/item/device/communicator/attackby(obj/item/C as obj, mob/user as mob) if(istype(C, /obj/item/weapon/card/id)) var/obj/item/weapon/card/id/idcard = C if(!idcard.registered_name || !idcard.assignment) user << "\The [src] rejects the ID." return if(!owner) user << "\The [src] rejects the ID." return if(owner == idcard.registered_name) occupation = idcard.assignment user << "Occupation updated." return else return // Proc: attack_self() // Parameters: 1 (user - the mob that clicked the device in their hand) // Description: Makes an exonet datum if one does not exist, allocates an address for it, maintains the lists of all devies, clears the alert icon, and // finally makes NanoUI appear. /obj/item/device/communicator/attack_self(mob/user) initialize_exonet(user) alert_called = 0 update_icon() ui_interact(user) // Proc: attack_ghost() // Parameters: 1 (user - the ghost clicking on the device) // Description: Recreates the known_devices list, so that the ghost looking at the device can see themselves, then calls ..() so that NanoUI appears. /obj/item/device/communicator/attack_ghost(mob/user) populate_known_devices() //Update the devices so ghosts can see the list on NanoUI. ..() /mob/observer/dead var/datum/exonet_protocol/exonet = null var/list/exonet_messages = list() // Proc: New() // Parameters: None // Description: Gives ghosts an exonet address based on their key and ghost name. /mob/observer/dead/New() . = ..() spawn(20) exonet = new(src) if(client) exonet.make_address("communicator-[src.client]-[src.client.prefs.real_name]") else exonet.make_address("communicator-[key]-[src.real_name]") // Proc: Destroy() // Parameters: None // Description: Removes the ghost's address and nulls the exonet datum, to allow qdel()ing. /mob/observer/dead/Destroy() . = ..() if(exonet) exonet.remove_address() exonet = null ..() // Proc: ui_interact() // Parameters: 4 (standard NanoUI arguments) // Description: Uses a bunch of for loops to turn lists into lists of lists, so they can be displayed in nanoUI, then displays various buttons to the user. /obj/item/device/communicator/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) // this is the data which will be sent to the ui var/data[0] //General nanoUI information var/communicators[0] //List of communicators var/invites[0] //Communicators and ghosts we've invited to our communicator. var/requests[0] //Communicators and ghosts wanting to go in our communicator. var/voices[0] //Current /mob/living/voice s inside the device. var/connected_communicators[0] //Current communicators connected to the device. var/im_contacts_ui[0] //List of communicators that have been messaged. var/im_list_ui[0] //List of messages. var/modules_ui[0] //Home screen info. //First we add other 'local' communicators. for(var/obj/item/device/communicator/comm in known_devices) if(comm.network_visibility && comm.exonet) communicators[++communicators.len] = list("name" = sanitize(comm.name), "address" = comm.exonet.address) //Now for ghosts who we pretend have communicators. for(var/mob/observer/dead/O in known_devices) if(O.client && O.client.prefs.communicator_visibility == 1 && O.exonet) communicators[++communicators.len] = list("name" = sanitize("[O.client.prefs.real_name]'s communicator"), "address" = O.exonet.address, "ref" = "\ref[O]") //Lists all the other communicators that we invited. for(var/obj/item/device/communicator/comm in voice_invites) if(comm.exonet) invites[++invites.len] = list("name" = sanitize(comm.name), "address" = comm.exonet.address, "ref" = "\ref[comm]") //Ghosts we invited. for(var/mob/observer/dead/O in voice_invites) if(O.exonet && O.client) invites[++invites.len] = list("name" = sanitize("[O.client.prefs.real_name]'s communicator"), "address" = O.exonet.address, "ref" = "\ref[O]") //Communicators that want to talk to us. for(var/obj/item/device/communicator/comm in voice_requests) if(comm.exonet) requests[++requests.len] = list("name" = sanitize(comm.name), "address" = comm.exonet.address, "ref" = "\ref[comm]") //Ghosts that want to talk to us. for(var/mob/observer/dead/O in voice_requests) if(O.exonet && O.client) requests[++requests.len] = list("name" = sanitize("[O.client.prefs.real_name]'s communicator"), "address" = O.exonet.address, "ref" = "\ref[O]") //Now for all the voice mobs inside the communicator. for(var/mob/living/voice/voice in contents) voices[++voices.len] = list("name" = sanitize("[voice.name]'s communicator"), "true_name" = sanitize(voice.name)) //Finally, all the communicators linked to this one. for(var/obj/item/device/communicator/comm in communicating) connected_communicators[++connected_communicators.len] = list("name" = sanitize(comm.name), "true_name" = sanitize(comm.name), "ref" = "\ref[comm]") //Devices that have been messaged or recieved messages from. for(var/obj/item/device/communicator/comm in im_contacts) if(comm.exonet) im_contacts_ui[++im_contacts_ui.len] = list("name" = sanitize(comm.name), "address" = comm.exonet.address, "ref" = "\ref[comm]") for(var/mob/observer/dead/ghost in im_contacts) if(ghost.exonet) im_contacts_ui[++im_contacts_ui.len] = list("name" = sanitize(ghost.name), "address" = ghost.exonet.address, "ref" = "\ref[ghost]") //Actual messages. for(var/I in im_list) im_list_ui[++im_list_ui.len] = list("address" = I["address"], "to_address" = I["to_address"], "im" = I["im"]) //Modules for homescreen. for(var/list/R in modules) modules_ui[++modules_ui.len] = R data["owner"] = owner ? owner : "Unset" data["occupation"] = occupation ? occupation : "Swipe ID to set." data["connectionStatus"] = get_connection_to_tcomms() data["visible"] = network_visibility data["address"] = exonet.address ? exonet.address : "Unallocated" data["targetAddress"] = target_address data["targetAddressName"] = target_address_name data["currentTab"] = selected_tab data["knownDevices"] = communicators data["invitesSent"] = invites data["requestsReceived"] = requests data["voice_mobs"] = voices data["communicating"] = connected_communicators data["video_comm"] = video_source ? "\ref[video_source.loc]" : null data["imContacts"] = im_contacts_ui data["imList"] = im_list_ui data["time"] = worldtime2text() data["ring"] = ringer data["homeScreen"] = modules_ui data["note"] = note // current notes // update the ui if it exists, returns null if no ui is passed/found ui = nanomanager.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, "communicator.tmpl", "Communicator", 475, 700) // 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 five Master Controller tick ui.set_auto_update(5) // Proc: Topic() // Parameters: 2 (standard Topic arguments) // Description: Responds to NanoUI button presses. /obj/item/device/communicator/Topic(href, href_list) if(..()) return 1 if(href_list["rename"]) var/new_name = sanitizeSafe(input(usr,"Please enter your name.","Communicator",usr.name) ) if(new_name) owner = new_name name = "[owner]'s [initial(name)]" if(camera) camera.name = name camera.c_tag = name if(href_list["toggle_visibility"]) switch(network_visibility) if(1) //Visible, becoming invisbile network_visibility = 0 if(camera) camera.remove_network(NETWORK_COMMUNICATORS) if(0) //Invisible, becoming visible network_visibility = 1 if(camera) camera.add_network(NETWORK_COMMUNICATORS) if(href_list["toggle_ringer"]) ringer = !ringer if(href_list["add_hex"]) var/hex = href_list["add_hex"] add_to_EPv2(hex) if(href_list["write_target_address"]) var/new_address = sanitizeSafe(input(usr,"Please enter the desired target EPv2 address. Note that you must write the colons \ yourself.","Communicator",src.target_address) ) if(new_address) target_address = new_address if(href_list["clear_target_address"]) target_address = "" if(href_list["dial"]) if(!get_connection_to_tcomms()) usr << "Error: Cannot connect to Exonet node." return var/their_address = href_list["dial"] exonet.send_message(their_address, "voice") if(href_list["decline"]) var/ref_to_remove = href_list["decline"] var/atom/decline = locate(ref_to_remove) if(decline) del_request(decline) if(href_list["message"]) if(!get_connection_to_tcomms()) usr << "Error: Cannot connect to Exonet node." return var/their_address = href_list["message"] var/text = sanitizeSafe(input(usr,"Enter your message.","Text Message")) if(text) exonet.send_message(their_address, "text", text) im_list += list(list("address" = exonet.address, "to_address" = their_address, "im" = text)) log_pda("[usr] (COMM: [src]) sent \"[text]\" to [exonet.get_atom_from_address(their_address)]") if(href_list["disconnect"]) var/name_to_disconnect = href_list["disconnect"] for(var/mob/living/voice/V in contents) if(name_to_disconnect == V.name) close_connection(usr, V, "[usr] hung up") for(var/obj/item/device/communicator/comm in communicating) if(name_to_disconnect == comm.name) close_connection(usr, comm, "[usr] hung up") if(href_list["startvideo"]) var/ref_to_video = href_list["startvideo"] var/obj/item/device/communicator/comm = locate(ref_to_video) if(comm) connect_video(usr, comm) if(href_list["endvideo"]) if(video_source) end_video() if(href_list["watchvideo"]) if(video_source) watch_video(usr,video_source.loc) if(href_list["copy"]) target_address = href_list["copy"] if(href_list["copy_name"]) target_address_name = href_list["copy_name"] if(href_list["hang_up"]) for(var/mob/living/voice/V in contents) close_connection(usr, V, "[usr] hung up") for(var/obj/item/device/communicator/comm in communicating) close_connection(usr, comm, "[usr] hung up") if(href_list["switch_tab"]) selected_tab = href_list["switch_tab"] if(href_list["edit"]) var/n = input(usr, "Please enter message", name, notehtml) n = sanitizeSafe(n, extra = 0) if(n) note = html_decode(n) notehtml = note note = replacetext(note, "\n", "
") else note = "" notehtml = note nanomanager.update_uis(src) add_fingerprint(usr) // Proc: receive_exonet_message() // Parameters: 4 (origin atom - the source of the message's holder, origin_address - where the message came from, message - the message received, // text - message text to send if message is of type "text") // Description: Handles voice requests and invite messages originating from both real communicators and ghosts. Also includes a ping response and IM function. /obj/item/device/communicator/receive_exonet_message(var/atom/origin_atom, origin_address, message, text) if(message == "voice") if(isobserver(origin_atom) || istype(origin_atom, /obj/item/device/communicator)) if(origin_atom in voice_invites) var/user = null if(ismob(origin_atom.loc)) user = origin_atom.loc open_connection(user, origin_atom) return else if(origin_atom in voice_requests) return //Spam prevention else request(origin_atom) if(message == "ping") if(network_visibility) var/random = rand(200,350) random = random / 10 exonet.send_message(origin_address, "64 bytes received from [exonet.address] ecmp_seq=1 ttl=51 time=[random] ms") if(message == "text") request_im(origin_atom, origin_address, text) return // Proc: receive_exonet_message() // Parameters: 3 (origin atom - the source of the message's holder, origin_address - where the message came from, message - the message received) // Description: Handles voice requests and invite messages originating from both real communicators and ghosts. Also includes a ping response. /mob/observer/dead/receive_exonet_message(origin_atom, origin_address, message, text) if(message == "voice") if(istype(origin_atom, /obj/item/device/communicator)) var/obj/item/device/communicator/comm = origin_atom if(src in comm.voice_invites) comm.open_connection(src) return src << "\icon[origin_atom] Receiving communicator request from [origin_atom]. To answer, use the Call Communicator \ verb, and select that name to answer the call." comm.voice_invites |= src if(message == "ping") if(client && client.prefs.communicator_visibility) var/random = rand(450,700) random = random / 10 exonet.send_message(origin_address, "64 bytes received from [exonet.address] ecmp_seq=1 ttl=51 time=[random] ms") if(message == "text") src << "\icon[origin_atom] Received text message from [origin_atom]: \"[text]\"" exonet_messages.Add("From [origin_atom]:
[text]") return // Proc: register_device() // Parameters: 1 (user - the person to use their name for) // Description: Updates the owner's name and the device's name. /obj/item/device/communicator/proc/register_device(mob/user) if(!user) return owner = user.name name = "[owner]'s [initial(name)]" if(camera) camera.name = name camera.c_tag = name // Proc: add_communicating() // Parameters: 1 (comm - the communicator to add to communicating) // Description: Used when this communicator gets a new communicator to relay say/me messages to /obj/item/device/communicator/proc/add_communicating(obj/item/device/communicator/comm) if(!comm || !istype(comm)) return communicating |= comm listening_objects |= src update_icon() // Proc: del_communicating() // Parameters: 1 (comm - the communicator to remove from communicating) // Description: Used when this communicator is being asked to stop relaying say/me messages to another /obj/item/device/communicator/proc/del_communicating(obj/item/device/communicator/comm) if(!comm || !istype(comm)) return communicating.Remove(comm) update_icon() // Proc: open_connection() // Parameters: 2 (user - the person who initiated the connecting being opened, candidate - the communicator or observer that will connect to the device) // Description: Typechecks the candidate, then calls the correct proc for further connecting. /obj/item/device/communicator/proc/open_connection(mob/user, var/atom/candidate) if(isobserver(candidate)) voice_invites.Remove(candidate) open_connection_to_ghost(user, candidate) else if(istype(candidate, /obj/item/device/communicator)) open_connection_to_communicator(user, candidate) // Proc: open_connection_to_communicator() // Parameters: 2 (user - the person who initiated this and will be receiving feedback information, candidate - someone else's communicator) // Description: Adds the candidate and src to each other's communicating lists, allowing messages seen by the devices to be relayed. /obj/item/device/communicator/proc/open_connection_to_communicator(mob/user, var/atom/candidate) if(!istype(candidate, /obj/item/device/communicator)) return var/obj/item/device/communicator/comm = candidate voice_invites.Remove(candidate) comm.voice_requests.Remove(src) if(user) comm.visible_message("\icon[src] Connecting to [src].") user << "\icon[src] Attempting to call [comm]." sleep(10) user << "\icon[src] Dialing internally from [station_name()], Kara Subsystem, [system_name()]." sleep(20) //If they don't have an exonet something is very wrong and we want a runtime. user << "\icon[src] Connection re-routed to [comm] at [comm.exonet.address]." sleep(40) user << "\icon[src] Connection to [comm] at [comm.exonet.address] established." comm.visible_message("\icon[src] Connection to [src] at [exonet.address] established.") sleep(20) src.add_communicating(comm) comm.add_communicating(src) // Proc: open_connection_to_ghost() // Parameters: 2 (user - the person who initiated this, candidate - the ghost that will be turned into a voice mob) // Description: Pulls the candidate ghost from deadchat, makes a new voice mob, transfers their identity, then their client. /obj/item/device/communicator/proc/open_connection_to_ghost(mob/user, var/mob/candidate) if(!isobserver(candidate)) return //Handle moving the ghost into the new shell. announce_ghost_joinleave(candidate, 0, "They are occupying a personal communications device now.") voice_requests.Remove(candidate) voice_invites.Remove(candidate) var/mob/living/voice/new_voice = new /mob/living/voice(src) //Make the voice mob the ghost is going to be. new_voice.transfer_identity(candidate) //Now make the voice mob load from the ghost's active character in preferences. //Do some simple logging since this is a tad risky as a concept. var/msg = "[candidate && candidate.client ? "[candidate.client.key]" : "*no key*"] ([candidate]) has entered [src], triggered by \ [user && user.client ? "[user.client.key]" : "*no key*"] ([user ? "[user]" : "*null*"]) at [x],[y],[z]. They have joined as [new_voice.name]." message_admins(msg) log_game(msg) new_voice.mind = candidate.mind //Transfer the mind, if any. new_voice.ckey = candidate.ckey //Finally, bring the client over. voice_mobs.Add(new_voice) listening_objects |= src var/obj/screen/blackness = new() //Makes a black screen, so the candidate can't see what's going on before actually 'connecting' to the communicator. blackness.screen_loc = ui_entire_screen blackness.icon = 'icons/effects/effects.dmi' blackness.icon_state = "1" blackness.mouse_opacity = 2 //Can't see anything! new_voice.client.screen.Add(blackness) update_icon() //Now for some connection fluff. if(user) user << "\icon[src] Connecting to [candidate]." new_voice << "\icon[src] Attempting to call [src]." sleep(10) new_voice << "\icon[src] Dialing to [station_name()], Kara Subsystem, [system_name()]." sleep(20) new_voice << "\icon[src] Connecting to [station_name()] telecommunications array." sleep(40) new_voice << "\icon[src] Connection to [station_name()] telecommunications array established. Redirecting signal to [src]." sleep(20) //We're connected, no need to hide everything. new_voice.client.screen.Remove(blackness) qdel(blackness) new_voice << "\icon[src] Connection to [src] established." new_voice << "To talk to the person on the other end of the call, just talk normally." new_voice << "If you want to end the call, use the 'Hang Up' verb. The other person can also hang up at any time." new_voice << "Remember, your character does not know anything you've learned from observing!" if(new_voice.mind) new_voice.mind.assigned_role = "Disembodied Voice" if(user) user << "\icon[src] Your communicator is now connected to [candidate]'s communicator." // Proc: close_connection() // Parameters: 3 (user - the user who initiated the disconnect, target - the mob or device being disconnected, reason - string shown when disconnected) // Description: Deletes specific voice_mobs or disconnects communicators, and shows a message to everyone when doing so. If target is null, all communicators // and voice mobs are removed. /obj/item/device/communicator/proc/close_connection(mob/user, var/atom/target, var/reason) if(voice_mobs.len == 0 && communicating.len == 0) return for(var/mob/living/voice/voice in voice_mobs) //Handle ghost-callers if(target && voice != target) //If no target is inputted, it deletes all of them. continue voice << "\icon[src] [reason]." visible_message("\icon[src] [reason].") voice_mobs.Remove(voice) qdel(voice) update_icon() for(var/obj/item/device/communicator/comm in communicating) //Now we handle real communicators. if(target && comm != target) continue src.del_communicating(comm) comm.del_communicating(src) comm.visible_message("\icon[src] [reason].") visible_message("\icon[src] [reason].") if(comm.camera && video_source == comm.camera) //We hung up on the person on video end_video() if(camera && comm.video_source == camera) //We hung up on them while they were watching us comm.end_video() if(voice_mobs.len == 0 && communicating.len == 0) listening_objects.Remove(src) // Proc: request() // Parameters: 1 (candidate - the ghost or communicator wanting to call the device) // Description: Response to a communicator or observer trying to call the device. Adds them to the list of requesters /obj/item/device/communicator/proc/request(var/atom/candidate) if(candidate in voice_requests) return var/who = null if(isobserver(candidate)) who = candidate.name else if(istype(candidate, /obj/item/device/communicator)) var/obj/item/device/communicator/comm = candidate who = comm.owner comm.voice_invites |= src if(!who) return voice_requests |= candidate if(ringer) playsound(loc, 'sound/machines/twobeep.ogg', 50, 1) for (var/mob/O in hearers(2, loc)) O.show_message(text("\icon[src] *beep*")) alert_called = 1 update_icon() //Search for holder of the device. var/mob/living/L = null if(loc && isliving(loc)) L = loc if(L) L << "\icon[src] Communications request from [who]." // Proc: del_request() // Parameters: 1 (candidate - the ghost or communicator to be declined) // Description: Declines a request and cleans up both ends /obj/item/device/communicator/proc/del_request(var/atom/candidate) if(!(candidate in voice_requests)) return if(isobserver(candidate)) candidate << "Your communicator call request was declined." else if(istype(candidate, /obj/item/device/communicator)) var/obj/item/device/communicator/comm = candidate comm.voice_invites -= src voice_requests -= candidate //Search for holder of our device. var/mob/living/us = null if(loc && isliving(loc)) us = loc if(us) us << "\icon[src] Declined request." // Proc: request_im() // Parameters: 3 (candidate - the communicator wanting to message the device, origin_address - the address of the sender, text - the message) // Description: Response to a communicator trying to message the device. // Adds them to the list of people that have messaged this device and adds the message to the message list. /obj/item/device/communicator/proc/request_im(var/atom/candidate, var/origin_address, var/text) var/who = null if(isobserver(candidate)) var/mob/observer/dead/ghost = candidate who = ghost im_list += list(list("address" = origin_address, "to_address" = exonet.address, "im" = text)) else if(istype(candidate, /obj/item/device/communicator)) var/obj/item/device/communicator/comm = candidate who = comm.owner comm.im_contacts |= src im_list += list(list("address" = origin_address, "to_address" = exonet.address, "im" = text)) else return im_contacts |= candidate if(!who) return if(ringer) playsound(loc, 'sound/machines/twobeep.ogg', 50, 1) for (var/mob/O in hearers(2, loc)) O.show_message(text("\icon[src] *beep*")) alert_called = 1 update_icon() //Search for holder of the device. var/mob/living/L = null if(loc && isliving(loc)) L = loc if(L) L << "\icon[src] Message from [who]." // Proc: Destroy() // Parameters: None // Description: Deletes all the voice mobs, disconnects all linked communicators, and cuts lists to allow successful qdel() /obj/item/device/communicator/Destroy() for(var/mob/living/voice/voice in contents) voice_mobs.Remove(voice) voice << "\icon[src] Connection timed out with remote host." qdel(voice) close_connection(reason = "Connection timed out") communicating.Cut() voice_requests.Cut() voice_invites.Cut() all_communicators -= src processing_objects -= src listening_objects.Remove(src) qdel(camera) camera = null if(exonet) exonet.remove_address() exonet = null ..() // Proc: update_icon() // Parameters: None // Description: Self explanatory /obj/item/device/communicator/update_icon() if(video_source) icon_state = "communicator-video" return if(voice_mobs.len || communicating.len) icon_state = "communicator-active" return if(alert_called) icon_state = "communicator-called" return icon_state = initial(icon_state) // Proc: see_emote() // Parameters: 2 (M - the mob the emote originated from, text - the emote's contents) // Description: Relays the emote to all linked communicators. /obj/item/device/communicator/see_emote(mob/living/M, text) var/rendered = "\icon[src] [text]" for(var/obj/item/device/communicator/comm in communicating) var/turf/T = get_turf(comm) if(!T) return var/list/in_range = get_mobs_and_objs_in_view_fast(T,world.view,0) //Range of 3 since it's a tiny video display var/list/mobs_to_relay = in_range["mobs"] for(var/mob/mob in mobs_to_relay) //We can't use visible_message(), or else we will get an infinite loop if two communicators hear each other. var/dst = get_dist(get_turf(mob),get_turf(comm)) if(dst <= video_range) mob.show_message(rendered) else mob << "You can barely see some movement on \the [src]'s display." ..() // Proc: hear_talk() // Parameters: 4 (M - the mob the speech originated from, text - what is being said, verb - the word used to describe how text is being said, speaking - language // being used) // Description: Relays the speech to all linked communicators. /obj/item/device/communicator/hear_talk(mob/living/M, text, verb, datum/language/speaking) for(var/obj/item/device/communicator/comm in communicating) var/turf/T = get_turf(comm) if(!T) return var/list/in_range = get_mobs_and_objs_in_view_fast(T,world.view,0) var/list/mobs_to_relay = in_range["mobs"] for(var/mob/mob in mobs_to_relay) //Can whoever is hearing us understand? if(!mob.say_understands(M, speaking)) if(speaking) text = speaking.scramble(text) else text = stars(text) var/name_used = M.GetVoice() var/rendered = null if(speaking) //Language being used rendered = "\icon[src] [name_used] [speaking.format_message(text, verb)]" else rendered = "\icon[src] [name_used] [verb], \"[text]\"" mob.show_message(rendered, 2) // Proc: show_message() // Parameters: 4 (msg - the message, type - number to determine if message is visible or audible, alt - unknown, alt_type - unknown) // Description: Relays the message to all linked communicators. /obj/item/device/communicator/show_message(msg, type, alt, alt_type) var/rendered = "\icon[src] [msg]" for(var/obj/item/device/communicator/comm in communicating) var/turf/T = get_turf(comm) if(!T) return var/list/in_range = get_mobs_and_objs_in_view_fast(T,world.view,0) var/list/mobs_to_relay = in_range["mobs"] for(var/mob/mob in mobs_to_relay) mob.show_message(rendered) ..() // Verb: join_as_voice() // Parameters: None // Description: Allows ghosts to call communicators, if they meet all the requirements. /mob/observer/dead/verb/join_as_voice() set category = "Ghost" set name = "Call Communicator" set desc = "If there is a communicator available, send a request to speak through it. This will reset your respawn timer, if someone picks up." if(ticker.current_state < GAME_STATE_PLAYING) src << "The game hasn't started yet!" return if (!src.stat) return if (usr != src) return //something is terribly wrong var/confirm = alert(src, "Would you like to talk as [src.client.prefs.real_name], over a communicator? \ This will reset your respawn timer, if someone answers.", "Join as Voice?", "Yes","No") if(confirm == "No") return for(var/mob/living/L in mob_list) //Simple check so you don't have dead people calling. if(src.client.prefs.real_name == L.real_name) src << "Your identity is already present in the game world. Please load in a different character first." return var/obj/machinery/exonet_node/E = get_exonet_node() if(!E || !E.on || !E.allow_external_communicators) src << "The Exonet node at telecommunications is down at the moment, or is actively blocking you, so your call can't go through." return var/list/choices = list() for(var/obj/item/device/communicator/comm in all_communicators) if(!comm.network_visibility || !comm.exonet || !comm.exonet.address) continue choices.Add(comm) if(!choices.len) src << "There are no available communicators, sorry." return var/choice = input(src,"Send a voice request to whom?") as null|anything in choices if(choice) var/obj/item/device/communicator/chosen_communicator = choice var/mob/observer/dead/O = src if(O.exonet) O.exonet.send_message(chosen_communicator.exonet.address, "voice") src << "A communications request has been sent to [chosen_communicator]. Now you need to wait until someone answers." // Verb: text_communicator() // Parameters: None // Description: Allows a ghost to send a text message to a communicator. /mob/observer/dead/verb/text_communicator() set category = "Ghost" set name = "Text Communicator" set desc = "If there is a communicator available, send a text message to it." if(ticker.current_state < GAME_STATE_PLAYING) src << "The game hasn't started yet!" return if (!src.stat) return if (usr != src) return //something is terribly wrong for(var/mob/living/L in mob_list) //Simple check so you don't have dead people calling. if(src.client.prefs.real_name == L.real_name) src << "Your identity is already present in the game world. Please load in a different character first." return var/obj/machinery/exonet_node/E = get_exonet_node() if(!E || !E.on || !E.allow_external_communicators) src << "The Exonet node at telecommunications is down at the moment, or is actively blocking you, so your call can't go through." return var/list/choices = list() for(var/obj/item/device/communicator/comm in all_communicators) if(!comm.network_visibility || !comm.exonet || !comm.exonet.address) continue choices.Add(comm) if(!choices.len) src << "There are no available communicators, sorry." return var/choice = input(src,"Send a text message to whom?") as null|anything in choices if(choice) var/obj/item/device/communicator/chosen_communicator = choice var/mob/observer/dead/O = src var/text_message = sanitize(input(src, "What do you want the message to say?")) as message if(text_message && O.exonet) O.exonet.send_message(chosen_communicator.exonet.address, "text", text_message) src << "You have sent '[text_message]' to [chosen_communicator].." exonet_messages.Add("To [chosen_communicator]:
[text_message]") log_pda("[usr] (COMM: [src]) sent \"[text_message]\" to [chosen_communicator]") // Verb: show_text_messages() // Parameters: None // Description: Lets ghosts review messages they've sent or received. /mob/observer/dead/verb/show_text_messages() set category = "Ghost" set name = "Show Text Messages" set desc = "Allows you to see exonet text messages you've sent and received." var/HTML = "Exonet Message Log" for(var/line in exonet_messages) HTML += line + "
" HTML +="" usr << browse(HTML, "window=log;size=400x444;border=1;can_resize=1;can_close=1;can_minimize=0") // Proc: connect_video() // Parameters: user - the mob doing the viewing of video, comm - the communicator at the far end // Description: Sets up a videocall and puts the first view into it using watch_video, and updates the icon /obj/item/device/communicator/proc/connect_video(mob/user,obj/item/device/communicator/comm) if((!user) || (!comm) || user.stat) return //KO or dead, or already in a video if(video_source) //Already in a video user << "You are already connected to a video call!" if(user.blinded) //User is blinded user << "You cannot see well enough to do that!" if(!(src in comm.communicating) || !comm.camera) //You called someone with a broken communicator or one that's fake or yourself or something user << "\icon[src]ERROR: Video failed. Either bandwidth is too low, or the other communicator is malfunctioning." user << "\icon[src] Attempting to start video over existing call." sleep(30) user << "\icon[src] Please wait..." video_source = comm.camera comm.visible_message("\icon[src] New video connection from [comm].") watch_video(user) update_icon() // Proc: watch_video() // Parameters: user - the mob doing the viewing of video // Description: Moves a mob's eye to the far end for the duration of viewing the far end /obj/item/device/communicator/proc/watch_video(mob/user) if(!Adjacent(user) || !video_source) return user.set_machine(video_source) user.reset_view(video_source) user << "Now viewing video session. To leave camera view: OOC -> Cancel Camera View" spawn(0) while(user.machine == video_source && Adjacent(user)) var/turf/T = get_turf(video_source) if(!T || !is_on_same_plane_or_station(T.z, user.z) || !video_source.can_use()) user << "The screen bursts into static, then goes black." video_cleanup(user) return sleep(10) video_cleanup(user) // Proc: video_cleanup() // Parameters: user - the mob who doesn't want to see video anymore // Description: Cleans up mob's client when they stop watching a video /obj/item/device/communicator/proc/video_cleanup(mob/user) if(!user) return user.reset_view(null) user.unset_machine() // Proc: end_video() // Parameters: reason - the text reason to print for why it ended // Description: Ends the video call by clearing video_source /obj/item/device/communicator/proc/end_video(var/reason) video_source = null . = "\icon[src] [reason ? reason : "Video session ended"]." visible_message(.) update_icon() //For synths who have no hands. /obj/item/device/communicator/integrated name = "integrated communicator" desc = "A circuit used for long-range communications, able to be integrated into a system." //A stupid hack because synths don't use languages properly or something. //I don't want to go digging in saycode for a week, so BS it as translation software or something. // Proc: open_connection_to_ghost() // Parameters: 2 (refer to base definition for arguments) // Description: Synths don't use languages properly, so this is a bandaid fix until that can be resolved.. /obj/item/device/communicator/integrated/open_connection_to_ghost(user, candidate) ..(user, candidate) spawn(1) for(var/mob/living/voice/V in contents) V.universal_speak = 1 V.universal_understand = 1 // Verb: activate() // Parameters: None // Description: Lets synths use their communicators without hands. /obj/item/device/communicator/integrated/verb/activate() set category = "AI IM" set name = "Use Communicator" set desc = "Utilizes your built-in communicator." set src in usr if(usr.stat == 2) usr << "You can't do that because you are dead!" return src.attack_self(usr) // A camera preset for spawning in the communicator /obj/machinery/camera/communicator network = list(NETWORK_COMMUNICATORS) /obj/machinery/camera/communicator/New() ..() client_huds |= global_hud.whitense client_huds |= global_hud.darkMask