diff --git a/code/__DEFINES/_helpers.dm b/code/__DEFINES/_helpers.dm index 08d4b39906e..b9c5e8d7d13 100644 --- a/code/__DEFINES/_helpers.dm +++ b/code/__DEFINES/_helpers.dm @@ -14,6 +14,9 @@ /// subtypesof(), typesof() without the parent path #define subtypesof(typepath) ( typesof(typepath) - typepath ) +/// Until a condition is true, sleep +#define UNTIL(X) while(!(X)) stoplag() + /// Takes a datum as input, returns its ref string, or a cached version of it /// This allows us to cache \ref creation, which ensures it'll only ever happen once per datum, saving string tree time /// It is slightly less optimal then a []'d datum, but the cost is massively outweighed by the potential savings diff --git a/code/__DEFINES/layers.dm b/code/__DEFINES/layers.dm index cca6686e99c..8680212a0ba 100644 --- a/code/__DEFINES/layers.dm +++ b/code/__DEFINES/layers.dm @@ -120,6 +120,7 @@ #define GAS_FILTER_LAYER 2.48 #define GAS_PUMP_LAYER 2.49 #define PLUMBING_PIPE_VISIBILE_LAYER 2.495//layer = initial(layer) + ducting_layer / 3333 in atmospherics/handle_layer() to determine order of duct overlap +#define BOT_PATH_LAYER 2.497 #define LOW_OBJ_LAYER 2.5 ///catwalk overlay of /turf/open/floor/plating/catwalk_floor #define CATWALK_LAYER 2.51 diff --git a/code/__DEFINES/path.dm b/code/__DEFINES/path.dm new file mode 100644 index 00000000000..95713c5d36f --- /dev/null +++ b/code/__DEFINES/path.dm @@ -0,0 +1,5 @@ +// Define set that decides how an atom will be scanned for astar things +/// If set, we make the assumption that CanAStarPass() will NEVER return FALSE unless density is true +#define CANASTARPASS_DENSITY 0 +/// If this is set, we bypass density checks and always call the proc +#define CANASTARPASS_ALWAYS_PROC 1 diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index 281f682a9c6..ee5d29cd8b2 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -202,6 +202,7 @@ #define FIRE_PRIORITY_NPC 20 #define FIRE_PRIORITY_NPC_MOVEMENT 21 #define FIRE_PRIORITY_NPC_ACTIONS 22 +#define FIRE_PRIORITY_PATHFINDING 23 #define FIRE_PRIORITY_PROCESS 25 #define FIRE_PRIORITY_THROWING 25 #define FIRE_PRIORITY_REAGENTS 26 diff --git a/code/__HELPERS/path.dm b/code/__HELPERS/path.dm index 89f64cf9fb6..08280617e97 100644 --- a/code/__HELPERS/path.dm +++ b/code/__HELPERS/path.dm @@ -7,6 +7,7 @@ /** * This is the proc you use whenever you want to have pathfinding more complex than "try stepping towards the thing". * If no path was found, returns an empty list, which is important for bots like medibots who expect an empty list rather than nothing. + * It will yield until a path is returned, using magic * * Arguments: * * caller: The movable atom that's trying to find the path @@ -19,27 +20,24 @@ * * skip_first: Whether or not to delete the first item in the path. This would be done because the first item is the starting tile, which can break movement for some creatures. * * diagonal_safety: ensures diagonal moves won't use invalid midstep turfs by splitting them into two orthogonal moves if necessary */ -/proc/get_path_to(caller, end, max_distance = 30, mintargetdist, id=null, simulated_only = TRUE, turf/exclude, skip_first=TRUE, diagonal_safety=TRUE) - if(!caller || !get_turf(end)) - return +/proc/get_path_to(atom/movable/caller, atom/end, max_distance = 30, mintargetdist, id=null, simulated_only = TRUE, turf/exclude, skip_first=TRUE, diagonal_safety=TRUE) + var/list/path = list() + // We're guarenteed that list will be the first list in pathfinding_finished's argset because of how callback handles the arguments list + var/datum/callback/await = CALLBACK(GLOBAL_PROC, /proc/pathfinding_finished, path) + if(!SSpathfinder.pathfind(caller, end, max_distance, mintargetdist, id, simulated_only, exclude, skip_first, diagonal_safety, await)) + return null - var/l = SSpathfinder.mobs.getfree(caller) - while(!l) - stoplag(3) - l = SSpathfinder.mobs.getfree(caller) - - var/list/path - var/datum/pathfind/pathfind_datum = new(caller, end, id, max_distance, mintargetdist, simulated_only, exclude, diagonal_safety) - path = pathfind_datum.search() - qdel(pathfind_datum) - - SSpathfinder.mobs.found(l) - if(!path) - path = list() - if(length(path) > 0 && skip_first) - path.Cut(1,2) + UNTIL(length(path)) + if(length(path) == 1 && path[1] == null) // It's trash, just hand back null to make it easy + return null return path +/// Uses funny pass by reference bullshit to take the path created by pathfinding, and insert it into a return list +/// We'll be able to use this return list to tell a sleeping proc to continue execution +/proc/pathfinding_finished(list/return_list, list/path) + // We use += here to ensure the list is still pointing at the same thing + return_list += path + /** * A helper macro to see if it's possible to step from the first turf into the second one, minding things like door access and directional windows. * Note that this can only be used inside the [datum/pathfind][pathfind datum] since it uses variables from said datum. @@ -122,10 +120,14 @@ var/simulated_only /// A specific turf we're avoiding, like if a mulebot is being blocked by someone t-posing in a doorway we're trying to get through var/turf/avoid + /// If we should delete the first step in the path or not. Used often because it is just the starting tile + var/skip_first = FALSE /// Ensures diagonal moves won't use invalid midstep turfs by splitting them into two orthogonal moves if necessary var/diagonal_safety = TRUE + /// The callback to invoke when we're done working, passing in the completed var/list/path + var/datum/callback/on_finish -/datum/pathfind/New(atom/movable/caller, atom/goal, id, max_distance, mintargetdist, simulated_only, avoid, diagonal_safety) +/datum/pathfind/New(atom/movable/caller, atom/goal, id, max_distance, mintargetdist, simulated_only, avoid, skip_first, diagonal_safety, datum/callback/on_finish) src.caller = caller end = get_turf(goal) open = new /datum/heap(/proc/HeapPathWeightCompare) @@ -135,34 +137,46 @@ src.mintargetdist = mintargetdist src.simulated_only = simulated_only src.avoid = avoid + src.skip_first = skip_first src.diagonal_safety = diagonal_safety + src.on_finish = on_finish + +/datum/pathfind/Destroy(force, ...) + . = ..() + SSpathfinder.active_pathing -= src + SSpathfinder.currentrun -= src + if(on_finish) + on_finish.Invoke(null) /** - * search() is the proc you call to kick off and handle the actual pathfinding, and kills the pathfind datum instance when it's done. - * - * If a valid path was found, it's returned as a list. If invalid or cross-z-level params are entered, or if there's no valid path found, we - * return null, which [/proc/get_path_to] translates to an empty list (notable for simple bots, who need empty lists) + * "starts" off the pathfinding, by storing the values this datum will need to work later on + * returns FALSE if it fails to setup properly, TRUE otherwise */ -/datum/pathfind/proc/search() +/datum/pathfind/proc/start() start = get_turf(caller) - if(!start || !end) + if(!start || !get_turf(end)) stack_trace("Invalid A* start or destination") - return + return FALSE if(start.z != end.z || start == end ) //no pathfinding between z levels - return + return FALSE if(max_distance && (max_distance < get_dist(start, end))) //if start turf is farther than max_distance from end turf, no need to do anything - return + return FALSE - //initialization var/datum/jps_node/current_processed_node = new (start, -1, 0, end) open.insert(current_processed_node) sources[start] = start // i'm sure this is fine + return TRUE + +/** + * search_step() is the workhorse of pathfinding. It'll do the searching logic, and will slowly build up a path + * returns TRUE if everything is stable, FALSE if the pathfinding logic has failed, and we need to abort + */ +/datum/pathfind/proc/search_step() + if(QDELETED(caller)) + return FALSE - //then run the main loop while(!open.is_empty() && !path) - if(!caller) - return - current_processed_node = open.pop() //get the lower f_value turf in the open list + var/datum/jps_node/current_processed_node = open.pop() //get the lower f_value turf in the open list if(max_distance && (current_processed_node.number_tiles > max_distance))//if too many steps, don't process that path continue @@ -173,20 +187,37 @@ for(var/scan_direction in list(NORTHEAST, SOUTHEAST, NORTHWEST, SOUTHWEST)) diag_scan_spec(current_turf, scan_direction, current_processed_node) - CHECK_TICK + // Stable, we'll just be back later + if(TICK_CHECK) + return TRUE + return TRUE +/** + * early_exit() is called when something goes wrong in processing, and we need to halt the pathfinding NOW + */ +/datum/pathfind/proc/early_exit() + on_finish.Invoke(null) + on_finish = null + qdel(src) + +/** + * Cleanup pass for the pathfinder. This tidies up the path, and fufills the pathfind's obligations + */ +/datum/pathfind/proc/finished() //we're done! reverse the path to get it from start to finish if(path) for(var/i = 1 to round(0.5 * length(path))) path.Swap(i, length(path) - i + 1) - sources = null - qdel(open) + QDEL_NULL(open) if(diagonal_safety) path = diagonal_movement_safety() - - return path + if(length(path) > 0 && skip_first) + path.Cut(1,2) + on_finish.Invoke(path) + on_finish = null + qdel(src) /// Called when we've hit the goal with the node that represents the last tile, then sets the path var to that path so it can be returned by [datum/pathfind/proc/search] /datum/pathfind/proc/unwind_path(datum/jps_node/unwind_node) @@ -371,6 +402,8 @@ * For seeing if we can actually move between 2 given turfs while accounting for our access and the caller's pass_flags * * Assumes destinantion turf is non-dense - check and shortcircuit in code invoking this proc to avoid overhead. + * Makes some other assumptions, such as assuming that unless declared, non dense objects will not block movement. + * It's fragile, but this is VERY much the most expensive part of JPS, so it'd better be fast * * Arguments: * * caller: The movable, if one exists, being used for mobility checks to see what tiles it can reach @@ -378,7 +411,7 @@ * * simulated_only: Do we only worry about turfs with simulated atmos, most notably things that aren't space? * * no_id: When true, doors with public access will count as impassible */ -/turf/proc/LinkBlockedWithAccess(turf/destination_turf, caller, ID, no_id = FALSE) +/turf/proc/LinkBlockedWithAccess(turf/destination_turf, atom/movable/caller, ID, no_id = FALSE) if(destination_turf.x != x && destination_turf.y != y) //diagonal var/in_dir = get_dir(destination_turf,src) // eg. northwest (1+8) = 9 (00001001) var/first_step_direction_a = in_dir & 3 // eg. north (1+8)&3 (0000 0011) = 1 (0000 0001) @@ -386,11 +419,10 @@ for(var/first_step_direction in list(first_step_direction_a,first_step_direction_b)) var/turf/midstep_turf = get_step(destination_turf,first_step_direction) - var/way_blocked = midstep_turf.density || LinkBlockedWithAccess(midstep_turf,caller,ID, no_id = no_id) || midstep_turf.LinkBlockedWithAccess(destination_turf,caller,ID, no_id = no_id) + var/way_blocked = midstep_turf.density || LinkBlockedWithAccess(midstep_turf, caller, ID, no_id) || midstep_turf.LinkBlockedWithAccess(destination_turf, caller, ID, no_id) if(!way_blocked) return FALSE return TRUE - var/actual_dir = get_dir(src, destination_turf) /// These are generally cheaper than looping contents so they go first @@ -400,35 +432,27 @@ // if(destination_turf.density) // return TRUE if(TURF_PATHING_PASS_PROC) - if(!destination_turf.CanAStarPass(ID, actual_dir , caller, no_id = no_id)) + if(!destination_turf.CanAStarPass(ID, actual_dir, caller, no_id)) return TRUE if(TURF_PATHING_PASS_NO) return TRUE + var/static/list/directional_blocker_cache = typecacheof(list(/obj/structure/window, /obj/machinery/door/window, /obj/structure/railing, /obj/machinery/door/firedoor/border_only)) // Source border object checks - for(var/obj/structure/window/iter_window in src) - if(!iter_window.CanAStarPass(ID, actual_dir, no_id = no_id)) - return TRUE - - for(var/obj/machinery/door/window/iter_windoor in src) - if(!iter_windoor.CanAStarPass(ID, actual_dir, no_id = no_id)) - return TRUE - - for(var/obj/structure/railing/iter_rail in src) - if(!iter_rail.CanAStarPass(ID, actual_dir, no_id = no_id)) - return TRUE - - for(var/obj/machinery/door/firedoor/border_only/firedoor in src) - if(!firedoor.CanAStarPass(ID, actual_dir, no_id = no_id)) + for(var/obj/border in src) + if(!directional_blocker_cache[border.type]) + continue + if(!border.density && border.can_astar_pass == CANASTARPASS_DENSITY) + continue + if(!border.CanAStarPass(ID, actual_dir, no_id = no_id)) return TRUE // Destination blockers check var/reverse_dir = get_dir(destination_turf, src) for(var/obj/iter_object in destination_turf) - if(!iter_object.CanAStarPass(ID, reverse_dir, caller, no_id = no_id)) + // This is an optimization because of the massive call count of this code + if(!iter_object.density && iter_object.can_astar_pass == CANASTARPASS_DENSITY) + continue + if(!iter_object.CanAStarPass(ID, reverse_dir, caller, no_id)) return TRUE - return FALSE - -#undef CAN_STEP -#undef STEP_NOT_HERE_BUT_THERE diff --git a/code/__HELPERS/stoplag.dm b/code/__HELPERS/stoplag.dm index a24db8f7972..e838ddd97c9 100644 --- a/code/__HELPERS/stoplag.dm +++ b/code/__HELPERS/stoplag.dm @@ -19,5 +19,3 @@ while (TICK_USAGE > min(TICK_LIMIT_TO_RUN, Master.current_ticklimit)) #undef DELTA_CALC - -#define UNTIL(X) while(!(X)) stoplag() diff --git a/code/controllers/subsystem/pathfinder.dm b/code/controllers/subsystem/pathfinder.dm index b13d3d465f2..6ed21e0a048 100644 --- a/code/controllers/subsystem/pathfinder.dm +++ b/code/controllers/subsystem/pathfinder.dm @@ -1,46 +1,46 @@ +/// Queues and manages JPS pathfinding steps SUBSYSTEM_DEF(pathfinder) name = "Pathfinder" init_order = INIT_ORDER_PATH - flags = SS_NO_FIRE - var/datum/flowcache/mobs + priority = FIRE_PRIORITY_PATHFINDING + /// List of pathfind datums we are currently trying to process + var/list/datum/pathfind/active_pathing = list() + /// List of pathfind datums being ACTIVELY processed. exists to make subsystem stats readable + var/list/datum/pathfind/currentrun = list() var/static/space_type_cache /datum/controller/subsystem/pathfinder/Initialize() space_type_cache = typecacheof(/turf/open/space) - mobs = new(10) return SS_INIT_SUCCESS -/datum/flowcache - var/lcount - var/run - var/free - var/list/flow +/datum/controller/subsystem/pathfinder/stat_entry(msg) + msg = "P:[length(active_pathing)]" + return ..() -/datum/flowcache/New(n) - . = ..() - lcount = n - run = 0 - free = 1 - flow = new/list(lcount) +// This is another one of those subsystems (hey lighting) in which one "Run" means fully processing a queue +// We'll use a copy for this just to be nice to people reading the mc panel +/datum/controller/subsystem/pathfinder/fire(resumed) + if(!resumed) + src.currentrun = active_pathing.Copy() -/datum/flowcache/proc/getfree(atom/M) - if(run < lcount) - run += 1 - while(flow[free]) - CHECK_TICK - free = (free % lcount) + 1 - var/t = addtimer(CALLBACK(src, TYPE_PROC_REF(/datum/flowcache, toolong), free), 150, TIMER_STOPPABLE) - flow[free] = t - flow[t] = M - return free - else - return 0 + // Dies of sonic speed from caching datum var reads + var/list/currentrun = src.currentrun + while(length(currentrun)) + var/datum/pathfind/path = currentrun[length(currentrun)] + if(!path.search_step()) // Something's wrong + path.early_exit() + currentrun.len-- + continue + if(MC_TICK_CHECK) + return + path.finished() + // Next please + currentrun.len-- -/datum/flowcache/proc/toolong(l) - log_game("Pathfinder route took longer than 150 ticks, src bot [flow[flow[l]]]") - found(l) - -/datum/flowcache/proc/found(l) - deltimer(flow[l]) - flow[l] = null - run -= 1 +/// Initiates a pathfind. Returns true if we're good, FALSE if something's failed +/datum/controller/subsystem/pathfinder/proc/pathfind(atom/movable/caller, atom/end, max_distance = 30, mintargetdist, id=null, simulated_only = TRUE, turf/exclude, skip_first=TRUE, diagonal_safety=TRUE, datum/callback/on_finish) + var/datum/pathfind/path = new(caller, end, id, max_distance, mintargetdist, simulated_only, exclude, skip_first, diagonal_safety, on_finish) + if(path.start()) + active_pathing += path + return TRUE + return FALSE diff --git a/code/game/atoms.dm b/code/game/atoms.dm index d3ac1c5f1aa..c3f2e431116 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -173,6 +173,8 @@ /// the datum handler for our contents - see create_storage() for creation method var/datum/storage/atom_storage + /// How this atom should react to having its astar blocking checked + var/can_astar_pass = CANASTARPASS_DENSITY /** * Called when an atom is created in byond (built in engine proc) @@ -2067,6 +2069,9 @@ * * to_dir- What direction we're trying to move in, relevant for things like directional windows that only block movement in certain directions * * caller- The movable we're checking pass flags for, if we're making any such checks * * no_id: When true, doors with public access will count as impassible + * + * IMPORTANT NOTE: /turf/proc/LinkBlockedWithAccess assumes that overrides of CanAStarPass will always return true if density is FALSE + * If this is NOT you, ensure you edit your can_astar_pass variable. Check __DEFINES/path.dm **/ /atom/proc/CanAStarPass(obj/item/card/id/ID, to_dir, atom/movable/caller, no_id = FALSE) if(caller && (caller.pass_flags & pass_flags_self)) diff --git a/code/game/objects/structures/plasticflaps.dm b/code/game/objects/structures/plasticflaps.dm index 5532988725e..ed05978f57c 100644 --- a/code/game/objects/structures/plasticflaps.dm +++ b/code/game/objects/structures/plasticflaps.dm @@ -8,6 +8,7 @@ density = FALSE anchored = TRUE can_atmos_pass = ATMOS_PASS_NO + can_astar_pass = CANASTARPASS_ALWAYS_PROC /obj/structure/plasticflaps/opaque opacity = TRUE diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index b0e9d0d1960..3fac3df03af 100755 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -694,7 +694,7 @@ GLOBAL_LIST_EMPTY(station_turfs) * * simulated_only: Do we only worry about turfs with simulated atmos, most notably things that aren't space? * * no_id: When true, doors with public access will count as impassible */ -/turf/proc/reachableAdjacentTurfs(caller, ID, simulated_only, no_id = FALSE) +/turf/proc/reachableAdjacentTurfs(atom/movable/caller, ID, simulated_only, no_id = FALSE) var/static/space_type_cache = typecacheof(/turf/open/space) . = list() diff --git a/code/modules/mob/living/simple_animal/bot/bot.dm b/code/modules/mob/living/simple_animal/bot/bot.dm index 2f2536078d9..cb18ffe6be6 100644 --- a/code/modules/mob/living/simple_animal/bot/bot.dm +++ b/code/modules/mob/living/simple_animal/bot/bot.dm @@ -91,8 +91,6 @@ var/destination ///The next destination in the patrol route var/next_destination - ///If we should shuffle our adjacency checking - var/shuffle = FALSE /// the nearest beacon's tag var/nearest_beacon @@ -487,6 +485,7 @@ //Generalized behavior code, override where needed! +GLOBAL_LIST_EMPTY(scan_typecaches) /** * Attempt to scan tiles near [src], first by checking adjacent, then if a target is still not found, nearby. * @@ -495,31 +494,48 @@ * scan_range - how far away from [src] will be scanned, if nothing is found directly adjacent. */ /mob/living/simple_animal/bot/proc/scan(list/scan_types, old_target, scan_range = DEFAULT_SCAN_RANGE) - var/list/adjacent = shuffle(view(1, src)) - for(var/turf/scan as anything in adjacent) //Let's see if there's something right next to us first! - if(check_bot(scan)) //Is there another bot there? Then let's just skip it - continue - var/final_result = checkscan(scan, scan_types, old_target) - if(final_result) - return final_result + var/key = scan_types.Join(",") + var/list/scan_cache = GLOB.scan_typecaches[key] + if(!scan_cache) + scan_cache = typecacheof(scan_types) + GLOB.scan_typecaches[key] = scan_cache + if(!get_turf(src)) + return + // Nicer behavior, ensures we don't conflict with other bots quite so often + var/list/adjacent = list() + for(var/turf/to_walk in view(1, src)) + adjacent += to_walk - for(var/turf/scanned_turfs as anything in view(scan_range, src) - adjacent) //Search for something in range, minus what we already checked. - if(check_bot(scanned_turfs)) //Is there another bot there? Then let's just skip it - continue - var/final_result = checkscan(scanned_turfs, scan_types, old_target) - if(final_result) - return final_result + adjacent = shuffle(adjacent) -/mob/living/simple_animal/bot/proc/checkscan(atom/scan, list/scan_types, old_target) - for(var/scan_type in scan_types) - if(!istype(scan, scan_type)) //Check that the thing we found is the type we want! - continue //If not, keep searching! - if((REF(scan) in ignore_list) || (scan == old_target)) //Filter for blacklisted elements, usually unreachable or previously processed oness + var/list/turfs_to_walk = list() + for(var/turf/victim in view(scan_range, src)) + turfs_to_walk += victim + + turfs_to_walk = turfs_to_walk - adjacent + // Now we prepend adjacent since we want to run those first + turfs_to_walk = adjacent + turfs_to_walk + + for(var/turf/scanned as anything in turfs_to_walk) + // Check bot is inlined here to save cpu time + //Is there another bot there? Then let's just skip it so we dont all atack on top of eachother. + var/bot_found = FALSE + for(var/mob/living/simple_animal/bot/buddy in scanned.contents) + if(istype(buddy, type) && (buddy != src)) + bot_found = TRUE + break + if(bot_found) continue - var/scan_result = process_scan(scan) //Some bots may require additional processing when a result is selected. - if(!isnull(scan_result)) - return scan_result + for(var/atom/thing as anything in scanned) + if(!scan_cache[thing.type]) //Check that the thing we found is the type we want! + continue //If not, keep searching! + if(thing == old_target || (REF(thing) in ignore_list)) //Filter for blacklisted elements, usually unreachable or previously processed oness + continue + + var/scan_result = process_scan(thing) //Some bots may require additional processing when a result is selected. + if(!isnull(scan_result)) + return scan_result //When the scan finds a target, run bot specific processing to select it for the next step. Empty by default. /mob/living/simple_animal/bot/proc/process_scan(scan_target) @@ -529,10 +545,9 @@ var/turf/target_turf = get_turf(targ) if(!target_turf) return FALSE - for(var/turf_contents in target_turf.contents) - //Is there another bot there already? If so, let's skip it so we dont all atack on top of eachother. - if(istype(turf_contents, type) && (turf_contents != src)) - return TRUE //Let's abort if we find a bot so we dont have to keep rechecking + for(var/mob/living/simple_animal/bot/buddy in target_turf.contents) + if(istype(buddy, type) && (buddy != src)) + return TRUE return FALSE /mob/living/simple_animal/bot/proc/add_to_ignore(subject) @@ -813,10 +828,11 @@ Pass a positive integer as an argument to override a bot's default speed. /mob/living/simple_animal/bot/proc/calc_summon_path(turf/avoid) check_bot_access() - INVOKE_ASYNC(src, PROC_REF(do_calc_summon_path), avoid) + var/datum/callback/path_complete = CALLBACK(src, PROC_REF(on_summon_path_finish)) + SSpathfinder.pathfind(src, summon_target, max_distance=150, id=access_card, exclude=avoid, on_finish = path_complete) -/mob/living/simple_animal/bot/proc/do_calc_summon_path(turf/avoid) - set_path(get_path_to(src, summon_target, max_distance=150, id=access_card, exclude=avoid)) +/mob/living/simple_animal/bot/proc/on_summon_path_finish(list/path) + set_path(path) if(!length(path)) //Cannot reach target. Give up and announce the issue. speak("Summon command failed, destination unreachable.",radio_channel) bot_reset() @@ -1024,6 +1040,12 @@ Pass a positive integer as an argument to override a bot's default speed. var/list/path_images = active_hud_list[DIAG_PATH_HUD] QDEL_LIST(path_images) if(newpath) + var/mutable_appearance/path_image = new /mutable_appearance() + path_image.icon = path_image_icon + path_image.icon_state = path_image_icon_state + path_image.layer = BOT_PATH_LAYER + path_image.appearance_flags = RESET_COLOR|RESET_TRANSFORM + path_image.color = path_image_color for(var/i in 1 to newpath.len) var/turf/T = newpath[i] if(T == loc) //don't bother putting an image if it's where we already exist. @@ -1047,16 +1069,11 @@ Pass a positive integer as an argument to override a bot's default speed. else ntransform.Scale(1, -1) prevI.transform = ntransform - var/mutable_appearance/MA = new /mutable_appearance() - MA.icon = path_image_icon - MA.icon_state = path_image_icon_state - MA.layer = ABOVE_OPEN_TURF_LAYER - SET_PLANE(MA, GAME_PLANE, T) - MA.appearance_flags = RESET_COLOR|RESET_TRANSFORM - MA.color = path_image_color - MA.dir = direction + + SET_PLANE(path_image, GAME_PLANE, T) + path_image.dir = direction var/image/I = image(loc = T) - I.appearance = MA + I.appearance = path_image path[T] = I path_images += I diff --git a/code/modules/mob/living/simple_animal/bot/floorbot.dm b/code/modules/mob/living/simple_animal/bot/floorbot.dm index 2130f9c443f..cb2e56ccb4b 100644 --- a/code/modules/mob/living/simple_animal/bot/floorbot.dm +++ b/code/modules/mob/living/simple_animal/bot/floorbot.dm @@ -226,7 +226,6 @@ if(target) if(loc == target || loc == get_turf(target)) if(check_bot(target)) //Target is not defined at the parent - shuffle = TRUE if(prob(50)) //50% chance to still try to repair so we dont end up with 2 floorbots failing to fix the last breach target = null path = list() diff --git a/code/modules/mob/living/simple_animal/parrot.dm b/code/modules/mob/living/simple_animal/parrot.dm index 26f4f11d3c9..66462edc785 100644 --- a/code/modules/mob/living/simple_animal/parrot.dm +++ b/code/modules/mob/living/simple_animal/parrot.dm @@ -654,7 +654,7 @@ GLOBAL_LIST_INIT(strippable_parrot_items, create_strippable_list(list( item = I break if(item) - if(!get_path_to(src, item)) + if(!get_path_to(src, item)) // WHY DO WE DISREGARD THE PATH AHHHHHH item = null continue return item diff --git a/tgstation.dme b/tgstation.dme index a2a1202bc42..c75ae2bce4a 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -139,6 +139,7 @@ #include "code\__DEFINES\pai.dm" #include "code\__DEFINES\paintings.dm" #include "code\__DEFINES\paper.dm" +#include "code\__DEFINES\path.dm" #include "code\__DEFINES\perf_test.dm" #include "code\__DEFINES\pinpointers.dm" #include "code\__DEFINES\pipe_construction.dm"