diff --git a/code/datums/looping_sounds/machinery_sounds.dm b/code/datums/looping_sounds/machinery_sounds.dm index fdb21dc7ecf..f958e957f3a 100644 --- a/code/datums/looping_sounds/machinery_sounds.dm +++ b/code/datums/looping_sounds/machinery_sounds.dm @@ -18,6 +18,16 @@ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/datum/looping_sound/destabilized_crystal + mid_sounds = list('sound/machines/sm/loops/delamming.ogg' = 1) + mid_length = 60 + volume = 55 + extra_range = 15 + falloff = 5 + vary = TRUE + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /datum/looping_sound/generator start_sound = 'sound/machines/generator/generator_start.ogg' start_length = 4 diff --git a/code/game/objects/effects/anomalies.dm b/code/game/objects/effects/anomalies.dm index c2ea23a0d6a..da11b2a7488 100644 --- a/code/game/objects/effects/anomalies.dm +++ b/code/game/objects/effects/anomalies.dm @@ -161,6 +161,10 @@ var/shockdamage = 20 var/explosive = TRUE +/obj/effect/anomaly/flux/Initialize(mapload, new_lifespan, drops_core = TRUE, _explosive = TRUE) + . = ..() + explosive = _explosive + /obj/effect/anomaly/flux/anomalyEffect() ..() canshock = TRUE diff --git a/code/modules/assembly/signaler.dm b/code/modules/assembly/signaler.dm index 82fe352d497..91d29110337 100644 --- a/code/modules/assembly/signaler.dm +++ b/code/modules/assembly/signaler.dm @@ -179,6 +179,9 @@ /obj/item/assembly/signaler/anomaly/attack_self() return +/obj/item/assembly/signaler/crystal_anomaly/attack_self() + return + /obj/item/assembly/signaler/cyborg /obj/item/assembly/signaler/cyborg/attackby(obj/item/W, mob/user, params) diff --git a/code/modules/events/crystal_event.dm b/code/modules/events/crystal_event.dm index 1b822e38bda..368fe90aab7 100644 --- a/code/modules/events/crystal_event.dm +++ b/code/modules/events/crystal_event.dm @@ -35,9 +35,9 @@ This section is for the event controller earliest_start = 25 MINUTES /datum/round_event/crystal_invasion - startWhen = 10 + startWhen = 5 announceWhen = 1 - endWhen = 25 MINUTES + endWhen = 460 ///Is the name of the wave, used to check wich wave will be generated var/wave_name ///Max number of portals that can spawn per type of wave @@ -95,7 +95,7 @@ This section is for the event controller anomaly from a portal by using the anomaly neutralizer, place it inside a crystal stabilizer, and inject it into your Supermatter to stop a ZK-Lambda-Class Cosmic Fragmentation Scenario from occurring.", "Alert") sound_to_playing_players('sound/misc/notice1.ogg') - addtimer(CALLBACK(src, .proc/spawn_portals), 10 SECONDS) + addtimer(CALLBACK(src, .proc/spawn_portals), 3 SECONDS) ///Pick a location from the generic_event_spawns list that are present on the maps and call the spawn anomaly and portal procs /datum/round_event/crystal_invasion/proc/spawn_portals() @@ -109,14 +109,26 @@ This section is for the event controller for(var/i in 1 to 6) spawn_anomaly(spawners) - addtimer(CALLBACK(src, .proc/more_portals, GLOB.crystal_invasion_waves[wave_name]), 15 MINUTES) + var/list/crystal_spawner_turfs = list() + for(var/range_turf in RANGE_TURFS(6, dest_crystal.loc)) + if(!isopenturf(range_turf) || isspaceturf(range_turf)) + continue + crystal_spawner_turfs += range_turf + for(var/i in 1 to 6) + if(!length(crystal_spawner_turfs)) + break + var/pick_portal = pickweight(GLOB.crystal_invasion_waves["big wave"]) + var/turf/crystal_spawner_turf = pick_n_take(crystal_spawner_turfs) + new pick_portal(crystal_spawner_turf) + + addtimer(CALLBACK(src, .proc/more_portals, GLOB.crystal_invasion_waves[wave_name]), 10 MINUTES) ///Spawn an anomaly randomly in a different location than spawn_portal() /datum/round_event/crystal_invasion/proc/spawn_anomaly(list/spawners) if(!spawners.len) CRASH("No landmarks on the station map, aborting") var/obj/spawner = pick(spawners) - new/obj/effect/anomaly/flux(spawner.loc) + new/obj/effect/anomaly/flux(spawner.loc, 30 SECONDS, FALSE, FALSE) ///Spawn one portal in a random location choosen from the generic_event_spawns list /datum/round_event/crystal_invasion/proc/spawn_portal(list/wave_type, list/spawners) @@ -129,7 +141,7 @@ This section is for the event controller ///If after 10 minutes the crystal is not stabilized more portals are spawned and the event progress further /datum/round_event/crystal_invasion/proc/more_portals() priority_announce("WARNING - Detected another spike from the destabilized crystal. More portals are spawning all around the station, the next spike could \ - cause a \[REDACTED] class event we assume you have 10 more minutes before total crystal annihilation", "Alert") + cause a \[REDACTED] class event we assume you have five more minutes before total crystal annihilation", "Alert") sound_to_playing_players('sound/misc/notice1.ogg') var/list/spawners = list() for(var/es in GLOB.generic_event_spawns) @@ -140,18 +152,17 @@ This section is for the event controller spawn_portal(GLOB.crystal_invasion_waves["small wave"], spawners) /datum/round_event/crystal_invasion/tick() - if(dest_crystal == null) - processing = FALSE - message_admins("Deleted Destabilized crystal, aborting") - kill() if(dest_crystal.is_stabilized == TRUE) processing = FALSE is_zk_scenario = FALSE - end() - -/datum/round_event/crystal_invasion/end() - if(is_zk_scenario == TRUE) + finish_event() + if(activeFor == endWhen - 10) processing = FALSE + finish_event() + +///Handles wich end the event shall have +/datum/round_event/crystal_invasion/proc/finish_event() + if(is_zk_scenario == TRUE) zk_event_announcement() else restore() @@ -193,6 +204,7 @@ This section is for the event controller /datum/round_event/crystal_invasion/proc/on_dest_crystal_qdel() UnregisterSignal(dest_crystal, COMSIG_PARENT_QDELETING) processing = FALSE + message_admins("Deleted Destabilized crystal, aborting") dest_crystal = null kill() @@ -213,12 +225,26 @@ This section is for the destabilized SM var/active = TRUE ///Check if the crew managed to stop the ZK-class event by stabilizing the crystal var/is_stabilized = FALSE + ///Our sound loop + var/datum/looping_sound/destabilized_crystal/soundloop + +/obj/machinery/destabilized_crystal/Initialize() + . = ..() + soundloop = new(list(src), TRUE) + +/obj/machinery/destabilized_crystal/Destroy() + QDEL_NULL(soundloop) + return ..() /obj/machinery/destabilized_crystal/process() if(!active) return if(prob(75)) radiation_pulse(src, 250, 6) + if(prob(30)) + playsound(loc, 'sound/weapons/emitter2.ogg', 100, TRUE, extrarange = 10) + if(prob(15)) + fire_nuclear_particle() var/turf/loc_turf = loc var/datum/gas_mixture/env = loc_turf.return_air() var/datum/gas_mixture/removed @@ -256,6 +282,36 @@ This section is for the destabilized SM active = FALSE is_stabilized = TRUE +/obj/machinery/destabilized_crystal/examine(mob/user) + . = ..() + . += "The Crystal appears to be heavily destabilized. Maybe it can be fixed by injecting it with something from another world." + +/obj/machinery/destabilized_crystal/Bumped(atom/movable/movable_atom) + if(!isliving(movable_atom)) + return + var/mob/living/user = movable_atom + if(isnull(user.mind)) + return + movable_atom.visible_message("\The [movable_atom] slams into \the [src] inducing a resonance... [movable_atom.p_their()] body starts to glow and burst into flames before flashing into dust!",\ + "You slam into \the [src] as your ears are filled with unearthly ringing. Your last thought is \"Oh, fuck.\"",\ + "You hear an unearthly noise as a wave of heat washes over you.") + playsound(get_turf(src), 'sound/effects/supermatter.ogg', 50, TRUE) + consume(movable_atom) + +/obj/machinery/destabilized_crystal/proc/consume(atom/movable/movable_atom) + if(isliving(movable_atom)) + var/mob/living/user = movable_atom + if(user.status_flags & GODMODE || isnull(user.mind)) + return + message_admins("[src] has consumed [key_name_admin(user)] [ADMIN_JMP(src)].") + investigate_log("has consumed [key_name(user)].", INVESTIGATE_SUPERMATTER) + user.dust(force = TRUE) + for(var/find_portal in GLOB.crystal_portals) + var/obj/structure/crystal_portal/portal = find_portal + portal.modify_component() + priority_announce("The sacrifice of a member of the station (we hope was the clown) has weakened the portals and the monsters generation is slowing down!") + sound_to_playing_players('sound/misc/notice2.ogg') + /* This section is for the crystal stabilizer item and the crystal from the closed portals */ @@ -271,6 +327,7 @@ This section is for the crystal stabilizer item and the crystal from the closed /obj/item/crystal_stabilizer/examine(user) . = ..() + . += "There is a compartment for something small... like a crystal..." if(!filled) . += "The [src] is empty." else @@ -295,6 +352,27 @@ This section is for the crystal stabilizer item and the crystal from the closed icon = 'icons/obj/stack_objects.dmi' material_type = /datum/material/otherworld_crystal +/* +This section is for the signaler part of the crystal portals +*/ +/obj/item/assembly/signaler/crystal_anomaly + name = "Nothing here" + desc = "Nothing to see here." + ///Link to the crystal + var/anomaly_type = /obj/structure/crystal_portal + +/obj/item/assembly/signaler/crystal_anomaly/receive_signal(datum/signal/signal) + if(!signal) + return FALSE + if(signal.data["code"] != code) + return FALSE + if(suicider) + manual_suicide(suicider) + for(var/obj/structure/crystal_portal/portal in get_turf(src)) + portal.closed = TRUE + qdel(portal) + return TRUE + /* This section is for the crystal portals variations */ @@ -305,11 +383,12 @@ This section is for the crystal portals variations icon_state = "anom" color = COLOR_SILVER anchored = TRUE + light_range = 3 resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF | FREEZE_PROOF ///Max amount of mobs that a portal can spawn in any given time var/max_mobs = 5 ///Spawn time between each mobs - var/spawn_time = 1000 + var/spawn_time = 0 ///Type of mob that the portal will spawn, if more than one type in the list will choose randomly var/mob_types = list(/mob/living/simple_animal/hostile/carp) ///Fluff text for each mob spawned @@ -320,6 +399,8 @@ This section is for the crystal portals variations var/spawner_type = /datum/component/spawner ///This var check if the portal has been closed by a player with a neutralizer var/closed = FALSE + ///Link to the signaler object for signaling uses + var/obj/item/assembly/signaler/crystal_anomaly/a_signal = /obj/item/assembly/signaler/crystal_anomaly /obj/structure/crystal_portal/Initialize() . = ..() @@ -327,9 +408,18 @@ This section is for the crystal portals variations GLOB.crystal_portals += src mob_types = typelist("crystal_portal mob_types", mob_types) faction = typelist("crystal_portal faction", faction) + a_signal = new a_signal(src) + a_signal.code = rand(1,100) + a_signal.anomaly_type = type + var/frequency = rand(MIN_FREE_FREQ, MAX_FREE_FREQ) + if(ISMULTIPLE(frequency, 2))//signaller frequencies are always uneven! + frequency++ + a_signal.set_frequency(frequency) /obj/structure/crystal_portal/Destroy() GLOB.crystal_portals -= src + if(a_signal) + QDEL_NULL(a_signal) if(!closed) switch(name) if("Small Portal") @@ -343,6 +433,10 @@ This section is for the crystal portals variations new/obj/item/stack/sheet/otherworld_crystal(loc) return ..() +/obj/structure/crystal_portal/examine(user) + . = ..() + . += "The [src] seems to be releasing some sort or high frequency wavelength, maybe it could be closed if another signal is sent back or if an equivalent device is used on it." + /obj/structure/crystal_portal/attack_animal(mob/living/simple_animal/M) if(faction_check(faction, M.faction, FALSE) && !M.client) return ..() @@ -356,13 +450,19 @@ This section is for the crystal portals variations to_chat(user, "You successfully close \the [src]!") closed = TRUE qdel(src) + if(W.tool_behaviour == TOOL_ANALYZER) + to_chat(user, "Analyzing... [src]'s unstable field is fluctuating along frequency [format_frequency(a_signal.frequency)], code [a_signal.code].") + +/obj/structure/crystal_portal/proc/modify_component() + spawn_time += 5 + AddComponent(spawner_type, mob_types, spawn_time, faction, spawn_text, max_mobs) /obj/structure/crystal_portal/small name = "Small Portal" desc = "A small portal to an unkown dimension!" color = COLOR_BRIGHT_BLUE max_mobs = 3 - spawn_time = 200 + spawn_time = 5 SECONDS mob_types = list( /mob/living/simple_animal/hostile/crystal_monster/minion, /mob/living/simple_animal/hostile/crystal_monster/thug @@ -372,8 +472,8 @@ This section is for the crystal portals variations name = "Medium Portal" desc = "A medium portal to an unkown dimension!" color = COLOR_GREEN - max_mobs = 5 - spawn_time = 180 + max_mobs = 4 + spawn_time = 10 SECONDS mob_types = list( /mob/living/simple_animal/hostile/crystal_monster/minion, /mob/living/simple_animal/hostile/crystal_monster/thug, @@ -384,8 +484,8 @@ This section is for the crystal portals variations name = "Big Portal" desc = "A big portal to an unkown dimension!" color = COLOR_RED - max_mobs = 8 - spawn_time = 160 + max_mobs = 5 + spawn_time = 10 SECONDS mob_types = list( /mob/living/simple_animal/hostile/crystal_monster/minion, /mob/living/simple_animal/hostile/crystal_monster/thug, @@ -397,8 +497,8 @@ This section is for the crystal portals variations name = "Huge Portal" desc = "A huge portal to an unkown dimension!" color = COLOR_BLACK - max_mobs = 12 - spawn_time = 140 + max_mobs = 6 + spawn_time = 15 SECONDS mob_types = list( /mob/living/simple_animal/hostile/crystal_monster/minion, /mob/living/simple_animal/hostile/crystal_monster/thug, @@ -419,16 +519,10 @@ This section is for the crystal monsters variations icon_dead = "crystal_minion" gender = NEUTER mob_biotypes = MOB_MINERAL|MOB_HUMANOID - turns_per_move = 5 + turns_per_move = 1 speak_emote = list("resonates") emote_see = list("resonates") a_intent = INTENT_HARM - maxHealth = 25 - health = 25 - speed = 1.2 - harm_intent_damage = 2.5 - melee_damage_lower = 5 - melee_damage_upper = 5 minbodytemp = 0 maxbodytemp = 1500 healable = 0 //they're crystals how would bruise packs help them?? @@ -438,13 +532,16 @@ This section is for the crystal monsters variations atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) unsuitable_atmos_damage = 10 robust_searching = 1 - stat_attack = UNCONSCIOUS + stat_attack = HARD_CRIT faction = list("crystal") see_in_dark = 8 lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE deathmessage = "collapses into dust!" del_on_death = 1 footstep_type = FOOTSTEP_MOB_SHOE + stop_automated_movement = FALSE + stop_automated_movement_when_pulled = FALSE + wander = TRUE /mob/living/simple_animal/hostile/crystal_monster/minion name = "crystal minion" @@ -453,12 +550,26 @@ This section is for the crystal monsters variations icon_state = "crystal_minion" icon_living = "crystal_minion" icon_dead = "crystal_minion" - maxHealth = 40 - health = 40 - speed = 1.5 - harm_intent_damage = 5 - melee_damage_lower = 5 - melee_damage_upper = 10 + maxHealth = 20 + health = 20 + speed = 0.8 + harm_intent_damage = 11 + melee_damage_lower = 20 + melee_damage_upper = 35 + move_force = MOVE_FORCE_WEAK + move_resist = MOVE_FORCE_WEAK + pull_force = MOVE_FORCE_WEAK + var/death_cloud_size = 2 + +/mob/living/simple_animal/hostile/crystal_monster/minion/Destroy() + if(isturf(loc)) + var/datum/effect_system/smoke_spread/chem/S = new + create_reagents(3) + reagents.add_reagent(/datum/reagent/toxin/lexorin, 4) + S.attach(loc) + S.set_up(reagents, death_cloud_size, loc, silent = TRUE) + S.start() + return ..() /mob/living/simple_animal/hostile/crystal_monster/thug name = "crystal thug" @@ -467,12 +578,30 @@ This section is for the crystal monsters variations icon_state = "crystal_thug" icon_living = "crystal_thug" icon_dead = "crystal_thug" - maxHealth = 50 - health = 50 - speed = 1.3 - harm_intent_damage = 9 - melee_damage_lower = 15 - melee_damage_upper = 20 + maxHealth = 20 + health = 20 + speed = 0.9 + harm_intent_damage = 11 + melee_damage_lower = 20 + melee_damage_upper = 35 + move_force = MOVE_FORCE_NORMAL + move_resist = MOVE_FORCE_NORMAL + pull_force = MOVE_FORCE_NORMAL + dodging = TRUE + dodge_prob = 25 + +/mob/living/simple_animal/hostile/crystal_monster/thug/attackby(obj/item/O, mob/user, params) + if(prob(30)) + var/list/temp_turfs = list() + for(var/turf/around_turfs in view(7, src)) + if(!isopenturf(around_turfs) || isspaceturf(around_turfs)) + continue + temp_turfs += around_turfs + if(length(temp_turfs)) + var/turf/open/choosen_turf = pick(temp_turfs) + do_teleport(src, choosen_turf) + return + return ..() /mob/living/simple_animal/hostile/crystal_monster/recruit name = "crystal recruit" @@ -481,12 +610,24 @@ This section is for the crystal monsters variations icon_state = "crystal_recruit" icon_living = "crystal_recruit" icon_dead = "crystal_recruit" - maxHealth = 65 - health = 65 + maxHealth = 20 + health = 20 speed = 1.2 harm_intent_damage = 11 melee_damage_lower = 20 melee_damage_upper = 35 + move_force = MOVE_FORCE_STRONG + move_resist = MOVE_FORCE_STRONG + pull_force = MOVE_FORCE_STRONG + obj_damage = 100 + environment_smash = ENVIRONMENT_SMASH_WALLS + +/mob/living/simple_animal/hostile/crystal_monster/recruit/Bump(atom/clong) + . = ..() + if(isturf(clong)) + var/turf/turf_bump = clong + turf_bump.Melt() + playsound(get_turf(src), 'sound/effects/supermatter.ogg', 35, TRUE) /mob/living/simple_animal/hostile/crystal_monster/killer name = "crystal killer" @@ -495,12 +636,40 @@ This section is for the crystal monsters variations icon_state = "crystal_killer" icon_living = "crystal_killer" icon_dead = "crystal_killer" - maxHealth = 80 - health = 80 - speed = 1.2 - harm_intent_damage = 13 - melee_damage_lower = 30 + maxHealth = 35 + health = 35 + speed = 0.75 + harm_intent_damage = 20 + melee_damage_lower = 25 melee_damage_upper = 45 + move_force = MOVE_FORCE_VERY_STRONG + move_resist = MOVE_FORCE_VERY_STRONG + pull_force = MOVE_FORCE_VERY_STRONG + dodging = TRUE + dodge_prob = 35 + environment_smash = ENVIRONMENT_SMASH_RWALLS + projectiletype = /obj/projectile/temp/basilisk + projectilesound = 'sound/weapons/pierce.ogg' + ranged = 1 + ranged_message = "throws" + ranged_cooldown_time = 25 + +/obj/projectile/temp/crystal_killer + name = "freezing blast" + icon_state = "ice_2" + color = COLOR_YELLOW + damage = 0 + damage_type = BURN + nodamage = TRUE + flag = ENERGY + temperature = -75 + +/mob/living/simple_animal/hostile/crystal_monster/killer/Bump(atom/clong) + . = ..() + if(isturf(clong)) + var/turf/turf_bump = clong + turf_bump.Melt() + playsound(get_turf(src), 'sound/effects/supermatter.ogg', 35, TRUE) /mob/living/simple_animal/hostile/crystal_monster/boss name = "crystal boss" @@ -509,9 +678,48 @@ This section is for the crystal monsters variations icon_state = "crystal_boss" icon_living = "crystal_boss" icon_dead = "crystal_boss" - maxHealth = 150 - health = 150 - speed = 1 - harm_intent_damage = 15 - melee_damage_lower = 45 - melee_damage_upper = 65 + maxHealth = 300 + health = 30 + speed = 1.3 + harm_intent_damage = 11 + melee_damage_lower = 20 + melee_damage_upper = 35 + move_force = MOVE_FORCE_EXTREMELY_STRONG + move_resist = MOVE_FORCE_EXTREMELY_STRONG + pull_force = MOVE_FORCE_EXTREMELY_STRONG + environment_smash = ENVIRONMENT_SMASH_RWALLS + projectiletype = /obj/projectile/magic/aoe/lightning + projectilesound = 'sound/weapons/pierce.ogg' + ranged = 1 + ranged_message = "throws" + ranged_cooldown_time = 45 + +/mob/living/simple_animal/hostile/crystal_monster/boss/Bump(atom/clong) + . = ..() + if(isliving(clong)) + var/mob/living/mob = clong + if(mob.stat >= HARD_CRIT) + mob.dust() + health += 35 + else if(isturf(clong)) + var/turf/turf_bump = clong + turf_bump.Melt() + playsound(get_turf(src), 'sound/effects/supermatter.ogg', 35, TRUE) + +/mob/living/simple_animal/hostile/crystal_monster/boss/AttackingTarget() + . = ..() + if(isliving(target)) + var/mob/living/mob = target + if(mob.stat >= HARD_CRIT) + mob.dust() + health += 35 + else + var/obj/item/bodypart/body_part = mob.get_bodypart(pick(BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG)) + if(body_part) + body_part.drop_limb(FALSE, TRUE) + else if(isturf(target)) + var/turf/turf_bump = target + turf_bump.Melt() + playsound(get_turf(src), 'sound/effects/supermatter.ogg', 35, TRUE) + +