mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-29 19:11:51 +00:00
* Synchronise AI and Player basic mob melee behaviours (#78337) ## About The Pull Request I like for things that mobs do to be consistent regardless of whether they are controlled by a player or by the AI. One big offender of this is the melee behaviour cooldown. Basic mobs piloted by AI have arbitrary melee attack cooldowns which are not reflected when they are controlled by players who can generally attack much faster (but in _two_ instances, slower). To remedy this I added `melee_attack_cooldown` as a var on `living/basic` (sinful) and the ai now uses NextMove to not click too often, meaning that players can only bite things as often as the AI can and also that if you VV the cooldown it can speed the AI up (or slow it down) as well as a player. This also gets rid of a lot of subtypes of that datum, as we mostly made them to change the cooldown. I also hunted down a few places where there was behaviour placed inside an AI behaviour which wasn't easily replicable by a player piloting the same mob, preferably a player should be able to do everything that the AI can. Fixing this was largely a simple case of moving code from `ai_behaviour/melee_attack/perform` to `basic/mob_subtype/melee_attack` and also adding an element for one thing shared by three different mobs. Strictly speaking I didn't need the element that much because a player is perfectly capable of clicking on something they attack to drag it, but it's nice for it to be automatic? ## Why It's Good For The Game If you see a mob do something then you should also be able to do it. Mobs shouldn't have significantly different capabilities when controlled by a player (aside from usually being smarter). ## Changelog 🆑 balance: Player-controlled basic mobs attack as fast as those mobs can when controlled by the AI balance: Player-controlled Faithless can paralyse people they attack, like the AI does balance: Player-controlled Star Gazers (if an admin felt like making one) apply the star mark on attack and deal damage to everything around them, like the AI does /🆑 * Synchronise AI and Player basic mob melee behaviours --------- Co-authored-by: Jacquerel <hnevard@gmail.com>
91 lines
3.6 KiB
Plaintext
91 lines
3.6 KiB
Plaintext
/**
|
|
* Delays outgoing attacks which are directed at mobs to give players time to get out of the way
|
|
*/
|
|
/datum/component/basic_mob_attack_telegraph
|
|
/// Time to wait before attack can complete
|
|
var/telegraph_duration
|
|
/// Overlay which we display over targets
|
|
var/mutable_appearance/target_overlay
|
|
/// Our current target, if we have one
|
|
var/mob/living/current_target
|
|
/// Callback executed when we start aiming at something
|
|
var/datum/callback/on_began_forecast
|
|
|
|
/datum/component/basic_mob_attack_telegraph/Initialize(
|
|
telegraph_icon = 'icons/mob/telegraphing/telegraph.dmi',
|
|
telegraph_state = ATTACK_EFFECT_BITE,
|
|
telegraph_duration = 0.4 SECONDS,
|
|
datum/callback/on_began_forecast,
|
|
)
|
|
. = ..()
|
|
if (!isbasicmob(parent))
|
|
return ELEMENT_INCOMPATIBLE
|
|
|
|
target_overlay = mutable_appearance(telegraph_icon, telegraph_state)
|
|
src.telegraph_duration = telegraph_duration
|
|
src.on_began_forecast = on_began_forecast
|
|
|
|
/datum/component/basic_mob_attack_telegraph/Destroy(force, silent)
|
|
if(current_target)
|
|
forget_target(current_target)
|
|
target_overlay = null
|
|
on_began_forecast = null
|
|
return ..()
|
|
|
|
/datum/component/basic_mob_attack_telegraph/RegisterWithParent()
|
|
. = ..()
|
|
RegisterSignal(parent, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(on_attack))
|
|
|
|
/datum/component/basic_mob_attack_telegraph/UnregisterFromParent()
|
|
if (current_target)
|
|
forget_target(current_target)
|
|
QDEL_NULL(target_overlay)
|
|
REMOVE_TRAIT(parent, TRAIT_BASIC_ATTACK_FORECAST, REF(src))
|
|
UnregisterSignal(parent, COMSIG_HOSTILE_PRE_ATTACKINGTARGET)
|
|
return ..()
|
|
|
|
/// When we attempt to attack, check if it is allowed
|
|
/datum/component/basic_mob_attack_telegraph/proc/on_attack(mob/living/basic/source, atom/target)
|
|
SIGNAL_HANDLER
|
|
if (!(isliving(target) || ismecha(target))) // Curse you CLARKE
|
|
return
|
|
if (HAS_TRAIT_FROM(source, TRAIT_BASIC_ATTACK_FORECAST, REF(src)))
|
|
REMOVE_TRAIT(source, TRAIT_BASIC_ATTACK_FORECAST, REF(src))
|
|
return
|
|
|
|
if (!DOING_INTERACTION(source, INTERACTION_BASIC_ATTACK_FORCEAST))
|
|
INVOKE_ASYNC(src, PROC_REF(delayed_attack), source, target)
|
|
return COMPONENT_HOSTILE_NO_ATTACK
|
|
|
|
/// Perform an attack after a delay
|
|
/datum/component/basic_mob_attack_telegraph/proc/delayed_attack(mob/living/basic/source, atom/target)
|
|
current_target = target
|
|
target.add_overlay(target_overlay)
|
|
RegisterSignal(target, COMSIG_QDELETING, PROC_REF(forget_target))
|
|
RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(target_moved))
|
|
|
|
on_began_forecast?.Invoke(target)
|
|
//we stop the do_after if the target moves out of neighboring turfs but if they dance around us they get their face smashed
|
|
if (!do_after(source, delay = telegraph_duration, target = target, timed_action_flags = IGNORE_TARGET_LOC_CHANGE, extra_checks = CALLBACK(source, TYPE_PROC_REF(/atom/movable, Adjacent), target), interaction_key = INTERACTION_BASIC_ATTACK_FORCEAST))
|
|
forget_target(target)
|
|
return
|
|
if (isnull(target)) // They got out of the way :(
|
|
return
|
|
ADD_TRAIT(source, TRAIT_BASIC_ATTACK_FORECAST, REF(src))
|
|
forget_target(target)
|
|
source.melee_attack(target, ignore_cooldown = TRUE) // We already started the cooldown when we triggered the forecast
|
|
|
|
/// The guy we're trying to attack moved, is he still in range?
|
|
/datum/component/basic_mob_attack_telegraph/proc/target_moved(atom/target)
|
|
SIGNAL_HANDLER
|
|
if (in_range(parent, target))
|
|
return
|
|
forget_target(target)
|
|
|
|
/// The guy we're trying to attack isn't a valid target any more
|
|
/datum/component/basic_mob_attack_telegraph/proc/forget_target(atom/target)
|
|
SIGNAL_HANDLER
|
|
current_target = null
|
|
target.cut_overlay(target_overlay)
|
|
UnregisterSignal(target, list(COMSIG_QDELETING, COMSIG_MOVABLE_MOVED))
|