Files
Paradise/code/datums/ai/basic_mobs/basic_subtrees/ventcrawl_subtree.dm
Contrabang 211633f2b5 Adds AI ventcrawling code, does not implement it (#29661)
* bam

* push

* im trolling myself

* code

* ok lets go

* fix

* improvements
2025-07-04 19:05:23 +00:00

172 lines
7.5 KiB
Plaintext

/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