diff --git a/code/__DEFINES/dcs/signals/signals_camera.dm b/code/__DEFINES/dcs/signals/signals_camera.dm new file mode 100644 index 00000000000..6ec142f54fa --- /dev/null +++ b/code/__DEFINES/dcs/signals/signals_camera.dm @@ -0,0 +1,2 @@ +///Signal sent when a /datum/trackable found a target: (datum/trackable/source, mob/living/target) +#define COMSIG_TRACKABLE_TRACKING_TARGET "comsig_trackable_tracking_target" diff --git a/code/__HELPERS/cameras.dm b/code/__HELPERS/cameras.dm new file mode 100644 index 00000000000..9d74f3fe71b --- /dev/null +++ b/code/__HELPERS/cameras.dm @@ -0,0 +1,35 @@ +/** + * get_camera_list + * + * Builds a list of all available cameras that can be seen to networks_available + * Args: + * networks_available - List of networks that we use to see which cameras are visible to it. + */ +/proc/get_camera_list(list/networks_available) + var/list/all_camera_list = list() + for(var/obj/machinery/camera/camera as anything in GLOB.cameranet.cameras) + all_camera_list.Add(camera) + + camera_sort(all_camera_list) + + var/list/usable_camera_list = list() + + for(var/obj/machinery/camera/camera as anything in all_camera_list) + var/list/tempnetwork = camera.network & networks_available + if(length(tempnetwork)) + usable_camera_list["[camera.c_tag][camera.can_use() ? null : " (Deactivated)"]"] = camera + + return usable_camera_list + +///Sorts the list of cameras by their c_tag to display to players. +/proc/camera_sort(list/camera_list) + var/obj/machinery/camera/camera_comparing_a + var/obj/machinery/camera/camera_comparing_b + + for(var/i = length(camera_list), i > 0, i--) + for(var/j = 1 to i - 1) + camera_comparing_a = camera_list[j] + camera_comparing_b = camera_list[j + 1] + if(sorttext(camera_comparing_a.c_tag, camera_comparing_b.c_tag) < 0) + camera_list.Swap(j, j + 1) + return camera_list diff --git a/code/_onclick/ai.dm b/code/_onclick/ai.dm index 5f5209a2217..c1074d5b3ec 100644 --- a/code/_onclick/ai.dm +++ b/code/_onclick/ai.dm @@ -11,7 +11,7 @@ return if(ismob(A)) - ai_actual_track(A) + ai_tracking_tool.set_tracked_mob(src, A.name) else A.move_camera_by_click() diff --git a/code/_onclick/hud/ai.dm b/code/_onclick/hud/ai.dm index 3cd50a700e7..5f687d19642 100644 --- a/code/_onclick/hud/ai.dm +++ b/code/_onclick/hud/ai.dm @@ -30,13 +30,11 @@ icon_state = "track" /atom/movable/screen/ai/camera_track/Click() - if(..()) + . = ..() + if(.) return var/mob/living/silicon/ai/AI = usr - var/target_name = tgui_input_list(AI, "Select a target", "Tracking", AI.trackable_mobs()) - if(isnull(target_name)) - return - AI.ai_camera_track(target_name) + AI.ai_camera_track() /atom/movable/screen/ai/camera_light name = "Toggle Camera Light" diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm index 716785105f0..4f22cf9424a 100644 --- a/code/game/machinery/camera/camera.dm +++ b/code/game/machinery/camera/camera.dm @@ -202,6 +202,13 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/camera/xray, 0) return FALSE return ..() +/obj/machinery/camera/attack_ai(mob/living/silicon/ai/user) + if (!istype(user)) + return + if (!can_use()) + return + user.switchCamera(src) + /obj/machinery/camera/proc/setViewRange(num = 7) src.view_range = num GLOB.cameranet.updateVisibility(src, 0) diff --git a/code/game/machinery/camera/trackable.dm b/code/game/machinery/camera/trackable.dm new file mode 100644 index 00000000000..86f6f4cfb90 --- /dev/null +++ b/code/game/machinery/camera/trackable.dm @@ -0,0 +1,128 @@ +///How many ticks to try to find a target before giving up. +#define CAMERA_TICK_LIMIT 10 + +/datum/trackable + ///Boolean on whether or not we are currently trying to track something. + var/tracking = FALSE + ///Reference to the atom that owns us, used for tracking. + var/atom/tracking_holder + + ///If there is a mob currently being tracked, this will be the weakref to it. + var/datum/weakref/tracked_mob + ///How many times we've failed to locate our target. + var/cameraticks = 0 + + ///List of all names that can be tracked. + VAR_PRIVATE/list/names = list() + ///List of all namecounts for mobs with the exact same name, just in-case. + VAR_PRIVATE/list/namecounts = list() + ///List of all humans trackable by cameras. + VAR_PRIVATE/static/list/humans = list() + ///List of all non-humans trackable by cameras, split so humans take priority. + VAR_PRIVATE/static/list/others = list() + +/datum/trackable/New(mob/source) + . = ..() + tracking_holder = source + RegisterSignal(tracking_holder, COMSIG_MOB_RESET_PERSPECTIVE, PROC_REF(cancel_target_tracking)) + +/datum/trackable/Destroy(force, ...) + tracking_holder = null + tracked_mob = null + STOP_PROCESSING(SSprocessing, src) + return ..() + +/datum/trackable/process() + var/mob/living/tracked_target = tracked_mob?.resolve() + if(!tracked_target || !tracking) + set_tracking(FALSE) + return + + if(tracked_target.can_track(tracking_holder)) + cameraticks = initial(cameraticks) + SEND_SIGNAL(tracking_holder, COMSIG_TRACKABLE_TRACKING_TARGET, tracked_target) + return + + if(cameraticks < CAMERA_TICK_LIMIT) + if(!cameraticks) + to_chat(tracking_holder, span_warning("Target is not near any active cameras. Attempting to reacquire...")) + cameraticks++ + return + + to_chat(tracking_holder, span_warning("Unable to reacquire, cancelling track...")) + cameraticks = initial(cameraticks) + set_tracking(FALSE) + +///Generates a list of trackable people by name, returning a list of Humans + Non-Humans that can be tracked. +/datum/trackable/proc/find_trackable_mobs() + RETURN_TYPE(/list) + + names.Cut() + namecounts.Cut() + + humans.Cut() + others.Cut() + + for(var/mob/living/living_mob as anything in GLOB.mob_living_list) + if(!living_mob.can_track(usr)) + continue + + var/name = living_mob.name + while(name in names) + namecounts[name]++ + name = "[name] ([namecounts[name]])" + names.Add(name) + namecounts[name] = 1 + + if(ishuman(living_mob)) + humans[name] = WEAKREF(living_mob) + else + others[name] = WEAKREF(living_mob) + + var/list/targets = sort_list(humans) + sort_list(others) + return targets + +///Toggles whether or not we're tracking something. Arg is whether it's on or off. +/datum/trackable/proc/set_tracking(on = FALSE) + if(on) + START_PROCESSING(SSprocessing, src) + tracking = TRUE + else + STOP_PROCESSING(SSprocessing, src) + tracking = FALSE + tracked_mob = null + +///Called by Signals, used to cancel tracking of a target. +/datum/trackable/proc/cancel_target_tracking(atom/source) + SIGNAL_HANDLER + set_tracking(FALSE) + +/** + * set_tracked_mob + * + * Sets a mob as being tracked, if a target is already provided then it will track that directly, + * otherwise it will give a tgui input list to find targets to track. + * Args: + * tracker - The person trying to track, used for feedback messages. This is not the same as tracking_holder + * tracked_mob_name - (Optional) The person being tracked, to skip the input list. + */ +/datum/trackable/proc/set_tracked_mob(mob/living/tracker, tracked_mob_name) + if(!tracker || tracker.stat == DEAD) + return + + if(tracked_mob_name) + find_trackable_mobs() //this is in case the tracked mob is newly/no-longer in camera field of view. + tracked_mob = isnull(humans[tracked_mob_name]) ? others[tracked_mob_name] : humans[tracked_mob_name] + if(isnull(tracked_mob)) + to_chat(tracker, span_notice("Target is not on or near any active cameras. Tracking failed.")) + return + to_chat(tracker, span_notice("Now tracking [tracked_mob_name] on camera.")) + else + var/target_name = tgui_input_list(tracker, "Select a target", "Tracking", find_trackable_mobs()) + if(!target_name || isnull(target_name)) + return + tracked_mob = isnull(humans[target_name]) ? others[target_name] : humans[target_name] + + set_tracking(TRUE) + +#undef CAMERA_TICK_LIMIT diff --git a/code/game/machinery/camera/tracking.dm b/code/game/machinery/camera/tracking.dm deleted file mode 100644 index fb73bf305c0..00000000000 --- a/code/game/machinery/camera/tracking.dm +++ /dev/null @@ -1,158 +0,0 @@ -/mob/living/silicon/ai/proc/get_camera_list() - var/list/L = list() - for (var/obj/machinery/camera/C as anything in GLOB.cameranet.cameras) - L.Add(C) - - camera_sort(L) - - var/list/T = list() - - for (var/obj/machinery/camera/C in L) - var/list/tempnetwork = C.network&src.network - if (length(tempnetwork)) - T["[C.c_tag][C.can_use() ? null : " (Deactivated)"]"] = C - - return T - -/mob/living/silicon/ai/proc/show_camera_list() - var/list/cameras = get_camera_list() - var/camera = tgui_input_list(src, "Choose which camera you want to view", "Cameras", cameras) - if(isnull(camera)) - return - if(isnull(cameras[camera])) - return - switchCamera(cameras[camera]) - -/datum/trackable - var/initialized = FALSE - var/list/names = list() - var/list/namecounts = list() - var/list/humans = list() - var/list/others = list() - -/mob/living/silicon/ai/proc/trackable_mobs() - track.initialized = TRUE - track.names.Cut() - track.namecounts.Cut() - track.humans.Cut() - track.others.Cut() - - if(usr.stat == DEAD) - return list() - - for(var/i in GLOB.mob_living_list) - var/mob/living/L = i - if(!L.can_track(usr)) - continue - - var/name = L.name - while(name in track.names) - track.namecounts[name]++ - name = "[name] ([track.namecounts[name]])" - track.names.Add(name) - track.namecounts[name] = 1 - - if(ishuman(L)) - track.humans[name] = WEAKREF(L) - else - track.others[name] = WEAKREF(L) - - var/list/targets = sort_list(track.humans) + sort_list(track.others) - - return targets - -/mob/living/silicon/ai/verb/ai_camera_track(target_name in trackable_mobs()) - set name = "track" - set hidden = TRUE //Don't display it on the verb lists. This verb exists purely so you can type "track Oldman Robustin" and follow his ass - - if(!target_name) - return - - if(!track.initialized) - trackable_mobs() - - var/datum/weakref/target = (isnull(track.humans[target_name]) ? track.others[target_name] : track.humans[target_name]) - - ai_actual_track(target?.resolve()) - -/mob/living/silicon/ai/proc/ai_actual_track(mob/living/target) - if(!istype(target)) - return - var/mob/living/silicon/ai/U = usr - - U.cameraFollow = target - U.tracking = 1 - - if(!target || !target.can_track(usr)) - to_chat(U, span_warning("Target is not near any active cameras.")) - U.cameraFollow = null - return - - to_chat(U, span_notice("Now tracking [target.get_visible_name()] on camera.")) - - INVOKE_ASYNC(src, PROC_REF(do_track), target, U) - -/mob/living/silicon/ai/proc/do_track(mob/living/target, mob/living/silicon/ai/U) - var/cameraticks = 0 - - while(U.cameraFollow == target) - if(U.cameraFollow == null) - return - - if(!target.can_track(usr)) - U.tracking = TRUE - if(!cameraticks) - to_chat(U, span_warning("Target is not near any active cameras. Attempting to reacquire...")) - cameraticks++ - if(cameraticks > 9) - U.cameraFollow = null - to_chat(U, span_warning("Unable to reacquire, cancelling track...")) - tracking = FALSE - return - else - sleep(1 SECONDS) - continue - - else - cameraticks = 0 - U.tracking = FALSE - - if(U.eyeobj) - U.eyeobj.setLoc(get_turf(target)) - - else - view_core() - U.cameraFollow = null - return - - sleep(1 SECONDS) - -/proc/near_camera(mob/living/M) - if (!isturf(M.loc)) - return FALSE - if(issilicon(M)) - var/mob/living/silicon/S = M - if((QDELETED(S.builtInCamera) || !S.builtInCamera.can_use()) && !GLOB.cameranet.checkCameraVis(M)) - return FALSE - else if(!GLOB.cameranet.checkCameraVis(M)) - return FALSE - return TRUE - -/obj/machinery/camera/attack_ai(mob/living/silicon/ai/user) - if (!istype(user)) - return - if (!can_use()) - return - user.switchCamera(src) - -/proc/camera_sort(list/L) - var/obj/machinery/camera/a - var/obj/machinery/camera/b - - for (var/i = length(L), i > 0, i--) - for (var/j = 1 to i - 1) - a = L[j] - b = L[j + 1] - if (sorttext(a.c_tag, b.c_tag) < 0) - L.Swap(j, j + 1) - return L diff --git a/code/game/machinery/computer/camera.dm b/code/game/machinery/computer/camera.dm index e8fc202feed..5a604cd969b 100644 --- a/code/game/machinery/computer/camera.dm +++ b/code/game/machinery/computer/camera.dm @@ -94,7 +94,7 @@ /obj/machinery/computer/security/ui_static_data() var/list/data = list() data["mapRef"] = cam_screen.assigned_map - var/list/cameras = get_available_cameras() + var/list/cameras = get_camera_list(network) data["cameras"] = list() for(var/i in cameras) var/obj/machinery/camera/C = cameras[i] @@ -111,7 +111,7 @@ if(action == "switch_camera") var/c_tag = params["name"] - var/list/cameras = get_available_cameras() + var/list/cameras = get_camera_list(network) var/obj/machinery/camera/selected_camera = cameras[c_tag] active_camera = selected_camera playsound(src, get_sfx(SFX_TERMINAL_TYPE), 25, FALSE) @@ -178,28 +178,6 @@ 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/cam as anything in GLOB.cameranet.cameras) - //Get the camera's turf in case it's inside something like a borg - var/turf/camera_turf = get_turf(cam) - if((is_away_level(z) || is_away_level(camera_turf.z)) && (camera_turf.z != z))//if on away mission, can only receive feed from same z_level cameras - continue - L.Add(cam) - var/list/D = list() - for(var/obj/machinery/camera/cam 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) - D["[cam.c_tag]"] = cam - return D - // SECURITY MONITORS /obj/machinery/computer/security/wooden_tv diff --git a/code/game/machinery/computer/crew.dm b/code/game/machinery/computer/crew.dm index 0871592de55..3122c8c1e84 100644 --- a/code/game/machinery/computer/crew.dm +++ b/code/game/machinery/computer/crew.dm @@ -272,7 +272,7 @@ GLOBAL_DATUM_INIT(crewmonitor, /datum/crewmonitor, new) return results -/datum/crewmonitor/ui_act(action,params) +/datum/crewmonitor/ui_act(action, params) . = ..() if(.) return @@ -281,7 +281,7 @@ GLOBAL_DATUM_INIT(crewmonitor, /datum/crewmonitor, new) var/mob/living/silicon/ai/AI = usr if(!istype(AI)) return - AI.ai_camera_track(params["name"]) + AI.ai_tracking_tool.set_tracked_mob(AI, params["name"]) #undef SENSORS_UPDATE_PERIOD #undef UNKNOWN_JOB_ID diff --git a/code/game/objects/items/devices/multitool.dm b/code/game/objects/items/devices/multitool.dm index 0a0216e4c90..268295324f3 100644 --- a/code/game/objects/items/devices/multitool.dm +++ b/code/game/objects/items/devices/multitool.dm @@ -95,10 +95,6 @@ /obj/item/multitool/ai_detect/proc/multitool_detect() var/turf/our_turf = get_turf(src) detect_state = PROXIMITY_NONE - for(var/mob/living/silicon/ai/AI as anything in GLOB.ai_list) - if(AI.cameraFollow == src) - detect_state = PROXIMITY_ON_SCREEN - return for(var/mob/camera/ai_eye/AI_eye as anything in GLOB.aiEyes) if(!AI_eye.ai_detector_visible) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 3efbeed0ce4..8bd862b79cb 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -1205,12 +1205,8 @@ loc_temp = ((1 - occupied_space.contents_thermal_insulation) * loc_temp) + (occupied_space.contents_thermal_insulation * bodytemperature) return loc_temp -/mob/living/cancel_camera() - ..() - cameraFollow = null - /// Checks if this mob can be actively tracked by cameras / AI. -/// Can optionally be passed a user, which is the mob tracking. +/// Can optionally be passed a user, which is the mob who is tracking src. /mob/living/proc/can_track(mob/living/user) //basic fast checks go first. When overriding this proc, I recommend calling ..() at the end. if(SEND_SIGNAL(src, COMSIG_LIVING_CAN_TRACK, user) & COMPONENT_CANT_TRACK) @@ -1229,7 +1225,7 @@ if(invisibility || alpha == 0)//cloaked return FALSE // Now, are they viewable by a camera? (This is last because it's the most intensive check) - if(!near_camera(src)) + if(!GLOB.cameranet.checkCameraVis(src)) return FALSE return TRUE diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index d5e1c03be4a..07ec69616dd 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -93,8 +93,6 @@ /// Used by [living/Bump()][/mob/living/proc/Bump] and [living/PushAM()][/mob/living/proc/PushAM] to prevent potential infinite loop. var/now_pushing = null - var/cameraFollow = null - /// Time of death var/tod = null diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index 12a2b51f484..b27f56567f8 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -44,7 +44,6 @@ var/obj/item/multitool/aiMulti ///Weakref to the bot the ai's commanding right now var/datum/weakref/bot_ref - var/tracking = FALSE //this is 1 if the AI is currently tracking somebody, but the track has not yet been completed. var/datum/effect_system/spark_spread/spark_system //So they can initialize sparks whenever //MALFUNCTION @@ -64,7 +63,8 @@ var/camera_light_on = FALSE var/list/obj/machinery/camera/lit_cameras = list() - var/datum/trackable/track = new + ///The internal tool used to track players visible through cameras. + var/datum/trackable/ai_tracking_tool var/last_tablet_note_seen = null var/can_shunt = TRUE @@ -191,6 +191,9 @@ builtInCamera = new (src) builtInCamera.network = list("ss13") + ai_tracking_tool = new(src) + RegisterSignal(src, COMSIG_TRACKABLE_TRACKING_TARGET, PROC_REF(on_track_target)) + add_traits(list(TRAIT_PULL_BLOCKED, TRAIT_HANDS_BLOCKED), ROUNDSTART_TRAIT) alert_control = new(src, list(ALARM_ATMOS, ALARM_FIRE, ALARM_POWER, ALARM_CAMERA, ALARM_BURGLAR, ALARM_MOTION), list(z), camera_view = TRUE) @@ -203,7 +206,8 @@ switch(_key) if("`", "0") if(cam_prev) - cameraFollow = null //stop following something, we want to jump away. + if(ai_tracking_tool.tracking) + ai_tracking_tool.set_tracking(FALSE) eyeobj.setLoc(cam_prev) return if("1", "2", "3", "4", "5", "6", "7", "8", "9") @@ -214,7 +218,8 @@ return if(cam_hotkeys[_key]) //if this is false, no hotkey for this slot exists. cam_prev = eyeobj.loc - cameraFollow = null //stop following something, we want to jump away. + if(ai_tracking_tool.tracking) + ai_tracking_tool.set_tracking(FALSE) eyeobj.setLoc(cam_hotkeys[_key]) return return ..() @@ -230,6 +235,7 @@ QDEL_NULL(robot_control) QDEL_NULL(aiMulti) QDEL_NULL(alert_control) + QDEL_NULL(ai_tracking_tool) malfhack = null current = null bot_ref = null @@ -239,6 +245,7 @@ if(ai_voicechanger) ai_voicechanger.owner = null ai_voicechanger = null + UnregisterSignal(src, COMSIG_TRACKABLE_TRACKING_TARGET) return ..() /// Removes all malfunction-related abilities from the AI @@ -385,6 +392,20 @@ /mob/living/silicon/ai/cancel_camera() view_core() +/mob/living/silicon/ai/verb/ai_camera_track() + set name = "track" + set hidden = TRUE //Don't display it on the verb lists. This verb exists purely so you can type "track Oldman Robustin" and follow his ass + + ai_tracking_tool.set_tracked_mob(src) + +///Called when an AI finds their tracking target. +/mob/living/silicon/ai/proc/on_track_target(datum/trackable/source, mob/living/target) + SIGNAL_HANDLER + if(eyeobj) + eyeobj.setLoc(get_turf(target)) + else + view_core() + /mob/living/silicon/ai/verb/toggle_anchor() set category = "AI Commands" set name = "Toggle Floor Bolts" @@ -503,24 +524,7 @@ else to_chat(src, span_notice("Unable to project to the holopad.")) if(href_list["track"]) - var/string = href_list["track"] - trackable_mobs() - var/list/trackeable = list() - trackeable += track.humans + track.others - var/list/target = list() - for(var/I in trackeable) - var/datum/weakref/to_resolve = trackeable[I] - var/mob/to_track = to_resolve.resolve() - if(!to_track || to_track.name != string) - continue - target += to_track - if(name == string) - target += src - if(length(target)) - cam_prev = get_turf(eyeobj) - ai_actual_track(pick(target)) - else - to_chat(src, "Target is not on or near any active cameras on the station.") + ai_tracking_tool.set_tracked_mob(src, href_list["track"]) return if (href_list["ai_take_control"]) //Mech domination var/obj/vehicle/sealed/mecha/M = locate(href_list["ai_take_control"]) in GLOB.mechas_list @@ -559,12 +563,13 @@ if(QDELETED(C)) return FALSE - if(!tracking) - cameraFollow = null - if(QDELETED(eyeobj)) view_core() return + + if(ai_tracking_tool.tracking) + ai_tracking_tool.set_tracking(FALSE) + // ok, we're alive, camera is good and in our network... eyeobj.setLoc(get_turf(C)) return TRUE @@ -633,7 +638,8 @@ set category = "AI Commands" set name = "Jump To Network" unset_machine() - cameraFollow = null + if(ai_tracking_tool.tracking) + ai_tracking_tool.set_tracking(FALSE) var/cameralist[0] if(incapacitated()) @@ -1147,6 +1153,14 @@ else if(.) REMOVE_TRAIT(src, TRAIT_INCAPACITATED, POWER_LACK_TRAIT) +/mob/living/silicon/ai/proc/show_camera_list() + var/list/cameras = get_camera_list(network) + var/camera = tgui_input_list(src, "Choose which camera you want to view", "Cameras", cameras) + if(isnull(camera)) + return + if(isnull(cameras[camera])) + return + switchCamera(cameras[camera]) /mob/living/silicon/on_handsblocked_start() return // AIs have no hands diff --git a/code/modules/mob/living/silicon/ai/death.dm b/code/modules/mob/living/silicon/ai/death.dm index e064cb3da58..03824857c4e 100644 --- a/code/modules/mob/living/silicon/ai/death.dm +++ b/code/modules/mob/living/silicon/ai/death.dm @@ -21,8 +21,6 @@ if(icon_exists(icon, "[base_icon]_death_transition")) flick("[base_icon]_death_transition", src) - cameraFollow = null - if(is_anchored) flip_anchored() diff --git a/code/modules/mob/living/silicon/ai/freelook/cameranet.dm b/code/modules/mob/living/silicon/ai/freelook/cameranet.dm index 9b72c455995..4c2cc371f53 100644 --- a/code/modules/mob/living/silicon/ai/freelook/cameranet.dm +++ b/code/modules/mob/living/silicon/ai/freelook/cameranet.dm @@ -165,6 +165,8 @@ GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new) /// Will check if a mob is on a viewable turf. Returns 1 if it is, otherwise returns 0. /datum/cameranet/proc/checkCameraVis(mob/living/target) var/turf/position = get_turf(target) + if(!position) + return return checkTurfVis(position) diff --git a/code/modules/mob/living/silicon/ai/freelook/eye.dm b/code/modules/mob/living/silicon/ai/freelook/eye.dm index e5390f9a3f0..6204ecdea60 100644 --- a/code/modules/mob/living/silicon/ai/freelook/eye.dm +++ b/code/modules/mob/living/silicon/ai/freelook/eye.dm @@ -93,32 +93,33 @@ // It will also stream the chunk that the new loc is in. /mob/camera/ai_eye/proc/setLoc(destination, force_update = FALSE) - if(ai) - if(!isturf(ai.loc)) - return - destination = get_turf(destination) - if(!force_update && (destination == get_turf(src)) ) - return //we are already here! - if (destination) - abstract_move(destination) - else - moveToNullspace() - if(use_static) - ai.camera_visibility(src) - if(ai.client && !ai.multicam_on) - ai.client.set_eye(src) - update_ai_detect_hud() - update_parallax_contents() - //Holopad - if(istype(ai.current, /obj/machinery/holopad)) - var/obj/machinery/holopad/H = ai.current - if(!H.move_hologram(ai, destination)) - H.clear_holo(ai) + if(!ai) + return + if(!isturf(ai.loc)) + return + destination = get_turf(destination) + if(!force_update && (destination == get_turf(src))) + return //we are already here! + if (destination) + abstract_move(destination) + else + moveToNullspace() + if(use_static) + ai.camera_visibility(src) + if(ai.client && !ai.multicam_on) + ai.client.set_eye(src) + update_ai_detect_hud() + update_parallax_contents() + //Holopad + if(istype(ai.current, /obj/machinery/holopad)) + var/obj/machinery/holopad/H = ai.current + if(!H.move_hologram(ai, destination)) + H.clear_holo(ai) - if(ai.camera_light_on) - ai.light_cameras() - if(ai.master_multicam) - ai.master_multicam.refresh_view() + if(ai.camera_light_on) + ai.light_cameras() + if(ai.master_multicam) + ai.master_multicam.refresh_view() /mob/camera/ai_eye/zMove(dir, turf/target, z_move_flags = NONE, recursions_left = 1, list/falling_movs) . = ..() @@ -149,12 +150,14 @@ return ..() /atom/proc/move_camera_by_click() - if(isAI(usr)) - var/mob/living/silicon/ai/AI = usr - if(AI.eyeobj && (AI.multicam_on || (AI.client.eye == AI.eyeobj)) && (AI.eyeobj.z == z)) - AI.cameraFollow = null - if (isturf(loc) || isturf(src)) - AI.eyeobj.setLoc(src) + if(!isAI(usr)) + return + var/mob/living/silicon/ai/AI = usr + if(AI.eyeobj && (AI.multicam_on || (AI.client.eye == AI.eyeobj)) && (AI.eyeobj.z == z)) + if(AI.ai_tracking_tool.tracking) + AI.ai_tracking_tool.set_tracking(FALSE) + if (isturf(loc) || isturf(src)) + AI.eyeobj.setLoc(src) // This will move the AIEye. It will also cause lights near the eye to light up, if toggled. // This is handled in the proc below this one. @@ -178,8 +181,8 @@ else user.sprint = initial - if(!user.tracking) - user.cameraFollow = null + if(user.ai_tracking_tool.tracking) + user.ai_tracking_tool.set_tracking(FALSE) // Return to the Core. /mob/living/silicon/ai/proc/view_core() @@ -188,7 +191,8 @@ H.clear_holo(src) else current = null - cameraFollow = null + if(ai_tracking_tool && ai_tracking_tool.tracking) + ai_tracking_tool.set_tracking(FALSE) unset_machine() if(isturf(loc) && (QDELETED(eyeobj) || !eyeobj.loc)) @@ -227,7 +231,7 @@ /mob/camera/ai_eye/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, list/message_mods = list(), message_range) . = ..() - if(relay_speech && speaker && ai && !radio_freq && speaker != ai && near_camera(speaker)) + if(relay_speech && speaker && ai && !radio_freq && speaker != ai && GLOB.cameranet.checkCameraVis(speaker)) ai.relay_speech(message, speaker, message_language, raw_message, radio_freq, spans, message_mods) /obj/effect/overlay/ai_detect_hud diff --git a/code/modules/modular_computers/file_system/programs/secureye.dm b/code/modules/modular_computers/file_system/programs/secureye.dm index 5c97cd16147..c14c6ee4efc 100644 --- a/code/modules/modular_computers/file_system/programs/secureye.dm +++ b/code/modules/modular_computers/file_system/programs/secureye.dm @@ -86,7 +86,7 @@ /datum/computer_file/program/secureye/ui_static_data(mob/user) var/list/data = list() data["mapRef"] = cam_screen.assigned_map - var/list/cameras = get_available_cameras() + var/list/cameras = get_camera_list(network) data["cameras"] = list() for(var/i in cameras) var/obj/machinery/camera/C = cameras[i] @@ -102,7 +102,7 @@ return if(action == "switch_camera") var/c_tag = format_text(params["name"]) - var/list/cameras = get_available_cameras() + var/list/cameras = get_camera_list(network) var/obj/machinery/camera/selected_camera = cameras[c_tag] camera_ref = WEAKREF(selected_camera) playsound(src, get_sfx(SFX_TERMINAL_TYPE), 25, FALSE) @@ -168,26 +168,4 @@ 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 -/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) - //Get the camera's turf in case it's inside something like a borg - var/turf/camera_turf = get_turf(cam) - if(!is_station_level(camera_turf.z))//Only show station cameras. - continue - L.Add(cam) - var/list/camlist = list() - for(var/obj/machinery/camera/cam 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 - return camlist - #undef DEFAULT_MAP_SIZE diff --git a/tgstation.dme b/tgstation.dme index d1614134d3f..2d9c4f9b10c 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -276,6 +276,7 @@ #include "code\__DEFINES\dcs\signals\signals_backpack.dm" #include "code\__DEFINES\dcs\signals\signals_beam.dm" #include "code\__DEFINES\dcs\signals\signals_bot.dm" +#include "code\__DEFINES\dcs\signals\signals_camera.dm" #include "code\__DEFINES\dcs\signals\signals_changeling.dm" #include "code\__DEFINES\dcs\signals\signals_circuit.dm" #include "code\__DEFINES\dcs\signals\signals_client.dm" @@ -369,6 +370,7 @@ #include "code\__HELPERS\auxtools.dm" #include "code\__HELPERS\bitflag_lists.dm" #include "code\__HELPERS\byond_status.dm" +#include "code\__HELPERS\cameras.dm" #include "code\__HELPERS\chat.dm" #include "code\__HELPERS\chat_filter.dm" #include "code\__HELPERS\clients.dm" @@ -1737,7 +1739,7 @@ #include "code\game\machinery\camera\camera_assembly.dm" #include "code\game\machinery\camera\motion.dm" #include "code\game\machinery\camera\presets.dm" -#include "code\game\machinery\camera\tracking.dm" +#include "code\game\machinery\camera\trackable.dm" #include "code\game\machinery\computer\_computer.dm" #include "code\game\machinery\computer\accounting.dm" #include "code\game\machinery\computer\aifixer.dm"