From cf5d7505199c6ef5543a27a31cbfc8bb842fb52e Mon Sep 17 00:00:00 2001 From: Mechoid Date: Fri, 20 Mar 2020 12:29:40 -0700 Subject: [PATCH 1/4] Blobwork, Human mobs with AIs can attack, AI mobs consider blobs to be enemies unless they're Blob faction. --- code/_onclick/click.dm | 2 +- code/_onclick/hud/human.dm | 10 +- code/game/objects/effects/effect_system.dm | 12 ++ code/modules/ai/ai_holder.dm | 6 +- code/modules/ai/ai_holder_targeting.dm | 17 +- code/modules/ai/interfaces.dm | 5 + code/modules/blob2/blobs/base_blob.dm | 91 +++++++++- code/modules/blob2/blobs/core.dm | 9 + code/modules/blob2/blobs/factory.dm | 5 + code/modules/blob2/overmind/types.dm | 159 ++++++++++++++++++ .../human/ai_controlled/ai_controlled.dm | 2 + code/modules/mob/living/living_defense.dm | 3 + code/modules/projectiles/projectile/arc.dm | 30 +++- polaris.dme | 2 +- 14 files changed, 337 insertions(+), 16 deletions(-) diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm index ad36d33f34..8184c122e2 100644 --- a/code/_onclick/click.dm +++ b/code/_onclick/click.dm @@ -43,7 +43,7 @@ next_click = world.time + 1 - if(client.buildmode) + if(client && client.buildmode) build_click(src, client.buildmode, params, A) return diff --git a/code/_onclick/hud/human.dm b/code/_onclick/hud/human.dm index 1c903700b0..835834cf76 100644 --- a/code/_onclick/hud/human.dm +++ b/code/_onclick/hud/human.dm @@ -341,11 +341,13 @@ mymob.radio_use_icon.color = ui_color mymob.radio_use_icon.alpha = ui_alpha - mymob.client.screen = list() + if(mymob.client) + mymob.client.screen = list() + + mymob.client.screen += hud_elements + mymob.client.screen += src.adding + src.hotkeybuttons + mymob.client.screen += mymob.client.void - mymob.client.screen += hud_elements - mymob.client.screen += src.adding + src.hotkeybuttons - mymob.client.screen += mymob.client.void inventory_shown = 0 return diff --git a/code/game/objects/effects/effect_system.dm b/code/game/objects/effects/effect_system.dm index 7d5275cbcd..00896e7107 100644 --- a/code/game/objects/effects/effect_system.dm +++ b/code/game/objects/effects/effect_system.dm @@ -238,6 +238,15 @@ steam.start() -- spawns the effect if(prob(25)) L.emote("cough") +/obj/effect/effect/smoke/bad/noxious + opacity = 0 + +/obj/effect/effect/smoke/bad/noxious/affect(var/mob/living/L) + if (!..()) + return 0 + if(L.needs_to_breathe()) + L.adjustToxLoss(1) + /* Not feasile until a later date /obj/effect/effect/smoke/bad/Crossed(atom/movable/M as mob|obj) ..() @@ -369,6 +378,9 @@ steam.start() -- spawns the effect /datum/effect/effect/system/smoke_spread/bad smoke_type = /obj/effect/effect/smoke/bad +/datum/effect/effect/system/smoke_spread/noxious + smoke_type = /obj/effect/effect/smoke/bad/noxious + /datum/effect/effect/system/smoke_spread/fire smoke_type = /obj/effect/effect/smoke/elemental/fire diff --git a/code/modules/ai/ai_holder.dm b/code/modules/ai/ai_holder.dm index aadcdaa9e2..b5272ea996 100644 --- a/code/modules/ai/ai_holder.dm +++ b/code/modules/ai/ai_holder.dm @@ -9,6 +9,10 @@ /mob/living/Initialize() if(ai_holder_type) ai_holder = new ai_holder_type(src) + if(istype(src, /mob/living/carbon/human)) + var/mob/living/carbon/human/H = src + H.hud_used = new /datum/hud(H) + H.instantiate_hud(H.hud_used) return ..() /mob/living/Destroy() @@ -220,7 +224,7 @@ /datum/ai_holder/proc/handle_stance_strategical() ai_log("++++++++++ Slow Process Beginning ++++++++++", AI_LOG_TRACE) ai_log("handle_stance_strategical() : Called.", AI_LOG_TRACE) - + //We got left around for some reason. Goodbye cruel world. if(!holder) qdel(src) diff --git a/code/modules/ai/ai_holder_targeting.dm b/code/modules/ai/ai_holder_targeting.dm index f1f5ff4a3a..f33906c662 100644 --- a/code/modules/ai/ai_holder_targeting.dm +++ b/code/modules/ai/ai_holder_targeting.dm @@ -28,7 +28,7 @@ . = hearers(vision_range, holder) - holder // Remove ourselves to prevent suicidal decisions. ~ SRC is the ai_holder. . -= dview_mob // Not the dview mob either, nerd. - var/static/hostile_machines = typecacheof(list(/obj/machinery/porta_turret, /obj/mecha)) + var/static/hostile_machines = typecacheof(list(/obj/machinery/porta_turret, /obj/mecha, /obj/structure/blob)) for(var/HM in typecache_filter_list(range(vision_range, holder), hostile_machines)) if(can_see(holder, HM, vision_range)) @@ -75,7 +75,7 @@ /datum/ai_holder/proc/give_target(new_target, urgent = FALSE) ai_log("give_target() : Given '[new_target]', urgent=[urgent].", AI_LOG_TRACE) target = new_target - + if(target != null) lose_target_time = 0 track_target_position() @@ -155,6 +155,11 @@ return FALSE // Turrets won't get hurt if they're still in their cover. return TRUE + if(istype(the_target, /obj/structure/blob)) // Blob mobs are always blob faction, but the blob can anger other things. + var/obj/structure/blob/Blob = the_target + if(holder.faction == Blob.faction) + return FALSE + return TRUE // return FALSE @@ -189,12 +194,12 @@ ai_log("remove_target() : Entering.", AI_LOG_TRACE) if(target) target = null - + lose_target_time = 0 give_up_movement() lose_target_position() set_stance(STANCE_IDLE) - + // Check if target is visible to us. /datum/ai_holder/proc/can_see_target(atom/movable/the_target, view_range = vision_range) ai_log("can_see_target() : Entering.", AI_LOG_TRACE) @@ -274,7 +279,7 @@ // Sets a few vars so mobs that threaten will react faster to an attacker or someone who attacked them before. /datum/ai_holder/proc/on_attacked(atom/movable/AM) last_conflict_time = world.time - add_attacker(AM) + add_attacker(AM) // Checks to see if an atom attacked us lately /datum/ai_holder/proc/check_attacker(var/atom/movable/A) @@ -287,7 +292,7 @@ // Forgive this attacker /datum/ai_holder/proc/remove_attacker(var/atom/movable/A) attackers -= A.name - + // Causes targeting to prefer targeting the taunter if possible. // This generally occurs if more than one option is within striking distance, including the taunter. // Otherwise the default filter will prefer the closest target. diff --git a/code/modules/ai/interfaces.dm b/code/modules/ai/interfaces.dm index 59ffbeea72..d1f7e91c53 100644 --- a/code/modules/ai/interfaces.dm +++ b/code/modules/ai/interfaces.dm @@ -11,6 +11,11 @@ return FALSE return attack_target(A) // This will set click cooldown. +/mob/living/carbon/human/IAttack(atom/A) + if(!canClick()) // Still on cooldown from a "click". + return FALSE + return ClickOn(A) // Except this is an actual fake "click". + /mob/living/proc/IRangedAttack(atom/A) return FALSE diff --git a/code/modules/blob2/blobs/base_blob.dm b/code/modules/blob2/blobs/base_blob.dm index 767cf4c783..527184ebb2 100644 --- a/code/modules/blob2/blobs/base_blob.dm +++ b/code/modules/blob2/blobs/base_blob.dm @@ -17,6 +17,7 @@ var/list/blobs = list() var/heal_timestamp = 0 //we got healed when? var/mob/observer/blob/overmind = null var/base_name = "blob" // The name that gets appended along with the blob_type's name. + var/faction = "blob" /obj/structure/blob/New(var/newloc, var/new_overmind) ..(newloc) @@ -56,6 +57,8 @@ var/list/blobs = list() return TRUE else if(istype(mover, /obj/item/projectile)) var/obj/item/projectile/P = mover + if(istype(P.firer, /obj/structure/blob)) + return TRUE if(istype(P.firer) && P.firer.faction == "blob") return TRUE return FALSE @@ -218,13 +221,97 @@ var/list/blobs = list() qdel(src) return B +/obj/structure/blob/attack_generic(var/mob/user, var/damage, var/attack_verb) + visible_message("[user] [attack_verb] the [src]!") + playsound(loc, 'sound/effects/attackblob.ogg', 100, 1) + user.do_attack_animation(src) + if(overmind) + damage *= overmind.blob_type.brute_multiplier + else + damage *= 2 + + if(overmind) + damage = overmind.blob_type.on_received_damage(src, damage, BRUTE, user) + + adjust_integrity(-damage) + + return + +/obj/structure/blob/attack_hand(mob/living/M as mob) + if(ishuman(M)) + var/mob/living/carbon/human/H = M + H.setClickCooldown(H.get_attack_speed()) + var/datum/unarmed_attack/attack = H.get_unarmed_attack(src, BP_TORSO) + if(!attack) + return FALSE + + if(attack.unarmed_override(H, src, BP_TORSO)) + return FALSE + + H.do_attack_animation(src) + H.visible_message("[H] strikes \the [src]!") + + var/real_damage = rand(3,6) + var/hit_dam_type = attack.damage_type + real_damage += attack.get_unarmed_damage(H) + if(H.gloves) + if(istype(H.gloves, /obj/item/clothing/gloves)) + var/obj/item/clothing/gloves/G = H.gloves + real_damage += G.punch_force + hit_dam_type = G.punch_damtype + if(HULK in H.mutations) + real_damage *= 2 // Hulks do twice the damage + + real_damage = max(1, real_damage) + + var/damage_mult_burn = 1 + var/damage_mult_brute = 1 + + if(hit_dam_type == SEARING) + damage_mult_burn *= 0.3 + damage_mult_brute *= 0.6 + + else if(hit_dam_type == BIOACID) + damage_mult_burn *= 0.6 + damage_mult_brute = 0 + + else if(hit_dam_type in list(ELECTROCUTE, BURN)) + damage_mult_brute = 0 + + else if(hit_dam_type in list(BRUTE, CLONE)) + damage_mult_burn = 0 + + else if(hit_dam_type != HALLOSS) // Tox, Oxy, or something new. Half damage split to the organism. + damage_mult_burn = 0.25 + damage_mult_brute = 0.25 + + else + damage_mult_brute = 0.25 + damage_mult_burn = 0 + + var/burn_dam = real_damage * damage_mult_burn + var/brute_dam = real_damage * damage_mult_brute + + if(overmind) + if(brute_dam) + brute_dam = overmind.blob_type.on_received_damage(src, brute_dam, BRUTE, M) + if(burn_dam) + burn_dam = overmind.blob_type.on_received_damage(src, burn_dam, BURN, M) + + real_damage = burn_dam + brute_dam + + adjust_integrity(-real_damage) + + else + attack_generic(M, rand(1,10), "bashed") + /obj/structure/blob/attackby(var/obj/item/weapon/W, var/mob/user) user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN) playsound(loc, 'sound/effects/attackblob.ogg', 50, 1) visible_message("\The [src] has been attacked with \the [W][(user ? " by [user]." : ".")]") var/damage = W.force switch(W.damtype) - if(BURN) + if(BURN, BIOACID, ELECTROCUTE, OXY) if(overmind) damage *= overmind.blob_type.burn_multiplier else @@ -234,7 +321,7 @@ var/list/blobs = list() playsound(src.loc, 'sound/items/welder.ogg', 100, 1) else playsound(src, 'sound/weapons/tap.ogg', 50, 1) - if(BRUTE) + if(BRUTE, SEARING, TOX, CLONE) if(overmind) damage *= overmind.blob_type.brute_multiplier else diff --git a/code/modules/blob2/blobs/core.dm b/code/modules/blob2/blobs/core.dm index f43ec7ad6f..fc2e325ae2 100644 --- a/code/modules/blob2/blobs/core.dm +++ b/code/modules/blob2/blobs/core.dm @@ -78,6 +78,15 @@ var/list/blob_cores = list() /obj/structure/blob/core/volatile_alluvium desired_blob_type = /datum/blob_type/volatile_alluvium +/obj/structure/blob/core/ravenous_macrophage + desired_blob_type = /datum/blob_type/ravenous_macrophage + +/obj/structure/blob/core/roiling_mold + desired_blob_type = /datum/blob_type/roiling_mold + +/obj/structure/blob/core/ectoplasmic_horror + desired_blob_type = /datum/blob_type/ectoplasmic_horror + /obj/structure/blob/core/classic desired_blob_type = /datum/blob_type/classic diff --git a/code/modules/blob2/blobs/factory.dm b/code/modules/blob2/blobs/factory.dm index 6ee41ccd53..d574a0cfbc 100644 --- a/code/modules/blob2/blobs/factory.dm +++ b/code/modules/blob2/blobs/factory.dm @@ -51,3 +51,8 @@ name = "sluggish factory blob" max_spores = 4 spore_cooldown = 16 SECONDS + +/obj/structure/blob/factory/turret // Produces a single spore slowly, but is intended to be used as a 'mortar' by the blob type. + name = "volatile factory blob" + max_spores = 1 + spore_cooldown = 10 SECONDS diff --git a/code/modules/blob2/overmind/types.dm b/code/modules/blob2/overmind/types.dm index 24ffbd6b1b..6c453b51e9 100644 --- a/code/modules/blob2/overmind/types.dm +++ b/code/modules/blob2/overmind/types.dm @@ -595,6 +595,7 @@ /datum/blob_type/radioactive_ooze/on_pulse(var/obj/structure/blob/B) SSradiation.radiate(B, 200) +// A blob that steals your weapon. /datum/blob_type/volatile_alluvium name = "volatile alluvium" desc = "A churning, earthy mass that moves in waves." @@ -642,3 +643,161 @@ B.adjust_integrity(-(damage)) if(B && prob(damage)) B.visible_message("The [name] begins to crumble!") + +// A blob that produces noxious smoke-clouds and recycles its dying parts. +/datum/blob_type/ravenous_macrophage + name = "ravenous macrophage" + desc = "A disgusting gel that reeks of death." + ai_desc = "resourceful" + effect_desc = "Produces noxious fumes, and melts prey with acidic attacks. Weak to brute damage." + difficulty = BLOB_DIFFICULTY_MEDIUM + color = "#639b3f" + complementary_color = "#d1ec3c" + damage_type = BIOACID + damage_lower = 20 + damage_upper = 30 + armor_check = "bio" + brute_multiplier = 0.8 + burn_multiplier = 0.3 + spread_modifier = 0.8 + ai_aggressiveness = 70 + attack_message = "The macrophage splashes you" + attack_message_living = ", and you feel a horrible burning" + attack_message_synth = ", and your body begins to corrode" + attack_verb = "splashes" + +/datum/blob_type/ravenous_macrophage/on_pulse(var/obj/structure/blob/B) + var/mob/living/L = locate() in range(world.view, B) + if(prob(1) && L.mind && !L.stat) // There's some active living thing nearby, produce offgas. + 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() + +/datum/blob_type/ravenous_macrophage/on_death(obj/structure/blob/B) + var/obj/structure/blob/other = locate() in oview(2, B) + if(other) + B.visible_message("The dying mass is rapidly consumed by the nearby [other]!") + if(other.overmind) + other.overmind.add_points(rand(1,4)) + +// Blob that fires biological mortar shells from its factories. +/datum/blob_type/roiling_mold + name = "roiling mold" + desc = "A bubbling, creeping mold." + ai_desc = "bombarding" + effect_desc = "Bombards nearby organisms with toxic spores. Weak to all damage." + difficulty = BLOB_DIFFICULTY_MEDIUM + color = "#571509" + complementary_color = "#ec4940" + damage_type = BRUTE + damage_lower = 5 + damage_upper = 20 + armor_check = "melee" + brute_multiplier = 1.2 + burn_multiplier = 1.2 + spread_modifier = 0.8 + can_build_factories = TRUE + ai_aggressiveness = 50 + attack_message = "The mold whips you" + attack_message_living = ", and you feel a searing pain" + attack_message_synth = ", and your shell buckles" + attack_verb = "lashes" + spore_projectile = /obj/item/projectile/arc/spore + +/datum/blob_type/roiling_mold/proc/find_target(var/obj/structure/blob/B, var/tries = 0, var/list/previous_targets = null) + if(tries > 3) + return + var/mob/living/L = locate() in (view(world.view + 3, get_turf(B)) - view(2,get_turf(B)) - previous_targets) // No adjacent mobs. + + if(!check_trajectory(L, B, PASSTABLE)) + if(!LAZYLEN(previous_targets)) + previous_targets = list() + + previous_targets |= L + + L = find_target(B, tries + 1, previous_targets) + + return L + +/datum/blob_type/roiling_mold/on_pulse(var/obj/structure/blob/B) + var/mob/living/L = find_target(B) + + if(!istype(L)) + return + + if(istype(B, /obj/structure/blob/factory) && L.stat != DEAD && prob(ai_aggressiveness) && L.faction != "blob") + var/obj/item/projectile/arc/spore/P = new(get_turf(B)) + P.launch_projectile(L, BP_TORSO, B) + +// 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" + desc = "A disgusting translucent slime that feels out of place." + ai_desc = "dodging" + effect_desc = "Drains energy from nearby life-forms in order to expand itself. Weak to all damage." + difficulty = BLOB_DIFFICULTY_MEDIUM + color = "#72109eaa" + complementary_color = "#1a9de8" + damage_type = HALLOSS + damage_lower = 10 + damage_upper = 30 + armor_check = "energy" + brute_multiplier = 1.5 + burn_multiplier = 1.5 + spread_modifier = 0.9 + ai_aggressiveness = 50 + attack_message = "The horror strikes you" + attack_message_living = ", and you feel a wave of exhaustion" + attack_message_synth = ", and your systems begin to slow" + attack_verb = "strikes" + can_build_factories = TRUE + factory_type = /obj/structure/blob/factory/sluggish + spore_type = /mob/living/simple_mob/blob/spore/weak + + var/list/active_beams = list() + +/datum/blob_type/ectoplasmic_horror/on_pulse(var/obj/structure/blob/B) + if(B.type == /obj/structure/blob && (locate(/obj/structure/blob/node) in oview(2, get_turf(B)))) + B.visible_message("The [name] quakes, before hardening.") + new/obj/structure/blob/shield(get_turf(B), B.overmind) + qdel(B) + + if(istype(B, /obj/structure/blob/factory)) + listclearnulls(active_beams) + var/atom/movable/beam_origin = B + for(var/mob/living/L in oview(world.view, B)) + 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) + B.visible_message("\The [B] lashes out at \the [L]!") + var/datum/beam/drain_beam = beam_origin.Beam(L, icon_state = "drain_life", time = 10 SECONDS) + active_beams |= drain_beam + spawn(9 SECONDS) + if(B && drain_beam) + B.visible_message("\The [B] siphons energy from \the [L]") + L.add_modifier(/datum/modifier/berserk_exhaustion, 60 SECONDS) + B.overmind.add_points(rand(10,30)) + if(!QDELETED(drain_beam)) + qdel(drain_beam) + +/datum/blob_type/ectoplasmic_horror/on_received_damage(var/obj/structure/blob/B, damage, damage_type) + if(prob(round(damage * 0.5))) + B.visible_message("\The [B] shimmers, distorting through some unseen dimension.") + var/initial_alpha = B.alpha + spawn() + animate(B,alpha = initial_alpha, alpha = 10, time = 10) + animate(B,alpha = 10, alpha = initial_alpha, time = 10) + return 0 + return ..() diff --git a/code/modules/mob/living/carbon/human/ai_controlled/ai_controlled.dm b/code/modules/mob/living/carbon/human/ai_controlled/ai_controlled.dm index 431b7c4456..7b222d8a61 100644 --- a/code/modules/mob/living/carbon/human/ai_controlled/ai_controlled.dm +++ b/code/modules/mob/living/carbon/human/ai_controlled/ai_controlled.dm @@ -3,6 +3,8 @@ ai_holder_type = /datum/ai_holder/simple_mob/melee/evasive + a_intent = I_HURT + var/generate_species = SPECIES_HUMAN var/generate_dead = FALSE diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index 09e85364ca..b767c66b43 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -206,6 +206,9 @@ var/soaked = get_armor_soak(def_zone, armor_check, armor_pen) var/absorb = run_armor_check(def_zone, armor_check, armor_pen) + if(ai_holder) + ai_holder.react_to_attack(B) + apply_damage(damage, damage_type, def_zone, absorb, soaked) /mob/living/proc/resolve_item_attack(obj/item/I, mob/living/user, var/target_zone) diff --git a/code/modules/projectiles/projectile/arc.dm b/code/modules/projectiles/projectile/arc.dm index 1f19dc0242..5a281029ac 100644 --- a/code/modules/projectiles/projectile/arc.dm +++ b/code/modules/projectiles/projectile/arc.dm @@ -49,7 +49,6 @@ on_impact(loc) return ..() - /obj/item/projectile/arc/launch_projectile(atom/target, target_zone, mob/user, params, angle_override, forced_spread = 0) fired_dir = get_dir(user, target) // Used to determine if the projectile should turn in the air. distance_to_fly = calculate_initial_pixel_distance(user, target) // Calculates how many pixels to travel before hitting the ground. @@ -168,3 +167,32 @@ /obj/item/projectile/arc/radioactive/on_impact(turf/T) SSradiation.radiate(T, rad_power) + +// Blob mortar +/obj/item/projectile/arc/spore + name = "spore" + icon_state = "declone" + damage = 20 + damage_type = BIOACID + armor_penetration = 30 + fire_sound = 'sound/effects/slime_squish.ogg' + +/obj/item/projectile/arc/spore/on_impact(turf/T) + for(var/mob/living/L in T) + attack_mob(L) + + spawn() + T.visible_message("\The [src] covers \the [T] in a corrosive paste!") + 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("stomacid", 5) + splash.reagents.add_reagent("blood", 10,list("blood_colour" = "#ec4940")) + + splash.set_up(F, 2, 3) + + var/obj/effect/decal/cleanable/chemcoating/acid = locate() in T + if(!istype(acid)) + acid = new(T) + acid.reagents.add_reagent("stomacid", 5) diff --git a/polaris.dme b/polaris.dme index 33d64e5d5f..87856f2658 100644 --- a/polaris.dme +++ b/polaris.dme @@ -2864,7 +2864,7 @@ #include "code\ZAS\Zone.dm" #include "interface\interface.dm" #include "interface\skin.dmf" -#include "maps\southern_cross\southern_cross.dm" +#include "maps\example\example.dm" #include "maps\submaps\space_submaps\space.dm" #include "maps\submaps\surface_submaps\mountains\mountains.dm" #include "maps\submaps\surface_submaps\mountains\mountains_areas.dm" From 48c54fc43b27f5bbe6fba1d23ef18c69e6a8c9c3 Mon Sep 17 00:00:00 2001 From: Mechoid Date: Tue, 24 Mar 2020 14:26:27 -0700 Subject: [PATCH 2/4] More blob work. Blob core chunk added. --- code/__defines/species_languages.dm | 1 + code/game/objects/effects/chem/chemsmoke.dm | 14 +- code/modules/blob2/_defines.dm | 5 +- code/modules/blob2/blobs/core.dm | 7 +- code/modules/blob2/blobs/factory.dm | 2 + code/modules/blob2/blobs/node.dm | 4 +- code/modules/blob2/core_chunk.dm | 99 ++++++ code/modules/blob2/overmind/types.dm | 309 +++++++++++++++++- code/modules/mob/_modifiers/modifiers.dm | 1 + code/modules/mob/_modifiers/modifiers_misc.dm | 52 +++ code/modules/mob/living/living_defense.dm | 9 + .../living/simple_mob/subtypes/blob/blob.dm | 20 +- .../living/simple_mob/subtypes/blob/spore.dm | 7 + code/modules/projectiles/projectile/arc.dm | 2 + code/modules/projectiles/projectile/blob.dm | 13 +- icons/mob/blob.dmi | Bin 1474125 -> 1474609 bytes polaris.dme | 1 + 17 files changed, 534 insertions(+), 12 deletions(-) create mode 100644 code/modules/blob2/core_chunk.dm 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 86d961c4c8e7fedf38514cb159bad62753cd85d3..073a2b03eeb5b7abe84b9d44c67e0e601fab05cf 100644 GIT binary patch delta 13356 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 delta 12869 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 Date: Fri, 3 Apr 2020 21:10:55 -0700 Subject: [PATCH 3/4] Explosive Lattice complete. --- code/modules/blob2/overmind/types.dm | 7 +++++++ code/modules/mob/_modifiers/modifiers.dm | 1 + code/modules/mob/_modifiers/modifiers_misc.dm | 12 ++++++++++++ code/modules/mob/living/carbon/human/human.dm | 9 +++++++++ code/modules/mob/living/silicon/silicon.dm | 9 +++++++++ code/modules/mob/living/simple_mob/defense.dm | 10 ++++++++++ polaris.dme | 2 +- 7 files changed, 49 insertions(+), 1 deletion(-) diff --git a/code/modules/blob2/overmind/types.dm b/code/modules/blob2/overmind/types.dm index 01eb23bf69..bac7cfbd7c 100644 --- a/code/modules/blob2/overmind/types.dm +++ b/code/modules/blob2/overmind/types.dm @@ -719,6 +719,13 @@ exploding = FALSE +/datum/blob_type/explosive_lattice/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(1, T)) + L.add_modifier(/datum/modifier/blastshield, 30 SECONDS) // A blob that slips and drowns you. /datum/blob_type/pressurized_slime diff --git a/code/modules/mob/_modifiers/modifiers.dm b/code/modules/mob/_modifiers/modifiers.dm index 559de30cac..c38a76078f 100644 --- a/code/modules/mob/_modifiers/modifiers.dm +++ b/code/modules/mob/_modifiers/modifiers.dm @@ -49,6 +49,7 @@ 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. + var/explosion_modifier // Added to the bomb strength, which is an inverse scale from 1 to 3, with 1 being gibstrength. 4 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 4d84fc03e8..b6e5a8394f 100644 --- a/code/modules/mob/_modifiers/modifiers_misc.dm +++ b/code/modules/mob/_modifiers/modifiers_misc.dm @@ -360,6 +360,18 @@ the artifact triggers the rage. emp_modifier = 5 +// Nullifies explosions. +/datum/modifier/blastshield + name = "Blast Shielding" + desc = "You are protected from explosions somehow." + mob_overlay_state = "electricity" + + on_created_text = "You feel a surge of energy, that fades to a stalwart hum." + on_expired_text = "You feel a longing for the flow of energy." + stacks = MODIFIER_STACK_EXTEND + + explosion_modifier = 3 + // Kills on expiration. /datum/modifier/doomed name = "Doomed" diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 6587d5b0b6..e072bdb720 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -100,6 +100,15 @@ if(!blinded) flash_eyes() + for(var/datum/modifier/M in modifiers) + if(!isnull(M.explosion_modifier)) + severity = CLAMP(severity + M.explosion_modifier, 1, 4) + + severity = round(severity) + + if(severity > 3) + return + var/shielded = 0 var/b_loss = null var/f_loss = null diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm index b2959539b2..c861c480be 100644 --- a/code/modules/mob/living/silicon/silicon.dm +++ b/code/modules/mob/living/silicon/silicon.dm @@ -305,6 +305,15 @@ if(!blinded) flash_eyes() + for(var/datum/modifier/M in modifiers) + if(!isnull(M.explosion_modifier)) + severity = CLAMP(severity + M.explosion_modifier, 1, 4) + + severity = round(severity) + + if(severity > 3) + return + switch(severity) if(1.0) if (stat != 2) diff --git a/code/modules/mob/living/simple_mob/defense.dm b/code/modules/mob/living/simple_mob/defense.dm index 744237b21d..c09bf272cc 100644 --- a/code/modules/mob/living/simple_mob/defense.dm +++ b/code/modules/mob/living/simple_mob/defense.dm @@ -96,6 +96,16 @@ /mob/living/simple_mob/ex_act(severity) if(!blinded) flash_eyes() + + for(var/datum/modifier/M in modifiers) + if(!isnull(M.explosion_modifier)) + severity = CLAMP(severity + M.explosion_modifier, 1, 4) + + severity = round(severity) + + if(severity > 3) + return + var/armor = run_armor_check(def_zone = null, attack_flag = "bomb") var/bombdam = 500 switch (severity) diff --git a/polaris.dme b/polaris.dme index 707ca96e92..0888bad404 100644 --- a/polaris.dme +++ b/polaris.dme @@ -2867,7 +2867,7 @@ #include "code\ZAS\Zone.dm" #include "interface\interface.dm" #include "interface\skin.dmf" -#include "maps\example\example.dm" +#include "maps\southern_cross\southern_cross.dm" #include "maps\submaps\space_submaps\space.dm" #include "maps\submaps\surface_submaps\mountains\mountains.dm" #include "maps\submaps\surface_submaps\mountains\mountains_areas.dm" From 1520c2d190f3f9a42bc6ffe333e3c57770b27a6b Mon Sep 17 00:00:00 2001 From: Mechoid Date: Fri, 10 Apr 2020 15:54:50 -0700 Subject: [PATCH 4/4] S l e e p, and split to Ater's request. --- code/game/objects/effects/chem/chemsmoke.dm | 9 +- code/modules/blob2/overmind/types.dm | 1002 ----------------- .../blob2/overmind/types/blazing_oil.dm | 45 + code/modules/blob2/overmind/types/classic.dm | 37 + .../blob2/overmind/types/cryogenic_goo.dm | 59 + .../overmind/types/ectoplasmic_horror.dm | 128 +++ .../overmind/types/electromagnetic_web.dm | 34 + .../blob2/overmind/types/energized_jelly.dm | 39 + .../blob2/overmind/types/explosive_lattice.dm | 58 + .../blob2/overmind/types/fabrication_swarm.dm | 44 + .../overmind/types/fulminant_organism.dm | 50 + .../blob2/overmind/types/fungal_bloom.dm | 37 + code/modules/blob2/overmind/types/grey_goo.dm | 27 + .../blob2/overmind/types/pressurized_slime.dm | 56 + .../blob2/overmind/types/radioactive_ooze.dm | 27 + .../overmind/types/ravenous_macrophage.dm | 50 + .../blob2/overmind/types/reactive_spines.dm | 59 + .../blob2/overmind/types/roiling_mold.dm | 63 ++ .../overmind/types/shifting_fragments.dm | 40 + .../blob2/overmind/types/synchronous_mesh.dm | 73 ++ .../blob2/overmind/types/volatile_alluvium.dm | 55 + polaris.dme | 19 + 22 files changed, 1005 insertions(+), 1006 deletions(-) create mode 100644 code/modules/blob2/overmind/types/blazing_oil.dm create mode 100644 code/modules/blob2/overmind/types/classic.dm create mode 100644 code/modules/blob2/overmind/types/cryogenic_goo.dm create mode 100644 code/modules/blob2/overmind/types/ectoplasmic_horror.dm create mode 100644 code/modules/blob2/overmind/types/electromagnetic_web.dm create mode 100644 code/modules/blob2/overmind/types/energized_jelly.dm create mode 100644 code/modules/blob2/overmind/types/explosive_lattice.dm create mode 100644 code/modules/blob2/overmind/types/fabrication_swarm.dm create mode 100644 code/modules/blob2/overmind/types/fulminant_organism.dm create mode 100644 code/modules/blob2/overmind/types/fungal_bloom.dm create mode 100644 code/modules/blob2/overmind/types/grey_goo.dm create mode 100644 code/modules/blob2/overmind/types/pressurized_slime.dm create mode 100644 code/modules/blob2/overmind/types/radioactive_ooze.dm create mode 100644 code/modules/blob2/overmind/types/ravenous_macrophage.dm create mode 100644 code/modules/blob2/overmind/types/reactive_spines.dm create mode 100644 code/modules/blob2/overmind/types/roiling_mold.dm create mode 100644 code/modules/blob2/overmind/types/shifting_fragments.dm create mode 100644 code/modules/blob2/overmind/types/synchronous_mesh.dm create mode 100644 code/modules/blob2/overmind/types/volatile_alluvium.dm diff --git a/code/game/objects/effects/chem/chemsmoke.dm b/code/game/objects/effects/chem/chemsmoke.dm index f23eca0149..5538755d24 100644 --- a/code/game/objects/effects/chem/chemsmoke.dm +++ b/code/game/objects/effects/chem/chemsmoke.dm @@ -174,10 +174,11 @@ walk_to(smoke, T) 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) - qdel(smoke) + spawn() + 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) + qdel(smoke) /datum/effect/effect/system/smoke_spread/chem/spores/spawnSmoke(var/turf/T, var/icon/I, var/dist = 1) var/obj/effect/effect/smoke/chem/spores = new /obj/effect/effect/smoke/chem(location) diff --git a/code/modules/blob2/overmind/types.dm b/code/modules/blob2/overmind/types.dm index bac7cfbd7c..79df541aae 100644 --- a/code/modules/blob2/overmind/types.dm +++ b/code/modules/blob2/overmind/types.dm @@ -109,1005 +109,3 @@ // Set up the blob type for the chunk. /datum/blob_type/proc/chunk_setup(obj/item/weapon/blobcore_chunk/B) return - -// Subtypes - -// Super fast spreading, but weak to EMP. -/datum/blob_type/grey_goo - name = "grey tide" - desc = "A swarm of self replicating nanomachines. Extremely illegal and dangerous, the EIO was meant to prevent this from showing up a second time." - effect_desc = "Spreads much faster than average, but is harmed greatly by electromagnetic pulses." - ai_desc = "genocidal" - difficulty = BLOB_DIFFICULTY_SUPERHARD // Fastest spread of them all and has snowballing capabilities. - color = "#888888" - complementary_color = "#CCCCCC" - spread_modifier = 1.0 - slow_spread_with_size = FALSE - ai_aggressiveness = 80 - can_build_resources = TRUE - attack_message = "The tide tries to swallow you" - attack_message_living = ", and you feel your skin dissolve" - attack_message_synth = ", and your external plating dissolves" - -/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" - desc = "A swarm of self replicating construction nanites. Incredibly illegal, but only mildly dangerous." - effect_desc = "Slow-spreading, but incredibly resiliant. It has a chance to harden itself against attacks automatically for no resource cost, and uses cheaply-constructed hivebots as soldiers." - ai_desc = "defensive" - difficulty = BLOB_DIFFICULTY_MEDIUM // Emitters are okay, EMP is great. - color = "#666666" - complementary_color = "#B7410E" - spread_modifier = 0.2 - can_build_factories = TRUE - can_build_resources = TRUE - attack_message = "The tide tries to shove you away" - attack_message_living = ", and your skin itches" - attack_message_synth = ", and your external plating dulls" - attack_verb = "shoves" - armor_pen = 40 - damage_lower = 10 - damage_upper = 25 - brute_multiplier = 0.25 - burn_multiplier = 0.6 - ai_aggressiveness = 50 //Really doesn't like you near it. - spore_type = /mob/living/simple_mob/mechanical/hivebot/swarm - -/datum/blob_type/fabrication_swarm/on_received_damage(var/obj/structure/blob/B, damage, damage_type, mob/living/attacker) - if(istype(B, /obj/structure/blob/normal)) - if(damage > 0) - var/reinforce_probability = min(damage, 70) - if(prob(reinforce_probability)) - B.visible_message("The [name] quakes, before rapidly hardening!") - new/obj/structure/blob/shield(get_turf(B), B.overmind) - qdel(B) - return ..() - -/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" - desc = "A strange, extremely vicious liquid that seems to burn endlessly." - ai_desc = "aggressive" - effect_desc = "Cannot be harmed by burning weapons, and ignites entities it attacks. It will also gradually heat up the area it is in. Water harms it greatly." - difficulty = BLOB_DIFFICULTY_MEDIUM // Emitters don't work but extinguishers are fairly common. Might need fire/atmos suits. - color = "#B68D00" - complementary_color = "#BE5532" - spread_modifier = 0.5 - 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" - attack_verb = "splashes" - -/datum/blob_type/blazing_oil/on_attack(obj/structure/blob/B, mob/living/victim) - victim.fire_act() // Burn them. - -/datum/blob_type/blazing_oil/on_water(obj/structure/blob/B, amount) - spawn(1) - B.adjust_integrity(-(amount * 5)) - -/datum/blob_type/blazing_oil/on_pulse(var/obj/structure/blob/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_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 - name = "lethargic blob" - desc = "A mass that seems bound to its core." - ai_desc = "unambitious" - effect_desc = "Will not create any nodes. Has average strength and resistances." - difficulty = BLOB_DIFFICULTY_EASY // Behaves almost like oldblob, and as such is about as easy as oldblob. - color = "#AAFF00" - complementary_color = "#57787B" - can_build_nodes = FALSE - 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 - name = "electromagnetic web" - desc = "A gooy mesh that generates an electromagnetic field. Electronics will likely be ruined if nearby." - ai_desc = "balanced" - effect_desc = "Causes an EMP on attack, and will EMP upon death. It is also more fragile than average, especially to brute force." - difficulty = BLOB_DIFFICULTY_MEDIUM // Rough for robots but otherwise fragile and can be fought at range like most blobs anyways. - color = "#83ECEC" - complementary_color = "#EC8383" - damage_type = BURN - damage_lower = 10 - damage_upper = 20 - 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" - attack_verb = "lashes" - -/datum/blob_type/electromagnetic_web/on_death(obj/structure/blob/B) - empulse(B.loc, 0, 1, 2) - -/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 - name = "fungal bloom" - desc = "A massive network of rapidly expanding mycelium. Large spore-like particles can be seen spreading from it." - ai_desc = "swarming" - effect_desc = "Creates floating spores that attack enemies from specialized blobs, and will spread the blob if killed. The spores can also \ - infest deceased biological humanoids. It is vulnerable to fire." - difficulty = BLOB_DIFFICULTY_MEDIUM // The spores are more of an annoyance but can be difficult to contain. - color = "#AAAAAA" - complementary_color = "#FFFFFF" - damage_type = TOX - damage_lower = 15 - damage_upper = 25 - spread_modifier = 0.3 // Lower, since spores will do a lot of the spreading. - burn_multiplier = 3 - 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) - return // Don't make blobs if they were on someone's head. - var/turf/T = get_turf(S) - var/obj/structure/blob/B = locate(/obj/structure/blob) in T - if(B) // Is there already a blob here? If so, just heal it. - B.adjust_integrity(10) - else - 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" - desc = "A self expanding mass of living biomaterial, that appears to produce entities to defend it, much like a living organism's immune system." - ai_desc = "swarming" - effect_desc = "Creates weak floating spores that attack enemies from specialized blobs, has a chance to also create a spore when \ - it spreads onto a new tile, and has a chance to create a spore when a blob tile is destroyed. It is more fragile than average to all types of damage." - difficulty = BLOB_DIFFICULTY_HARD // Loads of spores that can overwhelm, and spreads quickly. - color = "#FF0000" // Red - complementary_color = "#FFCC00" // Orange-ish - damage_type = TOX - damage_lower = 10 - damage_upper = 20 - spread_modifier = 0.7 - burn_multiplier = 1.5 - brute_multiplier = 1.5 - 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. - var/mob/living/simple_mob/blob/spore/S = new spore_type(T) - if(istype(S)) - S.overmind = O - O.blob_mobs.Add(S) - else - S.faction = "blob" - S.update_icons() - -/datum/blob_type/fulminant_organism/on_death(obj/structure/blob/B) - if(prob(33)) // 33% chance to make a spore when dying. - var/mob/living/simple_mob/blob/spore/S = new spore_type(get_turf(B)) - B.visible_message("\The [S] floats free from the [name]!") - if(istype(S)) - S.overmind = B.overmind - B.overmind.blob_mobs.Add(S) - else - 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 - name = "reactive spines" - desc = "An ever-growing lifeform with a large amount of sharp, powerful looking spines. They look like they could pierce most armor." - ai_desc = "defensive" - effect_desc = "When attacked by a melee weapon, it will automatically retaliate, striking the attacker with an armor piercing attack. \ - The blob itself is rather weak to all forms of attacks regardless, and lacks automatic realitation from ranged attacks." - difficulty = BLOB_DIFFICULTY_EASY // Potentially deadly to people not knowing the mechanics, but otherwise fairly tame, due to its slow spread and weakness. - color = "#9ACD32" - complementary_color = "#FFA500" - damage_type = BRUTE - damage_lower = 30 - damage_upper = 40 - armor_pen = 50 // Even with riot armor and tactical jumpsuit, you'd have 90 armor, reduced by 50, totaling 40. Getting hit for around 21 damage is still rough. - burn_multiplier = 2.0 - 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) - if(damage > 0 && attacker && get_dist(B, attacker) <= 1) - B.visible_message("The [name] retaliates, lashing out at \the [attacker]!") - B.blob_attack_animation(attacker, B.overmind) - 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 - name = "synchronous mesh" - desc = "A mesh that seems strongly interconnected to itself. It moves slowly, but with purpose." - ai_desc = "defensive" - effect_desc = "When damaged, spreads the damage to nearby blobs. When attacking, damage is increased based on how many blobs are near the target. It is resistant to burn damage." - difficulty = BLOB_DIFFICULTY_EASY // Mostly a tank and spank. - color = "#65ADA2" - complementary_color = "#AD6570" - damage_type = BRUTE - damage_lower = 10 - damage_upper = 15 - brute_multiplier = 0.5 - burn_multiplier = 0.2 // Emitters do so much damage that this will likely not matter too much. - spread_modifier = 0.3 // Since the blob spreads damage, it takes awhile to actually kill, so spread is reduced. - ai_aggressiveness = 60 - attack_message = "The mesh synchronously strikes you" - attack_verb = "synchronously strikes" - var/synchronously_attacking = FALSE - -/datum/blob_type/synchronous_mesh/on_attack(obj/structure/blob/B, mob/living/victim) - if(synchronously_attacking) - return - synchronously_attacking = TRUE // To avoid infinite loops. - for(var/obj/structure/blob/C in orange(1, victim)) - if(victim) // Some things delete themselves when dead... - C.blob_attack_animation(victim) - victim.blob_act(C) - synchronously_attacking = FALSE - -/datum/blob_type/synchronous_mesh/on_received_damage(var/obj/structure/blob/B, damage, damage_type) - var/list/blobs_to_hurt = list() // Maximum split is 9, reducing the damage each blob takes to 11.1% but doing that damage to 9 blobs. - for(var/obj/structure/blob/C in range(1, B)) - if(!istype(C, /obj/structure/blob/core) && !istype(C, /obj/structure/blob/node) && C.overmind && (C.overmind == B.overmind) ) //if it doesn't have the same 'ownership' or is a core or node, don't split damage to it - blobs_to_hurt += C - - for(var/thing in blobs_to_hurt) - var/obj/structure/blob/C = thing - if(C == B) - continue // We'll damage this later. - - C.adjust_integrity(-(damage / blobs_to_hurt.len)) - - 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" - desc = "A collection of fragments that seem to shuffle around constantly." - ai_desc = "evasive" - effect_desc = "Swaps places with nearby blobs when hit or when expanding." - difficulty = BLOB_DIFFICULTY_EASY - color = "#C8963C" - complementary_color = "#3C6EC8" - damage_type = BRUTE - damage_lower = 20 - damage_upper = 30 - brute_multiplier = 0.5 - 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" - -/datum/blob_type/shifting_fragments/on_received_damage(var/obj/structure/blob/B, damage, damage_type) - if(damage > 0 && prob(60)) - var/list/available_blobs = list() - for(var/obj/structure/blob/OB in orange(1, B)) - if((istype(OB, /obj/structure/blob/normal) || (istype(OB, /obj/structure/blob/shield) && prob(25))) && OB.overmind && OB.overmind == B.overmind) - available_blobs += OB - if(available_blobs.len) - var/obj/structure/blob/targeted = pick(available_blobs) - var/turf/T = get_turf(targeted) - targeted.forceMove(get_turf(B)) - B.forceMove(T) // Swap places. - return ..() - -/datum/blob_type/shifting_fragments/on_expand(var/obj/structure/blob/B, var/obj/structure/blob/new_B, var/turf/T, var/mob/observer/blob/O) - if(istype(B, /obj/structure/blob/normal) || (istype(B, /obj/structure/blob/shield) && prob(25))) - 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" - desc = "A mass of goo that freezes anything it touches." - ai_desc = "balanced" - effect_desc = "Lowers the temperature of the room passively, and will also greatly lower the temperature of anything it attacks." - difficulty = BLOB_DIFFICULTY_MEDIUM - color = "#8BA6E9" - complementary_color = "#7D6EB4" - damage_type = BURN - damage_lower = 15 - damage_upper = 25 - brute_multiplier = 0.25 - 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" - attack_verb = "stabs" - -/datum/blob_type/cryogenic_goo/on_attack(obj/structure/blob/B, mob/living/victim) - if(ishuman(victim)) - var/mob/living/carbon/human/H = victim - var/protection = H.get_cold_protection(50) - if(protection < 1) - var/temp_change = 80 // Each hit can reduce temperature by up to 80 kelvin. - var/datum/species/baseline = GLOB.all_species["Human"] - var/temp_cap = baseline.cold_level_3 - 5 // Can't go lower than this. - - var/cold_factor = abs(protection - 1) - temp_change *= cold_factor // If protection was at 0.5, then they only lose 40 kelvin. - - H.bodytemperature = max(H.bodytemperature - temp_change, temp_cap) - else // Just do some extra burn for mobs who don't process bodytemp - victim.adjustFireLoss(20) - -/datum/blob_type/cryogenic_goo/on_pulse(var/obj/structure/blob/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_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" - desc = "A substance that seems to generate electricity." - ai_desc = "suppressive" - effect_desc = "When attacking an entity, it will shock them with a strong electric shock. Repeated attacks can stun the target." - difficulty = BLOB_DIFFICULTY_MEDIUM - color = "#EFD65A" - complementary_color = "#00E5B1" - damage_type = BURN - damage_lower = 5 - damage_upper = 10 - brute_multiplier = 0.5 - burn_multiplier = 0.5 - spread_modifier = 0.35 - ai_aggressiveness = 80 - attack_message = "The jelly prods you" - 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 - name = "explosive lattice" - desc = "A very unstable lattice that looks quite explosive." - ai_desc = "aggressive" - effect_desc = "When attacking an entity, it will cause a small explosion, hitting things near the target. It is somewhat resilient, but weaker to brute damage." - difficulty = BLOB_DIFFICULTY_MEDIUM - color = "#8B2500" - complementary_color = "#00668B" - damage_type = BURN - damage_lower = 25 - damage_upper = 35 - armor_check = "bomb" - armor_pen = 5 // This is so blob hits still hurt just slightly when wearing a bomb suit (100 bomb resist). - brute_multiplier = 0.75 - burn_multiplier = 0.5 - spread_modifier = 0.4 - ai_aggressiveness = 75 - attack_message = "The lattice blasts you" - attack_message_living = ", and your flesh burns from the blast wave" - attack_message_synth = ", and your plating burns from the blast wave" - attack_verb = "blasts" - var/exploding = FALSE - -/datum/blob_type/explosive_lattice/on_attack(obj/structure/blob/B, mob/living/victim, def_zone) // This doesn't use actual bombs since they're too strong and it would hurt the blob. - if(exploding) // We're busy, don't infinite loop us. - return - - exploding = TRUE - for(var/mob/living/L in range(get_turf(victim), 1)) // We don't use orange(), in case there is more than one mob on the target tile. - if(L == victim) // Already hit. - continue - if(L.faction == "blob") // No friendly fire - continue - L.blob_act() - - // Visual effect. - var/datum/effect/system/explosion/E = new/datum/effect/system/explosion/smokeless() - var/turf/T = get_turf(victim) - E.set_up(T) - E.start() - - // Now for sounds. - playsound(T, "explosion", 75, 1) // Local sound. - - for(var/mob/M in player_list) // For everyone else. - if(M.z == T.z && get_dist(M, T) > world.view && !M.ear_deaf && !istype(M.loc,/turf/space)) - M << 'sound/effects/explosionfar.ogg' - - exploding = FALSE - -/datum/blob_type/explosive_lattice/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(1, T)) - L.add_modifier(/datum/modifier/blastshield, 30 SECONDS) - -// A blob that slips and drowns you. -/datum/blob_type/pressurized_slime - name = "pressurized slime" - desc = "A large mass that seems to leak slippery fluid everywhere." - ai_desc = "drowning" - effect_desc = "Wets the floor when expanding and when hit. Tries to drown its enemies when attacking. It forces itself past internals. Resistant to burn damage." - difficulty = BLOB_DIFFICULTY_HARD - color = "#AAAABB" - complementary_color = "#BBBBAA" - damage_type = OXY - damage_lower = 5 - damage_upper = 15 - armor_check = null - brute_multiplier = 0.6 - burn_multiplier = 0.2 - spread_modifier = 0.4 - ai_aggressiveness = 75 - attack_message = "The slime splashes into you" - attack_message_living = ", and you gasp for breath" - attack_message_synth = ", and the fluid wears down on your components" - attack_verb = "splashes" - -/datum/blob_type/pressurized_slime/on_attack(obj/structure/blob/B, mob/living/victim, def_zone) - victim.water_act(5) - var/turf/simulated/T = get_turf(victim) - if(T) - T.wet_floor() - -/datum/blob_type/pressurized_slime/on_received_damage(var/obj/structure/blob/B, damage, damage_type) - wet_surroundings(B, damage) - return ..() - -/datum/blob_type/pressurized_slime/on_pulse(var/obj/structure/blob/B) - var/turf/simulated/T = get_turf(B) - if(!istype(T)) - return - T.wet_floor() - -/datum/blob_type/pressurized_slime/on_death(obj/structure/blob/B) - B.visible_message("The blob ruptures, spraying the area with liquid!") - 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, 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 - name = "radioactive ooze" - desc = "A goopy mess that glows with an unhealthy aura." - ai_desc = "radical" - effect_desc = "Irradiates the surrounding area, and inflicts toxic attacks. Weak to brute damage." - difficulty = BLOB_DIFFICULTY_MEDIUM - color = "#33CC33" - complementary_color = "#99FF66" - damage_type = TOX - damage_lower = 20 - damage_upper = 30 - armor_check = "rad" - brute_multiplier = 0.75 - burn_multiplier = 0.2 - spread_modifier = 0.8 - ai_aggressiveness = 50 - attack_message = "The ooze splashes you" - attack_message_living = ", and you feel warm" - attack_message_synth = ", and your internal systems are bombarded by ionizing radiation" - attack_verb = "splashes" - -/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" - desc = "A churning, earthy mass that moves in waves." - ai_desc = "earthen" - effect_desc = "Moves slowly, producing weak ranged spores to defend itself, and inflicts brute attacks. Attempts to disarm nearby attackers. Weak to water." - difficulty = BLOB_DIFFICULTY_HARD //Slow-starting, but can be overwhelming if left alone. - color = "#6B481E" - complementary_color = "#7F471F" - damage_lower = 10 - damage_upper = 20 - armor_pen = 40 - brute_multiplier = 0.7 - burn_multiplier = 0.5 - spread_modifier = 0.5 - ai_aggressiveness = 50 - attack_message = "The alluvium crashes against you" - attack_verb = "crashes against" - can_build_factories = TRUE - can_build_resources = TRUE - spore_type = /mob/living/simple_mob/blob/spore/weak - 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. - var/mob/living/carbon/human/H = attacker - var/obj/item/I = H.get_active_hand() - H.drop_item() - if(I) - if((I.sharp || I.edge) && !istype(I, /obj/item/weapon/gun)) - I.forceMove(get_turf(B)) // Disarmed entirely. - B.visible_message("The [name] heaves, \the [attacker]'s weapon becoming stuck in the churning mass!") - else - I.throw_at(B, 2, 4) // Just yoinked. - B.visible_message("The [name] heaves, pulling \the [attacker]'s weapon from their hands!") - B.blob_attack_animation(attacker, B.overmind) - return ..() - -/datum/blob_type/volatile_alluvium/on_water(obj/structure/blob/B, amount) - spawn(1) - 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" - desc = "A disgusting gel that reeks of death." - ai_desc = "resourceful" - effect_desc = "Produces noxious fumes, and melts prey with acidic attacks. Weak to brute damage." - difficulty = BLOB_DIFFICULTY_MEDIUM - color = "#639b3f" - complementary_color = "#d1ec3c" - damage_type = BIOACID - damage_lower = 20 - damage_upper = 30 - armor_check = "bio" - armor_pen = 50 - brute_multiplier = 0.8 - burn_multiplier = 0.3 - spread_modifier = 0.8 - ai_aggressiveness = 70 - attack_message = "The macrophage splashes you" - attack_message_living = ", and you feel a horrible burning" - attack_message_synth = ", and your body begins to corrode" - attack_verb = "splashes" - -/datum/blob_type/ravenous_macrophage/on_pulse(var/obj/structure/blob/B) - var/mob/living/L = locate() in range(world.view, B) - if(prob(1) && L.mind && !L.stat) // There's some active living thing nearby, produce offgas. - 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() - -/datum/blob_type/ravenous_macrophage/on_death(obj/structure/blob/B) - var/obj/structure/blob/other = locate() in oview(2, B) - if(other) - B.visible_message("The dying mass is rapidly consumed by the nearby [other]!") - 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" - desc = "A bubbling, creeping mold." - ai_desc = "bombarding" - effect_desc = "Bombards nearby organisms with toxic spores. Weak to all damage." - difficulty = BLOB_DIFFICULTY_MEDIUM - color = "#571509" - complementary_color = "#ec4940" - damage_type = BRUTE - damage_lower = 5 - damage_upper = 20 - armor_check = "melee" - brute_multiplier = 1.2 - burn_multiplier = 1.2 - spread_modifier = 0.8 - can_build_factories = TRUE - ai_aggressiveness = 50 - attack_message = "The mold whips you" - attack_message_living = ", and you feel a searing pain" - attack_message_synth = ", and your shell buckles" - attack_verb = "lashes" - spore_projectile = /obj/item/projectile/arc/spore - -/datum/blob_type/roiling_mold/proc/find_target(var/obj/structure/blob/B, var/tries = 0, var/list/previous_targets = null) - if(tries > 3) - return - var/mob/living/L = locate() in (view(world.view + 3, get_turf(B)) - view(2,get_turf(B)) - previous_targets) // No adjacent mobs. - - if(!check_trajectory(L, B, PASSTABLE)) - if(!LAZYLEN(previous_targets)) - previous_targets = list() - - previous_targets |= L - - L = find_target(B, tries + 1, previous_targets) - - return L - -/datum/blob_type/roiling_mold/on_pulse(var/obj/structure/blob/B) - var/mob/living/L = find_target(B) - - if(!istype(L)) - return - - if(istype(B, /obj/structure/blob/factory) && L.stat != DEAD && prob(ai_aggressiveness) && L.faction != "blob") - 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" - desc = "A disgusting translucent slime that feels out of place." - ai_desc = "dodging" - effect_desc = "Drains energy from nearby life-forms in order to expand itself. Weak to all damage." - difficulty = BLOB_DIFFICULTY_MEDIUM - color = "#72109eaa" - complementary_color = "#1a9de8" - damage_type = HALLOSS - damage_lower = 10 - damage_upper = 30 - armor_check = "energy" - brute_multiplier = 1.5 - burn_multiplier = 1.5 - spread_modifier = 0.9 - ai_aggressiveness = 50 - attack_message = "The horror strikes you" - attack_message_living = ", and you feel a wave of exhaustion" - attack_message_synth = ", and your systems begin to slow" - attack_verb = "strikes" - can_build_factories = TRUE - factory_type = /obj/structure/blob/factory/sluggish - spore_type = /mob/living/simple_mob/blob/spore/weak - - var/list/active_beams = list() - -/datum/blob_type/ectoplasmic_horror/on_pulse(var/obj/structure/blob/B) - if(B.type == /obj/structure/blob && (locate(/obj/structure/blob/node) in oview(2, get_turf(B)))) - B.visible_message("The [name] quakes, before hardening.") - new/obj/structure/blob/shield(get_turf(B), B.overmind) - qdel(B) - - if(istype(B, /obj/structure/blob/factory)) - listclearnulls(active_beams) - var/atom/movable/beam_origin = B - for(var/mob/living/L in oview(world.view, B)) - 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) - B.visible_message("\The [B] lashes out at \the [L]!") - var/datum/beam/drain_beam = beam_origin.Beam(L, icon_state = "drain_life", time = 10 SECONDS) - active_beams |= drain_beam - spawn(9 SECONDS) - if(B && drain_beam) - B.visible_message("\The [B] siphons energy from \the [L]") - L.add_modifier(/datum/modifier/berserk_exhaustion, 60 SECONDS) - B.overmind.add_points(rand(10,30)) - if(!QDELETED(drain_beam)) - qdel(drain_beam) - -/datum/blob_type/ectoplasmic_horror/on_received_damage(var/obj/structure/blob/B, damage, damage_type) - if(prob(round(damage * 0.5))) - B.visible_message("\The [B] shimmers, distorting through some unseen dimension.") - var/initial_alpha = B.alpha - spawn() - animate(B,alpha = initial_alpha, alpha = 10, time = 10) - 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/blob2/overmind/types/blazing_oil.dm b/code/modules/blob2/overmind/types/blazing_oil.dm new file mode 100644 index 0000000000..094083e778 --- /dev/null +++ b/code/modules/blob2/overmind/types/blazing_oil.dm @@ -0,0 +1,45 @@ +// A blob meant to be fought like a fire. +/datum/blob_type/blazing_oil + name = "blazing oil" + desc = "A strange, extremely vicious liquid that seems to burn endlessly." + ai_desc = "aggressive" + effect_desc = "Cannot be harmed by burning weapons, and ignites entities it attacks. It will also gradually heat up the area it is in. Water harms it greatly." + difficulty = BLOB_DIFFICULTY_MEDIUM // Emitters don't work but extinguishers are fairly common. Might need fire/atmos suits. + color = "#B68D00" + complementary_color = "#BE5532" + spread_modifier = 0.5 + 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" + attack_verb = "splashes" + +/datum/blob_type/blazing_oil/on_attack(obj/structure/blob/B, mob/living/victim) + victim.fire_act() // Burn them. + +/datum/blob_type/blazing_oil/on_water(obj/structure/blob/B, amount) + spawn(1) + B.adjust_integrity(-(amount * 5)) + +/datum/blob_type/blazing_oil/on_pulse(var/obj/structure/blob/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_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 \ No newline at end of file diff --git a/code/modules/blob2/overmind/types/classic.dm b/code/modules/blob2/overmind/types/classic.dm new file mode 100644 index 0000000000..450324b493 --- /dev/null +++ b/code/modules/blob2/overmind/types/classic.dm @@ -0,0 +1,37 @@ +// Mostly a classic blob. No nodes, no other blob types. +/datum/blob_type/classic + name = "lethargic blob" + desc = "A mass that seems bound to its core." + ai_desc = "unambitious" + effect_desc = "Will not create any nodes. Has average strength and resistances." + difficulty = BLOB_DIFFICULTY_EASY // Behaves almost like oldblob, and as such is about as easy as oldblob. + color = "#AAFF00" + complementary_color = "#57787B" + can_build_nodes = FALSE + 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 \ No newline at end of file diff --git a/code/modules/blob2/overmind/types/cryogenic_goo.dm b/code/modules/blob2/overmind/types/cryogenic_goo.dm new file mode 100644 index 0000000000..32684ed58f --- /dev/null +++ b/code/modules/blob2/overmind/types/cryogenic_goo.dm @@ -0,0 +1,59 @@ +// A very cool blob, literally. +/datum/blob_type/cryogenic_goo + name = "cryogenic goo" + desc = "A mass of goo that freezes anything it touches." + ai_desc = "balanced" + effect_desc = "Lowers the temperature of the room passively, and will also greatly lower the temperature of anything it attacks." + difficulty = BLOB_DIFFICULTY_MEDIUM + color = "#8BA6E9" + complementary_color = "#7D6EB4" + damage_type = BURN + damage_lower = 15 + damage_upper = 25 + brute_multiplier = 0.25 + 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" + attack_verb = "stabs" + +/datum/blob_type/cryogenic_goo/on_attack(obj/structure/blob/B, mob/living/victim) + if(ishuman(victim)) + var/mob/living/carbon/human/H = victim + var/protection = H.get_cold_protection(50) + if(protection < 1) + var/temp_change = 80 // Each hit can reduce temperature by up to 80 kelvin. + var/datum/species/baseline = GLOB.all_species["Human"] + var/temp_cap = baseline.cold_level_3 - 5 // Can't go lower than this. + + var/cold_factor = abs(protection - 1) + temp_change *= cold_factor // If protection was at 0.5, then they only lose 40 kelvin. + + H.bodytemperature = max(H.bodytemperature - temp_change, temp_cap) + else // Just do some extra burn for mobs who don't process bodytemp + victim.adjustFireLoss(20) + +/datum/blob_type/cryogenic_goo/on_pulse(var/obj/structure/blob/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_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 diff --git a/code/modules/blob2/overmind/types/ectoplasmic_horror.dm b/code/modules/blob2/overmind/types/ectoplasmic_horror.dm new file mode 100644 index 0000000000..f7689847cd --- /dev/null +++ b/code/modules/blob2/overmind/types/ectoplasmic_horror.dm @@ -0,0 +1,128 @@ +// 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" + desc = "A disgusting translucent slime that feels out of place." + ai_desc = "dodging" + effect_desc = "Drains energy from nearby life-forms in order to expand itself. Weak to all damage." + difficulty = BLOB_DIFFICULTY_MEDIUM + color = "#72109eaa" + complementary_color = "#1a9de8" + damage_type = HALLOSS + damage_lower = 10 + damage_upper = 30 + armor_check = "energy" + brute_multiplier = 1.5 + burn_multiplier = 1.5 + spread_modifier = 0.9 + ai_aggressiveness = 50 + attack_message = "The horror strikes you" + attack_message_living = ", and you feel a wave of exhaustion" + attack_message_synth = ", and your systems begin to slow" + attack_verb = "strikes" + can_build_factories = TRUE + factory_type = /obj/structure/blob/factory/sluggish + spore_type = /mob/living/simple_mob/blob/spore/weak + + var/list/active_beams = list() + +/datum/blob_type/ectoplasmic_horror/on_pulse(var/obj/structure/blob/B) + if(B.type == /obj/structure/blob && (locate(/obj/structure/blob/node) in oview(2, get_turf(B)))) + B.visible_message("The [name] quakes, before hardening.") + new/obj/structure/blob/shield(get_turf(B), B.overmind) + qdel(B) + + if(istype(B, /obj/structure/blob/factory)) + listclearnulls(active_beams) + var/atom/movable/beam_origin = B + for(var/mob/living/L in oview(world.view, B)) + 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) + B.visible_message("\The [B] lashes out at \the [L]!") + var/datum/beam/drain_beam = beam_origin.Beam(L, icon_state = "drain_life", time = 10 SECONDS) + active_beams |= drain_beam + spawn(9 SECONDS) + if(B && drain_beam) + B.visible_message("\The [B] siphons energy from \the [L]") + L.add_modifier(/datum/modifier/berserk_exhaustion, 60 SECONDS) + B.overmind.add_points(rand(10,30)) + if(!QDELETED(drain_beam)) + qdel(drain_beam) + +/datum/blob_type/ectoplasmic_horror/on_received_damage(var/obj/structure/blob/B, damage, damage_type) + if(prob(round(damage * 0.5))) + B.visible_message("\The [B] shimmers, distorting through some unseen dimension.") + var/initial_alpha = B.alpha + spawn() + animate(B,alpha = initial_alpha, alpha = 10, time = 10) + 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/blob2/overmind/types/electromagnetic_web.dm b/code/modules/blob2/overmind/types/electromagnetic_web.dm new file mode 100644 index 0000000000..9831998587 --- /dev/null +++ b/code/modules/blob2/overmind/types/electromagnetic_web.dm @@ -0,0 +1,34 @@ +// Makes robots cry. Really weak to brute damage. +/datum/blob_type/electromagnetic_web + name = "electromagnetic web" + desc = "A gooy mesh that generates an electromagnetic field. Electronics will likely be ruined if nearby." + ai_desc = "balanced" + effect_desc = "Causes an EMP on attack, and will EMP upon death. It is also more fragile than average, especially to brute force." + difficulty = BLOB_DIFFICULTY_MEDIUM // Rough for robots but otherwise fragile and can be fought at range like most blobs anyways. + color = "#83ECEC" + complementary_color = "#EC8383" + damage_type = BURN + damage_lower = 10 + damage_upper = 20 + 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" + attack_verb = "lashes" + +/datum/blob_type/electromagnetic_web/on_death(obj/structure/blob/B) + empulse(B.loc, 0, 1, 2) + +/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) \ No newline at end of file diff --git a/code/modules/blob2/overmind/types/energized_jelly.dm b/code/modules/blob2/overmind/types/energized_jelly.dm new file mode 100644 index 0000000000..33c570f24f --- /dev/null +++ b/code/modules/blob2/overmind/types/energized_jelly.dm @@ -0,0 +1,39 @@ +// Electric blob that stuns. +/datum/blob_type/energized_jelly + name = "energized jelly" + desc = "A substance that seems to generate electricity." + ai_desc = "suppressive" + effect_desc = "When attacking an entity, it will shock them with a strong electric shock. Repeated attacks can stun the target." + difficulty = BLOB_DIFFICULTY_MEDIUM + color = "#EFD65A" + complementary_color = "#00E5B1" + damage_type = BURN + damage_lower = 5 + damage_upper = 10 + brute_multiplier = 0.5 + burn_multiplier = 0.5 + spread_modifier = 0.35 + ai_aggressiveness = 80 + attack_message = "The jelly prods you" + 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 \ No newline at end of file diff --git a/code/modules/blob2/overmind/types/explosive_lattice.dm b/code/modules/blob2/overmind/types/explosive_lattice.dm new file mode 100644 index 0000000000..d0484c3725 --- /dev/null +++ b/code/modules/blob2/overmind/types/explosive_lattice.dm @@ -0,0 +1,58 @@ +// A blob with area of effect attacks. +/datum/blob_type/explosive_lattice + name = "explosive lattice" + desc = "A very unstable lattice that looks quite explosive." + ai_desc = "aggressive" + effect_desc = "When attacking an entity, it will cause a small explosion, hitting things near the target. It is somewhat resilient, but weaker to brute damage." + difficulty = BLOB_DIFFICULTY_MEDIUM + color = "#8B2500" + complementary_color = "#00668B" + damage_type = BURN + damage_lower = 25 + damage_upper = 35 + armor_check = "bomb" + armor_pen = 5 // This is so blob hits still hurt just slightly when wearing a bomb suit (100 bomb resist). + brute_multiplier = 0.75 + burn_multiplier = 0.5 + spread_modifier = 0.4 + ai_aggressiveness = 75 + attack_message = "The lattice blasts you" + attack_message_living = ", and your flesh burns from the blast wave" + attack_message_synth = ", and your plating burns from the blast wave" + attack_verb = "blasts" + var/exploding = FALSE + +/datum/blob_type/explosive_lattice/on_attack(obj/structure/blob/B, mob/living/victim, def_zone) // This doesn't use actual bombs since they're too strong and it would hurt the blob. + if(exploding) // We're busy, don't infinite loop us. + return + + exploding = TRUE + for(var/mob/living/L in range(get_turf(victim), 1)) // We don't use orange(), in case there is more than one mob on the target tile. + if(L == victim) // Already hit. + continue + if(L.faction == "blob") // No friendly fire + continue + L.blob_act() + + // Visual effect. + var/datum/effect/system/explosion/E = new/datum/effect/system/explosion/smokeless() + var/turf/T = get_turf(victim) + E.set_up(T) + E.start() + + // Now for sounds. + playsound(T, "explosion", 75, 1) // Local sound. + + for(var/mob/M in player_list) // For everyone else. + if(M.z == T.z && get_dist(M, T) > world.view && !M.ear_deaf && !istype(M.loc,/turf/space)) + M << 'sound/effects/explosionfar.ogg' + + exploding = FALSE + +/datum/blob_type/explosive_lattice/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(1, T)) + L.add_modifier(/datum/modifier/blastshield, 30 SECONDS) \ No newline at end of file diff --git a/code/modules/blob2/overmind/types/fabrication_swarm.dm b/code/modules/blob2/overmind/types/fabrication_swarm.dm new file mode 100644 index 0000000000..7e267558d9 --- /dev/null +++ b/code/modules/blob2/overmind/types/fabrication_swarm.dm @@ -0,0 +1,44 @@ +// Slow, tanky blobtype which uses not spores, but hivebots, as its soldiers. +/datum/blob_type/fabrication_swarm + name = "iron tide" + desc = "A swarm of self replicating construction nanites. Incredibly illegal, but only mildly dangerous." + effect_desc = "Slow-spreading, but incredibly resiliant. It has a chance to harden itself against attacks automatically for no resource cost, and uses cheaply-constructed hivebots as soldiers." + ai_desc = "defensive" + difficulty = BLOB_DIFFICULTY_MEDIUM // Emitters are okay, EMP is great. + color = "#666666" + complementary_color = "#B7410E" + spread_modifier = 0.2 + can_build_factories = TRUE + can_build_resources = TRUE + attack_message = "The tide tries to shove you away" + attack_message_living = ", and your skin itches" + attack_message_synth = ", and your external plating dulls" + attack_verb = "shoves" + armor_pen = 40 + damage_lower = 10 + damage_upper = 25 + brute_multiplier = 0.25 + burn_multiplier = 0.6 + ai_aggressiveness = 50 //Really doesn't like you near it. + spore_type = /mob/living/simple_mob/mechanical/hivebot/swarm + +/datum/blob_type/fabrication_swarm/on_received_damage(var/obj/structure/blob/B, damage, damage_type, mob/living/attacker) + if(istype(B, /obj/structure/blob/normal)) + if(damage > 0) + var/reinforce_probability = min(damage, 70) + if(prob(reinforce_probability)) + B.visible_message("The [name] quakes, before rapidly hardening!") + new/obj/structure/blob/shield(get_turf(B), B.overmind) + qdel(B) + return ..() + +/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 \ No newline at end of file diff --git a/code/modules/blob2/overmind/types/fulminant_organism.dm b/code/modules/blob2/overmind/types/fulminant_organism.dm new file mode 100644 index 0000000000..2d940f9359 --- /dev/null +++ b/code/modules/blob2/overmind/types/fulminant_organism.dm @@ -0,0 +1,50 @@ +// Makes tons of weak spores whenever it spreads. +/datum/blob_type/fulminant_organism + name = "fulminant organism" + desc = "A self expanding mass of living biomaterial, that appears to produce entities to defend it, much like a living organism's immune system." + ai_desc = "swarming" + effect_desc = "Creates weak floating spores that attack enemies from specialized blobs, has a chance to also create a spore when \ + it spreads onto a new tile, and has a chance to create a spore when a blob tile is destroyed. It is more fragile than average to all types of damage." + difficulty = BLOB_DIFFICULTY_HARD // Loads of spores that can overwhelm, and spreads quickly. + color = "#FF0000" // Red + complementary_color = "#FFCC00" // Orange-ish + damage_type = TOX + damage_lower = 10 + damage_upper = 20 + spread_modifier = 0.7 + burn_multiplier = 1.5 + brute_multiplier = 1.5 + 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. + var/mob/living/simple_mob/blob/spore/S = new spore_type(T) + if(istype(S)) + S.overmind = O + O.blob_mobs.Add(S) + else + S.faction = "blob" + S.update_icons() + +/datum/blob_type/fulminant_organism/on_death(obj/structure/blob/B) + if(prob(33)) // 33% chance to make a spore when dying. + var/mob/living/simple_mob/blob/spore/S = new spore_type(get_turf(B)) + B.visible_message("\The [S] floats free from the [name]!") + if(istype(S)) + S.overmind = B.overmind + B.overmind.blob_mobs.Add(S) + else + 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) diff --git a/code/modules/blob2/overmind/types/fungal_bloom.dm b/code/modules/blob2/overmind/types/fungal_bloom.dm new file mode 100644 index 0000000000..6bf20b99d3 --- /dev/null +++ b/code/modules/blob2/overmind/types/fungal_bloom.dm @@ -0,0 +1,37 @@ +// Makes spores that spread the blob and infest dead people. +/datum/blob_type/fungal_bloom + name = "fungal bloom" + desc = "A massive network of rapidly expanding mycelium. Large spore-like particles can be seen spreading from it." + ai_desc = "swarming" + effect_desc = "Creates floating spores that attack enemies from specialized blobs, and will spread the blob if killed. The spores can also \ + infest deceased biological humanoids. It is vulnerable to fire." + difficulty = BLOB_DIFFICULTY_MEDIUM // The spores are more of an annoyance but can be difficult to contain. + color = "#AAAAAA" + complementary_color = "#FFFFFF" + damage_type = TOX + damage_lower = 15 + damage_upper = 25 + spread_modifier = 0.3 // Lower, since spores will do a lot of the spreading. + burn_multiplier = 3 + 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) + return // Don't make blobs if they were on someone's head. + var/turf/T = get_turf(S) + var/obj/structure/blob/B = locate(/obj/structure/blob) in T + if(B) // Is there already a blob here? If so, just heal it. + B.adjust_integrity(10) + else + 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() \ No newline at end of file diff --git a/code/modules/blob2/overmind/types/grey_goo.dm b/code/modules/blob2/overmind/types/grey_goo.dm new file mode 100644 index 0000000000..5c93fb46c8 --- /dev/null +++ b/code/modules/blob2/overmind/types/grey_goo.dm @@ -0,0 +1,27 @@ +// Super fast spreading, but weak to EMP. +/datum/blob_type/grey_goo + name = "grey tide" + desc = "A swarm of self replicating nanomachines. Extremely illegal and dangerous, the EIO was meant to prevent this from showing up a second time." + effect_desc = "Spreads much faster than average, but is harmed greatly by electromagnetic pulses." + ai_desc = "genocidal" + difficulty = BLOB_DIFFICULTY_SUPERHARD // Fastest spread of them all and has snowballing capabilities. + color = "#888888" + complementary_color = "#CCCCCC" + spread_modifier = 1.0 + slow_spread_with_size = FALSE + ai_aggressiveness = 80 + can_build_resources = TRUE + attack_message = "The tide tries to swallow you" + attack_message_living = ", and you feel your skin dissolve" + attack_message_synth = ", and your external plating dissolves" + +/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 diff --git a/code/modules/blob2/overmind/types/pressurized_slime.dm b/code/modules/blob2/overmind/types/pressurized_slime.dm new file mode 100644 index 0000000000..6b2ff5800f --- /dev/null +++ b/code/modules/blob2/overmind/types/pressurized_slime.dm @@ -0,0 +1,56 @@ +// A blob that slips and drowns you. +/datum/blob_type/pressurized_slime + name = "pressurized slime" + desc = "A large mass that seems to leak slippery fluid everywhere." + ai_desc = "drowning" + effect_desc = "Wets the floor when expanding and when hit. Tries to drown its enemies when attacking. It forces itself past internals. Resistant to burn damage." + difficulty = BLOB_DIFFICULTY_HARD + color = "#AAAABB" + complementary_color = "#BBBBAA" + damage_type = OXY + damage_lower = 5 + damage_upper = 15 + armor_check = null + brute_multiplier = 0.6 + burn_multiplier = 0.2 + spread_modifier = 0.4 + ai_aggressiveness = 75 + attack_message = "The slime splashes into you" + attack_message_living = ", and you gasp for breath" + attack_message_synth = ", and the fluid wears down on your components" + attack_verb = "splashes" + +/datum/blob_type/pressurized_slime/on_attack(obj/structure/blob/B, mob/living/victim, def_zone) + victim.water_act(5) + var/turf/simulated/T = get_turf(victim) + if(T) + T.wet_floor() + +/datum/blob_type/pressurized_slime/on_received_damage(var/obj/structure/blob/B, damage, damage_type) + wet_surroundings(B, damage) + return ..() + +/datum/blob_type/pressurized_slime/on_pulse(var/obj/structure/blob/B) + var/turf/simulated/T = get_turf(B) + if(!istype(T)) + return + T.wet_floor() + +/datum/blob_type/pressurized_slime/on_death(obj/structure/blob/B) + B.visible_message("The blob ruptures, spraying the area with liquid!") + 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, 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) \ No newline at end of file diff --git a/code/modules/blob2/overmind/types/radioactive_ooze.dm b/code/modules/blob2/overmind/types/radioactive_ooze.dm new file mode 100644 index 0000000000..f3226f2b5b --- /dev/null +++ b/code/modules/blob2/overmind/types/radioactive_ooze.dm @@ -0,0 +1,27 @@ +// A blob that irradiates everything. +/datum/blob_type/radioactive_ooze + name = "radioactive ooze" + desc = "A goopy mess that glows with an unhealthy aura." + ai_desc = "radical" + effect_desc = "Irradiates the surrounding area, and inflicts toxic attacks. Weak to brute damage." + difficulty = BLOB_DIFFICULTY_MEDIUM + color = "#33CC33" + complementary_color = "#99FF66" + damage_type = TOX + damage_lower = 20 + damage_upper = 30 + armor_check = "rad" + brute_multiplier = 0.75 + burn_multiplier = 0.2 + spread_modifier = 0.8 + ai_aggressiveness = 50 + attack_message = "The ooze splashes you" + attack_message_living = ", and you feel warm" + attack_message_synth = ", and your internal systems are bombarded by ionizing radiation" + attack_verb = "splashes" + +/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)) \ No newline at end of file diff --git a/code/modules/blob2/overmind/types/ravenous_macrophage.dm b/code/modules/blob2/overmind/types/ravenous_macrophage.dm new file mode 100644 index 0000000000..0a73501c34 --- /dev/null +++ b/code/modules/blob2/overmind/types/ravenous_macrophage.dm @@ -0,0 +1,50 @@ +// A blob that produces noxious smoke-clouds and recycles its dying parts. +/datum/blob_type/ravenous_macrophage + name = "ravenous macrophage" + desc = "A disgusting gel that reeks of death." + ai_desc = "resourceful" + effect_desc = "Produces noxious fumes, and melts prey with acidic attacks. Weak to brute damage." + difficulty = BLOB_DIFFICULTY_MEDIUM + color = "#639b3f" + complementary_color = "#d1ec3c" + damage_type = BIOACID + damage_lower = 20 + damage_upper = 30 + armor_check = "bio" + armor_pen = 50 + brute_multiplier = 0.8 + burn_multiplier = 0.3 + spread_modifier = 0.8 + ai_aggressiveness = 70 + attack_message = "The macrophage splashes you" + attack_message_living = ", and you feel a horrible burning" + attack_message_synth = ", and your body begins to corrode" + attack_verb = "splashes" + +/datum/blob_type/ravenous_macrophage/on_pulse(var/obj/structure/blob/B) + var/mob/living/L = locate() in range(world.view, B) + if(prob(1) && L.mind && !L.stat) // There's some active living thing nearby, produce offgas. + 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() + +/datum/blob_type/ravenous_macrophage/on_death(obj/structure/blob/B) + var/obj/structure/blob/other = locate() in oview(2, B) + if(other) + B.visible_message("The dying mass is rapidly consumed by the nearby [other]!") + 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() \ No newline at end of file diff --git a/code/modules/blob2/overmind/types/reactive_spines.dm b/code/modules/blob2/overmind/types/reactive_spines.dm new file mode 100644 index 0000000000..0d0021f243 --- /dev/null +++ b/code/modules/blob2/overmind/types/reactive_spines.dm @@ -0,0 +1,59 @@ +// Auto-retaliates against melee attacks. Weak to projectiles. +/datum/blob_type/reactive_spines + name = "reactive spines" + desc = "An ever-growing lifeform with a large amount of sharp, powerful looking spines. They look like they could pierce most armor." + ai_desc = "defensive" + effect_desc = "When attacked by a melee weapon, it will automatically retaliate, striking the attacker with an armor piercing attack. \ + The blob itself is rather weak to all forms of attacks regardless, and lacks automatic realitation from ranged attacks." + difficulty = BLOB_DIFFICULTY_EASY // Potentially deadly to people not knowing the mechanics, but otherwise fairly tame, due to its slow spread and weakness. + color = "#9ACD32" + complementary_color = "#FFA500" + damage_type = BRUTE + damage_lower = 30 + damage_upper = 40 + armor_pen = 50 // Even with riot armor and tactical jumpsuit, you'd have 90 armor, reduced by 50, totaling 40. Getting hit for around 21 damage is still rough. + burn_multiplier = 2.0 + 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) + if(damage > 0 && attacker && get_dist(B, attacker) <= 1) + B.visible_message("The [name] retaliates, lashing out at \the [attacker]!") + B.blob_attack_animation(attacker, B.overmind) + 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 \ No newline at end of file diff --git a/code/modules/blob2/overmind/types/roiling_mold.dm b/code/modules/blob2/overmind/types/roiling_mold.dm new file mode 100644 index 0000000000..dd42be30ac --- /dev/null +++ b/code/modules/blob2/overmind/types/roiling_mold.dm @@ -0,0 +1,63 @@ +// Blob that fires biological mortar shells from its factories. +/datum/blob_type/roiling_mold + name = "roiling mold" + desc = "A bubbling, creeping mold." + ai_desc = "bombarding" + effect_desc = "Bombards nearby organisms with toxic spores. Weak to all damage." + difficulty = BLOB_DIFFICULTY_MEDIUM + color = "#571509" + complementary_color = "#ec4940" + damage_type = BRUTE + damage_lower = 5 + damage_upper = 20 + armor_check = "melee" + brute_multiplier = 1.2 + burn_multiplier = 1.2 + spread_modifier = 0.8 + can_build_factories = TRUE + ai_aggressiveness = 50 + attack_message = "The mold whips you" + attack_message_living = ", and you feel a searing pain" + attack_message_synth = ", and your shell buckles" + attack_verb = "lashes" + spore_projectile = /obj/item/projectile/arc/spore + +/datum/blob_type/roiling_mold/proc/find_target(var/obj/structure/blob/B, var/tries = 0, var/list/previous_targets = null) + if(tries > 3) + return + var/mob/living/L = locate() in (view(world.view + 3, get_turf(B)) - view(2,get_turf(B)) - previous_targets) // No adjacent mobs. + + if(!check_trajectory(L, B, PASSTABLE)) + if(!LAZYLEN(previous_targets)) + previous_targets = list() + + previous_targets |= L + + L = find_target(B, tries + 1, previous_targets) + + return L + +/datum/blob_type/roiling_mold/on_pulse(var/obj/structure/blob/B) + var/mob/living/L = find_target(B) + + if(!istype(L)) + return + + if(istype(B, /obj/structure/blob/factory) && L.stat != DEAD && prob(ai_aggressiveness) && L.faction != "blob") + 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 \ No newline at end of file diff --git a/code/modules/blob2/overmind/types/shifting_fragments.dm b/code/modules/blob2/overmind/types/shifting_fragments.dm new file mode 100644 index 0000000000..ffca17760b --- /dev/null +++ b/code/modules/blob2/overmind/types/shifting_fragments.dm @@ -0,0 +1,40 @@ +/datum/blob_type/shifting_fragments + name = "shifting fragments" + desc = "A collection of fragments that seem to shuffle around constantly." + ai_desc = "evasive" + effect_desc = "Swaps places with nearby blobs when hit or when expanding." + difficulty = BLOB_DIFFICULTY_EASY + color = "#C8963C" + complementary_color = "#3C6EC8" + damage_type = BRUTE + damage_lower = 20 + damage_upper = 30 + brute_multiplier = 0.5 + 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" + +/datum/blob_type/shifting_fragments/on_received_damage(var/obj/structure/blob/B, damage, damage_type) + if(damage > 0 && prob(60)) + var/list/available_blobs = list() + for(var/obj/structure/blob/OB in orange(1, B)) + if((istype(OB, /obj/structure/blob/normal) || (istype(OB, /obj/structure/blob/shield) && prob(25))) && OB.overmind && OB.overmind == B.overmind) + available_blobs += OB + if(available_blobs.len) + var/obj/structure/blob/targeted = pick(available_blobs) + var/turf/T = get_turf(targeted) + targeted.forceMove(get_turf(B)) + B.forceMove(T) // Swap places. + return ..() + +/datum/blob_type/shifting_fragments/on_expand(var/obj/structure/blob/B, var/obj/structure/blob/new_B, var/turf/T, var/mob/observer/blob/O) + if(istype(B, /obj/structure/blob/normal) || (istype(B, /obj/structure/blob/shield) && prob(25))) + 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 \ No newline at end of file diff --git a/code/modules/blob2/overmind/types/synchronous_mesh.dm b/code/modules/blob2/overmind/types/synchronous_mesh.dm new file mode 100644 index 0000000000..e234756b2d --- /dev/null +++ b/code/modules/blob2/overmind/types/synchronous_mesh.dm @@ -0,0 +1,73 @@ +// Spreads damage to nearby blobs, and attacks with the force of all nearby blobs. +/datum/blob_type/synchronous_mesh + name = "synchronous mesh" + desc = "A mesh that seems strongly interconnected to itself. It moves slowly, but with purpose." + ai_desc = "defensive" + effect_desc = "When damaged, spreads the damage to nearby blobs. When attacking, damage is increased based on how many blobs are near the target. It is resistant to burn damage." + difficulty = BLOB_DIFFICULTY_EASY // Mostly a tank and spank. + color = "#65ADA2" + complementary_color = "#AD6570" + damage_type = BRUTE + damage_lower = 10 + damage_upper = 15 + brute_multiplier = 0.5 + burn_multiplier = 0.2 // Emitters do so much damage that this will likely not matter too much. + spread_modifier = 0.3 // Since the blob spreads damage, it takes awhile to actually kill, so spread is reduced. + ai_aggressiveness = 60 + attack_message = "The mesh synchronously strikes you" + attack_verb = "synchronously strikes" + var/synchronously_attacking = FALSE + +/datum/blob_type/synchronous_mesh/on_attack(obj/structure/blob/B, mob/living/victim) + if(synchronously_attacking) + return + synchronously_attacking = TRUE // To avoid infinite loops. + for(var/obj/structure/blob/C in orange(1, victim)) + if(victim) // Some things delete themselves when dead... + C.blob_attack_animation(victim) + victim.blob_act(C) + synchronously_attacking = FALSE + +/datum/blob_type/synchronous_mesh/on_received_damage(var/obj/structure/blob/B, damage, damage_type) + var/list/blobs_to_hurt = list() // Maximum split is 9, reducing the damage each blob takes to 11.1% but doing that damage to 9 blobs. + for(var/obj/structure/blob/C in range(1, B)) + if(!istype(C, /obj/structure/blob/core) && !istype(C, /obj/structure/blob/node) && C.overmind && (C.overmind == B.overmind) ) //if it doesn't have the same 'ownership' or is a core or node, don't split damage to it + blobs_to_hurt += C + + for(var/thing in blobs_to_hurt) + var/obj/structure/blob/C = thing + if(C == B) + continue // We'll damage this later. + + C.adjust_integrity(-(damage / blobs_to_hurt.len)) + + 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) \ No newline at end of file diff --git a/code/modules/blob2/overmind/types/volatile_alluvium.dm b/code/modules/blob2/overmind/types/volatile_alluvium.dm new file mode 100644 index 0000000000..a689bb856f --- /dev/null +++ b/code/modules/blob2/overmind/types/volatile_alluvium.dm @@ -0,0 +1,55 @@ +// A blob that steals your weapon. +/datum/blob_type/volatile_alluvium + name = "volatile alluvium" + desc = "A churning, earthy mass that moves in waves." + ai_desc = "earthen" + effect_desc = "Moves slowly, producing weak ranged spores to defend itself, and inflicts brute attacks. Attempts to disarm nearby attackers. Weak to water." + difficulty = BLOB_DIFFICULTY_HARD //Slow-starting, but can be overwhelming if left alone. + color = "#6B481E" + complementary_color = "#7F471F" + damage_lower = 10 + damage_upper = 20 + armor_pen = 40 + brute_multiplier = 0.7 + burn_multiplier = 0.5 + spread_modifier = 0.5 + ai_aggressiveness = 50 + attack_message = "The alluvium crashes against you" + attack_verb = "crashes against" + can_build_factories = TRUE + can_build_resources = TRUE + spore_type = /mob/living/simple_mob/blob/spore/weak + 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. + var/mob/living/carbon/human/H = attacker + var/obj/item/I = H.get_active_hand() + H.drop_item() + if(I) + if((I.sharp || I.edge) && !istype(I, /obj/item/weapon/gun)) + I.forceMove(get_turf(B)) // Disarmed entirely. + B.visible_message("The [name] heaves, \the [attacker]'s weapon becoming stuck in the churning mass!") + else + I.throw_at(B, 2, 4) // Just yoinked. + B.visible_message("The [name] heaves, pulling \the [attacker]'s weapon from their hands!") + B.blob_attack_animation(attacker, B.overmind) + return ..() + +/datum/blob_type/volatile_alluvium/on_water(obj/structure/blob/B, amount) + spawn(1) + 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) diff --git a/polaris.dme b/polaris.dme index 7d0922f2b2..9570a0d860 100644 --- a/polaris.dme +++ b/polaris.dme @@ -1452,6 +1452,25 @@ #include "code\modules\blob2\overmind\overmind.dm" #include "code\modules\blob2\overmind\powers.dm" #include "code\modules\blob2\overmind\types.dm" +#include "code\modules\blob2\overmind\types\blazing_oil.dm" +#include "code\modules\blob2\overmind\types\classic.dm" +#include "code\modules\blob2\overmind\types\cryogenic_goo.dm" +#include "code\modules\blob2\overmind\types\ectoplasmic_horror.dm" +#include "code\modules\blob2\overmind\types\electromagnetic_web.dm" +#include "code\modules\blob2\overmind\types\energized_jelly.dm" +#include "code\modules\blob2\overmind\types\explosive_lattice.dm" +#include "code\modules\blob2\overmind\types\fabrication_swarm.dm" +#include "code\modules\blob2\overmind\types\fulminant_organism.dm" +#include "code\modules\blob2\overmind\types\fungal_bloom.dm" +#include "code\modules\blob2\overmind\types\grey_goo.dm" +#include "code\modules\blob2\overmind\types\pressurized_slime.dm" +#include "code\modules\blob2\overmind\types\radioactive_ooze.dm" +#include "code\modules\blob2\overmind\types\ravenous_macrophage.dm" +#include "code\modules\blob2\overmind\types\reactive_spines.dm" +#include "code\modules\blob2\overmind\types\roiling_mold.dm" +#include "code\modules\blob2\overmind\types\shifting_fragments.dm" +#include "code\modules\blob2\overmind\types\synchronous_mesh.dm" +#include "code\modules\blob2\overmind\types\volatile_alluvium.dm" #include "code\modules\busy_space\air_traffic.dm" #include "code\modules\busy_space\loremaster.dm" #include "code\modules\busy_space\organizations.dm"