[MIRROR] Converts Spiderlings from Structures to Basic Mobs [MDB IGNORE] (#20852)

* Converts Spiderlings from Structures to Basic Mobs (#75001)

If I could've made this more atomic, I would have in a heartbeat, trust
me.

## About The Pull Request

Hey there. People were mocking us for having spiderlings still be a
subtype of `/obj/structure`. I decided to take a lot of time to fix
that. A lot of behavior it was implementing was just pseudo-mob stuff,
so it was actually easier than it looked for the raw conversion. A lot
of the footwork on spider stuff in the basic framework was already done
previously by Jacquerel, so that was pretty nice.

However, there are two new things that weren't introduced in the code
that had to be put in.

A) A component to handle growth and differentiation into a mob. This may
have already existed, no clue. If it does (and it's NOT
evolutionary_leap), let me know.
B) AI Behavior to handle seeking out a vent, entering a vent, and then
exiting out of a different vent. I may have gone a bit wacky on the
code, but it certainly works as expected (spiderling goes in one vent,
exits the other). Let me know if you can think of a way it can be better
optimized, but it was deliberately written to be very failsafey in case
shit goes yonkers.

One fundamental difference between structure spiderlings and basic mob
spiderlings (beyond the AI and not just a random prob() check for
movement) is the fact that they had vent movement coded in... but we
_really_ don't need stuff like that for our intents and purposes. If the
range turns out to be too OP in the current framework, we can always
change it up a bit, but also there's a _lot_ of vents we can end up in
the station (my testing had one spiderling end up in the AI sat to get
obliterated).
## Why It's Good For The Game

Spiderlings aren't structures! They behave like a mob should! Players
can possess spiderlings! They work seamlessly with differentiating into
a giant spider! Better AI! More room for people to add into this very
under-utilized buggers!
## Changelog
🆑
refactor: Spiderlings are now basic mobs, report any complete
weirdness/deviation from known behavior. They should be a lot more
intelligent now though.
add: AI Spiderlings are super fragile, but they're also super fast,
especially when they get into a vent. Once they're in circulation, they
could end up everywhere! Maybe in the armory, maybe in a locked closet
in maintenance. Be sure to be vigilant and splat them whenever you can
to save the station from a whole lotta heartache!
/🆑

---------

Co-authored-by: MrMelbert <51863163+MrMelbert@ users.noreply.github.com>

* Converts Spiderlings from Structures to Basic Mobs

---------

