Files
san7890 8f73588d9a Blood Drunk Miner Basic Boss Refactor - "Similar Enough" Edition (#94728)
## About The Pull Request

I thought megafauna were hard to refactor into simple mobs, and they
kinda are, but also enough work has been done on them through various
refactors (e.g. mob abilities) that it's not _too_ bad, but I didn't
really relish working on it.

Regardless, it's refactored! A few more of the ol' simple mobs flushed
down the toilet, with a bunch more features to make porting over more
`megafauna` to the basic mob's `boss` framework even simpler. There's
some weird patterns that are introduced in here to better fit the old
system's parity, but I don't really mind having done that since it's
more important to get stuff out of the simple mob framework and into
something a bit easier to work with and extend.

Here are all of the changes I can recall having made:

* A lot of the documentation regarding the blood drunk miner did not
actually meet reality. The current refactor reflects what the code was
actually doing, not what was documented.
* The code regarding using the saw's `melee attack chain` stuff wasn't
changed. Sorry but I can't even start to unravel that, I just overrode
the whole attack thing because it's not really incorporable from what I
was finding.
* Basic mobs operate differently than simple mobs, thus this mob will be
"harder" for a shorter amount of time as people are not used to the
current timings/pathfinding behavior/cooldowns/etc. of the modern blood
drunk miner. The overall difficulty didn't feel too different to me in
my playtesting, but changes can certainly be made if someone can tell me
which variable to fix.
* Basic Bosses now appear in the orbit menu as mob POIs, parity with
megafauna
* Basic Bosses can now use the boss music component.
## Why It's Good For The Game

Cleans up the code by incorporating it into a modern framework that
already accounts for a lot of the stuff that was taken for granted 8-9
years ago when this was first implemented, and tries to keep any
possible number the same in doing so. Should be much easier to add new
graphics or overhaul the boss's AI to transform it into something even
more interesting should someone choose to do so.

Let me know if I fucked up with signals/ai behavior patterns/etc.
somewhere as it's been a while since I touched this stuff and I had to
clean out a lot of cobwebs in my brain to get to this implementation.
## Changelog
🆑
balance: Miners Beware: Blood Drunk Miners have been refactored into
basic mobs. This means their timings and such may be a bit more
unpredictable than what you're used to. The difficulty should be about
the same, but do approach with caution lest you get devoured...
/🆑

Hopefully more people can pitch in with refactoring megafauna now... not
too bad anymore after I fixed some of the jank...
2026-01-12 01:36:54 +00:00

86 lines
3.3 KiB
Plaintext

/**
* Attaches to a mob and plays that music while they have a target.
*/
/datum/component/boss_music
///The music track we will play to players.
var/boss_track
///How long the track is, used to clear players out when the music is supposed to end.
var/track_duration
///List of all mobs listening to the boss music currently. Cleared on Destroy or after `track_duration`.
var/list/datum/weakref/players_listening_refs = list()
///List of callback timers, used to clear out mobs listening to boss music after `track_duration`.
var/list/music_callbacks = list()
/// Signal that we listen to on our parent to know when to start boss music.
/// On basic mobs, the signal should be a variant of `COMSIG_AI_BLACKBOARD_KEY_SET()`, feeding in whatever key you want.
var/signal = null
/datum/component/boss_music/Initialize(boss_track, signal)
. = ..()
if(!ismob(parent) || isnull(signal))
return COMPONENT_INCOMPATIBLE
src.boss_track = boss_track
src.signal = signal
track_duration = SSsounds.get_sound_length(boss_track)
/datum/component/boss_music/Destroy(force)
. = ..()
for(var/callback in music_callbacks)
deltimer(callback)
music_callbacks = null
for(var/player_refs in players_listening_refs)
clear_target(player_refs)
players_listening_refs = null
/datum/component/boss_music/RegisterWithParent()
. = ..()
if(isbasicmob(parent))
RegisterSignal(parent, signal, PROC_REF(basic_target_found))
else
RegisterSignal(parent, signal, PROC_REF(on_target_found))
/datum/component/boss_music/UnregisterFromParent()
UnregisterSignal(parent, signal)
return ..()
/// Handler wrapper for basic mobs getting a target. The signal we pass to basic mobs passes along the blackboard key which we can access off parent.
/datum/component/boss_music/proc/basic_target_found(mob/source, key)
SIGNAL_HANDLER
var/mob/new_target = source.ai_controller.blackboard[key]
on_target_found(source, new_target)
///Handles giving the boss music to a new target the fauna has received.
///Keeps track of them to not repeatedly overwrite its own track.
/datum/component/boss_music/proc/on_target_found(mob/source, mob/new_target)
SIGNAL_HANDLER
if(QDELETED(source) || !istype(new_target))
return
var/datum/weakref/new_ref = WEAKREF(new_target)
if(new_ref in players_listening_refs)
return
players_listening_refs += new_ref
RegisterSignal(new_target, COMSIG_LIVING_DEATH, PROC_REF(on_mob_death))
music_callbacks += addtimer(CALLBACK(src, PROC_REF(clear_target), new_ref), track_duration, TIMER_STOPPABLE)
new_target.playsound_local(new_target, boss_track, 200, FALSE, channel = CHANNEL_BOSS_MUSIC, pressure_affected = FALSE, use_reverb = FALSE)
///Called when a mob listening to boss music dies- ends their music early.
/datum/component/boss_music/proc/on_mob_death(mob/living/source)
SIGNAL_HANDLER
var/datum/weakref/player_ref = WEAKREF(source)
clear_target(player_ref)
///Removes `old_target` from the list of players listening, and stops their music if it is still playing.
///This allows them to have music played again if they re-enter combat with this fauna.
/datum/component/boss_music/proc/clear_target(datum/weakref/old_ref)
players_listening_refs -= old_ref
var/mob/old_target = old_ref?.resolve()
if(old_target)
UnregisterSignal(old_target, COMSIG_LIVING_DEATH)
old_target.stop_sound_channel(CHANNEL_BOSS_MUSIC)