diff --git a/modular_pentest/modules/xenobiology/code/xenobio_camera.dm b/modular_pentest/modules/xenobiology/code/xenobio_camera.dm index bd0ab5dd0bf..e2cddf309db 100644 --- a/modular_pentest/modules/xenobiology/code/xenobio_camera.dm +++ b/modular_pentest/modules/xenobiology/code/xenobio_camera.dm @@ -17,7 +17,8 @@ /mob/camera/aiEye/remote/xenobio/setLoc(t) var/area/new_area = get_area(t) - if(new_area && new_area.name == allowed_area || new_area && (new_area.area_flags & XENOBIOLOGY_COMPATIBLE)) + // Only allow movement within the same area name + if(new_area && new_area.name == allowed_area) return ..() else return @@ -25,7 +26,7 @@ /obj/machinery/computer/camera_advanced/xenobio name = "Slime management console" desc = "A computer used for remotely handling slimes." - networks = list("ss13") + networks = list("xenobio_local") // Dummy network that doesn't match anything circuit = /obj/item/circuitboard/computer/xenobiology var/datum/action/innate/slime_place/slime_place_action var/datum/action/innate/slime_pick_up/slime_up_action @@ -41,6 +42,9 @@ var/max_slimes = 5 var/monkeys = 0 + /// Internal camera built into the console as a fallback + var/obj/machinery/camera/internal_camera + icon_screen = "slime_comp" icon_keyboard = "rd_key" @@ -56,6 +60,13 @@ potion_action = new hotkey_help = new stored_slimes = list() + + // Create internal camera + internal_camera = new /obj/machinery/camera(src) + internal_camera.c_tag = "[src] Internal Camera" + internal_camera.network = list("xenobio_internal") + internal_camera.internal_light = FALSE // Don't light up when viewed + for(var/obj/machinery/monkey_recycler/recycler in GLOB.monkey_recyclers) if(get_area(recycler.loc) == get_area(loc)) connected_recycler = recycler @@ -70,6 +81,7 @@ if(connected_recycler) connected_recycler.connected -= src connected_recycler = null + QDEL_NULL(internal_camera) // Clean up our camera return ..() /obj/machinery/computer/camera_advanced/xenobio/handle_atom_del(atom/A) @@ -87,8 +99,21 @@ eyeobj.icon_state = "generic_camera" /obj/machinery/computer/camera_advanced/xenobio/GrantActions(mob/living/user) - ..() - + // Don't call parent - we're replacing the jump action + if(off_action) + off_action.target = user + off_action.Grant(user) + actions += off_action + + // Use our custom xenobio jump action instead of the parent's + if(!jump_action || !istype(jump_action, /datum/action/innate/camera_jump/xenobio)) + jump_action = new /datum/action/innate/camera_jump/xenobio() + if(jump_action) + jump_action.target = user + jump_action.Grant(user) + actions += jump_action + + // Rest of the xenobio-specific actions if(slime_up_action) slime_up_action.target = src slime_up_action.Grant(user) @@ -176,6 +201,27 @@ return ..() +/obj/machinery/computer/camera_advanced/xenobio/attack_hand(mob/user, list/modifiers) + . = ..() + if(.) + return + if(!is_operational) + return + + var/mob/living/L = user + if(!eyeobj) + CreateEye() + + if(!eyeobj.eye_initialized) + // Start at internal camera if it exists, otherwise at console + var/turf/starting_turf = internal_camera ? get_turf(internal_camera) : get_turf(src) + eyeobj.eye_initialized = TRUE + give_eye_control(L) + eyeobj.setLoc(starting_turf, TRUE) + else + give_eye_control(L) + eyeobj.setLoc(eyeobj.loc, TRUE) + /obj/machinery/computer/camera_advanced/xenobio/multitool_act(mob/living/user, obj/item/multitool/I) . = ..() if (istype(I) && istype(I.buffer,/obj/machinery/monkey_recycler)) @@ -184,6 +230,84 @@ connected_recycler.connected += src return TRUE +/obj/machinery/computer/camera_advanced/xenobio/proc/get_area_cameras() + var/area/console_area = get_area(src) + if(!console_area) + // Emergency fallback - just return our internal camera + if(internal_camera) + return list(internal_camera) + return list() + + var/list/area_cameras = list() + + // First, add cameras from the area + for(var/obj/machinery/camera/C in console_area.cameras) + if(C.can_use() && C != internal_camera) // Don't double-add our internal camera + area_cameras += C + + // Always include our internal camera as a fallback option + if(internal_camera && internal_camera.can_use()) + area_cameras += internal_camera + + // If we found no cameras at all, at least return our internal one + if(!area_cameras.len && internal_camera) + return list(internal_camera) + + return area_cameras + +/datum/action/innate/camera_jump/xenobio + // Inherits from the parent camera_jump action + +/datum/action/innate/camera_jump/xenobio/Activate() + if(!target || !isliving(target)) + return + var/mob/living/C = target + var/mob/camera/aiEye/remote/remote_eye = C.remote_control + var/obj/machinery/computer/camera_advanced/xenobio/origin = remote_eye.origin + + // Get cameras in our area (includes internal camera) + var/list/area_cameras = origin.get_area_cameras() + if(!area_cameras.len) + to_chat(C, span_warning("No cameras detected.")) + return + + camera_sort(area_cameras) + + var/list/T = list() + for(var/obj/machinery/camera/cam in area_cameras) + var/cam_name = cam.c_tag + // Mark the internal camera specially so users know it's the console's own camera + if(cam == origin.internal_camera) + cam_name = "[cam.c_tag] (Console)" + T["[cam_name][cam.can_use() ? null : " (Deactivated)"]"] = cam + + playsound(origin, 'sound/machines/terminal_prompt.ogg', 25, FALSE) + var/camera = input("Choose which camera you want to view", "Cameras") as null|anything in T + var/obj/machinery/camera/final = T[camera] + playsound(src, "terminal_type", 25, FALSE) + if(final) + playsound(origin, 'sound/machines/terminal_prompt_confirm.ogg', 25, FALSE) + remote_eye.setLoc(get_turf(final), TRUE) + C.overlay_fullscreen("flash", /atom/movable/screen/fullscreen/flash/static) + C.clear_fullscreen("flash", 3) + else + playsound(origin, 'sound/machines/terminal_prompt_deny.ogg', 25, FALSE) + +/obj/machinery/computer/camera_advanced/xenobio/examine(mob/user) + . = ..() + var/area/console_area = get_area(src) + var/camera_count = 0 + for(var/obj/machinery/camera/C in console_area.cameras) + if(C.can_use() && C != internal_camera) + camera_count++ + + if(camera_count > 0) + . += span_notice("Detects [camera_count] camera[camera_count == 1 ? "" : "s"] in [console_area.name].") + else + . += span_notice("No external cameras detected. Using internal console camera only.") + + . += span_info("The console has a built-in camera for remote viewing.") + /datum/action/innate/slime_place name = "Place Slimes" icon_icon = 'icons/mob/actions/actions_silicon.dmi'