From c7733216a399ff2882fc2fce376004c84a5f2851 Mon Sep 17 00:00:00 2001 From: zimon9 <122945887+zimon9@users.noreply.github.com> Date: Tue, 28 Jan 2025 21:14:12 -0500 Subject: [PATCH] Camera Console Recalibration and Bodycameras (#3952) ## About The Pull Request This change modifies camera consoles so that they can take a custom network input. Camera consoles were also modified to get closer in parity to the SecurEye app. Tablets were given the ability to download securEye. | Camera Consoles |![image](https://github.com/user-attachments/assets/b59c4498-d0c9-4eaa-a051-e3365a4705c8)| |-----------------|---| | SecurEye |![image](https://github.com/user-attachments/assets/320ca69a-5d6c-46b1-a152-2244403415b2)| Static cameras were modified so that using a multitool on one with its panel open now presents several new options. One can copy a network that a camera is set to onto a multitool, transfer a saved network into a camera, or even set a new network entirely. ![image](https://github.com/user-attachments/assets/3dadb434-6217-493c-a3fd-1a36b2fb06d3) They were also given a new variable, can_transmit_across_z_levels, which defaults to false. This variable is for varedits, and a potential future addition that may enable transmission across Z-levels for some cost. A new portable type of camera was also added, the body camera. It can be activated or deactivated by being alt-clicked, and its tag or network settings can be modified if a multitool is used on it. They can be purchased from the outpost cargo market at a rate of two units for 250 credits. Bodycameras can be worn in your pockets, your coat/armor, security belts or webbings, or a helmet, but they get obscured when put into a bag, a box, or your boots. They have a view range of 5, which is 2 less than camera structures. https://github.com/user-attachments/assets/9c69cd3a-59c4-40a7-94fd-2be9932ec15f https://github.com/user-attachments/assets/75655b88-583f-44a6-93ef-e59e159826ce This PR by Timberpoes proved instrumental in solving a major problem during the development process: https://github.com/tgstation/tgstation/pull/52767 The bodycamera sprite is modeled after the handheld radio sprite made by @rye-rice in this PR: https://github.com/shiptest-ss13/Shiptest/pull/2610 ![image](https://github.com/user-attachments/assets/c4f3f659-d285-4936-942e-7d11db54a9a9) I'm not a good pixel artist, so any feedback here is appreciated, on top of feedback with regards to anything else about this PR. ## Why It's Good For The Game It would be nice to be able to track your crew more effectively, be it if you were to send them out to scrap a derelict, or to secure contested territory. This is something that Command or Foremen that want to oversee operations might find useful, as it can be kind of boring to not be able to see your crew when they're at work. This should help that issue. ## Changelog :cl: Rye-Rice, Timberpoes add: Added bodycameras add: Added the ability to set custom networks on cameras and camera consoles code: Made camera consoles and SecurEye have better parity /:cl: --- .../components/storage/concrete/pockets.dm | 1 + code/game/machinery/camera/camera.dm | 25 +- code/game/machinery/computer/camera.dm | 241 +++++++++++++----- code/game/objects/items/bodycamera.dm | 146 +++++++++++ code/game/objects/items/storage/belt.dm | 10 +- code/modules/cargo/packs/tools.dm | 9 + .../living/silicon/ai/freelook/cameranet.dm | 31 ++- .../file_system/programs/secureye.dm | 120 ++++++--- icons/obj/item/bodycamera.dmi | Bin 0 -> 515 bytes shiptest.dme | 1 + sound/items/bodycamera_off.ogg | Bin 0 -> 15955 bytes sound/items/bodycamera_on.ogg | Bin 0 -> 16490 bytes .../packages/tgui/interfaces/CameraConsole.js | 20 +- tgui/packages/tgui/interfaces/NtosSecurEye.js | 3 + 14 files changed, 505 insertions(+), 102 deletions(-) create mode 100644 code/game/objects/items/bodycamera.dm create mode 100644 icons/obj/item/bodycamera.dmi create mode 100644 sound/items/bodycamera_off.ogg create mode 100644 sound/items/bodycamera_on.ogg diff --git a/code/datums/components/storage/concrete/pockets.dm b/code/datums/components/storage/concrete/pockets.dm index 98d5f5ac53d1..185fd3d93ecd 100644 --- a/code/datums/components/storage/concrete/pockets.dm +++ b/code/datums/components/storage/concrete/pockets.dm @@ -94,6 +94,7 @@ /obj/item/clothing/glasses/sunglasses/ballistic, /obj/item/ammo_casing, /obj/item/ammo_box/magazine/illestren_a850r, + /obj/item/bodycamera, )) /datum/component/storage/concrete/pockets/holster diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm index 21eb3cc362b7..4125146dfe5a 100644 --- a/code/game/machinery/camera/camera.dm +++ b/code/game/machinery/camera/camera.dm @@ -36,6 +36,7 @@ var/busy = FALSE var/emped = FALSE //Number of consecutive EMP's on this camera var/in_use_lights = 0 + var/can_transmit_across_z_levels = FALSE // Upgrades bitflag var/upgrades = 0 @@ -258,8 +259,26 @@ if(!panel_open) return - setViewRange((view_range == initial(view_range)) ? short_range : initial(view_range)) - to_chat(user, "You [(view_range == initial(view_range)) ? "restore" : "mess up"] the camera's focus.") + var/obj/item/multitool/M = I + var/list/choice_list = list("Occlude the camera lens", "Save the network to the multitool buffer", "Transfer the buffered network to the camera", "Change the camera network") + var/choice = tgui_input_list(user, "Select an option", "Camera Settings", choice_list) + switch(choice) + if("Occlude the camera lens") + setViewRange((view_range == initial(view_range)) ? short_range : initial(view_range)) + to_chat(user, "You [(view_range == initial(view_range)) ? "restore" : "mess up"] the camera's focus.") + + if("Save the network to the multitool buffer") + M.buffer = network[1] + to_chat(user, "You add network '[network[1]]' to the multitool's buffer.") + + if("Transfer the buffered network to the camera") + network[1] = M.buffer + to_chat(user, "You tune [src] to transmit across the '[network[1]]' network using the saved data from the multiool's buffer.") + + if("Change the camera network") + network[1] = stripped_input(user, "Tune [src] to a specific network. Enter the network name and ensure that it is no bigger than 32 characters long. Network names are not case sensitive.", "Network Tuning", max_length = 32) + to_chat(user, "You set [src] to transmit across the '[network[1]]' network.") + return TRUE /obj/machinery/camera/welder_act(mob/living/user, obj/item/I) @@ -508,6 +527,6 @@ user.sight |= (SEE_TURFS|SEE_MOBS|SEE_OBJS) user.see_in_dark = max(user.see_in_dark, 8) else - user.sight = 0 + user.sight = SEE_BLACKNESS user.see_in_dark = 2 return 1 diff --git a/code/game/machinery/computer/camera.dm b/code/game/machinery/computer/camera.dm index de6daa417946..dd6eb9a24e8d 100644 --- a/code/game/machinery/computer/camera.dm +++ b/code/game/machinery/computer/camera.dm @@ -7,14 +7,18 @@ light_color = COLOR_SOFT_RED var/list/network = list("ss13") + var/temp_network = list("") var/obj/machinery/camera/active_camera + /// The turf where the camera was last updated. + var/turf/last_camera_turf var/list/concurrent_users = list() // Stuff needed to render the map var/map_name var/const/default_map_size = 15 var/atom/movable/screen/map_view/cam_screen - var/atom/movable/screen/plane_master/lighting/cam_plane_master + /// All the plane masters that need to be applied. + var/list/cam_plane_masters var/atom/movable/screen/background/cam_background /obj/machinery/computer/security/retro @@ -43,18 +47,20 @@ cam_screen.assigned_map = map_name cam_screen.del_on_map_removal = FALSE cam_screen.screen_loc = "[map_name]:1,1" - cam_plane_master = new - cam_plane_master.name = "plane_master" - cam_plane_master.assigned_map = map_name - cam_plane_master.del_on_map_removal = FALSE - cam_plane_master.screen_loc = "[map_name]:CENTER" + cam_plane_masters = list() + for(var/plane in subtypesof(/atom/movable/screen/plane_master)) + var/atom/movable/screen/instance = new plane() + instance.assigned_map = map_name + instance.del_on_map_removal = FALSE + instance.screen_loc = "[map_name]:CENTER" + cam_plane_masters += instance cam_background = new cam_background.assigned_map = map_name cam_background.del_on_map_removal = FALSE /obj/machinery/computer/security/Destroy() qdel(cam_screen) - qdel(cam_plane_master) + QDEL_LIST(cam_plane_masters) qdel(cam_background) return ..() @@ -63,12 +69,21 @@ network -= i network += "[REF(port)][i]" +/obj/machinery/computer/security/multitool_act(mob/living/user, obj/item/I) + . = ..() + var/obj/item/multitool/M = I + if(M.buffer != null) + network = M.buffer + to_chat(user, "You input network '[M.buffer]' from the multitool's buffer into [src].") + return + /obj/machinery/computer/security/ui_interact(mob/user, datum/tgui/ui) // Update UI ui = SStgui.try_update_ui(user, src, ui) + // Show static if can't use the camera - if(!active_camera?.can_use()) - show_camera_static() + update_active_camera_screen() + if(!ui) var/user_ref = REF(user) var/is_living = isliving(user) @@ -82,7 +97,8 @@ use_power(active_power_usage) // Register map objects user.client.register_map_obj(cam_screen) - user.client.register_map_obj(cam_plane_master) + for(var/plane in cam_plane_masters) + user.client.register_map_obj(plane) user.client.register_map_obj(cam_background) // Open UI ui = new(user, src, "CameraConsole", name) @@ -93,16 +109,33 @@ data["network"] = network data["activeCamera"] = null if(active_camera) - if(!active_camera?.can_use()) - data["activeCamera"] = list( - name = active_camera.c_tag + " (DEACTIVATED)", - status = active_camera.status, - ) - else - data["activeCamera"] = list( - name = active_camera.c_tag, - status = active_camera.status, - ) + if(istype(active_camera, /obj/machinery/camera)) + var/obj/machinery/camera/active_camera_S = active_camera + if(!active_camera_S?.can_use()) + data["activeCamera"] = list( + name = active_camera_S.c_tag, + status = active_camera_S.status, + ) + else + data["activeCamera"] = list( + name = active_camera_S.c_tag, + status = active_camera_S.status, + ) + active_camera = active_camera_S + + else if(istype(active_camera, /obj/item/bodycamera)) + var/obj/machinery/camera/active_camera_B = active_camera + if(!active_camera_B?.can_use()) + data["activeCamera"] = list( + name = active_camera_B.c_tag, + status = active_camera_B.status, + ) + else + data["activeCamera"] = list( + name = active_camera_B.c_tag, + status = active_camera_B.status, + ) + active_camera = active_camera_B return data /obj/machinery/computer/security/ui_static_data() @@ -111,38 +144,77 @@ var/list/cameras = get_available_cameras() data["cameras"] = list() for(var/i in cameras) - var/obj/machinery/camera/C = cameras[i] - if(!C?.can_use()) + var/obj/C = cameras[i] + if(istype(C, /obj/machinery/camera)) + var/obj/machinery/camera/C_cam = C data["cameras"] += list(list( - name = C.c_tag + " (DEACTIVATED)", + name = C_cam.c_tag, )) - else + else if(istype(C, /obj/item/bodycamera)) + var/obj/item/bodycamera/C_cam = C data["cameras"] += list(list( - name = C.c_tag, + name = C_cam.c_tag, )) return data -/obj/machinery/computer/security/ui_act(action, params) +//This is the only way to refresh the UI, from what I've found +/obj/machinery/computer/security/proc/ui_refresh(mob/user, datum/tgui/ui) + ui.close() + ui_interact(user, ui) + show_camera_static() + +/obj/machinery/computer/security/ui_act(action, params, ui) . = ..() if(.) return + if(action == "set_network") + network = temp_network + ui_refresh(usr, ui) + + if(action == "set_temp_network") + temp_network = sanitize_filename(params["name"]) + + if(action == "refresh") + ui_refresh(usr, ui) + if(action == "switch_camera") var/c_tag = params["name"] var/list/cameras = get_available_cameras() - var/obj/machinery/camera/C = cameras[c_tag] + var/obj/C = cameras[c_tag] active_camera = C playsound(src, get_sfx("terminal_type"), 25, FALSE) + update_active_camera_screen() + + return TRUE + +/obj/machinery/computer/security/ui_close(mob/user) + var/user_ref = REF(user) + var/is_living = isliving(user) + // Living creature or not, we remove you anyway. + concurrent_users -= user_ref + // Unregister map objects + user.client.clear_map(map_name) + // Turn off the console + if(length(concurrent_users) == 0 && is_living) + active_camera = null + playsound(src, 'sound/machines/terminal_off.ogg', 25, FALSE) + use_power(0) + +/obj/machinery/computer/security/proc/update_active_camera_screen() + if(istype(active_camera, /obj/machinery/camera)) + var/obj/machinery/camera/active_camera_S = active_camera + // Show static if can't use the camera - if(!active_camera?.can_use()) + if(!active_camera_S?.can_use()) show_camera_static() return TRUE var/list/visible_turfs = list() - for(var/turf/T in (C.isXRay() \ - ? range(C.view_range, C) \ - : view(C.view_range, C))) + for(var/turf/T in (active_camera_S.isXRay() \ + ? range(active_camera_S.view_range, active_camera_S) \ + : view(active_camera_S.view_range, active_camera_S))) visible_turfs += T var/list/bbox = get_bbox_of_atoms(visible_turfs) @@ -153,44 +225,97 @@ cam_background.icon_state = "clear" cam_background.fill_rect(1, 1, size_x, size_y) - return TRUE + if(istype(active_camera, /obj/item/bodycamera)) + var/obj/item/bodycamera/active_camera_B = active_camera -/obj/machinery/computer/security/ui_close(mob/user) - var/user_ref = REF(user) - var/is_living = isliving(user) - // Living creature or not, we remove you anyway. - concurrent_users -= user_ref - // Unregister map objects - user.client.clear_map(map_name) - // Turn off the console - if(length(concurrent_users) == 0 && is_living) - active_camera = null - playsound(src, 'sound/machines/terminal_off.ogg', 25, FALSE) - use_power(0) + // Show static if can't use the camera + if(!active_camera_B?.can_use()) + show_camera_static() + return TRUE + + var/list/visible_turfs = list() + + if(!active_camera_B.loc) + return + + // Derived from https://github.com/tgstation/tgstation/pull/52767 + // Is this camera located in or attached to a living thing? If so, assume the camera's loc is the living thing. + var/cam_location = active_camera_B.loc + + // Is the camera in the following items? If so, let it transmit an image as normal + if((istype(cam_location, /obj/item/clothing/suit)) || (istype(cam_location, /obj/item/clothing/head/helmet)) || istype(cam_location, /obj/item/storage/belt)) + cam_location = active_camera_B.loc.loc + + // If we're not forcing an update for some reason and the cameras are in the same location, + // we don't need to update anything. + // Most security cameras will end here as they're not moving. + if(istype(active_camera, /obj/machinery/camera)) + return + + // Cameras that get here are moving, and are likely attached to some moving atom such as cyborgs. + last_camera_turf = get_turf(cam_location) + + var/list/visible_things = view(active_camera_B.view_range, cam_location) + + for(var/turf/visible_turf in visible_things) + visible_turfs += visible_turf + + var/list/bbox = get_bbox_of_atoms(visible_turfs) + var/size_x = bbox[3] - bbox[1] + 1 + var/size_y = bbox[4] - bbox[2] + 1 + + cam_screen.vis_contents = visible_turfs + cam_background.icon_state = "clear" + cam_background.fill_rect(1, 1, size_x, size_y) /obj/machinery/computer/security/proc/show_camera_static() cam_screen.vis_contents.Cut() cam_background.icon_state = "scanline2" cam_background.fill_rect(1, 1, default_map_size, default_map_size) -// Returns the list of cameras accessible from this computer /obj/machinery/computer/security/proc/get_available_cameras() var/list/L = list() - for (var/obj/machinery/camera/C in GLOB.cameranet.cameras) - if((is_away_level(src) || is_away_level(C)) && (C.virtual_z() != virtual_z()))//if on away mission, can only receive feed from same z_level cameras - continue + for (var/obj/C in GLOB.cameranet.cameras) + if(istype(C, /obj/machinery/camera)) + var/obj/machinery/camera/cam = C + if(cam.virtual_z() != virtual_z()) + if(cam.can_transmit_across_z_levels) + //let them transmit + else + continue + else if(istype(C, /obj/item/bodycamera)) + var/obj/item/bodycamera/cam = C + if((cam.virtual_z() != virtual_z()) || (cam.can_transmit_across_z_levels))//if on away mission, can only receive feed from same z_level cameras + if(cam.can_transmit_across_z_levels) + //let them transmit + else + continue L.Add(C) var/list/D = list() - for(var/obj/machinery/camera/C in L) - if(!C.network) - stack_trace("Camera in a cameranet has no camera network") - continue - if(!(islist(C.network))) - stack_trace("Camera in a cameranet has a non-list camera network") - continue - var/list/tempnetwork = C.network & network - if(tempnetwork.len) - D["[C.c_tag]"] = C + for(var/obj/C in L) + if(istype(C, /obj/machinery/camera)) + var/obj/machinery/camera/cam = C + if(!cam.network) + stack_trace("Camera in a cameranet has no camera network") + continue + if(!(islist(cam.network))) + stack_trace("Camera in a cameranet has a non-list camera network") + continue + var/list/tempnetwork = cam.network & network + if(tempnetwork.len) + D["[cam.c_tag]"] = C + + else if(istype(C, /obj/item/bodycamera)) + var/obj/item/bodycamera/cam = C + if(!cam.network) + stack_trace("Camera in a cameranet has no camera network") + continue + if(!(islist(cam.network))) + stack_trace("Camera in a cameranet has a non-list camera network") + continue + var/list/tempnetwork = cam.network & network + if(tempnetwork.len) + D["[cam.c_tag]"] = cam return D // SECURITY MONITORS diff --git a/code/game/objects/items/bodycamera.dm b/code/game/objects/items/bodycamera.dm new file mode 100644 index 000000000000..9472578f2e12 --- /dev/null +++ b/code/game/objects/items/bodycamera.dm @@ -0,0 +1,146 @@ +/obj/item/bodycamera + name = "body camera" + desc = "Ruggedized portable camera unit. Warranty void if exposed to space." + icon = 'icons/obj/item/bodycamera.dmi' + icon_state = "bodycamera-off" + resistance_flags = FIRE_PROOF //double check that this flag works for fireproof objects + var/list/network = list("default") + var/c_tag = "Body Camera" + var/c_tag_addition = "" + var/status = FALSE + var/start_active = FALSE //If it ignores the random chance to start broken on round start + var/area/myarea = null + w_class = WEIGHT_CLASS_SMALL + slot_flags = ITEM_SLOT_BELT + var/view_range = 5 + var/busy = FALSE + var/can_transmit_across_z_levels = FALSE + +/obj/item/bodycamera/Initialize() + . = ..() + for(var/i in network) + network -= i + network += lowertext(i) + + GLOB.cameranet.cameras += src + GLOB.cameranet.addCamera(src) + c_tag = "Body Camera - " + random_string(4, list("0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F")) + update_appearance() + +/obj/item/bodycamera/Destroy() + if(can_use()) + toggle_cam(null, 0) //kick anyone viewing out and remove from the camera chunks + GLOB.cameranet.cameras -= src + return ..() + +/obj/item/bodycamera/examine(mob/user) + . += ..() + . += "The body camera is currently [status ? "ON" : "OFF"]. Alt-Click to toggle its status." + if(in_range(src, user)) + . += "The body camera is set to a nametag of '[c_tag]'." + . += "The body camera is set to transmit on the '[network[1]]' network." + . += "It looks like you can modify the camera settings by using a multitool on it." + +/obj/item/bodycamera/AltClick(mob/user) + . = ..() + if(!user.CanReach(src)) + return + if(do_after(user, 10, src, IGNORE_USER_LOC_CHANGE)) + status = !status + if(status) + icon_state = "bodycamera-on" + playsound(user, 'sound/items/bodycamera_on.ogg', 23, FALSE) + else + icon_state = "bodycamera-off" + playsound(user, 'sound/items/bodycamera_off.ogg', 23, FALSE) + user.visible_message( + span_notice("[user] turns [src] [status ? "ON" : "OFF"]."), + span_notice("You turn [src] [status ? "ON" : "OFF"]."), + update_appearance() + ) + +/obj/item/bodycamera/multitool_act(mob/living/user, obj/item/I) + . = ..() + var/obj/item/multitool/M = I + var/list/choice_list = list("Modify the camera tag", "Change the camera network", "Save the network to the multitool buffer", "Transfer the buffered network to the camera") + var/choice = tgui_input_list(user, "Select an option", "Camera Configuration", choice_list) + + switch(choice) + if("Modify the camera tag") + c_tag_addition = stripped_input(user, "Set a nametag for this camera. Ensure that it is no bigger than 32 characters long.", "Nametag Setup", max_length = 32) + if(c_tag_addition == "") + c_tag = "Body Camera - " + random_string(4, list("0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F")) + else + c_tag = c_tag_addition + to_chat(user, "You set [src] nametag to '[c_tag]'.") + + if("Change the camera network") + network[1] = stripped_input(user, "Tune [src] to a specific network. Enter the network name and ensure that it is no bigger than 32 characters long. Network names are case sensitive.", "Network Tuning", max_length = 32) + to_chat(user, "You set [src] to transmit across the '[network[1]]' network.") + + if("Save the network to the multitool buffer") + M.buffer = network[1] + to_chat(user, "You add network '[network[1]]' to the multitool's buffer.") + + if("Transfer the buffered network to the camera") + network[1] = M.buffer + to_chat(user, "You tune [src] to transmit across the '[network[1]]' network using the saved data from the multiool's buffer.") + + return TRUE + +/obj/item/bodycamera/proc/setViewRange(num = 5) + src.view_range = num + GLOB.cameranet.updateVisibility(src, 0) + +/obj/item/bodycamera/proc/toggle_cam(mob/user, displaymessage = 1) + status = !status + if(can_use()) + GLOB.cameranet.addCamera(src) + myarea = null + else + GLOB.cameranet.removeCamera(src) + if (isarea(myarea)) + LAZYREMOVE(myarea.cameras, src) + GLOB.cameranet.updateChunk(x, y, z) + update_appearance() //update Initialize() if you remove this. + + // now disconnect anyone using the camera + //Apparently, this will disconnect anyone even if the camera was re-activated. + //I guess that doesn't matter since they can't use it anyway? + for(var/mob/O in GLOB.player_list) + if (O.client.eye == src) + O.unset_machine() + O.reset_perspective(null) + to_chat(O, "The screen bursts into static!") + +/obj/item/bodycamera/proc/can_use() + if(!status) + return FALSE + return TRUE + +/obj/item/bodycamera/proc/can_see() + var/list/see = null + var/turf/pos = get_turf(src) + see = view(view_range, pos) + return see + +/obj/item/bodycamera/proc/isXRay() + return FALSE + +/obj/item/bodycamera/update_remote_sight(mob/living/user) + user.see_invisible = SEE_INVISIBLE_LIVING //can't see ghosts through cameras + user.sight = SEE_BLACKNESS + user.see_in_dark = 2 + return 1 + +/obj/item/paper/guides/bodycam + name = "Portable Camera Unit Users Guide" + default_raw_text = "
Portable Camera Unit User's Guide\n
 The Mark I Portable Camera unit is a versatile solution ⠀   for all of your project management needs.\n\n
 Features
- Real-time visual data feedback
- Configurable EEPROM memory settings
- Passive thermal regulator
- Long-range millimeter-wave band antenna
- High-capacity self-recharging battery
- Easy to reach power button
\n\n To activate the camera, simply press and hold the\n power button for one second. You should hear a chime\n and a green status light should become lit.\n\n To deactivate the camera, depress the power button\n again for one second.\n\n In order to modify the settings of your portable camera\n unit, a ISO-standard multitool will be required.\n \n Simply connect the tool to the camera's settings port,\n and you should be able to modify the internal address\n of the camera, or the network configuration.\n\n You will also be able to save the network configuration\n of the camera and copy it to other Mark I Portable\n Camera units.\n\n We hope that our tools will provide the edge you need\n in order to ensure your team stays on-task." + + + + + + + + diff --git a/code/game/objects/items/storage/belt.dm b/code/game/objects/items/storage/belt.dm index 39e7452b9bf0..c5e043976795 100644 --- a/code/game/objects/items/storage/belt.dm +++ b/code/game/objects/items/storage/belt.dm @@ -82,7 +82,8 @@ /obj/item/clothing/glasses/welding, //WS edit: ok mald sure I'll add the welding stuff to the. ok. /obj/item/clothing/mask/gas/welding, /obj/item/clothing/head/welding, //WS end - /obj/item/gun/energy/plasmacutter + /obj/item/gun/energy/plasmacutter, + /obj/item/bodycamera )) /obj/item/storage/belt/utility/chief @@ -225,7 +226,8 @@ /obj/item/construction/plumbing, /obj/item/plunger, /obj/item/reagent_containers/spray, - /obj/item/shears + /obj/item/shears, + /obj/item/bodycamera )) /obj/item/storage/belt/medical/paramedic/PopulateContents() @@ -319,6 +321,7 @@ /obj/item/holosign_creator/security, /obj/item/stock_parts/cell/gun, /obj/item/ammo_box/magazine/ammo_stack, //handfuls of bullets + /obj/item/bodycamera, )) /obj/item/storage/belt/security/full/PopulateContents() @@ -404,7 +407,8 @@ /obj/item/storage/bag/plants, /obj/item/stack/marker_beacon, /obj/item/restraints/legcuffs/bola/watcher, - /obj/item/melee/sword/bone + /obj/item/melee/sword/bone, + /obj/item/bodycamera, )) diff --git a/code/modules/cargo/packs/tools.dm b/code/modules/cargo/packs/tools.dm index 4f89804880c4..7948bcb004db 100644 --- a/code/modules/cargo/packs/tools.dm +++ b/code/modules/cargo/packs/tools.dm @@ -39,6 +39,15 @@ /obj/item/clothing/glasses/meson/engine) crate_name = "engineering gear crate" +/datum/supply_pack/tools/bodycamera + name = "Body Camera Crate" + desc = "Contains two portable cameras, designed to help keep track of a working group at all times." + cost = 250 + contains = list(/obj/item/bodycamera, + /obj/item/bodycamera, + /obj/item/paper/guides/bodycam) + crate_name = "bodycamera crate" + /datum/supply_pack/tools/assbelt name = "Assault Belt" desc = "Contains an assault belt, with not one, not two, but six pockets." diff --git a/code/modules/mob/living/silicon/ai/freelook/cameranet.dm b/code/modules/mob/living/silicon/ai/freelook/cameranet.dm index 27136c4bbc94..542f216ca2bd 100644 --- a/code/modules/mob/living/silicon/ai/freelook/cameranet.dm +++ b/code/modules/mob/living/silicon/ai/freelook/cameranet.dm @@ -118,18 +118,33 @@ GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new) chunk.hasChanged() /// Removes a camera from a chunk. -/datum/cameranet/proc/removeCamera(obj/machinery/camera/c) - majorChunkChange(c, 0) +/datum/cameranet/proc/removeCamera(obj/C) + if((istype(C, /obj/machinery/camera))||(istype(C, /obj/item/bodycamera))) + majorChunkChange(C, 0) /// Add a camera to a chunk. -/datum/cameranet/proc/addCamera(obj/machinery/camera/c) - if(c.can_use()) - majorChunkChange(c, 1) +/datum/cameranet/proc/addCamera(obj/C) + if(istype(C, /obj/machinery/camera)) + var/obj/machinery/camera/cam = C + if(cam.can_use()) + majorChunkChange(cam, 1) + + if(istype(C, /obj/item/bodycamera)) + var/obj/item/bodycamera/cam = C + if(cam.can_use()) + majorChunkChange(cam, 1) /// Used for Cyborg cameras. Since portable cameras can be in ANY chunk. -/datum/cameranet/proc/updatePortableCamera(obj/machinery/camera/c) - if(c.can_use()) - majorChunkChange(c, 1) +/datum/cameranet/proc/updatePortableCamera(obj/C) + if(istype(C, /obj/machinery/camera)) + var/obj/machinery/camera/cam = C + if(cam.can_use()) + majorChunkChange(cam, 1) + + if(istype(C, /obj/item/bodycamera)) + var/obj/item/bodycamera/cam = C + if(cam.can_use()) + majorChunkChange(cam, 1) /// Never access this proc directly!!!! /// This will update the chunk and all the surrounding chunks. diff --git a/code/modules/modular_computers/file_system/programs/secureye.dm b/code/modules/modular_computers/file_system/programs/secureye.dm index ca770b6f5b77..419ea05a5af0 100644 --- a/code/modules/modular_computers/file_system/programs/secureye.dm +++ b/code/modules/modular_computers/file_system/programs/secureye.dm @@ -8,12 +8,13 @@ extended_desc = "This program allows access to standard security camera networks." requires_ntnet = TRUE transfer_access = ACCESS_SECURITY - usage_flags = PROGRAM_CONSOLE | PROGRAM_LAPTOP + usage_flags = PROGRAM_CONSOLE | PROGRAM_LAPTOP | PROGRAM_TABLET size = 5 tgui_id = "NtosSecurEye" program_icon = "eye" var/list/network = list("ss13") + var/temp_network = list("") var/obj/machinery/camera/active_camera /// The turf where the camera was last updated. var/turf/last_camera_turf @@ -96,22 +97,44 @@ var/list/cameras = get_available_cameras() data["cameras"] = list() for(var/i in cameras) - var/obj/machinery/camera/C = cameras[i] - data["cameras"] += list(list( - name = C.c_tag, - )) - + var/obj/C = cameras[i] + if(istype(C, /obj/machinery/camera)) + var/obj/machinery/camera/C_cam = C + data["cameras"] += list(list( + name = C_cam.c_tag, + )) + else if(istype(C, /obj/item/bodycamera)) + var/obj/item/bodycamera/C_cam = C + data["cameras"] += list(list( + name = C_cam.c_tag, + )) return data -/datum/computer_file/program/secureye/ui_act(action, params) +//This is the only way to refresh the UI, from what I've found +/datum/computer_file/program/secureye/proc/ui_refresh(mob/user, datum/tgui/ui) + ui.close() + ui_interact(user, ui) + show_camera_static() + +/datum/computer_file/program/secureye/ui_act(action, params, ui) . = ..() if(.) return + if(action == "set_network") + network = temp_network + ui_refresh(usr, ui) + + if(action == "set_temp_network") + temp_network = sanitize_filename(params["name"]) + + if(action == "refresh") + ui_refresh(usr, ui) + if(action == "switch_camera") var/c_tag = params["name"] var/list/cameras = get_available_cameras() - var/obj/machinery/camera/selected_camera = cameras[c_tag] + var/obj/selected_camera = cameras[c_tag] active_camera = selected_camera playsound(src, get_sfx("terminal_type"), 25, FALSE) @@ -137,21 +160,34 @@ /datum/computer_file/program/secureye/proc/update_active_camera_screen() // Show static if can't use the camera - if(!active_camera?.can_use()) - show_camera_static() - return + if(istype(active_camera, /obj/machinery/camera)) + var/obj/machinery/camera/active_camera_S = active_camera + if(!active_camera_S?.can_use()) + show_camera_static() + return + else if(istype(active_camera, /obj/item/bodycamera)) + var/obj/item/bodycamera/active_camera_B = active_camera + if(!active_camera_B?.can_use()) + show_camera_static() + return var/list/visible_turfs = list() - // Is this camera located in or attached to a living thing? If so, assume the camera's loc is the living thing. - var/cam_location = isliving(active_camera.loc) ? active_camera.loc : active_camera + if(!active_camera.loc) + return + + var/cam_location = active_camera.loc + + if((istype(cam_location, /obj/item/clothing/suit)) || (istype(cam_location, /obj/item/clothing/head/helmet)) || istype(cam_location, /obj/item/storage/belt)) + cam_location = active_camera.loc.loc // If we're not forcing an update for some reason and the cameras are in the same location, // we don't need to update anything. // Most security cameras will end here as they're not moving. - var/newturf = get_turf(cam_location) - if(last_camera_turf == newturf) - return + if(istype(active_camera, /obj/machinery/camera)) + var/newturf = get_turf(cam_location) + if(last_camera_turf == newturf) + return // Cameras that get here are moving, and are likely attached to some moving atom such as cyborgs. last_camera_turf = get_turf(cam_location) @@ -177,19 +213,45 @@ // Returns the list of cameras accessible from this computer /datum/computer_file/program/secureye/proc/get_available_cameras() var/list/L = list() - for (var/obj/machinery/camera/cam as anything in GLOB.cameranet.cameras) - if(cam.virtual_z() != computer.virtual_z())//Only show cameras on the same level. - continue + for (var/obj/cam as anything in GLOB.cameranet.cameras) + if(istype(cam, /obj/machinery/camera)) + var/obj/machinery/camera/cam_S = cam + if((cam_S.virtual_z() != computer.virtual_z()) || (cam_S.can_transmit_across_z_levels)) //Only show cameras on the same level. + if(cam_S.can_transmit_across_z_levels) + //let them transmit + else + continue + else if(istype(cam, /obj/item/bodycamera)) + var/obj/machinery/camera/cam_B = cam + if((cam_B.virtual_z() != computer.virtual_z()) || (cam_B.can_transmit_across_z_levels)) //Only show cameras on the same level. + if(cam_B.can_transmit_across_z_levels) + //let them transmit + else + continue L.Add(cam) var/list/camlist = list() - for(var/obj/machinery/camera/cam as anything in L) - if(!cam.network) - stack_trace("Camera in a cameranet has no camera network") - continue - if(!(islist(cam.network))) - stack_trace("Camera in a cameranet has a non-list camera network") - continue - var/list/tempnetwork = cam.network & network - if(tempnetwork.len) - camlist["[cam.c_tag]"] = cam + for(var/obj/cam as anything in L) + if(istype(cam, /obj/machinery/camera)) + var/obj/machinery/camera/cam_S = cam + if(!cam_S.network) + stack_trace("Camera in a cameranet has no camera network") + continue + if(!(islist(cam_S.network))) + stack_trace("Camera in a cameranet has a non-list camera network") + continue + var/list/tempnetwork = cam_S.network & network + if(tempnetwork.len) + camlist["[cam_S.c_tag]"] = cam + + else if(istype(cam, /obj/item/bodycamera)) + var/obj/machinery/camera/cam_B = cam + if(!cam_B.network) + stack_trace("Camera in a cameranet has no camera network") + continue + if(!(islist(cam_B.network))) + stack_trace("Camera in a cameranet has a non-list camera network") + continue + var/list/tempnetwork = cam_B.network & network + if(tempnetwork.len) + camlist["[cam_B.c_tag]"] = cam return camlist diff --git a/icons/obj/item/bodycamera.dmi b/icons/obj/item/bodycamera.dmi new file mode 100644 index 0000000000000000000000000000000000000000..180fdd0975d29409a341aaf1fee72d8d8560f8fc GIT binary patch literal 515 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 literal 0 HcmV?d00001 diff --git a/sound/items/bodycamera_on.ogg b/sound/items/bodycamera_on.ogg new file mode 100644 index 0000000000000000000000000000000000000000..08d4ca68d19eeb6cb063a27fadf9282403911a73 GIT binary patch literal 16490 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 literal 0 HcmV?d00001 diff --git a/tgui/packages/tgui/interfaces/CameraConsole.js b/tgui/packages/tgui/interfaces/CameraConsole.js index 5af1f139c3cb..b19ab142d9d6 100644 --- a/tgui/packages/tgui/interfaces/CameraConsole.js +++ b/tgui/packages/tgui/interfaces/CameraConsole.js @@ -54,6 +54,9 @@ export const CameraConsole = (props, context) => {
+ Network: + {data.network || '—'} + {'\n'} Camera: {(activeCamera && activeCamera.name) || '—'}
@@ -100,7 +103,22 @@ export const CameraConsoleContent = (props, context) => { + act('set_temp_network', { + name: value, + }) + } + /> +
+ Network: + {data.network || '—'} + {'\n'} Camera: {(activeCamera && activeCamera.name) || '—'}