Files
Bubberstation/code/datums/components/basic_mob_attack_telegraph.dm
SkyratBot c2639c816b [MIRROR] Synchronise AI and Player basic mob melee behaviours [MDB IGNORE] (#23780)
* 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>
2023-09-18 15:23:14 -04:00

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))