[MIRROR] Dogs use the Pet Command system [MDB IGNORE] (#18393)

* Dogs use the Pet Command system

* modular pets

* modular dogs

* oops

* NO

Co-authored-by: Jacquerel <hnevard@gmail.com>
Co-authored-by: tastyfish <crazychris32@gmail.com>
This commit is contained in:
SkyratBot
2023-01-09 08:06:58 -05:00
committed by GitHub
co-authored by Jacquerel tastyfish
parent a15fe71efd
commit ff97c189db
40 changed files with 831 additions and 669 deletions
+3 -5
View File
@@ -26,7 +26,10 @@
#define AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION (1<<4)
///AI flags
/// Don't move if being pulled
#define STOP_MOVING_WHEN_PULLED (1<<0)
/// Don't act if you're dead
#define STOP_ACTING_WHILE_DEAD (1<<1)
//Base Subtree defines
@@ -157,14 +160,9 @@
///Dog AI controller blackboard keys
#define BB_SIMPLE_CARRY_ITEM "BB_SIMPLE_CARRY_ITEM"
#define BB_FETCH_TARGET "BB_FETCH_TARGET"
#define BB_FETCH_IGNORE_LIST "BB_FETCH_IGNORE_LISTlist"
#define BB_FETCH_DELIVER_TO "BB_FETCH_DELIVER_TO"
#define BB_DOG_FRIENDS "BB_DOG_FRIENDS"
#define BB_DOG_ORDER_MODE "BB_DOG_ORDER_MODE"
#define BB_DOG_PLAYING_DEAD "BB_DOG_PLAYING_DEAD"
#define BB_DOG_HARASS_TARGET "BB_DOG_HARASS_TARGET"
#define BB_DOG_HARASS_FRUSTRATION "BB_DOG_HARASS_FRUSTRATION"
#define BB_DOG_HARASS_HARM "BB_DOG_HARASS_HARM"
#define BB_DOG_IS_SLOW "BB_DOG_IS_SLOW"
-10
View File
@@ -1,15 +1,5 @@
/// Blackboard field for the most recent command the pet was given
#define BB_ACTIVE_PET_COMMAND "BB_active_pet_command"
/// Follow your normal behaviour
#define PET_COMMAND_NONE "pet_command_none"
/// Don't take any actions at all
#define PET_COMMAND_IDLE "pet_command_idle"
/// Pursue and attack the pointed target
#define PET_COMMAND_ATTACK "pet_command_attack"
/// Pursue the person who made this command
#define PET_COMMAND_FOLLOW "pet_commmand_follow"
/// Use a targetted mob ability
#define PET_COMMAND_USE_ABILITY "pet_command_use_ability"
/// Blackboard field for what we actually want the pet to target
#define BB_CURRENT_PET_TARGET "BB_current_pet_target"
@@ -133,3 +133,5 @@
/// From /mob/living/befriend() : (mob/living/new_friend)
#define COMSIG_LIVING_BEFRIENDED "living_befriended"
/// From /mob/living/unfriend() : (mob/living/old_friend)
#define COMSIG_LIVING_UNFRIENDED "living_unfriended"
+26 -9
View File
@@ -8,7 +8,7 @@ multiple modular subtrees with behaviors
///The atom this controller is controlling
var/atom/pawn
///Bitfield of traits for this AI to handle extra behavior
var/ai_traits
var/ai_traits = STOP_ACTING_WHILE_DEAD
///Current actions planned to be performed by the AI in the upcoming plan
var/list/planned_behaviors
///Current actions being performed by the AI.
@@ -106,24 +106,34 @@ multiple modular subtrees with behaviors
pawn = new_pawn
pawn.ai_controller = src
if(!continue_processing_when_client && istype(new_pawn, /mob))
var/mob/possible_client_holder = new_pawn
if(possible_client_holder.client)
set_ai_status(AI_STATUS_OFF)
else
set_ai_status(AI_STATUS_ON)
else
if (!ismob(new_pawn))
set_ai_status(AI_STATUS_ON)
else
set_ai_status(get_setup_mob_ai_status(new_pawn))
RegisterSignal(pawn, COMSIG_MOB_LOGIN, PROC_REF(on_sentience_gained))
/// Mobs have more complicated factors about whether their AI should be on or not
/datum/ai_controller/proc/get_setup_mob_ai_status(mob/mob_pawn)
var/final_status = AI_STATUS_ON
if(!continue_processing_when_client && mob_pawn.client)
final_status = AI_STATUS_OFF
if(ai_traits & STOP_ACTING_WHILE_DEAD)
RegisterSignal(pawn, COMSIG_MOB_STATCHANGE, PROC_REF(on_stat_changed))
if(mob_pawn.stat == DEAD)
final_status = AI_STATUS_OFF
return final_status
///Abstract proc for initializing the pawn to the new controller
/datum/ai_controller/proc/TryPossessPawn(atom/new_pawn)
return
///Proc for deinitializing the pawn to the old controller
/datum/ai_controller/proc/UnpossessPawn(destroy)
UnregisterSignal(pawn, list(COMSIG_MOB_LOGIN, COMSIG_MOB_LOGOUT))
UnregisterSignal(pawn, list(COMSIG_MOB_LOGIN, COMSIG_MOB_LOGOUT, COMSIG_MOB_STATCHANGE))
if(ai_movement.moving_controllers[src])
ai_movement.stop_moving_towards(src)
pawn.ai_controller = null
@@ -161,6 +171,7 @@ multiple modular subtrees with behaviors
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
@@ -284,6 +295,12 @@ multiple modular subtrees with behaviors
arguments += stored_arguments
current_behavior.finish_action(arglist(arguments))
/// Turn the controller on or off based on if you're alive, we only register to this if the flag is present so don't need to check again
/datum/ai_controller/proc/on_stat_changed(mob/living/source, new_stat)
SIGNAL_HANDLER
var/new_ai_status = (new_stat == DEAD) ? AI_STATUS_OFF : AI_STATUS_ON
set_ai_status(new_ai_status)
/datum/ai_controller/proc/on_sentience_gained()
SIGNAL_HANDLER
UnregisterSignal(pawn, COMSIG_MOB_LOGIN)
@@ -0,0 +1,50 @@
/**
* Simple behaviour for picking up an item we are already in range of.
* The blackboard storage key isn't very safe because it doesn't make sense to register signals in here.
* Use the AI held item component to manage this.
*/
/datum/ai_behavior/pick_up_item
/datum/ai_behavior/pick_up_item/setup(datum/ai_controller/controller, target_key, storage_key)
. = ..()
var/datum/weakref/weak_target = controller.blackboard[target_key]
var/obj/item/target = weak_target?.resolve()
return isitem(target) && isturf(target.loc) && !target.anchored
/datum/ai_behavior/pick_up_item/perform(delta_time, datum/ai_controller/controller, target_key, storage_key)
. = ..()
var/datum/weakref/weak_target = controller.blackboard[target_key]
var/obj/item/target = weak_target?.resolve()
if(!isturf(target?.loc)) // Someone picked it up or it got deleted
finish_action(controller, FALSE, target_key)
return
if(!in_range(controller.pawn, target)) // It teleported
finish_action(controller, FALSE, target_key)
return
pickup_item(controller, target, storage_key)
finish_action(controller, TRUE, target_key)
/datum/ai_behavior/pick_up_item/finish_action(datum/ai_controller/controller, success, target_key, storage_key)
. = ..()
controller.blackboard[target_key] = null
/datum/ai_behavior/pick_up_item/proc/pickup_item(datum/ai_controller/controller, obj/item/target, storage_key)
var/atom/pawn = controller.pawn
drop_existing_item(controller, storage_key)
pawn.visible_message(span_notice("[pawn] picks up [target]."))
target.forceMove(pawn)
controller.blackboard[storage_key] = WEAKREF(target)
return TRUE
/datum/ai_behavior/pick_up_item/proc/drop_existing_item(datum/ai_controller/controller, storage_key)
var/datum/weakref/carried_ref = controller.blackboard[storage_key]
var/obj/item/carried_item = carried_ref?.resolve()
if(!carried_item)
return
controller.blackboard[storage_key] = null
var/atom/pawn = controller.pawn
if(carried_item.loc != pawn)
return
pawn.visible_message(span_notice("[pawn] drops [carried_item]."))
carried_item.forceMove(get_turf(pawn))
return TRUE
@@ -0,0 +1,157 @@
/**
* Traverse to a target with the intention of picking it up.
* If we can't do that, add it to a list of ignored items.
*/
/datum/ai_behavior/fetch_seek
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT
/datum/ai_behavior/fetch_seek/setup(datum/ai_controller/controller, target_key, delivery_key)
. = ..()
var/datum/weakref/thing_ref = controller.blackboard[target_key]
var/obj/item/fetch_thing = thing_ref?.resolve()
// It stopped existing
if (!fetch_thing)
return FALSE
set_movement_target(fetch_thing)
/datum/ai_behavior/fetch_seek/perform(delta_time, datum/ai_controller/controller, target_key, delivery_key)
. = ..()
var/datum/weakref/thing_ref = controller.blackboard[target_key]
var/obj/item/fetch_thing = thing_ref?.resolve()
// It stopped existing
if (!fetch_thing)
finish_action(controller, FALSE, target_key, delivery_key)
return
var/mob/living/living_pawn = controller.pawn
// We can't pick this up
if (fetch_thing.anchored || !isturf(fetch_thing.loc) || !living_pawn.CanReach(fetch_thing))
finish_action(controller, FALSE, target_key, delivery_key)
return
finish_action(controller, TRUE, target_key, delivery_key)
/datum/ai_behavior/fetch_seek/finish_action(datum/ai_controller/controller, success, target_key, delivery_key)
. = ..()
if (success)
return
// Blacklist item if we failed
var/datum/weakref/thing_ref = controller.blackboard[target_key]
var/obj/item/target = thing_ref?.resolve()
if (target)
controller.blackboard[BB_FETCH_IGNORE_LIST][thing_ref] = TRUE
controller.blackboard[target_key] = null
controller.blackboard[delivery_key] = null
/**
* The second half of fetching, deliver the item to a target.
*/
/datum/ai_behavior/deliver_fetched_item
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT
/datum/ai_behavior/deliver_fetched_item/setup(datum/ai_controller/controller, delivery_key, storage_key)
. = ..()
var/datum/weakref/return_ref = controller.blackboard[delivery_key]
var/mob/living/return_target = return_ref?.resolve()
if(!return_target) // Guess it's mine now
return FALSE
set_movement_target(return_target)
/datum/ai_behavior/deliver_fetched_item/perform(delta_time, datum/ai_controller/controller, delivery_key, storage_key)
. = ..()
var/datum/weakref/return_ref = controller.blackboard[delivery_key]
var/mob/living/return_target = return_ref?.resolve()
if(!return_target)
finish_action(controller, FALSE, delivery_key)
return
deliver_item(controller, return_target, storage_key)
finish_action(controller, TRUE, delivery_key)
/datum/ai_behavior/deliver_fetched_item/finish_action(datum/ai_controller/controller, success, delivery_key)
. = ..()
controller.blackboard[delivery_key] = null
controller.blackboard[BB_ACTIVE_PET_COMMAND] = null
/// Actually deliver the fetched item to the target, if we still have it
/datum/ai_behavior/deliver_fetched_item/proc/deliver_item(datum/ai_controller/controller, return_target, storage_key)
var/mob/pawn = controller.pawn
var/datum/weakref/carried_ref = controller.blackboard[storage_key]
var/obj/item/carried_item = carried_ref?.resolve()
if(!carried_item || carried_item.loc != pawn)
pawn.visible_message(span_notice("[pawn] looks around as if [pawn.p_they()] [pawn.p_have()] lost something."))
finish_action(controller, FALSE)
return
pawn.visible_message(span_notice("[pawn] delivers [carried_item] to [return_target]."))
carried_item.forceMove(get_turf(return_target))
controller.blackboard[storage_key] = null
return TRUE
/**
* The alternate second half of fetching, attack the item if we can eat it.
* Or make pleading eyes at someone who has picked it up.
*
* Unfortunately this doesn't work because food can't currently be eaten by mobs.
*/
/datum/ai_behavior/eat_fetched_snack
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT
action_cooldown = 0.8 SECONDS
/datum/ai_behavior/eat_fetched_snack/setup(datum/ai_controller/controller, target_key, delivery_key)
. = ..()
var/datum/weakref/thing_ref = controller.blackboard[target_key]
var/obj/item/snack = thing_ref?.resolve()
if(!istype(snack) || !IS_EDIBLE(snack) || !(isturf(snack.loc) || ishuman(snack.loc)))
return FALSE // This isn't food at all!
set_movement_target(snack)
/datum/ai_behavior/eat_fetched_snack/perform(delta_time, datum/ai_controller/controller, target_key, delivery_key)
. = ..()
var/datum/weakref/thing_ref = controller.blackboard[target_key]
var/obj/item/snack = thing_ref?.resolve()
if(!(isturf(snack.loc) || ishuman(snack.loc)))
finish_action(controller, FALSE) // Where did it go?
var/mob/living/basic/basic_pawn = controller.pawn
if(!in_range(basic_pawn, snack))
return
if(isturf(snack.loc))
basic_pawn.melee_attack(snack) // snack attack!
else if(iscarbon(snack.loc) && DT_PROB(10, delta_time))
basic_pawn.manual_emote("Stares at [snack.loc]'s [snack.name] intently.")
if(QDELETED(snack)) // we ate it!
finish_action(controller, TRUE, target_key, delivery_key)
/datum/ai_behavior/eat_fetched_snack/finish_action(datum/ai_controller/controller, succeeded, target_key, delivery_key)
. = ..()
controller.blackboard[target_key] = null
controller.blackboard[delivery_key] = null
controller.blackboard[BB_ACTIVE_PET_COMMAND] = null
/**
* Clear our failed fetch list every so often
*/
/datum/ai_behavior/forget_failed_fetches
/// How long to wait between resetting the list
var/cooldown_duration = AI_FETCH_IGNORE_DURATION
/// Time until we should forget things we failed to pick up
COOLDOWN_DECLARE(reset_ignore_cooldown)
/datum/ai_behavior/forget_failed_fetches/setup(datum/ai_controller/controller, ...)
. = ..()
if (!COOLDOWN_FINISHED(src, reset_ignore_cooldown))
return FALSE
if (!length(controller.blackboard[BB_FETCH_IGNORE_LIST]))
return
/datum/ai_behavior/forget_failed_fetches/perform(delta_time, datum/ai_controller/controller)
. = ..()
COOLDOWN_START(src, reset_ignore_cooldown, cooldown_duration)
controller.blackboard[BB_FETCH_IGNORE_LIST] = list()
finish_action(controller, TRUE)
@@ -8,11 +8,8 @@
/datum/ai_planning_subtree/pet_planning
/datum/ai_planning_subtree/pet_planning/SelectBehaviors(datum/ai_controller/controller, delta_time)
var/active_command_key = controller.blackboard[BB_ACTIVE_PET_COMMAND]
if (!active_command_key)
return // Do something else
var/datum/weakref/weak_command = controller.blackboard[active_command_key]
var/datum/weakref/weak_command = controller.blackboard[BB_ACTIVE_PET_COMMAND]
var/datum/pet_command/command = weak_command?.resolve()
if (!command)
return // We forgot this command at some point
return // Do something else
return command.execute_action(controller)
@@ -23,5 +23,4 @@
/datum/ai_behavior/pet_use_ability/finish_action(datum/ai_controller/controller, succeeded, ability_key, target_key)
. = ..()
controller.blackboard[BB_ACTIVE_PET_COMMAND] = PET_COMMAND_IDLE // Wait for further instruction
controller.blackboard[target_key] = null
@@ -0,0 +1,24 @@
/// Pretend to be dead
/datum/ai_behavior/play_dead
/datum/ai_behavior/play_dead/setup(datum/ai_controller/controller)
. = ..()
var/mob/living/basic/basic_pawn = controller.pawn
if(!istype(basic_pawn) || basic_pawn.stat) // Can't act dead if you're dead
return
basic_pawn.emote("deathgasp", intentional=FALSE)
basic_pawn.look_dead()
/datum/ai_behavior/play_dead/perform(delta_time, datum/ai_controller/controller)
. = ..()
if(DT_PROB(10, delta_time))
finish_action(controller, TRUE)
/datum/ai_behavior/play_dead/finish_action(datum/ai_controller/controller, succeeded)
. = ..()
var/mob/living/basic/basic_pawn = controller.pawn
if(!istype(basic_pawn) || basic_pawn.stat) // imagine actually dying while playing dead. hell, imagine being the kid waiting for your pup to get back up :(
return
basic_pawn.visible_message(span_notice("[basic_pawn] miraculously springs back to life!"))
basic_pawn.look_alive()
controller.blackboard[BB_ACTIVE_PET_COMMAND] = null
+45 -218
View File
@@ -1,233 +1,60 @@
/datum/ai_behavior/battle_screech/dog
screeches = list("barks","howls")
/// Fetching makes the pawn chase after whatever it's targeting and pick it up when it's in range, with the dog_equip behavior
/datum/ai_behavior/fetch
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT
/datum/ai_behavior/fetch/perform(delta_time, datum/ai_controller/controller)
. = ..()
var/mob/living/living_pawn = controller.pawn
var/obj/item/fetch_thing = controller.blackboard[BB_FETCH_TARGET]
//either we can't pick it up, or we'd rather eat it, so stop trying.
if(fetch_thing.anchored || !isturf(fetch_thing.loc) || IS_EDIBLE(fetch_thing) || !living_pawn.CanReach(fetch_thing))
finish_action(controller, FALSE)
return
finish_action(controller, TRUE)
/datum/ai_behavior/fetch/finish_action(datum/ai_controller/controller, success)
. = ..()
if(!success) //Don't try again on this item if we failed
var/obj/item/target = controller.blackboard[BB_FETCH_TARGET]
if(target)
controller.blackboard[BB_FETCH_IGNORE_LIST][WEAKREF(target)] = TRUE
controller.blackboard[BB_FETCH_TARGET] = null
controller.blackboard[BB_FETCH_DELIVER_TO] = null
/// This is simply a behaviour to pick up a fetch target
/datum/ai_behavior/simple_equip/perform(delta_time, datum/ai_controller/controller)
. = ..()
var/obj/item/fetch_target = controller.blackboard[BB_FETCH_TARGET]
if(!isturf(fetch_target?.loc) || !isitem(fetch_target)) // someone picked it up, something happened to it, or it wasn't an item anyway
finish_action(controller, FALSE)
return
if(in_range(controller.pawn, fetch_target))
pickup_item(controller, fetch_target)
finish_action(controller, TRUE)
else
finish_action(controller, FALSE)
/datum/ai_behavior/simple_equip/finish_action(datum/ai_controller/controller, success)
. = ..()
controller.blackboard[BB_FETCH_TARGET] = null
if(!success)
controller.blackboard[BB_FETCH_DELIVER_TO] = null
/datum/ai_behavior/simple_equip/proc/pickup_item(datum/ai_controller/controller, obj/item/target)
var/atom/pawn = controller.pawn
drop_item(controller)
pawn.visible_message(span_notice("[pawn] picks up [target] in [pawn.p_their()] mouth."))
target.forceMove(pawn)
controller.blackboard[BB_SIMPLE_CARRY_ITEM] = target
return TRUE
/datum/ai_behavior/simple_equip/proc/drop_item(datum/ai_controller/controller)
var/obj/item/carried_item = controller.blackboard[BB_SIMPLE_CARRY_ITEM]
if(!carried_item)
return
var/atom/pawn = controller.pawn
pawn.visible_message(span_notice("[pawn] drops [carried_item]."))
carried_item.forceMove(get_turf(pawn))
controller.blackboard[BB_SIMPLE_CARRY_ITEM] = null
return TRUE
/// This behavior involves dropping off a carried item to a specified person (or place)
/datum/ai_behavior/deliver_item
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT
/datum/ai_behavior/deliver_item/perform(delta_time, datum/ai_controller/controller)
. = ..()
var/mob/living/return_target = controller.blackboard[BB_FETCH_DELIVER_TO]
if(!return_target)
finish_action(controller, FALSE)
if(in_range(controller.pawn, return_target))
deliver_item(controller)
finish_action(controller, TRUE)
/datum/ai_behavior/deliver_item/finish_action(datum/ai_controller/controller, success)
. = ..()
controller.blackboard[BB_FETCH_TARGET] = null
controller.blackboard[BB_FETCH_DELIVER_TO] = null
/// Actually drop the fetched item to the target
/datum/ai_behavior/deliver_item/proc/deliver_item(datum/ai_controller/controller)
var/obj/item/carried_item = controller.blackboard[BB_SIMPLE_CARRY_ITEM]
var/atom/movable/return_target = controller.blackboard[BB_FETCH_DELIVER_TO]
if(!carried_item || !return_target)
finish_action(controller, FALSE)
return
if(ismob(return_target))
controller.pawn.visible_message(span_notice("[controller.pawn] delivers [carried_item] at [return_target]'s feet."))
else // not sure how to best phrase this
controller.pawn.visible_message(span_notice("[controller.pawn] delivers [carried_item] to [return_target]."))
carried_item.forceMove(get_turf(return_target))
controller.blackboard[BB_SIMPLE_CARRY_ITEM] = null
return TRUE
/// This behavior involves either eating a snack we can reach, or begging someone holding a snack
/datum/ai_behavior/eat_snack
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT
/datum/ai_behavior/eat_snack/perform(delta_time, datum/ai_controller/controller)
. = ..()
var/obj/item/snack = controller.current_movement_target
if(!istype(snack) || !IS_EDIBLE(snack) || !(isturf(snack.loc) || ishuman(snack.loc)))
finish_action(controller, FALSE)
var/mob/living/living_pawn = controller.pawn
if(!in_range(living_pawn, snack))
return
if(isturf(snack.loc))
snack.attack_animal(living_pawn) // snack attack!
else if(iscarbon(snack.loc) && DT_PROB(10, delta_time))
living_pawn.manual_emote("stares at [snack.loc]'s [snack.name] with a sad puppy-face.")
if(QDELETED(snack)) // we ate it!
finish_action(controller, TRUE)
/// This behavior involves either eating a snack we can reach, or begging someone holding a snack
/datum/ai_behavior/play_dead
behavior_flags = NONE
/datum/ai_behavior/play_dead/perform(delta_time, datum/ai_controller/controller)
. = ..()
var/mob/living/basic/simple_pawn = controller.pawn
if(!istype(simple_pawn))
return
if(!controller.blackboard[BB_DOG_PLAYING_DEAD])
controller.blackboard[BB_DOG_PLAYING_DEAD] = TRUE
simple_pawn.emote("deathgasp", intentional=FALSE)
simple_pawn.icon_state = simple_pawn.icon_dead
if(simple_pawn.basic_mob_flags & FLIP_ON_DEATH)
simple_pawn.transform = simple_pawn.transform.Turn(180)
simple_pawn.set_density(FALSE)
if(DT_PROB(10, delta_time))
finish_action(controller, TRUE)
/datum/ai_behavior/play_dead/finish_action(datum/ai_controller/controller, succeeded)
. = ..()
var/mob/living/basic/simple_pawn = controller.pawn
if(!istype(simple_pawn) || simple_pawn.stat) // imagine actually dying while playing dead. hell, imagine being the kid waiting for your pup to get back up :(
return
controller.blackboard[BB_DOG_PLAYING_DEAD] = FALSE
simple_pawn.visible_message(span_notice("[simple_pawn] springs to [simple_pawn.p_their()] feet, panting excitedly!"))
simple_pawn.icon_state = simple_pawn.icon_living
if(simple_pawn.basic_mob_flags & FLIP_ON_DEATH)
simple_pawn.transform = simple_pawn.transform.Turn(180)
simple_pawn.set_density(initial(simple_pawn.density))
/// This behavior involves either eating a snack we can reach, or begging someone holding a snack
/datum/ai_behavior/harass
/**
* Pursue the target, growl if we're close, and bite if we're adjacent
* Dogs are actually not very aggressive and won't attack unless you approach them
* Adds a floor to the melee damage of the dog, as most pet dogs don't actually have any melee strength
*/
/datum/ai_behavior/basic_melee_attack/dog
action_cooldown = 0.8 SECONDS
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM
required_distance = 3
/datum/ai_behavior/harass/perform(delta_time, datum/ai_controller/controller)
. = ..()
/datum/ai_behavior/basic_melee_attack/dog/perform(delta_time, datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key)
controller.behavior_cooldowns[src] = world.time + action_cooldown
var/mob/living/living_pawn = controller.pawn
if(!istype(living_pawn) || !(isturf(living_pawn.loc) || HAS_TRAIT(living_pawn, TRAIT_AI_BAGATTACK)))
if(!(isturf(living_pawn.loc) || HAS_TRAIT(living_pawn, TRAIT_AI_BAGATTACK))) // Void puppies can attack from inside bags
finish_action(controller, FALSE, target_key, targetting_datum_key, hiding_location_key)
return
var/datum/weakref/harass_ref = controller.blackboard[BB_DOG_HARASS_TARGET]
var/atom/movable/harass_target = harass_ref.resolve()
if(!harass_target || !can_see(living_pawn, harass_target, length=AI_DOG_VISION_RANGE))
finish_action(controller, FALSE)
// Unfortunately going to repeat this check in parent call but what can you do
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(living_pawn, target))
finish_action(controller, FALSE, target_key, targetting_datum_key, hiding_location_key)
return
if(controller.blackboard[BB_DOG_FRIENDS][harass_ref])
living_pawn.visible_message(span_danger("[living_pawn] looks sideways at [harass_target] for a moment, then shakes [living_pawn.p_their()] head and ceases aggression."))
finish_action(controller, FALSE)
if (!in_range(living_pawn, target))
growl_at(living_pawn, target, delta_time)
return
var/mob/living/living_target = harass_target
if(istype(living_target) && (living_target.stat || HAS_TRAIT(living_target, TRAIT_FAKEDEATH)))
finish_action(controller, TRUE)
if(!controller.blackboard[BB_DOG_HARASS_HARM])
paw_harmlessly(living_pawn, target, delta_time)
return
if(!controller.blackboard[BB_DOG_HARASS_FRUSTRATION])
controller.blackboard[BB_DOG_HARASS_FRUSTRATION] = world.time
else if(controller.blackboard[BB_DOG_HARASS_FRUSTRATION] + AI_DOG_HARASS_FRUSTRATE_TIME < world.time) // if we haven't actually bit them in a while, give up
living_pawn.visible_message(span_danger("[living_pawn] yawns and seems to lose interest in harassing [harass_target]."))
finish_action(controller, FALSE)
// Give Ian some teeth
var/old_melee_lower = living_pawn.melee_damage_lower
var/old_melee_upper = living_pawn.melee_damage_upper
living_pawn.melee_damage_lower = max(5, old_melee_lower)
living_pawn.melee_damage_upper = max(10, old_melee_upper)
. = ..() // Bite time
living_pawn.melee_damage_lower = old_melee_lower
living_pawn.melee_damage_upper = old_melee_upper
/// Swat at someone we don't like but won't hurt
/datum/ai_behavior/basic_melee_attack/dog/proc/paw_harmlessly(mob/living/living_pawn, atom/target, delta_time)
if(!DT_PROB(20, delta_time))
return
living_pawn.do_attack_animation(target, ATTACK_EFFECT_DISARM)
playsound(target, 'sound/weapons/thudswoosh.ogg', 50, TRUE, -1)
target.visible_message(span_danger("[living_pawn] paws ineffectually at [target]!"), span_danger("[living_pawn] paws ineffectually at you!"))
// subtypes of this behavior can change behavior for how eager/averse the pawn is to attack the target as opposed to falling back/making noise/getting help
if(in_range(living_pawn, living_target))
attack(controller, living_target)
else if(DT_PROB(15, delta_time))
living_pawn.manual_emote("[pick("barks", "growls", "stares")] menacingly at [harass_target]!")
if(DT_PROB(40, delta_time))
playsound(living_pawn, pick('sound/creatures/dog/growl1.ogg', 'sound/creatures/dog/growl2.ogg'), 50, TRUE, -1)
/datum/ai_behavior/harass/finish_action(datum/ai_controller/controller, succeeded)
. = ..()
controller.blackboard[BB_DOG_HARASS_TARGET] = null
controller.blackboard[BB_DOG_HARASS_FRUSTRATION] = null
/// A proc representing when the mob is pushed to actually attack the target. Again, subtypes can be used to represent different attacks from different animals, or it can be some other generic behavior
/datum/ai_behavior/harass/proc/attack(datum/ai_controller/controller, mob/living/living_target)
var/mob/living/living_pawn = controller.pawn
if(!istype(living_pawn))
/// Let them know we mean business
/datum/ai_behavior/basic_melee_attack/dog/proc/growl_at(mob/living/living_pawn, atom/target, delta_time)
if(!DT_PROB(15, delta_time))
return
controller.blackboard[BB_DOG_HARASS_FRUSTRATION] = world.time
if(controller.blackboard[BB_DOG_HARASS_HARM])
// make sure the pawn gets some temporary strength boost to actually attack the target instead of pathetically nuzzling them.
var/old_melee_lower = living_pawn.melee_damage_lower
var/old_melee_upper = living_pawn.melee_damage_upper
living_pawn.melee_damage_lower = max(5, old_melee_lower)
living_pawn.melee_damage_upper = max(10, old_melee_upper)
living_pawn.UnarmedAttack(living_target, FALSE)
living_pawn.melee_damage_lower = old_melee_lower
living_pawn.melee_damage_upper = old_melee_upper
else
if(prob(20))
living_pawn.do_attack_animation(living_target, ATTACK_EFFECT_DISARM)
playsound(living_target, 'sound/weapons/thudswoosh.ogg', 50, TRUE, -1)
living_target.visible_message(span_danger("[living_pawn] paws ineffectually at [living_target]!"), span_danger("[living_pawn] paws ineffectually at you!"))
living_pawn.manual_emote("[pick("barks", "growls", "stares")] menacingly at [target]!")
if(!DT_PROB(40, delta_time))
return
playsound(living_pawn, pick('sound/creatures/dog/growl1.ogg', 'sound/creatures/dog/growl2.ogg'), 50, TRUE, -1)
+11 -294
View File
@@ -1,320 +1,37 @@
/datum/ai_controller/dog
/datum/ai_controller/basic_controller/dog
blackboard = list(
BB_SIMPLE_CARRY_ITEM = null,
BB_FETCH_TARGET = null,
BB_FETCH_DELIVER_TO = null,
BB_DOG_FRIENDS = list(),
BB_FETCH_IGNORE_LIST = list(),
BB_DOG_ORDER_MODE = DOG_COMMAND_NONE,
BB_DOG_PLAYING_DEAD = FALSE,
BB_DOG_HARASS_TARGET = null,
BB_DOG_HARASS_FRUSTRATION = null,
BB_DOG_HARASS_HARM = TRUE,
BB_VISION_RANGE = AI_DOG_VISION_RANGE,
BB_PET_TARGETTING_DATUM = new /datum/targetting_datum/not_friends(),
)
ai_movement = /datum/ai_movement/jps
ai_movement = /datum/ai_movement/basic_avoidance
idle_behavior = /datum/idle_behavior/idle_dog
planning_subtrees = list(
/datum/ai_planning_subtree/random_speech/dog,
/datum/ai_planning_subtree/dog,
/datum/ai_planning_subtree/pet_planning,
/datum/ai_planning_subtree/dog_harassment,
)
COOLDOWN_DECLARE(heel_cooldown)
COOLDOWN_DECLARE(command_cooldown)
/datum/ai_controller/dog/process(delta_time)
if(ismob(pawn))
var/mob/living/living_pawn = pawn
movement_delay = living_pawn.cached_multiplicative_slowdown
return ..()
/datum/ai_controller/dog/TryPossessPawn(atom/new_pawn)
if(!isliving(new_pawn))
return AI_CONTROLLER_INCOMPATIBLE
RegisterSignal(new_pawn, COMSIG_ATOM_ATTACK_HAND, PROC_REF(on_attack_hand))
RegisterSignal(new_pawn, COMSIG_PARENT_EXAMINE, PROC_REF(on_examined))
RegisterSignal(new_pawn, COMSIG_CLICK_ALT, PROC_REF(check_altclicked))
RegisterSignals(new_pawn, list(COMSIG_LIVING_DEATH, COMSIG_PARENT_QDELETING), PROC_REF(on_death))
RegisterSignal(SSdcs, COMSIG_GLOB_CARBON_THROW_THING, PROC_REF(listened_throw))
return ..() //Run parent at end
/datum/ai_controller/dog/UnpossessPawn(destroy)
var/obj/item/carried_item = blackboard[BB_SIMPLE_CARRY_ITEM]
if(carried_item)
pawn.visible_message(span_danger("[pawn] drops [carried_item]"))
carried_item.forceMove(pawn.drop_location())
blackboard[BB_SIMPLE_CARRY_ITEM] = null
UnregisterSignal(pawn, list(COMSIG_ATOM_ATTACK_HAND, COMSIG_PARENT_EXAMINE, COMSIG_CLICK_ALT, COMSIG_LIVING_DEATH, COMSIG_PARENT_QDELETING))
UnregisterSignal(SSdcs, COMSIG_GLOB_CARBON_THROW_THING)
return ..() //Run parent at end
/datum/ai_controller/dog/able_to_run()
var/mob/living/living_pawn = pawn
if(IS_DEAD_OR_INCAP(living_pawn))
return FALSE
return ..()
/// Someone has thrown something, see if it's someone we care about and start listening to the thrown item so we can see if we want to fetch it when it lands
/datum/ai_controller/dog/proc/listened_throw(datum/source, mob/living/carbon/carbon_thrower)
SIGNAL_HANDLER
if(blackboard[BB_FETCH_TARGET] || blackboard[BB_FETCH_DELIVER_TO] || blackboard[BB_DOG_PLAYING_DEAD]) // we're already busy
return
if(!COOLDOWN_FINISHED(src, heel_cooldown))
return
if(!can_see(pawn, carbon_thrower, length=AI_DOG_VISION_RANGE))
return
var/obj/item/thrown_thing = carbon_thrower.get_active_held_item()
if(!isitem(thrown_thing))
return
if(blackboard[BB_FETCH_IGNORE_LIST][WEAKREF(thrown_thing)])
return
RegisterSignal(thrown_thing, COMSIG_MOVABLE_THROW_LANDED, PROC_REF(listen_throw_land))
/// A throw we were listening to has finished, see if it's in range for us to try grabbing it
/datum/ai_controller/dog/proc/listen_throw_land(obj/item/thrown_thing, datum/thrownthing/throwing_datum)
SIGNAL_HANDLER
UnregisterSignal(thrown_thing, list(COMSIG_PARENT_QDELETING, COMSIG_MOVABLE_THROW_LANDED))
if(!istype(thrown_thing) || !isturf(thrown_thing.loc) || !can_see(pawn, thrown_thing, length=AI_DOG_VISION_RANGE))
return
var/mob/living/living_pawn = pawn
if(IS_DEAD_OR_INCAP(living_pawn))
return
set_movement_target(thrown_thing)
blackboard[BB_FETCH_TARGET] = thrown_thing
blackboard[BB_FETCH_DELIVER_TO] = throwing_datum.thrower
if(living_pawn.buckled)
queue_behavior(/datum/ai_behavior/resist)
queue_behavior(/datum/ai_behavior/fetch)
/// Someone's interacting with us by hand, see if they're being nice or mean
/datum/ai_controller/dog/proc/on_attack_hand(datum/source, mob/living/user)
SIGNAL_HANDLER
var/mob/living/living_pawn = pawn
var/additional_text = HAS_TRAIT(user, TRAIT_NAIVE) ? "It looks like [living_pawn.p_theyre()] sleeping." : "[living_pawn.p_they(capitalized = TRUE)] seem[living_pawn.p_s()] to be dead."
if(living_pawn.stat == DEAD)
to_chat(user, span_warning("Touching [living_pawn], you feel [living_pawn.p_their()] cold skin through the fur. [additional_text]"))
return
if(user.combat_mode)
unfriend(user)
else
if(prob(AI_DOG_PET_FRIEND_PROB))
befriend(user)
// if the dog has something in their mouth that they're not bringing to someone for whatever reason, have them drop it when pet by a friend
var/list/friends = blackboard[BB_DOG_FRIENDS]
if(blackboard[BB_SIMPLE_CARRY_ITEM] && !current_movement_target && friends[WEAKREF(user)])
var/obj/item/carried_item = blackboard[BB_SIMPLE_CARRY_ITEM]
pawn.visible_message(span_danger("[pawn] drops [carried_item] at [user]'s feet!"))
// maybe have a dedicated proc for dropping things
carried_item.forceMove(get_turf(user))
blackboard[BB_SIMPLE_CARRY_ITEM] = null
/// Someone is being nice to us, let's make them a friend!
/datum/ai_controller/dog/proc/befriend(mob/living/new_friend)
var/list/friends = blackboard[BB_DOG_FRIENDS]
var/datum/weakref/friend_ref = WEAKREF(new_friend)
if(friends[friend_ref])
return
if(in_range(pawn, new_friend))
new_friend.visible_message("<b>[pawn]</b> licks at [new_friend] in a friendly manner!", span_notice("[pawn] licks at you in a friendly manner!"))
friends[friend_ref] = TRUE
RegisterSignal(new_friend, COMSIG_MOB_POINTED, PROC_REF(check_point))
RegisterSignal(new_friend, COMSIG_MOB_SAY, PROC_REF(check_verbal_command))
/// Someone is being mean to us, take them off our friends (add actual enemies behavior later)
/datum/ai_controller/dog/proc/unfriend(mob/living/ex_friend)
var/list/friends = blackboard[BB_DOG_FRIENDS]
friends -= WEAKREF(ex_friend)
UnregisterSignal(ex_friend, list(COMSIG_MOB_POINTED, COMSIG_MOB_SAY))
/// Someone is looking at us, if we're currently carrying something then show what it is, and include a message if they're our friend
/datum/ai_controller/dog/proc/on_examined(datum/source, mob/user, list/examine_text)
SIGNAL_HANDLER
var/obj/item/carried_item = blackboard[BB_SIMPLE_CARRY_ITEM]
if(carried_item)
examine_text += span_notice("[pawn.p_they(TRUE)] [pawn.p_are()] carrying [carried_item.get_examine_string(user)] in [pawn.p_their()] mouth.")
if(blackboard[BB_DOG_FRIENDS][WEAKREF(user)])
var/mob/living/living_pawn = pawn
if(!IS_DEAD_OR_INCAP(living_pawn))
examine_text += span_notice("[pawn.p_they(TRUE)] seem[pawn.p_s()] happy to see you!")
/// If we died, drop anything we were carrying
/datum/ai_controller/dog/proc/on_death(mob/living/ol_yeller)
SIGNAL_HANDLER
var/obj/item/carried_item = blackboard[BB_SIMPLE_CARRY_ITEM]
if(!carried_item)
return
ol_yeller.visible_message(span_danger("[ol_yeller] drops [carried_item] as [ol_yeller.p_they()] die[ol_yeller.p_s()]."))
carried_item.forceMove(ol_yeller.drop_location())
blackboard[BB_SIMPLE_CARRY_ITEM] = null
// next section is regarding commands
/// Someone alt clicked us, see if they're someone we should show the radial command menu to
/datum/ai_controller/dog/proc/check_altclicked(datum/source, mob/living/clicker)
SIGNAL_HANDLER
if(!COOLDOWN_FINISHED(src, command_cooldown))
return
if(!istype(clicker) || !blackboard[BB_DOG_FRIENDS][WEAKREF(clicker)])
return
. = COMPONENT_CANCEL_CLICK_ALT
INVOKE_ASYNC(src, PROC_REF(command_radial), clicker)
/// Show the command radial menu
/datum/ai_controller/dog/proc/command_radial(mob/living/clicker)
var/list/commands = list(
COMMAND_HEEL = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow"),
COMMAND_FETCH = image(icon = 'icons/mob/actions/actions_spells.dmi', icon_state = "summons"),
COMMAND_ATTACK = image(icon = 'icons/effects/effects.dmi', icon_state = "bite"),
COMMAND_DIE = image(icon = 'icons/mob/simple/pets.dmi', icon_state = "puppy_dead")
)
var/choice = show_radial_menu(clicker, pawn, commands, custom_check = CALLBACK(src, PROC_REF(check_menu), clicker), tooltips = TRUE)
if(!choice || !check_menu(clicker))
return
set_command_mode(clicker, choice)
/datum/ai_controller/dog/proc/check_menu(mob/user)
if(!istype(user))
CRASH("A non-mob is trying to issue an order to [pawn].")
if(user.incapacitated() || !can_see(user, pawn))
return FALSE
return TRUE
/// One of our friends said something, see if it's a valid command, and if so, take action
/datum/ai_controller/dog/proc/check_verbal_command(mob/speaker, speech_args)
SIGNAL_HANDLER
if(!blackboard[BB_DOG_FRIENDS][WEAKREF(speaker)])
return
if(!COOLDOWN_FINISHED(src, command_cooldown))
return
var/mob/living/living_pawn = pawn
if(IS_DEAD_OR_INCAP(living_pawn))
return
var/spoken_text = speech_args[SPEECH_MESSAGE] // probably should check for full words
var/command
if(findtext(spoken_text, "heel") || findtext(spoken_text, "sit") || findtext(spoken_text, "stay"))
command = COMMAND_HEEL
else if(findtext(spoken_text, "fetch"))
command = COMMAND_FETCH
else if(findtext(spoken_text, "attack") || findtext(spoken_text, "sic"))
command = COMMAND_ATTACK
else if(findtext(spoken_text, "play dead"))
command = COMMAND_DIE
else
return
if(!can_see(pawn, speaker, length=AI_DOG_VISION_RANGE))
return
set_command_mode(speaker, command)
/// Whether we got here via radial menu or a verbal command, this is where we actually process what our new command will be
/datum/ai_controller/dog/proc/set_command_mode(mob/commander, command)
COOLDOWN_START(src, command_cooldown, AI_DOG_COMMAND_COOLDOWN)
switch(command)
// heel: stop what you're doing, relax and try not to do anything for a little bit
if(COMMAND_HEEL)
pawn.visible_message(span_notice("[pawn]'s ears prick up at [commander]'s command, and [pawn.p_they()] sit[pawn.p_s()] down obediently, awaiting further orders."))
blackboard[BB_DOG_ORDER_MODE] = DOG_COMMAND_NONE
COOLDOWN_START(src, heel_cooldown, AI_DOG_HEEL_DURATION)
CancelActions()
// fetch: whatever the commander points to, try and bring it back
if(COMMAND_FETCH)
pawn.visible_message(span_notice("[pawn]'s ears prick up at [commander]'s command, and [pawn.p_they()] bounce[pawn.p_s()] slightly in anticipation."))
blackboard[BB_DOG_ORDER_MODE] = DOG_COMMAND_FETCH
// attack: harass whoever the commander points to
if(COMMAND_ATTACK)
pawn.visible_message(span_danger("[pawn]'s ears prick up at [commander]'s command, and [pawn.p_they()] growl[pawn.p_s()] intensely.")) // imagine getting intimidated by a corgi
blackboard[BB_DOG_ORDER_MODE] = DOG_COMMAND_ATTACK
if(COMMAND_DIE)
blackboard[BB_DOG_ORDER_MODE] = DOG_COMMAND_NONE
CancelActions()
queue_behavior(/datum/ai_behavior/play_dead)
/// Someone we like is pointing at something, see if it's something we might want to interact with (like if they might want us to fetch something for them)
/datum/ai_controller/dog/proc/check_point(mob/pointing_friend, atom/movable/pointed_movable)
SIGNAL_HANDLER
var/mob/living/living_pawn = pawn
if(IS_DEAD_OR_INCAP(living_pawn))
return
if(!COOLDOWN_FINISHED(src, command_cooldown))
return
if(pointed_movable == pawn || blackboard[BB_FETCH_TARGET] || !istype(pointed_movable) || blackboard[BB_DOG_ORDER_MODE] == DOG_COMMAND_NONE) // busy or no command
return
if(!can_see(pawn, pointing_friend, length=AI_DOG_VISION_RANGE) || !can_see(pawn, pointed_movable, length=AI_DOG_VISION_RANGE))
return
COOLDOWN_START(src, command_cooldown, AI_DOG_COMMAND_COOLDOWN)
switch(blackboard[BB_DOG_ORDER_MODE])
if(DOG_COMMAND_FETCH)
if(!isitem(pointed_movable) || pointed_movable.anchored)
return
var/obj/item/pointed_item = pointed_movable
if(pointed_item.obj_flags & ABSTRACT)
return
pawn.visible_message(span_notice("[pawn] follows [pointing_friend]'s gesture towards [pointed_movable] and barks excitedly!"))
set_movement_target(pointed_movable)
blackboard[BB_FETCH_TARGET] = pointed_movable
blackboard[BB_FETCH_DELIVER_TO] = pointing_friend
if(living_pawn.buckled)
queue_behavior(/datum/ai_behavior/resist)//in case they are in bed or something
queue_behavior(/datum/ai_behavior/fetch)
if(DOG_COMMAND_ATTACK)
pawn.visible_message(span_notice("[pawn] follows [pointing_friend]'s gesture towards [pointed_movable] and growls intensely!"))
set_movement_target(pointed_movable)
blackboard[BB_DOG_HARASS_TARGET] = WEAKREF(pointed_movable)
blackboard[BB_DOG_HARASS_HARM] = TRUE
if(living_pawn.buckled)
queue_behavior(/datum/ai_behavior/resist)//in case they are in bed or something
queue_behavior(/datum/ai_behavior/harass)
/**
* Same thing but with make tiny corgis and use access cards.
*/
/datum/ai_controller/dog/corgi
/datum/ai_controller/basic_controller/dog/corgi
blackboard = list(
BB_SIMPLE_CARRY_ITEM = null,
BB_FETCH_TARGET = null,
BB_FETCH_DELIVER_TO = null,
BB_DOG_FRIENDS = list(),
BB_FETCH_IGNORE_LIST = list(),
BB_DOG_ORDER_MODE = DOG_COMMAND_NONE,
BB_DOG_PLAYING_DEAD = FALSE,
BB_DOG_HARASS_TARGET = null,
BB_DOG_HARASS_FRUSTRATION = null,
BB_DOG_HARASS_HARM = TRUE,
BB_VISION_RANGE = AI_DOG_VISION_RANGE,
BB_PET_TARGETTING_DATUM = new /datum/targetting_datum/not_friends(),
BB_BABIES_PARTNER_TYPES = list(/mob/living/basic/pet/dog),
BB_BABIES_CHILD_TYPES = list(/mob/living/basic/pet/dog/corgi/puppy = 95, /mob/living/basic/pet/dog/corgi/puppy/void = 5),
)
planning_subtrees = list(
/datum/ai_planning_subtree/random_speech/dog,
/datum/ai_planning_subtree/make_babies,
/datum/ai_planning_subtree/dog,
/datum/ai_planning_subtree/make_babies, // Ian WILL prioritise sex over following your instructions
/datum/ai_planning_subtree/pet_planning,
/datum/ai_planning_subtree/dog_harassment,
)
/datum/ai_controller/dog/corgi/get_access()
/datum/ai_controller/basic_controller/dog/corgi/get_access()
var/mob/living/basic/pet/dog/corgi/corgi_pawn = pawn
if(!istype(corgi_pawn))
return
+31 -46
View File
@@ -1,52 +1,37 @@
/datum/ai_planning_subtree/dog
COOLDOWN_DECLARE(heel_cooldown)
COOLDOWN_DECLARE(reset_ignore_cooldown)
/// Find someone we don't like and annoy them
/datum/ai_planning_subtree/dog_harassment
/datum/ai_planning_subtree/dog/SelectBehaviors(datum/ai_controller/dog/controller, delta_time)
var/mob/living/living_pawn = controller.pawn
// occasionally reset our ignore list
if(COOLDOWN_FINISHED(src, reset_ignore_cooldown) && length(controller.blackboard[BB_FETCH_IGNORE_LIST]))
COOLDOWN_START(src, reset_ignore_cooldown, AI_FETCH_IGNORE_DURATION)
controller.blackboard[BB_FETCH_IGNORE_LIST] = list()
// if we were just ordered to heel, chill out for a bit
if(!COOLDOWN_FINISHED(src, heel_cooldown))
/datum/ai_planning_subtree/dog_harassment/SelectBehaviors(datum/ai_controller/controller, delta_time)
if(!DT_PROB(10, delta_time))
return
controller.queue_behavior(/datum/ai_behavior/find_hated_dog_target, BB_DOG_HARASS_TARGET, BB_PET_TARGETTING_DATUM)
var/datum/weakref/weak_target = controller.blackboard[BB_DOG_HARASS_TARGET]
var/atom/harass_target = weak_target?.resolve()
if (isnull(harass_target))
return
// if we're not already carrying something and we have a fetch target (and we're not already doing something with it), see if we can eat/equip it
if(!controller.blackboard[BB_SIMPLE_CARRY_ITEM] && controller.blackboard[BB_FETCH_TARGET])
var/atom/movable/interact_target = controller.blackboard[BB_FETCH_TARGET]
if(in_range(living_pawn, interact_target) && (isturf(interact_target.loc)))
controller.set_movement_target(type, interact_target)
if(IS_EDIBLE(interact_target))
controller.queue_behavior(/datum/ai_behavior/eat_snack)
else if(isitem(interact_target))
controller.queue_behavior(/datum/ai_behavior/simple_equip)
else
controller.blackboard[BB_FETCH_TARGET] = null
controller.blackboard[BB_FETCH_DELIVER_TO] = null
return
controller.queue_behavior(/datum/ai_behavior/basic_melee_attack/dog, BB_DOG_HARASS_TARGET, BB_PET_TARGETTING_DATUM)
return SUBTREE_RETURN_FINISH_PLANNING
// if we're carrying something and we have a destination to deliver it, do that
if(controller.blackboard[BB_SIMPLE_CARRY_ITEM] && controller.blackboard[BB_FETCH_DELIVER_TO])
var/atom/return_target = controller.blackboard[BB_FETCH_DELIVER_TO]
if(!can_see(controller.pawn, return_target, length=AI_DOG_VISION_RANGE))
// if the return target isn't in sight, we'll just forget about it and carry the thing around
controller.blackboard[BB_FETCH_DELIVER_TO] = null
return
controller.set_movement_target(type, return_target)
controller.queue_behavior(/datum/ai_behavior/deliver_item)
return
/datum/ai_behavior/find_hated_dog_target
if(DT_PROB(10, delta_time))
for(var/mob/living/iter_living in oview(2, living_pawn))
if(iter_living.stat != CONSCIOUS || !HAS_TRAIT(iter_living, TRAIT_HATED_BY_DOGS))
continue
/datum/ai_behavior/find_hated_dog_target/setup(datum/ai_controller/controller, target_key, targetting_datum_key)
. = ..()
var/mob/living/dog = controller.pawn
var/datum/targetting_datum/targetting_datum = controller.blackboard[targetting_datum_key]
for(var/mob/living/iter_living in oview(2, dog))
if(iter_living.stat != CONSCIOUS || !HAS_TRAIT(iter_living, TRAIT_HATED_BY_DOGS))
continue
if(!targetting_datum.can_attack(dog, iter_living))
continue
living_pawn.audible_message(span_warning("[living_pawn] growls at [iter_living], seemingly annoyed by [iter_living.p_their()] presence."), hearing_distance = COMBAT_MESSAGE_RANGE)
controller.set_movement_target(iter_living)
controller.blackboard[BB_DOG_HARASS_TARGET] = WEAKREF(iter_living)
controller.blackboard[BB_DOG_HARASS_HARM] = FALSE
controller.queue_behavior(/datum/ai_behavior/harass)
return
dog.audible_message(span_warning("[dog] growls at [iter_living], seemingly annoyed by [iter_living.p_their()] presence."), hearing_distance = COMBAT_MESSAGE_RANGE)
controller.blackboard[target_key] = WEAKREF(iter_living)
controller.blackboard[BB_DOG_HARASS_HARM] = FALSE
return TRUE
controller.blackboard[target_key] = null
/datum/ai_behavior/find_hated_dog_target/perform(delta_time, datum/ai_controller/controller, target_key)
. = ..()
finish_action(controller, TRUE)
+4 -7
View File
@@ -1,16 +1,13 @@
///Dog specific idle behavior.
/datum/idle_behavior/idle_dog/perform_idle_behavior(delta_time, datum/ai_controller/dog/controller)
/datum/idle_behavior/idle_dog/perform_idle_behavior(delta_time, datum/ai_controller/basic_controller/dog/controller)
var/mob/living/living_pawn = controller.pawn
if(!isturf(living_pawn.loc) || living_pawn.pulledby)
return
// if we were just ordered to heel, chill out for a bit
if(!COOLDOWN_FINISHED(controller, heel_cooldown))
return
var/datum/weakref/weak_item = controller.blackboard[BB_SIMPLE_CARRY_ITEM]
var/obj/item/carry_item = weak_item?.resolve()
// if we're just ditzing around carrying something, occasionally print a message so people know we have something
if(controller.blackboard[BB_SIMPLE_CARRY_ITEM] && DT_PROB(5, delta_time))
var/obj/item/carry_item = controller.blackboard[BB_SIMPLE_CARRY_ITEM]
if(carry_item && DT_PROB(5, delta_time))
living_pawn.visible_message(span_notice("[living_pawn] gently teethes on \the [carry_item] in [living_pawn.p_their()] mouth."), vision_distance=COMBAT_MESSAGE_RANGE)
// Custom movement rate, for old corgis, etc.
+1 -1
View File
@@ -74,7 +74,7 @@ Behavior that's still missing from this component that original food items had t
/datum/component/edible/RegisterWithParent()
RegisterSignal(parent, COMSIG_PARENT_EXAMINE, PROC_REF(examine))
RegisterSignal(parent, COMSIG_ATOM_ATTACK_ANIMAL, PROC_REF(UseByAnimal))
RegisterSignals(parent, COMSIG_ATOM_ATTACK_ANIMAL, PROC_REF(UseByAnimal))
RegisterSignal(parent, COMSIG_ATOM_CHECKPARTS, PROC_REF(OnCraft))
RegisterSignal(parent, COMSIG_ATOM_CREATEDBY_PROCESSING, PROC_REF(OnProcessed))
RegisterSignal(parent, COMSIG_FOOD_INGREDIENT_ADDED, PROC_REF(edible_ingredient_added))
@@ -0,0 +1,116 @@
/**
* # Pet Command: Fetch
* Watch for someone throwing or pointing at something and then go get it and bring it back.
* If it's food we might eat it instead.
*/
/datum/pet_command/point_targetting/fetch
command_name = "Fetch"
command_desc = "Command your pet to retrieve something you throw or point at."
radial_icon = 'icons/mob/actions/actions_spells.dmi'
radial_icon_state = "summons"
speech_commands = list("fetch")
command_feedback = "bounces"
pointed_reaction = "with great interest"
/// If true, this command will trigger if the pet sees a friend throw any item, if they're not doing anything else
var/trigger_on_throw = TRUE
/// If true, this is a poorly trained pet who will eat food you throw instead of bringing it back
var/will_eat_targets = TRUE
/datum/pet_command/point_targetting/fetch/New(mob/living/parent)
. = ..()
parent.AddElement(/datum/element/ai_held_item) // We don't remove this on destroy because they might still be holding something
/datum/pet_command/point_targetting/fetch/add_new_friend(mob/living/tamer)
. = ..()
RegisterSignal(tamer, COMSIG_MOB_THROW, PROC_REF(listened_throw))
/datum/pet_command/point_targetting/fetch/remove_friend(mob/living/unfriended)
. = ..()
UnregisterSignal(unfriended, COMSIG_MOB_THROW)
/// A friend has thrown something, if we're listening or at least not busy then go get it
/datum/pet_command/point_targetting/fetch/proc/listened_throw(mob/living/carbon/thrower)
SIGNAL_HANDLER
var/mob/living/parent = weak_parent.resolve()
if (!parent)
return
var/list/blackboard = parent.ai_controller.blackboard
if (!trigger_on_throw && !blackboard[BB_ACTIVE_PET_COMMAND])
return // We don't have a command and we only listen to commands
if (blackboard[BB_ACTIVE_PET_COMMAND] && blackboard[BB_ACTIVE_PET_COMMAND] != WEAKREF(src))
return // We have a command and it's not this one
if (blackboard[BB_CURRENT_PET_TARGET] || blackboard[BB_FETCH_DELIVER_TO])
return // We're already very fetching
if (!can_see(parent, thrower, length = sense_radius))
return // Can't see it
var/obj/item/thrown_thing = thrower.get_active_held_item()
if (!isitem(thrown_thing))
return
if (blackboard[BB_FETCH_IGNORE_LIST] && blackboard[BB_FETCH_IGNORE_LIST][WEAKREF(thrown_thing)])
return // We're ignoring it already
RegisterSignal(thrown_thing, COMSIG_MOVABLE_THROW_LANDED, PROC_REF(listen_throw_land))
/// A throw we were listening to has finished, see if it's in range for us to try grabbing it
/datum/pet_command/point_targetting/fetch/proc/listen_throw_land(obj/item/thrown_thing, datum/thrownthing/throwing_datum)
SIGNAL_HANDLER
UnregisterSignal(thrown_thing, COMSIG_MOVABLE_THROW_LANDED)
var/mob/living/parent = weak_parent.resolve()
if (!parent)
return
if (!isturf(thrown_thing.loc))
return
if (!can_see(parent, thrown_thing, length = sense_radius))
return
try_activate_command(throwing_datum.thrower)
set_command_target(parent, thrown_thing)
parent.ai_controller.blackboard[BB_FETCH_DELIVER_TO] = WEAKREF(throwing_datum.thrower)
// Don't try and fetch turfs or anchored objects if someone points at them
/datum/pet_command/point_targetting/fetch/look_for_target(mob/living/pointing_friend, obj/item/pointed_atom)
if (!istype(pointed_atom))
return FALSE
if (pointed_atom.anchored)
return FALSE
. = ..()
if (!.)
return FALSE
var/mob/living/parent = weak_parent.resolve()
parent.ai_controller.blackboard[BB_FETCH_DELIVER_TO] = WEAKREF(pointing_friend)
// Finally, plan our actions
/datum/pet_command/point_targetting/fetch/execute_action(datum/ai_controller/controller)
controller.queue_behavior(/datum/ai_behavior/forget_failed_fetches)
var/datum/weakref/weak_target = controller.blackboard[BB_CURRENT_PET_TARGET]
var/atom/target = weak_target?.resolve()
// We got something to fetch so go fetch it
if (target)
if (get_dist(controller.pawn, target) > 1) // We're not there yet
controller.queue_behavior(/datum/ai_behavior/fetch_seek, BB_CURRENT_PET_TARGET, BB_FETCH_DELIVER_TO)
return SUBTREE_RETURN_FINISH_PLANNING
// If mobs could attack food you would branch here to call `eat_fetched_snack`, however that's a task for the future
controller.queue_behavior(/datum/ai_behavior/pick_up_item, BB_CURRENT_PET_TARGET, BB_SIMPLE_CARRY_ITEM)
return SUBTREE_RETURN_FINISH_PLANNING
var/datum/weakref/carried_ref = controller.blackboard[BB_SIMPLE_CARRY_ITEM]
var/obj/item/carried_item = carried_ref?.resolve()
if (!carried_item)
return
var/datum/weakref/delivery_ref = controller.blackboard[BB_FETCH_DELIVER_TO]
var/atom/delivery_target = delivery_ref?.resolve()
if (!delivery_target || !can_see(controller.pawn, delivery_target, sense_radius))
// We don't know where to return this to so we're just going to keep it
controller.blackboard[BB_ACTIVE_PET_COMMAND] = null
return
// We got something to deliver and someone to deliver it to
controller.queue_behavior(/datum/ai_behavior/deliver_fetched_item, BB_FETCH_DELIVER_TO, BB_SIMPLE_CARRY_ITEM)
return SUBTREE_RETURN_FINISH_PLANNING
@@ -28,11 +28,14 @@
/datum/component/obeys_commands/RegisterWithParent()
RegisterSignal(parent, COMSIG_LIVING_BEFRIENDED, PROC_REF(add_friend))
RegisterSignal(parent, COMSIG_LIVING_UNFRIENDED, PROC_REF(remove_friend))
RegisterSignal(parent, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine))
RegisterSignal(parent, COMSIG_CLICK_ALT, PROC_REF(display_menu))
/datum/component/obeys_commands/UnregisterFromParent()
UnregisterSignal(parent, list(COMSIG_LIVING_BEFRIENDED, COMSIG_CLICK_ALT))
UnregisterSignal(parent, list(COMSIG_LIVING_BEFRIENDED, COMSIG_LIVING_UNFRIENDED, COMSIG_PARENT_EXAMINE, COMSIG_CLICK_ALT))
/// Add someone to our friends list
/datum/component/obeys_commands/proc/add_friend(datum/source, mob/living/new_friend)
SIGNAL_HANDLER
@@ -40,6 +43,26 @@
var/datum/pet_command/command = available_commands[command_name]
INVOKE_ASYNC(command, TYPE_PROC_REF(/datum/pet_command, add_new_friend), new_friend)
/// Remove someone from our friends list
/datum/component/obeys_commands/proc/remove_friend(datum/source, mob/living/old_friend)
SIGNAL_HANDLER
for (var/command_name as anything in available_commands)
var/datum/pet_command/command = available_commands[command_name]
INVOKE_ASYNC(command, TYPE_PROC_REF(/datum/pet_command, remove_friend), old_friend)
/// Add a note about whether they will follow the instructions of the inspecting mob
/datum/component/obeys_commands/proc/on_examine(mob/living/source, mob/user, list/examine_list)
SIGNAL_HANDLER
if (IS_DEAD_OR_INCAP(source))
return
if (!source.ai_controller)
return
var/list/friends_list = source.ai_controller.blackboard[BB_FRIENDS_LIST]
if (!friends_list || !friends_list[WEAKREF(user)])
return
examine_list += span_notice("[source.p_they(capitalized = TRUE)] seem[source.p_s()] happy to see you!")
/// Displays a radial menu of commands
/datum/component/obeys_commands/proc/display_menu(datum/source, mob/living/clicker)
@@ -61,7 +84,10 @@
var/list/radial_options = list()
for (var/command_name as anything in available_commands)
var/datum/pet_command/command = available_commands[command_name]
radial_options += command.provide_radial_data()
var/datum/radial_menu_choice/choice = command.provide_radial_data()
if (!choice)
continue
radial_options += choice
var/pick = show_radial_menu(clicker, clicker, radial_options, require_near = TRUE, tooltips = TRUE)
if (!pick)
@@ -6,18 +6,18 @@
/datum/pet_command
/// Weak reference to who follows this command
var/datum/weakref/weak_parent
/// Key for command applied when you receive an order
var/command_key = PET_COMMAND_NONE
/// Unique name used for radial selection, should not be shared with other commands on one mob
var/command_name
/// Description to display in radial menu
var/command_desc
/// If true, command will not appear in radial menu and can only be accessed through speech
var/hidden = FALSE
/// Icon to display in radial menu
var/icon/radial_icon
/// Icon state to display in radial menu
var/radial_icon_state
/// Speech strings to listen out for
var/list/speech_commands
var/list/speech_commands = list()
/// Shown above the mob's head when it hears you
var/command_feedback
/// How close a mob needs to be to a target to respond to a command
@@ -26,12 +26,15 @@
/datum/pet_command/New(mob/living/parent)
. = ..()
weak_parent = WEAKREF(parent)
parent.ai_controller.blackboard[command_key] = WEAKREF(src)
/// Register a new guy we want to listen to
/datum/pet_command/proc/add_new_friend(mob/living/tamer)
RegisterSignal(tamer, COMSIG_MOB_SAY, PROC_REF(respond_to_command))
/// Stop listening to a guy
/datum/pet_command/proc/remove_friend(mob/living/unfriended)
UnregisterSignal(unfriended, COMSIG_MOB_SAY)
/// Respond to something that one of our friends has asked us to do
/datum/pet_command/proc/respond_to_command(mob/living/speaker, speech_args)
SIGNAL_HANDLER
@@ -65,7 +68,7 @@
return
if (IS_DEAD_OR_INCAP(parent)) // Probably can't hear them if we're dead
return
if (parent.ai_controller.blackboard[BB_ACTIVE_PET_COMMAND] == command_key) // We're already doing it
if (parent.ai_controller.blackboard[BB_ACTIVE_PET_COMMAND] == WEAKREF(src)) // We're already doing it
return
set_command_active(parent, commander)
@@ -74,7 +77,7 @@
set_command_target(parent, null)
parent.ai_controller.CancelActions() // Stop whatever you're doing and do this instead
parent.ai_controller.blackboard[BB_ACTIVE_PET_COMMAND] = command_key
parent.ai_controller.blackboard[BB_ACTIVE_PET_COMMAND] = WEAKREF(src)
if (command_feedback)
parent.balloon_alert_to_viewers("[command_feedback]") // If we get a nicer runechat way to do this, refactor this
@@ -84,6 +87,8 @@
/// Provide information about how to display this command in a radial menu
/datum/pet_command/proc/provide_radial_data()
if (hidden)
return
var/datum/radial_menu_choice/choice = new()
choice.name = command_name
choice.image = icon(icon = radial_icon, icon_state = radial_icon_state)
@@ -117,27 +122,31 @@
. = ..()
RegisterSignal(tamer, COMSIG_MOB_POINTED, PROC_REF(look_for_target))
/datum/pet_command/point_targetting/remove_friend(mob/living/unfriended)
. = ..()
UnregisterSignal(unfriended, COMSIG_MOB_POINTED)
/// Target the pointed atom for actions
/datum/pet_command/point_targetting/proc/look_for_target(mob/living/friend, atom/pointed_atom)
SIGNAL_HANDLER
var/mob/living/parent = weak_parent.resolve()
if (!parent)
return
return FALSE
if (!parent.ai_controller)
return
return FALSE
if (IS_DEAD_OR_INCAP(parent))
return
if (parent.ai_controller.blackboard[BB_ACTIVE_PET_COMMAND] != command_key) // We're not listening right now
return
return FALSE
if (parent.ai_controller.blackboard[BB_ACTIVE_PET_COMMAND] != WEAKREF(src)) // We're not listening right now
return FALSE
if (parent.ai_controller.blackboard[BB_CURRENT_PET_TARGET] == WEAKREF(pointed_atom)) // That's already our target
return
return FALSE
if (!can_see(parent, pointed_atom, sense_radius))
return
return FALSE
parent.ai_controller.CancelActions()
// Deciding if they can actually do anything with this target is the behaviour's job
set_command_target(parent, pointed_atom)
// These are usually hostile actions so should have a record in chat
parent.visible_message(span_warning("[parent] follows [friend]'s gesture towards [pointed_atom] and [pointed_reaction]!"))
parent.visible_message(span_warning("[parent] follows [friend]'s gesture towards [pointed_atom] [pointed_reaction]!"))
return TRUE
@@ -7,9 +7,8 @@
/datum/pet_command/idle
command_name = "Stay"
command_desc = "Command your pet to stay idle in this location."
radial_icon = 'icons/testing/turf_analysis.dmi'
radial_icon_state = "red_arrow"
command_key = PET_COMMAND_IDLE
radial_icon = 'icons/obj/objects.dmi'
radial_icon_state = "dogbed"
speech_commands = list("sit", "stay", "stop")
command_feedback = "sits"
@@ -25,11 +24,11 @@
command_desc = "Allow your pet to resume its natural behaviours."
radial_icon = 'icons/mob/actions/actions_spells.dmi'
radial_icon_state = "repulse"
command_key = PET_COMMAND_NONE
speech_commands = list("free", "loose")
command_feedback = "relaxes"
/datum/pet_command/free/execute_action(datum/ai_controller/controller)
controller.blackboard[BB_ACTIVE_PET_COMMAND] = null
return // Just move on to the next planning subtree.
/**
@@ -39,9 +38,8 @@
/datum/pet_command/follow
command_name = "Follow"
command_desc = "Command your pet to accompany you."
radial_icon = 'icons/mob/actions/actions_spells.dmi'
radial_icon_state = "summons"
command_key = PET_COMMAND_FOLLOW
radial_icon = 'icons/testing/turf_analysis.dmi'
radial_icon_state = "red_arrow"
speech_commands = list("heel", "follow")
/datum/pet_command/follow/set_command_active(mob/living/parent, mob/living/commander)
@@ -52,6 +50,52 @@
controller.queue_behavior(/datum/ai_behavior/pet_follow_friend, BB_CURRENT_PET_TARGET)
return SUBTREE_RETURN_FINISH_PLANNING
/**
* # Pet Command: Play Dead
* Pretend to be dead for a random period of time
*/
/datum/pet_command/play_dead
command_name = "Play Dead"
command_desc = "Play a macabre trick."
radial_icon = 'icons/mob/simple/pets.dmi'
radial_icon_state = "puppy_dead"
speech_commands = list("play dead") // Don't get too creative here, people talk about dying pretty often
/datum/pet_command/play_dead/execute_action(datum/ai_controller/controller)
controller.queue_behavior(/datum/ai_behavior/play_dead)
return SUBTREE_RETURN_FINISH_PLANNING
/**
* # Pet Command: Good Boy
* React if complimented
*/
/datum/pet_command/good_boy
command_name = "Good Boy"
command_desc = "Give your pet a compliment."
hidden = TRUE
/datum/pet_command/good_boy/New(mob/living/parent)
. = ..()
speech_commands += "good [parent.name]"
switch (parent.gender)
if (MALE)
speech_commands += "good boy"
return
if (FEMALE)
speech_commands += "good girl"
return
// If we get past this point someone has finally added a non-binary dog
/datum/pet_command/good_boy/execute_action(datum/ai_controller/controller)
controller.blackboard[BB_ACTIVE_PET_COMMAND] = null
var/mob/living/parent = weak_parent.resolve()
if (!parent)
return SUBTREE_RETURN_FINISH_PLANNING
new /obj/effect/temp_visual/heart(parent.loc)
parent.emote("spin")
return SUBTREE_RETURN_FINISH_PLANNING
/**
* # Pet Command: Attack
* Tells a pet to chase and bite the next thing you point at
@@ -62,10 +106,9 @@
radial_icon = 'icons/effects/effects.dmi'
radial_icon_state = "bite"
command_key = PET_COMMAND_ATTACK
speech_commands = list("attack", "sic", "kill")
command_feedback = "growl"
pointed_reaction = "growls"
pointed_reaction = "and growls"
/// Balloon alert to display if providing an invalid target
var/refuse_reaction = "shakes head"
/// Attack behaviour to use, generally you will want to override this to add some kind of cooldown
@@ -105,10 +148,9 @@
command_desc = "Command your pet to use one of its special skills on something that you point out to it."
radial_icon = 'icons/mob/actions/actions_spells.dmi'
radial_icon_state = "projectile"
command_key = PET_COMMAND_USE_ABILITY
speech_commands = list("shoot", "blast", "cast")
command_feedback = "growl"
pointed_reaction = "growls"
pointed_reaction = "and growls"
/// Blackboard key where a reference to some kind of mob ability is stored
var/pet_ability_key
+71
View File
@@ -0,0 +1,71 @@
/**
* # AI Held Item Element
*
* Manages holding an item for a mob which doesn't have hands but needs to for AI purposes.
*/
/datum/element/ai_held_item
/datum/element/ai_held_item/Attach(datum/target)
. = ..()
if (!isliving(target))
return ELEMENT_INCOMPATIBLE
var/mob/living/living_target = target
if (!living_target.ai_controller)
return ELEMENT_INCOMPATIBLE // If there's no blackboard this would need to be a component
RegisterSignal(target, COMSIG_ATOM_ATTACK_HAND, PROC_REF(on_click))
RegisterSignal(target, COMSIG_ATOM_EXITED, PROC_REF(atom_exited))
RegisterSignal(target, COMSIG_PARENT_EXAMINE, PROC_REF(on_examined))
RegisterSignals(target, list(COMSIG_PARENT_QDELETING, COMSIG_LIVING_DEATH), PROC_REF(on_death))
/// Returns the item held in a mob's blackboard, if it has one
/datum/element/ai_held_item/proc/get_held_item(mob/living/source)
var/datum/weakref/weak_item = source.ai_controller.blackboard[BB_SIMPLE_CARRY_ITEM]
return weak_item?.resolve()
/// Someone's interacting with us by hand, if we have an item and like them we'll hand it over
/datum/element/ai_held_item/proc/on_click(mob/living/source, mob/living/user)
SIGNAL_HANDLER
if (user.combat_mode)
return
var/list/friends = source.ai_controller.blackboard[BB_FRIENDS_LIST]
if (!friends || !friends[WEAKREF(user)])
return // We don't care about this bozo
var/obj/item/carried_item = get_held_item(source)
if (!carried_item)
return
source.visible_message(span_danger("[source] drops [carried_item] at [user]'s feet!"))
carried_item.forceMove(get_turf(user))
source.ai_controller.blackboard[BB_SIMPLE_CARRY_ITEM] = null
/// If our held item is removed from our atom then take it off the blackboard
/datum/element/ai_held_item/proc/atom_exited(mob/living/source, atom/movable/gone)
SIGNAL_HANDLER
var/obj/item/carried_item = get_held_item(source)
if (carried_item == gone)
source.ai_controller.blackboard[BB_SIMPLE_CARRY_ITEM] = null
/// Report that we're holding an item.
/datum/element/ai_held_item/proc/on_examined(mob/living/source, mob/user, list/examine_text)
SIGNAL_HANDLER
var/obj/item/carried_item = get_held_item(source)
if (!carried_item)
return
examine_text += span_notice("[source.p_they(TRUE)] [source.p_are()] carrying [carried_item.get_examine_string(user)].")
/// If we died, drop anything we were carrying
/datum/element/ai_held_item/proc/on_death(mob/living/ol_yeller)
SIGNAL_HANDLER
var/obj/item/carried_item = get_held_item(ol_yeller)
if(!carried_item)
return
ol_yeller.visible_message(span_danger("[ol_yeller] drops [carried_item] as [ol_yeller.p_they()] die[ol_yeller.p_s()]."))
carried_item.forceMove(ol_yeller.drop_location())
ol_yeller.ai_controller.blackboard[BB_SIMPLE_CARRY_ITEM] = null
+77
View File
@@ -0,0 +1,77 @@
#define BEFRIEND_REPLACE_KEY_SOURCE "%SOURCE%"
#define BEFRIEND_REPLACE_KEY_TARGET "%TARGET%"
/**
* # Befriend Petting
*
* Element which makes a mob befriend you if you pet it enough, and unfriend you if you hurt it.
*/
/datum/element/befriend_petting
element_flags = ELEMENT_BESPOKE
argument_hash_start_idx = 2
/// Chance of success per interaction.
var/befriend_chance
/// Message to print if we gain a friend. String %SOURCE% and %TARGET% are replaced by names if present.
var/tamed_reaction
/// Message to print if we remove a friend. String %SOURCE% and %TARGET% are replaced by names if present.
var/untamed_reaction
/datum/element/befriend_petting/Attach(datum/target, befriend_chance = AI_DOG_PET_FRIEND_PROB, tamed_reaction, untamed_reaction)
. = ..()
if (!isliving(target))
return ELEMENT_INCOMPATIBLE
src.befriend_chance = befriend_chance
src.tamed_reaction = tamed_reaction
src.untamed_reaction = untamed_reaction
target.AddElement(/datum/element/ai_retaliate)
RegisterSignal(target, COMSIG_ATOM_ATTACK_HAND, PROC_REF(on_click))
RegisterSignal(target, COMSIG_ATOM_WAS_ATTACKED, PROC_REF(on_hurt))
/datum/element/befriend_petting/Detach(datum/target)
. = ..()
UnregisterSignal(target, list(COMSIG_ATOM_ATTACK_HAND, COMSIG_ATOM_WAS_ATTACKED))
/// If it's a nice touch make friends
/datum/element/befriend_petting/proc/on_click(mob/living/owner, mob/living/user)
SIGNAL_HANDLER
if (!istype(user))
return
if (user.combat_mode)
return // We'll deal with this later
if (owner.stat == DEAD)
var/additional_text = HAS_TRAIT(user, TRAIT_NAIVE) || HAS_TRAIT(user.mind, TRAIT_NAIVE) ? "It looks like [owner.p_theyre()] sleeping." : "[owner.p_they(capitalized = TRUE)] seem[owner.p_s()] to be dead."
to_chat(user, span_warning("[owner] feels cold to the touch. [additional_text]"))
return
if (owner.stat != CONSCIOUS)
return
if (!prob(befriend_chance))
return
if (!owner.befriend(user))
return
if (!tamed_reaction)
return
var/display_message = replacetext(tamed_reaction, BEFRIEND_REPLACE_KEY_SOURCE, "[owner]")
display_message = replacetext(display_message, BEFRIEND_REPLACE_KEY_TARGET, "[user]")
owner.visible_message(span_notice(display_message))
/// If it's a bad touch make enemies
/datum/element/befriend_petting/proc/on_hurt(mob/living/owner, atom/attacker)
SIGNAL_HANDLER
if (owner.stat != CONSCIOUS)
return
if (!isliving(attacker))
return
var/mob/living/living_attacker = attacker
if (!owner.unfriend(living_attacker))
return
if (!untamed_reaction)
return
var/display_message = replacetext(untamed_reaction, BEFRIEND_REPLACE_KEY_SOURCE, "[owner]")
display_message = replacetext(display_message, BEFRIEND_REPLACE_KEY_TARGET, "[attacker]")
owner.visible_message(span_notice(display_message))
#undef BEFRIEND_REPLACE_KEY_SOURCE
#undef BEFRIEND_REPLACE_KEY_TARGET
+14 -3
View File
@@ -137,8 +137,15 @@
. = ..()
if(basic_mob_flags & DEL_ON_DEATH)
qdel(src)
return
health = 0
else
health = 0
look_dead()
/**
* Apply the appearance and properties this mob has when it dies
* This is called by the mob pretending to be dead too so don't put loot drops in here or something
*/
/mob/living/basic/proc/look_dead()
icon_state = icon_dead
if(basic_mob_flags & FLIP_ON_DEATH)
transform = transform.Turn(180)
@@ -147,8 +154,12 @@
/mob/living/basic/revive(full_heal_flags = NONE, excess_healing = 0, force_grab_ghost = FALSE)
. = ..()
if (!.)
if(!.)
return
look_alive()
/// Apply the appearance and properties this mob has when it is alive
/mob/living/basic/proc/look_alive()
icon_state = icon_living
if(basic_mob_flags & FLIP_ON_DEATH)
transform = transform.Turn(180)
@@ -5,7 +5,7 @@
BB_BASIC_MOB_TIPPER = null,
)
ai_traits = STOP_MOVING_WHEN_PULLED
ai_traits = STOP_MOVING_WHEN_PULLED | STOP_ACTING_WHILE_DEAD
ai_movement = /datum/ai_movement/basic_avoidance
idle_behavior = /datum/idle_behavior/idle_random_walk
planning_subtrees = list(
@@ -51,7 +51,7 @@
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/ignore_faction(),
)
ai_traits = STOP_MOVING_WHEN_PULLED
ai_traits = STOP_MOVING_WHEN_PULLED | STOP_ACTING_WHILE_DEAD
ai_movement = /datum/ai_movement/basic_avoidance
idle_behavior = /datum/idle_behavior/idle_random_walk
@@ -50,7 +50,7 @@
BB_BASIC_MOB_FLEEING = TRUE,
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/ignore_faction(),
)
ai_traits = STOP_MOVING_WHEN_PULLED
ai_traits = STOP_MOVING_WHEN_PULLED | STOP_ACTING_WHILE_DEAD
ai_movement = /datum/ai_movement/basic_avoidance
idle_behavior = /datum/idle_behavior/idle_random_walk
planning_subtrees = list(
@@ -85,7 +85,7 @@
BB_BASIC_MOB_FLEEING = TRUE,
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/ignore_faction(),
)
ai_traits = STOP_MOVING_WHEN_PULLED
ai_traits = STOP_MOVING_WHEN_PULLED | STOP_ACTING_WHILE_DEAD
ai_movement = /datum/ai_movement/basic_avoidance
idle_behavior = /datum/idle_behavior/idle_random_walk
planning_subtrees = list(
+32 -3
View File
@@ -1,5 +1,21 @@
//Dogs.
// Add 'walkies' as valid input
/datum/pet_command/follow/dog
speech_commands = list("heel", "follow", "walkies")
// Add 'good dog' as valid input
/datum/pet_command/good_boy/dog
speech_commands = list("good dog")
// Set correct attack behaviour
/datum/pet_command/point_targetting/attack/dog
attack_behaviour = /datum/ai_behavior/basic_melee_attack/dog
/datum/pet_command/point_targetting/attack/dog/set_command_active(mob/living/parent, mob/living/commander)
. = ..()
parent.ai_controller.blackboard[BB_DOG_HARASS_HARM] = TRUE
/mob/living/basic/pet/dog
mob_biotypes = MOB_ORGANIC|MOB_BEAST
response_help_continuous = "pets"
@@ -12,17 +28,29 @@
faction = list(FACTION_NEUTRAL)
see_in_dark = 5
can_be_held = TRUE
ai_controller = /datum/ai_controller/dog
///In the case 'melee_damage_upper' is somehow raised above 0
ai_controller = /datum/ai_controller/basic_controller/dog
// The dog attack pet command can raise melee attack above 0
attack_verb_continuous = "bites"
attack_verb_simple = "bite"
attack_sound = 'sound/weapons/bite.ogg'
attack_vis_effect = ATTACK_EFFECT_BITE
/// Instructions you can give to dogs
var/static/list/pet_commands = list(
/datum/pet_command/idle,
/datum/pet_command/free,
/datum/pet_command/good_boy/dog,
/datum/pet_command/follow/dog,
/datum/pet_command/point_targetting/attack/dog,
/datum/pet_command/point_targetting/fetch,
/datum/pet_command/play_dead,
)
/mob/living/basic/pet/dog/Initialize(mapload)
. = ..()
AddElement(/datum/element/pet_bonus, "woofs happily!")
AddElement(/datum/element/footstep, FOOTSTEP_MOB_CLAW)
AddElement(/datum/element/befriend_petting, tamed_reaction = "%SOURCE% licks at %TARGET% in a friendly manner!", untamed_reaction = "%SOURCE% fixes %TARGET% with a look of betrayal.")
AddComponent(/datum/component/obeys_commands, pet_commands)
/mob/living/basic/pet/dog/proc/update_dog_speech(datum/ai_planning_subtree/random_speech/speech)
speech.speak = string_list(list("YAP", "Woof!", "Bark!", "AUUUUUU"))
@@ -42,7 +70,7 @@
butcher_results = list(/obj/item/food/meat/slab/corgi = 3, /obj/item/stack/sheet/animalhide/corgi = 1)
gold_core_spawnable = FRIENDLY_SPAWN
collar_icon_state = "corgi"
ai_controller = /datum/ai_controller/dog/corgi
ai_controller = /datum/ai_controller/basic_controller/dog/corgi
var/obj/item/inventory_head
var/obj/item/inventory_back
/// Access card for Ian.
@@ -522,6 +550,7 @@ GLOBAL_LIST_INIT(strippable_corgi_items, create_strippable_list(list(
desc = "At a ripe old age of [record_age], Ian's not as spry as he used to be, but he'll always be the HoP's beloved corgi." //RIP
ai_controller?.blackboard[BB_DOG_IS_SLOW] = TRUE
is_slow = TRUE
speed = 2
/mob/living/basic/pet/dog/corgi/ian/Life(delta_time = SSMOBS_DT, times_fired)
if(!stat && SSticker.current_state == GAME_STATE_FINISHED && !memory_saved)
@@ -36,6 +36,6 @@
ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT)
/datum/ai_controller/basic_controller/axolotl
ai_traits = STOP_MOVING_WHEN_PULLED
ai_traits = STOP_MOVING_WHEN_PULLED | STOP_ACTING_WHILE_DEAD
ai_movement = /datum/ai_movement/basic_avoidance
idle_behavior = /datum/idle_behavior/idle_random_walk
@@ -57,7 +57,7 @@
BB_PET_TARGETTING_DATUM = new /datum/targetting_datum/not_friends(),
)
ai_traits = STOP_MOVING_WHEN_PULLED
ai_traits = STOP_MOVING_WHEN_PULLED | STOP_ACTING_WHILE_DEAD
ai_movement = /datum/ai_movement/basic_avoidance
idle_behavior = /datum/idle_behavior/idle_random_walk
planning_subtrees = list(
@@ -67,7 +67,7 @@
/datum/ai_controller/basic_controller/mothroach
blackboard = list()
ai_traits = STOP_MOVING_WHEN_PULLED
ai_traits = STOP_MOVING_WHEN_PULLED | STOP_ACTING_WHILE_DEAD
ai_movement = /datum/ai_movement/basic_avoidance
idle_behavior = /datum/idle_behavior/idle_random_walk
planning_subtrees = list(
@@ -357,7 +357,7 @@
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(), // Use this to find people to run away from
)
ai_traits = STOP_MOVING_WHEN_PULLED
ai_traits = STOP_MOVING_WHEN_PULLED | STOP_ACTING_WHILE_DEAD
ai_movement = /datum/ai_movement/basic_avoidance
idle_behavior = /datum/idle_behavior/idle_random_walk
planning_subtrees = list(
@@ -401,7 +401,7 @@
BB_LOW_PRIORITY_HUNTING_TARGET = null, // cable
)
ai_traits = STOP_MOVING_WHEN_PULLED
ai_traits = STOP_MOVING_WHEN_PULLED | STOP_ACTING_WHILE_DEAD
ai_movement = /datum/ai_movement/basic_avoidance
idle_behavior = /datum/idle_behavior/idle_random_walk
planning_subtrees = list(
+22 -2
View File
@@ -2475,10 +2475,13 @@ GLOBAL_LIST_EMPTY(fire_appearances)
. = ..()
add_mood_event("gaming", /datum/mood_event/gaming)
/// Proc for giving a mob a new 'friend', generally used for AI control and targetting
/// Proc for giving a mob a new 'friend', generally used for AI control and targetting. Returns false if already friends.
/mob/living/proc/befriend(mob/living/new_friend)
SHOULD_CALL_PARENT(TRUE)
faction |= REF(new_friend)
var/friend_ref = REF(new_friend)
if (faction.Find(friend_ref))
return FALSE
faction |= friend_ref
if (ai_controller)
var/list/friends = ai_controller.blackboard[BB_FRIENDS_LIST]
if (!friends)
@@ -2486,6 +2489,23 @@ GLOBAL_LIST_EMPTY(fire_appearances)
friends[WEAKREF(new_friend)] = TRUE
ai_controller.blackboard[BB_FRIENDS_LIST] = friends
SEND_SIGNAL(src, COMSIG_LIVING_BEFRIENDED, new_friend)
return TRUE
/// Proc for removing a friend you added with the proc 'befriend'. Returns true if you removed a friend.
/mob/living/proc/unfriend(mob/living/old_friend)
SHOULD_CALL_PARENT(TRUE)
var/friend_ref = REF(old_friend)
if (!faction.Find(friend_ref))
return FALSE
faction -= friend_ref
if (ai_controller)
var/list/friends = ai_controller.blackboard[BB_FRIENDS_LIST]
if (!friends)
return
friends[WEAKREF(old_friend)] = FALSE
ai_controller.blackboard[BB_FRIENDS_LIST] = friends
SEND_SIGNAL(src, COMSIG_LIVING_UNFRIENDED, old_friend)
return TRUE
/// Admin only proc for making the mob hallucinate a certain thing
/mob/living/proc/admin_give_hallucination(mob/admin)
@@ -668,5 +668,7 @@
/mob/living/simple_animal/hostile/befriend(mob/living/new_friend)
. = ..()
if (!.)
return
friends += new_friend
faction = new_friend.faction.Copy()
@@ -396,7 +396,7 @@
/datum/pet_command/point_targetting/attack/mouse
speech_commands = list("attack", "sic", "kill", "cheese em")
command_feedback = "squeak!" // Frogs and roaches can squeak too it's fine
pointed_reaction = "squeaks aggressively!"
pointed_reaction = "and squeaks aggressively"
refuse_reaction = "quivers"
attack_behaviour = /datum/ai_behavior/basic_melee_attack/rat
@@ -404,7 +404,7 @@
/datum/pet_command/point_targetting/attack/glockroach
speech_commands = list("attack", "sic", "kill", "cheese em")
command_feedback = "squeak!"
pointed_reaction = "cocks gun"
pointed_reaction = "and cocks its gun"
refuse_reaction = "quivers"
attack_behaviour = /datum/ai_behavior/basic_ranged_attack/glockroach
@@ -120,7 +120,7 @@
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items(),
)
ai_traits = STOP_MOVING_WHEN_PULLED
ai_traits = STOP_MOVING_WHEN_PULLED | STOP_ACTING_WHILE_DEAD
ai_movement = /datum/ai_movement/basic_avoidance
idle_behavior = /datum/idle_behavior/idle_random_walk
@@ -9,7 +9,7 @@
icon_living = "markus"
var/static/list/markus_speak = list("Borf!", "Boof!", "Bork!", "Bowwow!", "Burg?")
butcher_results = list(/obj/item/food/burger/cheese = 1, /obj/item/food/meat/slab = 2, /obj/item/trash/syndi_cakes = 1)
ai_controller = /datum/ai_controller/dog/corgi
ai_controller = /datum/ai_controller/basic_controller/dog/corgi
gender = MALE
can_be_held = FALSE
gold_core_spawnable = FRIENDLY_SPAWN
@@ -58,7 +58,7 @@
death_message = "beeps, its mechanical parts hissing before the chassis collapses in a loud thud."
gold_core_spawnable = NO_SPAWN
nofur = TRUE
ai_controller = /datum/ai_controller/dog/borgi
ai_controller = /datum/ai_controller/basic_controller/dog/borgi
unsuitable_atmos_damage = 0
minimum_survivable_temperature = 0
@@ -96,11 +96,12 @@
* * always_shoot - always shoot the target, as opposed to only if not a friend.
*/
/mob/living/basic/pet/dog/corgi/borgi/proc/harass_target(mob/living/target, always_shoot = FALSE)
var/datum/ai_controller/dog/EN = ai_controller
var/datum/ai_controller/basic_controller/dog/EN = ai_controller
if(!EN)
return
var/is_friend = EN.blackboard[BB_DOG_FRIENDS][WEAKREF(target)]
var/list/friends_list = EN.blackboard[BB_FRIENDS_LIST]
var/is_friend = friends_list && friends_list[WEAKREF(target)]
if(always_shoot || !is_friend)
INVOKE_ASYNC(src, PROC_REF(shoot_at), target)
@@ -110,7 +111,7 @@
EN.set_movement_target(target)
EN.blackboard[BB_DOG_HARASS_TARGET] = WEAKREF(target)
EN.queue_behavior(/datum/ai_behavior/harass)
EN.queue_behavior(/datum/ai_behavior/basic_melee_attack/dog, BB_DOG_HARASS_TARGET, BB_PET_TARGETTING_DATUM)
/mob/living/basic/pet/dog/corgi/borgi/proc/on_attack_hand(datum/source, mob/living/target)
SIGNAL_HANDLER
@@ -206,7 +207,7 @@
UnregisterSignal(src, COMSIG_ATOM_EMAG_ACT)
do_sparks(3, 1, src)
var/datum/ai_controller/dog/EN = ai_controller
var/datum/ai_controller/basic_controller/dog/EN = ai_controller
LAZYCLEARLIST(EN.current_behaviors)
/mob/living/basic/pet/dog/corgi/borgi/proc/on_emag_act(mob/living/basic/pet/dog/target, mob/user)
@@ -239,26 +240,19 @@
gib() // Yuck, robo-blood
/// Dog controller but with emag attack support
/datum/ai_controller/dog/borgi
/datum/ai_controller/basic_controller/dog/borgi
blackboard = list(
BB_SIMPLE_CARRY_ITEM = null,
BB_FETCH_TARGET = null,
BB_FETCH_DELIVER_TO = null,
BB_DOG_FRIENDS = list(),
BB_FETCH_IGNORE_LIST = list(),
BB_DOG_ORDER_MODE = DOG_COMMAND_NONE,
BB_DOG_PLAYING_DEAD = FALSE,
BB_DOG_HARASS_TARGET = null,
BB_DOG_HARASS_FRUSTRATION = null,
BB_DOG_HARASS_HARM = TRUE,
BB_VISION_RANGE = AI_DOG_VISION_RANGE,
BB_DOG_IS_SLOW = TRUE,
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(),
BB_PET_TARGETTING_DATUM = new /datum/targetting_datum/basic(),
)
planning_subtrees = list(
/datum/ai_planning_subtree/emagged_borgi,
/datum/ai_planning_subtree/random_speech/dog,
/datum/ai_planning_subtree/dog,
/datum/ai_planning_subtree/pet_planning,
/datum/ai_planning_subtree/dog_harassment,
)
/// Subtree that schedules borgi to randomly shoot if they're emagged.
@@ -283,7 +277,7 @@
if(!DT_PROB(chance, delta_time))
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)
controller.queue_behavior(/datum/ai_behavior/find_potential_targets, BB_BASIC_MOB_CURRENT_TARGET, BB_PET_TARGETTING_DATUM, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION)
return
// Attack.
@@ -5,7 +5,7 @@
mob_biotypes = MOB_ROBOTIC
blood_volume = 0
unique_pet = TRUE
ai_controller = /datum/ai_controller/dog
ai_controller = /datum/ai_controller/basic_controller/dog
desc = "It's a Cybersun MiniVix robotic model wearing a microsized syndicate MODsuit and a cute little cap. Quite pretty."
icon = 'modular_skyrat/master_files/icons/mob/pets.dmi'
icon_state = "syndifox"
@@ -36,7 +36,7 @@
/datum/ai_controller/basic_controller/kiwi
blackboard = list()
ai_traits = STOP_MOVING_WHEN_PULLED
ai_traits = STOP_MOVING_WHEN_PULLED | STOP_ACTING_WHILE_DEAD
ai_movement = /datum/ai_movement/basic_avoidance
idle_behavior = /datum/idle_behavior/idle_random_walk
@@ -4,16 +4,16 @@
/mob/living/basic/pet/dog/corgi/ian
icon = 'modular_skyrat/modules/chadian/icons/ian.dmi'
ai_controller = /datum/ai_controller/dog/corgi/chadian
ai_controller = /datum/ai_controller/basic_controller/dog/corgi/chadian
var/resting_state = 0
/// AI controller that adds chad ian emotes
/datum/ai_controller/dog/corgi/chadian
/datum/ai_controller/basic_controller/dog/corgi/chadian
idle_behavior = /datum/idle_behavior/idle_dog/chadian
/// Actual idle behavior that adds chad ian emotes
/datum/idle_behavior/idle_dog/chadian/perform_idle_behavior(delta_time, datum/ai_controller/dog/controller)
/datum/idle_behavior/idle_dog/chadian/perform_idle_behavior(delta_time, datum/ai_controller/basic_controller/dog/controller)
. = ..()
var/mob/living/basic/pet/dog/corgi/ian/ian_pawn = controller.pawn
@@ -28,7 +28,6 @@
gold_core_spawnable = FRIENDLY_SPAWN
turns_per_move = 10
can_be_held = TRUE
ai_controller = /datum/ai_controller/dog //Tegus can be taught to fetch
stop_automated_movement = TRUE
/mob/living/simple_animal/hostile/retaliate/tegu/Initialize(mapload)
+6
View File
@@ -799,6 +799,7 @@
#include "code\datums\ai\basic_mobs\base_basic_controller.dm"
#include "code\datums\ai\basic_mobs\basic_ai_behaviors\basic_attacking.dm"
#include "code\datums\ai\basic_mobs\basic_ai_behaviors\nearest_targetting.dm"
#include "code\datums\ai\basic_mobs\basic_ai_behaviors\pick_up_item.dm"
#include "code\datums\ai\basic_mobs\basic_ai_behaviors\run_away_from_target.dm"
#include "code\datums\ai\basic_mobs\basic_ai_behaviors\step_towards_turf.dm"
#include "code\datums\ai\basic_mobs\basic_ai_behaviors\targetting.dm"
@@ -815,9 +816,11 @@
#include "code\datums\ai\basic_mobs\basic_subtrees\target_retaliate.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\targetted_mob_ability.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\tipped_subtree.dm"
#include "code\datums\ai\basic_mobs\pet_commands\fetch.dm"
#include "code\datums\ai\basic_mobs\pet_commands\pet_command_planning.dm"
#include "code\datums\ai\basic_mobs\pet_commands\pet_follow_friend.dm"
#include "code\datums\ai\basic_mobs\pet_commands\pet_use_targetted_ability.dm"
#include "code\datums\ai\basic_mobs\pet_commands\play_dead.dm"
#include "code\datums\ai\basic_mobs\targetting_datums\basic_targetting_datum.dm"
#include "code\datums\ai\basic_mobs\targetting_datums\dont_target_friends.dm"
#include "code\datums\ai\cursed\cursed_behaviors.dm"
@@ -1058,6 +1061,7 @@
#include "code\datums\components\food\decomposition.dm"
#include "code\datums\components\food\edible.dm"
#include "code\datums\components\food\ice_cream_holder.dm"
#include "code\datums\components\pet_commands\fetch.dm"
#include "code\datums\components\pet_commands\obeys_commands.dm"
#include "code\datums\components\pet_commands\pet_command.dm"
#include "code\datums\components\pet_commands\pet_commands_basic.dm"
@@ -1129,6 +1133,7 @@
#include "code\datums\diseases\advance\symptoms\youth.dm"
#include "code\datums\elements\_element.dm"
#include "code\datums\elements\ai_flee_while_injured.dm"
#include "code\datums\elements\ai_held_item.dm"
#include "code\datums\elements\ai_retaliate.dm"
#include "code\datums\elements\animal_variety.dm"
#include "code\datums\elements\art.dm"
@@ -1141,6 +1146,7 @@
#include "code\datums\elements\basic_eating.dm"
#include "code\datums\elements\beauty.dm"
#include "code\datums\elements\bed_tucking.dm"
#include "code\datums\elements\befriend_petting.dm"
#include "code\datums\elements\bsa_blocker.dm"
#include "code\datums\elements\bump_click.dm"
#include "code\datums\elements\can_barricade.dm"