mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-23 13:10:02 +01:00
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:
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user