Co-authored-by: san7890 <the@san7890.com>
Co-authored-by: MrMelbert <51863163+MrMelbert@ users.noreply.github.com>
This commit is contained in:
SkyratBot
2023-04-30 12:54:58 -07:00
committed by GitHub
co-authored by MrMelbert san7890
parent cf9fce9f8b
commit 7028b1d32e
25 changed files with 508 additions and 142 deletions
@@ -0,0 +1,138 @@
/// We hop into the vents through a vent outlet, and then crawl around a bit. Jolly good times.
/// This also assumes that we are on the turf that the vent outlet is on. If it isn't, shit.
/// Warning: this was really snowflake code lifted from an obscure feature that likely has not been touched for over five years years.
/// Something that isn't implemented is the ability to actually crawl through vents ourselves because I think that's just a waste of time for the same effect (instead of psuedo-teleportation, do REAL forceMoving)
/// If you are seriously considering using this component, it would be a great idea to extend this proc to be more versatile/less overpowered - the mobs that currently implement this benefit the most
/// since they are weak as shit with only five health. Up to you though, don't take what's written here as gospel.
/datum/ai_behavior/crawl_through_vents
action_cooldown = 10 SECONDS
/datum/ai_behavior/crawl_through_vents/setup(datum/ai_controller/controller, target_key)
action_cooldown = controller.blackboard[BB_VENTCRAWL_COOLDOWN] || initial(action_cooldown)
. = ..()
var/obj/machinery/atmospherics/components/unary/vent_pump/target = controller.blackboard[target_key] || controller.blackboard[BB_ENTRY_VENT_TARGET]
return istype(target) && isliving(controller.pawn) // only mobs can vent crawl in the current framework
/datum/ai_behavior/crawl_through_vents/perform(seconds_per_tick, datum/ai_controller/controller, target_key)
. = ..()
var/obj/machinery/atmospherics/components/unary/vent_pump/entry_vent = controller.blackboard[target_key] || controller.blackboard[BB_ENTRY_VENT_TARGET]
var/mob/living/cached_pawn = controller.pawn
if(HAS_TRAIT(cached_pawn, TRAIT_MOVE_VENTCRAWLING) || !controller.blackboard[BB_CURRENTLY_TARGETTING_VENT] || !is_vent_valid(entry_vent))
return
if(!cached_pawn.can_enter_vent(entry_vent, provide_feedback = FALSE)) // we're an AI we scoff at feedback
finish_action(controller, FALSE, target_key) // "never enter a hole you can't get out of"
return
var/vent_we_exit_out_of = calculate_exit_vent(controller, target_key)
if(isnull(vent_we_exit_out_of)) // don't get into the vents if we can't get out of them, that's SILLY.
finish_action(controller, FALSE, target_key)
return
controller.set_blackboard_key(BB_CURRENTLY_TARGETTING_VENT, FALSE) // must be done here because we have a do_after sleep in handle_ventcrawl unfortunately and double dipping could lead to erroneous suicide pill calls.
cached_pawn.handle_ventcrawl(entry_vent)
if(!HAS_TRAIT(cached_pawn, TRAIT_MOVE_VENTCRAWLING)) //something failed and we ARE NOT IN THE VENT even though the earlier check said we were good to go! odd.
finish_action(controller, FALSE, target_key)
return
controller.set_blackboard_key(BB_EXIT_VENT_TARGET, vent_we_exit_out_of)
if(prob(50))
cached_pawn.visible_message(
span_warning("[src] scrambles into the ventilation ducts!"),
span_hear("You hear something scampering through the ventilation ducts."),
)
var/lower_vent_time_limit = controller.blackboard[BB_LOWER_VENT_TIME_LIMIT] // the least amount of time we spend in the vents
var/upper_vent_time_limit = controller.blackboard[BB_UPPER_VENT_TIME_LIMIT] // the most amount of time we spend in the vents
addtimer(CALLBACK(src, PROC_REF(exit_the_vents), controller), rand(lower_vent_time_limit, upper_vent_time_limit))
controller.set_blackboard_key(BB_GIVE_UP_ON_VENT_PATHING_TIMER_ID, addtimer(CALLBACK(src, PROC_REF(suicide_pill), controller), controller.blackboard[BB_TIME_TO_GIVE_UP_ON_VENT_PATHING], TIMER_STOPPABLE))
/// Figure out an exit vent that we should head towards. If we don't have one, default to the entry vent. If they're all kaput, we die.
/datum/ai_behavior/crawl_through_vents/proc/calculate_exit_vent(datum/ai_controller/controller, target_key)
var/obj/machinery/atmospherics/components/unary/vent_pump/returnable_vent
var/obj/machinery/atmospherics/components/unary/vent_pump/vent_we_entered_through = controller.blackboard[target_key] || controller.blackboard[BB_ENTRY_VENT_TARGET]
var/datum/pipeline/entry_vent_parent = vent_we_entered_through.parents[1]
var/list/potential_exits = list()
for(var/obj/machinery/atmospherics/components/unary/vent_pump/vent in entry_vent_parent.other_atmos_machines)
if(is_vent_valid(vent))
potential_exits.Add(vent)
if(length(potential_exits))
returnable_vent = pick(potential_exits)
return returnable_vent
// if we're here, we're in "what the flarp" mode... okay maybe we can default to the vent we entered in.
returnable_vent = vent_we_entered_through
if(is_vent_valid(vent_we_entered_through))
// AH WHAT THE FUCK. okay, maybe we're not inside the vents yet? let's return null and we can pick up on that based on the wider context of the proc that invokes it.
return null
return returnable_vent // we return null in case something yonked between then and now so it's all good man
/// We've had enough horsing around in the vents, it's time to get out.
/datum/ai_behavior/crawl_through_vents/proc/exit_the_vents(datum/ai_controller/controller, target_key)
var/obj/machinery/atmospherics/components/unary/vent_pump/emergency_vent // vent we will scramble to search for in case plan A is a bust (exit vent)
var/obj/machinery/atmospherics/components/unary/vent_pump/exit_vent = controller.blackboard[BB_EXIT_VENT_TARGET]
var/mob/living/living_pawn = controller.pawn
if(!HAS_TRAIT(living_pawn, TRAIT_MOVE_VENTCRAWLING) && isturf(get_turf(living_pawn))) // we're out of the vents, so no need to do an exit
finish_action(controller, TRUE, target_key) // assume that we got yeeted out somehow and call this so we can halt the suicide pill timer.
return
living_pawn.forceMove(exit_vent)
if(!living_pawn.can_enter_vent(exit_vent, provide_feedback = FALSE))
// oh shit, something happened while we were waiting on that timer. let's figure out a different way to get out of here.
emergency_vent = calculate_exit_vent(controller)
if(isnull(emergency_vent))
// it's joever. we cooked too hard.
suicide_pill(controller, target_key)
return
controller.set_blackboard_key(BB_EXIT_VENT_TARGET, emergency_vent) // assign and go again
addtimer(CALLBACK(src, PROC_REF(exit_the_vents), controller), (rand(controller.blackboard[BB_LOWER_VENT_TIME_LIMIT], controller.blackboard[BB_UPPER_VENT_TIME_LIMIT]) / 2)) // we're in danger mode, so scurry out at half the time it would normally take.
return
living_pawn.handle_ventcrawl(exit_vent)
if(HAS_TRAIT(living_pawn, TRAIT_MOVE_VENTCRAWLING)) // how'd we fail? what the fuck
stack_trace("We failed to exit the vents, even though we should have been fine? This is very weird.")
suicide_pill() // all of the prior checks say we should have definitely made it through, but we didn't. dammit.
return
finish_action(controller, TRUE, target_key) // we did it! we went into the vents and out of the vents. poggers.
/// Incredibly stripped down version of the overarching `can_enter_vent` proc on `/mob, just meant for rapid rechecking of a vent. Will be TRUE if not blocked, FALSE otherwise.
/datum/ai_behavior/crawl_through_vents/proc/is_vent_valid(obj/machinery/atmospherics/components/unary/vent_pump/checkable)
return !QDELETED(checkable) && !checkable.welded
/// Aw fuck, we may have been bested somehow. Regardless of what we do, we can't exit through a vent! Let's end our misery and prevent useless endless calculations.
/datum/ai_behavior/crawl_through_vents/proc/suicide_pill(datum/ai_controller/controller, target_key)
var/mob/living/living_pawn = controller.pawn
if(istype(living_pawn))
finish_action(controller, FALSE, target_key)
if(isnull(living_pawn.client)) // only call death if we don't have a client because maybe their natural intelligence can pick up where our AI calculations have failed
living_pawn.death(TRUE) // call gibbed as true because we are never coming back it is so fucking joever
return
if(QDELETED(living_pawn)) // we got deleted by some other means, just presume the action is a wash and get outta here
return
qdel(living_pawn) // failover, we really should've been caught in the istype() but lets just bow out of existing at this point
/datum/ai_behavior/crawl_through_vents/finish_action(datum/ai_controller/controller, succeeded, target_key)
. = ..()
deltimer(controller.blackboard[BB_GIVE_UP_ON_VENT_PATHING_TIMER_ID])
controller.clear_blackboard_key(target_key)
controller.clear_blackboard_key(BB_ENTRY_VENT_TARGET)
controller.clear_blackboard_key(BB_EXIT_VENT_TARGET)
controller.set_blackboard_key(BB_CURRENTLY_TARGETTING_VENT, FALSE) // just in case
@@ -0,0 +1,20 @@
/// Opportunistically searches for and hides/scurries through vents.
/datum/ai_planning_subtree/opportunistic_ventcrawler
/datum/ai_planning_subtree/opportunistic_ventcrawler/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
if(HAS_TRAIT(controller.pawn, TRAIT_MOVE_VENTCRAWLING))
return SUBTREE_RETURN_FINISH_PLANNING // hold on let me cook
var/obj/machinery/atmospherics/components/unary/vent_pump/target = controller.blackboard[BB_ENTRY_VENT_TARGET]
if(QDELETED(target))
controller.queue_behavior(/datum/ai_behavior/find_and_set, BB_ENTRY_VENT_TARGET, /obj/machinery/atmospherics/components/unary/vent_pump) // keep looking otherwise they KILL US AND WE DIE
return
if(get_turf(controller.pawn) != get_turf(target))
controller.queue_behavior(/datum/ai_behavior/travel_towards, BB_ENTRY_VENT_TARGET)
return
controller.set_blackboard_key(BB_CURRENTLY_TARGETTING_VENT, TRUE)
controller.queue_behavior(/datum/ai_behavior/crawl_through_vents, BB_ENTRY_VENT_TARGET)
return SUBTREE_RETURN_FINISH_PLANNING // we are going into this vent... no distractions