[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
+17
View File
@@ -237,6 +237,23 @@
///How long have we spent with no target?
#define BB_TARGETLESS_TIME "BB_targetless_time"
/// Key that holds a nearby vent that looks like it's a good place to hide
#define BB_ENTRY_VENT_TARGET "BB_entry_vent_target"
/// Key that holds a vent that we want to exit out of (when we're already in a pipenet)
#define BB_EXIT_VENT_TARGET "BB_exit_vent_target"
/// Do we plan on going inside a vent? Boolean.
#define BB_CURRENTLY_TARGETTING_VENT "BB_currently_targetting_vent"
/// How long should we wait before we try and enter a vent again?
#define BB_VENTCRAWL_COOLDOWN "BB_ventcrawl_cooldown"
/// The least amount of time (in seconds) we take to go through the vents.
#define BB_LOWER_VENT_TIME_LIMIT "BB_lower_vent_time_limit"
/// The most amount of time (in seconds) we take to go through the vents.
#define BB_UPPER_VENT_TIME_LIMIT "BB_upper_vent_time_limit"
/// How much time (in seconds) do we take until we completely go bust on vent pathing?
#define BB_TIME_TO_GIVE_UP_ON_VENT_PATHING "BB_seconds_until_we_give_up_on_vent_pathing"
/// The timer ID of the timer that makes us give up on vent pathing.
#define BB_GIVE_UP_ON_VENT_PATHING_TIMER_ID "BB_give_up_on_vent_pathing_timer_id"
/// Is there something that scared us into being stationary? If so, hold the reference here
#define BB_STATIONARY_CAUSE "BB_thing_that_made_us_stationary"
///How long should we remain stationary for?
+3
View File
@@ -1001,6 +1001,9 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
/// Basically, without this, COMSIG_IN_RANGE_OF_IRRADIATION won't fire once the object is irradiated.
#define TRAIT_BYPASS_EARLY_IRRADIATED_CHECK "radiation_bypass_early_irradiated_check"
/// Simple trait that just holds if we came into growth from a specific mob type. Should hold a REF(src) to the type of mob that caused the growth, not anything else.
#define TRAIT_WAS_EVOLVED "was_evolved_from_the_mob_we_hold_a_textref_to"
// Traits to heal for
/// This mob heals from carp rifts.
@@ -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
@@ -0,0 +1,125 @@
/**
* ### Growth and Differentiation Component: Used to randomly "grow" a creature into a new entity over its lifespan.
*
* If we are passed a typepath, we will 100% grow into that type. However, if we are not passed a typepath, we will pick one from a subtype of the parent we were applied to!
*
* Used for spiderlings to turn them into giant spiders.
*/
/datum/component/growth_and_differentiation
/// What this mob turns into when fully grown.
var/growth_path
/// Failover for how much time we have until we fully grow. If passed as null, we eschew setting up the timer.
/// Remember: We can grow earlier than this if the randomness rolls turn out to be in our favor though!
var/growth_time
/// Integer - Probability we grow per SPT_PROB
var/growth_probability
/// Integer - The lower bound for the percentage we have to grow before we can differentiate.
var/lower_growth_value
/// Integer - The upper bound for the percentage we have to grow before we can differentiate.
var/upper_growth_value
/// Optional callback for checks to see if we're okay to grow.
var/datum/callback/optional_checks
/// Optional callback in case we wish to override the default grow() behavior. Assume we supersede the change_mob_type() call if we have this set.
var/datum/callback/optional_grow_behavior
/// ID for the failover timer.
var/timer_id
/// Percentage we have grown.
var/percent_grown = 0
/// Are we ready to grow? This is just in case we fail our checks and need to wait until the next tick.
/// We only really need this because we have two competing systems, the timer and the probability-based growth. When one succeeds, this component is considered successful in growth,
/// and will actively try to grow the mob (only barred by optional checks).
var/ready_to_grow = FALSE
/datum/component/growth_and_differentiation/Initialize(growth_time, growth_path, growth_probability, lower_growth_value, upper_growth_value, optional_checks, optional_grow_behavior)
if(!isliving(parent))
return COMPONENT_INCOMPATIBLE
src.growth_path = growth_path
src.growth_time = growth_time
src.growth_probability = growth_probability
src.lower_growth_value = lower_growth_value
src.upper_growth_value = upper_growth_value
src.optional_checks = optional_checks
src.optional_grow_behavior = optional_grow_behavior
// If we haven't started the round, we can't do timer stuff. Let's wait in case we're mapped in or something.
if(!SSticker.HasRoundStarted() && !isnull(growth_time))
RegisterSignal(SSticker, COMSIG_TICKER_ROUND_STARTING, PROC_REF(comp_on_round_start))
return
return setup_growth_tracking()
/datum/component/growth_and_differentiation/Destroy(force, silent)
. = ..()
deltimer(timer_id)
/datum/component/growth_and_differentiation/UnregisterFromParent()
UnregisterSignal(SSticker, COMSIG_TICKER_ROUND_STARTING)
/// What we invoke when the round starts so we can set up our timer.
/datum/component/growth_and_differentiation/proc/comp_on_round_start()
SIGNAL_HANDLER
setup_growth_tracking()
UnregisterSignal(SSticker, COMSIG_TICKER_ROUND_STARTING)
/// Sets up the failover timer for certain growth.
/datum/component/growth_and_differentiation/proc/setup_growth_tracking()
var/did_we_add_at_least_one_thing = FALSE
if(!isnull(growth_time))
timer_id = addtimer(CALLBACK(src, PROC_REF(grow), FALSE), growth_time, TIMER_STOPPABLE)
if(!isnull(timer_id)) // realistically shouldn't happen considering how hardy addtimer() is but you can never be too sure
did_we_add_at_least_one_thing = TRUE
if(!isnull(growth_probability))
START_PROCESSING(SSdcs, src)
did_we_add_at_least_one_thing = TRUE
if(!did_we_add_at_least_one_thing)
stack_trace("Growth and Differentiation Component: Neither growth time nor probability were set! This component is useless!")
return COMPONENT_INCOMPATIBLE // if we're invoked via COMSIG_TICKER_ROUND_STARTING this won't do anything (and shouldn't be invoked since we nullcheck growth_time before adding that signal anyways)
return null // just for explicitness's sake, if they ever change Component's Initialize to have more return values make sure this is the one for "Success!"
/datum/component/growth_and_differentiation/process(seconds_per_tick) // check the prob we were passed in, and if we're lucky, grow!
if(ready_to_grow)
INVOKE_ASYNC(src, PROC_REF(grow), FALSE)
return
if(percent_grown >= 100)
ready_to_grow = TRUE
INVOKE_ASYNC(src, PROC_REF(grow), FALSE) // lets not waste any more of SSmobs time this tick.
return
if(SPT_PROB(growth_probability, seconds_per_tick))
percent_grown += rand(lower_growth_value, upper_growth_value)
/// Grows the mob into its new form.
/datum/component/growth_and_differentiation/proc/grow(silent)
if(!isnull(optional_checks) && !optional_checks.Invoke()) // we failed our checks somehow, but we're still ready to grow. Let's wait until next tick to see if our circumstances have changed.
ready_to_grow = TRUE
return
var/mob/living/old_mob = parent
if (old_mob.stat == DEAD)
qdel(src) // assume that we are priced out of growth once dead
return
STOP_PROCESSING(SSdcs, src)
if(!isnull(optional_grow_behavior)) // basically growth_path is OK to be null but only if we have an optional grow behavior.
optional_grow_behavior.Invoke()
return
var/mob/living/new_mob = growth_path
if(!istype(new_mob))
CRASH("Growth and Differentiation Component: Growth path was not a mob type! If you wanted to do something special, please put it in the optional_grow_behavior callback instead!")
var/new_mob_name = initial(new_mob.name)
if(!silent)
old_mob.visible_message(span_warning("[old_mob] grows into \a [new_mob_name]!"))
old_mob.change_mob_type(growth_path, old_mob.loc, new_name = new_mob_name, delete_old_mob = TRUE)
-126
View File
@@ -101,132 +101,6 @@
else if(isprojectile(mover))
return prob(30)
/obj/structure/spider/spiderling
name = "spiderling"
desc = "It never stays still for long."
icon_state = "spiderling"
anchored = FALSE
layer = PROJECTILE_HIT_THRESHHOLD_LAYER
max_integrity = 3
var/amount_grown = 0
var/grow_as = null
var/obj/machinery/atmospherics/components/unary/vent_pump/entry_vent
var/travelling_in_vent = 0
var/directive = "" //Message from the mother
var/list/faction = list(FACTION_SPIDER)
/obj/structure/spider/spiderling/Destroy()
new/obj/item/food/spiderling(get_turf(src))
. = ..()
/obj/structure/spider/spiderling/Initialize(mapload)
. = ..()
pixel_x = rand(6,-6)
pixel_y = rand(6,-6)
START_PROCESSING(SSobj, src)
AddComponent(/datum/component/swarming)
/obj/structure/spider/spiderling/hunter
grow_as = /mob/living/basic/giant_spider/hunter
/obj/structure/spider/spiderling/nurse
grow_as = /mob/living/basic/giant_spider/nurse
/obj/structure/spider/spiderling/midwife
grow_as = /mob/living/basic/giant_spider/midwife
/obj/structure/spider/spiderling/viper
grow_as = /mob/living/basic/giant_spider/viper
/obj/structure/spider/spiderling/tarantula
grow_as = /mob/living/basic/giant_spider/tarantula
/obj/structure/spider/spiderling/Bump(atom/user)
if(istype(user, /obj/structure/table))
forceMove(user.loc)
else
..()
/obj/structure/spider/spiderling/proc/cancel_vent_move()
forceMove(entry_vent)
entry_vent = null
/obj/structure/spider/spiderling/proc/vent_move(obj/machinery/atmospherics/components/unary/vent_pump/exit_vent)
if(QDELETED(exit_vent) || exit_vent.welded)
cancel_vent_move()
return
forceMove(exit_vent)
var/travel_time = round(get_dist(loc, exit_vent.loc) / 2)
addtimer(CALLBACK(src, PROC_REF(do_vent_move), exit_vent, travel_time), travel_time)
/obj/structure/spider/spiderling/proc/do_vent_move(obj/machinery/atmospherics/components/unary/vent_pump/exit_vent, travel_time)
if(QDELETED(exit_vent) || exit_vent.welded)
cancel_vent_move()
return
if(prob(50))
audible_message(span_hear("You hear something scampering through the ventilation ducts."))
addtimer(CALLBACK(src, PROC_REF(finish_vent_move), exit_vent), travel_time)
/obj/structure/spider/spiderling/proc/finish_vent_move(obj/machinery/atmospherics/components/unary/vent_pump/exit_vent)
if(QDELETED(exit_vent) || exit_vent.welded)
cancel_vent_move()
return
forceMove(exit_vent.loc)
entry_vent = null
/obj/structure/spider/spiderling/process()
if(travelling_in_vent)
if(isturf(loc))
travelling_in_vent = 0
entry_vent = null
else if(entry_vent)
if(get_dist(src, entry_vent) <= 1)
var/list/vents = list()
var/datum/pipeline/entry_vent_parent = entry_vent.parents[1]
for(var/obj/machinery/atmospherics/components/unary/vent_pump/temp_vent in entry_vent_parent.other_atmos_machines)
vents.Add(temp_vent)
if(!vents.len)
entry_vent = null
return
var/obj/machinery/atmospherics/components/unary/vent_pump/exit_vent = pick(vents)
if(prob(50))
visible_message("<B>[src] scrambles into the ventilation ducts!</B>", \
span_hear("You hear something scampering through the ventilation ducts."))
addtimer(CALLBACK(src, PROC_REF(vent_move), exit_vent), rand(20,60))
//=================
else if(prob(33))
var/list/nearby = oview(10, src)
if(nearby.len)
var/target_atom = pick(nearby)
SSmove_manager.move_to(src, target_atom)
if(prob(40))
src.visible_message(span_notice("\The [src] skitters[pick(" away"," around","")]."))
else if(prob(10))
//ventcrawl!
for(var/obj/machinery/atmospherics/components/unary/vent_pump/v in view(7,src))
if(!v.welded)
entry_vent = v
SSmove_manager.move_to(src, entry_vent, 1)
break
if(isturf(loc))
amount_grown += rand(0,2)
if(amount_grown >= 100)
if(!grow_as)
if(prob(3))
grow_as = pick(/mob/living/basic/giant_spider/tarantula, /mob/living/basic/giant_spider/viper, /mob/living/basic/giant_spider/midwife)
else
grow_as = pick(/mob/living/basic/giant_spider, /mob/living/basic/giant_spider/hunter, /mob/living/basic/giant_spider/nurse)
var/mob/living/basic/giant_spider/S = new grow_as(src.loc)
S.faction = faction.Copy()
S.directive = directive
qdel(src)
/obj/structure/spider/cocoon
name = "cocoon"
desc = "Something wrapped in silky spider web."
+1 -1
View File
@@ -855,7 +855,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
/mob/living/simple_animal/butterfly,
/mob/living/basic/cockroach,
/obj/item/queen_bee,
/obj/structure/spider/spiderling,
/mob/living/basic/spiderling,
/mob/living/simple_animal/hostile/ant,
/obj/effect/decal/cleanable/ants,
))
@@ -10,5 +10,5 @@
/obj/item/organ/internal/heart/gland/spiderman/activate()
to_chat(owner, span_warning("You feel something crawling in your skin."))
owner.faction |= FACTION_SPIDER
var/obj/structure/spider/spiderling/S = new(owner.drop_location())
var/mob/living/basic/spiderling/S = new(owner.drop_location())
S.directive = "Protect your nest inside [owner.real_name]."
+1 -1
View File
@@ -822,7 +822,7 @@
message_admins("[ADMIN_LOOKUPFLW(user)] last altered a hydro tray's contents which spawned spiderlings.")
user.log_message("last altered a hydro tray, which spiderlings spawned from.", LOG_GAME)
visible_message(span_warning("The pests seem to behave oddly..."))
spawn_atom_to_turf(/obj/structure/spider/spiderling/hunter, src, 3, FALSE)
spawn_atom_to_turf(/mob/living/basic/spiderling/hunter, src, 3, FALSE)
else if(myseed)
visible_message(span_warning("The pests seem to behave oddly in [myseed.name] tray, but quickly settle down..."))
@@ -62,7 +62,7 @@
if(!message)
return
var/my_message = span_spider("<b>Command from [user]:</b> [message]")
for(var/mob/living/basic/giant_spider/spider as anything in GLOB.spidermobs)
for(var/mob/living/basic/spider as anything in GLOB.spidermobs)
to_chat(spider, my_message)
for(var/ghost in GLOB.dead_mob_list)
var/link = FOLLOW_LINK(ghost, user)
@@ -0,0 +1,155 @@
/**
* # Spiderlings
*
* Baby spiders that are generated through a variety of means (like botany for instance).
* Able to vent-crawl and eventually grow into a full fledged giant spider.
*
*/
/mob/living/basic/spiderling
name = "spiderling"
desc = "It never stays still for long."
icon_state = "spiderling"
icon_dead = "spiderling_dead"
faction = list(FACTION_SPIDER)
speed = 1
move_resist = INFINITY // YOU CAN'T HANDLE ME LET ME BE FREE LET ME BE FREE LET ME BE FREE
speak_emote = list("hisses")
basic_mob_flags = FLAMMABLE_MOB | DEL_ON_DEATH
mob_size = MOB_SIZE_TINY
unique_name = TRUE
gold_core_spawnable = HOSTILE_SPAWN // because of what we grow into!
// we have _some_ bite
melee_damage_lower = 2
melee_damage_upper = 4
health = 5 // very low.
maxHealth = 5
unsuitable_cold_damage = 4
unsuitable_heat_damage = 4
death_message = "lets out a final hiss..."
ai_controller = /datum/ai_controller/basic_controller/spiderling
// VERY red, to fit the eyes
lighting_cutoff_red = 22
lighting_cutoff_green = 5
lighting_cutoff_blue = 5
/// The mob we will grow into.
var/mob/living/basic/giant_spider/grow_as = null
/// The message that the mother left for our big strong selves.
var/directive = ""
/// Simple boolean that determines if we should apply the spider antag to the player if they possess this mob. TRUE by default since we're always going to evolve into a spider that will have an antagonistic role.
var/apply_spider_antag = TRUE
/mob/living/basic/spiderling/Initialize(mapload)
. = ..()
// random placement since we're pretty small and to make the swarming component actually look like it's doing something when we have a buncha these fuckers
pixel_x = rand(6,-6)
pixel_y = rand(6,-6)
// the proc that handles passtable is nice but we should always be able to pass through table since we're so small so we can eschew adding that here
pass_flags |= PASSTABLE
ADD_TRAIT(src, TRAIT_PASSTABLE, INNATE_TRAIT)
ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT)
AddComponent(/datum/component/swarming)
AddElement(/datum/element/footstep, FOOTSTEP_MOB_CLAW, volume = 0.2) // they're small but you can hear 'em
// it's A-OKAY for grow_as to be null for the purposes of this component since we override that behavior anyhow.
AddComponent(\
/datum/component/growth_and_differentiation,\
growth_time = 10 MINUTES,\
growth_path = grow_as,\
growth_probability = 25,\
lower_growth_value = 1,\
upper_growth_value = 2,\
optional_checks = CALLBACK(src, PROC_REF(ready_to_grow)),\
optional_grow_behavior = CALLBACK(src, PROC_REF(grow_into_giant_spider))\
)
// keep in mind we have infinite range (the entire pipenet is our playground, it's just a matter of random choice as to where we end up) so lower and upper both have their gives and takes.
// but, also remember the more time we aren't in a vent, the more susceptible we are to dying to anything and everything.
// also remember we can't evolve if we're in a vent. lots to keep in mind when you set these variables.
ai_controller.set_blackboard_key(BB_LOWER_VENT_TIME_LIMIT, rand(9, 11) SECONDS)
ai_controller.set_blackboard_key(BB_UPPER_VENT_TIME_LIMIT, rand(12, 14) SECONDS)
/mob/living/basic/spiderling/Destroy()
GLOB.spidermobs -= src
return ..()
/mob/living/basic/spiderling/death(gibbed)
if(isturf(get_turf(loc)) && (basic_mob_flags & DEL_ON_DEATH || gibbed))
var/obj/item/food/spiderling/dead_spider = new(loc) // mmm yummy
dead_spider.name = name
return ..()
/mob/living/basic/spiderling/Login() // this is only really here for admins dragging and dropping players into spiderlings, player control of spiderlings is otherwise unimplemented
. = ..()
if(!. || isnull(client))
return FALSE
basic_mob_flags &= ~DEL_ON_DEATH // we don't want to be deleted if we die while player controlled in case there's some revive schenanigans going on that can bring us back
GLOB.spidermobs[src] = TRUE
add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/player_spider_modifier, multiplicative_slowdown = -2) // let's pick up the tempo, we are meant to be fast after all
if (apply_spider_antag)
var/datum/antagonist/spider/spider_antag = new(directive)
mind.add_antag_datum(spider_antag)
/mob/living/basic/spiderling/Logout()
. = ..()
remove_movespeed_modifier(/datum/movespeed_modifier/player_spider_modifier)
/mob/living/basic/spiderling/mob_negates_gravity() // in case our sisters want to give us a helping hand
if(locate(/obj/structure/spider/stickyweb) in loc)
return TRUE
return ..()
/mob/living/basic/spiderling/start_pulling(atom/movable/pulled_atom, state, force = move_force, supress_message = FALSE) // we're TOO FUCKING SMALL
return
/// Checks to see if we're ready to grow, primarily if we are on solid ground and not in a vent or something.
/// The component will automagically grow us when we return TRUE and that threshold has been met.
/mob/living/basic/spiderling/proc/ready_to_grow()
if(isturf(loc))
return TRUE
return FALSE
/// Actually grows the spiderling into a giant spider. We have to do a bunch of unique behavior that really can't be genericized, so we have to override the component in this manner.
/mob/living/basic/spiderling/proc/grow_into_giant_spider()
if(isnull(grow_as))
if(prob(3))
grow_as = pick(/mob/living/basic/giant_spider/tarantula, /mob/living/basic/giant_spider/viper, /mob/living/basic/giant_spider/midwife)
else
grow_as = pick(/mob/living/basic/giant_spider, /mob/living/basic/giant_spider/hunter, /mob/living/basic/giant_spider/nurse)
var/mob/living/basic/giant_spider/grown = change_mob_type(grow_as, get_turf(src), initial(grow_as.name))
ADD_TRAIT(grown, TRAIT_WAS_EVOLVED, REF(src))
grown.faction = faction.Copy()
grown.directive = directive
qdel(src)
/// Opportunistically hops in and out of vents, if it can find one. We aren't interested in attacking due to how weak we are, we gotta be quick and hidey.
/datum/ai_controller/basic_controller/spiderling
blackboard = list(
BB_FLEE_TARGETTING_DATUM = new /datum/targetting_datum/basic/of_size/larger, // Run away from mobs bigger than we are
BB_BASIC_MOB_FLEEING = TRUE,
BB_VENTCRAWL_COOLDOWN = 20 SECONDS, // enough time to get splatted while we're out in the open.
BB_TIME_TO_GIVE_UP_ON_VENT_PATHING = 30 SECONDS,
)
ai_traits = STOP_MOVING_WHEN_PULLED
ai_movement = /datum/ai_movement/basic_avoidance
idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking
// We understand that vents are nice little hidey holes through epigenetic inheritance, so we'll use them.
planning_subtrees = list(
/datum/ai_planning_subtree/target_retaliate/to_flee,
/datum/ai_planning_subtree/flee_target/from_flee_key,
/datum/ai_planning_subtree/opportunistic_ventcrawler,
/datum/ai_planning_subtree/random_speech/insect,
)
@@ -0,0 +1,25 @@
// This whole file is just a container for the spiderling subtypes that actually differentiate into different giant spiders. None of them are particularly special as of now.
/// Will differentiate into the base giant spider (known colloquially as the "guard" spider).
/mob/living/basic/spiderling/guard
grow_as = /mob/living/basic/giant_spider
/// Will differentiate into the "hunter" giant spider.
/mob/living/basic/spiderling/hunter
grow_as = /mob/living/basic/giant_spider/hunter
/// Will differentiate into the "nurse" giant spider.
/mob/living/basic/spiderling/nurse
grow_as = /mob/living/basic/giant_spider/nurse
/// Will differentiate into the "midwife" giant spider.
/mob/living/basic/spiderling/midwife
grow_as = /mob/living/basic/giant_spider/midwife
/// Will differentiate into the "viper" giant spider.
/mob/living/basic/spiderling/viper
grow_as = /mob/living/basic/giant_spider/viper
/// Will differentiate into the "tarantula" giant spider.
/mob/living/basic/spiderling/tarantula
grow_as = /mob/living/basic/giant_spider/tarantula
+7 -3
View File
@@ -52,10 +52,12 @@
if(!can_enter_vent(ventcrawl_target))
return
var/has_client = !isnull(client) // clientless mobs can do this too! this is just stored in case the client disconnects while we sleep in do_after.
//Handle the exit here
if(HAS_TRAIT(src, TRAIT_MOVE_VENTCRAWLING) && istype(loc, /obj/machinery/atmospherics) && movement_type & VENTCRAWLING)
visible_message(span_notice("[src] begins climbing out from the ventilation system...") ,span_notice("You begin climbing out from the ventilation system..."))
if(!client)
visible_message(span_notice("[src] begins climbing out from the ventilation system..."), span_notice("You begin climbing out from the ventilation system..."))
if(has_client && isnull(client))
return
visible_message(span_notice("[src] scrambles out from the ventilation ducts!"),span_notice("You scramble out from the ventilation ducts."))
forceMove(ventcrawl_target.loc)
@@ -70,7 +72,7 @@
visible_message(span_notice("[src] begins climbing into the ventilation system...") ,span_notice("You begin climbing into the ventilation system..."))
if(!do_after(src, 2.5 SECONDS, target = ventcrawl_target, extra_checks = CALLBACK(src, PROC_REF(can_enter_vent), ventcrawl_target)))
return
if(!client)
if(has_client && isnull(client))
return
ventcrawl_target.flick_overlay_static(image('icons/effects/vent_indicator.dmi', "insert", ABOVE_MOB_LAYER), 1 SECONDS)
visible_message(span_notice("[src] scrambles into the ventilation ducts!"),span_notice("You climb into the ventilation ducts."))
@@ -103,6 +105,8 @@
* We move first and then call update. Dont flip this around
*/
/mob/living/proc/update_pipe_vision(full_refresh = FALSE)
if(!isnull(ai_controller) && isnull(client)) // we don't care about pipe vision if we have an AI controller with no client (typically means we are clientless).
return
// Take away all the pipe images if we're not doing anything with em
if(isnull(client) || !HAS_TRAIT(src, TRAIT_MOVE_VENTCRAWLING) || !istype(loc, /obj/machinery/atmospherics) || !(movement_type & VENTCRAWLING))
-1
View File
@@ -2,7 +2,6 @@ GLOBAL_LIST_INIT(blacklisted_cargo_types, typecacheof(list(
/mob/living,
/obj/structure/blob,
/obj/effect/rune,
/obj/structure/spider/spiderling,
/obj/item/disk/nuclear,
/obj/machinery/nuclearbomb,
/obj/item/beacon,
Binary file not shown.

Before

Width:  |  Height:  |  Size: 928 KiB

After

Width:  |  Height:  |  Size: 851 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 243 KiB

After

Width:  |  Height:  |  Size: 222 KiB

+13 -8
View File
@@ -843,9 +843,11 @@
#include "code\datums\ai\basic_mobs\basic_ai_behaviors\targetting.dm"
#include "code\datums\ai\basic_mobs\basic_ai_behaviors\tipped_reaction.dm"
#include "code\datums\ai\basic_mobs\basic_ai_behaviors\travel_towards.dm"
#include "code\datums\ai\basic_mobs\basic_ai_behaviors\ventcrawling.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\attack_obstacle_in_path.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\find_food.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\flee_target.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\opportunistic_ventcrawler.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\simple_attack_target.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\simple_find_nearest_target_to_flee.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\simple_find_target.dm"
@@ -1000,6 +1002,7 @@
#include "code\datums\components\gps.dm"
#include "code\datums\components\grillable.dm"
#include "code\datums\components\ground_sinking.dm"
#include "code\datums\components\growth_and_differentiation.dm"
#include "code\datums\components\gunpoint.dm"
#include "code\datums\components\hazard_area.dm"
#include "code\datums\components\healing_touch.dm"
@@ -4015,14 +4018,6 @@
#include "code\modules\mob\living\basic\space_fauna\carp\carp_controllers.dm"
#include "code\modules\mob\living\basic\space_fauna\carp\magicarp.dm"
#include "code\modules\mob\living\basic\space_fauna\carp\megacarp.dm"
#include "code\modules\mob\living\basic\space_fauna\giant_spider\giant_spider.dm"
#include "code\modules\mob\living\basic\space_fauna\giant_spider\giant_spider_ai.dm"
#include "code\modules\mob\living\basic\space_fauna\giant_spider\spider_subtrees.dm"
#include "code\modules\mob\living\basic\space_fauna\giant_spider\spider_variants.dm"
#include "code\modules\mob\living\basic\space_fauna\giant_spider\spider_abilities\hivemind.dm"
#include "code\modules\mob\living\basic\space_fauna\giant_spider\spider_abilities\lay_eggs.dm"
#include "code\modules\mob\living\basic\space_fauna\giant_spider\spider_abilities\web.dm"
#include "code\modules\mob\living\basic\space_fauna\giant_spider\spider_abilities\wrap.dm"
#include "code\modules\mob\living\basic\space_fauna\meteor_heart\chasing_spikes.dm"
#include "code\modules\mob\living\basic\space_fauna\meteor_heart\meteor_eyeball.dm"
#include "code\modules\mob\living\basic\space_fauna\meteor_heart\meteor_heart.dm"
@@ -4031,6 +4026,16 @@
#include "code\modules\mob\living\basic\space_fauna\netherworld\blankbody.dm"
#include "code\modules\mob\living\basic\space_fauna\netherworld\creature.dm"
#include "code\modules\mob\living\basic\space_fauna\netherworld\migo.dm"
#include "code\modules\mob\living\basic\space_fauna\spider\giant_spider\giant_spider.dm"
#include "code\modules\mob\living\basic\space_fauna\spider\giant_spider\giant_spider_ai.dm"
#include "code\modules\mob\living\basic\space_fauna\spider\giant_spider\giant_spider_subtrees.dm"
#include "code\modules\mob\living\basic\space_fauna\spider\giant_spider\giant_spider_variants.dm"
#include "code\modules\mob\living\basic\space_fauna\spider\spider_abilities\hivemind.dm"
#include "code\modules\mob\living\basic\space_fauna\spider\spider_abilities\lay_eggs.dm"
#include "code\modules\mob\living\basic\space_fauna\spider\spider_abilities\web.dm"
#include "code\modules\mob\living\basic\space_fauna\spider\spider_abilities\wrap.dm"
#include "code\modules\mob\living\basic\space_fauna\spider\spiderlings\spiderling.dm"
#include "code\modules\mob\living\basic\space_fauna\spider\spiderlings\spiderling_subtypes.dm"
#include "code\modules\mob\living\basic\space_fauna\statue\statue.dm"
#include "code\modules\mob\living\basic\space_fauna\wumborian_fugu\fugu_gland.dm"
#include "code\modules\mob\living\basic\space_fauna\wumborian_fugu\inflation.dm"
@@ -0,0 +1 @@
/obj/structure/spider/spiderling/@SUBTYPES : /mob/living/basic/spiderling/@SUBTYPES{@OLD}