mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-07-18 19:44:58 +01:00
31d4eb04f1
## 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. /🆑
293 lines
12 KiB
Plaintext
293 lines
12 KiB
Plaintext
/datum/ai_controller/basic_controller/cat
|
|
blackboard = list(
|
|
BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
|
|
BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends,
|
|
BB_HOSTILE_MEOWS = list("Mawwww", "Mrewwww", "mhhhhng..."),
|
|
BB_BABIES_PARTNER_TYPES = list(/mob/living/basic/pet/cat),
|
|
BB_BABIES_CHILD_TYPES = list(/mob/living/basic/pet/cat/kitten),
|
|
)
|
|
|
|
ai_movement = /datum/ai_movement/basic_avoidance
|
|
idle_behavior = /datum/idle_behavior/idle_random_walk
|
|
planning_subtrees = list(
|
|
/datum/ai_planning_subtree/escape_captivity/pacifist,
|
|
/datum/ai_planning_subtree/pet_planning,
|
|
/datum/ai_planning_subtree/reside_in_home,
|
|
/datum/ai_planning_subtree/flee_target/from_flee_key/cat_struggle,
|
|
/datum/ai_planning_subtree/find_and_hunt_target/hunt_mice,
|
|
/datum/ai_planning_subtree/find_and_hunt_target/find_cat_food,
|
|
/datum/ai_planning_subtree/simple_find_target,
|
|
/datum/ai_planning_subtree/haul_food_to_young,
|
|
/datum/ai_planning_subtree/territorial_struggle,
|
|
/datum/ai_planning_subtree/make_babies,
|
|
/datum/ai_planning_subtree/random_speech/cats,
|
|
)
|
|
|
|
/datum/ai_planning_subtree/reside_in_home
|
|
///chance we enter our home
|
|
var/reside_chance = 5
|
|
///chance we leave our home
|
|
var/leave_home_chance = 15
|
|
|
|
/datum/ai_planning_subtree/reside_in_home/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
|
|
var/mob/living/living_pawn = controller.pawn
|
|
|
|
if(controller.blackboard_key_exists(BB_CAT_HOME))
|
|
controller.queue_behavior(/datum/ai_behavior/enter_cat_home, BB_CAT_HOME)
|
|
return
|
|
|
|
if(istype(living_pawn.loc, /obj/structure/cat_house))
|
|
if(SPT_PROB(leave_home_chance, seconds_per_tick))
|
|
controller.set_blackboard_key(BB_CAT_HOME, living_pawn.loc)
|
|
return SUBTREE_RETURN_FINISH_PLANNING
|
|
|
|
if(SPT_PROB(reside_chance, seconds_per_tick))
|
|
controller.queue_behavior(/datum/ai_behavior/find_and_set/valid_home, BB_CAT_HOME, /obj/structure/cat_house)
|
|
|
|
/datum/ai_behavior/find_and_set/valid_home/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE)
|
|
for(var/obj/structure/cat_house/home in oview(search_range, controller.pawn))
|
|
if(home.resident_cat)
|
|
continue
|
|
return home
|
|
|
|
return null
|
|
|
|
/datum/ai_behavior/enter_cat_home
|
|
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION | AI_BEHAVIOR_REQUIRE_REACH
|
|
|
|
/datum/ai_behavior/enter_cat_home/setup(datum/ai_controller/controller, target_key)
|
|
. = ..()
|
|
var/atom/target = controller.blackboard[target_key]
|
|
if(QDELETED(target))
|
|
return FALSE
|
|
set_movement_target(controller, target)
|
|
|
|
/datum/ai_behavior/enter_cat_home/perform(seconds_per_tick, datum/ai_controller/controller, target_key)
|
|
var/obj/structure/cat_house/home = controller.blackboard[target_key]
|
|
var/mob/living/basic/living_pawn = controller.pawn
|
|
if(living_pawn == home.resident_cat || isnull(home.resident_cat))
|
|
controller.ai_interact(target = home)
|
|
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
|
|
|
|
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
|
|
|
|
/datum/ai_behavior/enter_cat_home/finish_action(datum/ai_controller/controller, success, target_key)
|
|
. = ..()
|
|
controller.clear_blackboard_key(target_key)
|
|
|
|
/datum/ai_planning_subtree/flee_target/from_flee_key/cat_struggle
|
|
flee_behaviour = /datum/ai_behavior/run_away_from_target/cat_struggle
|
|
|
|
/datum/ai_behavior/run_away_from_target/cat_struggle
|
|
clear_failed_targets = TRUE
|
|
|
|
/datum/ai_planning_subtree/territorial_struggle
|
|
///chance we become hostile to another cat
|
|
var/hostility_chance = 5
|
|
|
|
/datum/ai_planning_subtree/territorial_struggle/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
|
|
var/mob/living/living_pawn = controller.pawn
|
|
if(living_pawn.gender != MALE || !SPT_PROB(hostility_chance, seconds_per_tick))
|
|
return
|
|
if(controller.blackboard_key_exists(BB_TRESSPASSER_TARGET))
|
|
controller.queue_behavior(/datum/ai_behavior/territorial_struggle, BB_TRESSPASSER_TARGET, BB_HOSTILE_MEOWS)
|
|
return SUBTREE_RETURN_FINISH_PLANNING
|
|
|
|
controller.queue_behavior(/datum/ai_behavior/find_and_set/cat_tresspasser, BB_TRESSPASSER_TARGET, /mob/living/basic/pet/cat)
|
|
|
|
/datum/ai_behavior/find_and_set/cat_tresspasser/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE)
|
|
var/list/ignore_types = controller.blackboard[BB_BABIES_CHILD_TYPES]
|
|
for(var/mob/living/basic/pet/cat/potential_enemy in oview(search_range, controller.pawn))
|
|
if(potential_enemy.gender != MALE)
|
|
continue
|
|
if(is_type_in_list(potential_enemy, ignore_types))
|
|
continue
|
|
var/datum/ai_controller/basic_controller/enemy_controller = potential_enemy.ai_controller
|
|
if(isnull(enemy_controller))
|
|
continue
|
|
//theyre already engaged in a battle, leave them alone!
|
|
if(enemy_controller.blackboard_key_exists(BB_TRESSPASSER_TARGET))
|
|
continue
|
|
//u choose me and i choose u
|
|
enemy_controller.set_blackboard_key(BB_TRESSPASSER_TARGET, controller.pawn)
|
|
return potential_enemy
|
|
return null
|
|
|
|
/datum/ai_behavior/territorial_struggle
|
|
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION | AI_BEHAVIOR_REQUIRE_REACH
|
|
action_cooldown = 5 SECONDS
|
|
///chance the battle ends!
|
|
var/end_battle_chance = 25
|
|
|
|
/datum/ai_behavior/territorial_struggle/setup(datum/ai_controller/controller, target_key)
|
|
. = ..()
|
|
var/mob/living/living_pawn = controller.pawn
|
|
var/mob/living/target = controller.blackboard[target_key]
|
|
if(QDELETED(target))
|
|
return FALSE
|
|
if(target.ai_controller?.blackboard[target_key] != living_pawn)
|
|
return FALSE
|
|
set_movement_target(controller, target)
|
|
|
|
/datum/ai_behavior/territorial_struggle/perform(seconds_per_tick, datum/ai_controller/controller, target_key, cries_key)
|
|
. = ..()
|
|
var/mob/living/target = controller.blackboard[target_key]
|
|
|
|
if(QDELETED(target))
|
|
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
|
|
|
|
var/mob/living/living_pawn = controller.pawn
|
|
var/list/threaten_list = controller.blackboard[cries_key]
|
|
if(length(threaten_list))
|
|
living_pawn.say(pick(threaten_list), forced = "ai_controller")
|
|
|
|
if(!prob(end_battle_chance))
|
|
return
|
|
|
|
//50 50 chance we lose
|
|
var/datum/ai_controller/loser_controller = prob(50) ? controller : target.ai_controller
|
|
|
|
loser_controller.set_blackboard_key(BB_BASIC_MOB_FLEE_TARGET, target)
|
|
target.ai_controller.clear_blackboard_key(BB_TRESSPASSER_TARGET)
|
|
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
|
|
|
|
/datum/ai_behavior/territorial_struggle/finish_action(datum/ai_controller/controller, success, target_key)
|
|
. = ..()
|
|
controller.clear_blackboard_key(target_key)
|
|
|
|
/datum/ai_planning_subtree/find_and_hunt_target/hunt_mice
|
|
target_key = BB_MOUSE_TARGET
|
|
hunting_behavior = /datum/ai_behavior/play_with_mouse
|
|
finding_behavior = /datum/ai_behavior/find_hunt_target/hunt_mice
|
|
hunt_targets = list(/mob/living/basic/mouse)
|
|
hunt_chance = 75
|
|
hunt_range = 9
|
|
|
|
/datum/ai_planning_subtree/find_and_hunt_target/hunt_mice/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
|
|
var/mob/living/living_pawn = controller.pawn
|
|
var/list/items_we_carry = typecache_filter_list(living_pawn, controller.blackboard[BB_HUNTABLE_PREY])
|
|
if(length(items_we_carry))
|
|
return
|
|
return ..()
|
|
|
|
|
|
/datum/ai_behavior/find_hunt_target/hunt_mice/valid_dinner(mob/living/source, mob/living/mouse, radius)
|
|
if(mouse.stat == DEAD || mouse.mind)
|
|
return FALSE
|
|
return can_see(source, mouse, radius)
|
|
|
|
//play as in kill
|
|
/datum/ai_behavior/play_with_mouse
|
|
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION | AI_BEHAVIOR_REQUIRE_REACH
|
|
action_cooldown = 10 SECONDS
|
|
///chance we hunt the mouse!
|
|
var/consume_chance = 70
|
|
|
|
/datum/ai_behavior/play_with_mouse/setup(datum/ai_controller/controller, target_key)
|
|
. = ..()
|
|
var/mob/living/target = controller.blackboard[target_key]
|
|
if(QDELETED(target))
|
|
return FALSE
|
|
set_movement_target(controller, target)
|
|
|
|
/datum/ai_behavior/play_with_mouse/perform(seconds_per_tick, datum/ai_controller/controller, target_key)
|
|
var/mob/living/basic/mouse/target = controller.blackboard[target_key]
|
|
|
|
if(QDELETED(target))
|
|
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
|
|
|
|
consume_chance = istype(target, /mob/living/basic/mouse/brown/tom) ? 5 : initial(consume_chance)
|
|
if(prob(consume_chance))
|
|
target.splat()
|
|
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
|
|
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
|
|
|
|
/datum/ai_behavior/play_with_mouse/finish_action(datum/ai_controller/controller, success, target_key)
|
|
. = ..()
|
|
var/mob/living/living_pawn = controller.pawn
|
|
var/atom/target = controller.blackboard[target_key]
|
|
controller.clear_blackboard_key(target_key)
|
|
if(isnull(target) || QDELETED(living_pawn))
|
|
return
|
|
var/manual_emote = "attempts to hunt [target]..."
|
|
var/end_result = success ? "and succeeds!" : "but fails!"
|
|
manual_emote += end_result
|
|
living_pawn.manual_emote(manual_emote)
|
|
|
|
/datum/ai_planning_subtree/find_and_hunt_target/find_cat_food
|
|
target_key = BB_CAT_FOOD_TARGET
|
|
hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/find_cat_food
|
|
finding_behavior = /datum/ai_behavior/find_hunt_target/find_cat_food
|
|
hunt_targets = list(/obj/item/fish, /obj/item/food/deadmouse, /obj/item/food/fishmeat)
|
|
hunt_chance = 75
|
|
hunt_range = 9
|
|
|
|
/datum/ai_behavior/hunt_target/interact_with_target/find_cat_food
|
|
always_reset_target = TRUE
|
|
|
|
/datum/ai_behavior/find_hunt_target/find_cat_food/valid_dinner(mob/living/source, atom/dinner, radius)
|
|
//this food is already near a kitten, let the kitten eat it
|
|
var/mob/living/nearby_kitten = locate(/mob/living/basic/pet/cat/kitten) in oview(2, dinner)
|
|
if(nearby_kitten && nearby_kitten != source)
|
|
return FALSE
|
|
return can_see(source, dinner, radius)
|
|
|
|
/datum/ai_planning_subtree/haul_food_to_young/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
|
|
if(!controller.blackboard_key_exists(BB_FOOD_TO_DELIVER))
|
|
controller.queue_behavior(/datum/ai_behavior/find_and_set/in_hands/given_list, BB_FOOD_TO_DELIVER, controller.blackboard[BB_HUNTABLE_PREY])
|
|
return
|
|
if(!controller.blackboard_key_exists(BB_KITTEN_TO_FEED))
|
|
controller.queue_behavior(/datum/ai_behavior/find_and_set/valid_kitten, BB_KITTEN_TO_FEED, /mob/living/basic/pet/cat/kitten)
|
|
return
|
|
|
|
controller.queue_behavior(/datum/ai_behavior/deliver_food_to_kitten, BB_KITTEN_TO_FEED, BB_FOOD_TO_DELIVER)
|
|
|
|
/datum/ai_behavior/find_and_set/valid_kitten
|
|
|
|
/datum/ai_behavior/find_and_set/valid_kitten/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE)
|
|
var/mob/living/kitten = locate(locate_path) in oview(search_range, controller.pawn)
|
|
//kitten already has food near it, go feed another hungry kitten
|
|
|
|
if(isnull(kitten))
|
|
return null
|
|
|
|
var/list/nearby_food = typecache_filter_list(oview(2, kitten), controller.blackboard[BB_HUNTABLE_PREY])
|
|
if(kitten.stat != DEAD && !length(nearby_food))
|
|
return kitten
|
|
return null
|
|
|
|
/datum/ai_behavior/deliver_food_to_kitten
|
|
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION | AI_BEHAVIOR_REQUIRE_REACH
|
|
action_cooldown = 5 SECONDS
|
|
|
|
/datum/ai_behavior/deliver_food_to_kitten/setup(datum/ai_controller/controller, target_key, food_key)
|
|
. = ..()
|
|
var/mob/living/target = controller.blackboard[target_key]
|
|
if(QDELETED(target))
|
|
return FALSE
|
|
set_movement_target(controller, target)
|
|
|
|
/datum/ai_behavior/deliver_food_to_kitten/perform(seconds_per_tick, datum/ai_controller/controller, target_key, food_key)
|
|
var/mob/living/target = controller.blackboard[target_key]
|
|
|
|
if(QDELETED(target))
|
|
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
|
|
|
|
var/mob/living/living_pawn = controller.pawn
|
|
var/atom/movable/food = controller.blackboard[food_key]
|
|
|
|
if(isnull(food) || !(food in living_pawn))
|
|
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
|
|
|
|
food.forceMove(get_turf(living_pawn))
|
|
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
|
|
|
|
/datum/ai_behavior/deliver_food_to_kitten/finish_action(datum/ai_controller/controller, success, target_key, food_key)
|
|
. = ..()
|
|
controller.clear_blackboard_key(target_key)
|
|
controller.clear_blackboard_key(food_key)
|
|
|
|
|
|
|
|
|