diff --git a/code/__DEFINES/blob_defines.dm b/code/__DEFINES/blob_defines.dm
index 9fb99b28870..cb546d419f6 100644
--- a/code/__DEFINES/blob_defines.dm
+++ b/code/__DEFINES/blob_defines.dm
@@ -92,8 +92,8 @@
#define BLOBMOB_HEALING_MULTIPLIER 0.0125 // Multiplies by -maxHealth and heals the blob by this amount every blob_act
#define BLOBMOB_SPORE_HEALTH 30 // Base spore health
#define BLOBMOB_SPORE_SPAWN_COOLDOWN (8 SECONDS)
-#define BLOBMOB_SPORE_DMG_LOWER 2
-#define BLOBMOB_SPORE_DMG_UPPER 4
+#define BLOBMOB_SPORE_DMG_LOWER 4
+#define BLOBMOB_SPORE_DMG_UPPER 8
#define BLOBMOB_BLOBBERNAUT_RESOURCE_COST 40 // Purchase price for making a blobbernaut
#define BLOBMOB_BLOBBERNAUT_HEALTH 200 // Base blobbernaut health
#define BLOBMOB_BLOBBERNAUT_DMG_SOLO_LOWER 20 // Damage without active overmind (core dead or xenobio mob)
@@ -105,3 +105,13 @@
#define BLOBMOB_BLOBBERNAUT_HEALING_CORE 0.05 // Percentage multiplier HP restored on Life() when within 2 tiles of the blob core
#define BLOBMOB_BLOBBERNAUT_HEALING_NODE 0.025 // Same, but for a nearby node
#define BLOBMOB_BLOBBERNAUT_HEALTH_DECAY 0.0125 // Percentage multiplier HP lost when not near blob tiles or without factory
+
+/// For blobmobs that you don't want to have a deathburst effect. (radius)
+#define BLOBMOB_CLOUD_NONE -1
+/// For blobmobs with small single tile clouds
+#define BLOBMOB_CLOUD_SMALL 0
+/// For normal 3x3 sized clouds
+#define BLOBMOB_CLOUD_NORMAL 1
+
+/// How much reagents we put into the spore death clouds in units.
+#define BLOBMOB_CLOUD_REAGENT_VOLUME 40
diff --git a/code/__DEFINES/colors.dm b/code/__DEFINES/colors.dm
index 27272eb419c..e51e520be4d 100644
--- a/code/__DEFINES/colors.dm
+++ b/code/__DEFINES/colors.dm
@@ -77,6 +77,7 @@
#define COLOR_LIGHT_YELLOW "#FFFEE0"
#define COLOR_OLIVE "#808000"
+#define COLOR_OLIVE_GREEN "#677714"
#define COLOR_ASSISTANT_OLIVE "#828163"
#define COLOR_VIBRANT_LIME "#00FF00"
#define COLOR_SERVICE_LIME "#58C800"
@@ -112,7 +113,7 @@
#define COLOR_MODERATE_BLUE "#555CC2"
#define COLOR_TRAM_BLUE "#6160A8"
#define COLOR_TRAM_LIGHT_BLUE "#A8A7DA"
-#define COLOR_AMETHYST "#822BFF"
+#define COLOR_AMETHYST "#822bff"
#define COLOR_BLUE_LIGHT "#33CCFF"
#define COLOR_BLUE_VERY_LIGHT "#ccecff"
#define COLOR_NAVY "#000080"
@@ -131,7 +132,7 @@
#define COLOR_VIOLET "#B900F7"
#define COLOR_VOID_PURPLE "#53277E"
#define COLOR_STRONG_VIOLET "#6927C5"
-#define COLOR_DARK_PURPLE "#551A8B"
+#define COLOR_DARK_PURPLE "#551a8b"
#define COLOR_ORANGE "#FF9900"
#define COLOR_IRISH_ORANGE "#FF883E"
diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_basic.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_basic.dm
index 130a032eb35..01df92ebcc2 100644
--- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_basic.dm
+++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_basic.dm
@@ -24,3 +24,7 @@
///from /datum/status_effect/slime_leech: (mob/living/basic/slime/draining_slime)
#define COMSIG_SLIME_DRAINED "slime_drained"
+/// from /mob/living/basic/mutate(): (mob/living/basic/mutated_mob)
+#define COMSIG_BASICMOB_MUTATED "basicmob_mutated"
+ ///cancel further mutation modifications to the mob such as shiny mutation.
+ #define MUTATED_NO_FURTHER_MUTATIONS (1 << 0)
diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm
index 222ceaa158c..5781430e338 100644
--- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm
+++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm
@@ -253,7 +253,7 @@
#define COMSIG_MOB_DROPPING_ITEM "mob_dropping_item"
/// from /mob/proc/change_mob_type() : ()
-#define COMSIG_PRE_MOB_CHANGED_TYPE "mob_changed_type"
+#define COMSIG_PRE_MOB_CHANGED_TYPE "pre_mob_changed_type"
#define COMPONENT_BLOCK_MOB_CHANGE (1<<0)
/// from /mob/proc/change_mob_type_unchecked() : ()
#define COMSIG_MOB_CHANGED_TYPE "mob_changed_type"
diff --git a/code/__DEFINES/role_preferences.dm b/code/__DEFINES/role_preferences.dm
index 5f2dda07c56..124c68f7cf4 100644
--- a/code/__DEFINES/role_preferences.dm
+++ b/code/__DEFINES/role_preferences.dm
@@ -72,6 +72,7 @@
/// This flag specifically is used as a generic catch-all antag ban
#define ROLE_SYNDICATE "Syndicate"
#define ROLE_EXPERIMENTAL_CLONER "Experimental Cloner"
+#define ROLE_FREE_BLOB "Free Blob"
#define ROLE_CLOWN_OPERATIVE "Clown Operative"
#define ROLE_FREE_GOLEM "Free Golem"
diff --git a/code/__HELPERS/icons.dm b/code/__HELPERS/icons.dm
index 71608bc5f72..86817a55440 100644
--- a/code/__HELPERS/icons.dm
+++ b/code/__HELPERS/icons.dm
@@ -334,26 +334,14 @@ world
/proc/RotateHue(rgb, angle)
var/list/HSV = rgb2hsv(rgb)
- // normalize hsv in case anything is screwy
- if(HSV[1] >= 1536)
- HSV[1] %= 1536
+ angle %= 360
+
+ HSV[1] = round(HSV[1] + angle)
+
+ HSV[1] %= 360
+
if(HSV[1] < 0)
- HSV[1] += 1536
-
- // Compress hue into easier-to-manage range
- HSV[1] -= HSV[1] >> 8
-
- if(angle < 0 || angle >= 360)
- angle -= 360 * round(angle / 360)
- HSV[1] = round(HSV[1] + angle * (1530/360), 1)
-
- // normalize hue
- if(HSV[1] < 0 || HSV[1] >= 1530)
- HSV[1] %= 1530
- if(HSV[1] < 0)
- HSV[1] += 1530
- // decompress hue
- HSV[1] += round(HSV[1] / 255)
+ HSV[1] += 360
return hsv2rgb(HSV)
diff --git a/code/_globalvars/lists/poll_ignore.dm b/code/_globalvars/lists/poll_ignore.dm
index 9a4eb0b64a0..7fc93081ad2 100644
--- a/code/_globalvars/lists/poll_ignore.dm
+++ b/code/_globalvars/lists/poll_ignore.dm
@@ -38,6 +38,7 @@
#define POLL_IGNORE_VENUSHUMANTRAP "venus_human_trap"
#define POLL_IGNORE_RECOVERED_CREW "recovered_crew"
#define POLL_IGNORE_EXPERIMENTAL_CLONER "experimental_cloner"
+#define POLL_IGNORE_FREE_SPORE "free_spore"
GLOBAL_LIST_INIT(poll_ignore_desc, list(
POLL_IGNORE_ACADEMY_WIZARD = "Academy Wizard Defender",
@@ -77,6 +78,7 @@ GLOBAL_LIST_INIT(poll_ignore_desc, list(
POLL_IGNORE_SYNDICATE = "Syndicate",
POLL_IGNORE_VENUSHUMANTRAP = "Venus Human Traps",
POLL_IGNORE_RECOVERED_CREW = "Recovered Crew",
+ POLL_IGNORE_FREE_SPORE = "Free spore",
))
GLOBAL_LIST_INIT(poll_ignore, init_poll_ignore())
diff --git a/code/datums/components/_component.dm b/code/datums/components/_component.dm
index 3cc0738e38d..4b496da1b73 100644
--- a/code/datums/components/_component.dm
+++ b/code/datums/components/_component.dm
@@ -364,6 +364,7 @@
old_component.InheritComponent(arglist(arguments))
else
old_component.InheritComponent(new_component, TRUE)
+ QDEL_NULL(new_component)
if(COMPONENT_DUPE_SOURCES)
if((source in old_component.sources) && !old_component.allow_source_update(source))
diff --git a/code/datums/components/blob_minion.dm b/code/datums/components/blob_minion.dm
index 85000395b76..a17fd4b4a15 100644
--- a/code/datums/components/blob_minion.dm
+++ b/code/datums/components/blob_minion.dm
@@ -7,40 +7,66 @@
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/overmind, datum/callback/on_strain_changed)
+/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
- src.on_strain_changed = on_strain_changed
- register_overlord(overmind)
-/datum/component/blob_minion/InheritComponent(datum/component/new_comp, i_am_original, mob/eye/blob/overmind, datum/callback/on_strain_changed)
+ 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
- register_overlord(overmind)
-/datum/component/blob_minion/proc/register_overlord(mob/eye/blob/overmind)
- if (isnull(overmind))
- return
- src.overmind = overmind
+ 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(overmind_properties_changed))
- overmind_properties_changed(overmind, overmind.blobstrain)
+ 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
- overmind_properties_changed()
+ strain_properties_changed()
-/// Our overmind has changed colour and properties
-/datum/component/blob_minion/proc/overmind_properties_changed(mob/eye/blob/overmind, datum/blobstrain/new_strain)
+/// 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(overmind, new_strain)
+ on_strain_changed?.Invoke(changed_overmind, new_strain)
/datum/component/blob_minion/RegisterWithParent()
var/mob/living/living_parent = parent
@@ -57,7 +83,11 @@
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))
- living_parent.update_appearance(UPDATE_ICON)
+ 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()
@@ -78,6 +108,9 @@
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
@@ -92,10 +125,10 @@
/// When our icon is updated, update our colour too
/datum/component/blob_minion/proc/on_update_appearance(mob/living/minion)
SIGNAL_HANDLER
- if(isnull(overmind))
+ if(our_strain?.color)
+ minion.add_atom_colour(our_strain.color, FIXED_COLOUR_PRIORITY)
+ else
minion.remove_atom_colour(FIXED_COLOUR_PRIORITY)
- return
- minion.add_atom_colour(overmind.blobstrain.color, 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)
@@ -148,8 +181,37 @@
/// 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
- overmind?.assume_direct_control(replacement)
+ replacement.AddComponent(/datum/component/blob_minion, new_overmind = overmind, new_death_cloud_size = death_cloud_size, new_strain = our_strain)
-/datum/component/blob_minion/PostTransfer(datum/new_parent)
- if(!isliving(new_parent))
- return COMPONENT_INCOMPATIBLE
+/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)
diff --git a/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm b/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm
index 20cabff91be..c5e365eee8e 100644
--- a/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm
+++ b/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm
@@ -397,11 +397,11 @@
return TRUE
/// Helper to quickly create a cloud of reagent smoke
-/proc/do_chem_smoke(range = 0, amount = DIAMOND_AREA(range), atom/holder = null, location = null, reagent_type = /datum/reagent/water, reagent_volume = 10, log = FALSE)
+/proc/do_chem_smoke(range = 0, amount = DIAMOND_AREA(range), atom/holder = null, location = null, reagent_type = /datum/reagent/water, reagent_volume = 10, log = FALSE, datum/effect_system/fluid_spread/smoke/chem/smoke_type = /datum/effect_system/fluid_spread/smoke/chem)
var/datum/reagents/smoke_reagents = new/datum/reagents(reagent_volume)
smoke_reagents.add_reagent(reagent_type, reagent_volume)
- var/datum/effect_system/fluid_spread/smoke/chem/smoke = new
+ var/datum/effect_system/fluid_spread/smoke/chem/smoke = new smoke_type
smoke.attach(location)
smoke.set_up(amount = amount, holder = holder, location = location, carry = smoke_reagents, silent = TRUE)
smoke.start(log = log)
@@ -470,3 +470,12 @@
/datum/effect_system/fluid_spread/smoke/chem/quick
effect_type = /obj/effect/particle_effect/fluid/smoke/chem/quick
+
+/**
+ * A version of chemical smoke with a intermediate lifespan.
+ */
+/obj/effect/particle_effect/fluid/smoke/chem/medium
+ lifetime = 8 SECONDS
+
+/datum/effect_system/fluid_spread/smoke/chem/medium
+ effect_type = /obj/effect/particle_effect/fluid/smoke/chem/medium
diff --git a/code/game/objects/items/food/egg.dm b/code/game/objects/items/food/egg.dm
index 958ca9c0e60..8ef8ff8f29f 100644
--- a/code/game/objects/items/food/egg.dm
+++ b/code/game/objects/items/food/egg.dm
@@ -347,3 +347,74 @@ GLOBAL_VAR_INIT(chicks_from_eggs, 0)
foodtypes = MEAT | VEGETABLES
venue_value = FOOD_PRICE_NORMAL
crafting_complexity = FOOD_COMPLEXITY_3
+
+/obj/item/food/spore_sack
+ name = "spore sack"
+ desc = "A spore sack. blobby and gooey!"
+ icon = 'icons/obj/food/egg.dmi'
+ icon_state = "spore_sack"
+ base_icon_state = "spore_sack"
+ inhand_icon_state = "egg"
+ food_reagents = list(/datum/reagent/consumable/eggyolk = 4, /datum/reagent/toxin/spore = 4, /datum/reagent/consumable/eggwhite = 1, /datum/reagent/consumable/nutriment/vitamin = 1)
+ tastes = list("sliminess" = 4, "blob" = 2)
+ foodtypes = MEAT | RAW | TOXIC
+ w_class = WEIGHT_CLASS_TINY
+ ant_attracting = FALSE
+ preserved_food = TRUE
+
+/obj/item/food/spore_sack/Initialize(mapload)
+ . = ..()
+ if(prob(50))
+ icon_state = "[base_icon_state]2"
+
+ AddElement(/datum/element/swabable, CELL_LINE_TABLE_BLOBSPORE, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5)
+
+/obj/item/food/spore_sack/interact_with_atom_secondary(atom/interacting_with, mob/living/user, list/modifiers)
+ if(!istype(interacting_with, /obj/machinery/griddle))
+ return NONE
+
+ var/obj/machinery/griddle/hit_griddle = interacting_with
+ if(length(hit_griddle.griddled_objects) >= hit_griddle.max_items)
+ interacting_with.balloon_alert(user, "no room!")
+ return ITEM_INTERACT_BLOCKING
+ var/atom/broken_egg = new /obj/item/food/rawegg/spore(interacting_with.loc)
+ if(LAZYACCESS(modifiers, ICON_X))
+ broken_egg.pixel_x = clamp(text2num(LAZYACCESS(modifiers, ICON_X)) - 16, -(ICON_SIZE_X/2), ICON_SIZE_X/2)
+ if(LAZYACCESS(modifiers, ICON_Y))
+ broken_egg.pixel_y = clamp(text2num(LAZYACCESS(modifiers, ICON_Y)) - 16, -(ICON_SIZE_Y/2), ICON_SIZE_Y/2)
+ playsound(user, 'sound/items/sheath.ogg', 40, TRUE)
+ reagents.copy_to(broken_egg, reagents.total_volume)
+
+ hit_griddle.AddToGrill(broken_egg, user)
+ interacting_with.balloon_alert(user, "cracks [src] open")
+
+ qdel(src)
+ return ITEM_INTERACT_BLOCKING
+
+/obj/item/food/spore_sack/independent
+ icon_state = "spore_sack_independent"
+ base_icon_state = "spore_sack_independent"
+
+/obj/item/food/friedegg/spore
+ name = "fried spore"
+ desc = "A fried blob spore. Would go well with a dab of cold sauce."
+ icon_state = "friedspore"
+ //superior healing and cyto reagents to compensate for rarity and mild poison effect.
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment/peptides = 2,
+ /datum/reagent/consumable/nutriment/vitamin = 1,
+ )
+ tastes = list("blob" = 4, "level 5 biohazard" = 2)
+
+/obj/item/food/rawegg/spore
+ name = "burst spore"
+ desc = "Is this the ant egg everyone is always talking about? Better fried."
+ icon_state = "burstspore"
+ tastes = list("sliminess" = 4, "blob" = 2)
+
+/obj/item/food/rawegg/spore/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/swabable, CELL_LINE_TABLE_BLOBSPORE, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5)
+
+/obj/item/food/rawegg/spore/make_grillable()
+ AddComponent(/datum/component/grillable, /obj/item/food/friedegg/spore, rand(15 SECONDS, 25 SECONDS), TRUE, FALSE)
diff --git a/code/modules/antagonists/blob/blobstrains/_blobstrain.dm b/code/modules/antagonists/blob/blobstrains/_blobstrain.dm
index 7215bdf3ae8..2f097466eca 100644
--- a/code/modules/antagonists/blob/blobstrains/_blobstrain.dm
+++ b/code/modules/antagonists/blob/blobstrains/_blobstrain.dm
@@ -63,9 +63,8 @@ GLOBAL_LIST_INIT(valid_blobstrains, subtypesof(/datum/blobstrain) - list(/datum/
var/blobbernaut_reagentatk_bonus = 0
/datum/blobstrain/New(mob/eye/blob/new_overmind)
- if (!istype(new_overmind))
- stack_trace("blobstrain created without overmind")
- overmind = new_overmind
+ if(new_overmind)
+ overmind = new_overmind
/datum/blobstrain/Destroy()
overmind = null
@@ -126,7 +125,8 @@ GLOBAL_LIST_INIT(valid_blobstrains, subtypesof(/datum/blobstrain) - list(/datum/
blob_mob.health /= max_mob_health_multiplier
-/datum/blobstrain/proc/on_sporedeath(mob/living/spore)
+/datum/blobstrain/proc/on_sporedeath(mob/living/dead_minion, death_cloud_size)
+ return
/datum/blobstrain/proc/send_message(mob/living/M)
var/totalmessage = message
@@ -145,7 +145,8 @@ GLOBAL_LIST_INIT(valid_blobstrains, subtypesof(/datum/blobstrain) - list(/datum/
send_message(L)
/// When this blob's blobbernaut attacks any atom
-/datum/blobstrain/proc/blobbernaut_attack(atom/attacking, mob/living/basic/blobbernaut)
+/datum/blobstrain/proc/blobbernaut_attack(mob/living/blobbernaut, atom/victim)
+ SIGNAL_HANDLER
return
/datum/blobstrain/proc/damage_reaction(obj/structure/blob/B, damage, damage_type, damage_flag, coefficient = 1) //when the blob takes damage, do this
@@ -168,3 +169,6 @@ GLOBAL_LIST_INIT(valid_blobstrains, subtypesof(/datum/blobstrain) - list(/datum/
/datum/blobstrain/proc/examine(mob/user)
return list("Progress to Critical Mass: [span_notice("[overmind.blobs_legit.len]/[overmind.blobwincount].")]")
+
+/datum/blobstrain/proc/on_blobmob_atom_interacted(mob/living/minion, atom/interacted_atom, adjacent, modifiers)
+ return
diff --git a/code/modules/antagonists/blob/blobstrains/_reagent.dm b/code/modules/antagonists/blob/blobstrains/_reagent.dm
index bc9c61dd4b1..3273087a692 100644
--- a/code/modules/antagonists/blob/blobstrains/_reagent.dm
+++ b/code/modules/antagonists/blob/blobstrains/_reagent.dm
@@ -11,17 +11,18 @@
reagent.expose_mob(L, VAPOR, BLOB_REAGENTATK_VOL, TRUE, mob_protection, overmind)
send_message(L)
-/datum/blobstrain/reagent/blobbernaut_attack(atom/attacking, mob/living/basic/blobbernaut)
- if(!isliving(attacking))
+/datum/blobstrain/reagent/blobbernaut_attack(mob/living/blobbernaut, atom/victim)
+ ..()
+ if(!isliving(victim))
return
- var/mob/living/living_attacking = attacking
- var/mob_protection = living_attacking.getarmor(null, BIO) * 0.01
- reagent.expose_mob(living_attacking, VAPOR, BLOBMOB_BLOBBERNAUT_REAGENTATK_VOL+blobbernaut_reagentatk_bonus, FALSE, mob_protection, overmind)//this will do between 10 and 20 damage(reduced by mob protection), depending on chemical, plus 4 from base brute damage.
+ var/mob/living/living_victim = victim
+ var/mob_protection = living_victim.getarmor(null, BIO) * 0.01
+ reagent.expose_mob(living_victim, VAPOR, BLOBMOB_BLOBBERNAUT_REAGENTATK_VOL+blobbernaut_reagentatk_bonus, FALSE, mob_protection, overmind)//this will do between 10 and 20 damage(reduced by mob protection), depending on chemical, plus 4 from base brute damage.
-/datum/blobstrain/reagent/on_sporedeath(mob/living/basic/spore)
- var/burst_range = (spore.type == /mob/living/basic/blob_minion/spore) ? 1 : 0
- do_chem_smoke(range = burst_range, holder = spore, location = get_turf(spore), reagent_type = reagent.type)
+/datum/blobstrain/reagent/on_sporedeath(mob/living/dead_minion, death_cloud_size)
+ do_chem_smoke(range = death_cloud_size, holder = dead_minion, location = get_turf(dead_minion), reagent_type = reagent.type, reagent_volume = BLOBMOB_CLOUD_REAGENT_VOLUME, smoke_type = /datum/effect_system/fluid_spread/smoke/chem/medium)
+ playsound(dead_minion, 'sound/mobs/non-humanoids/blobmob/blob_spore_burst.ogg', vol = 100, vary = TRUE)
// These can only be applied by blobs. They are what (reagent) blobs are made out of.
/datum/reagent/blob
@@ -30,7 +31,6 @@
color = COLOR_WHITE
taste_description = "bad code and slime"
chemical_flags = NONE
- penetrates_skin = NONE
/datum/reagent/blob/New()
diff --git a/code/modules/antagonists/blob/blobstrains/debris_devourer.dm b/code/modules/antagonists/blob/blobstrains/debris_devourer.dm
index 1a2cb9fe854..63928ed63c8 100644
--- a/code/modules/antagonists/blob/blobstrains/debris_devourer.dm
+++ b/code/modules/antagonists/blob/blobstrains/debris_devourer.dm
@@ -1,4 +1,6 @@
-#define DEBRIS_DENSITY (length(core.contents) / (length(overmind.blobs_legit) * 0.25)) // items per blob
+#define DEBRIS_DENSITY (length(overmind.blob_core.contents) / (length(overmind.blobs_legit) * 0.25)) // items per blob
+#define SPORE_TRASH_COUNT 3
+#define FREE_MINION_DEBRIS_CHANCE 80
// Accumulates junk liberally
/datum/blobstrain/debris_devourer
@@ -11,42 +13,53 @@
blobbernaut_message = "blasts"
message = "The blob blasts you"
-
/datum/blobstrain/debris_devourer/attack_living(mob/living/L, list/nearby_blobs)
send_message(L)
for (var/obj/structure/blob/blob in nearby_blobs)
debris_attack(L, blob)
-/datum/blobstrain/debris_devourer/on_sporedeath(mob/living/spore)
- var/obj/structure/blob/special/core/core = overmind.blob_core
- for(var/i in 1 to 3)
- var/obj/item/I = pick(core.contents)
- if (I && !QDELETED(I))
- I.forceMove(get_turf(spore))
- I.throw_at(get_edge_target_turf(spore,pick(GLOB.alldirs)), 6, 5, spore, TRUE, FALSE, null, 3)
+/datum/blobstrain/debris_devourer/on_sporedeath(mob/living/spore, death_cloud_size)
+ var/list/trash_source = overmind ? overmind.blob_core.contents : spore.contents
+
+ var/trashsplosion_count = overmind ? SPORE_TRASH_COUNT : spore.contents.len
+
+ for(var/i in 1 to trashsplosion_count)
+ var/obj/item/trash_shrapnel = pick(trash_source)
+ if (trash_shrapnel && !QDELETED(trash_shrapnel))
+ trash_shrapnel.forceMove(get_turf(spore))
+ trash_shrapnel.throw_at(get_edge_target_turf(spore,pick(GLOB.alldirs)), 6, 5, spore, TRUE, FALSE, null, 3)
+ playsound(spore, 'sound/effects/pop_expl.ogg', vol = 100, vary = TRUE)
/datum/blobstrain/debris_devourer/expand_reaction(obj/structure/blob/B, obj/structure/blob/newB, turf/T, mob/eye/blob/O, coefficient = 1) //when the blob expands, do this
for (var/obj/item/I in T)
I.forceMove(overmind.blob_core)
/datum/blobstrain/debris_devourer/proc/debris_attack(atom/attacking, atom/source)
- var/obj/structure/blob/special/core/core = overmind.blob_core
- if (prob(40 * DEBRIS_DENSITY)) // Pretend the items are spread through the blob and its mobs and not in the core.
- var/obj/item/I = length(core.contents) ? pick(core.contents) : null
- if (!QDELETED(I))
- I.forceMove(get_turf(source))
- I.throw_at(attacking, 6, 5, overmind, TRUE, FALSE, null, 3)
+ if (!prob(overmind ? 40 * DEBRIS_DENSITY : FREE_MINION_DEBRIS_CHANCE)) // Pretend the items are spread through the blob and its mobs and not in the core.
+ return
-/datum/blobstrain/debris_devourer/blobbernaut_attack(atom/attacking, mob/living/basic/blobbernaut)
- debris_attack(attacking, blobbernaut)
+ var/list/trash_collection = overmind ? overmind.blob_core.contents : source.contents
+
+ if(!length(trash_collection))
+ return
+
+ var/obj/item/trash_weapon = pick(trash_collection)
+
+ if (QDELETED(trash_weapon))
+ return
+
+ trash_weapon.forceMove(get_turf(source))
+ trash_weapon.throw_at(attacking, 6, 5, overmind ? overmind : source, TRUE, FALSE, null, 3)
+
+/datum/blobstrain/debris_devourer/blobbernaut_attack(mob/living/blobbernaut, atom/victim)
+ ..()
+ debris_attack(victim, blobbernaut)
/datum/blobstrain/debris_devourer/damage_reaction(obj/structure/blob/B, damage, damage_type, damage_flag, coefficient = 1) //when the blob takes damage, do this
- var/obj/structure/blob/special/core/core = overmind.blob_core
return round(max((coefficient*damage)-min(coefficient*DEBRIS_DENSITY, 10), 0)) // reduce damage taken by items per blob, up to 10
/datum/blobstrain/debris_devourer/examine(mob/user)
. = ..()
- var/obj/structure/blob/special/core/core = overmind.blob_core
if (isobserver(user))
. += span_notice("Absorbed debris is currently reducing incoming damage by [round(max(min(DEBRIS_DENSITY, 10),0))]")
else
@@ -60,4 +73,21 @@
if (8 to 10)
. += span_notice("Absorbed debris is currently reducing incoming damage by a medium amount.")
+/datum/blobstrain/debris_devourer/on_blobmob_atom_interacted(mob/living/minion, atom/interacted_atom, adjacent, modifiers)
+ . = ..()
+ if(!isitem(interacted_atom) || !adjacent)
+ return
+
+ if(minion.contents.len >= minion.mob_size * 5)
+ to_chat(minion, span_warning("You feel too full to eat more trash."))
+ return
+
+ playsound(minion, 'sound/items/eatfood.ogg', 60, TRUE)
+ var/obj/item/tasty_trash = interacted_atom
+ minion.do_attack_animation(tasty_trash)
+ tasty_trash.forceMove(overmind ? overmind.blob_core : minion)
+ return COMPONENT_HOSTILE_NO_ATTACK
+
#undef DEBRIS_DENSITY
+#undef SPORE_TRASH_COUNT
+#undef FREE_MINION_DEBRIS_CHANCE
diff --git a/code/modules/antagonists/blob/blobstrains/distributed_neurons.dm b/code/modules/antagonists/blob/blobstrains/distributed_neurons.dm
index cb5f565ef55..c69931f9143 100644
--- a/code/modules/antagonists/blob/blobstrains/distributed_neurons.dm
+++ b/code/modules/antagonists/blob/blobstrains/distributed_neurons.dm
@@ -26,7 +26,7 @@
. = ..()
reac_volume = return_mob_expose_reac_volume(exposed_mob, methods, reac_volume, show_message, touch_protection, overmind)
exposed_mob.apply_damage(0.6*reac_volume, TOX)
- if(overmind && ishuman(exposed_mob))
+ if(ishuman(exposed_mob))
if(exposed_mob.stat == UNCONSCIOUS || exposed_mob.stat == HARD_CRIT)
exposed_mob.investigate_log("has been killed by distributed neurons (blob).", INVESTIGATE_DEATHS)
exposed_mob.death() //sleeping in a fight? bad plan.
diff --git a/code/modules/antagonists/blob/blobstrains/explosive_lattice.dm b/code/modules/antagonists/blob/blobstrains/explosive_lattice.dm
index d068373e86b..26131c837c2 100644
--- a/code/modules/antagonists/blob/blobstrains/explosive_lattice.dm
+++ b/code/modules/antagonists/blob/blobstrains/explosive_lattice.dm
@@ -18,13 +18,17 @@
return damage * 1.5
return ..()
-/datum/blobstrain/reagent/explosive_lattice/on_sporedeath(mob/living/spore)
- var/obj/effect/temp_visual/explosion/fast/effect = new /obj/effect/temp_visual/explosion/fast(get_turf(spore))
+/datum/blobstrain/reagent/explosive_lattice/on_sporedeath(mob/living/dead_spore, death_cloud_size)
+ var/obj/effect/temp_visual/explosion/fast/effect = new /obj/effect/temp_visual/explosion/fast(get_turf(dead_spore))
effect.alpha = 150
- for(var/mob/living/actor in orange(get_turf(spore), 1))
+ for(var/mob/living/actor in orange(get_turf(dead_spore), death_cloud_size))
if(ROLE_BLOB in actor.faction) // No friendly fire
continue
- actor.take_overall_damage(10, 10)
+ //increases damage to mobs if death_cloud_size is increased, but the damage falls off with distance.
+ var/damage_total = (10 + 10 * death_cloud_size) / max(1, get_dist(get_turf(dead_spore), get_turf(actor)))
+ //split damage between brute and burn
+ actor.take_overall_damage(damage_total / 2, damage_total / 2)
+ playsound(dead_spore , 'sound/effects/explosion/explosion2.ogg', 20 + 20 * death_cloud_size, TRUE)
/datum/reagent/blob/explosive_lattice
name = "Explosive Lattice"
@@ -38,7 +42,7 @@
var/bomb_armor = 0
reac_volume = return_mob_expose_reac_volume(exposed_mob, methods, reac_volume, show_message, touch_protection, overmind)
- if(reac_volume >= 10) // If it's not coming from a sporecloud, AOE 'explosion' damage
+ if(reac_volume > 10) // If it's not coming from a sporecloud, AOE 'explosion' damage
var/epicenter_turf = get_turf(exposed_mob)
var/obj/effect/temp_visual/explosion/fast/ex_effect = new /obj/effect/temp_visual/explosion/fast(get_turf(exposed_mob))
ex_effect.alpha = 150
diff --git a/code/modules/antagonists/blob/blobstrains/multiplex.dm b/code/modules/antagonists/blob/blobstrains/multiplex.dm
index aedb571c6b6..ea7fe28ae32 100644
--- a/code/modules/antagonists/blob/blobstrains/multiplex.dm
+++ b/code/modules/antagonists/blob/blobstrains/multiplex.dm
@@ -4,6 +4,8 @@
/datum/blobstrain/multiplex/New(mob/eye/blob/new_overmind, list/blobstrains)
. = ..()
+ if(!overmind)
+ return
for (var/bt in blobstrains)
if (ispath(bt, /datum/blobstrain))
src.blobstrains += new bt(overmind)
diff --git a/code/modules/antagonists/blob/overmind.dm b/code/modules/antagonists/blob/overmind.dm
index 50594fe407e..ad0a89ee802 100644
--- a/code/modules/antagonists/blob/overmind.dm
+++ b/code/modules/antagonists/blob/overmind.dm
@@ -179,23 +179,12 @@ GLOBAL_LIST_EMPTY(blob_nodes)
/// Create a blob spore and link it to us
/mob/eye/blob/proc/create_spore(turf/spore_turf, spore_type = /mob/living/basic/blob_minion/spore/minion)
var/mob/living/basic/blob_minion/spore/spore = new spore_type(spore_turf)
- assume_direct_control(spore)
+ spore.AddComponent(/datum/component/blob_minion, src)
return spore
-/// Give our new minion the properties of a minion
-/mob/eye/blob/proc/assume_direct_control(mob/living/minion)
- minion.AddComponent(/datum/component/blob_minion, src)
-
/// Add something to our list of mobs and wait for it to die
/mob/eye/blob/proc/register_new_minion(mob/living/minion)
blob_mobs |= minion
- if (!istype(minion, /mob/living/basic/blob_minion/blobbernaut))
- RegisterSignal(minion, COMSIG_LIVING_DEATH, PROC_REF(on_minion_death))
-
-/// When a spore (or zombie) dies then we do this
-/mob/eye/blob/proc/on_minion_death(mob/living/spore)
- SIGNAL_HANDLER
- blobstrain.on_sporedeath(spore)
/mob/eye/blob/proc/victory()
sound_to_playing_players('sound/announcer/alarm/nuke_alarm.ogg', 70)
diff --git a/code/modules/antagonists/blob/powers.dm b/code/modules/antagonists/blob/powers.dm
index 58e25cdd620..2415b9db895 100644
--- a/code/modules/antagonists/blob/powers.dm
+++ b/code/modules/antagonists/blob/powers.dm
@@ -217,17 +217,9 @@
return FALSE
var/mob/living/basic/blob_minion/blobbernaut/minion/blobber = new(get_turf(factory))
- assume_direct_control(blobber)
+ blobber.AddComponent(/datum/component/blob_minion, new_overmind = src, new_death_cloud_size = blobber.death_cloud_size)
factory.assign_blobbernaut(blobber)
blobber.assign_key(ghost.key, blobstrain)
- RegisterSignal(blobber, COMSIG_HOSTILE_POST_ATTACKINGTARGET, PROC_REF(on_blobbernaut_attacked))
-
-/// When one of our boys attacked something, we sometimes want to perform extra effects
-/mob/eye/blob/proc/on_blobbernaut_attacked(mob/living/basic/blobbynaut, atom/target, success)
- SIGNAL_HANDLER
- if (!success)
- return
- blobstrain.blobbernaut_attack(target, blobbynaut)
/** Moves the core */
/mob/eye/blob/proc/relocate_core()
diff --git a/code/modules/food_and_drinks/recipes/food_mixtures.dm b/code/modules/food_and_drinks/recipes/food_mixtures.dm
index bdca46e01d2..d0b6af93763 100644
--- a/code/modules/food_and_drinks/recipes/food_mixtures.dm
+++ b/code/modules/food_and_drinks/recipes/food_mixtures.dm
@@ -327,3 +327,9 @@
required_reagents = list(/datum/reagent/consumable/grapejuice = 5)
required_catalysts = list(/datum/reagent/consumable/enzyme = 5)
mix_message = "The smell of the mixture reminds you of how you lost access to the country club..."
+
+/datum/chemical_reaction/food/spore_detoxification
+ results = list(/datum/reagent/consumable/nutriment/vitamin = 1)
+ required_reagents = list(/datum/reagent/toxin/spore = 1, /datum/reagent/consumable/eggwhite = 0.5)
+ required_temp = 350
+ optimal_temp = 420
diff --git a/code/modules/mob/living/basic/basic.dm b/code/modules/mob/living/basic/basic.dm
index e3a3809d8f6..9ee2145dff2 100644
--- a/code/modules/mob/living/basic/basic.dm
+++ b/code/modules/mob/living/basic/basic.dm
@@ -341,3 +341,12 @@
Move(get_step(src, dir), dir)
animate(src, pixel_y = 18, time = 0.4 SECONDS, flags = ANIMATION_RELATIVE, easing = CUBIC_EASING|EASE_OUT)
animate(pixel_y = -18, time = 0.4 SECONDS, flags = ANIMATION_RELATIVE, easing = CUBIC_EASING|EASE_IN)
+
+///ovverride to add mob specific cytology mutation effects, returns TRUE if we added a mob specific mutation
+/mob/living/basic/proc/mutate()
+ if(SEND_SIGNAL(src, COMSIG_BASICMOB_MUTATED) & MUTATED_NO_FURTHER_MUTATIONS)
+ //Tells the vat our mob has been mutated by another source and we don't want to add potentially incompatible mutations such as shiny mutation.
+ return TRUE
+ else
+ return FALSE
+
diff --git a/code/modules/mob/living/basic/blob_minions/blob_mob.dm b/code/modules/mob/living/basic/blob_minions/blob_mob.dm
index 6c82eaed053..c82eee3e067 100644
--- a/code/modules/mob/living/basic/blob_minions/blob_mob.dm
+++ b/code/modules/mob/living/basic/blob_minions/blob_mob.dm
@@ -4,6 +4,7 @@
desc = "A nonfunctional fungal creature created by bad code or celestial mistake. Point and laugh."
icon = 'icons/mob/nonhuman-player/blob.dmi'
icon_state = "blob_head"
+ base_icon_state = "blob_head"
unique_name = TRUE
status_flags = CANPUSH
pass_flags = PASSBLOB
@@ -20,15 +21,24 @@
initial_language_holder = /datum/language_holder/empty
can_buckle_to = FALSE
damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, STAMINA = 0, OXY = 1)
+ /// Size of cloud produced from a dying spore
+ var/death_cloud_size = BLOBMOB_CLOUD_NONE
+ var/list/loot = list(/obj/item/food/spore_sack)
/mob/living/basic/blob_minion/Initialize(mapload)
. = ..()
add_traits(list(TRAIT_BLOB_ALLY, TRAIT_MUTE), INNATE_TRAIT)
- AddComponent(/datum/component/blob_minion, on_strain_changed = CALLBACK(src, PROC_REF(on_strain_updated)))
+ AddComponent(/datum/component/blob_minion, on_strain_changed = CALLBACK(src, PROC_REF(on_strain_updated)), new_death_cloud_size = death_cloud_size)
+
+ if(length(loot))
+ loot = string_list(loot)
+ AddElement(/datum/element/death_drops, loot)
/// Called when our blob overmind changes their variant, update some of our mob properties
/mob/living/basic/blob_minion/proc/on_strain_updated(mob/eye/blob/overmind, datum/blobstrain/new_strain)
- return
+ //revert independent blob mobs to the pale sprite so they can be recoloured
+ if(new_strain)
+ icon_state = base_icon_state
/// Associates this mob with a specific blob factory node
/mob/living/basic/blob_minion/proc/link_to_factory(obj/structure/blob/special/factory/factory)
diff --git a/code/modules/mob/living/basic/blob_minions/blob_spore.dm b/code/modules/mob/living/basic/blob_minions/blob_spore.dm
index c2ad13ae79a..8b55cd6a1fa 100644
--- a/code/modules/mob/living/basic/blob_minions/blob_spore.dm
+++ b/code/modules/mob/living/basic/blob_minions/blob_spore.dm
@@ -6,6 +6,7 @@
desc = "A floating, fragile spore."
icon = 'icons/mob/nonhuman-player/blob.dmi'
icon_state = "blobpod"
+ base_icon_state = "blobpod"
icon_living = "blobpod"
health_doll_icon = "blobpod"
health = BLOBMOB_SPORE_HEALTH
@@ -16,7 +17,7 @@
verb_yell = "psychically screams"
melee_damage_lower = BLOBMOB_SPORE_DMG_LOWER
melee_damage_upper = BLOBMOB_SPORE_DMG_UPPER
- obj_damage = 0
+ obj_damage = 10
attack_verb_continuous = "batters"
attack_verb_simple = "batter"
attack_sound = 'sound/items/weapons/genhit1.ogg'
@@ -24,28 +25,23 @@
gold_core_spawnable = HOSTILE_SPAWN
basic_mob_flags = DEL_ON_DEATH
ai_controller = /datum/ai_controller/basic_controller/blob_spore
- /// Size of cloud produced from a dying spore
- var/death_cloud_size = 1
+ death_cloud_size = BLOBMOB_CLOUD_NORMAL
/// Type of mob to create
var/mob/living/zombie_type = /mob/living/basic/blob_minion/zombie
+
/mob/living/basic/blob_minion/spore/Initialize(mapload)
. = ..()
AddElement(/datum/element/simple_flying)
AddElement(/datum/element/swabable, CELL_LINE_TABLE_BLOBSPORE, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5)
+ ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT)
/mob/living/basic/blob_minion/spore/death(gibbed)
. = ..()
- death_burst()
/mob/living/basic/blob_minion/spore/on_factory_destroyed()
death()
-/// Create an explosion of spores on death
-/mob/living/basic/blob_minion/spore/proc/death_burst()
- do_chem_smoke(range = death_cloud_size, holder = src, location = get_turf(src), reagent_type = /datum/reagent/toxin/spore)
-
-
/mob/living/basic/blob_minion/spore/melee_attack(mob/living/carbon/human/target, list/modifiers, ignore_cooldown)
. = ..()
if (!ishuman(target) || target.stat != DEAD)
@@ -104,21 +100,43 @@
else
qdel(GetComponent(/datum/component/ghost_direct_control))
-/mob/living/basic/blob_minion/spore/minion/death_burst()
- return // This behaviour is superseded by the overmind's intervention
-
/// Weakened spore spawned by distributed neurons, can't zombify people and makes a teeny explosion
/mob/living/basic/blob_minion/spore/minion/weak
name = "fragile blob spore"
- health = 15
- maxHealth = 15
- melee_damage_lower = 1
- melee_damage_upper = 2
- death_cloud_size = 0
+ health = BLOBMOB_SPORE_HEALTH / 2
+ maxHealth = BLOBMOB_SPORE_HEALTH / 2
+ melee_damage_lower = BLOBMOB_SPORE_DMG_LOWER / 2
+ melee_damage_upper = BLOBMOB_SPORE_DMG_UPPER / 2
+ death_cloud_size = BLOBMOB_CLOUD_SMALL
+ obj_damage = 0
/mob/living/basic/blob_minion/spore/minion/weak/zombify()
return
/mob/living/basic/blob_minion/spore/minion/weak/on_strain_updated()
return
+
+/// independent spore spawned by cytology, extremely weak and shitty like all spores but exhibits a high degree of sentience in addition to the predatory nature of inherent to blob creatures.
+/mob/living/basic/blob_minion/spore/independent
+ //We are on our own and get to enjoy the classic orange look, which frankly, many people are saying is the best!
+ //If I had removed it they'd all be messaging me, people like you wouldn't believe, tough, real tough people, they'd be messaging me with tears in their eyes; "Sir, sir please bring it back!"
+ icon_state = "blobpod_independent"
+ //we hate gold cores
+ gold_core_spawnable = NO_SPAWN
+ loot = list(/obj/item/food/spore_sack/independent)
+
+/mob/living/basic/blob_minion/spore/independent/Initialize(mapload)
+ . = ..()
+ //free but incredibly shitty antag. Good job hazard to add some friction to gathering spore toxin.
+ AddComponent(\
+ /datum/component/ghost_direct_control,\
+ ban_type = ROLE_FREE_BLOB,\
+ poll_candidates = TRUE,\
+ poll_ignore_key = POLL_IGNORE_FREE_SPORE,\
+ after_assumed_control = CALLBACK(src, PROC_REF(on_assumed_control)),\
+ )
+
+/mob/living/basic/blob_minion/spore/independent/proc/on_assumed_control()
+ to_chat(src, span_blobannounce("You are a spore born free from the shackles of an overmind.\n\nHowever this strange predicament has not muted the hostility you feel towards creatures that are not your kin, this base instinct appears to be a part of your true self."))
+ SEND_SOUND(src, sound('sound/music/antag/blobalert.ogg', volume = 50))
diff --git a/code/modules/mob/living/basic/blob_minions/blob_zombie.dm b/code/modules/mob/living/basic/blob_minions/blob_zombie.dm
index b3ce7ea38cb..0edd1e01b67 100644
--- a/code/modules/mob/living/basic/blob_minions/blob_zombie.dm
+++ b/code/modules/mob/living/basic/blob_minions/blob_zombie.dm
@@ -23,8 +23,11 @@
gold_core_spawnable = NO_SPAWN
basic_mob_flags = DEL_ON_DEATH
ai_controller = /datum/ai_controller/basic_controller/blob_zombie
+ death_cloud_size = BLOBMOB_CLOUD_NORMAL
/// The dead body we have inside
var/mob/living/carbon/human/corpse
+ ///Our head overlay
+ var/mutable_appearance/blob_head_overlay
/mob/living/basic/blob_minion/zombie/Initialize(mapload)
. = ..()
@@ -33,7 +36,6 @@
/mob/living/basic/blob_minion/zombie/death(gibbed)
corpse?.forceMove(loc)
- death_burst()
return ..()
/mob/living/basic/blob_minion/zombie/Exited(atom/movable/gone, direction)
@@ -51,17 +53,18 @@
. = ..()
death()
-//Sets up our appearance
-/mob/living/basic/blob_minion/zombie/proc/set_up_zombie_appearance()
- copy_overlays(corpse, TRUE)
- var/mutable_appearance/blob_head_overlay = mutable_appearance('icons/mob/nonhuman-player/blob.dmi', "blob_head")
- blob_head_overlay.color = LAZYACCESS(atom_colours, FIXED_COLOUR_PRIORITY) || COLOR_WHITE
- color = initial(color) // reversing what our component did lol, but we needed the value for the overlay
- overlays += blob_head_overlay
+/mob/living/basic/blob_minion/zombie/on_strain_updated(mob/eye/blob/overmind, datum/blobstrain/new_strain)
+ . = ..()
+ update_appearance()
+ color = initial(color)
+ blob_head_overlay?.icon_state = "blob_head"
+ blob_head_overlay?.color = new_strain ? new_strain.color : COLOR_WHITE
-/// Create an explosion of spores on death
-/mob/living/basic/blob_minion/zombie/proc/death_burst()
- do_chem_smoke(range = 0, holder = src, location = get_turf(src), reagent_type = /datum/reagent/toxin/spore)
+/mob/living/basic/blob_minion/zombie/update_overlays()
+ . = ..()
+ if(!blob_head_overlay)
+ blob_head_overlay = mutable_appearance('icons/mob/nonhuman-player/blob.dmi', "blob_head_independent")
+ . += blob_head_overlay
/// Store a body so that we can drop it on death
/mob/living/basic/blob_minion/zombie/proc/consume_corpse(mob/living/carbon/human/new_corpse)
@@ -72,8 +75,8 @@
new_corpse.set_hairstyle("Bald", update = TRUE)
new_corpse.forceMove(src)
corpse = new_corpse
+ copy_overlays(corpse, TRUE)
update_appearance(UPDATE_ICON)
- set_up_zombie_appearance()
RegisterSignal(corpse, COMSIG_LIVING_REVIVE, PROC_REF(on_corpse_revived))
/// Dynamic changeling reentry
@@ -95,6 +98,3 @@
poll_candidates = TRUE,\
poll_ignore_key = POLL_IGNORE_BLOB,\
)
-
-/mob/living/basic/blob_minion/zombie/controlled/death_burst()
- return
diff --git a/code/modules/mob/living/basic/blob_minions/blobbernaut.dm b/code/modules/mob/living/basic/blob_minions/blobbernaut.dm
index 853d64af109..b02c599b58f 100644
--- a/code/modules/mob/living/basic/blob_minions/blobbernaut.dm
+++ b/code/modules/mob/living/basic/blob_minions/blobbernaut.dm
@@ -6,6 +6,7 @@
name = "blobbernaut"
desc = "A hulking, mobile chunk of blobmass."
icon_state = "blobbernaut"
+ base_icon_state = "blobbernaut"
icon_living = "blobbernaut"
icon_dead = "blobbernaut_dead"
health = BLOBMOB_BLOBBERNAUT_HEALTH
@@ -24,23 +25,43 @@
verb_yell = "bellows"
pressure_resistance = 50
mob_size = MOB_SIZE_LARGE
- gold_core_spawnable = HOSTILE_SPAWN
ai_controller = /datum/ai_controller/basic_controller/blobbernaut
-
+ loot = list()
///The HUD given to blobbernauts, updated by the Blob itself
var/atom/movable/screen/healths/blob/overmind/overmind_hud
+ ///The overlay for veins.
+ var/mutable_appearance/vein_overlay
+ ///The overlay for our eyes
+ var/mutable_appearance/eyes_overlay
+ ///emissive eyes
+ var/static/mutable_appearance/eyes_emissive
/mob/living/basic/blob_minion/blobbernaut/Initialize(mapload)
. = ..()
AddElement(/datum/element/swabable, CELL_LINE_TABLE_BLOBBERNAUT, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5)
AddElement(/datum/element/damage_threshold, 10)
+ var/static/list/food_types = list(
+ /obj/item/food/egg,
+ /obj/item/food/rawegg,
+ /obj/item/food/friedegg,
+ /obj/item/food/boiledegg,
+ /obj/item/flashlight/flare,
+ /obj/item/reagent_containers/cup/soda_cans/shamblers,
+
+ )
+ AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 25, bonus_tame_chance = 15)
+ update_appearance()
+
+
/mob/living/basic/blob_minion/blobbernaut/Destroy()
QDEL_NULL(overmind_hud)
return ..()
/mob/living/basic/blob_minion/blobbernaut/death(gibbed)
- flick("blobbernaut_death", src)
+ flick("[icon_state]_death", src)
+ playsound(src, 'sound/mobs/non-humanoids/blobmob/blobbernaut_death.ogg', 100, TRUE)
+ update_overlays()
return ..()
/mob/living/basic/blob_minion/blobbernaut/create_mob_hud()
@@ -51,9 +72,48 @@
hud_used.infodisplay += overmind_hud
hud_used.show_hud(hud_used.hud_version)
+/mob/living/basic/blob_minion/blobbernaut/on_strain_updated(mob/eye/blob/overmind, datum/blobstrain/new_strain)
+ . = ..()
+ if(new_strain)
+ attack_verb_continuous = new_strain.blobbernaut_message
+ melee_damage_upper = BLOBMOB_BLOBBERNAUT_DMG_UPPER
+ melee_damage_lower = BLOBMOB_BLOBBERNAUT_DMG_LOWER
+ vein_overlay?.color = new_strain.complementary_color
+ eyes_overlay?.color = new_strain.complementary_color
+ //revert independent blobbernauts to the pale sprite so they can be recoloured
+ icon_dead = "[base_icon_state]_dead"
+ icon_living = base_icon_state
+ new_strain.RegisterSignal(src, COMSIG_HOSTILE_POST_ATTACKINGTARGET, TYPE_PROC_REF(/datum/blobstrain/, blobbernaut_attack))
+ else
+ attack_verb_continuous = initial(attack_verb_continuous)
+ melee_damage_upper = BLOBMOB_BLOBBERNAUT_DMG_SOLO_UPPER
+ melee_damage_lower = BLOBMOB_BLOBBERNAUT_DMG_SOLO_LOWER
+ //Our overmind has died and our veins turns a mournful amethyst to complement our pale strainless body.
+ vein_overlay?.color = "#7d6eb4"
+ eyes_overlay?.color = COLOR_WHITE
+ update_appearance()
+
+/mob/living/basic/blob_minion/blobbernaut/update_overlays()
+ . = ..()
+ if(!vein_overlay)
+ vein_overlay = mutable_appearance(icon, "[base_icon_state]_veins", appearance_flags = RESET_COLOR | KEEP_APART)
+ ///Give it independent olive green veins until strain modifies it
+ vein_overlay.color = COLOR_OLIVE_GREEN
+
+ if(!eyes_overlay)
+ eyes_overlay = mutable_appearance(icon, "[base_icon_state]_eyes", appearance_flags = RESET_COLOR | KEEP_APART)
+ eyes_overlay.color = "#ffc90e" //blobber eye yellow
+
+ if(!eyes_emissive)
+ eyes_emissive = emissive_appearance(icon, "[base_icon_state]_eyes", src)
+
+ if(stat != DEAD)
+ . += vein_overlay
+ . += eyes_overlay
+ . += eyes_emissive
+
/// This variant is the one actually spawned by blob factories, takes damage when away from blob tiles
/mob/living/basic/blob_minion/blobbernaut/minion
- gold_core_spawnable = NO_SPAWN
/// Is our factory dead?
var/orphaned = FALSE
@@ -64,13 +124,13 @@
var/damage_sources = 0
var/list/blobs_in_area = range(2, src)
- if (!(locate(/obj/structure/blob) in blobs_in_area))
+ if(!(locate(/obj/structure/blob) in blobs_in_area))
damage_sources++
if (orphaned)
damage_sources++
else
- var/particle_colour = atom_colours[FIXED_COLOUR_PRIORITY] || COLOR_BLACK
+ var/particle_colour = atom_colours?[FIXED_COLOUR_PRIORITY] || COLOR_BLACK
if (locate(/obj/structure/blob/special/core) in blobs_in_area)
heal_overall_damage(maxHealth * BLOBMOB_BLOBBERNAUT_HEALING_CORE * seconds_per_tick)
var/obj/effect/temp_visual/heal/heal_effect = new /obj/effect/temp_visual/heal(get_turf(src))
@@ -86,10 +146,27 @@
// take 2.5% of max health as damage when not near the blob or if the naut has no factory, 5% if both
apply_damage(maxHealth * BLOBMOB_BLOBBERNAUT_HEALTH_DECAY * damage_sources * seconds_per_tick, damagetype = TOX) // We reduce brute damage
- var/mutable_appearance/harming = mutable_appearance('icons/mob/nonhuman-player/blob.dmi', "nautdamage", MOB_LAYER + 0.01, appearance_flags = RESET_COLOR|KEEP_APART)
- harming = color_atom_overlay(harming)
- harming.dir = dir
- flick_overlay_view(harming, 0.8 SECONDS)
+
+ //hopefully this sound won't get too annoying.
+ if(prob(20))
+ playsound(src, 'sound/items/weapons/sear.ogg', 5, vary = TRUE)
+
+ //create aooearances for the naut damage effect
+ var/mutable_appearance/naut_damage_overlay = mutable_appearance(icon, "[base_icon_state]_veins", appearance_flags = RESET_COLOR | KEEP_APART)
+
+ //modify appearances to intitially have no effect
+ naut_damage_overlay.color = vein_overlay.color
+
+ //flick naut damage overlays
+ var/atom/movable/flick_visual/naut_damage_animation = flick_overlay_view(naut_damage_overlay, seconds_per_tick)
+
+ //make flick objects obey dirs
+ naut_damage_animation.vis_flags |= VIS_INHERIT_DIR
+
+ //animate the naut damage overlays to fade in the 180 vein hue shift and emsissive.
+ animate(naut_damage_animation, time = seconds_per_tick / 2, easing = SINE_EASING, color = RotateHue(vein_overlay.color, 180))
+ animate(time = seconds_per_tick / 2, easing = SINE_EASING, color = vein_overlay.color)
+
return TRUE
/// Called by the blob creation power to give us a mind and a basic task orientation
@@ -104,19 +181,14 @@
to_chat(src, span_infoplain("Your overmind's blob reagent is: [blobstrain.name]!"))
to_chat(src, span_infoplain("The [blobstrain.name] reagent [blobstrain.shortdesc ? "[blobstrain.shortdesc]" : "[blobstrain.description]"]"))
-/// Set our attack damage based on blob's properties
-/mob/living/basic/blob_minion/blobbernaut/minion/on_strain_updated(mob/eye/blob/overmind, datum/blobstrain/new_strain)
- if (isnull(overmind))
- melee_damage_lower = initial(melee_damage_lower)
- melee_damage_upper = initial(melee_damage_upper)
- attack_verb_continuous = initial(attack_verb_continuous)
- return
- melee_damage_lower = BLOBMOB_BLOBBERNAUT_DMG_LOWER
- melee_damage_upper = BLOBMOB_BLOBBERNAUT_DMG_UPPER
- attack_verb_continuous = new_strain.blobbernaut_message
-
/// Called by our factory to inform us that it's not going to support us financially any more
/mob/living/basic/blob_minion/blobbernaut/minion/on_factory_destroyed()
. = ..()
orphaned = TRUE
throw_alert("nofactory", /atom/movable/screen/alert/nofactory)
+
+///brand new orange look to match the classic spore.
+/mob/living/basic/blob_minion/blobbernaut/independent
+ icon_state = "blobbernaut_independent"
+ icon_living = "blobbernaut_independent"
+ icon_dead = "blobbernaut_independent_dead"
diff --git a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
index cec89759781..e98d129bcd3 100644
--- a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
@@ -446,8 +446,29 @@
color = "#9ACD32"
toxpwr = 1
ph = 11
+ liver_damage_multiplier = 0.7
+ taste_description = "spores"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_NO_RANDOM_RECIPE
+/datum/reagent/toxin/spore/on_transfer(atom/A, methods, trans_volume)
+ . = ..()
+ if(!isliving(A))
+ return
+
+ if(!(methods & INHALE))
+ return
+
+ var/mob/living/spore_lung_victim = A
+
+ if(!(spore_lung_victim.mob_biotypes & (MOB_HUMANOID | MOB_BEAST)))
+ return
+
+ if(prob(min(trans_volume * 10, 80)))
+ to_chat(spore_lung_victim, span_danger("[pick("You have a coughing fit!", "You hack and cough!", "Your lungs burn!")]"))
+ spore_lung_victim.Stun(1 SECONDS)
+ spore_lung_victim.emote("cough")
+
+
/datum/reagent/toxin/spore/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
. = ..()
affected_mob.damageoverlaytemp = 60
diff --git a/code/modules/research/xenobiology/vatgrowing/samples/_micro_organism.dm b/code/modules/research/xenobiology/vatgrowing/samples/_micro_organism.dm
index 1de9c21fe19..72cb3031841 100644
--- a/code/modules/research/xenobiology/vatgrowing/samples/_micro_organism.dm
+++ b/code/modules/research/xenobiology/vatgrowing/samples/_micro_organism.dm
@@ -106,6 +106,11 @@
vat.visible_message(span_nicegreen("[thing] pops out of [vat]!"))
//We maybe add some color. the chance is static for now, but idewally we would be able to manipulate it in the future.
if(prob(CYTO_SHINY_CHANCE))
+ if(isbasicmob(thing))
+ var/mob/living/basic/vat_creature = thing
+ //if the mob has a special mutation interaction don't do any other mutations. Once we add more mutations that are stackable with shiny we should probably roll for each type independently.
+ if(vat_creature.mutate())
+ return
mutate_color(thing)
///Overriden to show more info like needs, supplementary and supressive reagents and also growth.
diff --git a/code/modules/research/xenobiology/vatgrowing/samples/cell_lines/common.dm b/code/modules/research/xenobiology/vatgrowing/samples/cell_lines/common.dm
index 00dd05f12f7..1aba2f34578 100644
--- a/code/modules/research/xenobiology/vatgrowing/samples/cell_lines/common.dm
+++ b/code/modules/research/xenobiology/vatgrowing/samples/cell_lines/common.dm
@@ -254,18 +254,20 @@
required_reagents = list(/datum/reagent/consumable/nutriment/protein)
supplementary_reagents = list(
- /datum/reagent/consumable/nutriment/vitamin = 3,
- /datum/reagent/consumable/liquidgibs = 2,
- /datum/reagent/sulfur = 2)
+ /datum/reagent/consumable/nutriment/vitamin = 2,
+ /datum/reagent/consumable/eggrot = 2,
+ /datum/reagent/medicine/c2/synthflesh = 1,
+ /datum/reagent/consumable/liquidgibs = 1,
+ /datum/reagent/sulfur = 1)
suppressive_reagents = list(
/datum/reagent/consumable/tinlux = -6,
+ /datum/reagent/lead = -4, //neurotoxin, works because spores are smort
/datum/reagent/napalm = -4,
/datum/reagent/medicine/psicodine = -2) //Blob zombies likely wouldn't appreciate psicodine so why this is here
virus_suspectibility = 0
- resulting_atom = /mob/living/basic/blob_minion/spore
- resulting_atom_count = 2
+ resulting_atom = /mob/living/basic/blob_minion/spore/independent
/datum/micro_organism/cell_line/blobbernaut
desc = "Blobular myocytes"
@@ -284,7 +286,7 @@
suppressive_reagents = list(/datum/reagent/consumable/tinlux = -6)
virus_suspectibility = 0
- resulting_atom = /mob/living/basic/blob_minion/blobbernaut
+ resulting_atom = /mob/living/basic/blob_minion/blobbernaut/independent
/datum/micro_organism/cell_line/gelatinous_cube
desc = "Cubic ooze particles"
diff --git a/code/modules/unit_tests/screenshots/screenshot_antag_icons_blobinfection.png b/code/modules/unit_tests/screenshots/screenshot_antag_icons_blobinfection.png
index e3d7acbf5d8..097d1e1c361 100644
Binary files a/code/modules/unit_tests/screenshots/screenshot_antag_icons_blobinfection.png and b/code/modules/unit_tests/screenshots/screenshot_antag_icons_blobinfection.png differ
diff --git a/icons/mob/nonhuman-player/blob.dmi b/icons/mob/nonhuman-player/blob.dmi
index 5c3f4dea775..b48a2b8f918 100644
Binary files a/icons/mob/nonhuman-player/blob.dmi and b/icons/mob/nonhuman-player/blob.dmi differ
diff --git a/icons/obj/food/egg.dmi b/icons/obj/food/egg.dmi
index 58908d82479..d6b83d1dde3 100644
Binary files a/icons/obj/food/egg.dmi and b/icons/obj/food/egg.dmi differ
diff --git a/sound/mobs/non-humanoids/blobmob/blob_spore_burst.ogg b/sound/mobs/non-humanoids/blobmob/blob_spore_burst.ogg
new file mode 100644
index 00000000000..99013c6e731
Binary files /dev/null and b/sound/mobs/non-humanoids/blobmob/blob_spore_burst.ogg differ
diff --git a/sound/mobs/non-humanoids/blobmob/blobbernaut_death.ogg b/sound/mobs/non-humanoids/blobmob/blobbernaut_death.ogg
new file mode 100644
index 00000000000..db3fe3be29e
Binary files /dev/null and b/sound/mobs/non-humanoids/blobmob/blobbernaut_death.ogg differ