diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_ai.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_ai.dm new file mode 100644 index 00000000000..2331c66a2b9 --- /dev/null +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_ai.dm @@ -0,0 +1,2 @@ +/// Signal sent when a blackboard key is set to a new value +#define COMSIG_AI_BLACKBOARD_KEY_SET(blackboard_key) "ai_blackboard_key_set_[blackboard_key]" diff --git a/code/datums/ai/_ai_controller.dm b/code/datums/ai/_ai_controller.dm index 84b415b90f1..ccf58c9bdf6 100644 --- a/code/datums/ai/_ai_controller.dm +++ b/code/datums/ai/_ai_controller.dm @@ -396,6 +396,9 @@ multiple modular subtrees with behaviors // Assume it is an error when trying to set a value overtop a list if(islist(blackboard[key])) CRASH("set_blackboard_key attempting to set a blackboard value to key [key] when it's a list!") + // Don't do anything if it's already got this value + if (blackboard[key] == thing) + return // Clear existing values if(!isnull(blackboard[key])) @@ -403,6 +406,7 @@ multiple modular subtrees with behaviors TRACK_AI_DATUM_TARGET(thing, key) blackboard[key] = thing + post_blackboard_key_set(key) /** * Sets the key at index thing to the passed value @@ -416,9 +420,14 @@ multiple modular subtrees with behaviors /datum/ai_controller/proc/set_blackboard_key_assoc(key, thing, value) if(!islist(blackboard[key])) CRASH("set_blackboard_key_assoc called on non-list key [key]!") + // Don't do anything if it's already got this value + if (blackboard[key][thing] == value) + return + TRACK_AI_DATUM_TARGET(thing, key) TRACK_AI_DATUM_TARGET(value, key) blackboard[key][thing] = value + post_blackboard_key_set(key) /** * Similar to [proc/set_blackboard_key_assoc] but operates under the assumption the key is a lazylist (so it will create a list) @@ -430,9 +439,22 @@ multiple modular subtrees with behaviors */ /datum/ai_controller/proc/set_blackboard_key_assoc_lazylist(key, thing, value) LAZYINITLIST(blackboard[key]) + // Don't do anything if it's already got this value + if (blackboard[key][thing] == value) + return + TRACK_AI_DATUM_TARGET(thing, key) TRACK_AI_DATUM_TARGET(value, key) blackboard[key][thing] = value + post_blackboard_key_set(key) + +/** + * Called after we set a blackboard key, forwards signal information. + */ +/datum/ai_controller/proc/post_blackboard_key_set(key) + if (isnull(pawn)) + return + SEND_SIGNAL(pawn, COMSIG_AI_BLACKBOARD_KEY_SET(key)) /** * Adds the passed "thing" to the associated key diff --git a/code/datums/components/aggro_emote.dm b/code/datums/components/aggro_emote.dm new file mode 100644 index 00000000000..0688d277aa4 --- /dev/null +++ b/code/datums/components/aggro_emote.dm @@ -0,0 +1,54 @@ +/// A component for ai-controlled atoms which plays a sound if they switch to a living target which they can attack +/datum/component/aggro_emote + /// Blackboard key in which target data is stored + var/target_key + /// If we want to limit emotes to only play at mobs + var/living_only + /// List of emotes to play + var/list/emote_list + /// Chance to play an emote + var/emote_chance + /// Chance to subtract every time we play an emote (permanently) + var/subtract_chance + /// Minimum chance to play an emote + var/minimum_chance + +/datum/component/aggro_emote/Initialize( + target_key = BB_BASIC_MOB_CURRENT_TARGET, + living_only = FALSE, + list/emote_list, + emote_chance = 30, + minimum_chance = 2, + subtract_chance = 7, +) + . = ..() + if (!isatom(parent)) + return COMPONENT_INCOMPATIBLE + var/atom/atom_parent = parent + if (!atom_parent.ai_controller) + return COMPONENT_INCOMPATIBLE + + src.target_key = target_key + src.emote_list = emote_list + src.emote_chance = emote_chance + src.minimum_chance = minimum_chance + src.subtract_chance = subtract_chance + +/datum/component/aggro_emote/RegisterWithParent() + . = ..() + RegisterSignal(parent, COMSIG_AI_BLACKBOARD_KEY_SET(target_key), PROC_REF(on_target_changed)) + +/datum/component/aggro_emote/UnregisterFromParent() + UnregisterSignal(parent, COMSIG_AI_BLACKBOARD_KEY_SET(target_key)) + return ..() + +/// When we get a new target, see if we want to bark at it +/datum/component/aggro_emote/proc/on_target_changed(atom/source) + SIGNAL_HANDLER + var/atom/new_target = source.ai_controller.blackboard[target_key] + if (isnull(new_target) || !prob(emote_chance)) + return + if (living_only && !isliving(new_target)) + return // If we don't want to bark at food items or chairs or windows + emote_chance = max(emote_chance - subtract_chance, minimum_chance) + source.manual_emote("[pick(emote_list)] at [new_target].") diff --git a/code/datums/emotes.dm b/code/datums/emotes.dm index 8c5a5c5c055..d718d149882 100644 --- a/code/datums/emotes.dm +++ b/code/datums/emotes.dm @@ -289,24 +289,27 @@ * * Returns TRUE if it was able to run the emote, FALSE otherwise. */ -/mob/proc/manual_emote(text) //Just override the song and dance - . = TRUE - if(stat != CONSCIOUS) - return - +/atom/proc/manual_emote(text) if(!text) CRASH("Someone passed nothing to manual_emote(), fix it") log_message(text, LOG_EMOTE) - - var/ghost_text = "[src] [text]" - - var/origin_turf = get_turf(src) - if(client) - for(var/mob/ghost as anything in GLOB.dead_mob_list) - if(!ghost.client || isnewplayer(ghost)) - continue - if(ghost.client.prefs.chat_toggles & CHAT_GHOSTSIGHT && !(ghost in viewers(origin_turf, null))) - ghost.show_message("[FOLLOW_LINK(ghost, src)] [ghost_text]") - visible_message(text, visible_message_flags = EMOTE_MESSAGE) + return TRUE + +/mob/manual_emote(text) + if (stat != CONSCIOUS) + return FALSE + . = ..() + if (!.) + return FALSE + if (!client) + return TRUE + var/ghost_text = "[src] [text]" + var/origin_turf = get_turf(src) + for(var/mob/ghost as anything in GLOB.dead_mob_list) + if(!ghost.client || isnewplayer(ghost)) + continue + if(ghost.client.prefs.chat_toggles & CHAT_GHOSTSIGHT && !(ghost in viewers(origin_turf, null))) + ghost.show_message("[FOLLOW_LINK(ghost, src)] [ghost_text]") + return TRUE diff --git a/code/modules/mob/living/basic/festivus_pole.dm b/code/modules/mob/living/basic/festivus_pole.dm index 3cc80afb437..069da5b8906 100644 --- a/code/modules/mob/living/basic/festivus_pole.dm +++ b/code/modules/mob/living/basic/festivus_pole.dm @@ -43,6 +43,7 @@ /mob/living/basic/festivus/Initialize(mapload) . = ..() AddElement(/datum/element/death_drops, list(/obj/item/stack/rods)) + AddComponent(/datum/component/aggro_emote, emote_list = string_list(list("growls")), emote_chance = 20) var/datum/action/cooldown/mob_cooldown/charge_apc/charge_ability = new(src) charge_ability.Grant(src) ai_controller.set_blackboard_key(BB_FESTIVE_APC, charge_ability) diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp.dm b/code/modules/mob/living/basic/space_fauna/carp/carp.dm index 29a444707cd..f57b7b4f20d 100644 --- a/code/modules/mob/living/basic/space_fauna/carp/carp.dm +++ b/code/modules/mob/living/basic/space_fauna/carp/carp.dm @@ -110,6 +110,7 @@ AddElement(/datum/element/ai_flee_while_injured) setup_eating() + AddComponent(/datum/component/aggro_emote, emote_list = string_list(list("gnashes"))) AddComponent(/datum/component/regenerator, outline_colour = regenerate_colour) if (tamer) on_tamed(tamer, feedback = FALSE) diff --git a/code/modules/mob/living/basic/tree.dm b/code/modules/mob/living/basic/tree.dm index c6abaefe4b2..4b07630722c 100644 --- a/code/modules/mob/living/basic/tree.dm +++ b/code/modules/mob/living/basic/tree.dm @@ -56,6 +56,7 @@ . = ..() AddElement(/datum/element/swabable, CELL_LINE_TABLE_PINE, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) AddElement(/datum/element/death_drops, list(/obj/item/stack/sheet/mineral/wood)) + AddComponent(/datum/component/aggro_emote, emote_list = string_list(list("growls")), emote_chance = 20) /mob/living/basic/tree/Life(seconds_per_tick = SSMOBS_DT, times_fired) ..() diff --git a/tgstation.dme b/tgstation.dme index e33bd6c7520..9502c2a4b68 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -330,6 +330,7 @@ #include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_movable.dm" #include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_movement.dm" #include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_x_act.dm" +#include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_ai.dm" #include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_arcade.dm" #include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_carbon.dm" #include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_living.dm" @@ -869,6 +870,7 @@ #include "code\datums\components\acid.dm" #include "code\datums\components\action_item_overlay.dm" #include "code\datums\components\admin_popup.dm" +#include "code\datums\components\aggro_emote.dm" #include "code\datums\components\ai_retaliate_advanced.dm" #include "code\datums\components\anti_magic.dm" #include "code\datums\components\aquarium.dm"