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)
+
+