Files
Bubberstation/code/datums/components/udder.dm
Ben10Omintrix 761b14ef7c lavaland raptors (#82537)
## About The Pull Request
adds raptors to lavaland. these are creatures that have been created
through countless xenobiological experiments by nanotrasen to breed an
animal that can withstand the harsh conditions of lavaland and aid
miners. theres now a new ranch miners can access bottom right to the
mining base

![ranch](https://github.com/tgstation/tgstation/assets/138636438/20d9e358-15a5-48e2-aee3-9364ca139e43)
this ranch starts somewhat empty as most raptors have escaped
containment and are now scattered all across lavaland, u can find them
and return them to ur ranch.

in order to tame a raptor, u first need to prove to it that ur a capable
master. when u try to ride it, a little minigame prompt will pop up

![minigame](https://github.com/tgstation/tgstation/assets/138636438/dcc13102-7100-40c8-ae7a-089cd4daf868)
in this game, the bird's icon rapidly changes direction and u have to
quickly click the arrow thats OPPOSITE to the direction its facing
several times before the direction changes. if you fail 3 times itll
knock you off and run away, however if u win it will deem u a suitable
master and listen to your orders.

There's many different breeds of raptors you can find across lavaland,
all with different capabilities:
red raptors: these excel at combat and can be very useful for dealing
with lavaland mobs or defending the node drone
yellow raptors: are very speedy mounts, theyll get u from point A to
point B in record time
green raptors: they are the tankiest type of raptor and are very good
miners. while mounted, they will clear any rock walls in their path
purple raptors: can store items in them. they have a decent storage size
allowing players to carry more items across trips
white raptors: are able to heal other injured raptors. having one in ur
party would be very useful as they can nurse the combat raptors back to
full health when they need it
blue raptors: produce very nutritious milk with healing capabilities.
having 1 or 2 of these back at ur ranch would be very useful
black raptors: by far the rarest breed, its very unlikely that ull be
able to get one of these, but in the case u do, they have the combat
capabilities of the red raptor, speed of the yellow raptor, and
tankiness of the green raptor.

Breeding different colored raptors together can net u an entirely new
colored raptor. each breed has atleast 1 guaranteed combination of
parents that it will result out of.

you will also need to maintain a good friendship bond with ur raptors,
this is done by feeding them, grooming them, and petting them. u can see
the strength of ur bond by SHIFT clicking them. more hearts indicate a
stronger bond

![hearts](https://github.com/tgstation/tgstation/assets/138636438/5662c5a7-2df3-4f98-99f4-a11faa17b569)
having higher friendship bonds means ur raptors will perform better in
combat, and in the case of blue raptors, they will produce more milk.
Maintaining friendship bonds with baby raptors and keeping them happy
will also encourage them to grow faster

U can also analyze raptors using the new raptor-dex device available at
ur ranch

![pokedex](https://github.com/tgstation/tgstation/assets/138636438/82b92c0c-b7db-4a0d-997e-384a69c0ecbe)
the inherit modifiers indicate how strong this raptor's offspring will
be. raptors inherit attack and health stats from both their parents,
breeding raptors with higher inherit modifiers means the offspring will
be stronger.

raptors will also inherit some traits from their parents that will
change how they will act around u and around other raptors, some of them
being:
Playful: raptors will play with their masters and tease them
motherly: raptors will care for baby raptors, this will encourage baby
raptors to grow quicker
depressed: means its hard to keep this raptor happy and friendship bonds
will deteriorate faster if not given enough care.
coward: makes them flee combat if severly injured, ditching u to the
wolves
trouble maker: makes them attack other raptors at the ranch. however,
trouble maker raptors will not attack other trouble maker raptors,
instead they will form posses and bully raptors together. it might be a
good idea to isolate them from the other raptors

raptors primarily consume ores. to feed raptors, you need to place ore
into the food troughs at the ranch. they are too civilized to eat ores
off the ground or directly from ur hand, they will only eat it if its in
their trough

![trough](https://github.com/tgstation/tgstation/assets/138636438/70723cc7-5743-4ace-9955-4307879e7a83)

beautiful raptor sprites by spessmenart! (rest are codersprites)

## Why It's Good For The Game
adds a new layer to lavaland mobs, and gives miners new interesting
tools and ways to tackle the challenges of lavaland.

## Changelog
🆑 sheets, spacemenart, ben10omintrix, goofball, infrared baron, aofie
add: adds lavaland raptors and the raptor ranch
/🆑

---------

Co-authored-by: Iamgoofball <iamgoofball@gmail.com>
2024-05-16 19:54:00 -07:00

220 lines
8.2 KiB
Plaintext

/**
* Udder component; for farm animals to generate milk.
*
* Used for cows, goats, gutlunches. neat!
*/
/datum/component/udder
///abstract item for managing reagents (further down in this file)
var/obj/item/udder/udder
///optional proc to callback to when the udder is milked
var/datum/callback/on_milk_callback
//udder_type and reagent_produced_typepath are typepaths, not reference
/datum/component/udder/Initialize(udder_type = /obj/item/udder, datum/callback/on_milk_callback, datum/callback/on_generate_callback, reagent_produced_override)
if(!isliving(parent)) //technically is possible to drop this on carbons... but you wouldn't do that to me, would you?
return COMPONENT_INCOMPATIBLE
udder = new udder_type(null)
udder.add_features(parent, on_generate_callback, reagent_produced_override)
src.on_milk_callback = on_milk_callback
/datum/component/udder/RegisterWithParent()
RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
RegisterSignal(parent, COMSIG_ATOM_ATTACKBY, PROC_REF(on_attackby))
/datum/component/udder/UnregisterFromParent()
QDEL_NULL(udder)
on_milk_callback = null
UnregisterSignal(parent, list(COMSIG_ATOM_EXAMINE, COMSIG_ATOM_ATTACKBY))
///signal called on parent being examined
/datum/component/udder/proc/on_examine(datum/source, mob/user, list/examine_list)
SIGNAL_HANDLER
var/mob/living/milked = parent
if(milked.stat != CONSCIOUS)
return //come on now
var/udder_filled_percentage = PERCENT(udder.reagents.total_volume / udder.reagents.maximum_volume)
switch(udder_filled_percentage)
if(0 to 10)
examine_list += span_notice("[parent]'s [udder] is dry.")
if(11 to 99)
examine_list += span_notice("[parent]'s [udder] can be milked if you have something to contain it.")
if(100)
examine_list += span_notice("[parent]'s [udder] is round and full, and can be milked if you have something to contain it.")
///signal called on parent being attacked with an item
/datum/component/udder/proc/on_attackby(datum/source, obj/item/milking_tool, mob/user)
SIGNAL_HANDLER
var/mob/living/milked = parent
if(milked.stat == CONSCIOUS && istype(milking_tool, /obj/item/reagent_containers/cup))
udder.milk(milking_tool, user)
if(on_milk_callback)
on_milk_callback.Invoke(udder.reagents.total_volume, udder.reagents.maximum_volume)
return COMPONENT_NO_AFTERATTACK
/**
* # udder item
*
* Abstract item that is held in nullspace and manages reagents. Created by udder component.
* While perhaps reagents created by udder component COULD be managed in the mob, it would be somewhat finnicky and I actually like the abstract udders.
*/
/obj/item/udder
name = "udder"
///typepath of reagent produced by the udder
var/reagent_produced_typepath = /datum/reagent/consumable/milk
///how much the udder holds
var/size = 50
///the probability that the udder will produce the reagent (0 - 100)
var/production_probability = 5
///mob that has the udder component
var/mob/living/udder_mob
///optional proc to callback to when the udder generates milk
var/datum/callback/on_generate_callback
///do we require some food to generate milk?
var/require_consume_type
///how long does each food consumption allow us to make milk
var/require_consume_timer = 2 MINUTES
///hunger key we set to look for food
var/hunger_key = BB_CHECK_HUNGRY
/obj/item/udder/proc/add_features(parent, callback, reagent_override)
udder_mob = parent
on_generate_callback = callback
create_reagents(size, REAGENT_HOLDER_ALIVE)
if(reagent_override)
reagent_produced_typepath = reagent_override
initial_conditions()
if(isnull(require_consume_type))
return
RegisterSignal(udder_mob, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(on_mob_consume))
RegisterSignal(udder_mob, COMSIG_ATOM_ATTACKBY, PROC_REF(on_mob_feed))
udder_mob.ai_controller?.set_blackboard_key(BB_CHECK_HUNGRY, TRUE)
/obj/item/udder/proc/on_mob_consume(datum/source, atom/feed)
SIGNAL_HANDLER
if(!istype(feed, require_consume_type))
return
INVOKE_ASYNC(src, PROC_REF(handle_consumption), feed)
return COMPONENT_HOSTILE_NO_ATTACK
/obj/item/udder/proc/on_mob_feed(datum/source, atom/used_item, mob/living/user)
SIGNAL_HANDLER
if(!istype(used_item, require_consume_type))
return
INVOKE_ASYNC(src, PROC_REF(handle_consumption), used_item, user)
return COMPONENT_NO_AFTERATTACK
/obj/item/udder/proc/handle_consumption(atom/movable/food, mob/user)
if(locate(food.type) in src)
if(user)
user.balloon_alert(user, "already full!")
return
playsound(udder_mob.loc,'sound/items/eatfood.ogg', 50, TRUE)
udder_mob.visible_message(span_notice("[udder_mob] gobbles up [food]!"), span_notice("You gobble up [food]!"))
var/atom/movable/final_food = food
if(isstack(food)) //if stack, only consume 1
var/obj/item/stack/food_stack = food
final_food = food_stack.split_stack(udder_mob, 1)
final_food.forceMove(src)
/obj/item/udder/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs)
if(!istype(arrived, require_consume_type))
return ..()
udder_mob.ai_controller?.set_blackboard_key(hunger_key, FALSE)
QDEL_IN(arrived, require_consume_timer)
return ..()
/obj/item/udder/Exited(atom/movable/gone, direction)
. = ..()
if(!istype(gone, require_consume_type))
return
udder_mob.ai_controller?.set_blackboard_key(hunger_key, TRUE)
/obj/item/udder/Destroy()
. = ..()
STOP_PROCESSING(SSobj, src)
udder_mob = null
on_generate_callback = null
/obj/item/udder/process(seconds_per_tick)
if(udder_mob.stat != DEAD)
generate() //callback is on generate() itself as sometimes generate does not add new reagents, or is not called via process
/**
* Proc called on creation separate from the reagent datum creation to allow for signalled milk generation instead of processing milk generation
* also useful for changing initial amounts in reagent holder (cows start with milk, gutlunches start empty)
*/
/obj/item/udder/proc/initial_conditions()
reagents.add_reagent(reagent_produced_typepath, 20, added_purity = 1)
START_PROCESSING(SSobj, src)
/**
* Proc called every 2 seconds from SSMobs to add whatever reagent the udder is generating.
*/
/obj/item/udder/proc/generate()
if(!isnull(require_consume_type) && !(locate(require_consume_type) in src))
return FALSE
if(!prob(production_probability))
return FALSE
reagents.add_reagent(reagent_produced_typepath, rand(5, 10), added_purity = 1)
if(on_generate_callback)
on_generate_callback.Invoke(reagents.total_volume, reagents.maximum_volume)
return TRUE
/**
* Proc called from attacking the component parent with the correct item, moves reagents into the glass basically.
*
* Arguments:
* * obj/item/reagent_containers/cup/milk_holder - what we are trying to transfer the reagents to
* * mob/user - who is trying to do this
*/
/obj/item/udder/proc/milk(obj/item/reagent_containers/cup/milk_holder, mob/user)
if(milk_holder.reagents.total_volume >= milk_holder.volume)
to_chat(user, span_warning("[milk_holder] is full."))
return
var/transferred = reagents.trans_to(milk_holder, rand(5,10))
if(transferred)
user.visible_message(span_notice("[user] milks [udder_mob] using \the [milk_holder]."), span_notice("You milk [udder_mob] using \the [milk_holder]."))
else
to_chat(user, span_warning("The udder is dry. Wait a bit longer..."))
/**
* # gutlunch udder subtype
*/
/obj/item/udder/gutlunch
name = "nutrient sac"
require_consume_type = /obj/item/stack/ore
reagent_produced_typepath = /datum/reagent/medicine/mine_salve
/obj/item/udder/gutlunch/generate()
. = ..()
if(!.)
return
if(locate(/obj/item/stack/ore/gold) in src)
reagents.add_reagent(/datum/reagent/consumable/cream, rand(2, 5), added_purity = 1)
if(locate(/obj/item/stack/ore/bluespace_crystal) in src)
reagents.add_reagent(/datum/reagent/medicine/salglu_solution, rand(2,5))
if(on_generate_callback)
on_generate_callback.Invoke(reagents.total_volume, reagents.maximum_volume)
/obj/item/udder/raptor
name = "bird udder"
/obj/item/udder/raptor/generate()
if(!prob(production_probability))
return FALSE
var/happiness_percentage = udder_mob.ai_controller?.blackboard[BB_BASIC_HAPPINESS]
if(prob(happiness_percentage))
reagents.add_reagent(/datum/reagent/consumable/cream, 5, added_purity = 1)
var/minimum_bound = happiness_percentage > 0.6 ? 10 : 5
var/upper_bound = minimum_bound + 5
reagents.add_reagent(reagent_produced_typepath, rand(minimum_bound, upper_bound), added_purity = 1)