Cows can eat wheat off the ground. If cows see wheat on the ground, they'll try to go eat it (+ moonicorns with galaxy thistle) (#69253)

About The Pull Request

Cows are now grazers, they love eatin' wheat and it even heals them if hurt. If they see it just on the ground, they might eat it all! Careful, botanists! While tamed, cows won't eat off the ground if they're busy ferrying you around.

FYI: this is going to conflict with #69247 and so thiss should not be merged until that is

Why It's Good For The Game

Wanted to add this with the original port of cows to basic mobs, didn't have the TECH to do so. Now I do, now it's done. I also wanted it ready for the future where mice and rats are ported, so they'd seek out cheese to eat. I also also think it's a neat way for a cow to heal.
This commit is contained in:
tralezab
2022-08-30 15:39:30 -07:00
committed by GitHub
parent 61f5673fc3
commit ce20a5b85e
18 changed files with 412 additions and 270 deletions
+3
View File
@@ -219,6 +219,9 @@
///some behaviors that check current_target also set this on deep crit mobs
#define BB_BASIC_MOB_EXECUTION_TARGET "BB_basic_execution_target"
///list of foods this mob likes
#define BB_BASIC_FOODS "BB_basic_foods"
///Bileworm AI keys
#define BB_BILEWORM_SPEW_BILE "BB_bileworm_spew_bile"
+9 -5
View File
@@ -140,13 +140,17 @@ multiple modular subtrees with behaviors
idle_behavior.perform_idle_behavior(delta_time, src) //Do some stupid shit while we have nothing to do
return
if(current_movement_target && get_dist(pawn, current_movement_target) > max_target_distance) //The distance is out of range
CancelActions()
return
if(current_movement_target)
if(!isatom(current_movement_target))
stack_trace("[pawn]'s current movement target is not an atom, rather a [current_movement_target.type]! Did you accidentally set it to a weakref?")
CancelActions()
return
for(var/i in current_behaviors)
var/datum/ai_behavior/current_behavior = i
if(get_dist(pawn, current_movement_target) > max_target_distance) //The distance is out of range
CancelActions()
return
for(var/datum/ai_behavior/current_behavior as anything in current_behaviors)
// Convert the current behaviour action cooldown to realtime seconds from deciseconds.current_behavior
// Then pick the max of this and the delta_time passed to ai_controller.process()
@@ -4,12 +4,19 @@
/datum/ai_behavior/basic_melee_attack/setup(datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key)
. = ..()
controller.current_movement_target = controller.blackboard[hiding_location_key] || controller.blackboard[target_key] //Hiding location is priority
//Hiding location is priority
var/datum/weakref/weak_target = controller.blackboard[hiding_location_key] || controller.blackboard[target_key]
var/atom/target = weak_target?.resolve()
if(!target)
return FALSE
controller.current_movement_target = target
/datum/ai_behavior/basic_melee_attack/perform(delta_time, datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key)
. = ..()
var/mob/living/basic/basic_mob = controller.pawn
var/atom/target = controller.blackboard[target_key]
//targetting datum will kill the action if not real anymore
var/datum/weakref/weak_target = controller.blackboard[target_key]
var/atom/target = weak_target?.resolve()
var/datum/targetting_datum/targetting_datum = controller.blackboard[targetting_datum_key]
if(!targetting_datum.can_attack(basic_mob, target))
@@ -38,23 +45,27 @@
/datum/ai_behavior/basic_ranged_attack/setup(datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key)
. = ..()
controller.current_movement_target = controller.blackboard[hiding_location_key] || controller.blackboard[target_key] //Hiding location is priority
var/datum/weakref/weak_target = controller.blackboard[hiding_location_key] || controller.blackboard[target_key]
var/atom/target = weak_target?.resolve()
if(!target)
return FALSE
controller.current_movement_target = target
/datum/ai_behavior/basic_ranged_attack/perform(delta_time, datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key)
. = ..()
var/mob/living/basic/basic_mob = controller.pawn
var/atom/target = controller.blackboard[target_key]
//targetting datum will kill the action if not real anymore
var/datum/weakref/weak_target = controller.blackboard[target_key]
var/atom/target = weak_target?.resolve()
var/datum/targetting_datum/targetting_datum = controller.blackboard[targetting_datum_key]
if(!targetting_datum.can_attack(basic_mob, target))
finish_action(controller, FALSE, target_key)
return
var/hiding_target = targetting_datum.find_hidden_mobs(basic_mob, target) //If this is valid, theyre hidden in something!
var/atom/hiding_target = targetting_datum.find_hidden_mobs(basic_mob, target) //If this is valid, theyre hidden in something!
controller.blackboard[hiding_location_key] = hiding_target
controller.blackboard[hiding_location_key] = WEAKREF(hiding_target)
if(hiding_target) //Shoot it!
basic_mob.RangedAttack(hiding_target)
@@ -35,11 +35,11 @@
return
var/atom/target = pick(filtered_targets)
controller.blackboard[target_key] = target
controller.blackboard[target_key] = WEAKREF(target)
var/atom/potential_hiding_location = targetting_datum.find_hidden_mobs(living_mob, target)
if(potential_hiding_location) //If they're hiding inside of something, we need to know so we can go for that instead initially.
controller.blackboard[hiding_location_key] = potential_hiding_location
controller.blackboard[hiding_location_key] = WEAKREF(potential_hiding_location)
finish_action(controller, TRUE)
@@ -0,0 +1,10 @@
/// similar to finding a target but looks for food types in the
/datum/ai_planning_subtree/find_food
/datum/ai_planning_subtree/find_food/SelectBehaviors(datum/ai_controller/controller, delta_time)
. = ..()
var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
if(target && !QDELETED(target))
return
var/list/wanted = controller.blackboard[BB_BASIC_FOODS]
controller.queue_behavior(/datum/ai_behavior/find_and_set/in_list, BB_BASIC_MOB_CURRENT_TARGET, wanted)
@@ -3,7 +3,9 @@
/datum/ai_planning_subtree/basic_melee_attack_subtree/SelectBehaviors(datum/ai_controller/controller, delta_time)
. = ..()
var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
var/datum/weakref/weak_target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
var/atom/target = weak_target?.resolve()
if(!target || QDELETED(target))
return
controller.queue_behavior(melee_attack_behavior, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETTING_DATUM, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION)
@@ -15,7 +17,8 @@
/datum/ai_planning_subtree/basic_ranged_attack_subtree/SelectBehaviors(datum/ai_controller/controller, delta_time)
. = ..()
var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
var/datum/weakref/weak_target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
var/atom/target = weak_target?.resolve()
if(!target || QDELETED(target))
return
controller.queue_behavior(ranged_attack_behavior, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETTING_DATUM, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION)
@@ -2,7 +2,8 @@
/datum/ai_planning_subtree/simple_find_target/SelectBehaviors(datum/ai_controller/controller, delta_time)
. = ..()
var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
var/datum/weakref/weak_target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
var/atom/target = weak_target?.resolve()
if(target && !QDELETED(target))
return
controller.queue_behavior(/datum/ai_behavior/find_potential_targets, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETTING_DATUM, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION)
@@ -53,3 +53,13 @@
return TRUE
return FALSE
/// Subtype more forgiving for items.
/// Careful, this can go wrong and keep a mob hyperfocused on an item it can't lose aggro on
/datum/targetting_datum/basic/allow_items
/datum/targetting_datum/basic/allow_items/can_attack(mob/living/living_mob, atom/the_target)
. = ..()
if(isitem(the_target))
// trust fall exercise
return TRUE
+74
View File
@@ -0,0 +1,74 @@
/**find and set
* Finds an item near themselves, sets a blackboard key as it. Very useful for ais that need to use machines or something.
* if you want to do something more complicated than find a single atom, change the search_tactic() proc
* cool tip: search_tactic() can set lists
*/
/datum/ai_behavior/find_and_set
action_cooldown = 2 SECONDS
/datum/ai_behavior/find_and_set/perform(delta_time, datum/ai_controller/controller, set_key, locate_path, search_range)
. = ..()
var/find_this_thing = search_tactic(controller, locate_path, search_range)
if(find_this_thing)
controller.blackboard[set_key] = WEAKREF(find_this_thing)
finish_action(controller, TRUE)
else
finish_action(controller, FALSE)
/datum/ai_behavior/find_and_set/proc/search_tactic(datum/ai_controller/controller, locate_path, search_range)
return locate(locate_path) in oview(search_range, controller.pawn)
/**
* Variant of find and set that fails if the living pawn doesn't hold something
*/
/datum/ai_behavior/find_and_set/pawn_must_hold_item
/datum/ai_behavior/find_and_set/pawn_must_hold_item/search_tactic(datum/ai_controller/controller)
var/mob/living/living_pawn = controller.pawn
if(!living_pawn.get_num_held_items())
return //we want to fail the search if we don't have something held
return ..()
/**
* Variant of find and set that also requires the item to be edible. checks hands too
*/
/datum/ai_behavior/find_and_set/edible
/datum/ai_behavior/find_and_set/edible/search_tactic(datum/ai_controller/controller, locate_path, search_range)
var/mob/living/living_pawn = controller.pawn
var/list/food_candidates = list()
for(var/held_candidate as anything in living_pawn.held_items)
if(!held_candidate || !IsEdible(held_candidate))
continue
food_candidates += held_candidate
var/list/local_results = locate(locate_path) in oview(search_range, controller.pawn)
for(var/local_candidate in local_results)
if(!IsEdible(local_candidate))
continue
food_candidates += local_candidate
if(food_candidates.len)
return pick(food_candidates)
/**
* Variant of find and set that only checks in hands, search range should be excluded for this
*/
/datum/ai_behavior/find_and_set/in_hands
/datum/ai_behavior/find_and_set/in_hands/search_tactic(datum/ai_controller/controller, locate_path)
var/mob/living/living_pawn = controller.pawn
return locate(locate_path) in living_pawn.held_items
/**
* Variant of find and set that takes a list of things to find.
*/
/datum/ai_behavior/find_and_set/in_list
/datum/ai_behavior/find_and_set/in_list/search_tactic(datum/ai_controller/controller, locate_paths, search_range)
var/list/found = list()
for(var/locate_path in locate_paths)
var/single_locate = ..(controller, locate_path, search_range)
if(single_locate)
found += single_locate
if(found.len)
return pick(found)
@@ -184,67 +184,6 @@
if(succeeded)
controller.blackboard[hunger_timer_key] = world.time + rand(12 SECONDS, 60 SECONDS)
/**find and set
* Finds an item near themselves, sets a blackboard key as it. Very useful for ais that need to use machines or something.
* if you want to do something more complicated than find a single atom, change the search_tactic() proc
* cool tip: search_tactic() can set lists
*/
/datum/ai_behavior/find_and_set
action_cooldown = 5 SECONDS
/datum/ai_behavior/find_and_set/perform(delta_time, datum/ai_controller/controller, set_key, locate_path, search_range)
. = ..()
var/find_this_thing = search_tactic(controller, locate_path, search_range)
if(find_this_thing)
controller.blackboard[set_key] = WEAKREF(find_this_thing)
finish_action(controller, TRUE)
else
finish_action(controller, FALSE)
/datum/ai_behavior/find_and_set/proc/search_tactic(datum/ai_controller/controller, locate_path, search_range)
return locate(locate_path) in oview(search_range, controller.pawn)
/**
* Variant of find and set that fails if the living pawn doesn't hold something
*/
/datum/ai_behavior/find_and_set/pawn_must_hold_item
/datum/ai_behavior/find_and_set/pawn_must_hold_item/search_tactic(datum/ai_controller/controller)
var/mob/living/living_pawn = controller.pawn
if(!living_pawn.get_num_held_items())
return //we want to fail the search if we don't have something held
return ..()
/**
* Variant of find and set that also requires the item to be edible. checks hands too
*/
/datum/ai_behavior/find_and_set/edible
/datum/ai_behavior/find_and_set/edible/search_tactic(datum/ai_controller/controller, locate_path, search_range)
var/mob/living/living_pawn = controller.pawn
var/list/food_candidates = list()
for(var/held_candidate as anything in living_pawn.held_items)
if(!held_candidate || !IsEdible(held_candidate))
continue
food_candidates += held_candidate
var/list/local_results = locate(locate_path) in oview(search_range, controller.pawn)
for(var/local_candidate in local_results)
if(!IsEdible(local_candidate))
continue
food_candidates += local_candidate
if(food_candidates.len)
return pick(food_candidates)
/**
* Variant of find and set that only checks in hands, search range should be excluded for this
*/
/datum/ai_behavior/find_and_set/in_hands
/datum/ai_behavior/find_and_set/in_hands/search_tactic(datum/ai_controller/controller, locate_path)
var/mob/living/living_pawn = controller.pawn
return locate(locate_path) in living_pawn.held_items
/**
* Drops items in hands, very important for future behaviors that require the pawn to grab stuff
*/
+1 -1
View File
@@ -20,7 +20,7 @@ We're simply starting out with our definition of what we're modifying. Any atom
Next, we'll want to define the AI Controller. This is the "brain" of the AI. It starts as a type, but is turned into an instance once the object is instanced.
### Object Declaraction
### Object Declaration
For clarity, i've included all the variables we're going to set up but haven't yet as nulls. In reality, some of these are always expected to be something and you should take a look at the base controller for which.
+51
View File
@@ -0,0 +1,51 @@
/**
* ## basic eating element!
*
* Small behavior for non-carbons to eat certain stuff they interact with
*/
/datum/element/basic_eating
element_flags = ELEMENT_BESPOKE|ELEMENT_DETACH
id_arg_index = 2
///Path of the reagent added
var/heal_amt
/// Types the animal can eat.
var/list/food_types
/datum/element/basic_eating/Attach(datum/target, heal_amt = 0, food_types = list())
. = ..()
if(!isliving(target))
return ELEMENT_INCOMPATIBLE
src.heal_amt = heal_amt
src.food_types = food_types
//this lets players eat
RegisterSignal(target, COMSIG_LIVING_UNARMED_ATTACK, .proc/on_unarm_attack)
//this lets ai eat. yes, i'm serious
RegisterSignal(target, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, .proc/on_pre_attackingtarget)
/datum/element/basic_eating/Detach(datum/target)
UnregisterSignal(target, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HOSTILE_PRE_ATTACKINGTARGET))
return ..()
/datum/element/basic_eating/proc/on_unarm_attack(mob/living/eater, atom/target, proximity, modifiers)
SIGNAL_HANDLER
try_eating(eater, target)
/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)
if(eater.combat_mode)
return
if(!is_type_in_list(target, food_types))
return
var/eat_verb = pick("bite","chew","nibble","gnaw","gobble","chomp")
var/healed = heal_amt && eater.health < eater.maxHealth
if(heal_amt)
eater.heal_overall_damage(heal_amt)
eater.visible_message(span_notice("[eater] [eat_verb]s [target]."), span_notice("You [eat_verb] [target][healed ? ", restoring some health" : ""]."))
playsound(eater.loc,'sound/items/eatfood.ogg', rand(10,50), TRUE)
qdel(target)
@@ -0,0 +1,91 @@
//cow
/mob/living/basic/cow
name = "cow"
desc = "Known for their milk, just don't tip them over."
icon = 'icons/mob/cows.dmi'
icon_state = "cow"
icon_living = "cow"
icon_dead = "cow_dead"
icon_gib = "cow_gib"
gender = FEMALE
mob_biotypes = MOB_ORGANIC | MOB_BEAST
speak_emote = list("moos","moos hauntingly")
speed = 1.1
see_in_dark = 6
butcher_results = list(/obj/item/food/meat/slab = 6)
response_help_continuous = "pets"
response_help_simple = "pet"
response_disarm_continuous = "gently pushes aside"
response_disarm_simple = "gently push aside"
response_harm_continuous = "kicks"
response_harm_simple = "kick"
attack_verb_continuous = "kicks"
attack_verb_simple = "kick"
attack_sound = 'sound/weapons/punch1.ogg'
attack_vis_effect = ATTACK_EFFECT_KICK
health = 50
maxHealth = 50
gold_core_spawnable = FRIENDLY_SPAWN
blood_volume = BLOOD_VOLUME_NORMAL
ai_controller = /datum/ai_controller/basic_controller/cow
/// what this cow munches on, and what can be used to tame it.
var/list/food_types = list(/obj/item/food/grown/wheat)
/// message sent when tamed
var/tame_message = "lets out a happy moo"
/// singular version for player cows
var/self_tame_message = "let out a happy moo"
/mob/living/basic/cow/Initialize(mapload)
AddComponent(/datum/component/tippable, \
tip_time = 0.5 SECONDS, \
untip_time = 0.5 SECONDS, \
self_right_time = rand(25 SECONDS, 50 SECONDS), \
post_tipped_callback = CALLBACK(src, .proc/after_cow_tipped))
AddElement(/datum/element/pet_bonus, "moos happily!")
AddElement(/datum/element/swabable, CELL_LINE_TABLE_COW, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5)
udder_component()
setup_eating()
. = ..()
ai_controller.blackboard[BB_BASIC_FOODS] = food_types
///wrapper for the udder component addition so you can have uniquely uddered cow subtypes
/mob/living/basic/cow/proc/udder_component()
AddComponent(/datum/component/udder)
/*
* food related components and elements are set up here for a few reasons:
*
* * static list can be created per-subtype, since static lists cannot be inherited and then changed
* * all eating-related components and elements share the same pool of food the mob likes
*/
/mob/living/basic/cow/proc/setup_eating()
var/static/list/food_types
if(!food_types)
food_types = src.food_types.Copy()
AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 25, bonus_tame_chance = 15, after_tame = CALLBACK(src, .proc/tamed))
AddElement(/datum/element/basic_eating, 10, food_types)
/mob/living/basic/cow/proc/tamed(mob/living/tamer)
buckle_lying = 0
visible_message("[src] [tame_message] as it seems to bond with [tamer].", "You [self_tame_message], recognizing [tamer] as your new pal.")
AddElement(/datum/element/ridable, /datum/component/riding/creature/cow)
/*
* Proc called via callback after the cow is tipped by the tippable component.
* Begins a timer for us pleading for help.
*
* tipper - the mob who tipped us
*/
/mob/living/basic/cow/proc/after_cow_tipped(mob/living/carbon/tipper)
addtimer(CALLBACK(src, .proc/set_tip_react_blackboard, tipper), rand(10 SECONDS, 20 SECONDS))
/*
* We've been waiting long enough, we're going to tell our AI to begin pleading.
*
* tipper - the mob who originally tipped us
*/
/mob/living/basic/cow/proc/set_tip_react_blackboard(mob/living/carbon/tipper)
if(!HAS_TRAIT_FROM(src, TRAIT_IMMOBILIZED, TIPPED_OVER) || !ai_controller)
return
ai_controller.blackboard[BB_BASIC_MOB_TIP_REACTING] = TRUE
ai_controller.blackboard[BB_BASIC_MOB_TIPPER] = tipper
@@ -0,0 +1,17 @@
/datum/ai_controller/basic_controller/cow
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items(),
BB_BASIC_MOB_TIP_REACTING = FALSE,
BB_BASIC_MOB_TIPPER = null,
)
ai_traits = STOP_MOVING_WHEN_PULLED
ai_movement = /datum/ai_movement/basic_avoidance
idle_behavior = /datum/idle_behavior/idle_random_walk
planning_subtrees = list(
/datum/ai_planning_subtree/tip_reaction,
/datum/ai_planning_subtree/find_food,
//attacking the food will eat it
/datum/ai_planning_subtree/basic_melee_attack_subtree,
/datum/ai_planning_subtree/random_speech/cow,
)
@@ -0,0 +1,80 @@
/// moonicorn subtype, very hostile unless there's some food to be eatin'
/mob/living/basic/cow/moonicorn
name = "moonicorn"
desc = "Magical cow of legend. Its horn will pacify anything it touches, rendering victims mostly helpless. \
Victims, yes, because despite the enimatic and wonderous appearance, the moonicorn is incredibly aggressive."
icon_state = "moonicorn"
icon_living = "moonicorn"
icon_dead = "moonicorn_dead"
icon_gib = null //otherwise does the regular cow gib animation
faction = list("hostile")
speed = 1
melee_damage_lower = 25
melee_damage_upper = 25
obj_damage = 35
attack_verb_continuous = "telekinetically rams its moonihorn into"
attack_verb_simple = "telekinetically ram your moonihorn into"
gold_core_spawnable = NO_SPAWN
attack_sound = 'sound/weapons/bladeslice.ogg'
attack_vis_effect = ATTACK_EFFECT_SLASH
ai_controller = /datum/ai_controller/basic_controller/cow/moonicorn
food_types = list(/obj/item/food/grown/galaxythistle)
tame_message = "nods with respect"
self_tame_message = "nod with respect"
/mob/living/basic/cow/moonicorn/Initialize(mapload)
. = ..()
AddElement(/datum/element/venomous, /datum/reagent/pax, 5)
AddElement(/datum/element/movement_turf_changer, /turf/open/floor/grass/fairy)
/mob/living/basic/cow/moonicorn/udder_component()
AddComponent(/datum/component/udder, /obj/item/udder, null, null, /datum/reagent/drug/mushroomhallucinogen)
/mob/living/basic/cow/moonicorn/setup_eating()
var/static/list/food_types
if(!food_types)
food_types = src.food_types.Copy()
AddElement(/datum/element/basic_eating, 10, food_types)
AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 25, bonus_tame_chance = 15, after_tame = CALLBACK(src, .proc/tamed))
/mob/living/basic/cow/moonicorn/tamed(mob/living/tamer)
. = ..()
///stop killing my FRIENDS
faction |= tamer.faction
/datum/ai_controller/basic_controller/cow/moonicorn
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items/moonicorn(),
BB_BASIC_MOB_TIP_REACTING = FALSE,
BB_BASIC_MOB_TIPPER = null,
)
planning_subtrees = list(
/datum/ai_planning_subtree/tip_reaction,
/datum/ai_planning_subtree/random_speech/cow,
//finds someone to kill
/datum/ai_planning_subtree/simple_find_target,
//...or something to eat, possibly. both types of target handled by melee attack subtree
/datum/ai_planning_subtree/find_food,
/datum/ai_planning_subtree/basic_melee_attack_subtree/moonicorn,
)
/datum/ai_planning_subtree/basic_melee_attack_subtree/moonicorn
melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/moonicorn
/datum/ai_behavior/basic_melee_attack/moonicorn
//it's a fairly strong attack and it applies pax, so they do not attack often
action_cooldown = 2 SECONDS
///moonicorns will not attack people holding something that could tame them.
/datum/targetting_datum/basic/allow_items/moonicorn
/datum/targetting_datum/basic/allow_items/moonicorn/can_attack(mob/living/living_mob, atom/the_target)
. = ..()
if(!.)
return FALSE
if(isliving(the_target)) //Targetting vs living mobs
var/mob/living/living_target = the_target
for(var/obj/item/food/grown/galaxythistle/tame_food in living_target.held_items)
return FALSE //heyyy this can tame me! let's NOT fight
@@ -0,0 +1,31 @@
///Wisdom cow, gives XP to a random skill and speaks wisdoms
/mob/living/basic/cow/wisdom
name = "wisdom cow"
desc = "Known for its wisdom, shares it with all."
gold_core_spawnable = FALSE
ai_controller = /datum/ai_controller/basic_controller/cow/wisdom
/mob/living/basic/cow/wisdom/setup_eating()
return //cannot tame me! and I don't care about eatin' nothing, neither!
/datum/ai_controller/basic_controller/cow/wisdom
//don't give a targetting datum
blackboard = list(
BB_BASIC_MOB_TIP_REACTING = FALSE,
BB_BASIC_MOB_TIPPER = null,
)
planning_subtrees = list(
/datum/ai_planning_subtree/tip_reaction,
/datum/ai_planning_subtree/random_speech/cow/wisdom,
)
///Give intense wisdom to the attacker if they're being friendly about it
/mob/living/basic/cow/wisdom/attack_hand(mob/living/carbon/user, list/modifiers)
if(!stat && !user.combat_mode)
to_chat(user, span_nicegreen("[src] whispers you some intense wisdoms and then disappears!"))
user.mind?.adjust_experience(pick(GLOB.skill_types), 500)
do_smoke(1, holder = src, location = get_turf(src))
qdel(src)
return
return ..()
@@ -1,189 +0,0 @@
//cow
/mob/living/basic/cow
name = "cow"
desc = "Known for their milk, just don't tip them over."
icon = 'icons/mob/cows.dmi'
icon_state = "cow"
icon_living = "cow"
icon_dead = "cow_dead"
icon_gib = "cow_gib"
gender = FEMALE
mob_biotypes = MOB_ORGANIC | MOB_BEAST
speak_emote = list("moos","moos hauntingly")
speed = 1.1
see_in_dark = 6
butcher_results = list(/obj/item/food/meat/slab = 6)
response_help_continuous = "pets"
response_help_simple = "pet"
response_disarm_continuous = "gently pushes aside"
response_disarm_simple = "gently push aside"
response_harm_continuous = "kicks"
response_harm_simple = "kick"
attack_verb_continuous = "kicks"
attack_verb_simple = "kick"
attack_sound = 'sound/weapons/punch1.ogg'
attack_vis_effect = ATTACK_EFFECT_KICK
health = 50
maxHealth = 50
gold_core_spawnable = FRIENDLY_SPAWN
blood_volume = BLOOD_VOLUME_NORMAL
ai_controller = /datum/ai_controller/basic_controller/cow
var/tame_message = "lets out a happy moo"
var/self_tame_message = "let out a happy moo"
/mob/living/basic/cow/Initialize(mapload)
AddComponent(/datum/component/tippable, \
tip_time = 0.5 SECONDS, \
untip_time = 0.5 SECONDS, \
self_right_time = rand(25 SECONDS, 50 SECONDS), \
post_tipped_callback = CALLBACK(src, .proc/after_cow_tipped))
AddElement(/datum/element/pet_bonus, "moos happily!")
AddElement(/datum/element/swabable, CELL_LINE_TABLE_COW, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5)
udder_component()
make_tameable()
. = ..()
///wrapper for the udder component addition so you can have uniquely uddered cow subtypes
/mob/living/basic/cow/proc/udder_component()
AddComponent(/datum/component/udder)
///wrapper for the tameable component addition so you can have non tamable cow subtypes
/mob/living/basic/cow/proc/make_tameable()
AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/grown/wheat), tame_chance = 25, bonus_tame_chance = 15, after_tame = CALLBACK(src, .proc/tamed))
/mob/living/basic/cow/proc/tamed(mob/living/tamer)
buckle_lying = 0
visible_message("[src] [tame_message] as it seems to bond with [tamer].", "You [self_tame_message], recognizing [tamer] as your new pal.")
AddElement(/datum/element/ridable, /datum/component/riding/creature/cow)
/*
* Proc called via callback after the cow is tipped by the tippable component.
* Begins a timer for us pleading for help.
*
* tipper - the mob who tipped us
*/
/mob/living/basic/cow/proc/after_cow_tipped(mob/living/carbon/tipper)
addtimer(CALLBACK(src, .proc/set_tip_react_blackboard, tipper), rand(10 SECONDS, 20 SECONDS))
/*
* We've been waiting long enough, we're going to tell our AI to begin pleading.
*
* tipper - the mob who originally tipped us
*/
/mob/living/basic/cow/proc/set_tip_react_blackboard(mob/living/carbon/tipper)
if(!HAS_TRAIT_FROM(src, TRAIT_IMMOBILIZED, TIPPED_OVER) || !ai_controller)
return
ai_controller.blackboard[BB_BASIC_MOB_TIP_REACTING] = TRUE
ai_controller.blackboard[BB_BASIC_MOB_TIPPER] = tipper
/datum/ai_controller/basic_controller/cow
blackboard = list(
BB_BASIC_MOB_TIP_REACTING = FALSE,
BB_BASIC_MOB_TIPPER = null,
)
ai_traits = STOP_MOVING_WHEN_PULLED
ai_movement = /datum/ai_movement/basic_avoidance
idle_behavior = /datum/idle_behavior/idle_random_walk
planning_subtrees = list(
/datum/ai_planning_subtree/tip_reaction,
/datum/ai_planning_subtree/random_speech/cow,
)
///Wisdom cow, gives XP to a random skill and speaks wisdoms
/mob/living/basic/cow/wisdom
name = "wisdom cow"
desc = "Known for its wisdom, shares it with all."
gold_core_spawnable = FALSE
ai_controller = /datum/ai_controller/basic_controller/cow/wisdom
/mob/living/basic/cow/wisdom/make_tameable()
return //cannot tame me!
/datum/ai_controller/basic_controller/cow/wisdom
planning_subtrees = list(
/datum/ai_planning_subtree/tip_reaction,
/datum/ai_planning_subtree/random_speech/cow/wisdom,
)
///Give intense wisdom to the attacker if they're being friendly about it
/mob/living/basic/cow/wisdom/attack_hand(mob/living/carbon/user, list/modifiers)
if(!stat && !user.combat_mode)
to_chat(user, span_nicegreen("[src] whispers you some intense wisdoms and then disappears!"))
user.mind?.adjust_experience(pick(GLOB.skill_types), 500)
do_smoke(1, holder = src, location = get_turf(src))
qdel(src)
return
return ..()
/mob/living/basic/cow/moonicorn
name = "moonicorn"
desc = "Magical cow of legend. Its horn will pacify anything it touches, rendering victims mostly helpless. \
Victims, yes, because despite the enimatic and wonderous appearance, the moonicorn is incredibly aggressive."
icon_state = "moonicorn"
icon_living = "moonicorn"
icon_dead = "moonicorn_dead"
icon_gib = null //otherwise does the regular cow gib animation
faction = list("hostile")
speed = 1
melee_damage_lower = 25
melee_damage_upper = 25
obj_damage = 35
attack_verb_continuous = "telekinetically rams its moonihorn into"
attack_verb_simple = "telekinetically ram your moonihorn into"
gold_core_spawnable = NO_SPAWN
attack_sound = 'sound/weapons/bladeslice.ogg'
attack_vis_effect = ATTACK_EFFECT_SLASH
ai_controller = /datum/ai_controller/basic_controller/cow/moonicorn
tame_message = "nods with respect"
self_tame_message = "nod with respect"
/mob/living/basic/cow/moonicorn/Initialize(mapload)
. = ..()
AddElement(/datum/element/venomous, /datum/reagent/pax, 5)
AddElement(/datum/element/movement_turf_changer, /turf/open/floor/grass/fairy)
/mob/living/basic/cow/moonicorn/udder_component()
AddComponent(/datum/component/udder, /obj/item/udder, null, null, /datum/reagent/drug/mushroomhallucinogen)
/mob/living/basic/cow/moonicorn/make_tameable()
AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/grown/galaxythistle), tame_chance = 25, bonus_tame_chance = 15, after_tame = CALLBACK(src, .proc/tamed))
/mob/living/basic/cow/moonicorn/tamed(mob/living/tamer)
. = ..()
///stop killing my FRIENDS
faction |= tamer.faction
/datum/ai_controller/basic_controller/cow/moonicorn
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/moonicorn(),
BB_BASIC_MOB_TIP_REACTING = FALSE,
BB_BASIC_MOB_TIPPER = null,
)
planning_subtrees = list(
/datum/ai_planning_subtree/tip_reaction,
/datum/ai_planning_subtree/random_speech/cow,
/datum/ai_planning_subtree/simple_find_target,
/datum/ai_planning_subtree/basic_melee_attack_subtree/moonicorn,
)
/datum/ai_planning_subtree/basic_melee_attack_subtree/moonicorn
melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/moonicorn
/datum/ai_behavior/basic_melee_attack/moonicorn
//it's a fairly strong attack and it applies pax, so they do not attack often
action_cooldown = 2 SECONDS
///moonicorns will not attack people holding something that could tame them.
/datum/targetting_datum/basic/moonicorn
/datum/targetting_datum/basic/moonicorn/can_attack(mob/living/living_mob, atom/the_target)
. = ..()
if(!.)
return FALSE
if(isliving(the_target)) //Targetting vs living mobs
var/mob/living/living_target = the_target
for(var/obj/item/food/grown/galaxythistle/tame_food in living_target.held_items)
return FALSE //heyyy this can tame me! let's NOT fight
+7 -1
View File
@@ -678,6 +678,7 @@
#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\try_mob_ability.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\find_food.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\simple_attack_target.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\simple_find_target.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\speech_subtree.dm"
@@ -689,6 +690,7 @@
#include "code\datums\ai\dog\dog_behaviors.dm"
#include "code\datums\ai\dog\dog_controller.dm"
#include "code\datums\ai\dog\dog_subtrees.dm"
#include "code\datums\ai\generic\find_and_set.dm"
#include "code\datums\ai\generic\generic_behaviors.dm"
#include "code\datums\ai\generic\generic_subtrees.dm"
#include "code\datums\ai\hauntium\haunted_controller.dm"
@@ -976,6 +978,7 @@
#include "code\datums\elements\backblast.dm"
#include "code\datums\elements\bane.dm"
#include "code\datums\elements\basic_body_temp_sensitive.dm"
#include "code\datums\elements\basic_eating.dm"
#include "code\datums\elements\beauty.dm"
#include "code\datums\elements\bed_tucking.dm"
#include "code\datums\elements\bsa_blocker.dm"
@@ -3416,9 +3419,12 @@
#include "code\modules\mob\living\basic\basic.dm"
#include "code\modules\mob\living\basic\basic_defense.dm"
#include "code\modules\mob\living\basic\health_adjustment.dm"
#include "code\modules\mob\living\basic\farm_animals\cows.dm"
#include "code\modules\mob\living\basic\farm_animals\pig.dm"
#include "code\modules\mob\living\basic\farm_animals\sheep.dm"
#include "code\modules\mob\living\basic\farm_animals\cow\_cow.dm"
#include "code\modules\mob\living\basic\farm_animals\cow\cow_ai.dm"
#include "code\modules\mob\living\basic\farm_animals\cow\cow_moonicorn.dm"
#include "code\modules\mob\living\basic\farm_animals\cow\cow_wisdom.dm"
#include "code\modules\mob\living\basic\lavaland\bileworm\_bileworm.dm"
#include "code\modules\mob\living\basic\lavaland\bileworm\bileworm_actions.dm"
#include "code\modules\mob\living\basic\lavaland\bileworm\bileworm_ai.dm"