diff --git a/code/__DEFINES/ai/blackboard_defines.dm b/code/__DEFINES/ai/blackboard_defines.dm index 27c543720d5..9ed9dc200e0 100644 --- a/code/__DEFINES/ai/blackboard_defines.dm +++ b/code/__DEFINES/ai/blackboard_defines.dm @@ -59,6 +59,11 @@ /// Key that holds the cooldown for our hunting subtree #define BB_HUNTING_COOLDOWN(type) "BB_HUNTING_COOLDOWN_[type]" +/// Chance to randomly acquire a new target +#define BB_RANDOM_AGGRO_CHANCE "BB_random_aggro_chance" +/// Chance to randomly drop all of our targets +#define BB_RANDOM_DEAGGRO_CHANCE "BB_random_deaggro_chance" + // Food and eating /// list of foods this mob likes diff --git a/code/datums/ai/basic_mobs/basic_subtrees/capricious_retaliate.dm b/code/datums/ai/basic_mobs/basic_subtrees/capricious_retaliate.dm new file mode 100644 index 00000000000..79e1dd05747 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/capricious_retaliate.dm @@ -0,0 +1,65 @@ +/// 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/targeting_strategy_key = BB_TARGETING_STRATEGY + /// Whether we should skip checking faction for our decision + var/ignore_faction = TRUE + +/datum/ai_planning_subtree/capricious_retaliate/select_behaviors(datum/ai_controller/controller, seconds_per_tick) + . = ..() + controller.queue_behavior(/datum/ai_behavior/capricious_retaliate, targeting_strategy_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, targeting_strategy_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)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + pawn.visible_message("[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) + controller.clear_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET) + controller.cancel_actions() // Otherwise they will try and get one last kick in + return AI_BEHAVIOR_DELAY + + var/aggro_chance = controller.blackboard[BB_RANDOM_AGGRO_CHANCE] || 0.5 + if(!SPT_PROB(aggro_chance, seconds_per_tick)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/aggro_range = controller.blackboard[BB_AGGRO_RANGE] || 10 + var/list/potential_targets = hearers(aggro_range, get_turf(pawn)) - pawn + if(!length(potential_targets)) + failed_targeting(pawn) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/datum/targeting_strategy/target_helper = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy_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_targeting(pawn) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + controller.insert_blackboard_key_lazylist(BB_BASIC_MOB_RETALIATE_LIST, final_target) + pawn.visible_message("[pawn] glares grumpily at [final_target]!") + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/// Called if we try but fail to target something +/datum/ai_behavior/capricious_retaliate/proc/failed_targeting(atom/pawn) + pawn.visible_message("[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) diff --git a/code/datums/ai/basic_mobs/generic_controllers.dm b/code/datums/ai/basic_mobs/generic_controllers.dm new file mode 100644 index 00000000000..d7766e1934a --- /dev/null +++ b/code/datums/ai/basic_mobs/generic_controllers.dm @@ -0,0 +1,134 @@ +/// Find a target, walk at target, attack intervening obstacles +/datum/ai_controller/basic_controller/simple/simple_hostile_obstacles + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) + +/// Prowls around when not attacking people +/datum/ai_controller/basic_controller/simple/simple_hostile_obstacles/prowler + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + /datum/ai_planning_subtree/find_and_hunt_target/prowl, + /datum/ai_planning_subtree/attack_obstacle_in_path/prowl, + ) + +/// Will ventcrawl around until it finds a target to attack. +/datum/ai_controller/basic_controller/simple/simple_hostile_obstacles/ventcrawler + blackboard = list ( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_VENT_SEARCH_RANGE = 10 + ) + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + /datum/ai_planning_subtree/ventcrawl_find_target, + /datum/ai_planning_subtree/ventcrawl, + ) + +/// Find a target, keep distance +/datum/ai_controller/basic_controller/simple/simple_ranged + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/maintain_distance, + /datum/ai_planning_subtree/ranged_skirmish, + ) + +/// Retaliate at range, keep distance +/datum/ai_controller/basic_controller/simple/simple_ranged_retaliate + planning_subtrees = list( + /datum/ai_planning_subtree/target_retaliate, + /datum/ai_planning_subtree/maintain_distance, + /datum/ai_planning_subtree/ranged_skirmish, + ) + +/// Find a target, walk towards it AND shoot it +/datum/ai_controller/basic_controller/simple/simple_skirmisher + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/ranged_skirmish, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) + +/// Wanders around until it finds something it can walk towards and shoot +/datum/ai_controller/basic_controller/simple/simple_skirmisher/prowler + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/ranged_skirmish, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + /datum/ai_planning_subtree/find_and_hunt_target/prowl, + /datum/ai_planning_subtree/attack_obstacle_in_path/prowl, + ) + +/// Use an ability on target on cooldown +/datum/ai_controller/basic_controller/simple/simple_ability + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/maintain_distance, + /datum/ai_planning_subtree/targeted_mob_ability, + ) + +/// Use an ability to retaliate on cooldown +/datum/ai_controller/basic_controller/simple/simple_ability_retaliate + planning_subtrees = list( + /datum/ai_planning_subtree/target_retaliate, + /datum/ai_planning_subtree/maintain_distance, + /datum/ai_planning_subtree/targeted_mob_ability, + ) + +/// Use an ability on target on cooldown, then try to punch them +/datum/ai_controller/basic_controller/simple/simple_ability_melee + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/targeted_mob_ability, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) + +/// Use an ability on target on cooldown, then try to shoot them +/datum/ai_controller/basic_controller/simple/simple_ability_ranged + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/maintain_distance, + /datum/ai_planning_subtree/targeted_mob_ability, + /datum/ai_planning_subtree/ranged_skirmish, + ) + +/// Fight back if attacked +/datum/ai_controller/basic_controller/simple/retaliate + ai_traits = AI_FLAG_STOP_MOVING_WHEN_PULLED + planning_subtrees = list( + /datum/ai_planning_subtree/target_retaliate, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) + +/// Get pissed at random people for no reason. Most often seen with goats. +/datum/ai_controller/basic_controller/simple/simple_capricious + ai_traits = AI_FLAG_STOP_MOVING_WHEN_PULLED + planning_subtrees = list( + /datum/ai_planning_subtree/capricious_retaliate, + /datum/ai_planning_subtree/target_retaliate, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) + +/// Runs away from anyone it sees +/datum/ai_controller/basic_controller/simple/simple_fearful + ai_traits = AI_FLAG_STOP_MOVING_WHEN_PULLED + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_nearest_target_to_flee, + /datum/ai_planning_subtree/flee_target, + ) + +/// Runs away when attacked +/datum/ai_controller/basic_controller/simple/simple_skittish + ai_traits = AI_FLAG_STOP_MOVING_WHEN_PULLED + planning_subtrees = list( + /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee, + /datum/ai_planning_subtree/flee_target, + ) diff --git a/code/datums/ai/basic_mobs/generic_controllers/hostile_controllers.dm b/code/datums/ai/basic_mobs/generic_controllers/hostile_controllers.dm deleted file mode 100644 index 8d285a38c08..00000000000 --- a/code/datums/ai/basic_mobs/generic_controllers/hostile_controllers.dm +++ /dev/null @@ -1,56 +0,0 @@ -/// Find a target, walk at target, attack intervening obstacles -/datum/ai_controller/basic_controller/simple/simple_hostile_obstacles - planning_subtrees = list( - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) - -/datum/ai_controller/basic_controller/simple/simple_hostile_obstacles/demonic_incursion - planning_subtrees = list( - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/find_and_hunt_target/prowl, - /datum/ai_planning_subtree/attack_obstacle_in_path/prowl, - ) - -/datum/ai_controller/basic_controller/simple/simple_hostile_obstacles/ventcrawler - blackboard = list ( - BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, - BB_VENT_SEARCH_RANGE = 10 - ) - planning_subtrees = list( - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/ventcrawl_find_target, - /datum/ai_planning_subtree/ventcrawl, - ) - -/// Find a target, walk towards it AND shoot it -/datum/ai_controller/basic_controller/simple/simple_skirmisher - planning_subtrees = list( - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/ranged_skirmish, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) - -/datum/ai_controller/basic_controller/simple/simple_skirmisher/demon_incursion - planning_subtrees = list( - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/ranged_skirmish, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/find_and_hunt_target/prowl, - /datum/ai_planning_subtree/attack_obstacle_in_path/prowl, - ) - -/// Fight back if attacked -/datum/ai_controller/basic_controller/simple/retaliate - ai_traits = AI_FLAG_STOP_MOVING_WHEN_PULLED - planning_subtrees = list( - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) diff --git a/code/modules/mob/living/basic/hostile/skeleton_mob.dm b/code/modules/mob/living/basic/hostile/skeleton_mob.dm index aaebfe7cc43..17aa73081ef 100644 --- a/code/modules/mob/living/basic/hostile/skeleton_mob.dm +++ b/code/modules/mob/living/basic/hostile/skeleton_mob.dm @@ -32,7 +32,7 @@ step_type = FOOTSTEP_MOB_SHOE gold_core_spawnable = HOSTILE_SPAWN loot = list(/obj/effect/decal/remains/human) - ai_controller = /datum/ai_controller/basic_controller/simple/simple_hostile_obstacles/demonic_incursion + ai_controller = /datum/ai_controller/basic_controller/simple/simple_hostile_obstacles/prowler /mob/living/basic/skeleton/Initialize(mapload) . = ..() diff --git a/code/modules/mob/living/basic/nether_mobs/nether_mobs.dm b/code/modules/mob/living/basic/nether_mobs/nether_mobs.dm index 3c5b169bcdc..211fd482c92 100644 --- a/code/modules/mob/living/basic/nether_mobs/nether_mobs.dm +++ b/code/modules/mob/living/basic/nether_mobs/nether_mobs.dm @@ -12,7 +12,7 @@ melee_attack_cooldown_min = 1.5 SECONDS melee_attack_cooldown_max = 2.5 SECONDS environment_smash = ENVIRONMENT_SMASH_STRUCTURES - ai_controller = /datum/ai_controller/basic_controller/simple/simple_hostile_obstacles/demonic_incursion + ai_controller = /datum/ai_controller/basic_controller/simple/simple_hostile_obstacles/prowler attack_verb_simple = "chomp" attack_verb_continuous = "chomps" attack_sound = 'sound/weapons/bladeslice.ogg' @@ -32,7 +32,7 @@ if(prob(grappler_chance)) AddComponent(/datum/component/ranged_attacks, projectile_type = /obj/item/projectile/energy/demonic_grappler, projectile_sound = 'sound/weapons/wave.ogg') name = "grappling " + name - ai_controller = new /datum/ai_controller/basic_controller/simple/simple_skirmisher/demon_incursion(src) + ai_controller = new /datum/ai_controller/basic_controller/simple/simple_skirmisher/prowler(src) update_appearance(UPDATE_NAME) color = "#5494DA" AddElement(/datum/element/ai_retaliate) diff --git a/paradise.dme b/paradise.dme index 378b356da8a..80e0179cd27 100644 --- a/paradise.dme +++ b/paradise.dme @@ -458,6 +458,7 @@ #include "code\datums\ai\ai_controller.dm" #include "code\datums\ai\ai_planning_subtree.dm" #include "code\datums\ai\basic_mobs\basic_controller.dm" +#include "code\datums\ai\basic_mobs\generic_controllers.dm" #include "code\datums\ai\basic_mobs\simple_controller.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\basic_attacking.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\befriend_target.dm" @@ -473,6 +474,7 @@ #include "code\datums\ai\basic_mobs\basic_ai_behaviors\travel_towards.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\attack_adjacent_target.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\attack_obstacle_in_path.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\capricious_retaliate.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\find_food.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\flee_target.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\maintain_distance.dm" @@ -491,7 +493,6 @@ #include "code\datums\ai\basic_mobs\basic_subtrees\use_mob_ability.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\ventcrawl_find_target.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\ventcrawl_subtree.dm" -#include "code\datums\ai\basic_mobs\generic_controllers\hostile_controllers.dm" #include "code\datums\ai\basic_mobs\hunting_behavior\find_and_hunt_target.dm" #include "code\datums\ai\basic_mobs\hunting_behavior\find_hunt_target.dm" #include "code\datums\ai\basic_mobs\hunting_behavior\hunt_target.dm"