Files
Cirrial 31d4eb04f1 Fix Stoat Steal Behaviour (and some other AI stuff hopefully) (#94153)
## About The Pull Request

Two main things. 

Multiple instances of use of a proc in AI controllers seemingly assuming
the default behaviour will work for them, but what ends up happening is
`search_tactic` gets redefined and redefined with no defaut search range
parameter, so nothing ends up passed to the `search_tactic` child procs,
so they all call `oview` with `null` and this... somehow doesn't
runtime? Has behaviour that works some of the time??? I hate this
fucking language. Anyway.

Stoat steal items behaviour was completely broken and apparently was not
tested once since it was merged in. I've made the corrections I can, but
I haven't figured out why stoat AI never enters idle, so we have a
behaviour that leads to the stoat running up to an item, grabbing it,
and then just staying there, unmoving. I've sunk too many hours into
this, I'm just going to call it fixed and let someone else figure out
what exciting additions there need to be to a behaviour that was never
functional in the first place.

## Why It's Good For The Game

i don't know man i just want the pain to stop 

okay, generally speaking, when people write AI behaviours, they want
those AI behaviours to do something and not just silently fail for six
months or longer

## Changelog
🆑
fix: Stoats have a chance to try and grab items like they always should
have.
/🆑
2025-11-28 13:35:53 +02:00

226 lines
9.1 KiB
Plaintext

#define BOT_CLEAN_PATH_LIMIT 15
#define POST_CLEAN_COOLDOWN 5 SECONDS
/datum/ai_controller/basic_controller/bot/cleanbot
blackboard = list(
BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/allow_items,
BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends,
BB_UNREACHABLE_LIST_COOLDOWN = 3 MINUTES,
BB_SALUTE_MESSAGES = list(
"salutes",
"nods in appreciation towards",
"mops the dirt away in the path of",
),
BB_FRIENDLY_MESSAGE = "empathetically acknowledges your hardwork and tough circumstances",
)
planning_subtrees = list(
/datum/ai_planning_subtree/escape_captivity/pacifist,
/datum/ai_planning_subtree/respond_to_summon,
/datum/ai_planning_subtree/pet_planning/cleanbot,
/datum/ai_planning_subtree/cleaning_subtree,
/datum/ai_planning_subtree/befriend_janitors,
/datum/ai_planning_subtree/acid_spray,
/datum/ai_planning_subtree/use_mob_ability/foam_area,
/datum/ai_planning_subtree/salute_authority,
/datum/ai_planning_subtree/find_patrol_beacon/cleanbot,
)
reset_keys = list(
BB_ACTIVE_PET_COMMAND,
BB_CLEAN_TARGET,
BB_BEACON_TARGET,
BB_PREVIOUS_BEACON_TARGET,
BB_BOT_SUMMON_TARGET,
)
///list that ties each flag to its list key
var/static/list/clean_flags = list(
BB_CLEANABLE_BLOOD = CLEANBOT_CLEAN_BLOOD,
BB_HUNTABLE_PESTS = CLEANBOT_CLEAN_PESTS,
BB_CLEANABLE_DRAWINGS = CLEANBOT_CLEAN_DRAWINGS,
BB_HUNTABLE_TRASH = CLEANBOT_CLEAN_TRASH,
)
/datum/ai_planning_subtree/pet_planning/cleanbot/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick)
var/mob/living/basic/bot/bot_pawn = controller.pawn
//we are DONE listening to orders
if(bot_pawn.bot_access_flags & BOT_COVER_EMAGGED)
return
return ..()
/datum/ai_planning_subtree/cleaning_subtree
/datum/ai_planning_subtree/cleaning_subtree/SelectBehaviors(datum/ai_controller/basic_controller/bot/cleanbot/controller, seconds_per_tick)
if(controller.blackboard_key_exists(BB_CLEAN_TARGET))
controller.queue_behavior(/datum/ai_behavior/execute_clean, BB_CLEAN_TARGET)
return SUBTREE_RETURN_FINISH_PLANNING
var/list/final_hunt_list = list()
final_hunt_list += controller.blackboard[BB_CLEANABLE_DECALS]
var/list/flag_list = controller.clean_flags
var/mob/living/basic/bot/cleanbot/bot_pawn = controller.pawn
for(var/list_key in flag_list)
if(!(bot_pawn.janitor_mode_flags & flag_list[list_key]))
continue
final_hunt_list += controller.blackboard[list_key]
controller.queue_behavior(/datum/ai_behavior/find_and_set/in_list/clean_targets, BB_CLEAN_TARGET, final_hunt_list)
/datum/ai_behavior/find_and_set/in_list/clean_targets
action_cooldown = 3 SECONDS
/datum/ai_behavior/find_and_set/in_list/clean_targets/search_tactic(datum/ai_controller/basic_controller/bot/controller, locate_paths, search_range = SEARCH_TACTIC_DEFAULT_RANGE)
var/list/found = typecache_filter_list(oview(search_range, controller.pawn), locate_paths)
var/list/ignore_list = controller.blackboard[BB_TEMPORARY_IGNORE_LIST]
for(var/atom/found_item in found)
if(QDELETED(controller.pawn))
break
if(LAZYACCESS(ignore_list, found_item))
continue
if(get_turf(found_item) == get_turf(controller.pawn))
return found_item
var/list/path = get_path_to(controller.pawn, found_item, max_distance = BOT_CLEAN_PATH_LIMIT, access = controller.get_access())
if(!length(path))
controller.add_to_blacklist(found_item)
continue
return found_item
/datum/ai_planning_subtree/acid_spray
/datum/ai_planning_subtree/acid_spray/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick)
var/mob/living/basic/bot/cleanbot/bot_pawn = controller.pawn
if(!(bot_pawn.bot_access_flags & BOT_COVER_EMAGGED))
return
if(controller.blackboard_key_exists(BB_ACID_SPRAY_TARGET))
controller.queue_behavior(/datum/ai_behavior/execute_clean, BB_ACID_SPRAY_TARGET)
return SUBTREE_RETURN_FINISH_PLANNING
controller.queue_behavior(/datum/ai_behavior/find_and_set/spray_target, BB_ACID_SPRAY_TARGET, /mob/living/carbon/human, 5)
/datum/ai_behavior/find_and_set/spray_target
action_cooldown = 30 SECONDS
behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION
/datum/ai_behavior/find_and_set/spray_target/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE)
var/list/ignore_list = controller.blackboard[BB_TEMPORARY_IGNORE_LIST]
for(var/mob/living/carbon/human/human_target in oview(search_range, controller.pawn))
if(LAZYACCESS(ignore_list, human_target))
continue
if(human_target.stat != CONSCIOUS || isnull(human_target.mind))
continue
return human_target
return null
/datum/ai_behavior/execute_clean
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION | AI_BEHAVIOR_REQUIRE_REACH
/datum/ai_behavior/execute_clean/setup(datum/ai_controller/controller, target_key)
. = ..()
var/turf/target = controller.blackboard[target_key]
if(isnull(target))
return FALSE
set_movement_target(controller, target)
/datum/ai_behavior/execute_clean/perform(seconds_per_tick, datum/ai_controller/controller, target_key)
var/mob/living/basic/living_pawn = controller.pawn
var/atom/target = controller.blackboard[target_key]
if(QDELETED(target))
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
living_pawn.UnarmedAttack(target, proximity_flag = TRUE)
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
/datum/ai_behavior/execute_clean/finish_action(datum/ai_controller/basic_controller/bot/controller, succeeded, target_key, targeting_strategy_key, hiding_location_key)
. = ..()
controller.set_blackboard_key(BB_POST_CLEAN_COOLDOWN, POST_CLEAN_COOLDOWN + world.time)
var/atom/target = controller.blackboard[target_key]
if(!succeeded && !isnull(target))
controller.clear_blackboard_key(target_key)
controller.add_to_blacklist(target)
return
if(QDELETED(target) || is_type_in_typecache(target, controller.blackboard[BB_HUNTABLE_TRASH]))
return
if(!iscarbon(target))
controller.clear_blackboard_key(target_key)
return
var/list/speech_list = controller.blackboard[BB_CLEANBOT_EMAGGED_PHRASES]
if(length(speech_list))
var/mob/living/living_pawn = controller.pawn
if(!QDELETED(living_pawn)) // pawn can be null at this point
living_pawn.say(pick(speech_list), forced = "ai controller")
controller.clear_blackboard_key(target_key)
/datum/ai_planning_subtree/use_mob_ability/foam_area
ability_key = BB_CLEANBOT_FOAM
finish_planning = FALSE
/datum/ai_planning_subtree/use_mob_ability/foam_area/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick)
var/mob/living/basic/bot/bot_pawn = controller.pawn
if(!(bot_pawn.bot_access_flags & BOT_COVER_EMAGGED))
return
return ..()
/datum/ai_planning_subtree/befriend_janitors
/datum/ai_planning_subtree/befriend_janitors/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick)
var/mob/living/basic/bot/bot_pawn = controller.pawn
//we are now evil. dont befriend the janitors
if(bot_pawn.bot_access_flags & BOT_COVER_EMAGGED)
return
if(controller.blackboard_key_exists(BB_FRIENDLY_JANITOR))
controller.queue_behavior(/datum/ai_behavior/befriend_target, BB_FRIENDLY_JANITOR, BB_FRIENDLY_MESSAGE)
return SUBTREE_RETURN_FINISH_PLANNING
controller.queue_behavior(/datum/ai_behavior/find_and_set/friendly_janitor, BB_FRIENDLY_JANITOR, /mob/living/carbon/human, 5)
/datum/ai_behavior/find_and_set/friendly_janitor
action_cooldown = 30 SECONDS
behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION
/datum/ai_behavior/find_and_set/friendly_janitor/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE)
var/mob/living/living_pawn = controller.pawn
for(var/mob/living/carbon/human/human_target in oview(search_range, living_pawn))
if(human_target.stat != CONSCIOUS || isnull(human_target.mind))
continue
if(!HAS_TRAIT(human_target, TRAIT_CLEANBOT_WHISPERER))
continue
if((living_pawn.faction.Find(REF(human_target))))
continue
return human_target
return null
/datum/ai_planning_subtree/find_patrol_beacon/cleanbot
/datum/ai_planning_subtree/find_patrol_beacon/cleanbot/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick)
if(controller.blackboard[BB_POST_CLEAN_COOLDOWN] >= world.time)
return
return ..()
/datum/pet_command/clean
command_name = "Clean"
command_desc = "Command a cleanbot to clean the mess."
requires_pointing = TRUE
radial_icon = 'icons/obj/service/janitor.dmi'
radial_icon_state = "mop"
speech_commands = list("clean", "mop")
/datum/pet_command/clean/set_command_target(mob/living/parent, atom/target)
if(isnull(target) || !istype(target, /obj/effect/decal/cleanable))
return FALSE
if(isnull(parent.ai_controller))
return FALSE
if(LAZYACCESS(parent.ai_controller.blackboard[BB_TEMPORARY_IGNORE_LIST], target))
return FALSE
return ..()
/datum/pet_command/clean/execute_action(datum/ai_controller/basic_controller/bot/controller)
if(controller.blackboard_key_exists(BB_CURRENT_PET_TARGET))
controller.queue_behavior(/datum/ai_behavior/execute_clean, BB_CURRENT_PET_TARGET)
return SUBTREE_RETURN_FINISH_PLANNING
controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND)
#undef BOT_CLEAN_PATH_LIMIT
#undef POST_CLEAN_COOLDOWN