Reworks how AI tracking is handled & reorganizes it (#77776)

## About The Pull Request

Completely reworks how AI tracking is handled, this has no in-game
effects.
This moves nearly all AI tracking handling onto ``/datum/tracking``,
which previously was pretty bad. I tried documenting as much as I can,
making comments actually useful and give accurate information.

Turns ``get_camera_list`` into a global proc, which we now use for
camera consoles (including the app), cutting down on copy paste in 2
areas and standardizing its behavior/backend.

## Why It's Good For The Game

I wanted to generalize this behavior so I can use it for tracking
players in https://github.com/tgstation/tgstation/pull/77713 - This
helps me do that.
Also the current state of AI tracking code is pretty poor and hopefully
this improves that area.

Closes https://github.com/tgstation/tgstation/issues/42355

## Changelog

🆑
refactor: AI's player-tracking eyes received an unwanted obligatory
update, and should now not tell you that a player is untrackable when
they clearly obviously can be.
/🆑
This commit is contained in:
John Willard
2023-08-21 12:59:28 +00:00
committed by GitHub
parent bf9b39d1f4
commit 78e96f2f12
18 changed files with 268 additions and 290 deletions
@@ -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"
+35
View File
@@ -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
+1 -1
View File
@@ -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()
+3 -5
View File
@@ -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"
+7
View File
@@ -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)
+128
View File
@@ -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
-158
View File
@@ -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
+2 -24
View File
@@ -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
+2 -2
View File
@@ -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
@@ -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)
+2 -6
View File
@@ -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
@@ -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
+40 -26
View File
@@ -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
@@ -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()
@@ -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)
@@ -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
@@ -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
+3 -1
View File
@@ -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"