mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-27 07:08:00 +01:00
[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:
co-authored by
Jacquerel
tastyfish
parent
a15fe71efd
commit
ff97c189db
@@ -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"
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
|
||||
+13
-19
@@ -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.
|
||||
|
||||
+1
-1
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user