Files
Bubberstation/code/datums/components/growth_and_differentiation.dm
SkyratBot 68f4696e25 [MIRROR] You can now raise lobstrosities from chasms chrabs. (#29019)
* You can now raise lobstrosities from chasms chrabs. (#84969)

## About The Pull Request

Lobstrosities can now be raised from aquarium icemoon/lavaland chrabs.
First of all, you've to get a live chrab, an aquarium, and some fish
feed. Second, you place the chrab inside the aquarium and turn the
'allow breeding' settting on (should probably rename it to a more apt
name now). Keep the chrab well fed, and possibly with some friends and
props in the same aquarium until it develops into a hopefully calm
juveline lobstrosity and plops out of the aquarium (it can take some
time). From there you can tame it by feeding it its favorite food: arms
and lavaloop fish, and wait another dozen minutes for it to mature into
a fully grown lobstrosity.

Juveline lobstrosities are basically smaller and weaker lobstrosities,
if not slightly faster in some ways. Unlike their taller counterparts,
they can be tamed. Once done so, they'll retain their tamedness once
grown up. Regardless, tamed lobstrosities can be given the pet command
to fish for things by pointing at them. Thanks BenMatrix for the
profound fisher component, woo.

The chrab's weigth and size influence the growth speed of the first
stage faster, meaning bigger chrabs (may require crossbreeding) will
turn into juveline lobstrosities quickly. Amongst other things
influencing the resulting mob are fish traits:
Several traits have been given effects that apply to the mob, such as
nocturnal regeneration, being venomous or being able to fly akin space
carps. Also a new one that prevents the resulting lobstrosity from fully
developing

Now tested.

## Why It's Good For The Game
I'm building upon fishing and aquarium stuff, which has been an interest
of mine in a good while, though most of it doesn't have that many
practical uses, I'm slowly trying to make it cooler, and chasm chrabs
growing into lobstrosities is pretty much in line with the fluff texts
for the fish.

Eventually I'll have to add tips inside fishing toolboxes, otherwise
people won't know even half of it.

## Changelog

🆑
add: You can raise lobstrosities from chasm chrabs inside an aquarium
with the 'allow breeding' setting on. Keep the fish well fed, healthy
and not lonely if you don't want an hostile one.
add: Juveline lobstrosities (from chasms, plasma rivers, or aquariums,
xenobio too) can be tamed with arms and lavaloop fishes.
add: For lobstrosities grown from aquariums, they can have additional
effects based on the fish traits they had in the aquarium, like being
venomous or even flying.
/🆑

* You can now raise lobstrosities from chasms chrabs.

---------

Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com>
2024-07-24 22:39:49 +05:30

163 lines
7.0 KiB
Plaintext

/**
* ### 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!
*/
/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
/// Stores the growth_probability the component had when it was Initialized
var/initial_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
/// List of signals we kill on ourselves when we grow.
var/list/signals_to_kill_on
/// 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,
scale_with_happiness,
list/signals_to_kill_on,
datum/callback/optional_checks,
datum/callback/optional_grow_behavior,
)
if(!isliving(parent))
return COMPONENT_INCOMPATIBLE
src.growth_path = growth_path
src.growth_time = growth_time
initial_growth_probability = 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(islist(signals_to_kill_on))
src.signals_to_kill_on = signals_to_kill_on
RegisterSignals(parent, src.signals_to_kill_on, PROC_REF(stop_component_processing_entirely))
if(scale_with_happiness)
parent.AddComponent(/datum/component/happiness)
RegisterSignal(parent, COMSIG_MOB_HAPPINESS_CHANGE, PROC_REF(on_happiness_change))
// 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)
STOP_PROCESSING(SSdcs, src)
deltimer(timer_id)
optional_checks = null
optional_grow_behavior = null
return ..()
/// Wrapper for qdel() so we can pass it in RegisterSignals(). I hate it here too.
/datum/component/growth_and_differentiation/proc/stop_component_processing_entirely()
SIGNAL_HANDLER
qdel(src)
/// 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 two different systems for growth: the timer and the probability based one. Both can coexist. Return COMPONENT_INCOMPATIBLE if we fail to set up either.
/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))
if(lower_growth_value == upper_growth_value)
percent_grown += upper_growth_value
else
percent_grown += rand(lower_growth_value, upper_growth_value)
/datum/component/growth_and_differentiation/proc/on_happiness_change(datum/source, happiness_percentage)
SIGNAL_HANDLER
var/probability_to_add = initial_growth_probability * happiness_percentage
growth_probability = min(initial_growth_probability + probability_to_add, 100)
/// 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
if(!ispath(growth_path, /mob/living))
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/mob/living/new_mob = growth_path
var/new_mob_name = initial(new_mob.name)
if(!silent)
old_mob.visible_message(span_warning("[old_mob] grows into \a [new_mob_name]!"))
var/mob/living/transformed_mob = old_mob.change_mob_type(growth_path, old_mob.loc, new_name = new_mob_name, delete_old_mob = TRUE)
if(initial(new_mob.unique_name))
transformed_mob.set_name()
ADD_TRAIT(transformed_mob, TRAIT_MOB_HATCHED, INNATE_TRAIT)