[MIRROR] Goats will randomly attack you [MDB IGNORE] (#24312)

* Goats will randomly attack you (#78930)

## About The Pull Request

We accidentally lost this behaviour when we converted goats to basic
mobs.
_Formerly_ (and now again) goats had a 0.5% chance per second to simply
decide to attack you for no reason at all.
While attacking you they also have a 10% chance per second to get bored
of doing that and stop.

Additionally, we were outputting a fluff message every time you attacked
a goat which would spam chat if you were trying to fist fight each
other. I added a 20 second cooldown onto it.

As is often the case, implementing this led me down a bit of a rabbit
hole.
We were previously bypassing faction checks via a mixture of flags on AI
behaviours and blackboard keys.
I have moved this _entirely_ to the blackboard now, rather than making
targetting subtypes just to skip faction checks.

This entails having one blackboard key which is "by default do we care
about factions?" and another which is "are we currently ignoring
factions for some other reason?"
Retaliatory AI will generally enable the second flag, so you can get
pissed off at someone you would usually not mind hanging out with if
they start something with you. Certain mobs which want to hunt other
mobs but not be hunted in return just ignore factions entirely all the
time and use the former.

The upshot of this is that the default behaviour for expected default
retaliatory AI shouldn't require you to set any specific kind of
targetting datum and will Just Work.

In a similar vein because I was touching largely the same mobs I made
the "flee when injured" component apply its "don't flee because not
injured" flag instantly upon application rather than needing to manually
set it in the blackboard definition, so that also Just Works.

## Changelog

🆑
fix: Pete's anger management training has worn off, and he will once
again sometimes pick a fight with you for absolutely no reason.
qol: Attacking a goat will not spam messages so frequently.
/🆑

---------

Co-authored-by: san7890 <the@ san7890.com>

* Goats will randomly attack you

---------

Co-authored-by: Jacquerel <hnevard@gmail.com>
Co-authored-by: san7890 <the@ san7890.com>
This commit is contained in:
SkyratBot
2023-10-13 09:30:01 +02:00
committed by GitHub
parent a00c05e3c3
commit 0d5a0c0bb9
34 changed files with 147 additions and 78 deletions
@@ -0,0 +1,69 @@
/// Add or remove people to our retaliation shitlist just on an arbitrary whim
/datum/ai_planning_subtree/capricious_retaliate
/// Blackboard key which tells us how to select valid targets
var/targetting_datum_key = BB_TARGETTING_DATUM
/// Whether we should skip checking faction for our decision
var/ignore_faction = TRUE
/datum/ai_planning_subtree/capricious_retaliate/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
. = ..()
controller.queue_behavior(/datum/ai_behavior/capricious_retaliate, targetting_datum_key, ignore_faction)
/// Add or remove people to our retaliation shitlist just on an arbitrary whim
/datum/ai_behavior/capricious_retaliate
action_cooldown = 1 SECONDS
/datum/ai_behavior/capricious_retaliate/perform(seconds_per_tick, datum/ai_controller/controller, targetting_datum_key, ignore_faction)
. = ..()
var/atom/pawn = controller.pawn
if (controller.blackboard_key_exists(BB_BASIC_MOB_RETALIATE_LIST))
var/deaggro_chance = controller.blackboard[BB_RANDOM_DEAGGRO_CHANCE] || 10
if (!SPT_PROB(deaggro_chance, seconds_per_tick))
finish_action(controller, TRUE, ignore_faction) // "true" here means "don't clear our ignoring factions status"
return
pawn.visible_message(span_notice("[pawn] calms down.")) // We can blackboard key this if anyone else actually wants to customise it
controller.clear_blackboard_key(BB_BASIC_MOB_RETALIATE_LIST)
finish_action(controller, FALSE, ignore_faction)
controller.CancelActions() // Otherwise they will try and get one last kick in
return
var/aggro_chance = controller.blackboard[BB_RANDOM_AGGRO_CHANCE] || 0.5
if (!SPT_PROB(aggro_chance, seconds_per_tick))
finish_action(controller, FALSE, ignore_faction)
return
var/aggro_range = controller.blackboard[BB_AGGRO_RANGE] || 9
var/list/potential_targets = hearers(aggro_range, get_turf(pawn)) - pawn
if (!length(potential_targets))
failed_targetting(controller, pawn, ignore_faction)
return
var/datum/targetting_datum/target_helper = controller.blackboard[targetting_datum_key]
var/mob/living/final_target = null
if (ignore_faction)
controller.set_blackboard_key(BB_TEMPORARILY_IGNORE_FACTION, TRUE)
while (isnull(final_target) && length(potential_targets))
var/mob/living/test_target = pick_n_take(potential_targets)
if (target_helper.can_attack(pawn, test_target, vision_range = aggro_range))
final_target = test_target
if (isnull(final_target))
failed_targetting(controller, pawn, ignore_faction)
return
controller.insert_blackboard_key_lazylist(BB_BASIC_MOB_RETALIATE_LIST, final_target)
pawn.visible_message(span_warning("[pawn] glares grumpily at [final_target]!"))
finish_action(controller, TRUE, ignore_faction)
/// Called if we try but fail to target something
/datum/ai_behavior/capricious_retaliate/proc/failed_targetting(datum/ai_controller/controller, atom/pawn, ignore_faction)
finish_action(controller, FALSE, ignore_faction)
pawn.visible_message(span_notice("[pawn] grumbles.")) // We're pissed off but with no outlet to vent our frustration upon
/datum/ai_behavior/capricious_retaliate/finish_action(datum/ai_controller/controller, succeeded, ignore_faction)
. = ..()
if (succeeded || !ignore_faction)
return
var/usually_ignores_faction = controller.blackboard[BB_ALWAYS_IGNORE_FACTION] || FALSE
controller.set_blackboard_key(BB_TEMPORARILY_IGNORE_FACTION, usually_ignores_faction)
@@ -41,23 +41,27 @@
if(!targetting_datum)
CRASH("No target datum was supplied in the blackboard for [controller.pawn]")
var/list/shitlist = controller.blackboard[shitlist_key]
var/atom/existing_target = controller.blackboard[target_key]
if (!check_faction)
controller.set_blackboard_key(BB_TEMPORARILY_IGNORE_FACTION, TRUE)
if (!QDELETED(existing_target) && (locate(existing_target) in shitlist) && targetting_datum.can_attack(living_mob, existing_target, vision_range))
finish_action(controller, succeeded = TRUE, check_faction = check_faction)
return
var/list/enemies_list = list()
for(var/mob/living/potential_target as anything in controller.blackboard[shitlist_key])
if(!targetting_datum.can_attack(living_mob, potential_target, vision_range, check_faction))
for(var/mob/living/potential_target as anything in shitlist)
if(!targetting_datum.can_attack(living_mob, potential_target, vision_range))
continue
enemies_list += potential_target
if(!length(enemies_list))
controller.clear_blackboard_key(target_key)
finish_action(controller, succeeded = FALSE, check_faction = check_faction)
return
if (controller.blackboard[target_key] in enemies_list) // Don't bother changing
finish_action(controller, succeeded = TRUE, check_faction = check_faction)
return
var/atom/new_target = pick_final_target(controller, enemies_list)
controller.set_blackboard_key(target_key, new_target)
@@ -72,9 +76,9 @@
/datum/ai_behavior/target_from_retaliate_list/proc/pick_final_target(datum/ai_controller/controller, list/enemies_list)
return pick(enemies_list)
/datum/ai_behavior/target_from_retaliate_list/finish_action(datum/ai_controller/controller, succeeded, target_key, check_faction)
/datum/ai_behavior/target_from_retaliate_list/finish_action(datum/ai_controller/controller, succeeded, check_faction)
. = ..()
if(check_faction)
if (succeeded || check_faction)
return
controller.set_blackboard_key(BB_BASIC_MOB_SKIP_FACTION_CHECK, succeeded)
var/usually_ignores_faction = controller.blackboard[BB_ALWAYS_IGNORE_FACTION] || FALSE
controller.set_blackboard_key(BB_TEMPORARILY_IGNORE_FACTION, usually_ignores_faction)