diff --git a/code/__defines/species_languages.dm b/code/__defines/species_languages.dm
index bacd4ebd37..656b0dff2a 100644
--- a/code/__defines/species_languages.dm
+++ b/code/__defines/species_languages.dm
@@ -53,6 +53,7 @@
#define LANGUAGE_AKHANI "Akhani"
#define LANGUAGE_ALAI "Alai"
#define LANGUAGE_ZADDAT "Vedahq"
+#define LANGUAGE_BLOB "Blob"
#define LANGUAGE_GIBBERISH "Babel"
// Language flags.
diff --git a/code/game/objects/effects/chem/chemsmoke.dm b/code/game/objects/effects/chem/chemsmoke.dm
index 43b42cbac9..7790bbcddb 100644
--- a/code/game/objects/effects/chem/chemsmoke.dm
+++ b/code/game/objects/effects/chem/chemsmoke.dm
@@ -3,7 +3,7 @@
/////////////////////////////////////////////
/obj/effect/effect/smoke/chem
icon = 'icons/effects/chemsmoke.dmi'
- opacity = 0
+ opacity = TRUE
time_to_live = 300
pass_flags = PASSTABLE | PASSGRILLE | PASSGLASS //PASSGLASS is fine here, it's just so the visual effect can "flow" around glass
@@ -16,6 +16,9 @@
walk(src, 0) // Because we might have called walk_to, we must stop the walk loop or BYOND keeps an internal reference to us forever.
return ..()
+/obj/effect/effect/smoke/chem/transparent
+ opacity = FALSE
+
/datum/effect/effect/system/smoke_spread/chem
smoke_type = /obj/effect/effect/smoke/chem
var/obj/chemholder
@@ -36,6 +39,10 @@
qdel(src)
..()
+/datum/effect/effect/system/smoke_spread/chem/blob
+ show_log = 0
+ smoke_type = /obj/effect/effect/smoke/chem/transparent
+
/datum/effect/effect/system/smoke_spread/chem/New()
..()
chemholder = new/obj()
@@ -156,7 +163,7 @@
if(passed_smoke)
smoke = passed_smoke
else
- smoke = new /obj/effect/effect/smoke/chem(location)
+ smoke = new smoke_type(location)
if(chemholder.reagents.reagent_list.len)
chemholder.reagents.trans_to_obj(smoke, chemholder.reagents.total_volume / dist, copy = 1) //copy reagents to the smoke so mob/breathe() can handle inhaling the reagents
@@ -166,7 +173,8 @@
smoke.pixel_x = -32 + rand(-8, 8)
smoke.pixel_y = -32 + rand(-8, 8)
walk_to(smoke, T)
- smoke.set_opacity(1) //switching opacity on after the smoke has spawned, and then
+ if(initial(smoke.opacity))
+ smoke.set_opacity(1) //switching opacity on after the smoke has spawned, and then
sleep(150+rand(0,20)) // turning it off before it is deleted results in cleaner
smoke.set_opacity(0) // lighting and view range updates
fadeOut(smoke)
diff --git a/code/modules/blob2/_defines.dm b/code/modules/blob2/_defines.dm
index 1ad0b73893..85f9d39115 100644
--- a/code/modules/blob2/_defines.dm
+++ b/code/modules/blob2/_defines.dm
@@ -7,4 +7,7 @@
#define BLOB_DIFFICULTY_EASY 0
#define BLOB_DIFFICULTY_MEDIUM 1
#define BLOB_DIFFICULTY_HARD 2
-#define BLOB_DIFFICULTY_SUPERHARD 3
\ No newline at end of file
+#define BLOB_DIFFICULTY_SUPERHARD 3
+
+#define BLOB_CHUNK_CONSTANT 0
+#define BLOB_CHUNK_TOGGLE 1
diff --git a/code/modules/blob2/blobs/core.dm b/code/modules/blob2/blobs/core.dm
index fc2e325ae2..d9bd056d7f 100644
--- a/code/modules/blob2/blobs/core.dm
+++ b/code/modules/blob2/blobs/core.dm
@@ -102,6 +102,9 @@ var/list/blob_cores = list()
point_rate = new_rate
/obj/structure/blob/core/Destroy()
+ var/turf/T = get_turf(src)
+ new /obj/item/weapon/blobcore_chunk(T, overmind.blob_type)
+
blob_cores -= src
if(overmind)
overmind.blob_core = null
@@ -139,6 +142,8 @@ var/list/blob_cores = list()
if(prob(5))
B.change_to(/obj/structure/blob/shield/core, overmind)
+ overmind.blob_type.on_core_process(src)
+
/obj/structure/blob/core/proc/create_overmind(client/new_overmind, override_delay)
if(overmind_get_delay > world.time && !override_delay)
return
@@ -192,4 +197,4 @@ var/list/blob_cores = list()
if(initial(BT.difficulty) > difficulty_threshold)
continue
valid_types += BT
- return pick(valid_types)
\ No newline at end of file
+ return pick(valid_types)
diff --git a/code/modules/blob2/blobs/factory.dm b/code/modules/blob2/blobs/factory.dm
index d574a0cfbc..f9415a6e66 100644
--- a/code/modules/blob2/blobs/factory.dm
+++ b/code/modules/blob2/blobs/factory.dm
@@ -42,6 +42,8 @@
if(overmind.blob_type.ranged_spores)
S.projectiletype = overmind.blob_type.spore_projectile
S.projectilesound = overmind.blob_type.spore_firesound
+ S.projectile_accuracy = overmind.blob_type.spore_accuracy
+ S.projectile_dispersion = overmind.blob_type.spore_dispersion
else //Other mobs don't add themselves in New. Ew.
S.nest = src
spores += S
diff --git a/code/modules/blob2/blobs/node.dm b/code/modules/blob2/blobs/node.dm
index aa1dcb3e8f..5f54b6b80a 100644
--- a/code/modules/blob2/blobs/node.dm
+++ b/code/modules/blob2/blobs/node.dm
@@ -33,4 +33,6 @@ var/list/blob_nodes = list()
/obj/structure/blob/node/process()
set waitfor = FALSE
if(overmind) // This check is so that if the core is killed, the nodes stop.
- pulse_area(overmind, 10, BLOB_NODE_PULSE_RANGE, BLOB_NODE_EXPAND_RANGE)
\ No newline at end of file
+ pulse_area(overmind, 10, BLOB_NODE_PULSE_RANGE, BLOB_NODE_EXPAND_RANGE)
+
+ overmind.blob_type.on_node_process(src)
diff --git a/code/modules/blob2/core_chunk.dm b/code/modules/blob2/core_chunk.dm
new file mode 100644
index 0000000000..8a5795aae9
--- /dev/null
+++ b/code/modules/blob2/core_chunk.dm
@@ -0,0 +1,99 @@
+
+/obj/item/weapon/blobcore_chunk
+ name = "core chunk"
+ desc = "The remains of some strange life-form. It smells awful."
+ description_info = "Some blob types will have core effects when the chunk is used in-hand, toggled with an alt click, or constantly active."
+ icon = 'icons/mob/blob.dmi'
+ icon_state = "blobcore"
+ var/datum/blob_type/blob_type // The blob type this dropped from.
+
+ var/active_ability_cooldown = 20 SECONDS
+ var/last_active_use = 0
+
+ var/should_tick = TRUE // Incase it's a toggle.
+
+ var/passive_ability_cooldown = 5 SECONDS
+ var/last_passive_use = 0
+
+ drop_sound = 'sound/effects/slime_squish.ogg'
+
+/obj/item/weapon/blobcore_chunk/New(var/atom/newloc, var/datum/blob_type/parentblob = null)
+ ..(newloc)
+
+ setup_blobtype(parentblob)
+
+/obj/item/weapon/blobcore_chunk/Destroy()
+ STOP_PROCESSING(SSobj, src)
+
+ blob_type = null
+
+ ..()
+
+/obj/item/weapon/blobcore_chunk/proc/setup_blobtype(var/datum/blob_type/parentblob = null)
+ if(!parentblob)
+ name = "inert [initial(name)]"
+
+ else
+ blob_type = parentblob
+ name = "[blob_type.name] [initial(name)]"
+
+ if(blob_type)
+ color = blob_type.color
+ if(LAZYLEN(blob_type.core_tech))
+ origin_tech = blob_type.core_tech.Copy()
+
+ if(blob_type.chunk_active_type == BLOB_CHUNK_CONSTANT)
+ should_tick = TRUE
+ else if(blob_type.chunk_active_type == BLOB_CHUNK_TOGGLE)
+ should_tick = FALSE
+
+ active_ability_cooldown = blob_type.chunk_active_ability_cooldown
+ passive_ability_cooldown = blob_type.chunk_passive_ability_cooldown
+
+ blob_type.chunk_setup(src)
+
+ START_PROCESSING(SSobj, src)
+
+/obj/item/weapon/blobcore_chunk/proc/call_chunk_unique()
+ if(blob_type)
+ blob_type.chunk_unique(src, args)
+ return
+
+/obj/item/weapon/blobcore_chunk/proc/get_carrier(var/atom/target)
+ var/atom/A = target ? target.loc : src
+ if(!istype(A, /mob/living))
+ A = get_carrier(A)
+
+ if(isturf(A) || isarea(A)) // Something has gone horribly wrong if the second is true.
+ return FALSE // No mob is carrying us.
+
+ return A
+
+/obj/item/weapon/blobcore_chunk/blob_act(obj/structure/blob/B)
+ if(B.overmind && !blob_type)
+ setup_blobtype(B.overmind.blob_type)
+
+ return
+
+/obj/item/weapon/blobcore_chunk/attack_self(var/mob/user)
+ if(blob_type && world.time > active_ability_cooldown + last_active_use)
+ last_active_use = world.time
+ to_chat(user, "\icon [src] \The [src] gesticulates.")
+ blob_type.on_chunk_use(src, user)
+ else
+ to_chat(user, "\The [src] doesn't seem to respond.")
+ ..()
+
+/obj/item/weapon/blobcore_chunk/process()
+ if(blob_type && should_tick && world.time > passive_ability_cooldown + last_passive_use)
+ last_passive_use = world.time
+ blob_type.on_chunk_tick(src)
+
+/obj/item/weapon/blobcore_chunk/AltClick(mob/living/carbon/user)
+ if(blob_type &&blob_type.chunk_active_type == BLOB_CHUNK_TOGGLE)
+ should_tick = !should_tick
+
+ if(should_tick)
+ to_chat(user, "\The [src] shudders with life.")
+ else
+ to_chat(user, "\The [src] stills, returning to a death-like state.")
diff --git a/code/modules/blob2/overmind/types.dm b/code/modules/blob2/overmind/types.dm
index 6c453b51e9..01eb23bf69 100644
--- a/code/modules/blob2/overmind/types.dm
+++ b/code/modules/blob2/overmind/types.dm
@@ -37,12 +37,19 @@
var/spore_firesound = 'sound/effects/slime_squish.ogg'
var/spore_range = 7 // The range the spore can fire.
var/spore_projectile = /obj/item/projectile/energy/blob
+ var/spore_accuracy = 0 // Projectile accuracy
+ var/spore_dispersion = 0 // Dispersion.
var/factory_type = /obj/structure/blob/factory
var/resource_type = /obj/structure/blob/resource
var/node_type = /obj/structure/blob/node
var/shield_type = /obj/structure/blob/shield
+ var/list/core_tech = list(TECH_BIO = 4, TECH_MATERIAL = 3) // Tech for the item created when a core is destroyed.
+ var/chunk_active_type = BLOB_CHUNK_TOGGLE
+ var/chunk_active_ability_cooldown = 20 SECONDS
+ var/chunk_passive_ability_cooldown = 5 SECONDS
+
// Called when a blob receives damage. This needs to return the final damage or blobs will be immortal.
/datum/blob_type/proc/on_received_damage(var/obj/structure/blob/B, damage, damage_type)
return damage
@@ -63,6 +70,14 @@
/datum/blob_type/proc/on_pulse(var/obj/structure/blob/B)
return
+// Called when the core processes.
+/datum/blob_type/proc/on_core_process(var/obj/structure/blob/B)
+ return
+
+// Called when a node processes.
+/datum/blob_type/proc/on_node_process(var/obj/structure/blob/B)
+ return
+
// Called when hit by EMP.
/datum/blob_type/proc/on_emp(obj/structure/blob/B, severity)
return
@@ -71,10 +86,29 @@
/datum/blob_type/proc/on_water(obj/structure/blob/B, amount)
return
-// Spore things
+// Spore death
/datum/blob_type/proc/on_spore_death(mob/living/simple_mob/blob/spore/S)
return
+// Spore handle_special call.
+/datum/blob_type/proc/on_spore_lifetick(mob/living/simple_mob/blob/spore/S)
+ return
+
+// Blob core chunk process.
+/datum/blob_type/proc/on_chunk_tick(obj/item/weapon/blobcore_chunk/B)
+ return
+
+// Blob core chunk use in-hand.
+/datum/blob_type/proc/on_chunk_use(obj/item/weapon/blobcore_chunk/B, mob/user)
+ return
+
+// Proc that is unique to the blob type.
+/datum/blob_type/proc/chunk_unique(obj/item/weapon/blobcore_chunk/B, var/list/extra_args = null)
+ return
+
+// Set up the blob type for the chunk.
+/datum/blob_type/proc/chunk_setup(obj/item/weapon/blobcore_chunk/B)
+ return
// Subtypes
@@ -98,6 +132,14 @@
/datum/blob_type/grey_goo/on_emp(obj/structure/blob/B, severity)
B.adjust_integrity(-(20 / severity))
+/datum/blob_type/grey_goo/on_chunk_tick(obj/item/weapon/blobcore_chunk/B)
+ var/turf/T = get_turf(B)
+ for(var/mob/living/L in view(world.view, T))
+ if(L.stat != DEAD)
+ L.adjustBruteLoss(-1)
+ L.adjustFireLoss(-1)
+ return
+
// Slow, tanky blobtype which uses not spores, but hivebots, as its soldiers.
/datum/blob_type/fabrication_swarm
name = "iron tide"
@@ -135,6 +177,14 @@
/datum/blob_type/fabrication_swarm/on_emp(obj/structure/blob/B, severity)
B.adjust_integrity(-(30 / severity))
+/datum/blob_type/fabrication_swarm/on_chunk_tick(obj/item/weapon/blobcore_chunk/B)
+ var/turf/T = get_turf(B)
+ for(var/mob/living/L in view(world.view, T))
+ if(L.stat != DEAD && L.isSynthetic())
+ L.adjustBruteLoss(-1)
+ L.adjustFireLoss(-1)
+ return
+
// A blob meant to be fought like a fire.
/datum/blob_type/blazing_oil
name = "blazing oil"
@@ -148,6 +198,7 @@
ai_aggressiveness = 50
damage_type = BURN
burn_multiplier = 0 // Fire immunity
+ chunk_active_ability_cooldown = 4 MINUTES
attack_message = "The blazing oil splashes you with its burning oil"
attack_message_living = ", and you feel your skin char and melt"
attack_message_synth = ", and your external plating melts"
@@ -168,6 +219,17 @@
if(env)
env.add_thermal_energy(10 * 1000)
+/datum/blob_type/blazing_oil/on_chunk_tick(obj/item/weapon/blobcore_chunk/B)
+ var/turf/T = get_turf(B)
+ if(!T)
+ return
+ var/datum/gas_mixture/env = T.return_air()
+ if(env)
+ env.add_thermal_energy(10 * 1000)
+
+/datum/blob_type/blazing_oil/on_chunk_use(obj/item/weapon/blobcore_chunk/B, mob/living/user)
+ user.add_modifier(/datum/modifier/exothermic, 5 MINUTES)
+ return
// Mostly a classic blob. No nodes, no other blob types.
/datum/blob_type/classic
@@ -182,6 +244,30 @@
spread_modifier = 1.0
ai_aggressiveness = 0
+/datum/blob_type/classic/on_chunk_use(obj/item/weapon/blobcore_chunk/B, mob/living/user)
+ var/turf/T = get_turf(B)
+
+ to_chat(user, "\The [B] produces a soothing ooze!")
+
+ T.visible_message("\The [B] shudders at \the [user]'s touch, before disgorging a disgusting ooze.")
+
+ for(var/turf/simulated/floor/F in view(2, T))
+ spawn()
+ var/obj/effect/effect/water/splash = new(T)
+ splash.create_reagents(15)
+ splash.reagents.add_reagent("blood", 10,list("blood_colour" = color))
+ splash.set_color()
+
+ splash.set_up(F, 2, 3)
+
+ var/obj/effect/decal/cleanable/chemcoating/blood = locate() in T
+ if(!istype(blood))
+ blood = new(T)
+ blood.reagents.add_reagent("blood", 10,list("blood_colour" = color))
+ blood.reagents.add_reagent("tricorlidaze", 5)
+ blood.update_icon()
+
+ return
// Makes robots cry. Really weak to brute damage.
/datum/blob_type/electromagnetic_web
@@ -198,6 +284,7 @@
brute_multiplier = 3
burn_multiplier = 2
ai_aggressiveness = 60
+ chunk_active_type = BLOB_CHUNK_CONSTANT
attack_message = "The web lashes you"
attack_message_living = ", and you hear a faint buzzing"
attack_message_synth = ", and your electronics get badly damaged"
@@ -209,6 +296,13 @@
/datum/blob_type/electromagnetic_web/on_attack(obj/structure/blob/B, mob/living/victim)
victim.emp_act(2)
+/datum/blob_type/electromagnetic_web/on_chunk_tick(obj/item/weapon/blobcore_chunk/B)
+ var/turf/T = get_turf(B)
+ if(!T)
+ return
+
+ for(var/mob/living/L in view(2, T))
+ L.add_modifier(/datum/modifier/faraday, 30 SECONDS)
// Makes spores that spread the blob and infest dead people.
/datum/blob_type/fungal_bloom
@@ -228,6 +322,7 @@
ai_aggressiveness = 40
can_build_factories = TRUE
spore_type = /mob/living/simple_mob/blob/spore/infesting
+ chunk_active_ability_cooldown = 2 MINUTES
/datum/blob_type/fungal_bloom/on_spore_death(mob/living/simple_mob/blob/spore/S)
if(S.is_infesting)
@@ -240,6 +335,13 @@
B = new /obj/structure/blob/normal(T, S.overmind) // Otherwise spread it.
B.visible_message("\A [B] forms on \the [T] as \the [S] bursts!")
+/datum/blob_type/fungal_bloom/on_chunk_use(obj/item/weapon/blobcore_chunk/B, mob/living/user)
+ var/mob/living/simple_mob/blob/spore/S = new spore_type(get_turf(B))
+ S.faction = user.faction
+ S.blob_type = src
+ S.update_icons()
+ S.ai_holder.forget_everything()
+
// Makes tons of weak spores whenever it spreads.
/datum/blob_type/fulminant_organism
name = "fulminant organism"
@@ -259,6 +361,7 @@
ai_aggressiveness = 30 // The spores do most of the fighting.
can_build_factories = TRUE
spore_type = /mob/living/simple_mob/blob/spore/weak
+ chunk_active_ability_cooldown = 60 SECONDS
/datum/blob_type/fulminant_organism/on_expand(var/obj/structure/blob/B, var/obj/structure/blob/new_B, var/turf/T, var/mob/observer/blob/O)
if(prob(10)) // 10% chance to make a weak spore when expanding.
@@ -281,6 +384,14 @@
S.faction = "blob"
S.update_icons()
+/datum/blob_type/fulminant_organism/on_chunk_use(obj/item/weapon/blobcore_chunk/B, mob/living/user)
+ for(var/I = 1 to rand(3,4))
+ var/mob/living/simple_mob/blob/spore/S = new spore_type(get_turf(B))
+ S.faction = user.faction
+ S.blob_type = src
+ S.update_icons()
+ S.ai_holder.forget_everything()
+ S.add_modifier(/datum/modifier/doomed, 2 MINUTES)
// Auto-retaliates against melee attacks. Weak to projectiles.
/datum/blob_type/reactive_spines
@@ -300,10 +411,12 @@
brute_multiplier = 2.0
spread_modifier = 0.35 // Ranged projectiles tend to have a higher material cost, so ease up on the spreading.
ai_aggressiveness = 40
+ chunk_passive_ability_cooldown = 0.5 SECONDS
attack_message = "The blob stabs you"
attack_message_living = ", and you feel sharp spines pierce your flesh"
attack_message_synth = ", and your external plating is pierced by sharp spines"
attack_verb = "stabs"
+ spore_projectile = /obj/item/projectile/bullet/thorn
// Even if the melee attack is enough to one-shot this blob, it gets to retaliate at least once.
/datum/blob_type/reactive_spines/on_received_damage(var/obj/structure/blob/B, damage, damage_type, mob/living/attacker)
@@ -313,6 +426,32 @@
attacker.blob_act(B)
return ..()
+// We're expecting 1 to be a target, 2 to be an old move loc, and 3 to be a new move loc.
+/datum/blob_type/reactive_spines/chunk_unique(obj/item/weapon/blobcore_chunk/B, var/list/extra_data = null)
+ if(!LAZYLEN(extra_data))
+ return
+
+ var/atom/movable/A = extra_data[1]
+
+ if(istype(A, /mob/living) && world.time > (B.last_passive_use + B.passive_ability_cooldown) && B.should_tick)
+ B.last_passive_use = world.time
+ var/mob/living/L = A
+
+ var/mob/living/carrier = B.get_carrier()
+
+ if(!istype(carrier) || L.z != carrier.z || L == carrier || get_dist(L, carrier) > 3)
+ return
+
+ var/obj/item/projectile/P = new spore_projectile(get_turf(B))
+
+ carrier.visible_message("\The [B] fires a spine at \the [L]!")
+ P.launch_projectile(L, BP_TORSO, carrier)
+
+ return
+
+/datum/blob_type/reactive_spines/chunk_setup(obj/item/weapon/blobcore_chunk/B)
+ GLOB.moved_event.register_global(B, /obj/item/weapon/blobcore_chunk/proc/call_chunk_unique)
+ return
// Spreads damage to nearby blobs, and attacks with the force of all nearby blobs.
/datum/blob_type/synchronous_mesh
@@ -359,6 +498,34 @@
return damage / max(blobs_to_hurt.len, 1) // To hurt the blob that got hit.
+/datum/blob_type/synchronous_mesh/on_chunk_tick(obj/item/weapon/blobcore_chunk/B)
+ var/mob/living/carrier = B.get_carrier()
+
+ if(!carrier)
+ return
+
+ var/list/nearby_mobs = list()
+ for(var/mob/living/L in oview(world.view, carrier))
+ if(L.stat != DEAD)
+ nearby_mobs |= L
+
+ if(nearby_mobs.len)
+ for(var/mob/living/victim in nearby_mobs)
+ var/need_beam = FALSE
+
+ if(carrier.getBruteLoss())
+ need_beam = TRUE
+ victim.adjustBruteLoss(3 / nearby_mobs.len)
+ carrier.adjustBruteLoss(-3 / nearby_mobs.len)
+
+ if(carrier.getFireLoss())
+ need_beam = TRUE
+ victim.adjustFireLoss(3 / nearby_mobs.len)
+ carrier.adjustFireLoss(-3 / nearby_mobs.len)
+
+ if(need_beam)
+ carrier.visible_message("\icon [B] \The [B] sends noxious spores toward \the [victim]!")
+ carrier.Beam(victim, icon_state = "lichbeam", time = 2 SECONDS)
/datum/blob_type/shifting_fragments
name = "shifting fragments"
@@ -375,6 +542,7 @@
burn_multiplier = 0.5
spread_modifier = 0.5
ai_aggressiveness = 30
+ chunk_active_ability_cooldown = 3 MINUTES
attack_message = "A fragment strikes you"
attack_verb = "strikes"
@@ -396,6 +564,10 @@
new_B.forceMove(get_turf(B))
B.forceMove(T)
+/datum/blob_type/shifting_fragments/on_chunk_use(obj/item/weapon/blobcore_chunk/B, mob/living/user)
+ user.add_modifier(/datum/modifier/sprinting, 2 MINUTES)
+ return
+
// A very cool blob, literally.
/datum/blob_type/cryogenic_goo
name = "cryogenic goo"
@@ -412,6 +584,7 @@
burn_multiplier = 1.2
spread_modifier = 0.5
ai_aggressiveness = 50
+ chunk_active_ability_cooldown = 4 MINUTES
attack_message = "The goo stabs you"
attack_message_living = ", and you feel an intense chill from within"
attack_message_synth = ", and your system reports lower internal temperatures"
@@ -442,6 +615,19 @@
if(env)
env.add_thermal_energy(-10 * 1000)
+/datum/blob_type/cryogenic_goo/on_chunk_tick(obj/item/weapon/blobcore_chunk/B)
+ var/turf/simulated/T = get_turf(B)
+ if(!istype(T))
+ return
+ T.freeze_floor()
+ var/datum/gas_mixture/env = T.return_air()
+ if(env)
+ env.add_thermal_energy(-10 * 1000)
+
+/datum/blob_type/cryogenic_goo/on_chunk_use(obj/item/weapon/blobcore_chunk/B, mob/living/user)
+ user.add_modifier(/datum/modifier/endothermic, 5 MINUTES)
+ return
+
// Electric blob that stuns.
/datum/blob_type/energized_jelly
name = "energized jelly"
@@ -462,11 +648,25 @@
attack_message_living = ", and your flesh burns as electricity arcs into you"
attack_message_synth = ", and your internal circuity is overloaded as electricity arcs into you"
attack_verb = "prods"
+ spore_projectile = /obj/item/projectile/beam/shock
/datum/blob_type/energized_jelly/on_attack(obj/structure/blob/B, mob/living/victim, def_zone)
victim.electrocute_act(10, src, 1, def_zone)
victim.stun_effect_act(0, 40, BP_TORSO, src)
+/datum/blob_type/energized_jelly/on_chunk_tick(obj/item/weapon/blobcore_chunk/B)
+ for(var/mob/living/L in oview(world.view, get_turf(B)))
+ var/mob/living/carrier = B.get_carrier()
+
+ if(istype(carrier) && carrier == L)
+ continue
+
+ var/obj/item/projectile/P = new spore_projectile(get_turf(B))
+
+ carrier.visible_message("\The [B] discharges energy toward \the [L]!")
+ P.launch_projectile(L, BP_TORSO, carrier)
+
+ return
// A blob with area of effect attacks.
/datum/blob_type/explosive_lattice
@@ -563,12 +763,19 @@
wet_surroundings(B, 50)
/datum/blob_type/pressurized_slime/proc/wet_surroundings(var/obj/structure/blob/B, var/probability = 50)
- for(var/turf/simulated/T in range(1, B))
+ for(var/turf/simulated/T in range(1, get_turf(B)))
if(prob(probability))
T.wet_floor()
for(var/atom/movable/AM in T)
AM.water_act(2)
+/datum/blob_type/pressurized_slime/on_chunk_tick(obj/item/weapon/blobcore_chunk/B)
+ wet_surroundings(B, 10)
+
+/datum/blob_type/pressurized_slime/on_chunk_use(obj/item/weapon/blobcore_chunk/B, mob/living/user) // Drenches you in water.
+ if(user)
+ user.ExtinguishMob()
+ user.fire_stacks = CLAMP(user.fire_stacks - 1, -25, 25)
// A blob that irradiates everything.
/datum/blob_type/radioactive_ooze
@@ -595,6 +802,9 @@
/datum/blob_type/radioactive_ooze/on_pulse(var/obj/structure/blob/B)
SSradiation.radiate(B, 200)
+/datum/blob_type/radioactive_ooze/on_chunk_tick(obj/item/weapon/blobcore_chunk/B)
+ SSradiation.radiate(B, rand(25,100))
+
// A blob that steals your weapon.
/datum/blob_type/volatile_alluvium
name = "volatile alluvium"
@@ -619,8 +829,11 @@
ranged_spores = TRUE
spore_range = 3
spore_projectile = /obj/item/projectile/energy/blob/splattering
+ spore_accuracy = 15
+ spore_dispersion = 45
factory_type = /obj/structure/blob/factory/sluggish
resource_type = /obj/structure/blob/resource/sluggish
+ chunk_active_ability_cooldown = 2 MINUTES
/datum/blob_type/volatile_alluvium/on_received_damage(var/obj/structure/blob/B, damage, damage_type, mob/living/attacker)
if(damage > 0 && attacker && get_dist(B, attacker) <= 2 && prob(min(damage, 70)) && istype(attacker, /mob/living/carbon/human)) // Melee weapons of any type carried by a human will have a high chance of being stolen.
@@ -639,11 +852,15 @@
/datum/blob_type/volatile_alluvium/on_water(obj/structure/blob/B, amount)
spawn(1)
- var/damage = amount * 2
+ var/damage = amount * 4
B.adjust_integrity(-(damage))
if(B && prob(damage))
B.visible_message("The [name] begins to crumble!")
+/datum/blob_type/volatile_alluvium/on_chunk_use(obj/item/weapon/blobcore_chunk/B, mob/living/user)
+ if(user)
+ user.add_modifier(/datum/modifier/fortify, 60 SECONDS)
+
// A blob that produces noxious smoke-clouds and recycles its dying parts.
/datum/blob_type/ravenous_macrophage
name = "ravenous macrophage"
@@ -657,6 +874,7 @@
damage_lower = 20
damage_upper = 30
armor_check = "bio"
+ armor_pen = 50
brute_multiplier = 0.8
burn_multiplier = 0.3
spread_modifier = 0.8
@@ -683,6 +901,17 @@
if(other.overmind)
other.overmind.add_points(rand(1,4))
+/datum/blob_type/ravenous_macrophage/on_chunk_tick(obj/item/weapon/blobcore_chunk/B)
+ var/mob/living/L = locate() in range(world.view, B)
+ if(prob(5) && !L.stat) // There's some active living thing nearby, produce offgas.
+ B.visible_message("\icon [B] \The [B] disgorches a cloud of noxious gas!")
+ var/turf/T = get_turf(B)
+ var/datum/effect/effect/system/smoke_spread/noxious/BS = new /datum/effect/effect/system/smoke_spread/noxious
+ BS.attach(T)
+ BS.set_up(3, 0, T)
+ playsound(T, 'sound/effects/smoke.ogg', 50, 1, -3)
+ BS.start()
+
// Blob that fires biological mortar shells from its factories.
/datum/blob_type/roiling_mold
name = "roiling mold"
@@ -732,6 +961,21 @@
var/obj/item/projectile/arc/spore/P = new(get_turf(B))
P.launch_projectile(L, BP_TORSO, B)
+/datum/blob_type/roiling_mold/on_chunk_use(obj/item/weapon/blobcore_chunk/B, mob/living/user)
+ for(var/mob/living/L in oview(world.view, get_turf(B)))
+ if(istype(user) && user == L)
+ continue
+
+ if(!check_trajectory(L, B, PASSTABLE)) // Can't fire at things on the other side of walls / windows.
+ continue
+
+ var/obj/item/projectile/P = new spore_projectile(get_turf(B))
+
+ user.visible_message("\icon [B] \The [B] discharges energy toward \the [L]!")
+ P.launch_projectile(L, BP_TORSO, user)
+
+ return
+
// A blob that drains energy from nearby mobs in order to fuel itself, and 'negates' some attacks extradimensionally.
/datum/blob_type/ectoplasmic_horror
name = "ectoplasmic horror"
@@ -801,3 +1045,62 @@
animate(B,alpha = 10, alpha = initial_alpha, time = 10)
return 0
return ..()
+
+/datum/blob_type/ectoplasmic_horror/on_chunk_tick(obj/item/weapon/blobcore_chunk/B)
+ var/mob/living/carrier = B.get_carrier()
+
+ if(!carrier)
+ return
+
+ var/list/nearby_mobs = list()
+ for(var/mob/living/L in oview(world.view, carrier))
+ if(L.stat != DEAD)
+ nearby_mobs |= L
+
+ if(nearby_mobs.len)
+ listclearnulls(active_beams)
+ for(var/mob/living/L in nearby_mobs)
+ if(L.stat == DEAD || L.faction == "blob")
+ continue
+ if(prob(5))
+ var/beamtarget_exists = FALSE
+
+ if(active_beams.len)
+ for(var/datum/beam/Beam in active_beams)
+ if(Beam.target == L)
+ beamtarget_exists = TRUE
+ break
+
+ if(!beamtarget_exists && GetAnomalySusceptibility(L) >= 0.5)
+ carrier.visible_message("\icon [B] \The [B] lashes out at \the [L]!")
+ var/datum/beam/drain_beam = carrier.Beam(L, icon_state = "drain_life", time = 10 SECONDS)
+ active_beams |= drain_beam
+ spawn(9 SECONDS)
+ if(B && drain_beam)
+ carrier.visible_message("\The [B] siphons energy from \the [L]")
+ L.add_modifier(/datum/modifier/berserk_exhaustion, 30 SECONDS)
+ var/total_heal = 0
+
+ if(carrier.getBruteLoss())
+ carrier.adjustBruteLoss(-5)
+ total_heal += 5
+
+ if(carrier.getFireLoss())
+ carrier.adjustFireLoss(-5)
+ total_heal += 5
+
+ if(carrier.getToxLoss())
+ carrier.adjustToxLoss(-5)
+ total_heal += 5
+
+ if(carrier.getOxyLoss())
+ carrier.adjustOxyLoss(-5)
+ total_heal += 5
+
+ if(carrier.getCloneLoss())
+ carrier.adjustCloneLoss(-5)
+ total_heal += 5
+
+ carrier.add_modifier(/datum/modifier/berserk_exhaustion, total_heal SECONDS)
+ if(!QDELETED(drain_beam))
+ qdel(drain_beam)
diff --git a/code/modules/mob/_modifiers/modifiers.dm b/code/modules/mob/_modifiers/modifiers.dm
index 665ae80617..559de30cac 100644
--- a/code/modules/mob/_modifiers/modifiers.dm
+++ b/code/modules/mob/_modifiers/modifiers.dm
@@ -48,6 +48,7 @@
var/pain_immunity // Makes the holder not care about pain while this is on. Only really useful to human mobs.
var/pulse_modifier // Modifier for pulse, will be rounded on application, then added to the normal 'pulse' multiplier which ranges between 0 and 5 normally. Only applied if they're living.
var/pulse_set_level // Positive number. If this is non-null, it will hard-set the pulse level to this. Pulse ranges from 0 to 5 normally.
+ var/emp_modifier // Added to the EMP strength, which is an inverse scale from 1 to 4, with 1 being the strongest EMP. 5 is a nullification.
/datum/modifier/New(var/new_holder, var/new_origin)
holder = new_holder
diff --git a/code/modules/mob/_modifiers/modifiers_misc.dm b/code/modules/mob/_modifiers/modifiers_misc.dm
index 9d87088dae..4d84fc03e8 100644
--- a/code/modules/mob/_modifiers/modifiers_misc.dm
+++ b/code/modules/mob/_modifiers/modifiers_misc.dm
@@ -321,3 +321,55 @@ the artifact triggers the rage.
/datum/modifier/homeothermic/tick()
..()
holder.bodytemperature = round((holder.bodytemperature + T20C) / 2)
+
+/datum/modifier/exothermic
+ name = "heat resistance"
+ desc = "Your body lowers to room temperature."
+
+ on_created_text = "You feel comfortable."
+ on_expired_text = "You feel.. still probably comfortable."
+ stacks = MODIFIER_STACK_EXTEND
+
+/datum/modifier/exothermic/tick()
+ ..()
+ if(holder.bodytemperature > T20C)
+ holder.bodytemperature = round((holder.bodytemperature + T20C) / 2)
+
+/datum/modifier/endothermic
+ name = "cold resistance"
+ desc = "Your body rises to room temperature."
+
+ on_created_text = "You feel comfortable."
+ on_expired_text = "You feel.. still probably comfortable."
+ stacks = MODIFIER_STACK_EXTEND
+
+/datum/modifier/endothermic/tick()
+ ..()
+ if(holder.bodytemperature < T20C)
+ holder.bodytemperature = round((holder.bodytemperature + T20C) / 2)
+
+// Nullifies EMP.
+/datum/modifier/faraday
+ name = "EMP shielding"
+ desc = "You are covered in some form of faraday shielding. EMPs have no effect."
+ mob_overlay_state = "electricity"
+
+ on_created_text = "You feel a surge of energy, that fades to a calm tide."
+ on_expired_text = "You feel a longing for the flow of energy."
+ stacks = MODIFIER_STACK_EXTEND
+
+ emp_modifier = 5
+
+// Kills on expiration.
+/datum/modifier/doomed
+ name = "Doomed"
+ desc = "You are doomed."
+
+ on_created_text = "You feel an overwhelming sense of dread."
+ on_expired_text = "You feel the life drain from your body."
+ stacks = MODIFIER_STACK_EXTEND
+
+/datum/modifier/doomed/on_expire()
+ if(holder.stat != DEAD)
+ holder.visible_message("\The [holder] collapses, the life draining from their body.")
+ holder.death()
diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm
index b767c66b43..b4160abf70 100644
--- a/code/modules/mob/living/living_defense.dm
+++ b/code/modules/mob/living/living_defense.dm
@@ -167,6 +167,15 @@
/mob/living/emp_act(severity)
var/list/L = src.get_contents()
+
+ if(LAZYLEN(modifiers))
+ for(var/datum/modifier/M in modifiers)
+ if(!isnull(M.emp_modifier))
+ severity = CLAMP(severity + M.emp_modifier, 1, 5)
+
+ if(severity == 5) // Effectively nullified.
+ return
+
for(var/obj/O in L)
O.emp_act(severity)
..()
diff --git a/code/modules/mob/living/simple_mob/subtypes/blob/blob.dm b/code/modules/mob/living/simple_mob/subtypes/blob/blob.dm
index 2d700646ae..44ab4fdd09 100644
--- a/code/modules/mob/living/simple_mob/subtypes/blob/blob.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/blob/blob.dm
@@ -23,6 +23,7 @@
var/mob/observer/blob/overmind = null
var/obj/structure/blob/factory/factory = null
+ var/datum/blob_type/blob_type = null // Used for the blob core items, as they have no overmind mob.
mob_class = MOB_CLASS_SLIME
ai_holder_type = /datum/ai_holder/simple_mob/melee
@@ -33,6 +34,8 @@
/mob/living/simple_mob/blob/update_icons()
if(overmind)
color = overmind.blob_type.complementary_color
+ else if(blob_type)
+ color = blob_type.complementary_color
else
color = null
..()
@@ -40,6 +43,8 @@
/mob/living/simple_mob/blob/Destroy()
if(overmind)
overmind.blob_mobs -= src
+ if(blob_type)
+ blob_type = null
return ..()
/mob/living/simple_mob/blob/blob_act(obj/structure/blob/B)
@@ -59,4 +64,17 @@
/mob/living/simple_mob/blob/Process_Spacemove()
for(var/obj/structure/blob/B in range(1, src))
return TRUE
- return ..()
\ No newline at end of file
+ return ..()
+
+/mob/living/simple_mob/blob/IIsAlly(mob/living/L)
+ var/ally = ..(L)
+ if(!ally)
+ var/list/items = L.get_all_held_items()
+ for(var/obj/item/I in items)
+ if(istype(I, /obj/item/weapon/blobcore_chunk))
+ var/obj/item/weapon/blobcore_chunk/BC = I
+ if(!overmind || (BC.blob_type && overmind.blob_type.type == BC.blob_type.type))
+ ally = TRUE
+ break
+
+ return ally
diff --git a/code/modules/mob/living/simple_mob/subtypes/blob/spore.dm b/code/modules/mob/living/simple_mob/subtypes/blob/spore.dm
index 9c540c00f6..3347d73cd2 100644
--- a/code/modules/mob/living/simple_mob/subtypes/blob/spore.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/blob/spore.dm
@@ -75,6 +75,10 @@
color = overmind.blob_type.complementary_color
glow_color = color
glow_toggle = TRUE
+ else if(blob_type)
+ color = blob_type.complementary_color
+ glow_color = color
+ glow_toggle = TRUE
else
color = null
glow_color = null
@@ -102,6 +106,9 @@
infest(H)
break
+ if(overmind)
+ overmind.blob_type.on_spore_lifetick(src)
+
if(factory && z != factory.z) // This is to prevent spores getting lost in space and making the factory useless.
qdel(src)
diff --git a/code/modules/projectiles/projectile/arc.dm b/code/modules/projectiles/projectile/arc.dm
index 5a281029ac..12f135a9bb 100644
--- a/code/modules/projectiles/projectile/arc.dm
+++ b/code/modules/projectiles/projectile/arc.dm
@@ -189,6 +189,7 @@
splash.create_reagents(15)
splash.reagents.add_reagent("stomacid", 5)
splash.reagents.add_reagent("blood", 10,list("blood_colour" = "#ec4940"))
+ splash.set_color()
splash.set_up(F, 2, 3)
@@ -196,3 +197,4 @@
if(!istype(acid))
acid = new(T)
acid.reagents.add_reagent("stomacid", 5)
+ acid.update_icon()
diff --git a/code/modules/projectiles/projectile/blob.dm b/code/modules/projectiles/projectile/blob.dm
index 7ebc5cdfad..f73fe9d008 100644
--- a/code/modules/projectiles/projectile/blob.dm
+++ b/code/modules/projectiles/projectile/blob.dm
@@ -28,9 +28,9 @@
/obj/item/projectile/energy/blob/on_impact(var/atom/A)
if(splatter)
var/turf/location = get_turf(src)
- var/datum/effect/effect/system/smoke_spread/chem/S = new /datum/effect/effect/system/smoke_spread/chem
+ var/datum/effect/effect/system/smoke_spread/chem/blob/S = new /datum/effect/effect/system/smoke_spread/chem/blob
S.attach(location)
- S.set_up(reagents, splatter_volume, 0, location)
+ S.set_up(reagents, rand(1, splatter_volume), 0, location)
playsound(location, 'sound/effects/slime_squish.ogg', 30, 1, -3)
spawn(0)
S.start()
@@ -70,3 +70,12 @@
/obj/item/projectile/energy/blob/freezing/splattering
splatter = TRUE
+
+/obj/item/projectile/bullet/thorn
+ name = "spike"
+ icon_state = "SpearFlight"
+ damage = 20
+ damage_type = BIOACID
+ armor_penetration = 20
+ penetrating = 3
+ fire_sound = 'sound/effects/slime_squish.ogg'
diff --git a/icons/mob/blob.dmi b/icons/mob/blob.dmi
index 86d961c4c8..073a2b03ee 100644
Binary files a/icons/mob/blob.dmi and b/icons/mob/blob.dmi differ
diff --git a/polaris.dme b/polaris.dme
index 2a7184312b..707ca96e92 100644
--- a/polaris.dme
+++ b/polaris.dme
@@ -1426,6 +1426,7 @@
#include "code\modules\awaymissions\zlevel.dm"
#include "code\modules\blob2\_defines.dm"
#include "code\modules\blob2\announcement.dm"
+#include "code\modules\blob2\core_chunk.dm"
#include "code\modules\blob2\blobs\base_blob.dm"
#include "code\modules\blob2\blobs\core.dm"
#include "code\modules\blob2\blobs\factory.dm"