mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-07-16 01:23:41 +01:00
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:
@@ -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
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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()
|
||||
@@ -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))
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user