From fa67ff6e8143d8fe229f7cb8285873b6f565949d Mon Sep 17 00:00:00 2001 From: Jacquerel Date: Sun, 18 Dec 2022 16:24:01 +0000 Subject: [PATCH] Rats can attack obstacles between them and their target (#71741) ## About The Pull Request Fixes #71568 I wrote this for basic mob carp but it will be needed in a lot of places. Rats used to be able to attack windows or dense objects between them and their target, but basic mobs didn't have this capability. Now they do. Behind the scenes, an important change is that this adds `AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION` to `/datum/ai_behavior/basic_melee_attack`. This is because `basic_melee_attack` essentially enters a loop which won't end until the target is dead or lost, but there are plenty of circumstances where we'll actually want to interrupt this to do something else. Such as attack windows. ## Why It's Good For The Game Restores accidentally removed behaviour. Will likely be required for future basic mob development. ## Changelog :cl: fix: Rats will once again attempt to attack windows or other dense objects separating them from their targets. /:cl: --- .../basic_subtrees/attack_obstacle_in_path.dm | 77 +++++++++++++++++++ code/modules/mob/living/basic/vermin/mouse.dm | 7 ++ tgstation.dme | 1 + 3 files changed, 85 insertions(+) create mode 100644 code/datums/ai/basic_mobs/basic_subtrees/attack_obstacle_in_path.dm diff --git a/code/datums/ai/basic_mobs/basic_subtrees/attack_obstacle_in_path.dm b/code/datums/ai/basic_mobs/basic_subtrees/attack_obstacle_in_path.dm new file mode 100644 index 00000000000..6ff6c9f1ae8 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/attack_obstacle_in_path.dm @@ -0,0 +1,77 @@ +/// If there's something between us and our target then we need to queue a behaviour to make it not be there +/datum/ai_planning_subtree/attack_obstacle_in_path + /// Blackboard key containing current target + var/target_key = BB_BASIC_MOB_CURRENT_TARGET + /// The action to execute, extend to add a different cooldown or something + var/attack_behaviour = /datum/ai_behavior/attack_obstructions + +/datum/ai_planning_subtree/attack_obstacle_in_path/SelectBehaviors(datum/ai_controller/controller, delta_time) + . = ..() + var/datum/weakref/weak_target = controller.blackboard[target_key] + var/atom/target = weak_target?.resolve() + + if(QDELETED(target)) + return + + var/turf/next_step = get_step_towards(controller.pawn, target) + if (!next_step.is_blocked_turf(exclude_mobs = TRUE)) + return + + controller.queue_behavior(attack_behaviour, target_key) + // Don't cancel future planning, maybe we can move now + +/// Something is in our way, get it outta here +/datum/ai_behavior/attack_obstructions + action_cooldown = 1 SECONDS + /// If we should attack walls, be prepared for complaints about breaches + var/can_attack_turfs = FALSE + +/datum/ai_behavior/attack_obstructions/perform(delta_time, datum/ai_controller/controller, target_key) + . = ..() + var/mob/living/basic/basic_mob = controller.pawn + var/datum/weakref/weak_target = controller.blackboard[target_key] + var/atom/target = weak_target?.resolve() + + if (!target) + finish_action(controller, succeeded = FALSE) + return + + var/turf/next_step = get_step_towards(basic_mob, target) + var/dir_to_next_step = get_dir(basic_mob, next_step) + // If moving diagonally we need to punch both ways, or more accurately the one we are blocked in + var/list/dirs_to_move = list() + if (ISDIAGONALDIR(dir_to_next_step)) + for(var/direction in GLOB.cardinals) + if(direction & dir_to_next_step) + dirs_to_move += direction + else + dirs_to_move += dir_to_next_step + + for (var/direction in dirs_to_move) + if (attack_in_direction(controller, basic_mob, direction)) + return + +/datum/ai_behavior/attack_obstructions/proc/attack_in_direction(datum/ai_controller/controller, mob/living/basic/basic_mob, direction) + var/turf/next_step = get_step(basic_mob, direction) + if (!next_step.is_blocked_turf(exclude_mobs = TRUE)) + return FALSE + + for (var/obj/object as anything in next_step.contents) + if (!can_smash_object(basic_mob, object)) + continue + basic_mob.melee_attack(object) + return TRUE + + if (can_attack_turfs) + basic_mob.melee_attack(next_step) + return TRUE + return FALSE + +/datum/ai_behavior/attack_obstructions/proc/can_smash_object(mob/living/basic/basic_mob, obj/object) + if (!object.density) + return FALSE + if (object.IsObscured()) + return FALSE + if (basic_mob.see_invisible < object.invisibility) + return FALSE + return TRUE // It's in our way, let's get it out of our way diff --git a/code/modules/mob/living/basic/vermin/mouse.dm b/code/modules/mob/living/basic/vermin/mouse.dm index 0a659d3014c..1ec6f390c5e 100644 --- a/code/modules/mob/living/basic/vermin/mouse.dm +++ b/code/modules/mob/living/basic/vermin/mouse.dm @@ -371,6 +371,7 @@ planning_subtrees = list( /datum/ai_planning_subtree/pet_planning, /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/attack_obstacle_in_path/rat, /datum/ai_planning_subtree/basic_melee_attack_subtree/rat, /datum/ai_planning_subtree/find_and_hunt_target/look_for_cheese, /datum/ai_planning_subtree/random_speech/mouse, @@ -382,3 +383,9 @@ /datum/ai_behavior/basic_melee_attack/rat action_cooldown = 2 SECONDS + +/datum/ai_planning_subtree/attack_obstacle_in_path/rat + attack_behaviour = /datum/ai_behavior/attack_obstructions/rat + +/datum/ai_behavior/attack_obstructions/rat + action_cooldown = 2 SECONDS diff --git a/tgstation.dme b/tgstation.dme index 2ae7e5fa739..2c1792d830b 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -721,6 +721,7 @@ #include "code\datums\ai\basic_mobs\basic_ai_behaviors\targetting.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\tipped_reaction.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\try_mob_ability.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\attack_obstacle_in_path.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\simple_attack_target.dm"