mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-01-03 05:21:27 +00:00
[MIRROR] Pet Command Component + Regal Rats can order their subjects around [MDB IGNORE] (#18131)
* Pet Command Component + Regal Rats can order their subjects around (#71590) ## About The Pull Request Another atomisation of #71421 but I had a fun idea while I was testing it. This adds a component based on the existing system for giving instructions to tamed carp or dogs, but hopefully more modular. It also gives it to the rat minions of a regal rat. The basic function allows the mob to listen and react to spoken commands, which passes things to its AI blackboard. Additionally if you alt-click a commandable mob it will show a radial menu which both allows you to select a command, and also contains tooltips explaining what they do and what audible words trigger it. <details> <summary>Video</summary> https://user-images.githubusercontent.com/7483112/204308693-0eccebec-75c9-411c-81c5-5aa0d682d1a5.mp4 </details> Now if you riot some rats, you can alt click on them individually to give them specific orders (more useful for other creatures than rats), or you can speak out loud to command your legion. Rats aren't very smart so you can't give them many instructions, but this is expandable for other creatures. Additional change: Mice don't squeak if stepped on by other mice because this made an absolutely unholy noise and I am not sure there's a way to get non-dense mobs to spread out. ## Why It's Good For The Game Allows for giving more mobs the ability to be tamed and instructable by their owner, without copy/pasting code which lives inside a specific mob. Yelling at your rats to give them commands is funny. It also adds the possibility of telling your rats to stop biting someone if they have agreed to your demands, allowing for more courtly roleplay. When Regal Rat is converted to a basic mob its AI can also give other AIs instructions by yelling at them which I think is a good feature. ## Changelog 🆑 add: The followers of Regal Rats will now respond to simple instructions, if given by their rightful lord. Except frogs. They're too busy licking themselves and watching the colours. /🆑 * Pet Command Component + Regal Rats can order their subjects around Co-authored-by: Jacquerel <hnevard@gmail.com>
This commit is contained in:
19
code/__DEFINES/ai_pet_commands.dm
Normal file
19
code/__DEFINES/ai_pet_commands.dm
Normal file
@@ -0,0 +1,19 @@
|
||||
/// 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"
|
||||
/// Blackboard field for how we target things, as usually we want to be more permissive than normal
|
||||
#define BB_PET_TARGETTING_DATUM "BB_pet_targetting"
|
||||
/// Typecache of weakrefs to mobs this mob is friends with, will follow their instructions and won't attack them
|
||||
#define BB_FRIENDS_LIST "BB_friends_list"
|
||||
@@ -128,3 +128,6 @@
|
||||
#define STOP_SACRIFICE (1<<0)
|
||||
/// Don't send a message for sacrificing this thing, we have our own
|
||||
#define SILENCE_SACRIFICE_MESSAGE (1<<1)
|
||||
|
||||
/// From /mob/living/befriend() : (mob/living/new_friend)
|
||||
#define COMSIG_LIVING_BEFRIENDED "living_befriended"
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
var/atom/target = weak_target?.resolve()
|
||||
if(!target)
|
||||
return FALSE
|
||||
var/datum/targetting_datum/targetting_datum = controller.blackboard[targetting_datum_key]
|
||||
if (!targetting_datum)
|
||||
return
|
||||
controller.set_movement_target(target)
|
||||
|
||||
/datum/ai_behavior/basic_melee_attack/perform(delta_time, datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key)
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* # Pet Planning
|
||||
* Perform behaviour based on what pet commands you have received. This is delegated to the pet command datum.
|
||||
* When a command is set, we blackboard a key to our currently active command.
|
||||
* The blackboard also has a weak reference to every command datum available to us.
|
||||
* We use the key to figure out which datum to run, then ask it to figure out how to execute its action.
|
||||
*/
|
||||
/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/pet_command/command = weak_command?.resolve()
|
||||
if (!command)
|
||||
return // We forgot this command at some point
|
||||
return command.execute_action(controller)
|
||||
19
code/datums/ai/basic_mobs/pet_commands/pet_follow_friend.dm
Normal file
19
code/datums/ai/basic_mobs/pet_commands/pet_follow_friend.dm
Normal file
@@ -0,0 +1,19 @@
|
||||
/// Just keep following the target until the command is interrupted
|
||||
/datum/ai_behavior/pet_follow_friend
|
||||
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM
|
||||
|
||||
/datum/ai_behavior/pet_follow_friend/setup(datum/ai_controller/controller, target_key)
|
||||
. = ..()
|
||||
var/datum/weakref/weak_target = controller.blackboard[target_key]
|
||||
var/atom/target = weak_target?.resolve()
|
||||
if (!target)
|
||||
return FALSE
|
||||
controller.current_movement_target = target
|
||||
|
||||
/datum/ai_behavior/pet_follow_friend/perform(delta_time, datum/ai_controller/controller, target_key)
|
||||
. = ..()
|
||||
var/datum/weakref/weak_target = controller.blackboard[target_key]
|
||||
var/atom/target = weak_target?.resolve()
|
||||
if (!target)
|
||||
finish_action(controller, FALSE, target_key)
|
||||
return
|
||||
@@ -0,0 +1,27 @@
|
||||
/// Pet owners can't see their pet's ability cooldowns so we keep attempting to use an ability until we succeed
|
||||
/datum/ai_behavior/pet_use_ability
|
||||
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM
|
||||
|
||||
/datum/ai_behavior/pet_use_ability/setup(datum/ai_controller/controller, ability_key, target_key)
|
||||
. = ..()
|
||||
var/datum/weakref/weak_target = controller.blackboard[target_key]
|
||||
var/mob/living/target = weak_target?.resolve()
|
||||
if (QDELETED(target))
|
||||
return FALSE
|
||||
controller.current_movement_target = target
|
||||
|
||||
/datum/ai_behavior/pet_use_ability/perform(delta_time, datum/ai_controller/controller, ability_key, target_key)
|
||||
var/datum/action/cooldown/mob_cooldown/ability = controller.blackboard[ability_key]
|
||||
var/datum/weakref/weak_target = controller.blackboard[target_key]
|
||||
var/mob/living/target = weak_target?.resolve()
|
||||
if (!ability || QDELETED(target))
|
||||
finish_action(controller, FALSE, ability_key, target_key)
|
||||
return
|
||||
var/mob/pawn = controller.pawn
|
||||
if (ability.InterceptClickOn(pawn, null, target))
|
||||
finish_action(controller, TRUE, ability_key, target_key)
|
||||
|
||||
/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,30 @@
|
||||
/// Don't target an atom in our friends list (or turfs), anything else is fair game
|
||||
/datum/targetting_datum/not_friends
|
||||
|
||||
///Returns true or false depending on if the target can be attacked by the mob
|
||||
/datum/targetting_datum/not_friends/can_attack(mob/living/living_mob, atom/target)
|
||||
if (!target)
|
||||
return FALSE
|
||||
if (isturf(target))
|
||||
return FALSE
|
||||
|
||||
if (ismob(target))
|
||||
var/mob/mob_target = target
|
||||
if (mob_target.status_flags & GODMODE)
|
||||
return FALSE
|
||||
|
||||
if (living_mob.see_invisible < target.invisibility)
|
||||
return FALSE
|
||||
if (isturf(target.loc) && living_mob.z != target.z) // z check will always fail if target is in a mech
|
||||
return FALSE
|
||||
|
||||
if (!living_mob.ai_controller) // How did you get here?
|
||||
return FALSE
|
||||
|
||||
var/list/friends_list = living_mob.ai_controller.blackboard[BB_FRIENDS_LIST]
|
||||
if (!friends_list)
|
||||
return TRUE // We don't have any friends, anything's fair game
|
||||
if (!friends_list[WEAKREF(target)])
|
||||
return TRUE // This is not our friend, fire at will
|
||||
|
||||
return FALSE
|
||||
70
code/datums/components/pet_commands/obeys_commands.dm
Normal file
70
code/datums/components/pet_commands/obeys_commands.dm
Normal file
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* # Obeys Commands Component
|
||||
* Manages a list of pet command datums, allowing you to boss it around
|
||||
* Creates a radial menu of pet commands when this creature is alt-clicked, if it has any
|
||||
*/
|
||||
/datum/component/obeys_commands
|
||||
/// List of commands you can give to the owner of this component
|
||||
var/list/available_commands = list()
|
||||
|
||||
/// The available_commands parameter should be passed as a list of typepaths
|
||||
/datum/component/obeys_commands/Initialize(list/command_typepaths = list())
|
||||
. = ..()
|
||||
if (!isliving(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
var/mob/living/living_parent = parent
|
||||
if (!living_parent.ai_controller)
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
if (!length(command_typepaths))
|
||||
CRASH("Initialised obedience component with no commands.")
|
||||
|
||||
for (var/command_path in command_typepaths)
|
||||
var/datum/pet_command/new_command = new command_path(parent)
|
||||
available_commands[new_command.command_name] = new_command
|
||||
|
||||
/datum/component/obeys_commands/Destroy(force, silent)
|
||||
. = ..()
|
||||
QDEL_NULL(available_commands)
|
||||
|
||||
/datum/component/obeys_commands/RegisterWithParent()
|
||||
RegisterSignal(parent, COMSIG_LIVING_BEFRIENDED, PROC_REF(add_friend))
|
||||
RegisterSignal(parent, COMSIG_CLICK_ALT, PROC_REF(display_menu))
|
||||
|
||||
/datum/component/obeys_commands/UnregisterFromParent()
|
||||
UnregisterSignal(parent, list(COMSIG_LIVING_BEFRIENDED, COMSIG_CLICK_ALT))
|
||||
|
||||
/datum/component/obeys_commands/proc/add_friend(datum/source, mob/living/new_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, add_new_friend), new_friend)
|
||||
|
||||
|
||||
/// Displays a radial menu of commands
|
||||
/datum/component/obeys_commands/proc/display_menu(datum/source, mob/living/clicker)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
var/mob/living/living_parent = parent
|
||||
if (IS_DEAD_OR_INCAP(living_parent))
|
||||
return
|
||||
if (!living_parent.ai_controller)
|
||||
return
|
||||
var/list/friends_list = living_parent.ai_controller.blackboard[BB_FRIENDS_LIST]
|
||||
if (!friends_list || !friends_list[WEAKREF(clicker)])
|
||||
return // Not our friend, can't boss us around
|
||||
|
||||
INVOKE_ASYNC(src, PROC_REF(display_radial_menu), clicker)
|
||||
|
||||
/// Actually display the radial menu and then do something with the result
|
||||
/datum/component/obeys_commands/proc/display_radial_menu(mob/living/clicker)
|
||||
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/pick = show_radial_menu(clicker, clicker, radial_options, require_near = TRUE, tooltips = TRUE)
|
||||
if (!pick)
|
||||
return
|
||||
var/datum/pet_command/picked_command = available_commands[pick]
|
||||
picked_command.try_activate_command(clicker)
|
||||
143
code/datums/components/pet_commands/pet_command.dm
Normal file
143
code/datums/components/pet_commands/pet_command.dm
Normal file
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* # Pet Command
|
||||
* Set some AI blackboard commands in response to receiving instructions
|
||||
* This is abstract and should be extended for actual behaviour
|
||||
*/
|
||||
/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
|
||||
/// 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
|
||||
/// 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
|
||||
var/sense_radius = 7
|
||||
|
||||
/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))
|
||||
|
||||
/// 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
|
||||
|
||||
var/mob/living/parent = weak_parent.resolve()
|
||||
if (!parent)
|
||||
return
|
||||
if (!can_see(parent, speaker, sense_radius)) // Basically the same rules as hearing
|
||||
return
|
||||
|
||||
var/spoken_text = speech_args[SPEECH_MESSAGE]
|
||||
if (!find_command_in_text(spoken_text))
|
||||
return
|
||||
|
||||
try_activate_command(speaker)
|
||||
|
||||
/// Returns true if we find any of our spoken commands in the text
|
||||
/datum/pet_command/proc/find_command_in_text(spoken_text)
|
||||
for (var/command as anything in speech_commands)
|
||||
if (!findtext(spoken_text, command))
|
||||
continue
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
/// Apply a command state if conditions are right, return command if successful
|
||||
/datum/pet_command/proc/try_activate_command(mob/living/commander)
|
||||
var/mob/living/parent = weak_parent.resolve()
|
||||
if (!parent)
|
||||
return
|
||||
if (!parent.ai_controller) // We stopped having a brain at some point
|
||||
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
|
||||
return
|
||||
set_command_active(parent, commander)
|
||||
|
||||
/// Activate the command, extend to add visible messages and the like
|
||||
/datum/pet_command/proc/set_command_active(mob/living/parent, mob/living/commander)
|
||||
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
|
||||
if (command_feedback)
|
||||
parent.balloon_alert_to_viewers("[command_feedback]") // If we get a nicer runechat way to do this, refactor this
|
||||
|
||||
/// Store the target for the AI blackboard
|
||||
/datum/pet_command/proc/set_command_target(mob/living/parent, atom/target)
|
||||
parent.ai_controller.blackboard[BB_CURRENT_PET_TARGET] = WEAKREF(target)
|
||||
|
||||
/// Provide information about how to display this command in a radial menu
|
||||
/datum/pet_command/proc/provide_radial_data()
|
||||
var/datum/radial_menu_choice/choice = new()
|
||||
choice.name = command_name
|
||||
choice.image = icon(icon = radial_icon, icon_state = radial_icon_state)
|
||||
var/tooltip = command_desc
|
||||
if (length(speech_commands))
|
||||
tooltip += "<br>Speak this command with the words [speech_commands.Join(", ")]."
|
||||
choice.info = tooltip
|
||||
|
||||
return list("[command_name]" = choice)
|
||||
|
||||
/**
|
||||
* Execute an AI action on the provided controller, what we should actually do when this command is active.
|
||||
* This should basically always be called from a planning subtree which passes its own controller.
|
||||
* Return SUBTREE_RETURN_FINISH_PLANNING to pass that instruction on to the controller, or don't if you don't want that.
|
||||
*/
|
||||
/datum/pet_command/proc/execute_action(datum/ai_controller/controller)
|
||||
SHOULD_CALL_PARENT(FALSE)
|
||||
CRASH("Pet command execute action not implemented.")
|
||||
|
||||
/**
|
||||
* # Point Targetting Pet Command
|
||||
* As above but also listens for you pointing at something and marks it as a target
|
||||
*/
|
||||
/datum/pet_command/point_targetting
|
||||
/// Text describing an action we perform upon receiving a new target
|
||||
var/pointed_reaction
|
||||
/// Blackboard key for targetting datum, this is likely going to need it
|
||||
var/targetting_datum_key = BB_PET_TARGETTING_DATUM
|
||||
|
||||
/datum/pet_command/point_targetting/add_new_friend(mob/living/tamer)
|
||||
. = ..()
|
||||
RegisterSignal(tamer, COMSIG_MOB_POINTED, PROC_REF(look_for_target))
|
||||
|
||||
/// 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
|
||||
if (!parent.ai_controller)
|
||||
return
|
||||
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
|
||||
if (parent.ai_controller.blackboard[BB_CURRENT_PET_TARGET] == WEAKREF(pointed_atom)) // That's already our target
|
||||
return
|
||||
if (!can_see(parent, pointed_atom, sense_radius))
|
||||
return
|
||||
|
||||
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]!"))
|
||||
124
code/datums/components/pet_commands/pet_commands_basic.dm
Normal file
124
code/datums/components/pet_commands/pet_commands_basic.dm
Normal file
@@ -0,0 +1,124 @@
|
||||
// None of these are really complex enough to merit their own file
|
||||
|
||||
/**
|
||||
* # Pet Command: Idle
|
||||
* Tells a pet to resume its idle behaviour, usually staying put where you leave it
|
||||
*/
|
||||
/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
|
||||
speech_commands = list("sit", "stay", "stop")
|
||||
command_feedback = "sits"
|
||||
|
||||
/datum/pet_command/idle/execute_action(datum/ai_controller/controller)
|
||||
return SUBTREE_RETURN_FINISH_PLANNING // This cancels further AI planning
|
||||
|
||||
/**
|
||||
* # Pet Command: Stop
|
||||
* Tells a pet to exit command mode and resume its normal behaviour, which includes regular target-seeking and what have you
|
||||
*/
|
||||
/datum/pet_command/free
|
||||
command_name = "Loose"
|
||||
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)
|
||||
return // Just move on to the next planning subtree.
|
||||
|
||||
/**
|
||||
* # Pet Command: Follow
|
||||
* Tells a pet to follow you until you tell it to do something else
|
||||
*/
|
||||
/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
|
||||
speech_commands = list("heel", "follow")
|
||||
|
||||
/datum/pet_command/follow/set_command_active(mob/living/parent, mob/living/commander)
|
||||
. = ..()
|
||||
set_command_target(parent, commander)
|
||||
|
||||
/datum/pet_command/follow/execute_action(datum/ai_controller/controller)
|
||||
controller.queue_behavior(/datum/ai_behavior/pet_follow_friend, BB_CURRENT_PET_TARGET)
|
||||
return SUBTREE_RETURN_FINISH_PLANNING
|
||||
|
||||
/**
|
||||
* # Pet Command: Attack
|
||||
* Tells a pet to chase and bite the next thing you point at
|
||||
*/
|
||||
/datum/pet_command/point_targetting/attack
|
||||
command_name = "Attack"
|
||||
command_desc = "Command your pet to attack things that you point out to it."
|
||||
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"
|
||||
/// 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
|
||||
var/attack_behaviour = /datum/ai_behavior/basic_melee_attack
|
||||
|
||||
// Refuse to target things we can't target, chiefly other friends
|
||||
/datum/pet_command/point_targetting/attack/set_command_target(mob/living/parent, atom/target)
|
||||
if (!target)
|
||||
return
|
||||
var/mob/living/living_parent = parent
|
||||
if (!living_parent.ai_controller)
|
||||
return
|
||||
var/datum/targetting_datum/targeter = living_parent.ai_controller.blackboard[targetting_datum_key]
|
||||
if (!targeter)
|
||||
return
|
||||
if (!targeter.can_attack(living_parent, target))
|
||||
refuse_target(parent, target)
|
||||
return
|
||||
return ..()
|
||||
|
||||
/// Display feedback about not targetting something
|
||||
/datum/pet_command/point_targetting/attack/proc/refuse_target(mob/living/parent, atom/target)
|
||||
var/mob/living/living_parent = parent
|
||||
living_parent.balloon_alert_to_viewers("[refuse_reaction]")
|
||||
living_parent.visible_message(span_notice("[living_parent] refuses to attack [target]."))
|
||||
|
||||
/datum/pet_command/point_targetting/attack/execute_action(datum/ai_controller/controller)
|
||||
controller.queue_behavior(attack_behaviour, BB_CURRENT_PET_TARGET, targetting_datum_key)
|
||||
return SUBTREE_RETURN_FINISH_PLANNING
|
||||
|
||||
/**
|
||||
* # Pet Command: Targetted Ability
|
||||
* Tells a pet to use some kind of ability on the next thing you point at
|
||||
*/
|
||||
/datum/pet_command/point_targetting/use_ability
|
||||
command_name = "Use ability"
|
||||
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"
|
||||
/// Blackboard key where a reference to some kind of mob ability is stored
|
||||
var/pet_ability_key
|
||||
|
||||
/datum/pet_command/point_targetting/use_ability/execute_action(datum/ai_controller/controller)
|
||||
if (!pet_ability_key)
|
||||
return
|
||||
var/datum/action/cooldown/using_action = controller.blackboard[pet_ability_key]
|
||||
if (QDELETED(using_action))
|
||||
return
|
||||
// We don't check if the target exists because we want to 'sit attentively' if we've been instructed to attack but not given one yet
|
||||
// We also don't check if the cooldown is over because there's no way a pet owner can know that, the behaviour will handle it
|
||||
controller.queue_behavior(/datum/ai_behavior/pet_use_ability, pet_ability_key, targetting_datum_key)
|
||||
return SUBTREE_RETURN_FINISH_PLANNING
|
||||
@@ -97,6 +97,8 @@
|
||||
return
|
||||
if(arrived.movement_type & (FLYING|FLOATING) || !arrived.has_gravity())
|
||||
return
|
||||
if(ismob(arrived) && !arrived.density) // Prevents 10 overlapping mice from making an unholy sound while moving
|
||||
return
|
||||
var/atom/current_parent = parent
|
||||
if(isturf(current_parent?.loc))
|
||||
play_squeak()
|
||||
|
||||
@@ -56,9 +56,8 @@
|
||||
|
||||
after_tame?.Invoke(tamer)//Run custom behavior if needed
|
||||
|
||||
if(ishostile(parent) && isliving(tamer)) //Kinda shit check but this only applies to hostiles atm
|
||||
var/mob/living/simple_animal/hostile/evil_but_now_not_evil = parent
|
||||
evil_but_now_not_evil.friends = tamer
|
||||
evil_but_now_not_evil.faction = tamer.faction.Copy()
|
||||
if (isliving(parent) && isliving(tamer))
|
||||
var/mob/living/tamed = parent
|
||||
INVOKE_ASYNC(tamed, TYPE_PROC_REF(/mob/living, befriend), tamer)
|
||||
|
||||
qdel(src)
|
||||
|
||||
@@ -53,7 +53,8 @@
|
||||
|
||||
/datum/ai_controller/basic_controller/cockroach
|
||||
blackboard = list(
|
||||
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic()
|
||||
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(),
|
||||
BB_PET_TARGETTING_DATUM = new /datum/targetting_datum/not_friends(),
|
||||
)
|
||||
|
||||
ai_traits = STOP_MOVING_WHEN_PULLED
|
||||
@@ -92,6 +93,7 @@
|
||||
|
||||
/datum/ai_controller/basic_controller/cockroach/glockroach
|
||||
planning_subtrees = list(
|
||||
/datum/ai_planning_subtree/pet_planning,
|
||||
/datum/ai_planning_subtree/random_speech/cockroach,
|
||||
/datum/ai_planning_subtree/simple_find_target,
|
||||
/datum/ai_planning_subtree/basic_ranged_attack_subtree/glockroach, //If we are attacking someone, this will prevent us from hunting
|
||||
@@ -137,6 +139,7 @@
|
||||
return FALSE
|
||||
/datum/ai_controller/basic_controller/cockroach/hauberoach
|
||||
planning_subtrees = list(
|
||||
/datum/ai_planning_subtree/pet_planning,
|
||||
/datum/ai_planning_subtree/random_speech/cockroach,
|
||||
/datum/ai_planning_subtree/simple_find_target,
|
||||
/datum/ai_planning_subtree/basic_melee_attack_subtree/hauberoach, //If we are attacking someone, this will prevent us from hunting
|
||||
@@ -151,6 +154,7 @@
|
||||
|
||||
/datum/ai_controller/basic_controller/cockroach/sewer
|
||||
planning_subtrees = list(
|
||||
/datum/ai_planning_subtree/pet_planning,
|
||||
/datum/ai_planning_subtree/random_speech/cockroach,
|
||||
/datum/ai_planning_subtree/simple_find_target,
|
||||
/datum/ai_planning_subtree/basic_melee_attack_subtree/sewer,
|
||||
|
||||
@@ -346,6 +346,7 @@
|
||||
/datum/ai_controller/basic_controller/mouse/rat
|
||||
blackboard = list(
|
||||
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(),
|
||||
BB_PET_TARGETTING_DATUM = new /datum/targetting_datum/not_friends(),
|
||||
BB_BASIC_MOB_CURRENT_TARGET = null, // heathen
|
||||
BB_CURRENT_HUNTING_TARGET = null, // cheese
|
||||
BB_LOW_PRIORITY_HUNTING_TARGET = null, // cable
|
||||
@@ -355,6 +356,7 @@
|
||||
ai_movement = /datum/ai_movement/basic_avoidance
|
||||
idle_behavior = /datum/idle_behavior/idle_random_walk
|
||||
planning_subtrees = list(
|
||||
/datum/ai_planning_subtree/pet_planning,
|
||||
/datum/ai_planning_subtree/simple_find_target,
|
||||
/datum/ai_planning_subtree/basic_melee_attack_subtree/rat,
|
||||
/datum/ai_planning_subtree/find_and_hunt_target/look_for_cheese,
|
||||
|
||||
@@ -2467,6 +2467,18 @@ 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
|
||||
/mob/living/proc/befriend(mob/living/new_friend)
|
||||
SHOULD_CALL_PARENT(TRUE)
|
||||
faction |= REF(new_friend)
|
||||
if (ai_controller)
|
||||
var/list/friends = ai_controller.blackboard[BB_FRIENDS_LIST]
|
||||
if (!friends)
|
||||
friends = list()
|
||||
friends[WEAKREF(new_friend)] = TRUE
|
||||
ai_controller.blackboard[BB_FRIENDS_LIST] = friends
|
||||
SEND_SIGNAL(src, COMSIG_LIVING_BEFRIENDED, new_friend)
|
||||
|
||||
/// Admin only proc for making the mob hallucinate a certain thing
|
||||
/mob/living/proc/admin_give_hallucination(mob/admin)
|
||||
if(!admin || !check_rights(NONE))
|
||||
|
||||
@@ -665,3 +665,8 @@
|
||||
target = new_target
|
||||
if(target)
|
||||
RegisterSignal(target, COMSIG_PARENT_QDELETING, PROC_REF(handle_target_del))
|
||||
|
||||
/mob/living/simple_animal/hostile/befriend(mob/living/new_friend)
|
||||
. = ..()
|
||||
friends += new_friend
|
||||
faction = new_friend.faction.Copy()
|
||||
|
||||
@@ -238,8 +238,8 @@
|
||||
StartCooldown()
|
||||
|
||||
/**
|
||||
* This action checks all nearby mice, and converts them into hostile rats.
|
||||
* If no mice are nearby, creates a new one.
|
||||
* This action checks some nearby maintenance animals and makes them your minions.
|
||||
* If none are nearby, creates a new mouse.
|
||||
*/
|
||||
/datum/action/cooldown/riot
|
||||
name = "Raise Army"
|
||||
@@ -251,111 +251,158 @@
|
||||
overlay_icon_state = "bg_clock_border"
|
||||
cooldown_time = 8 SECONDS
|
||||
melee_cooldown_time = 0 SECONDS
|
||||
/// How close does something need to be for us to recruit it?
|
||||
var/range = 5
|
||||
/// Commands you can give to your mouse army
|
||||
var/static/list/mouse_commands = list(
|
||||
/datum/pet_command/idle,
|
||||
/datum/pet_command/free,
|
||||
/datum/pet_command/follow,
|
||||
/datum/pet_command/point_targetting/attack/mouse
|
||||
)
|
||||
/// Commands you can give to glockroaches
|
||||
var/static/list/glockroach_commands = list(
|
||||
/datum/pet_command/idle,
|
||||
/datum/pet_command/free,
|
||||
/datum/pet_command/follow,
|
||||
/datum/pet_command/point_targetting/attack/glockroach
|
||||
)
|
||||
|
||||
/datum/action/cooldown/riot/Activate(atom/target)
|
||||
StartCooldown(10 SECONDS)
|
||||
riot()
|
||||
StartCooldown()
|
||||
|
||||
/**
|
||||
* Attempts to, in order and ending at any successful step:
|
||||
* * Convert nearby mice into aggressive rats.
|
||||
* * Convert nearby roaches into aggressive roaches.
|
||||
* * Convert nearby frogs into aggressive frogs.
|
||||
* * Spawn a single mouse if below the mouse cap.
|
||||
*/
|
||||
/datum/action/cooldown/riot/proc/riot()
|
||||
var/cap = CONFIG_GET(number/ratcap)
|
||||
var/uplifted_mice = FALSE
|
||||
var/uplifted_frog = FALSE
|
||||
for (var/mob/living/basic/mouse/nearby_mouse in oview(owner, range))
|
||||
uplifted_mice = convert_mouse(nearby_mouse) || uplifted_mice
|
||||
if (uplifted_mice)
|
||||
owner.visible_message(span_warning("[owner] commands their army to action, mutating them into rats!"))
|
||||
return
|
||||
|
||||
var/static/list/converted_check_list = list(FACTION_RAT)
|
||||
var/uplifted_roach = FALSE
|
||||
var/list/converted_check_list = list(FACTION_RAT)
|
||||
for(var/mob/living/simple_animal/hostile/retaliate/frog/nearby_frog in oview(owner, 5))
|
||||
// No need to convert when not on the same team.
|
||||
if(faction_check(nearby_frog.faction, converted_check_list))
|
||||
continue
|
||||
if(nearby_frog.name == "frog")
|
||||
nearby_frog.name = "trash frog"
|
||||
nearby_frog.icon_state += "_trash"
|
||||
nearby_frog.icon_living += "_trash"
|
||||
nearby_frog.icon_dead = nearby_frog.icon_state + "_dead"
|
||||
else if(nearby_frog.name == "rare frog")
|
||||
nearby_frog.name = "rare trash frog"
|
||||
nearby_frog.icon_state += "_trash"
|
||||
nearby_frog.icon_living += "_trash"
|
||||
nearby_frog.icon_dead = nearby_frog.icon_state + "_dead"
|
||||
nearby_frog.desc += " ...Except this one lives in a trash bag."
|
||||
nearby_frog.maxHealth += 10
|
||||
nearby_frog.health += 10
|
||||
nearby_frog.melee_damage_lower += 1
|
||||
nearby_frog.melee_damage_upper += 5
|
||||
nearby_frog.faction = owner.faction.Copy()
|
||||
uplifted_frog = TRUE
|
||||
break
|
||||
|
||||
if(uplifted_frog)
|
||||
owner.visible_message(span_warning("[owner] commands their army to action, mutating them trash frogs!"))
|
||||
else
|
||||
for(var/mob/living/basic/cockroach/nearby_roach in oview(owner, 5))
|
||||
// No need to convert when not on the same team.
|
||||
if(faction_check(nearby_roach.faction, converted_check_list))
|
||||
continue
|
||||
if(istype(nearby_roach, /mob/living/basic/cockroach/glockroach))
|
||||
if(nearby_roach.name == "glockroach")
|
||||
nearby_roach.name = "sewer glockroach"
|
||||
nearby_roach.melee_damage_lower += 0.5
|
||||
nearby_roach.melee_damage_upper += 2
|
||||
else if(istype(nearby_roach, /mob/living/basic/cockroach/hauberoach))
|
||||
if(nearby_roach.name == "hauberoach")
|
||||
nearby_roach.name = "sewer hauberoach"
|
||||
nearby_roach.melee_damage_lower += 0.5
|
||||
nearby_roach.melee_damage_upper += 2
|
||||
else
|
||||
if(nearby_roach.name == "cockroach")
|
||||
nearby_roach.name = "sewer cockroach"
|
||||
nearby_roach.melee_damage_lower += 2
|
||||
nearby_roach.melee_damage_upper += 4
|
||||
nearby_roach.obj_damage += 5
|
||||
nearby_roach.ai_controller = new /datum/ai_controller/basic_controller/cockroach/sewer(nearby_roach)
|
||||
nearby_roach.desc += " ...Except this one looks very robust."
|
||||
nearby_roach.icon_state += "_sewer"
|
||||
nearby_roach.maxHealth += 1
|
||||
nearby_roach.health += 1
|
||||
nearby_roach.faction = owner.faction.Copy()
|
||||
uplifted_roach = TRUE
|
||||
break
|
||||
|
||||
if(uplifted_roach)
|
||||
for (var/mob/living/basic/cockroach/nearby_roach in oview(owner, range))
|
||||
uplifted_roach = convert_roach(nearby_roach, converted_check_list) || uplifted_roach
|
||||
if (uplifted_roach)
|
||||
owner.visible_message(span_warning("[owner] commands their army to action, mutating them into sewer roaches!"))
|
||||
else if(!uplifted_frog)
|
||||
for(var/mob/living/basic/mouse/nearby_mouse in oview(owner, 5))
|
||||
// This mouse is already rat controlled, let's not bother with it.
|
||||
if(istype(nearby_mouse.ai_controller, /datum/ai_controller/basic_controller/mouse/rat))
|
||||
continue
|
||||
var/mob/living/basic/mouse/rat/rat_path = /mob/living/basic/mouse/rat
|
||||
return
|
||||
|
||||
// Change name
|
||||
if(nearby_mouse.name == "mouse")
|
||||
nearby_mouse.name = initial(rat_path.name)
|
||||
// Buffs our combat stats to that of a rat
|
||||
nearby_mouse.melee_damage_lower = initial(rat_path.melee_damage_lower)
|
||||
nearby_mouse.melee_damage_upper = initial(rat_path.melee_damage_upper)
|
||||
nearby_mouse.obj_damage = initial(rat_path.obj_damage)
|
||||
nearby_mouse.maxHealth = initial(rat_path.maxHealth)
|
||||
nearby_mouse.health = initial(rat_path.health)
|
||||
// Replace our AI with a rat one
|
||||
nearby_mouse.ai_controller = new /datum/ai_controller/basic_controller/mouse/rat(nearby_mouse)
|
||||
// Give a hint in description too
|
||||
nearby_mouse.desc += " ...Except this one looks corrupted and aggressive."
|
||||
// Now we share factions!
|
||||
nearby_mouse.faction = owner.faction.Copy()
|
||||
uplifted_mice = TRUE
|
||||
break
|
||||
var/uplifted_frog = FALSE
|
||||
for (var/mob/living/simple_animal/hostile/retaliate/frog/nearby_frog in oview(owner, range))
|
||||
uplifted_frog = convert_frog(nearby_frog, converted_check_list) || uplifted_frog
|
||||
if (uplifted_frog)
|
||||
owner.visible_message(span_warning("[owner] commands their army to action, mutating them into trash frogs!"))
|
||||
return
|
||||
|
||||
if(uplifted_mice)
|
||||
owner.visible_message(span_warning("[owner] commands their army to action, mutating them into rats!"))
|
||||
var/rat_cap = CONFIG_GET(number/ratcap)
|
||||
if (LAZYLEN(SSmobs.cheeserats) >= rat_cap)
|
||||
to_chat(owner,span_warning("There's too many mice on this station to beckon a new one! Find them first!"))
|
||||
return
|
||||
new /mob/living/basic/mouse(owner.loc)
|
||||
owner.visible_message(span_warning("[owner] commands a rat to their side!"))
|
||||
|
||||
else if(LAZYLEN(SSmobs.cheeserats) < cap)
|
||||
new /mob/living/basic/mouse(owner.loc)
|
||||
owner.visible_message(span_warning("[owner] commands a rat to their side!"))
|
||||
/// Makes a passed mob into our minion
|
||||
/datum/action/cooldown/riot/proc/make_minion(mob/living/new_minion, minion_desc, list/command_list = mouse_commands)
|
||||
if (isbasicmob(new_minion)) // One day this will work for frogs too
|
||||
new_minion.AddComponent(/datum/component/obeys_commands, command_list)
|
||||
new_minion.befriend(owner)
|
||||
new_minion.faction = owner.faction.Copy()
|
||||
// Give a hint in description too
|
||||
new_minion.desc += minion_desc
|
||||
new_minion.balloon_alert_to_viewers("squeak")
|
||||
|
||||
else
|
||||
to_chat(owner,span_warning("There's too many mice on this station to beckon a new one! Find them first!"))
|
||||
/// Turns a mouse into an angry mouse
|
||||
/datum/action/cooldown/riot/proc/convert_mouse(mob/living/basic/mouse/nearby_mouse)
|
||||
// This mouse is already rat controlled, let's not bother with it.
|
||||
if (istype(nearby_mouse.ai_controller, /datum/ai_controller/basic_controller/mouse/rat))
|
||||
return FALSE
|
||||
|
||||
StartCooldown()
|
||||
var/mob/living/basic/mouse/rat/rat_path = /mob/living/basic/mouse/rat
|
||||
// Change name
|
||||
if (nearby_mouse.name == "mouse")
|
||||
nearby_mouse.name = initial(rat_path.name)
|
||||
// Buffs our combat stats to that of a rat
|
||||
nearby_mouse.melee_damage_lower = initial(rat_path.melee_damage_lower)
|
||||
nearby_mouse.melee_damage_upper = initial(rat_path.melee_damage_upper)
|
||||
nearby_mouse.obj_damage = initial(rat_path.obj_damage)
|
||||
nearby_mouse.maxHealth = initial(rat_path.maxHealth)
|
||||
nearby_mouse.health = initial(rat_path.health)
|
||||
// Replace our AI with a rat one
|
||||
nearby_mouse.ai_controller = new /datum/ai_controller/basic_controller/mouse/rat(nearby_mouse)
|
||||
make_minion(nearby_mouse, " ...Except this one looks corrupted and aggressive.")
|
||||
return TRUE
|
||||
|
||||
/// Turns a roach into an angry roach
|
||||
/datum/action/cooldown/riot/proc/convert_roach(mob/living/basic/cockroach/nearby_roach, list/converted_check_list)
|
||||
// No need to convert when not on the same team.
|
||||
if (faction_check(nearby_roach.faction, converted_check_list))
|
||||
return FALSE
|
||||
|
||||
var/list/minion_commands = mouse_commands
|
||||
if (!findtext(nearby_roach.name, "sewer"))
|
||||
nearby_roach.name = "sewer [nearby_roach.name]"
|
||||
|
||||
if (istype(nearby_roach, /mob/living/basic/cockroach/glockroach) || istype(nearby_roach, /mob/living/basic/cockroach/hauberoach))
|
||||
if (istype(nearby_roach, /mob/living/basic/cockroach/glockroach))
|
||||
minion_commands = glockroach_commands
|
||||
nearby_roach.melee_damage_lower += 0.5
|
||||
nearby_roach.melee_damage_upper += 2
|
||||
else
|
||||
nearby_roach.melee_damage_lower += 2
|
||||
nearby_roach.melee_damage_upper += 4
|
||||
nearby_roach.obj_damage += 5
|
||||
nearby_roach.ai_controller = new /datum/ai_controller/basic_controller/cockroach/sewer(nearby_roach)
|
||||
|
||||
nearby_roach.icon_state += "_sewer"
|
||||
nearby_roach.maxHealth += 1
|
||||
nearby_roach.health += 1
|
||||
make_minion(nearby_roach, " <br>This one looks extra robust.", minion_commands)
|
||||
return TRUE
|
||||
|
||||
/// Turns a frog into a crazy frog. This doesn't do anything interesting and should when it becomes a basic mob.
|
||||
/datum/action/cooldown/riot/proc/convert_frog(mob/living/simple_animal/hostile/retaliate/frog/nearby_frog, list/converted_check_list)
|
||||
// No need to convert when not on the same team.
|
||||
if(faction_check(nearby_frog.faction, converted_check_list))
|
||||
return FALSE
|
||||
|
||||
if (!findtext(nearby_frog.name, "trash"))
|
||||
nearby_frog.name = replacetext(nearby_frog.name, "frog", "trash frog")
|
||||
|
||||
nearby_frog.icon_state += "_trash"
|
||||
nearby_frog.icon_living += "_trash"
|
||||
nearby_frog.icon_dead = nearby_frog.icon_state + "_dead"
|
||||
nearby_frog.maxHealth += 10
|
||||
nearby_frog.health += 10
|
||||
nearby_frog.melee_damage_lower += 1
|
||||
nearby_frog.melee_damage_upper += 5
|
||||
var/crazy_frog_desc = " ...[findtext(nearby_frog.name, "rare") ? "even though" : "perhaps because"] they live in a trash bag."
|
||||
make_minion(nearby_frog, crazy_frog_desc)
|
||||
return TRUE
|
||||
|
||||
// Command you can give to a mouse to make it kill someone
|
||||
/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!"
|
||||
refuse_reaction = "quivers"
|
||||
attack_behaviour = /datum/ai_behavior/basic_melee_attack/rat
|
||||
|
||||
// Command you can give to a mouse to make it kill someone
|
||||
/datum/pet_command/point_targetting/attack/glockroach
|
||||
speech_commands = list("attack", "sic", "kill", "cheese em")
|
||||
command_feedback = "squeak!"
|
||||
pointed_reaction = "cocks gun"
|
||||
refuse_reaction = "quivers"
|
||||
attack_behaviour = /datum/ai_behavior/basic_ranged_attack/glockroach
|
||||
|
||||
/**
|
||||
*Spittle; harmless reagent that is added by rat king, and makes you disgusted.
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "code\__DEFINES\admin.dm"
|
||||
#include "code\__DEFINES\adventure.dm"
|
||||
#include "code\__DEFINES\ai.dm"
|
||||
#include "code\__DEFINES\ai_pet_commands.dm"
|
||||
#include "code\__DEFINES\alarm.dm"
|
||||
#include "code\__DEFINES\alerts.dm"
|
||||
#include "code\__DEFINES\antagonists.dm"
|
||||
@@ -799,7 +800,11 @@
|
||||
#include "code\datums\ai\basic_mobs\basic_subtrees\speech_subtree.dm"
|
||||
#include "code\datums\ai\basic_mobs\basic_subtrees\target_retaliate.dm"
|
||||
#include "code\datums\ai\basic_mobs\basic_subtrees\tipped_subtree.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\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"
|
||||
#include "code\datums\ai\cursed\cursed_controller.dm"
|
||||
#include "code\datums\ai\cursed\cursed_subtrees.dm"
|
||||
@@ -1035,6 +1040,9 @@
|
||||
#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\obeys_commands.dm"
|
||||
#include "code\datums\components\pet_commands\pet_command.dm"
|
||||
#include "code\datums\components\pet_commands\pet_commands_basic.dm"
|
||||
#include "code\datums\components\plumbing\_plumbing.dm"
|
||||
#include "code\datums\components\plumbing\chemical_acclimator.dm"
|
||||
#include "code\datums\components\plumbing\filter.dm"
|
||||
|
||||
Reference in New Issue
Block a user