u can now hand-feed animals. like cats and raptors (#88173)

## About The Pull Request
this PR does 2 things, firstly it allows u to directly feed animals from
ur hand instead of having to drop it on the floor and relying on their
AI to go eat it. So you can now directly feed ore to raptors or wheat to
cows by simply clicking on them with the item. secondly, it links the
tameable component and the eating element together, as now the former
relies on signals sent by the latter.

## Why It's Good For The Game
Small QOL for pet owners when it comes to feeding their animals.

## Changelog
🆑
qol: u can now directly feed animals from ur hands, like raptors or
cats, by clicking on them with their preferred food.
balance: u can now heal ur raptors mid or post battles by hand feeding
them ores
/🆑
This commit is contained in:
Ben10Omintrix
2024-12-06 23:43:56 +01:00
committed by GitHub
parent ba93bce9c1
commit 028244ef03
18 changed files with 81 additions and 53 deletions
@@ -287,6 +287,8 @@
/// From /datum/element/basic_eating/finish_eating()
#define COMSIG_MOB_ATE "mob_ate"
///cancel post eating
#define COMSIG_MOB_TERMINATE_EAT (1<<0)
///From mob/living/carbon/proc/throw_mode_on and throw_mode_off
#define COMSIG_LIVING_THROW_MODE_TOGGLE "living_throw_mode_toggle"
+13 -22
View File
@@ -2,8 +2,6 @@
/datum/component/tameable
///If true, this atom can only be domesticated by one person
var/unique
///What the mob eats, typically used for taming or animal husbandry.
var/list/food_types
///Starting success chance for taming.
var/tame_chance
///Added success chance after every failed tame attempt.
@@ -15,8 +13,6 @@
if(!isatom(parent)) //yes, you could make a tameable toolbox.
return COMPONENT_INCOMPATIBLE
if(food_types)
src.food_types = food_types
if(tame_chance)
src.tame_chance = tame_chance
src.current_tame_chance = tame_chance
@@ -24,35 +20,30 @@
src.bonus_tame_chance = bonus_tame_chance
src.unique = unique
RegisterSignal(parent, COMSIG_ATOM_ATTACKBY, PROC_REF(try_tame))
if(food_types && !HAS_TRAIT(parent, TRAIT_MOB_EATER))
parent.AddElement(/datum/element/basic_eating, food_types = food_types)
RegisterSignal(parent, COMSIG_MOB_ATE, PROC_REF(try_tame))
RegisterSignal(parent, COMSIG_SIMPLEMOB_SENTIENCEPOTION, PROC_REF(on_tame)) //Instantly succeeds
RegisterSignal(parent, COMSIG_SIMPLEMOB_TRANSFERPOTION, PROC_REF(on_tame)) //Instantly succeeds
/datum/component/tameable/proc/try_tame(datum/source, obj/item/food, mob/living/attacker, params)
/datum/component/tameable/proc/try_tame(atom/source, obj/item/food, mob/living/attacker)
SIGNAL_HANDLER
if(!is_type_in_list(food, food_types))
return
if(isliving(source))
var/mob/living/potentially_dead_horse = source
if(potentially_dead_horse.stat == DEAD)
to_chat(attacker, span_warning("[parent] is dead!"))
return COMPONENT_CANCEL_ATTACK_CHAIN
var/atom/atom_parent = source
if(isnull(attacker) || already_friends(attacker))
return
var/inform_tamer = FALSE
atom_parent.balloon_alert(attacker, "fed")
var/modified_tame_chance = current_tame_chance
if(HAS_TRAIT(attacker, TRAIT_BEAST_EMPATHY))
modified_tame_chance += 50
inform_tamer = TRUE
if(unique || !already_friends(attacker))
if(prob(modified_tame_chance)) //note: lack of feedback message is deliberate, keep them guessing unless they're an expert!
on_tame(source, attacker, food, inform_tamer)
else
current_tame_chance += bonus_tame_chance
qdel(food)
return COMPONENT_CANCEL_ATTACK_CHAIN
source.balloon_alert(attacker, "eats from your hand")
if(prob(modified_tame_chance)) //note: lack of feedback message is deliberate, keep them guessing unless they're an expert!
on_tame(source, attacker, food, inform_tamer)
else
current_tame_chance += bonus_tame_chance
/// Check if the passed mob is already considered one of our friends
/datum/component/tameable/proc/already_friends(mob/living/potential_friend)
+20 -14
View File
@@ -30,16 +30,25 @@
src.drinking = drinking
src.food_types = food_types
//this lets players eat
RegisterSignal(target, COMSIG_ATOM_ITEM_INTERACTION, PROC_REF(try_feed))
RegisterSignal(target, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(on_unarm_attack))
//this lets ai eat. yes, i'm serious
RegisterSignal(target, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(on_pre_attackingtarget))
/datum/element/basic_eating/Detach(datum/target)
REMOVE_TRAIT(target, TRAIT_MOB_EATER, REF(src))
UnregisterSignal(target, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HOSTILE_PRE_ATTACKINGTARGET))
UnregisterSignal(target, list(
COMSIG_LIVING_UNARMED_ATTACK,
COMSIG_ATOM_ITEM_INTERACTION,
))
return ..()
/datum/element/basic_eating/proc/try_feed(atom/source, mob/living/user, atom/possible_food)
SIGNAL_HANDLER
if(user.combat_mode || !is_type_in_list(possible_food, food_types))
return NONE
try_eating(source, possible_food, user)
/datum/element/basic_eating/proc/on_unarm_attack(mob/living/eater, atom/target, proximity, modifiers)
SIGNAL_HANDLER
if(!proximity)
@@ -49,14 +58,10 @@
return COMPONENT_CANCEL_ATTACK_CHAIN
return NONE
/datum/element/basic_eating/proc/on_pre_attackingtarget(mob/living/eater, atom/target)
SIGNAL_HANDLER
try_eating(eater, target)
/datum/element/basic_eating/proc/try_eating(mob/living/eater, atom/target)
/datum/element/basic_eating/proc/try_eating(mob/living/eater, atom/target, mob/living/feeder)
if(!is_type_in_list(target, food_types))
return FALSE
if(SEND_SIGNAL(eater, COMSIG_MOB_PRE_EAT, target) & COMSIG_MOB_CANCEL_EAT)
if(SEND_SIGNAL(eater, COMSIG_MOB_PRE_EAT, target, feeder) & COMSIG_MOB_CANCEL_EAT)
return FALSE
var/eat_verb
if(drinking)
@@ -75,21 +80,22 @@
if (damage_amount > 0 && damage_type)
eater.apply_damage(damage_amount, damage_type)
eater.visible_message(span_notice("[eater] [eat_verb]s [target], and seems to hurt itself."), span_notice("You [eat_verb] [target], hurting yourself in the process."))
finish_eating(eater, target)
finish_eating(eater, target, feeder)
return TRUE
eater.visible_message(span_notice("[eater] [eat_verb]s [target]."), span_notice("You [eat_verb] [target]."))
finish_eating(eater, target)
finish_eating(eater, target, feeder)
return TRUE
/datum/element/basic_eating/proc/finish_eating(mob/living/eater, atom/target)
/datum/element/basic_eating/proc/finish_eating(mob/living/eater, atom/target, mob/living/feeder)
set waitfor = FALSE
SEND_SIGNAL(eater, COMSIG_MOB_ATE)
if(drinking)
playsound(eater.loc,'sound/items/drink.ogg', rand(10,50), TRUE)
else
playsound(eater.loc,'sound/items/eatfood.ogg', rand(10,50), TRUE)
var/atom/final_target = target
if(SEND_SIGNAL(eater, COMSIG_MOB_ATE, final_target, feeder) & COMSIG_MOB_TERMINATE_EAT)
return
if(isstack(target)) //if stack, only consume 1
var/obj/item/stack/food_stack = target
final_target = food_stack.split_stack(eater, 1)
+5 -1
View File
@@ -403,7 +403,11 @@
GRANT_ACTION(/datum/action/cooldown/regurgitate)
AddElement(/datum/element/swabable, CELL_LINE_TABLE_GLUTTON, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5)
AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/cheesiehonkers, /obj/item/food/cornchips), tame_chance = 30, bonus_tame_chance = 0)
var/static/list/food_types = list(
/obj/item/food/cheesiehonkers,
/obj/item/food/cornchips,
)
AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 30, bonus_tame_chance = 0)
AddElement(/datum/element/damage_threshold, 10) //lots of fat to cushion blows.
/mob/living/basic/clown/mutant/glutton/attacked_by(obj/item/item, mob/living/user)
@@ -76,7 +76,6 @@
if(!food_types)
food_types = src.food_types.Copy()
AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 25, bonus_tame_chance = 15)
AddElement(/datum/element/basic_eating, food_types = food_types)
/mob/living/basic/cow/tamed(mob/living/tamer, atom/food)
visible_message("[src] [tame_message] as it seems to bond with [tamer].", "You [self_tame_message], recognizing [tamer] as your new pal.")
@@ -32,7 +32,6 @@
var/static/list/food_types
if(!food_types)
food_types = src.food_types.Copy()
AddElement(/datum/element/basic_eating, food_types = food_types)
AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 25, bonus_tame_chance = 15)
/mob/living/basic/cow/moonicorn/tamed(mob/living/tamer, atom/food)
@@ -48,7 +48,8 @@
///wrapper for the tameable component addition so you can have non tamable cow subtypes
/mob/living/basic/pig/proc/make_tameable()
AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/grown/carrot), tame_chance = 25, bonus_tame_chance = 15)
var/list/food_types = string_list(list(/obj/item/food/grown/carrot))
AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 25, bonus_tame_chance = 15)
/mob/living/basic/pig/tamed(mob/living/tamer, atom/food)
AddElement(/datum/element/ridable, /datum/component/riding/creature/pig)
@@ -53,7 +53,10 @@
AddElement(/datum/element/ai_retaliate)
AddElement(/datum/element/ai_flee_while_injured)
AddElementTrait(TRAIT_WADDLING, INNATE_TRAIT, /datum/element/waddling)
AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/grown/apple), tame_chance = 25, bonus_tame_chance = 15, unique = unique_tamer)
var/static/list/food_types = list(
/obj/item/food/grown/apple,
)
AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 25, bonus_tame_chance = 15, unique = unique_tamer)
/mob/living/basic/pony/tamed(mob/living/tamer, atom/food)
playsound(src, 'sound/mobs/non-humanoids/pony/snort.ogg', 50)
@@ -161,4 +164,5 @@
ponycolors = list("#5d566f", pick_weight(mane_colors))
name = pick("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday")
// Only one person can tame these fellas, and they only need one apple
AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/grown/apple), tame_chance = 100, bonus_tame_chance = 15, unique = unique_tamer)
var/static/list/food_types = list(/obj/item/food/grown/apple)
AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 100, bonus_tame_chance = 15, unique = unique_tamer)
@@ -66,7 +66,8 @@
make_tameable()
/mob/living/basic/mining/wolf/proc/make_tameable()
AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/meat/slab), tame_chance = 15, bonus_tame_chance = 5)
var/static/list/food_types = list(/obj/item/food/meat/slab)
AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 15, bonus_tame_chance = 5)
/mob/living/basic/mining/wolf/tamed(mob/living/tamer, atom/food)
new /obj/effect/temp_visual/heart(src.loc)
@@ -67,6 +67,7 @@
ADD_TRAIT(src, TRAIT_BOULDER_BREAKER, INNATE_TRAIT)
ADD_TRAIT(src, TRAIT_INSTANTLY_PROCESSES_BOULDERS, INNATE_TRAIT)
RegisterSignal(src, COMSIG_ATOM_PRE_BULLET_ACT, PROC_REF(block_bullets))
RegisterSignal(src, COMSIG_MOB_ATE, PROC_REF(on_eat))
/mob/living/basic/mining/goldgrub/proc/block_bullets(datum/source, obj/projectile/hitting_projectile)
SIGNAL_HANDLER
@@ -105,7 +106,8 @@
return ..()
/mob/living/basic/mining/goldgrub/proc/make_tameable()
AddComponent(/datum/component/tameable, food_types = list(/obj/item/stack/ore), tame_chance = 25, bonus_tame_chance = 5)
var/list/food_types = string_list(list(/obj/item/stack/ore))
AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 25, bonus_tame_chance = 5)
/mob/living/basic/mining/goldgrub/tamed(mob/living/tamer, atom/food)
new /obj/effect/temp_visual/heart(src.loc)
@@ -127,13 +129,18 @@
. = ..()
if(!istype(arrived, /obj/item/stack/ore))
return
playsound(src,'sound/items/eatfood.ogg', rand(10,50), TRUE)
if(!can_lay_eggs)
return
if(!istype(arrived, /obj/item/stack/ore/bluespace_crystal) || prob(60))
return
new /obj/item/food/egg/green/grub_egg(get_turf(src))
/mob/living/basic/mining/goldgrub/proc/on_eat(atom/source, atom/movable/food, mob/feeder)
SIGNAL_HANDLER
food.forceMove(src)
return COMSIG_MOB_TERMINATE_EAT
/mob/living/basic/mining/goldgrub/baby
icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi'
name = "goldgrub baby"
@@ -68,7 +68,8 @@
AddComponent(/datum/component/basic_mob_attack_telegraph)
AddComponentFrom(INNATE_TRAIT, /datum/component/shovel_hands)
if (tameable)
AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/grown/ash_flora), tame_chance = 10, bonus_tame_chance = 5)
var/static/list/food_types = list(/obj/item/food/grown/ash_flora)
AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 10, bonus_tame_chance = 5)
tentacles = new (src)
tentacles.Grant(src)
@@ -184,7 +184,7 @@
optional_checks = CALLBACK(src, PROC_REF(ready_to_grow)),\
optional_grow_behavior = CALLBACK(src, PROC_REF(grow_up))\
)
AddComponent(/datum/component/tameable, target_foods, tame_chance = 35, bonus_tame_chance = 20)
AddComponent(/datum/component/tameable, tame_chance = 35, bonus_tame_chance = 20)
AddComponent(/datum/component/swarming, 16, 11)
ADD_TRAIT(src, TRAIT_MOB_HIDE_HAPPINESS, INNATE_TRAIT) //Do not let strangers know it gets happy when poked if stray.
@@ -97,7 +97,7 @@ GLOBAL_LIST_EMPTY(raptor_population)
ai_controller.set_blackboard_key(BB_BASIC_MOB_SPEAK_LINES, display_emote)
inherited_stats = new
inherit_properties()
var/static/list/my_food = list(/obj/item/stack/ore)
var/list/my_food = string_list(list(/obj/item/stack/ore))
AddElement(/datum/element/basic_eating, food_types = my_food)
AddElement(/datum/element/ai_retaliate)
AddElement(/datum/element/ai_flee_while_injured, stop_fleeing_at = 0.5, start_fleeing_below = 0.2)
@@ -68,7 +68,11 @@
AddElement(/datum/element/pet_bonus, "woof")
AddElement(/datum/element/footstep, FOOTSTEP_MOB_CLAW)
AddElement(/datum/element/unfriend_attacker, untamed_reaction = "%SOURCE% fixes %TARGET% with a look of betrayal.")
AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/meat/slab/human/mutant/skeleton, /obj/item/stack/sheet/bone), tame_chance = 30, bonus_tame_chance = 15, unique = FALSE)
var/static/list/food_types = list(
/obj/item/food/meat/slab/human/mutant/skeleton,
/obj/item/stack/sheet/bone,
)
AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 30, bonus_tame_chance = 15, unique = FALSE)
AddComponent(/datum/component/obeys_commands, pet_commands)
var/dog_area = get_area(src)
for(var/obj/structure/bed/dogbed/dog_bed in dog_area)
@@ -111,7 +111,8 @@
tamed(tamer, feedback = FALSE)
befriend(tamer)
else
AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/meat), tame_chance = 10, bonus_tame_chance = 5)
var/static/list/food_types = list(/obj/item/food/meat)
AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 10, bonus_tame_chance = 5)
teleport = new(src)
teleport.Grant(src)
@@ -58,7 +58,8 @@
grant_actions_by_list(innate_actions)
AddElement(/datum/element/simple_flying)
AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/grown/carrot), tame_chance = 100)
var/list/food_types = string_list(list(/obj/item/food/grown/carrot))
AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 100)
ADD_TRAIT(src, TRAIT_SPACEWALK, INNATE_TRAIT)
on_hit_overlay = mutable_appearance(icon, "[icon_state]_crying")
@@ -83,7 +83,8 @@
if (tame)
faction |= FACTION_NEUTRAL
else
AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/cheese), tame_chance = 100)
var/static/list/food_types = list(/obj/item/food/cheese)
AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 100)
/mob/living/basic/mouse/Destroy()
SSmobs.cheeserats -= src
@@ -31,7 +31,13 @@
GRANT_ACTION(/datum/action/cooldown/tentacle_slap)
add_cell_sample()
AddComponent(/datum/component/tameable, list(/obj/item/food/fries, /obj/item/food/cheesyfries, /obj/item/food/cornchips, /obj/item/food/carrotfries), tame_chance = 30, bonus_tame_chance = 0)
var/static/list/food_types = list(
/obj/item/food/fries,
/obj/item/food/cheesyfries,
/obj/item/food/cornchips,
/obj/item/food/carrotfries,
)
AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 30, bonus_tame_chance = 0)
/mob/living/simple_animal/hostile/vatbeast/tamed(mob/living/tamer, obj/item/food)
buckle_lying = 0