Files
Bubberstation/code/datums/components/blob_minion.dm
Krysonism 26527f85e7 Da blobmob update! Indepedent strains, new sprites, egglike spores, fixes, sounds and buffs! #Cytology2025 (#91368)
This PR makes a number of changes focusing on improving the blob
minions, spores, nauts, zombies and their associated component.

The blob spore, blob zombie and blobbernaut has been resprited.

The spore and zombie have been lightly touched to preserve the most of
the original characteristics while given a cleaner look.

The spore and zombie have a partially desaturated version used to let
more of the strain colour through instead of them all ending up dark
brown.

The blobbernaut has been reshaded and the side sprite has been made
coherent with the front state, I made decided how the front state was
shaded should be the "canon" one (this might be a bit controversial but
the wild inconsistency was bugging me.)

The blobbernaut is a bit less veiny, but the veins look more natural and
use the strain complementary colour. Many combinations are cool, some
are a bit lacking due to the weird choices of complementary colour.

![image](https://github.com/user-attachments/assets/aee01fb1-5d3a-47ad-abd6-85096f8b8751)

![image](https://github.com/user-attachments/assets/732a4157-c7f6-4a43-ad46-62a505a97088)

Blob mobs can now have strains independently of an overmind.

The 15% mutation chance of vat grown creatures causes a spore or
blobbernaut to get a random strain.

When I first added the blob spore cell line, ghosts could click on cyto
blob spores to posses them, they would then presumably(?) but not
explicitly be free antags. This ability was lost when the blob spore
code was modernised.

Very few people knew about this, and no one grew blob spores anyway.

This feature is coming back in a big way, vat grown blob spores present
a new unique job hazard, they are automatically offered to ghost as an
extremely shitty, but free antag.

I have tested spawning like 15 antag pre-buff blob spores in a live
round and they failed completely to antagonise the crew effectively,
hopefully these buffed spores won't present too much of an issue to our
great administration team, if that ends up being the case, there are
many levers to pull to tone them down.

Blob spores prior to this PR were almost completely useless.

The main cause of this was the extremely dilute reagent smoke reaction;
10u divided over 20 seconds.

This resulted the smoke clouds dealing 0.15 - 0.6 DPS, a completely
negligible and useless amount.

The smoke reagent concentration has been massively increased(10u -> 40u)
and the smoke duration has been reduced(20s -> 8s).
The result of this is that blob spore clouds are something you want to
avoid standing in, but they provide less smoke cover for the blob and
nauts.

Blob spores have also gained the ability to vent crawl. Simple mobs that
can't either open doors or vent crawl feel super bad to play.

They also deal a little more melee damage, but this is still
pathetically low on account of their low attack speed.

I have adjusted their supplementary reagents and reduced the amounts of
spores produced per cycle(2 -> 1) to make them a bit harder to mass
produce.

I have not made this PR with the goal of buffing any particular strain,
but some changes have affected blob strain balance:

This was the only strain that was strongly mechanically tied to the
core.
In order to allow for independent debris devourer mobs, they can now eat
trash(or any item really), they are independent, they store these items
inside their mob, and use these for the debris devourer reactions.

If they have an overmind, the item gets sent to the core.

This should result in a nice buff to the strain, which I've been told is
one of the bad ones.

5 years back another contributor removed the ability of blobs and
blobbernauts to transfer reagents with their attacks(as their expose
method is vapour).

This was a completely undocumented change and possibly unintentional, so
I am reverting it by giving blob reagents penetrates_skin = VAPOR again.

This only really affects these two strains. It makes regenerative
materia much stronger, while barely having any effect on cryogenic
poison, because temperature normalisation changes has made it completely
ineffective even with much more reagent applied.

The spore reagent cloud buff might also give a boost to some strains
with good expose effects, like electromagnetic web.

Blob spores now drop spore sacks, they can be ground for spore toxin, or
cracked on a griddle to create an egg-like treat!

I also added a detoxification reaction to reduce the amount of toxin
when cooked, might not work yet because I think griddles may not
actually heat the food?

Blob spores bursting and blobbernauts dying have sound effects.

level 5 biohazard

🆑
image: blob mobs have been respectfully resprited.
add: vat grown blob mobs can sometimes get born with a blob strain.
add: blob spores drop spore sacks, crack them on the griddle.
add: debris devourer mobs can now eat trash, sending it to the core, if
there is one.
add: vat grown blob spores are now sentient and evil.
balance: blob spores now have much more concentrated smoke.
balance: blob spores can ventcrawl.
fix: regenerative materia and cryogenic poison strain blob tiles & nauts
now inject chems again.
sound: blob spores & blobbernaut now have death sound effects.
/🆑
2025-07-04 16:42:02 -04:00

218 lines
9.2 KiB
Plaintext

/**
* Common behaviour shared by things which are minions to a blob
*/
/datum/component/blob_minion
dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
/// Overmind who is our boss
var/mob/eye/blob/overmind
/// Callback to run if overmind strain changes
var/datum/callback/on_strain_changed
/// Our strain we should not acess the overminds strain directly as we may not have one.
var/datum/blobstrain/our_strain
/// Used to determine the size of blob mob death clouds or equivlent strain dependant spore death effects
var/death_cloud_size = BLOBMOB_CLOUD_NONE
/datum/component/blob_minion/Initialize(mob/eye/blob/new_overmind, datum/callback/on_strain_changed, new_death_cloud_size, datum/blobstrain/new_strain)
. = ..()
if (!isliving(parent))
return COMPONENT_INCOMPATIBLE
if(isnum(new_death_cloud_size))
death_cloud_size = new_death_cloud_size
src.on_strain_changed = on_strain_changed
//checking for a lack of overmind to avoid calling strain_properties changed twice.
if(new_strain && !new_overmind)
strain_properties_changed(null, new_strain)
if(new_overmind)
register_overlord(new_overmind)
/datum/component/blob_minion/InheritComponent(datum/component/new_comp, i_am_original, mob/eye/blob/new_overmind, datum/callback/on_strain_changed, new_death_cloud_size, datum/blobstrain/new_strain)
if(isnum(new_death_cloud_size))
death_cloud_size = new_death_cloud_size
if (!isnull(on_strain_changed))
src.on_strain_changed = on_strain_changed
if(new_strain && !new_overmind)
strain_properties_changed(null, new_strain)
if(new_overmind)
register_overlord(new_overmind)
/datum/component/blob_minion/proc/register_overlord(mob/eye/blob/new_overmind)
overmind = new_overmind
overmind.register_new_minion(parent)
RegisterSignal(overmind, COMSIG_QDELETING, PROC_REF(overmind_deleted))
RegisterSignal(overmind, COMSIG_BLOB_SELECTED_STRAIN, PROC_REF(strain_properties_changed))
strain_properties_changed(overmind, overmind.blobstrain)
/// Our overmind is gone, uh oh!
/datum/component/blob_minion/proc/overmind_deleted()
SIGNAL_HANDLER
overmind = null
strain_properties_changed()
/// Our strain has changed, perhaps because our blob overmind has changed strain, died, or because of a mutation.
/datum/component/blob_minion/proc/strain_properties_changed(mob/eye/blob/changed_overmind, datum/blobstrain/new_strain)
SIGNAL_HANDLER
var/mob/living/living_parent = parent
if(new_strain)
our_strain = new_strain
else
our_strain = null
living_parent.update_appearance(UPDATE_ICON)
on_strain_changed?.Invoke(changed_overmind, new_strain)
/datum/component/blob_minion/RegisterWithParent()
var/mob/living/living_parent = parent
living_parent.pass_flags |= PASSBLOB
living_parent.faction |= ROLE_BLOB
ADD_TRAIT(parent, TRAIT_BLOB_ALLY, REF(src))
remove_verb(parent, /mob/living/verb/pulled) // No dragging people into the blob
RegisterSignal(parent, COMSIG_MOB_MIND_INITIALIZED, PROC_REF(on_mind_init))
RegisterSignal(parent, COMSIG_ATOM_UPDATE_ICON, PROC_REF(on_update_appearance))
RegisterSignal(parent, COMSIG_MOB_GET_STATUS_TAB_ITEMS, PROC_REF(on_update_status_tab))
RegisterSignal(parent, COMSIG_ATOM_BLOB_ACT, PROC_REF(on_blob_touched))
RegisterSignal(parent, COMSIG_ATOM_FIRE_ACT, PROC_REF(on_burned))
RegisterSignal(parent, COMSIG_ATOM_TRIED_PASS, PROC_REF(on_attempted_pass))
RegisterSignal(parent, COMSIG_MOVABLE_SPACEMOVE, PROC_REF(on_space_move))
RegisterSignal(parent, COMSIG_MOB_TRY_SPEECH, PROC_REF(on_try_speech))
RegisterSignal(parent, COMSIG_MOB_CHANGED_TYPE, PROC_REF(on_transformed))
RegisterSignal(parent, COMSIG_LIVING_DEATH, PROC_REF(on_death))
RegisterSignal(parent, COMSIG_BASICMOB_MUTATED, PROC_REF(on_mutated))
RegisterSignal(parent, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(on_minion_atom_interacted))
if(overmind || our_strain)
strain_properties_changed(overmind, our_strain)
GLOB.blob_telepathy_mobs |= parent
/datum/component/blob_minion/UnregisterFromParent()
if (!isnull(overmind))
overmind.blob_mobs -= parent
var/mob/living/living_parent = parent
living_parent.pass_flags &= ~PASSBLOB
living_parent.faction -= ROLE_BLOB
REMOVE_TRAIT(parent, TRAIT_BLOB_ALLY, REF(src))
add_verb(parent, /mob/living/verb/pulled)
UnregisterSignal(parent, list(
COMSIG_ATOM_BLOB_ACT,
COMSIG_ATOM_FIRE_ACT,
COMSIG_ATOM_TRIED_PASS,
COMSIG_ATOM_UPDATE_ICON,
COMSIG_MOB_TRY_SPEECH,
COMSIG_MOB_CHANGED_TYPE,
COMSIG_MOB_GET_STATUS_TAB_ITEMS,
COMSIG_MOB_MIND_INITIALIZED,
COMSIG_MOVABLE_SPACEMOVE,
COMSIG_LIVING_DEATH,
COMSIG_BASICMOB_MUTATED,
COMSIG_HOSTILE_PRE_ATTACKINGTARGET,
))
GLOB.blob_telepathy_mobs -= parent
/// Become blobpilled when we gain a mind
/datum/component/blob_minion/proc/on_mind_init(mob/living/minion, datum/mind/new_mind)
SIGNAL_HANDLER
if (isnull(overmind))
return
var/datum/antagonist/blob_minion/minion_motive = new(overmind)
new_mind.add_antag_datum(minion_motive)
/// When our icon is updated, update our colour too
/datum/component/blob_minion/proc/on_update_appearance(mob/living/minion)
SIGNAL_HANDLER
if(our_strain?.color)
minion.add_atom_colour(our_strain.color, FIXED_COLOUR_PRIORITY)
else
minion.remove_atom_colour(FIXED_COLOUR_PRIORITY)
/// When our icon is updated, update our colour too
/datum/component/blob_minion/proc/on_update_status_tab(mob/living/minion, list/status_items)
SIGNAL_HANDLER
if (isnull(overmind))
return
status_items += "Blobs to Win: [length(overmind.blobs_legit)]/[overmind.blobwincount]"
/// If we feel the gentle caress of a blob, we feel better
/datum/component/blob_minion/proc/on_blob_touched(mob/living/minion)
SIGNAL_HANDLER
if(minion.stat == DEAD || minion.health >= minion.maxHealth)
return COMPONENT_CANCEL_BLOB_ACT // Don't hurt us in order to heal us
for(var/i in 1 to 2)
var/obj/effect/temp_visual/heal/heal_effect = new /obj/effect/temp_visual/heal(get_turf(parent)) // hello yes you are being healed
heal_effect.color = isnull(overmind) ? COLOR_BLACK : overmind.blobstrain.complementary_color
minion.heal_overall_damage(minion.maxHealth * BLOBMOB_HEALING_MULTIPLIER)
return COMPONENT_CANCEL_BLOB_ACT
/// If we feel the fearsome bite of open flame, we feel worse
/datum/component/blob_minion/proc/on_burned(mob/living/minion, exposed_temperature, exposed_volume)
SIGNAL_HANDLER
if(isnull(exposed_temperature))
minion.adjustFireLoss(5)
return
minion.adjustFireLoss(clamp(0.01 * exposed_temperature, 1, 5))
/// Someone is attempting to move through us, allow it if it is a blob tile
/datum/component/blob_minion/proc/on_attempted_pass(mob/living/minion, atom/movable/incoming)
SIGNAL_HANDLER
if(istype(incoming, /obj/structure/blob))
return COMSIG_COMPONENT_PERMIT_PASSAGE
/// If we're near a blob, stop drifting
/datum/component/blob_minion/proc/on_space_move(mob/living/minion)
SIGNAL_HANDLER
var/obj/structure/blob/blob_handhold = locate() in range(1, parent)
if (!isnull(blob_handhold))
return COMSIG_MOVABLE_STOP_SPACEMOVE
/// We only speak telepathically to blobs
/datum/component/blob_minion/proc/on_try_speech(mob/living/minion, message, ignore_spam, forced)
SIGNAL_HANDLER
minion.log_talk(message, LOG_SAY, tag = "blob hivemind telepathy")
var/spanned_message = minion.say_quote(message)
var/rendered = span_blob("<b>\[Blob Telepathy\] [minion.real_name]</b> [spanned_message]")
relay_to_list_and_observers(rendered, GLOB.blob_telepathy_mobs, minion, MESSAGE_TYPE_RADIO)
return COMPONENT_CANNOT_SPEAK
/// Called when a blob minion is transformed into something else, hopefully a spore into a zombie
/datum/component/blob_minion/proc/on_transformed(mob/living/minion, mob/living/replacement)
SIGNAL_HANDLER
replacement.AddComponent(/datum/component/blob_minion, new_overmind = overmind, new_death_cloud_size = death_cloud_size, new_strain = our_strain)
/datum/component/blob_minion/proc/on_death(mob/living/minion)
SIGNAL_HANDLER
if(death_cloud_size <= BLOBMOB_CLOUD_NONE)
return
if(our_strain)
our_strain.on_sporedeath(minion, death_cloud_size)
else
do_chem_smoke(range = death_cloud_size, holder = minion, location = get_turf(minion), reagent_type = /datum/reagent/toxin/spore, reagent_volume = BLOBMOB_CLOUD_REAGENT_VOLUME, smoke_type = /datum/effect_system/fluid_spread/smoke/chem/medium)
playsound(minion, 'sound/mobs/non-humanoids/blobmob/blob_spore_burst.ogg', vol = 100)
///When am independent mob with this component mutates, like from a random cytology mutation, give them a strain and modify their name to let the players know they have something special.
/datum/component/blob_minion/proc/on_mutated(mob/living/minion)
SIGNAL_HANDLER
if(overmind || our_strain)
return
var/datum/blobstrain/mutant_strain = pick(GLOB.valid_blobstrains)
strain_properties_changed(changed_overmind = null, new_strain = new mutant_strain)
minion.name = "[LOWER_TEXT(our_strain.name)] [minion.name]"
//normally the overmind would handle this, but we have none.
minion.maxHealth *= our_strain.max_mob_health_multiplier
minion.health *= our_strain.max_mob_health_multiplier
return MUTATED_NO_FURTHER_MUTATIONS
///For when we want to trigger effects when a blobmob clicks something, such as clicking on items.
/datum/component/blob_minion/proc/on_minion_atom_interacted(mob/living/minion, atom/interacted_atom, adjacent, modifiers)
SIGNAL_HANDLER
return our_strain?.on_blobmob_atom_interacted(minion, interacted_atom, adjacent, modifiers)