mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-25 06:00:16 +01:00
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:
@@ -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"
|
||||
@@ -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
@@ -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()
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user