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"