diff --git a/code/__DEFINES/dcs/signals/signals_camera.dm b/code/__DEFINES/dcs/signals/signals_camera.dm index 6ec142f54fa..92e9b94f35b 100644 --- a/code/__DEFINES/dcs/signals/signals_camera.dm +++ b/code/__DEFINES/dcs/signals/signals_camera.dm @@ -1,2 +1,4 @@ -///Signal sent when a /datum/trackable found a target: (datum/trackable/source, mob/living/target) +///Signal sent when a /datum/trackable found a target: (mob/living/target) #define COMSIG_TRACKABLE_TRACKING_TARGET "comsig_trackable_tracking_target" +///Signal sent when the mob a /datum/trackable is actively following changes glide size: mob/living/target, new_glide_size) +#define COMSIG_TRACKABLE_GLIDE_CHANGED "comsig_trackable_glide_changed" diff --git a/code/_onclick/ai.dm b/code/_onclick/ai.dm index 3d632a99f73..c8c35b30847 100644 --- a/code/_onclick/ai.dm +++ b/code/_onclick/ai.dm @@ -11,7 +11,7 @@ return if(ismob(A)) - ai_tracking_tool.set_tracked_mob(src, A.name) + ai_tracking_tool.track_mob(src, A) else A.move_camera_by_click() diff --git a/code/game/machinery/camera/trackable.dm b/code/game/machinery/camera/trackable.dm index 884f38f750b..b1c4b46da4b 100644 --- a/code/game/machinery/camera/trackable.dm +++ b/code/game/machinery/camera/trackable.dm @@ -2,13 +2,13 @@ #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 + ///What mob are we currently tracking, if any + var/mob/living/tracked_mob + ///If we're currently rechecking our target's trackability in hopes of something changing + var/rechecking = FALSE ///How many times we've failed to locate our target. var/cameraticks = 0 @@ -24,7 +24,7 @@ /datum/trackable/New(atom/source) . = ..() tracking_holder = source - RegisterSignal(tracking_holder, COMSIG_MOB_RESET_PERSPECTIVE, PROC_REF(cancel_target_tracking)) + RegisterSignal(tracking_holder, COMSIG_MOB_RESET_PERSPECTIVE, PROC_REF(perspective_reset)) /datum/trackable/Destroy(force) tracking_holder = null @@ -32,27 +32,6 @@ 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) @@ -82,47 +61,140 @@ 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) +/// Takes a mob to track, resets our state and begins trying to follow it +/// Best we can at least +/datum/trackable/proc/set_tracked_mob(mob/living/track) + set_rechecking(FALSE) + if(tracked_mob) + UnregisterSignal(tracked_mob, list(COMSIG_QDELETING, COMSIG_MOVABLE_MOVED, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE)) + if(track && !isliving(track)) + tracked_mob = null + return + tracked_mob = track + if(tracked_mob) + RegisterSignal(tracked_mob, COMSIG_QDELETING, PROC_REF(target_deleted)) + RegisterSignal(tracked_mob, COMSIG_MOVABLE_MOVED, PROC_REF(target_moved)) + RegisterSignal(tracked_mob, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE, PROC_REF(glide_size_changed)) + attempt_track() + +/datum/trackable/proc/target_deleted(datum/source) + SIGNAL_HANDLER + reset_tracking() + +/datum/trackable/proc/perspective_reset(atom/source) + SIGNAL_HANDLER + reset_tracking() + +/datum/trackable/proc/target_moved(datum/source) + SIGNAL_HANDLER + if(attempt_track()) + return + set_rechecking(TRUE) + +/// Controls if we're processing to recheck the conditions that prevent tracking or not +/datum/trackable/proc/set_rechecking(should_check) + if(should_check) START_PROCESSING(SSprocessing, src) - tracking = TRUE + cameraticks = initial(cameraticks) + rechecking = TRUE else STOP_PROCESSING(SSprocessing, src) - tracking = FALSE - tracked_mob = null + rechecking = FALSE -///Called by Signals, used to cancel tracking of a target. -/datum/trackable/proc/cancel_target_tracking(atom/source) +/datum/trackable/process() + if(!rechecking) + return PROCESS_KILL + + if(attempt_track()) + set_rechecking(FALSE) + 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...")) + reset_tracking() + +/// Tries to track onto our target mob. Returns true if it succeeds, false otherwise +/datum/trackable/proc/attempt_track() + if(!tracked_mob) + reset_tracking() + return FALSE + + if(!tracked_mob.can_track(tracking_holder)) + return FALSE + // In case we've been checking + set_rechecking(FALSE) + SEND_SIGNAL(src, COMSIG_TRACKABLE_TRACKING_TARGET, tracked_mob) + return TRUE + +/datum/trackable/proc/glide_size_changed(datum/source, new_glide_size) SIGNAL_HANDLER - set_tracking(FALSE) + SEND_SIGNAL(src, COMSIG_TRACKABLE_GLIDE_CHANGED, tracked_mob, new_glide_size) /** - * set_tracked_mob + * reset_tracking * - * 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. + * Resets our tracking + */ +/datum/trackable/proc/reset_tracking() + set_tracked_mob(null) + +/** + * track_input + * + * Sets a mob as being tracked, 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) +/datum/trackable/proc/track_input(mob/living/tracker) 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] + var/target_name = tgui_input_list(tracker, "Select a target", "Tracking", find_trackable_mobs()) + if(!target_name || isnull(target_name)) + return + var/datum/weakref/mob_ref = isnull(humans[target_name]) ? others[target_name] : humans[target_name] + if(isnull(mob_ref)) + to_chat(tracker, span_notice("Target is not on or near any active cameras. Tracking failed.")) + return + set_tracked_mob(mob_ref.resolve()) - set_tracking(TRUE) +/** + * track_name + * + * Sets a mob as being tracked, will track the passed in target name's target + * Args: + * tracker - The person trying to track, used for feedback messages. This is not the same as tracking_holder + * tracked_mob_name - The person being tracked. + */ +/datum/trackable/proc/track_name(mob/living/tracker, tracked_mob_name) + if(!tracker || tracker.stat == DEAD) + return + + find_trackable_mobs() //this is in case the tracked mob is newly/no-longer in camera field of view. + var/datum/weakref/mob_ref = isnull(humans[tracked_mob_name]) ? others[tracked_mob_name] : humans[tracked_mob_name] + if(isnull(mob_ref)) + 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.")) + set_tracked_mob(mob_ref.resolve()) + +/** + * track_mob + * + * Sets a mob as being tracked, will track the passed in target + * Args: + * tracker - The person trying to track, used for feedback messages. This is not the same as tracking_holder + * tracked - The person being tracked. + */ +/datum/trackable/proc/track_mob(mob/living/tracker, mob/living/tracked) + if(!tracker || tracker.stat == DEAD) + return + // Need to make sure the tracked mob is in our list + track_name(tracked.name) #undef CAMERA_TICK_LIMIT diff --git a/code/game/machinery/computer/crew.dm b/code/game/machinery/computer/crew.dm index e269dc46ad9..be15e3dde6c 100644 --- a/code/game/machinery/computer/crew.dm +++ b/code/game/machinery/computer/crew.dm @@ -301,7 +301,7 @@ GLOBAL_DATUM_INIT(crewmonitor, /datum/crewmonitor, new) var/mob/living/silicon/ai/AI = usr if(!istype(AI)) return - AI.ai_tracking_tool.set_tracked_mob(AI, params["name"]) + AI.ai_tracking_tool.track_name(AI, params["name"]) #undef SENSORS_UPDATE_PERIOD #undef UNKNOWN_JOB_ID diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index 096afab907d..211cf5c81a7 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -77,7 +77,7 @@ var/mob/camera/ai_eye/eyeobj var/sprint = 10 - var/cooldown = 0 + var/last_moved = 0 var/acceleration = TRUE var/obj/structure/ai_core/deactivated/linked_core //For exosuit control @@ -191,7 +191,8 @@ builtInCamera.network = list("ss13") ai_tracking_tool = new(src) - RegisterSignal(src, COMSIG_TRACKABLE_TRACKING_TARGET, PROC_REF(on_track_target)) + RegisterSignal(ai_tracking_tool, COMSIG_TRACKABLE_TRACKING_TARGET, PROC_REF(on_track_target)) + RegisterSignal(ai_tracking_tool, COMSIG_TRACKABLE_GLIDE_CHANGED, PROC_REF(tracked_glidesize_changed)) add_traits(list(TRAIT_PULL_BLOCKED, TRAIT_HANDS_BLOCKED), ROUNDSTART_TRAIT) @@ -211,8 +212,7 @@ switch(_key) if("`", "0") if(cam_prev) - if(ai_tracking_tool.tracking) - ai_tracking_tool.set_tracking(FALSE) + ai_tracking_tool.reset_tracking() eyeobj.setLoc(cam_prev) return if("1", "2", "3", "4", "5", "6", "7", "8", "9") @@ -223,8 +223,7 @@ return if(cam_hotkeys[_key]) //if this is false, no hotkey for this slot exists. cam_prev = eyeobj.loc - if(ai_tracking_tool.tracking) - ai_tracking_tool.set_tracking(FALSE) + ai_tracking_tool.reset_tracking() eyeobj.setLoc(cam_hotkeys[_key]) return return ..() @@ -250,7 +249,6 @@ 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 @@ -401,7 +399,7 @@ 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) + ai_tracking_tool.track_input(src) ///Called when an AI finds their tracking target. /mob/living/silicon/ai/proc/on_track_target(datum/trackable/source, mob/living/target) @@ -411,6 +409,12 @@ else view_core() +/// Keeps our rate of gliding in step with the mob we're following +/mob/living/silicon/ai/proc/tracked_glidesize_changed(datum/trackable/source, mob/living/target, new_glide_size) + SIGNAL_HANDLER + if(eyeobj) + eyeobj.glide_size = new_glide_size + /mob/living/silicon/ai/verb/toggle_anchor() set category = "AI Commands" set name = "Toggle Floor Bolts" @@ -525,7 +529,7 @@ else to_chat(src, span_notice("Unable to project to the holopad.")) if(href_list["track"]) - ai_tracking_tool.set_tracked_mob(src, href_list["track"]) + ai_tracking_tool.track_name(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 @@ -568,8 +572,7 @@ view_core() return - if(ai_tracking_tool.tracking) - ai_tracking_tool.set_tracking(FALSE) + ai_tracking_tool.reset_tracking() // ok, we're alive, camera is good and in our network... eyeobj.setLoc(get_turf(C)) @@ -643,8 +646,7 @@ set category = "AI Commands" set name = "Jump To Network" unset_machine() - if(ai_tracking_tool.tracking) - ai_tracking_tool.set_tracking(FALSE) + ai_tracking_tool.reset_tracking() var/cameralist[0] if(incapacitated()) diff --git a/code/modules/mob/living/silicon/ai/freelook/eye.dm b/code/modules/mob/living/silicon/ai/freelook/eye.dm index 416bbb19912..e8c1919b020 100644 --- a/code/modules/mob/living/silicon/ai/freelook/eye.dm +++ b/code/modules/mob/living/silicon/ai/freelook/eye.dm @@ -154,35 +154,44 @@ 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) + AI.ai_tracking_tool.reset_tracking() 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. +#define SPRINT_PER_TICK 0.5 +#define MAX_SPRINT 50 +#define SPRINT_PER_STEP 20 +/mob/living/silicon/ai/proc/AIMove(direction) + if(last_moved && last_moved + 1 < world.timeofday) + // Decay sprint based off how long it took us to input this next move + var/missed_sprint = max((world.timeofday + 1) - last_moved, 0) * SPRINT_PER_TICK + sprint = max(sprint - missed_sprint * 7, initial(sprint)) -/client/proc/AIMove(n, direct, mob/living/silicon/ai/user) - - var/initial = initial(user.sprint) - var/max_sprint = 50 - - if(user.cooldown && user.cooldown < world.timeofday) // 3 seconds - user.sprint = initial - - for(var/i = 0; i < max(user.sprint, initial); i += 20) - var/turf/step = get_turf(get_step(user.eyeobj, direct)) + // We move a full step, at least. Can't glide more with our current movement mode, so this is how I have to live + var/step_count = 0 + for(var/i = 0; i < max(sprint, initial(sprint)); i += SPRINT_PER_STEP) + step_count += 1 + var/turf/step = get_turf(get_step(eyeobj, direction)) if(step) - user.eyeobj.setLoc(step) + eyeobj.setLoc(step) - user.cooldown = world.timeofday + 5 - if(user.acceleration) - user.sprint = min(user.sprint + 0.5, max_sprint) + // I'd like to make this scale with the steps we take, but it like, just can't + // So we're doin this instead + eyeobj.glide_size = world.icon_size + + last_moved = world.timeofday + if(acceleration) + sprint = min(sprint + SPRINT_PER_TICK, MAX_SPRINT) else - user.sprint = initial + sprint = initial(sprint) - if(user.ai_tracking_tool.tracking) - user.ai_tracking_tool.set_tracking(FALSE) + ai_tracking_tool.reset_tracking() + +#undef SPRINT_PER_STEP +#undef MAX_SPRINT +#undef SPRINT_PER_TICK // Return to the Core. /mob/living/silicon/ai/proc/view_core() @@ -191,8 +200,8 @@ H.clear_holo(src) else current = null - if(ai_tracking_tool && ai_tracking_tool.tracking) - ai_tracking_tool.set_tracking(FALSE) + if(ai_tracking_tool) + ai_tracking_tool.reset_tracking() unset_machine() if(isturf(loc) && (QDELETED(eyeobj) || !eyeobj.loc)) diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm index 6660639b400..bac29902347 100644 --- a/code/modules/mob/mob_movement.dm +++ b/code/modules/mob/mob_movement.dm @@ -76,7 +76,8 @@ return mob.remote_control.relaymove(mob, direct) if(isAI(mob)) - return AIMove(new_loc,direct,mob) + var/mob/living/silicon/ai/smoovin_ai = mob + return smoovin_ai.AIMove(direct) if(Process_Grab()) //are we restrained by someone's grip? return diff --git a/code/modules/modular_computers/file_system/programs/secureye.dm b/code/modules/modular_computers/file_system/programs/secureye.dm index c9e4fc087f3..2b3b8f6ea8c 100644 --- a/code/modules/modular_computers/file_system/programs/secureye.dm +++ b/code/modules/modular_computers/file_system/programs/secureye.dm @@ -61,7 +61,6 @@ cam_background = new cam_background.assigned_map = map_name cam_background.del_on_map_removal = FALSE - RegisterSignal(src, COMSIG_TRACKABLE_TRACKING_TARGET, PROC_REF(on_track_target)) /datum/computer_file/program/secureye/Destroy() QDEL_NULL(cam_screen) @@ -138,8 +137,8 @@ playsound(computer, get_sfx(SFX_TERMINAL_TYPE), 25, FALSE) if(isnull(camera_ref)) return TRUE - if(internal_tracker && internal_tracker.tracking) - internal_tracker.set_tracking(FALSE) + if(internal_tracker) + internal_tracker.reset_tracking() update_active_camera_screen() return TRUE @@ -147,7 +146,8 @@ if("start_tracking") if(!internal_tracker) internal_tracker = new(src) - internal_tracker.set_tracked_mob(usr) + RegisterSignal(internal_tracker, COMSIG_TRACKABLE_TRACKING_TARGET, PROC_REF(on_track_target)) + internal_tracker.track_input(usr) return TRUE /datum/computer_file/program/secureye/proc/on_track_target(datum/trackable/source, mob/living/target) @@ -169,8 +169,8 @@ /datum/computer_file/program/secureye/ui_close(mob/user) . = ..() //don't track anyone while we're shutting off. - if(internal_tracker && internal_tracker.tracking) - internal_tracker.set_tracking(FALSE) + if(internal_tracker) + internal_tracker.reset_tracking() var/user_ref = REF(user) var/is_living = isliving(user) // Living creature or not, we remove you anyway.