diff --git a/code/__DEFINES/alerts.dm b/code/__DEFINES/alerts.dm index ad68b1d5595..662711610ee 100644 --- a/code/__DEFINES/alerts.dm +++ b/code/__DEFINES/alerts.dm @@ -71,3 +71,5 @@ #define ALERT_BITRUNNER_THREAT "bitrunning_threat" #define ALERT_BITRUNNER_BREACH "bitrunning_breach" #define ALERT_BITRUNNER_GLITCH "bitrunning_glitch" + +#define ALERT_SILICON_RECORDING "silicon_recording" diff --git a/code/datums/wires/robot.dm b/code/datums/wires/robot.dm index 2a45b8e2b3d..8696b4fe775 100644 --- a/code/datums/wires/robot.dm +++ b/code/datums/wires/robot.dm @@ -79,7 +79,7 @@ if(R.shell) R.undeploy() R.set_connected_ai(null) - R.logevent("AI connection fault [mend?"cleared":"detected"]") + R.logevent("AI connection fault [mend ? "cleared" : "detected"]") if(WIRE_LAWSYNC) // Cut the law wire, and the borg will no longer receive law updates from its AI. Repair and it will re-sync. if(mend) if(!R.emagged) @@ -91,11 +91,12 @@ R.logevent("Lawsync Module fault [mend ? "cleared" : "detected"]") if (WIRE_CAMERA) // Disable the camera. if(!QDELETED(R.builtInCamera) && !R.scrambledcodes) - R.builtInCamera.camera_enabled = mend + var/fixing_camera = !mend + R.builtInCamera.camera_enabled = fixing_camera R.builtInCamera.toggle_cam(usr, 0) R.visible_message(span_notice("[R]'s camera lens focuses loudly."), span_notice("Your camera lens focuses loudly.")) - R.logevent("Camera Module fault [mend?"cleared":"detected"]") - log_silicon("[key_name(usr)] [mend ? "enabled" : "disabled"] [key_name(R)]'s camera via wire") + R.logevent("Camera Module fault [fixing_camera ? "cleared" : "detected"]") + log_silicon("[key_name(usr)] [fixing_camera ? "enabled" : "disabled"] [key_name(R)]'s camera via wire") if(WIRE_LOCKDOWN) // Simple lockdown. R.SetLockdown(!mend) R.logevent("Motor Controller fault [mend?"cleared":"detected"]") diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm index 2834ffbd7e6..724f9f55684 100644 --- a/code/game/machinery/camera/camera.dm +++ b/code/game/machinery/camera/camera.dm @@ -70,7 +70,7 @@ var/alarm_on = FALSE ///How many times this camera has been EMP'ed consecutively, will reset back to 0 when fixed. var/emped - ///Boolean on whether the AI can even turn on this camera's light- borg caneras dont have one, for example. + ///Boolean on whether the AI can even turn on this camera's light- borg cameras dont have one, for example. var/internal_light = TRUE ///Number of AIs watching this camera with lights on, used for icons. var/in_use_lights = 0 @@ -444,3 +444,11 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/camera/xray, 0) else user.clear_sight(SEE_TURFS|SEE_MOBS|SEE_OBJS) return TRUE + +///Called when the camera starts being watched on a camera console. +/obj/machinery/camera/proc/on_start_watching() + return + +///Called when the camera stops being watched on a camera console. +/obj/machinery/camera/proc/on_stop_watching() + return diff --git a/code/game/machinery/camera/silicon_camera.dm b/code/game/machinery/camera/silicon_camera.dm new file mode 100644 index 00000000000..62f59171134 --- /dev/null +++ b/code/game/machinery/camera/silicon_camera.dm @@ -0,0 +1,38 @@ +///Special type of camera installed into silicons (drones included) that gives an alert when someone is watching them through cameras. +/obj/machinery/camera/silicon + network = list(CAMERANET_NETWORK_SS13) + internal_light = FALSE + start_active = TRUE + ///Reference to the host (drone, silicon) that we're a host of. + var/mob/living/living_host + ///Lazylist of sources watching the camera through consoles. + var/list/sources_watching + +/obj/machinery/camera/silicon/Initialize(mapload) + . = ..() + if(!isliving(loc)) + return INITIALIZE_HINT_QDEL + living_host = loc + +/obj/machinery/camera/silicon/Destroy() + if(!isnull(living_host)) + if(living_host.has_alert(ALERT_SILICON_RECORDING)) + living_host.clear_alert(ALERT_SILICON_RECORDING) + living_host = null + sources_watching = null + return ..() + +/obj/machinery/camera/silicon/on_start_watching(datum/source) + LAZYADD(sources_watching, source) + living_host.throw_alert(ALERT_SILICON_RECORDING, /atom/movable/screen/alert/being_recorded) + +/obj/machinery/camera/silicon/on_stop_watching(datum/no_longer_watching) + LAZYREMOVE(sources_watching, no_longer_watching) + if(!LAZYLEN(sources_watching) && living_host.has_alert(ALERT_SILICON_RECORDING)) + living_host.clear_alert(ALERT_SILICON_RECORDING) + +///The alert given to silicons being watched. +/atom/movable/screen/alert/being_recorded + icon_state = "recording" + name = "Recorded" + desc = "Someone is currently watching your internal camera through a camera console." diff --git a/code/game/machinery/computer/camera.dm b/code/game/machinery/computer/camera.dm index 991d0cb3f06..8725bda22f5 100644 --- a/code/game/machinery/computer/camera.dm +++ b/code/game/machinery/computer/camera.dm @@ -7,6 +7,7 @@ icon_keyboard = "security_key" circuit = /obj/item/circuitboard/computer/security light_color = COLOR_SOFT_RED + interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON|INTERACT_MACHINE_REQUIRES_SIGHT var/list/network = list(CAMERANET_NETWORK_SS13) var/obj/machinery/camera/active_camera @@ -17,8 +18,6 @@ // Stuff needed to render the map var/atom/movable/screen/map_view/camera/cam_screen - interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON|INTERACT_MACHINE_REQUIRES_SIGHT - /obj/machinery/computer/security/Initialize(mapload) . = ..() // Map name has to start and end with an A-Z character, @@ -99,12 +98,14 @@ return if(action == "switch_camera") + active_camera?.on_stop_watching(src) var/obj/machinery/camera/selected_camera = locate(params["camera"]) in GLOB.cameranet.cameras active_camera = selected_camera if(isnull(active_camera)) return TRUE + active_camera.on_start_watching(src) update_active_camera_screen() return TRUE @@ -149,9 +150,10 @@ // Living creature or not, we remove you anyway. concurrent_users -= user_ref // Unregister map objects - cam_screen.hide_from(user) + cam_screen?.hide_from(user) // Turn off the console if(length(concurrent_users) == 0 && is_living) + active_camera?.on_stop_watching(src) active_camera = null last_camera_turf = null playsound(src, 'sound/machines/terminal/terminal_off.ogg', 25, FALSE) diff --git a/code/modules/mob/living/basic/drone/_drone.dm b/code/modules/mob/living/basic/drone/_drone.dm index 8807f35e038..61650ce9387 100644 --- a/code/modules/mob/living/basic/drone/_drone.dm +++ b/code/modules/mob/living/basic/drone/_drone.dm @@ -70,6 +70,8 @@ var/obj/item/default_storage = /obj/item/storage/drone_tools /// Default [/mob/living/basic/drone/var/head] item var/obj/item/default_headwear + ///The camera built into the drone which allows it to be seen through cameras. + var/obj/machinery/camera/silicon/built_in_camera /** * icon_state of drone from icons/mobs/drone.dmi * @@ -180,6 +182,12 @@ AddComponent(/datum/component/simple_access, SSid_access.get_region_access_list(list(REGION_ALL_GLOBAL))) AddComponent(/datum/component/personal_crafting) // Kind of hard to be a drone and not be able to make tiles + //only shy drones (so all the station ones) gets a camera. + if(shy) + built_in_camera = new(src) + built_in_camera.c_tag = real_name + built_in_camera.network = list(CAMERANET_NETWORK_SS13) + if(default_storage) var/obj/item/storage = new default_storage(src) equip_to_slot_or_del(storage, ITEM_SLOT_DEX_STORAGE) @@ -236,6 +244,7 @@ /mob/living/basic/drone/Destroy() GLOB.drones_list -= src QDEL_NULL(listener) + QDEL_NULL(built_in_camera) return ..() /mob/living/basic/drone/Login() @@ -391,3 +400,8 @@ /mob/living/basic/drone/electrocute_act(shock_damage, source, siemens_coeff, flags = NONE) return FALSE //So they don't die trying to fix wiring + +/mob/living/basic/drone/can_track(mob/living/user) + if(built_in_camera?.can_use()) + return TRUE + return ..() diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index 349a3f71535..73de0cc05dc 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -77,8 +77,8 @@ GLOB.ai_list += src GLOB.shuttle_caller_list += src - builtInCamera = new (src) - builtInCamera.network = list(CAMERANET_NETWORK_SS13) + //They aren't given a c_tag so they don't show up in camera consoles + builtInCamera = new(src) ai_tracking_tool = new(src) RegisterSignal(ai_tracking_tool, COMSIG_TRACKABLE_TRACKING_TARGET, PROC_REF(on_track_target)) diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index fb337b8df09..a9100f82de4 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -48,12 +48,10 @@ lawupdate = FALSE if(!scrambledcodes && !builtInCamera) - builtInCamera = new (src) + builtInCamera = new(src) builtInCamera.c_tag = real_name - builtInCamera.network = list(CAMERANET_NETWORK_SS13) - builtInCamera.internal_light = FALSE if(wires.is_cut(WIRE_CAMERA)) - builtInCamera.camera_enabled = 0 + builtInCamera.camera_enabled = FALSE update_icons() . = ..() diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm index 5b8af577a20..e1672785be5 100644 --- a/code/modules/mob/living/silicon/silicon.dm +++ b/code/modules/mob/living/silicon/silicon.dm @@ -42,7 +42,7 @@ var/list/silicon_huds = list(DATA_HUD_MEDICAL_ADVANCED, DATA_HUD_SECURITY_ADVANCED, DATA_HUD_DIAGNOSTIC) var/law_change_counter = 0 - var/obj/machinery/camera/builtInCamera = null + var/obj/machinery/camera/silicon/builtInCamera var/updating = FALSE //portable camera camerachunk update ///Whether we have been emagged var/emagged = FALSE diff --git a/code/modules/modular_computers/file_system/programs/secureye.dm b/code/modules/modular_computers/file_system/programs/secureye.dm index 0c3208ee203..4a90f25f5b0 100644 --- a/code/modules/modular_computers/file_system/programs/secureye.dm +++ b/code/modules/modular_computers/file_system/programs/secureye.dm @@ -30,7 +30,7 @@ // Stuff needed to render the map var/atom/movable/screen/map_view/camera/cam_screen - ///Internal tracker used to find a specific person and keep them on cameras. + ///Internal tracker used to find a specific person and keep them on cameras, only used if this is a 'spying' console. var/datum/trackable/internal_tracker ///Syndicate subtype that has no access restrictions and is available on Syndinet @@ -52,13 +52,21 @@ ) spying = TRUE +///Human AI subtype that has access to most networks on the station and can't be copied. /datum/computer_file/program/secureye/human_ai filename = "Overseer" filedesc = "OverSeer" run_access = list(ACCESS_MINISAT) can_run_on_flags = PROGRAM_PDA program_flags = PROGRAM_UNIQUE_COPY - network = list("ss13", "mine", "rd", "labor", "ordnance", "minisat") + network = list( + CAMERANET_NETWORK_SS13, + CAMERANET_NETWORK_MINE, + CAMERANET_NETWORK_RD, + CAMERANET_NETWORK_LABOR, + CAMERANET_NETWORK_ORDNANCE, + CAMERANET_NETWORK_MINISAT, + ) spying = TRUE /datum/computer_file/program/secureye/on_install(datum/computer_file/source, obj/item/modular_computer/computer_installing) @@ -130,15 +138,21 @@ return switch(action) if("switch_camera") + var/obj/machinery/camera/active_camera = camera_ref?.resolve() + if(!spying && active_camera) + active_camera.on_stop_watching(src) + + if(!spying) + playsound(computer, SFX_TERMINAL_TYPE, 25, FALSE) + var/obj/machinery/camera/selected_camera = locate(params["camera"]) in GLOB.cameranet.cameras if(selected_camera) camera_ref = WEAKREF(selected_camera) else camera_ref = null - if(!spying) - playsound(computer, SFX_TERMINAL_TYPE, 25, FALSE) - if(isnull(camera_ref)) return TRUE + if(!spying) + selected_camera.on_start_watching(src) if(internal_tracker) internal_tracker.reset_tracking() @@ -181,6 +195,9 @@ cam_screen.hide_from(user) // Turn off the console if(length(concurrent_users) == 0 && is_living) + var/obj/machinery/camera/active_camera = camera_ref?.resolve() + if(!spying && active_camera) + active_camera.on_stop_watching(src) camera_ref = null last_camera_turf = null if(!spying) diff --git a/icons/hud/screen_alert.dmi b/icons/hud/screen_alert.dmi index 8e6bdfd01b1..8ebfdd14b4e 100755 Binary files a/icons/hud/screen_alert.dmi and b/icons/hud/screen_alert.dmi differ diff --git a/tgstation.dme b/tgstation.dme index 630f3d8586c..106d274a667 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -2319,6 +2319,7 @@ #include "code\game\machinery\camera\camera_construction.dm" #include "code\game\machinery\camera\motion.dm" #include "code\game\machinery\camera\presets.dm" +#include "code\game\machinery\camera\silicon_camera.dm" #include "code\game\machinery\camera\trackable.dm" #include "code\game\machinery\computer\_computer.dm" #include "code\game\machinery\computer\accounting.dm"