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

🆑
fix: Rats will once again attempt to attack windows or other dense
objects separating them from their targets.
/🆑
This commit is contained in:
Jacquerel
2022-12-18 16:24:01 +00:00
committed by GitHub
parent 95eabee819
commit fa67ff6e81
3 changed files with 85 additions and 0 deletions
@@ -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