diff --git a/code/__defines/machinery.dm b/code/__defines/machinery.dm index 1f65364e1a..cc77b8c3c5 100644 --- a/code/__defines/machinery.dm +++ b/code/__defines/machinery.dm @@ -50,6 +50,7 @@ var/global/defer_powernet_rebuild = 0 // True if net rebuild will be called #define NETWORK_SECURITY "Security" #define NETWORK_TELECOM "Tcomsat" #define NETWORK_THUNDER "Thunderdome" +#define NETWORK_COMMUNICATORS "Communicators" // Those networks can only be accessed by pre-existing terminals. AIs and new terminals can't use them. var/list/restricted_camera_networks = list(NETWORK_ERT,NETWORK_MERCENARY,"Secret") diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm index 291b985294..7c46651419 100644 --- a/code/game/machinery/camera/camera.dm +++ b/code/game/machinery/camera/camera.dm @@ -35,10 +35,13 @@ var/affected_by_emp_until = 0 + var/client_huds = list() + /obj/machinery/camera/New() wires = new(src) assembly = new(src) assembly.state = 4 + client_huds |= global_hud.whitense /* // Use this to look for cameras that have the same c_tag. for(var/obj/machinery/camera/C in cameranet.cameras) diff --git a/code/game/machinery/camera/presets.dm b/code/game/machinery/camera/presets.dm index 136bafa90e..883d9611c1 100644 --- a/code/game/machinery/camera/presets.dm +++ b/code/game/machinery/camera/presets.dm @@ -17,7 +17,8 @@ var/global/list/station_networks = list( NETWORK_RESEARCH_OUTPOST, NETWORK_ROBOTS, NETWORK_PRISON, - NETWORK_SECURITY + NETWORK_SECURITY, + NETWORK_COMMUNICATORS ) var/global/list/engineering_networks = list( NETWORK_ENGINE, diff --git a/code/game/objects/items/devices/communicator/communicator.dm b/code/game/objects/items/devices/communicator/communicator.dm index e783139136..bf7bd30ab1 100644 --- a/code/game/objects/items/devices/communicator/communicator.dm +++ b/code/game/objects/items/devices/communicator/communicator.dm @@ -18,6 +18,8 @@ var/global/list/obj/item/device/communicator/all_communicators = list() 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() @@ -63,6 +65,9 @@ var/global/list/obj/item/device/communicator/all_communicators = list() 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)) @@ -74,6 +79,14 @@ var/global/list/obj/item/device/communicator/all_communicators = list() 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 @@ -257,27 +270,27 @@ var/global/list/obj/item/device/communicator/all_communicators = list() //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) + 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) + 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) + 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) + 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) + 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) @@ -285,12 +298,12 @@ var/global/list/obj/item/device/communicator/all_communicators = list() //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)) + 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) + im_contacts_ui[++im_contacts_ui.len] = list("name" = sanitize(comm.name), "address" = comm.exonet.address, "ref" = "\ref[comm]") //Actual messages. for(var/I in im_list) @@ -313,6 +326,7 @@ var/global/list/obj/item/device/communicator/all_communicators = list() 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() @@ -325,7 +339,7 @@ var/global/list/obj/item/device/communicator/all_communicators = list() 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", 450, 700) + 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 @@ -344,9 +358,20 @@ var/global/list/obj/item/device/communicator/all_communicators = list() 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"]) - network_visibility = !network_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 @@ -371,6 +396,12 @@ var/global/list/obj/item/device/communicator/all_communicators = list() 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." @@ -385,10 +416,24 @@ var/global/list/obj/item/device/communicator/all_communicators = list() 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.") + 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.") + 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"] @@ -398,9 +443,9 @@ var/global/list/obj/item/device/communicator/all_communicators = list() if(href_list["hang_up"]) for(var/mob/living/voice/V in contents) - close_connection(usr, V, "[usr] hung up.") + close_connection(usr, V, "[usr] hung up") for(var/obj/item/device/communicator/comm in communicating) - close_connection(usr, comm, "[usr] hung up.") + close_connection(usr, comm, "[usr] hung up") if(href_list["switch_tab"]) selected_tab = href_list["switch_tab"] @@ -475,7 +520,9 @@ var/global/list/obj/item/device/communicator/all_communicators = list() 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) @@ -485,6 +532,7 @@ var/global/list/obj/item/device/communicator/all_communicators = list() communicating |= comm listening_objects |= src + update_icon() // Proc: del_communicating() // Parameters: 1 (comm - the communicator to remove from communicating) @@ -493,6 +541,7 @@ var/global/list/obj/item/device/communicator/all_communicators = list() 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) @@ -601,15 +650,19 @@ var/global/list/obj/item/device/communicator/all_communicators = list() 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 - comm.visible_message("\icon[src] [reason].") - visible_message("\icon[src] [reason].") src.del_communicating(comm) comm.del_communicating(src) - update_icon() + 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) @@ -649,6 +702,29 @@ var/global/list/obj/item/device/communicator/all_communicators = list() 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. @@ -700,6 +776,8 @@ var/global/list/obj/item/device/communicator/all_communicators = list() all_communicators -= src processing_objects -= src listening_objects.Remove(src) + qdel(camera) + camera = null if(exonet) exonet.remove_address() exonet = null @@ -709,7 +787,11 @@ var/global/list/obj/item/device/communicator/all_communicators = list() // Parameters: None // Description: Self explanatory /obj/item/device/communicator/update_icon() - if(voice_mobs.len > 0) + if(video_source) + icon_state = "communicator-video" + return + + if(voice_mobs.len || communicating.len) icon_state = "communicator-active" return @@ -833,7 +915,71 @@ var/global/list/obj/item/device/communicator/all_communicators = list() src << "A communications request has been sent to [chosen_communicator]. Now you need to wait until someone answers." -/obj/item/device/communicator/integrated //For synths who have no hands. +// 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." @@ -864,3 +1010,12 @@ var/global/list/obj/item/device/communicator/all_communicators = list() 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 \ No newline at end of file diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm index 13dbd179c0..2030f3407d 100644 --- a/code/modules/mob/living/carbon/human/life.dm +++ b/code/modules/mob/living/carbon/human/life.dm @@ -1051,7 +1051,11 @@ if(copytext(hud.icon_state,1,4) == "hud") //ugly, but icon comparison is worse, I believe client.images.Remove(hud) - client.screen.Remove(global_hud.blurry, global_hud.druggy, global_hud.vimpaired, global_hud.darkMask, global_hud.nvg, global_hud.thermal, global_hud.meson, global_hud.science) + client.screen.Remove(global_hud.blurry, global_hud.druggy, global_hud.vimpaired, global_hud.darkMask, global_hud.nvg, global_hud.thermal, global_hud.meson, global_hud.science, global_hud.whitense) + + if(istype(client.eye,/obj/machinery/camera)) + var/obj/machinery/camera/cam = client.eye + client.screen |= cam.client_huds if(damageoverlay.overlays) damageoverlay.overlays = list() diff --git a/icons/obj/device.dmi b/icons/obj/device.dmi index cba5ea04ca..2c2e234a3f 100644 Binary files a/icons/obj/device.dmi and b/icons/obj/device.dmi differ diff --git a/nano/templates/communicator.tmpl b/nano/templates/communicator.tmpl index bb0a26842f..58b86a30d7 100644 --- a/nano/templates/communicator.tmpl +++ b/nano/templates/communicator.tmpl @@ -103,7 +103,12 @@ Used In File(s): code\game\objects\items\devices\communicator\communicator.dm {{:value.name}}