Adds AI ventcrawling code, does not implement it (#29661)

* bam

* push

* im trolling myself

* code

* ok lets go

* fix

* improvements
This commit is contained in:
Contrabang
2025-07-04 12:05:23 -07:00
committed by GitHub
parent b1908df76b
commit 211633f2b5
16 changed files with 478 additions and 5 deletions
+15
View File
@@ -68,6 +68,21 @@
/// key holding our eating cooldown
#define BB_EAT_FOOD_COOLDOWN "BB_EAT_FOOD_COOLDOWN"
// Ventcrawling
/// key holding the turf we will move to
#define BB_VENTCRAWL_FINAL_TARGET "BB_VENTCRAWL_FINAL_TARGET"
/// key holding the vent we will move to
#define BB_VENTCRAWL_ENTRANCE "BB_VENTCRAWL_ENTRANCE"
/// key holding the vent we will ventcrawl to
#define BB_VENTCRAWL_EXIT "BB_VENTCRAWL_EXIT"
/// key holding a boolean value if we're entering into a vent
#define BB_VENTCRAWL_IS_ENTERING "BB_VENTCRAWL_IS_ENTERING"
/// key holding the amount of delay between steps in a vent (recommended: 1)
#define BB_VENTCRAWL_DELAY "BB_VENTCRAWL_DELAY"
/// key holding a range to look for vents (recommended: 10)
#define BB_VENT_SEARCH_RANGE "BB_VENT_SEARCH_RANGE"
// Tipped blackboards
/// Bool that means a basic mob will start reacting to being tipped in its planning
+117
View File
@@ -0,0 +1,117 @@
/datum/ventcrawl_node
var/obj/machinery/atmospherics/atmos
/// The node we just came from
var/datum/ventcrawl_node/previous_node
/// How many steps it's taken to get here from the start
var/number_tiles = 0
/// The A* node weight (f_value = number_of_tiles + heuristic)
var/f_value = INFINITY
/datum/ventcrawl_node/New(obj/machinery/atmospherics/atmos, datum/ventcrawl_node/incoming_previous_node, obj/machinery/atmospherics/incoming_goal)
src.atmos = atmos
src.previous_node = incoming_previous_node
if(previous_node)
src.number_tiles = previous_node.number_tiles + 1
src.f_value = number_tiles + get_dist_euclidian(atmos, incoming_goal)
/datum/ventcrawl_node/Destroy(force)
previous_node = null
return ..()
/proc/HeapPathWeightCompareVent(datum/ventcrawl_node/a, datum/ventcrawl_node/b)
return b.f_value - a.f_value
/**
* Basic A-star implementation (I think), for pathfinding through vents.
*/
/datum/pathfind/ventcrawl
/// The movable we are pathing
var/atom/movable/requester
/// The vent we are trying to pathfind to
var/obj/machinery/atmospherics/unary/end
/// The open list/stack we pop nodes out from (TODO: make this a normal list and macro-ize the heap operations to reduce proc overhead)
var/datum/heap/open
/// The list we compile at the end if successful to pass back
var/list/path
///An assoc list that serves as the closed list. Key is the turf, points to true if we've seen it before
var/list/closed_set
/// If we should delete the first step in the path or not. Used often because it is just the starting point
var/skip_first = FALSE
/datum/pathfind/ventcrawl/proc/setup(atom/movable/requester, obj/machinery/atmospherics/unary/goal, skip_first, list/datum/callback/on_finish)
src.requester = requester
src.end = goal
src.on_finish = on_finish
src.skip_first = skip_first
open = new /datum/heap(/proc/HeapPathWeightCompareVent)
closed_set = list()
/datum/pathfind/ventcrawl/Destroy(force)
. = ..()
requester = null
end = null
open = null
/datum/pathfind/ventcrawl/start()
start = start || requester.loc
. = ..()
if(!.)
return .
if(start.z != end.z || start == end) //no pathfinding between z levels
return FALSE
open.Insert(new /datum/ventcrawl_node(start, null, end))
closed_set[start] = TRUE
return TRUE
/datum/pathfind/ventcrawl/search_step()
. = ..()
if(!.)
return .
if(QDELETED(requester))
return FALSE
while(!open.IsEmpty() && !path)
var/datum/ventcrawl_node/current = open.Pop()
// var/obj/machinery/atmospherics/fast_neighbor = current.atmos.findConnecting(angle2dir_cardinal(get_angle(current.atmos, end)))
// if(fast_neighbor && !closed_set[fast_neighbor])
// open.Insert(new /datum/ventcrawl_node(fast_neighbor, current, end)) // try to move towards it first!
// continue
closed_set[current.atmos] = TRUE
for(var/dir in GLOB.cardinal)
if(!(dir & current.atmos.initialize_directions))
continue
var/obj/machinery/atmospherics/neighbor = current.atmos.findConnecting(dir)
if(!neighbor || closed_set[neighbor])
continue
if(neighbor == end)
unwind_path(current)
return
open.Insert(new /datum/ventcrawl_node(neighbor, current, end))
// Stable, we'll just be back later
if(TICK_CHECK)
return TRUE
return TRUE
/datum/pathfind/ventcrawl/finished()
//we're done! turn our reversed path (end to start) into a path (start to end)
closed_set = null
QDEL_NULL(open)
path ||= list()
if(skip_first && length(path))
path.Cut(1, 2)
hand_back(path)
return ..()
/datum/pathfind/ventcrawl/proc/unwind_path(datum/ventcrawl_node/unwind_node)
path = list(end, unwind_node.atmos)
while(unwind_node.previous_node)
path += unwind_node.previous_node.atmos
unwind_node = unwind_node.previous_node
path = reverselist(path)
@@ -833,3 +833,86 @@
var/turf/next = get_step_rand(moving)
moving.Move(next, get_dir(moving, next), FALSE, !(flags & MOVEMENT_LOOP_NO_DIR_UPDATE))
return old_loc != moving?.loc ? MOVELOOP_SUCCESS : MOVELOOP_FAILURE
/**
* Used for getting to a vent in a connected pipeline when ventcrawling.
*
* Returns TRUE if the loop sucessfully started, or FALSE if it failed
*
* Arguments:
* moving - The atom we want to move
* chasing - The atom we want to move towards
* min_dist - the closest we're allower to get to the target
* delay - How many deci-seconds to wait between fires. Defaults to the lowest value, 0.1
* timeout - Time in deci-seconds until the moveloop self expires. Defaults to infinity
* subsystem - The movement subsystem to use. Defaults to SSmovement. Only one loop can exist for any one subsystem
* priority - Defines how different move loops override each other. Lower numbers beat higher numbers, equal defaults to what currently exists. Defaults to MOVEMENT_DEFAULT_PRIORITY
* flags - Set of bitflags that effect move loop behavior in some way. Check _DEFINES/movement.dm
*
**/
/datum/move_manager/proc/ventcrawl(moving, chasing, delay, timeout, subsystem, priority, flags, skip_first = TRUE, datum/extra_info)
return add_to_loop(moving, subsystem, /datum/move_loop/has_target/ventcrawl, priority, flags, extra_info, delay, timeout, chasing, skip_first)
// Move loop for ventcrawling
/datum/move_loop/has_target/ventcrawl
flags = MOVEMENT_LOOP_IGNORE_GLIDE
///A list for the path we're currently following
var/list/movement_path
///Cooldown for repathing, prevents spam
COOLDOWN_DECLARE(repath_cooldown)
///Bool used to determine if we're already making a path in JPS. this prevents us from re-pathing while we're already busy.
var/is_pathing = FALSE
///Should we skip the first step? This is the tile we're currently on, which breaks some things
var/skip_first
/datum/move_loop/has_target/ventcrawl/setup(delay, timeout, atom/chasing, skip_first)
. = ..()
if(!.)
return
src.skip_first = skip_first
/datum/move_loop/has_target/ventcrawl/loop_started()
. = ..()
if(!movement_path)
INVOKE_ASYNC(src, PROC_REF(recalculate_path))
/datum/move_loop/has_target/ventcrawl/loop_stopped()
. = ..()
movement_path = null
///Tries to calculate a new path for this moveloop.
/datum/move_loop/has_target/ventcrawl/proc/recalculate_path()
if(!COOLDOWN_FINISHED(src, repath_cooldown))
return
COOLDOWN_START(src, repath_cooldown, 0.5 SECONDS)
if(SSpathfinder.ventcrawl_pathfind(moving, target, skip_first, list(CALLBACK(src, PROC_REF(on_finish_pathing)))))
is_pathing = TRUE
///Called when a path has finished being created
/datum/move_loop/has_target/ventcrawl/proc/on_finish_pathing(list/path)
movement_path = path
is_pathing = FALSE
/datum/move_loop/has_target/ventcrawl/move()
// jps has skip_first for this purpose i think but we're not jps
// while(length(movement_path) && movement_path[1] == moving.loc)
// movement_path.Cut(1, 2)
if(!length(movement_path))
if(is_pathing)
return MOVELOOP_NOT_READY
INVOKE_ASYNC(src, PROC_REF(recalculate_path))
return MOVELOOP_FAILURE
var/obj/machinery/atmospherics/next_step = movement_path[1]
var/atom/old_loc = moving.loc
old_loc.relaymove(moving, get_dir(moving, next_step))
. = (old_loc != moving?.loc) ? MOVELOOP_SUCCESS : MOVELOOP_FAILURE
// this check if we're on exactly the next tile may be overly brittle for dense objects who may get bumped slightly
// to the side while moving but could maybe still follow their path without needing a whole new path
if(.)
if(length(movement_path))
movement_path.Cut(1, 2)
return
INVOKE_ASYNC(src, PROC_REF(recalculate_path))
return MOVELOOP_FAILURE
@@ -204,3 +204,12 @@ SUBSYSTEM_DEF(pathfinder)
continue
valid_maps += shared_source
return valid_maps
/// Initiates a pathfind. Returns true if we're good, FALSE if something's failed
/datum/controller/subsystem/pathfinder/proc/ventcrawl_pathfind(atom/movable/caller, atom/end, skip_first, list/datum/callback/on_finish)
var/datum/pathfind/ventcrawl/path = new()
path.setup(caller, end, skip_first, on_finish)
if(path.start())
active_pathing += path
return TRUE
return FALSE
@@ -34,6 +34,9 @@
/datum/ai_behavior/travel_towards/adjacent
required_distance = 1
/datum/ai_behavior/travel_towards/ventcrawling
new_movement_type = /datum/ai_movement/ventcrawl
/**
* # Travel Towards Atom
* Travel towards an atom you pass directly from the controller rather than a blackboard key.
@@ -0,0 +1,171 @@
/datum/ai_planning_subtree/ventcrawl
/datum/ai_planning_subtree/ventcrawl/select_behaviors(datum/ai_controller/controller, seconds_per_tick)
// we're in a pipe
if(istype(controller.pawn.loc, /obj/machinery/atmospherics))
// we have an exit vent to go to
if(controller.blackboard_key_exists(BB_VENTCRAWL_EXIT))
var/obj/machinery/atmospherics/A = controller.blackboard[BB_VENTCRAWL_EXIT]
if(!A.can_crawl_through())
controller.set_blackboard_key(BB_VENTCRAWL_ENTRANCE, null)
controller.set_blackboard_key(BB_VENTCRAWL_EXIT, null)
return .() // call this proc again
// lets go there!
controller.queue_behavior(/datum/ai_behavior/travel_towards/ventcrawling, BB_VENTCRAWL_EXIT)
return SUBTREE_RETURN_FINISH_PLANNING
// We dont have an exit vent, but we have a target. Let's find an exit vent near it.
if(controller.blackboard_key_exists(BB_VENTCRAWL_FINAL_TARGET))
controller.queue_behavior(/datum/ai_behavior/find_and_set/ventcrawl, BB_VENTCRAWL_EXIT, null, controller.blackboard[BB_VENT_SEARCH_RANGE])
return
// we dont have an set exit vent nor a final target... let's just get out of the pipe.
controller.queue_behavior(/datum/ai_behavior/find_and_set/pipenet_vent, BB_VENTCRAWL_EXIT, null, controller.blackboard[BB_VENT_SEARCH_RANGE])
return
if(!controller.blackboard_key_exists(BB_VENTCRAWL_FINAL_TARGET))
return
// we have a target and we're nearby
if(controller.blackboard_key_exists(BB_VENT_SEARCH_RANGE) && get_dist(controller.pawn, controller.blackboard[BB_VENTCRAWL_FINAL_TARGET]) <= controller.blackboard[BB_VENT_SEARCH_RANGE])
// sometimes mobs can get stuck here if they're behind a wall... dunno how to fix this.
controller.set_blackboard_key(BB_VENTCRAWL_ENTRANCE, null)
controller.set_blackboard_key(BB_VENTCRAWL_EXIT, null)
controller.queue_behavior(/datum/ai_behavior/travel_towards/stop_on_arrival, BB_VENTCRAWL_FINAL_TARGET)
return SUBTREE_RETURN_FINISH_PLANNING
// we're far-away, lets try to find a ventcrawl path
if(!controller.blackboard_key_exists(BB_VENTCRAWL_EXIT))
controller.queue_behavior(/datum/ai_behavior/find_and_set/ventcrawl, BB_VENTCRAWL_EXIT, null, controller.blackboard[BB_VENT_SEARCH_RANGE], FALSE)
return
// we found a path, lets take it!
controller.queue_behavior(/datum/ai_behavior/interact_with_vent, BB_VENTCRAWL_ENTRANCE)
return SUBTREE_RETURN_FINISH_PLANNING
/**
* This proc assumes that all ventcrawling creatures are omniscient.
*/
/datum/ai_behavior/find_and_set/ventcrawl/search_tactic(datum/ai_controller/controller, locate_path, search_range)
// we're inside a vent already, lets look for another way
if(istype(controller.pawn.loc, /obj/machinery/atmospherics))
var/obj/machinery/atmospherics/A = controller.pawn.loc
var/datum/pipeline/P = A.returnPipenet()
var/list/datum/pipeline/connected_pipelines = P.get_connected_pipelines()
for(var/turf/T in spiral_range_turfs(search_range, controller.blackboard[BB_VENTCRAWL_FINAL_TARGET])) // get the nearest vents to the target first
for(var/obj/machinery/atmospherics/exit in T)
if(!is_type_in_list(exit, GLOB.ventcrawl_machinery))
continue
if(!exit.can_crawl_through())
continue
if(exit.returnPipenet() in connected_pipelines)
return exit
return
// else, lets do a search for vents we have access to
var/mob/M = controller.pawn
var/datum/can_pass_info/pass_info = new(M, M.get_access())
var/list/obj/machinery/atmospherics/entrance_vents = nearby_vents(get_turf(controller.pawn), FALSE, search_range, pass_info)
if(!length(entrance_vents))
return
var/list/obj/machinery/atmospherics/exit_vents = nearby_vents(get_turf(controller.blackboard[BB_VENTCRAWL_FINAL_TARGET]), TRUE, search_range, pass_info)
if(!length(exit_vents))
return
// try to find ones that share a pipeline first
for(var/obj/machinery/atmospherics/exit in exit_vents)
var/datum/pipeline/exit_pipeline = exit.returnPipenet()
for(var/obj/machinery/atmospherics/entrance in entrance_vents)
if(exit_pipeline == entrance.returnPipenet()) // It will probably be quicker if theyre on the same pipenet
controller.set_blackboard_key(BB_VENTCRAWL_ENTRANCE, entrance)
return exit
// fuck it, search all possible paths
for(var/obj/machinery/atmospherics/exit in exit_vents)
var/datum/pipeline/exit_pipeline = exit.returnPipenet()
var/list/obj/machinery/atmospherics/vents = exit_pipeline.get_ventcrawls()
for(var/obj/machinery/atmospherics/entrance in (entrance_vents & vents))
if(entrance != exit) // They're connected and not the same vent
controller.set_blackboard_key(BB_VENTCRAWL_ENTRANCE, entrance)
return exit
/**
* Find something interesting nearby that we have access to.
*/
/datum/ai_behavior/find_and_set/ventcrawl/proc/nearby_vents(initial_location, reversed, max_dist, pass_info)
// to search
var/list/open_set = list(initial_location)
// already searched
var/list/closed_set = list()
// the vents to return
var/list/obj/machinery/atmospherics/vents = list()
while(length(open_set))
var/turf/current = popleft(open_set)
closed_set |= current
for(var/obj/machinery/atmospherics/atmos in current)
if(is_type_in_list(atmos, GLOB.ventcrawl_machinery) && atmos.can_crawl_through())
vents |= atmos
for(var/dir in GLOB.cardinal)
var/turf/next = get_step(current, dir)
if((next in closed_set) || (get_dist(initial_location, next) > max_dist))
continue
// some accesses are directionally sensitive, like one-way airlocks
if(reversed)
if(current.density || next.LinkBlockedWithAccess(current, pass_info))
continue
else
if(next.density || current.LinkBlockedWithAccess(next, pass_info))
continue
open_set |= next
return vents
/datum/ai_behavior/find_and_set/pipenet_vent/search_tactic(datum/ai_controller/controller, locate_path, search_range)
// presume that all ventcrawling creatures are omniscient, at least about vents :)
if(!istype(controller.pawn.loc, /obj/machinery/atmospherics))
return
var/obj/machinery/atmospherics/atmos_loc = controller.pawn.loc
var/datum/pipeline/P = atmos_loc.returnPipenet()
if(!P)
return
var/obj/machinery/atmospherics/closest
var/closest_dist = INFINITY
for(var/obj/machinery/atmospherics/vent in P.other_atmosmch)
var/vent_dist = get_dist(atmos_loc, vent)
if(vent_dist <= closest_dist)
closest = vent
closest_dist = vent_dist
return closest
/datum/ai_behavior/interact_with_vent
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH
action_cooldown = 1 SECONDS
/datum/ai_behavior/interact_with_vent/setup(datum/ai_controller/controller, target_key)
. = ..()
var/atom/target = controller.blackboard[target_key]
if(QDELETED(target))
return FALSE
set_movement_target(controller, target)
/datum/ai_behavior/interact_with_vent/perform(seconds_per_tick, datum/ai_controller/controller, target_key)
var/atom/target = controller.blackboard[target_key]
if(QDELETED(target))
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
var/obj/machinery/atmospherics/vent = controller.blackboard[target_key]
var/mob/living/L = controller.pawn
if(!istype(L))
stack_trace("Something got a non-living pawn to try ventcrawling. How peculiar.")
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
if(controller.blackboard[BB_VENTCRAWL_IS_ENTERING] == TRUE)
return AI_BEHAVIOR_DELAY
controller.set_blackboard_key(BB_VENTCRAWL_IS_ENTERING, TRUE)
L.handle_ventcrawl(vent)
controller.set_blackboard_key(BB_VENTCRAWL_IS_ENTERING, FALSE)
if(controller.pawn.loc != vent)
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
+4 -1
View File
@@ -4,6 +4,8 @@
var/list/moving_controllers = list()
/// How many times a given controller can fail on their route before they just give up.
var/max_pathing_attempts
/// Should this movement update the delay when a moveloop is about to happen?
var/update_moveloop_delay = TRUE
/// Override this to setup the moveloop you want to use.
/datum/ai_movement/proc/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance)
@@ -59,7 +61,8 @@
qdel(source) // stop moving
return MOVELOOP_SKIP_STEP
source.delay = controller.movement_delay
if(update_moveloop_delay)
source.delay = controller.movement_delay
if(allowed_to_move(source))
return NONE
+32
View File
@@ -0,0 +1,32 @@
/datum/ai_movement/ventcrawl
max_pathing_attempts = 10
update_moveloop_delay = FALSE
/datum/ai_movement/ventcrawl/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance)
. = ..()
var/atom/movable/moving = controller.pawn
// var/delay = controller.movement_delay / 4 // mobs typically move a lot faster in pipes, so divide by 3
var/delay = controller.blackboard[BB_VENTCRAWL_DELAY] || 1 // mobs typically move a lot faster in pipes
var/datum/move_loop/loop = GLOB.move_manager.ventcrawl(moving, current_movement_target, delay, skip_first = TRUE, subsystem = SSai_movement, extra_info = controller)
RegisterSignal(loop, COMSIG_MOVELOOP_PREPROCESS_CHECK, PROC_REF(pre_move))
RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, PROC_REF(post_move))
/datum/ai_movement/ventcrawl/allowed_to_move(datum/move_loop/has_target/ventcrawl/source)
// maybe check if the tile it would move it into make it exit the ventcrawling?? Probably a good idea since it can get sabo'd
// this check will do for now.
if(!length(source.movement_path))
return FALSE
var/atom/A = source.movement_path[1]
return !QDELETED(A)
/datum/ai_movement/ventcrawl/increment_pathing_failures(datum/ai_controller/controller)
. = ..()
var/obj/machinery/atmospherics/exit = controller.blackboard[BB_VENTCRAWL_EXIT]
var/datum/pipeline/exit_pipeline = exit?.returnPipenet()
var/obj/machinery/atmospherics/current = controller.pawn.loc
if(current?.returnPipenet() in exit_pipeline?.get_connected_pipelines()) // they're still connected... let it try to find another route.
return
// they're not connected, give us another chance to find a way out.
controller.set_blackboard_key(BB_VENTCRAWL_ENTRANCE, null)
controller.set_blackboard_key(BB_VENTCRAWL_EXIT, null)
controller.cancel_actions()
+1 -1
View File
@@ -107,7 +107,7 @@
return 0
else
var/play_soundeffect = 1
if(M.environment_smash)
if(istype(M) && M.environment_smash)
play_soundeffect = 0
var/obj_turf = get_turf(src) // play from the turf in case the object gets deleted mid attack
if(M.obj_damage)
@@ -466,4 +466,7 @@ Pipelines + Other Objects -> Pipe network
if(user)
to_chat(user, "<span class='notice'>You set the target pressure of [src] to maximum.</span>")
/obj/machinery/atmospherics/proc/get_machinery_pipelines()
return list()
#undef VENT_SOUND_DELAY
@@ -131,6 +131,9 @@
else if(A == node2)
return parent2
/obj/machinery/atmospherics/binary/get_machinery_pipelines()
return list(parent1, parent2)
/obj/machinery/atmospherics/binary/is_pipenet_split()
return TRUE
@@ -182,8 +182,11 @@
else if(A == node3)
return parent3
/obj/machinery/atmospherics/trinary/get_machinery_pipelines()
return list(parent1, parent2, parent3)
/obj/machinery/atmospherics/trinary/is_pipenet_split()
return FALSE
return TRUE
/obj/machinery/atmospherics/trinary/replacePipenet(datum/pipeline/Old, datum/pipeline/New)
if(Old == parent1)
@@ -90,6 +90,9 @@
/obj/machinery/atmospherics/unary/returnPipenet()
return parent
/obj/machinery/atmospherics/unary/get_machinery_pipelines()
return parent
/obj/machinery/atmospherics/unary/replacePipenet(datum/pipeline/Old, datum/pipeline/New)
if(Old == parent)
parent = New
@@ -231,3 +231,30 @@
/datum/pipeline/proc/remove_ventcrawler(mob/living/crawler)
UnregisterSignal(crawler, COMSIG_LIVING_EXIT_VENTCRAWL)
crawlers -= crawler
/**
* Gets all pipelines connected to this with valves, including src.
*/
/datum/pipeline/proc/get_connected_pipelines()
. = list()
var/list/possible_expansions = list(src)
while(length(possible_expansions))
var/datum/pipeline/P = popleft(possible_expansions)
if(!P)
break
. |= P
for(var/obj/machinery/atmospherics/V in P.other_atmosmch)
for(var/datum/pipeline/possible in V.get_machinery_pipelines())
if(possible in .)
continue
possible_expansions |= possible
/datum/pipeline/proc/get_ventcrawls(check_welded = TRUE)
. = list()
for(var/datum/pipeline/P in get_connected_pipelines())
for(var/obj/machinery/atmospherics/V in P.other_atmosmch)
if(!is_type_in_list(V, GLOB.ventcrawl_machinery))
continue
if(check_welded && !V.can_crawl_through())
continue
. |= V
@@ -541,8 +541,6 @@ GLOBAL_LIST_INIT(ventcrawl_machinery, list(/obj/machinery/atmospherics/unary/ven
var/ventcrawl_delay = 0 SECONDS
#else
var/ventcrawl_delay = 4.5 SECONDS
if(!client)
return
#endif
if(ismorph(src))
if(!do_after(src, ventcrawl_delay, target = src, hidden = TRUE))
+3
View File
@@ -214,6 +214,7 @@
#include "code\__HELPERS\paths\jps.dm"
#include "code\__HELPERS\paths\path.dm"
#include "code\__HELPERS\paths\sssp.dm"
#include "code\__HELPERS\paths\ventcrawl_path.dm"
#include "code\__HELPERS\sorts\__main.dm"
#include "code\__HELPERS\sorts\InsertSort.dm"
#include "code\__HELPERS\sorts\MergeSort.dm"
@@ -472,6 +473,7 @@
#include "code\datums\ai\basic_mobs\basic_subtrees\target_retaliate.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\tip_reaction.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\use_mob_ability.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\ventcrawl_subtree.dm"
#include "code\datums\ai\basic_mobs\hunting_behavior\find_and_hunt_target.dm"
#include "code\datums\ai\basic_mobs\hunting_behavior\find_hunt_target.dm"
#include "code\datums\ai\basic_mobs\hunting_behavior\hunt_target.dm"
@@ -483,6 +485,7 @@
#include "code\datums\ai\movement\ai_movement_jps.dm"
#include "code\datums\ai\movement\basic_avoidance.dm"
#include "code\datums\ai\movement\complete_stop.dm"
#include "code\datums\ai\movement\ventcrawl.dm"
#include "code\datums\ai\targeting_strategy\basic_targeting_strategy.dm"
#include "code\datums\ai\targeting_strategy\dont_target_friends.dm"
#include "code\datums\ai\targeting_strategy\targeting_strategy.dm"