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"