From 6a556355e8faedeb17b2a585446d4a9779d4dfe7 Mon Sep 17 00:00:00 2001 From: Jacquerel Date: Thu, 17 Apr 2025 02:03:17 +0100 Subject: [PATCH] Replace basic mob attack telegraph with slower reaction times (#90585) ## About The Pull Request This PR removes the `basic_mob_attack_telegraph` component from all mobs which were using it and instead implements an attack delay directly into the basic melee AI behaviour. This delay simulates "moving your mouse into position", human reaction times, and the fact that the previous implentation of simple mobs would only try to melee attack on a predictable timer you could "juke" around. The way that this delay works is that it starts as soon as the mob enters melee range and resets if you are ever out of the mob's reach, so there will be no delay if you are foolish enough (or unfortunately preventing from doing otherwise) to continue standing next to a mob but there is enough of a one to duck in and out of melee range with a goliath while using a crusher without getting hit, although trying to run past one usually won't work. This delay defaults to 0.3 seconds which in my testing experience was roughly enough to dip in and out of range without getting hit but not enough to run all the way past a mob without getting hit. It can be overriden via the blackboard, although currently no mob does this. I _was_ testing this locally with no latency though so I guess we'll listen out to see if miners start yelling. ## Why It's Good For The Game The visible attack broadcast is very cool but its visibility made melee combat with any mob that had it significantly easier to such a degree that any mob with it on could not really be expected to do melee damage to any character that wasn't suffering some kind of immobilisation effect which was never really the intention. Removing it also removes any additional handicap that was applied to sapient versions of these mobs, which was never really necessary or intended in the first place. I did not remove the component entirely from the game for two reasons: - Out of the hope that there is some use for it somewhere, because I think it's cool, but more importantly: - Because it's still being used by simple mob megafauna as a workaround for a bug we couldn't figure out. ## Changelog :cl: balance: Most mobs will now hesitate for a moment before attacking rather than instantly hitting anything that enters melee range, to better simulate human behaviour. Please report if this delay seems too short, or too long. balance: Most mobs which telegraphed their basic attacks on you now will not do that /:cl: --- code/__DEFINES/ai/ai_blackboard.dm | 5 +++ .../basic_ai_behaviors/basic_attacking.dm | 32 +++++++++++++++---- .../mob/living/basic/boss/thing/thing.dm | 1 - .../basic/icemoon/ice_whelp/ice_whelp.dm | 1 - .../mob/living/basic/icemoon/wolf/wolf.dm | 1 - .../basic/lavaland/basilisk/basilisk.dm | 1 - .../living/basic/lavaland/goliath/goliath.dm | 1 - .../basic/lavaland/legion/legion_brood.dm | 1 - .../basic/lavaland/legion/legion_monkey.dm | 1 - 9 files changed, 30 insertions(+), 14 deletions(-) diff --git a/code/__DEFINES/ai/ai_blackboard.dm b/code/__DEFINES/ai/ai_blackboard.dm index c09333c6ed7..a4b4e5a15ea 100644 --- a/code/__DEFINES/ai/ai_blackboard.dm +++ b/code/__DEFINES/ai/ai_blackboard.dm @@ -64,6 +64,11 @@ ///Basic Mob Keys +/// How long to wait before attacking a target in range +#define BB_BASIC_MOB_MELEE_DELAY "BB_basic_melee_delay" +/// Key used to store the time we can actually attack +#define BB_BASIC_MOB_MELEE_COOLDOWN_TIMER "BB_basic_melee_cooldown_timer" + ///Targeting subtrees #define BB_BASIC_MOB_CURRENT_TARGET "BB_basic_current_target" #define BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION "BB_basic_current_target_hiding_location" diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/basic_attacking.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/basic_attacking.dm index aba62f2dc7b..b75fc1ef38e 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/basic_attacking.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/basic_attacking.dm @@ -1,6 +1,9 @@ +/// Amount of time to wait before executing attack if not specified +#define DEFAULT_ATTACK_DELAY (0.4 SECONDS) + /datum/ai_behavior/basic_melee_attack action_cooldown = 0.2 SECONDS // We gotta check unfortunately often because we're in a race condition with nextmove - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION ///do we finish this action after hitting once? var/terminate_after_action = FALSE @@ -16,20 +19,32 @@ set_movement_target(controller, target) /datum/ai_behavior/basic_melee_attack/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) + var/atom/target = controller.blackboard[target_key] + if (isnull(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if (!controller.pawn.CanReach(target)) + controller.clear_blackboard_key(BB_BASIC_MOB_MELEE_COOLDOWN_TIMER) + return AI_BEHAVIOR_INSTANT + + var/can_attack_time = controller.blackboard[BB_BASIC_MOB_MELEE_COOLDOWN_TIMER] + if (isnull(can_attack_time)) + var/blackboard_delay = controller.blackboard[BB_BASIC_MOB_MELEE_DELAY] + var/attack_delay = isnull(blackboard_delay) ? DEFAULT_ATTACK_DELAY : blackboard_delay + controller.set_blackboard_key(BB_BASIC_MOB_MELEE_COOLDOWN_TIMER, world.time + attack_delay) + return AI_BEHAVIOR_INSTANT + if (can_attack_time > world.time) + return AI_BEHAVIOR_INSTANT + if (isliving(controller.pawn)) var/mob/living/pawn = controller.pawn if (world.time < pawn.next_move) return AI_BEHAVIOR_INSTANT - var/mob/living/basic/basic_mob = controller.pawn - //targeting strategy will kill the action if not real anymore - var/atom/target = controller.blackboard[target_key] var/datum/targeting_strategy/targeting_strategy = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy_key]) - - if(!targeting_strategy.can_attack(basic_mob, target)) + if(!targeting_strategy.can_attack(controller.pawn, target)) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - var/hiding_target = targeting_strategy.find_hidden_mobs(basic_mob, target) //If this is valid, theyre hidden in something! + var/hiding_target = targeting_strategy.find_hidden_mobs(controller.pawn, target) //If this is valid, theyre hidden in something! controller.set_blackboard_key(hiding_location_key, hiding_target) @@ -41,6 +56,7 @@ /datum/ai_behavior/basic_melee_attack/finish_action(datum/ai_controller/controller, succeeded, target_key, targeting_strategy_key, hiding_location_key) . = ..() + controller.clear_blackboard_key(BB_BASIC_MOB_MELEE_COOLDOWN_TIMER) if(!succeeded) controller.clear_blackboard_key(target_key) @@ -145,3 +161,5 @@ /datum/ai_behavior/basic_ranged_attack/avoid_friendly_fire avoid_friendly_fire = TRUE + +#undef DEFAULT_ATTACK_DELAY diff --git a/code/modules/mob/living/basic/boss/thing/thing.dm b/code/modules/mob/living/basic/boss/thing/thing.dm index 4ac6f36e6db..402f9aba436 100644 --- a/code/modules/mob/living/basic/boss/thing/thing.dm +++ b/code/modules/mob/living/basic/boss/thing/thing.dm @@ -54,7 +54,6 @@ /datum/action/cooldown/mob_cooldown/the_thing/acid_spit = BB_THETHING_ACIDSPIT, ) grant_actions_by_list(innate_actions) - AddComponent(/datum/component/basic_mob_attack_telegraph, telegraph_duration = 0.4 SECONDS) AddElement(/datum/element/relay_attackers) // used to immediately aggro if shot from outside aggro range RegisterSignal(src, COMSIG_ATOM_WAS_ATTACKED, PROC_REF(immediate_aggro)) maploaded = mapload diff --git a/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.dm b/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.dm index a270692991a..e30a2910ca0 100644 --- a/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.dm +++ b/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.dm @@ -44,7 +44,6 @@ AddElement(/datum/element/footstep, FOOTSTEP_MOB_HEAVY) AddComponent(/datum/component/basic_mob_ability_telegraph) - AddComponent(/datum/component/basic_mob_attack_telegraph, telegraph_duration = 0.6 SECONDS) var/static/list/innate_actions = list( /datum/action/cooldown/mob_cooldown/fire_breath/ice = BB_WHELP_STRAIGHTLINE_FIRE, diff --git a/code/modules/mob/living/basic/icemoon/wolf/wolf.dm b/code/modules/mob/living/basic/icemoon/wolf/wolf.dm index f590d4ef54f..d06fea3a4da 100644 --- a/code/modules/mob/living/basic/icemoon/wolf/wolf.dm +++ b/code/modules/mob/living/basic/icemoon/wolf/wolf.dm @@ -60,7 +60,6 @@ AddElement(/datum/element/footstep, FOOTSTEP_MOB_CLAW) AddElement(/datum/element/ai_flee_while_injured) AddElement(/datum/element/ai_retaliate) - AddComponent(/datum/component/basic_mob_attack_telegraph, telegraph_duration = 0.6 SECONDS) if(can_tame) make_tameable() diff --git a/code/modules/mob/living/basic/lavaland/basilisk/basilisk.dm b/code/modules/mob/living/basic/lavaland/basilisk/basilisk.dm index 5f13d53160a..bd1539103f4 100644 --- a/code/modules/mob/living/basic/lavaland/basilisk/basilisk.dm +++ b/code/modules/mob/living/basic/lavaland/basilisk/basilisk.dm @@ -29,7 +29,6 @@ /mob/living/basic/mining/basilisk/Initialize(mapload) . = ..() - AddComponent(/datum/component/basic_mob_attack_telegraph) ranged_attacks = AddComponent(/datum/component/ranged_attacks, projectile_type = /obj/projectile/temp/watcher, projectile_sound = 'sound/items/weapons/pierce.ogg') RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(check_lava)) diff --git a/code/modules/mob/living/basic/lavaland/goliath/goliath.dm b/code/modules/mob/living/basic/lavaland/goliath/goliath.dm index 4d6ce4753d3..3d024564a86 100644 --- a/code/modules/mob/living/basic/lavaland/goliath/goliath.dm +++ b/code/modules/mob/living/basic/lavaland/goliath/goliath.dm @@ -65,7 +65,6 @@ ) AddComponent(/datum/component/ai_target_timer) - AddComponent(/datum/component/basic_mob_attack_telegraph) AddComponentFrom(INNATE_TRAIT, /datum/component/shovel_hands) if (tameable) AddComponent(/datum/component/tameable, tame_chance = 10, bonus_tame_chance = 5) diff --git a/code/modules/mob/living/basic/lavaland/legion/legion_brood.dm b/code/modules/mob/living/basic/lavaland/legion/legion_brood.dm index e86dfa00805..e09a617f122 100644 --- a/code/modules/mob/living/basic/lavaland/legion/legion_brood.dm +++ b/code/modules/mob/living/basic/lavaland/legion/legion_brood.dm @@ -38,7 +38,6 @@ AddElement(/datum/element/simple_flying) AddComponent(/datum/component/swarming) AddComponent(/datum/component/clickbox, icon_state = "sphere", max_scale = 2) - AddComponent(/datum/component/basic_mob_attack_telegraph) addtimer(CALLBACK(src, PROC_REF(death)), 10 SECONDS) /mob/living/basic/legion_brood/death(gibbed) diff --git a/code/modules/mob/living/basic/lavaland/legion/legion_monkey.dm b/code/modules/mob/living/basic/lavaland/legion/legion_monkey.dm index 9526dcaef52..3d54bdccb8c 100644 --- a/code/modules/mob/living/basic/lavaland/legion/legion_monkey.dm +++ b/code/modules/mob/living/basic/lavaland/legion/legion_monkey.dm @@ -20,7 +20,6 @@ /mob/living/basic/mining/legion/monkey/Initialize(mapload) . = ..() ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) - AddComponent(/datum/component/basic_mob_attack_telegraph) AddComponent(/datum/component/regenerator, outline_colour = COLOR_SOFT_RED) /mob/living/basic/mining/legion/monkey/assign_abilities()