From 109854659bd889f425856d34d8b6daeaa10b98bd Mon Sep 17 00:00:00 2001 From: ascio <81930475+asciodev@users.noreply.github.com> Date: Sun, 5 Jan 2025 14:58:26 -0500 Subject: [PATCH 1/6] Fix unmoving video cam feeds. This change refactor the screen-setting logic in camera consoles to its own method, and additionally calls that method whenever a video camera is moved, if that video camera's feed is being watched by a camera console. --- .../game/machinery/computer/camera_console.dm | 37 +++++++++++-------- .../mob/living/carbon/human/human_movement.dm | 8 ++++ code/modules/paperwork/photography.dm | 9 +++++ 3 files changed, 38 insertions(+), 16 deletions(-) diff --git a/code/game/machinery/computer/camera_console.dm b/code/game/machinery/computer/camera_console.dm index dfb4e1f08ea4..553fabee7fda 100644 --- a/code/game/machinery/computer/camera_console.dm +++ b/code/game/machinery/computer/camera_console.dm @@ -11,6 +11,7 @@ var/list/network = list("SS13","Mining Outpost") var/obj/machinery/camera/active_camera var/list/watchers = list() + var/list/living_watchers = list() // Stuff needed to render the map var/map_name @@ -64,10 +65,11 @@ var/is_living = isliving(user) // Ghosts shouldn't count towards concurrent users, which produces // an audible terminal_on click. + watchers += user_uid if(is_living) - watchers += user_uid + living_watchers += user_uid // Turn on the console - if(length(watchers) == 1 && is_living) + if(length(living_watchers) == 1 && is_living) if(!silent_console) playsound(src, 'sound/machines/terminal_on.ogg', 25, FALSE) use_power(active_power_consumption) @@ -89,6 +91,7 @@ /obj/machinery/computer/security/ui_close(mob/user) ..() watchers -= user.UID() + living_watchers -= user.UID() user.client.clear_map(map_name) /obj/machinery/computer/security/ui_data() @@ -126,25 +129,27 @@ active_camera = C if(!silent_console) playsound(src, get_sfx("terminal_type"), 25, FALSE) + return update_viewer() - // Show static if can't use the camera - if(!active_camera?.can_use()) - show_camera_static() - return TRUE +/obj/machinery/computer/security/proc/update_viewer() + // Show static if can't use the camera + if(!active_camera?.can_use()) + show_camera_static() + return TRUE - var/list/visible_turfs = list() - for(var/turf/T in view(C.view_range, get_turf(C))) - visible_turfs += T + var/list/visible_turfs = list() + for(var/turf/T in view(active_camera.view_range, get_turf(active_camera))) + visible_turfs += T - 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 + 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) + cam_screen.vis_contents = visible_turfs + cam_background.icon_state = "clear" + cam_background.fill_rect(1, 1, size_x, size_y) - return TRUE + return TRUE // Returns the list of cameras accessible from this computer /obj/machinery/computer/security/proc/get_available_cameras() diff --git a/code/modules/mob/living/carbon/human/human_movement.dm b/code/modules/mob/living/carbon/human/human_movement.dm index f9d728abfab8..9d14f0fd8b02 100644 --- a/code/modules/mob/living/carbon/human/human_movement.dm +++ b/code/modules/mob/living/carbon/human/human_movement.dm @@ -55,3 +55,11 @@ if(bloody_feet[blood_state] > BLOOD_LOSS_IN_SPREAD) createFootprintsFrom(src, dir, T) update_inv_shoes() + + var/obj/item/videocam/cam = belt + if(!istype(cam)) + cam = r_hand + if(!istype(cam)) + cam = l_hand + if(istype(cam)) + cam.update_viewers() diff --git a/code/modules/paperwork/photography.dm b/code/modules/paperwork/photography.dm index 38bd6bf1b3cc..637ede43131f 100644 --- a/code/modules/paperwork/photography.dm +++ b/code/modules/paperwork/photography.dm @@ -572,6 +572,7 @@ GLOBAL_LIST_INIT(SpookyGhosts, list("ghost","shade","shade2","ghost-narsie","hor materials = list(MAT_METAL = 1000, MAT_GLASS = 500) var/on = FALSE var/video_cooldown = 0 + var/update_viewer_cooldown = 0 var/obj/machinery/camera/camera var/icon_on = "videocam_on" var/icon_off = "videocam" @@ -630,6 +631,14 @@ GLOBAL_LIST_INIT(SpookyGhosts, list("ghost","shade","shade2","ghost-narsie","hor if(T.watchers[M] == camera) T.atom_say(msg) +/obj/item/videocam/proc/update_viewers() + if(!camera || !on || world.timeofday < update_viewer_cooldown) + return TRUE + update_viewer_cooldown = world.timeofday + 3 SECONDS + for(var/obj/machinery/computer/security/telescreen/T in GLOB.machines) + if(T.active_camera == camera && length(T.watchers)) + T.update_viewer() + /obj/item/videocam/advanced name = "advanced video camera" desc = "This video camera allows you to send live feeds even when attached to a belt." From 5a93b4d0064aee99ae3ed3accea34d35f5d8a380 Mon Sep 17 00:00:00 2001 From: ascio <81930475+asciodev@users.noreply.github.com> Date: Sun, 5 Jan 2025 16:52:22 -0500 Subject: [PATCH 2/6] Reduce follow lag and add stabilization to vidcams Extracts camera follow lag into a variable on video cameras called `update_viewer_cooldown_rate` and reduces it from 3 to 2 seconds. Additionally, adds another variable on video cameras called `update_viewer_stabilization_rate`, which updates all tuned-in camera consoles with the new position of the video camera a final time, so the feed remains centered on the camera. --- code/modules/paperwork/photography.dm | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/code/modules/paperwork/photography.dm b/code/modules/paperwork/photography.dm index 637ede43131f..411fc4a768f0 100644 --- a/code/modules/paperwork/photography.dm +++ b/code/modules/paperwork/photography.dm @@ -572,7 +572,14 @@ GLOBAL_LIST_INIT(SpookyGhosts, list("ghost","shade","shade2","ghost-narsie","hor materials = list(MAT_METAL = 1000, MAT_GLASS = 500) var/on = FALSE var/video_cooldown = 0 + /// The default amount of time the camera will wait before updating consoles with its new position. + var/update_viewer_cooldown_rate = 2 SECONDS + /// The next time that the camera should update consoles with its new position. var/update_viewer_cooldown = 0 + /// The amount of time after each move the camera will wait before updating consoles + /// with its position a final time. It should be higher than `update_viewer_cooldown_rate` to ensure + /// that it isn't debounced. (This ensures the feed tries to remain centered on the camera.) + var/update_viewer_stabilize_rate = 3 SECONDS var/obj/machinery/camera/camera var/icon_on = "videocam_on" var/icon_off = "videocam" @@ -632,12 +639,13 @@ GLOBAL_LIST_INIT(SpookyGhosts, list("ghost","shade","shade2","ghost-narsie","hor T.atom_say(msg) /obj/item/videocam/proc/update_viewers() - if(!camera || !on || world.timeofday < update_viewer_cooldown) - return TRUE - update_viewer_cooldown = world.timeofday + 3 SECONDS + if(!camera || world.timeofday < update_viewer_cooldown) + return + update_viewer_cooldown = world.timeofday + update_viewer_cooldown_rate for(var/obj/machinery/computer/security/telescreen/T in GLOB.machines) if(T.active_camera == camera && length(T.watchers)) T.update_viewer() + addtimer(CALLBACK(src, PROC_REF(update_viewers)), update_viewer_stabilize_rate) /obj/item/videocam/advanced name = "advanced video camera" From 096efd1a3f5b7566989aef5d24f1457e9a9cc7e0 Mon Sep 17 00:00:00 2001 From: ascio <81930475+asciodev@users.noreply.github.com> Date: Sun, 5 Jan 2025 21:08:35 -0500 Subject: [PATCH 3/6] Change video camera feed follow to use signals --- code/modules/mob/living/carbon/human/human_movement.dm | 8 -------- code/modules/paperwork/photography.dm | 5 +++++ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/code/modules/mob/living/carbon/human/human_movement.dm b/code/modules/mob/living/carbon/human/human_movement.dm index 9d14f0fd8b02..f9d728abfab8 100644 --- a/code/modules/mob/living/carbon/human/human_movement.dm +++ b/code/modules/mob/living/carbon/human/human_movement.dm @@ -55,11 +55,3 @@ if(bloody_feet[blood_state] > BLOOD_LOSS_IN_SPREAD) createFootprintsFrom(src, dir, T) update_inv_shoes() - - var/obj/item/videocam/cam = belt - if(!istype(cam)) - cam = r_hand - if(!istype(cam)) - cam = l_hand - if(istype(cam)) - cam.update_viewers() diff --git a/code/modules/paperwork/photography.dm b/code/modules/paperwork/photography.dm index 411fc4a768f0..92d9b8df65fd 100644 --- a/code/modules/paperwork/photography.dm +++ b/code/modules/paperwork/photography.dm @@ -584,15 +584,19 @@ GLOBAL_LIST_INIT(SpookyGhosts, list("ghost","shade","shade2","ghost-narsie","hor var/icon_on = "videocam_on" var/icon_off = "videocam" var/canhear_range = 7 + var/mob/holder /obj/item/videocam/proc/camera_state(mob/living/carbon/user) if(!on) + holder = loc + RegisterSignal(holder, COMSIG_MOVABLE_MOVED, PROC_REF(update_viewers)) on = TRUE camera = new /obj/machinery/camera(src) icon_state = icon_on camera.network = list("news") camera.c_tag = user.name else + UnregisterSignal(holder, COMSIG_MOVABLE_MOVED) on = FALSE icon_state = icon_off camera.c_tag = null @@ -639,6 +643,7 @@ GLOBAL_LIST_INIT(SpookyGhosts, list("ghost","shade","shade2","ghost-narsie","hor T.atom_say(msg) /obj/item/videocam/proc/update_viewers() + var/mob/holder = loc if(!camera || world.timeofday < update_viewer_cooldown) return update_viewer_cooldown = world.timeofday + update_viewer_cooldown_rate From 76245d3c3f7ce37ded7aabe4dc6439a78cba7073 Mon Sep 17 00:00:00 2001 From: ascio <81930475+asciodev@users.noreply.github.com> Date: Thu, 9 Jan 2025 20:18:40 -0500 Subject: [PATCH 4/6] Fix unmoving camera console feeds Makes camera consoles refresh their feeds every 2 seconds as long as they have active watchers. This fixes camera bugs, ERT cameras, video cameras (including the journalist's advanced video camera), and any other mobile cameras on a console's camera network. --- code/_globalvars/lists/objects.dm | 1 + .../game/machinery/computer/camera_console.dm | 30 ++++++++++++++++- code/modules/paperwork/photography.dm | 32 ++++--------------- 3 files changed, 36 insertions(+), 27 deletions(-) diff --git a/code/_globalvars/lists/objects.dm b/code/_globalvars/lists/objects.dm index f918fe47277f..efa225f48b7c 100644 --- a/code/_globalvars/lists/objects.dm +++ b/code/_globalvars/lists/objects.dm @@ -20,6 +20,7 @@ GLOBAL_LIST_EMPTY(pandemics) GLOBAL_LIST_EMPTY(all_areas) GLOBAL_LIST_EMPTY(all_unique_areas) // List of all unique areas. AKA areas with there_can_be_many = FALSE GLOBAL_LIST_EMPTY(machines) +GLOBAL_LIST_EMPTY(telescreens) /// List of entertainment telescreens connected to the "news" cameranet GLOBAL_LIST_EMPTY(rcd_list) //list of Rapid Construction Devices. GLOBAL_LIST_EMPTY(apcs) diff --git a/code/game/machinery/computer/camera_console.dm b/code/game/machinery/computer/camera_console.dm index 553fabee7fda..b4550d3a77bc 100644 --- a/code/game/machinery/computer/camera_console.dm +++ b/code/game/machinery/computer/camera_console.dm @@ -1,3 +1,10 @@ +/// The amount of time a camera console actively being watched will wait between feed refreshes. +#define FOLLOW_COOLDOWN_RATE 2 SECONDS + +/// The root type of all camera consoles. When used, these open a UI that has a left-hand sidebar +/// displaying a list of active cameras in the console's assigned camera `network`. The bulk of the window +/// on the right hand side displays a camera feed of the selected camera. This feed will attempt to refresh +/// at periods of `FOLLOW_COOLDOWN_RATE` as long as there are active watchers, living or dead. /obj/machinery/computer/security name = "security camera console" desc = "Used to access the various cameras networks on the station." @@ -10,7 +17,9 @@ var/list/network = list("SS13","Mining Outpost") var/obj/machinery/camera/active_camera + /// The list of total watchers, living and dead, of this console. var/list/watchers = list() + /// The list of living watchers of this console. Used for playing a "terminal on" sound on first live viewer. var/list/living_watchers = list() // Stuff needed to render the map @@ -21,12 +30,15 @@ var/list/cam_plane_masters var/atom/movable/screen/background/cam_background - // Parent object this camera is assigned to. Used for camera bugs + /// Parent object this camera is assigned to. Used for camera bugs var/atom/movable/parent /// is the console silent when switching cameras? var/silent_console = FALSE + /// Timer for the follow refresh cooldown; fires periodically every `FOLLOW_COOLDOWN_RATE` while there are watchers. + var/follow_cooldown + /obj/machinery/computer/security/ui_host() return parent ? parent : src @@ -47,6 +59,7 @@ cam_background.del_on_map_removal = FALSE /obj/machinery/computer/security/Destroy() + deltimer(follow_cooldown) qdel(cam_screen) qdel(cam_background) return ..() @@ -87,6 +100,14 @@ // Open UI ui = new(user, src, "CameraConsole", name) ui.open() + follow_cooldown = addtimer(CALLBACK(src, PROC_REF(refresh_feed)), FOLLOW_COOLDOWN_RATE, TIMER_UNIQUE) + +/obj/machinery/computer/security/proc/refresh_feed() + update_viewer() + if(length(watchers)) + follow_cooldown = addtimer(CALLBACK(src, PROC_REF(refresh_feed)), FOLLOW_COOLDOWN_RATE, TIMER_UNIQUE) + else if(follow_cooldown) + deltimer(follow_cooldown) /obj/machinery/computer/security/ui_close(mob/user) ..() @@ -238,6 +259,11 @@ /obj/machinery/computer/security/telescreen/entertainment/Initialize(mapload) . = ..() set_light(1, LIGHTING_MINIMUM_POWER) //so byond doesnt cull, and we get an emissive appearance + GLOB.telescreens += src + +/obj/machinery/computer/security/telescreen/entertainment/Destroy() + GLOB.telescreens -= src + return ..() /obj/machinery/computer/security/telescreen/entertainment/update_overlays() if(feeds_on) @@ -374,3 +400,5 @@ desc = "Used for watching the turbine vent."; network = list("Turbine") circuit = /obj/item/circuitboard/camera/turbine + +#undef FOLLOW_COOLDOWN_RATE diff --git a/code/modules/paperwork/photography.dm b/code/modules/paperwork/photography.dm index 92d9b8df65fd..f63ecdfb8c41 100644 --- a/code/modules/paperwork/photography.dm +++ b/code/modules/paperwork/photography.dm @@ -561,6 +561,7 @@ GLOBAL_LIST_INIT(SpookyGhosts, list("ghost","shade","shade2","ghost-narsie","hor /************** *video camera * ***************/ +/// The amount of time after being turned off that the camera is too hot to turn back on. #define CAMERA_STATE_COOLDOWN 2 SECONDS /obj/item/videocam @@ -571,47 +572,36 @@ GLOBAL_LIST_INIT(SpookyGhosts, list("ghost","shade","shade2","ghost-narsie","hor w_class = WEIGHT_CLASS_NORMAL materials = list(MAT_METAL = 1000, MAT_GLASS = 500) var/on = FALSE - var/video_cooldown = 0 - /// The default amount of time the camera will wait before updating consoles with its new position. - var/update_viewer_cooldown_rate = 2 SECONDS - /// The next time that the camera should update consoles with its new position. - var/update_viewer_cooldown = 0 - /// The amount of time after each move the camera will wait before updating consoles - /// with its position a final time. It should be higher than `update_viewer_cooldown_rate` to ensure - /// that it isn't debounced. (This ensures the feed tries to remain centered on the camera.) - var/update_viewer_stabilize_rate = 3 SECONDS var/obj/machinery/camera/camera var/icon_on = "videocam_on" var/icon_off = "videocam" var/canhear_range = 7 - var/mob/holder + + COOLDOWN_DECLARE(video_cooldown) /obj/item/videocam/proc/camera_state(mob/living/carbon/user) if(!on) - holder = loc - RegisterSignal(holder, COMSIG_MOVABLE_MOVED, PROC_REF(update_viewers)) on = TRUE camera = new /obj/machinery/camera(src) icon_state = icon_on camera.network = list("news") camera.c_tag = user.name else - UnregisterSignal(holder, COMSIG_MOVABLE_MOVED) on = FALSE icon_state = icon_off camera.c_tag = null QDEL_NULL(camera) visible_message("The video camera has been turned [on ? "on" : "off"].") - for(var/obj/machinery/computer/security/telescreen/entertainment/TV in GLOB.machines) + for(var/obj/machinery/computer/security/telescreen/entertainment/TV in GLOB.telescreens) if(on) TV.feeds_on++ else TV.feeds_on-- TV.update_icon(UPDATE_OVERLAYS) - video_cooldown = world.time + CAMERA_STATE_COOLDOWN + COOLDOWN_START(src, video_cooldown, CAMERA_STATE_COOLDOWN) /obj/item/videocam/attack_self__legacy__attackchain(mob/user) - if(world.time < video_cooldown) + if(!COOLDOWN_FINISHED(src, video_cooldown)) to_chat(user, "[src] is overheating, give it some time.") return camera_state(user) @@ -642,16 +632,6 @@ GLOBAL_LIST_INIT(SpookyGhosts, list("ghost","shade","shade2","ghost-narsie","hor if(T.watchers[M] == camera) T.atom_say(msg) -/obj/item/videocam/proc/update_viewers() - var/mob/holder = loc - if(!camera || world.timeofday < update_viewer_cooldown) - return - update_viewer_cooldown = world.timeofday + update_viewer_cooldown_rate - for(var/obj/machinery/computer/security/telescreen/T in GLOB.machines) - if(T.active_camera == camera && length(T.watchers)) - T.update_viewer() - addtimer(CALLBACK(src, PROC_REF(update_viewers)), update_viewer_stabilize_rate) - /obj/item/videocam/advanced name = "advanced video camera" desc = "This video camera allows you to send live feeds even when attached to a belt." From e230b589138610a1e6df7f216e5a47a04454236d Mon Sep 17 00:00:00 2001 From: ascio <81930475+asciodev@users.noreply.github.com> Date: Fri, 17 Jan 2025 17:13:36 -0500 Subject: [PATCH 5/6] Replace manual camera feed cooldown with process() Co-authored-by: Charlie Nolan --- .../game/machinery/computer/camera_console.dm | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/code/game/machinery/computer/camera_console.dm b/code/game/machinery/computer/camera_console.dm index a3f17153403c..03deb8a30825 100644 --- a/code/game/machinery/computer/camera_console.dm +++ b/code/game/machinery/computer/camera_console.dm @@ -1,6 +1,3 @@ -/// The amount of time a camera console actively being watched will wait between feed refreshes. -#define FOLLOW_COOLDOWN_RATE 2 SECONDS - /// The root type of all camera consoles. When used, these open a UI that has a left-hand sidebar /// displaying a list of active cameras in the console's assigned camera `network`. The bulk of the window /// on the right hand side displays a camera feed of the selected camera. This feed will attempt to refresh @@ -36,9 +33,6 @@ /// is the console silent when switching cameras? var/silent_console = FALSE - /// Timer for the follow refresh cooldown; fires periodically every `FOLLOW_COOLDOWN_RATE` while there are watchers. - var/follow_cooldown - /obj/machinery/computer/security/ui_host() return parent ? parent : src @@ -59,7 +53,7 @@ cam_background.del_on_map_removal = FALSE /obj/machinery/computer/security/Destroy() - deltimer(follow_cooldown) + STOP_PROCESSING(SSobj, src) qdel(cam_screen) qdel(cam_background) return ..() @@ -100,14 +94,13 @@ // Open UI ui = new(user, src, "CameraConsole", name) ui.open() - follow_cooldown = addtimer(CALLBACK(src, PROC_REF(refresh_feed)), FOLLOW_COOLDOWN_RATE, TIMER_UNIQUE) + START_PROCESSING(SSobj, src) -/obj/machinery/computer/security/proc/refresh_feed() +/obj/machinery/computer/security/process() update_viewer() if(length(watchers)) - follow_cooldown = addtimer(CALLBACK(src, PROC_REF(refresh_feed)), FOLLOW_COOLDOWN_RATE, TIMER_UNIQUE) - else if(follow_cooldown) - deltimer(follow_cooldown) + return + STOP_PROCESSING(SSobj, src) /obj/machinery/computer/security/ui_close(mob/user) ..() @@ -400,5 +393,3 @@ desc = "Used for watching the turbine vent."; network = list("Turbine") circuit = /obj/item/circuitboard/camera/turbine - -#undef FOLLOW_COOLDOWN_RATE From 5a17bcf48a058af9cab3f1935e13770badf0ce5d Mon Sep 17 00:00:00 2001 From: ascio <81930475+asciodev@users.noreply.github.com> Date: Fri, 17 Jan 2025 17:43:11 -0500 Subject: [PATCH 6/6] Update Camera Console autodoc to reflect process() implementation --- code/game/machinery/computer/camera_console.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/game/machinery/computer/camera_console.dm b/code/game/machinery/computer/camera_console.dm index 03deb8a30825..4825064a7d69 100644 --- a/code/game/machinery/computer/camera_console.dm +++ b/code/game/machinery/computer/camera_console.dm @@ -1,7 +1,7 @@ /// The root type of all camera consoles. When used, these open a UI that has a left-hand sidebar /// displaying a list of active cameras in the console's assigned camera `network`. The bulk of the window /// on the right hand side displays a camera feed of the selected camera. This feed will attempt to refresh -/// at periods of `FOLLOW_COOLDOWN_RATE` as long as there are active watchers, living or dead. +/// every time process() is called by SSobj as long as there are active watchers, living or dead. /obj/machinery/computer/security name = "security camera console" desc = "Used to access the various cameras networks on the station."