diff --git a/code/__HELPERS/areas.dm b/code/__HELPERS/areas.dm index 363a760d71d..d2501885286 100644 --- a/code/__HELPERS/areas.dm +++ b/code/__HELPERS/areas.dm @@ -176,3 +176,16 @@ GLOBAL_LIST_INIT(typecache_powerfailure_safe_areas, typecacheof(/area/station/en if(target_z == 0 || target_z == turf_in_area.z) turfs += turf_in_area return turfs + +///Takes: list of area types +///Returns: all mobs that are in an area type +/proc/mobs_in_area_type(list/area/checked_areas) + var/list/mobs_in_area = list() + for(var/mob/living/mob as anything in GLOB.mob_living_list) + if(QDELETED(mob)) + continue + for(var/area in checked_areas) + if(istype(get_area(mob), area)) + mobs_in_area += mob + break + return mobs_in_area diff --git a/code/controllers/subsystem/processing/supermatter_cascade.dm b/code/controllers/subsystem/processing/supermatter_cascade.dm index ddd5ef09b38..a6fbc3de46b 100644 --- a/code/controllers/subsystem/processing/supermatter_cascade.dm +++ b/code/controllers/subsystem/processing/supermatter_cascade.dm @@ -2,3 +2,6 @@ PROCESSING_SUBSYSTEM_DEF(supermatter_cascade) name = "Supermatter Cascade" wait = 0.5 SECONDS stat_tag = "SC" + + ///Is a cascade happening right now? + var/cascade_initiated = FALSE diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm index ab9c1a6ddb6..d42fd525c92 100644 --- a/code/controllers/subsystem/ticker.dm +++ b/code/controllers/subsystem/ticker.dm @@ -623,7 +623,7 @@ SUBSYSTEM_DEF(ticker) if(GANG_DESTROYED) news_message = "The crew of [decoded_station_name] would like to thank the Spinward Stellar Coalition Police Department for quickly resolving a minor terror threat to the station." if(SUPERMATTER_CASCADE) - news_message = "Recovery of the surviving crew of [decoded_station_name] is underway following a major supermatter cascade." + news_message = "Officials are advising nearby colonies about a newly declared exclusion zone in the sector surrounding [decoded_station_name]." //SKYRAT EDIT - START if(SSblackbox.first_death) diff --git a/code/datums/mood_events/generic_negative_events.dm b/code/datums/mood_events/generic_negative_events.dm index e5f3615e546..6a904a00343 100644 --- a/code/datums/mood_events/generic_negative_events.dm +++ b/code/datums/mood_events/generic_negative_events.dm @@ -47,6 +47,11 @@ mood_change = -2 timeout = 4 MINUTES +/datum/mood_event/cascade // Big boi delamination + description = "The engineers have finally done it, we are all going to die..." + mood_change = -8 + timeout = 5 MINUTES + /datum/mood_event/depression_minimal description = "I feel a bit down." mood_change = -10 diff --git a/code/game/gamemodes/game_mode.dm b/code/game/gamemodes/game_mode.dm index 470aa6f455b..388f56311a6 100644 --- a/code/game/gamemodes/game_mode.dm +++ b/code/game/gamemodes/game_mode.dm @@ -200,6 +200,8 @@ SSticker.news_report = STATION_EVACUATED if(SSshuttle.emergency.is_hijacked()) SSticker.news_report = SHUTTLE_HIJACK + if(SSsupermatter_cascade.cascade_initiated) + SSticker.news_report = SUPERMATTER_CASCADE /// Mode specific admin panel. /datum/game_mode/proc/admin_panel() diff --git a/code/game/objects/items/devices/destabilizing_crystal.dm b/code/game/objects/items/devices/destabilizing_crystal.dm index da15adb3ee0..b6a2b2c8083 100644 --- a/code/game/objects/items/devices/destabilizing_crystal.dm +++ b/code/game/objects/items/devices/destabilizing_crystal.dm @@ -1,6 +1,7 @@ /obj/item/destabilizing_crystal name = "destabilizing crystal" - desc = "A crystal that can be used to destabilize the supermatter to cause a resonance cascade. Apply on fully stable supermatter and start the delamination. Prevent anyone from stopping you." + desc = "A crystal that can be used to destabilize the supermatter to cause a resonance cascade. Carefully apply this on \ + a fully stable supermatter crystal and start the delamination. Prevent anyone from stopping you." icon = 'icons/obj/supermatter.dmi' icon_state = "destabilizing_crystal" w_class = WEIGHT_CLASS_NORMAL diff --git a/code/modules/antagonists/traitor/equipment/Malf_Modules.dm b/code/modules/antagonists/traitor/equipment/Malf_Modules.dm index a9d46b3e56f..46b09c1501f 100644 --- a/code/modules/antagonists/traitor/equipment/Malf_Modules.dm +++ b/code/modules/antagonists/traitor/equipment/Malf_Modules.dm @@ -758,7 +758,7 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module)) /datum/action/innate/ai/emergency_lights/Activate() for(var/obj/machinery/light/L in GLOB.machines) if(is_station_level(L.z)) - L.no_emergency = TRUE + L.no_low_power = TRUE INVOKE_ASYNC(L, /obj/machinery/light/.proc/update, FALSE) CHECK_TICK to_chat(owner, span_notice("Emergency light connections severed.")) diff --git a/code/modules/antagonists/traitor/objectives/final_objective/supermatter_cascade.dm b/code/modules/antagonists/traitor/objectives/final_objective/supermatter_cascade.dm index 8b3c02004d7..0fd68891db1 100644 --- a/code/modules/antagonists/traitor/objectives/final_objective/supermatter_cascade.dm +++ b/code/modules/antagonists/traitor/objectives/final_objective/supermatter_cascade.dm @@ -1,7 +1,7 @@ /datum/traitor_objective/final/supermatter_cascade - name = "destroy the station by causing a crystallizing resonance cascade" + name = "Destroy the station by causing a crystallizing resonance cascade" description = "Destroy the station by causing a supermatter cascade. Go to %AREA% to retrieve the destabilizing crystal \ - and use it on the SM." + and use it on the supermatter." ///area type the objective owner must be in to recieve the destabilizing crystal var/area/dest_crystal_area_pickup @@ -34,7 +34,7 @@ return var/area/delivery_area = get_area(user) if(delivery_area.type != dest_crystal_area_pickup) - to_chat(user, span_warning("You must be in [initial(dest_crystal_area_pickup.name)] to recieve the supermatter cascade kit.")) + to_chat(user, span_warning("You must be in [initial(dest_crystal_area_pickup.name)] to receive the supermatter cascade kit.")) return sent_crystal = TRUE podspawn(list( diff --git a/code/modules/power/apc/apc_main.dm b/code/modules/power/apc/apc_main.dm index 08d4358da6a..58c5db2e7be 100644 --- a/code/modules/power/apc/apc_main.dm +++ b/code/modules/power/apc/apc_main.dm @@ -370,8 +370,8 @@ if("emergency_lighting") emergency_lights = !emergency_lights for(var/obj/machinery/light/L in area) - if(!initial(L.no_emergency)) //If there was an override set on creation, keep that override - L.no_emergency = emergency_lights + if(!initial(L.no_low_power)) //If there was an override set on creation, keep that override + L.no_low_power = emergency_lights INVOKE_ASYNC(L, /obj/machinery/light/.proc/update, FALSE) CHECK_TICK return TRUE diff --git a/code/modules/power/lighting/light.dm b/code/modules/power/lighting/light.dm index 3154621e6d0..b7325575301 100644 --- a/code/modules/power/lighting/light.dm +++ b/code/modules/power/lighting/light.dm @@ -52,18 +52,24 @@ var/nightshift_light_power = 0.45 ///Basecolor of the nightshift light var/nightshift_light_color = "#FFDDCC" - ///If true, the light is in emergency mode - var/emergency_mode = FALSE - ///If true, this light cannot ever have an emergency mode - var/no_emergency = FALSE - ///Multiplier for this light's base brightness in emergency power mode - var/bulb_emergency_brightness_mul = 0.25 - ///Determines the colour of the light while it's in emergency mode - var/bulb_emergency_colour = "#FF3232" - ///The multiplier for determining the light's power in emergency mode - var/bulb_emergency_pow_mul = 0.75 - ///The minimum value for the light's power in emergency mode - var/bulb_emergency_pow_min = 0.5 + ///If true, the light is in low power mode + var/low_power_mode = FALSE + ///If true, this light cannot ever be in low power mode + var/no_low_power = FALSE + ///If true, overrides lights to use emergency lighting + var/major_emergency = FALSE + ///Multiplier for this light's base brightness during a cascade + var/bulb_major_emergency_brightness_mul = 0.75 + ///Colour of the light when major emergency mode is on + var/bulb_emergency_colour = "#ff4e4e" + ///Multiplier for this light's base brightness in low power power mode + var/bulb_low_power_brightness_mul = 0.25 + ///Determines the colour of the light while it's in low power mode + var/bulb_low_power_colour = "#FF3232" + ///The multiplier for determining the light's power in low power mode + var/bulb_low_power_pow_mul = 0.75 + ///The minimum value for the light's power in low power mode + var/bulb_low_power_pow_min = 0.5 ///Power usage - W per unit of luminosity var/power_consumption_rate = 20 @@ -81,7 +87,7 @@ var/obj/machinery/power/apc/temp_apc = our_area.apc nightshift_enabled = temp_apc?.nightshift_lights - if(start_with_cell && !no_emergency) + if(start_with_cell && !no_low_power) cell = new/obj/item/stock_parts/cell/emergency_light(src) RegisterSignal(src, COMSIG_LIGHT_EATER_ACT, .proc/on_light_eater) @@ -109,8 +115,8 @@ /obj/machinery/light/update_icon_state() switch(status) // set icon_states if(LIGHT_OK) - //var/area/A = get_area(src) //SKYRAT EDIT REMOVAL - if(emergency_mode || firealarm) //SKYRAT EDIT CHANGE + //var/area/local_area = get_area(src) SKYRAT EDIT REMOVAL + if(low_power_mode || major_emergency) // SKYRAT EDIT CHANGE icon_state = "[base_state]_emergency" else icon_state = "[base_state]" @@ -131,7 +137,7 @@ var/area/local_area = get_area(src) if(emergency_mode || (local_area?.fire)) */ - if(emergency_mode || firealarm) // SKYRAT EDIT END + if(low_power_mode || major_emergency) // SKYRAT EDIT END . += mutable_appearance(overlay_icon, "[base_state]_emergency") return if(nightshift_enabled) @@ -172,7 +178,7 @@ switch(status) if(LIGHT_BROKEN,LIGHT_BURNED,LIGHT_EMPTY) on = FALSE - emergency_mode = FALSE + low_power_mode = FALSE if(on) /* SKYRAT EDIT ORIGINAL var/brightness_set = brightness @@ -182,12 +188,15 @@ color_set = color var/area/local_area = get_area(src) if (local_area?.fire) - color_set = bulb_emergency_colour + color_set = bulb_low_power_colour else if (nightshift_enabled) brightness_set = nightshift_brightness power_set = nightshift_light_power if(!color) color_set = nightshift_light_color + else if (major_emergency) + color_set = bulb_low_power_colour + brightness_set = brightness * bulb_major_emergency_brightness_mul var/matching = light && brightness_set == light.light_range && power_set == light.light_power && color_set == light.light_color if(!matching) switchcount++ @@ -217,7 +226,7 @@ //SKYRAT EDIT END else if(has_emergency_power(LIGHT_EMERGENCY_POWER_USE) && !turned_off()) use_power = IDLE_POWER_USE - emergency_mode = TRUE + low_power_mode = TRUE START_PROCESSING(SSmachines, src) else use_power = IDLE_POWER_USE @@ -266,7 +275,7 @@ if (cell.charge == cell.maxcharge) return PROCESS_KILL cell.charge = min(cell.maxcharge, cell.charge + LIGHT_EMERGENCY_POWER_USE) //Recharge emergency power automatically while not using it - if(emergency_mode && !use_emergency_power(LIGHT_EMERGENCY_POWER_USE)) + if(low_power_mode && !use_emergency_power(LIGHT_EMERGENCY_POWER_USE)) update(FALSE) //Disables emergency mode and sets the color to normal /obj/machinery/light/proc/burn_out() @@ -452,7 +461,7 @@ // returns whether this light has emergency power // can also return if it has access to a certain amount of that power /obj/machinery/light/proc/has_emergency_power(power_usage_amount) - if(no_emergency || !cell) + if(no_low_power || !cell) return FALSE if(power_usage_amount ? cell.charge >= power_usage_amount : cell.charge) return status == LIGHT_OK @@ -467,9 +476,9 @@ return FALSE cell.use(power_usage_amount) set_light( - l_range = brightness * bulb_emergency_brightness_mul, - l_power = max(bulb_emergency_pow_min, bulb_emergency_pow_mul * (cell.charge / cell.maxcharge)), - l_color = bulb_emergency_colour + l_range = brightness * bulb_low_power_brightness_mul, + l_power = max(bulb_low_power_pow_min, bulb_low_power_pow_mul * (cell.charge / cell.maxcharge)), + l_color = bulb_low_power_colour ) return TRUE @@ -494,8 +503,8 @@ // ai attack - make lights flicker, because why not /obj/machinery/light/attack_ai(mob/user) - no_emergency = !no_emergency - to_chat(user, span_notice("Emergency lights for this fixture have been [no_emergency ? "disabled" : "enabled"].")) + no_low_power = !no_low_power + to_chat(user, span_notice("Emergency lights for this fixture have been [no_low_power ? "disabled" : "enabled"].")) update(FALSE) return @@ -568,6 +577,14 @@ // create a light tube/bulb item and put it in the user's hand drop_light_tube(user) +/obj/machinery/light/proc/set_major_emergency_light() + major_emergency = TRUE + update() + +/obj/machinery/light/proc/unset_major_emergency_light() + major_emergency = FALSE + update() + /obj/machinery/light/proc/drop_light_tube(mob/user) var/obj/item/light/light_object = new light_type() light_object.status = status diff --git a/code/modules/power/lighting/light_mapping_helpers.dm b/code/modules/power/lighting/light_mapping_helpers.dm index 4774241db22..dccf1f3c655 100644 --- a/code/modules/power/lighting/light_mapping_helpers.dm +++ b/code/modules/power/lighting/light_mapping_helpers.dm @@ -30,7 +30,7 @@ /obj/machinery/light/red bulb_colour = "#FF3232" nightshift_allowed = FALSE - no_emergency = TRUE + no_low_power = TRUE brightness = 4 bulb_power = 0.7 @@ -72,7 +72,7 @@ /obj/machinery/light/small/red bulb_colour = "#FF3232" - no_emergency = TRUE + no_low_power = TRUE nightshift_allowed = FALSE brightness = 2 bulb_power = 0.8 diff --git a/code/modules/power/supermatter/supermatter.dm b/code/modules/power/supermatter/supermatter.dm index 75040a151cf..b6b2db5541d 100644 --- a/code/modules/power/supermatter/supermatter.dm +++ b/code/modules/power/supermatter/supermatter.dm @@ -12,6 +12,10 @@ #define OBJECT (LOWEST + 1) #define LOWEST (1) +#define CASCADING_ADMIN "Admin" +#define CASCADING_CRITICAL_GAS "Critical gas point" +#define CASCADING_DESTAB_CRYSTAL "Destabilizing crystal" + GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) /obj/machinery/power/supermatter_crystal @@ -313,7 +317,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) /obj/machinery/power/supermatter_crystal/Destroy() if(warp) vis_contents -= warp - warp = null + QDEL_NULL(warp) investigate_log("has been destroyed.", INVESTIGATE_ENGINE) SSair.stop_processing_machine(src) QDEL_NULL(radio) @@ -334,6 +338,8 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) var/immune = HAS_TRAIT(user, TRAIT_MADNESS_IMMUNE) || (user.mind && HAS_TRAIT(user.mind, TRAIT_MADNESS_IMMUNE)) if(isliving(user) && !immune && (get_dist(user, src) < HALLUCINATION_RANGE(power))) . += span_danger("You get headaches just from looking at it.") + if(cascade_initiated) + . += span_bolddanger("The crystal is vibrating at immense speeds, warping space around it!") // SupermatterMonitor UI for ghosts only. Inherited attack_ghost will call this. /obj/machinery/power/supermatter_crystal/ui_interact(mob/user, datum/tgui/ui) @@ -439,7 +445,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) /obj/machinery/power/supermatter_crystal/update_overlays() . = ..() - if(final_countdown) + if(final_countdown && !cascade_initiated) . += "casuality_field" /obj/machinery/power/supermatter_crystal/proc/countdown() @@ -450,11 +456,22 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) final_countdown = TRUE update_appearance() - var/speaking = "[emergency_alert] The supermatter has reached critical integrity failure. Emergency causality destabilization field has been activated." + var/cascading = cascade_initiated + + var/speaking = "[emergency_alert] The supermatter has reached critical integrity failure." + + if(cascading) + speaking += " Harmonic frequency limits exceeded. Causality destabilization field could not be engaged." + else + speaking += " Emergency causality destabilization field has been activated." + radio.talk_into(src, speaking, common_channel, language = get_selected_language()) for(var/i in SUPERMATTER_COUNTDOWN_TIME to 0 step -10) if(damage < explosion_point) // Cutting it a bit close there engineers - radio.talk_into(src, "[safe_alert] Failsafe has been disengaged.", common_channel) + if(cascading) + radio.talk_into(src, "[safe_alert] Harmonic frequency restored within emergency bounds. Anti-resonance filter initiated.", common_channel) + else + radio.talk_into(src, "[safe_alert] Failsafe has been disengaged.", common_channel) final_countdown = FALSE update_appearance() return @@ -462,18 +479,21 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) sleep(10) continue else if(i > 50) - speaking = "[DisplayTimeText(i, TRUE)] remain before causality stabilization." + if(cascading) + speaking = "[DisplayTimeText(i, TRUE)] remain before resonance-induced stabilization." + else + speaking = "[DisplayTimeText(i, TRUE)] remain before causality stabilization." else speaking = "[i*0.1]..." radio.talk_into(src, speaking, common_channel) - sleep(10) + sleep(1 SECONDS) delamination_event() /obj/machinery/power/supermatter_crystal/proc/delamination_event() var/can_spawn_anomalies = is_station_level(loc.z) && is_main_engine && anomaly_event - var/is_cascading = check_cascade_requirements(anomaly_event) + var/is_cascading = cascade_initiated new /datum/supermatter_delamination(power, combined_gas, get_turf(src), explosion_power, gasmix_power_ratio, can_spawn_anomalies, is_cascading) qdel(src) @@ -484,29 +504,34 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) delamination_event() /** - * Checks if the supermatter is in a state where it can cascade + * Checks if and why the supermatter is in a state where it can cascade * - * Arguments: can_trigger = TRUE if the supermatter can trigger the cascade - * Returns: TRUE if the supermatter can cascade + * Returns: cause of the cascade, for logging */ -/obj/machinery/power/supermatter_crystal/proc/check_cascade_requirements(can_trigger) +/obj/machinery/power/supermatter_crystal/proc/check_cascade_requirements() + if(admin_cascade) + return CASCADING_ADMIN - if(get_integrity_percent() < SUPERMATTER_CASCADE_PERCENT && !cascade_initiated && !admin_cascade && can_trigger) + if(!anomaly_event) return FALSE - var/supermatter_cascade = can_trigger + if(has_destabilizing_crystal) + return CASCADING_DESTAB_CRYSTAL + + var/critical_gas_exceeded = TRUE var/list/required_gases = list(/datum/gas/hypernoblium, /datum/gas/antinoblium) - for(var/gas_path in required_gases) - if(has_destabilizing_crystal) - break // We have a destabilizing crystal, we're good - if(gas_comp[gas_path] < 0.4 || environment_total_moles < MOLE_PENALTY_THRESHOLD) - supermatter_cascade = FALSE - break + if(environment_total_moles < MOLE_PENALTY_THRESHOLD) + critical_gas_exceeded = FALSE + else + for(var/gas_path in required_gases) + if(gas_comp[gas_path] < 0.4) + critical_gas_exceeded = FALSE + break - if(admin_cascade) - supermatter_cascade = TRUE + if(critical_gas_exceeded) + return CASCADING_CRITICAL_GAS - return supermatter_cascade + return FALSE /obj/machinery/power/supermatter_crystal/proc/supermatter_pull(turf/center, pull_range = 3) playsound(center, 'sound/weapons/marauder.ogg', 100, TRUE, extrarange = pull_range - world.view) @@ -736,6 +761,10 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) pixel_x = -176 pixel_y = -176 +#undef CASCADING_ADMIN +#undef CASCADING_CRITICAL_GAS +#undef CASCADING_DESTAB_CRYSTAL + #undef BIKE #undef COIL #undef ROD diff --git a/code/modules/power/supermatter/supermatter_cascade_components.dm b/code/modules/power/supermatter/supermatter_cascade_components.dm index fae4dd5d9d1..6851760b6ac 100644 --- a/code/modules/power/supermatter/supermatter_cascade_components.dm +++ b/code/modules/power/supermatter/supermatter_cascade_components.dm @@ -10,11 +10,13 @@ anchored = TRUE resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF | FREEZE_PROOF light_power = 1 - light_range = 7 + light_range = 5 light_color = COLOR_VIVID_YELLOW move_resist = INFINITY ///All dirs we can expand to var/list/available_dirs = list(NORTH,SOUTH,EAST,WEST,UP,DOWN) + ///Handler that helps with properly killing mobs that the crystal grows over + var/datum/component/supermatter_crystal/sm_comp ///Cooldown on the expansion process COOLDOWN_DECLARE(sm_wall_cooldown) @@ -23,13 +25,14 @@ icon_state = "crystal_cascade_[rand(1,6)]" START_PROCESSING(SSsupermatter_cascade, src) - AddComponent(/datum/component/supermatter_crystal, null, null) + sm_comp = AddComponent(/datum/component/supermatter_crystal, null, null) playsound(src, 'sound/misc/cracking_crystal.ogg', 45, TRUE) available_dirs -= dir_to_remove var/turf/our_turf = get_turf(src) + if(our_turf) our_turf.opacity = FALSE @@ -39,7 +42,6 @@ return if(!available_dirs || available_dirs.len <= 0) - light_range = 0 return PROCESS_KILL COOLDOWN_START(src, sm_wall_cooldown, rand(0, 3 SECONDS)) @@ -53,9 +55,18 @@ return for(var/atom/movable/checked_atom as anything in next_turf) - if(!isliving(checked_atom) && !istype(checked_atom, /obj/cascade_portal)) - continue - qdel(checked_atom) + if(isliving(checked_atom)) + sm_comp.dust_mob(src, checked_atom, span_danger("\The [src] lunges out on [checked_atom], touching [checked_atom.p_them()]... \ + [checked_atom.p_their()] body begins to shine with a brilliant light before crystallizing from the inside out and joining \the [src]!"), + span_userdanger("The crystal mass lunges on you and hits you in the chest. As your vision is filled with a blinding light, you think to yourself \"Damn it.\"")) + else if(istype(checked_atom, /obj/cascade_portal)) + checked_atom.visible_message(span_userdanger("\The [checked_atom] screeches and closes away as it is hit by \a [src]! Too late!")) + playsound(get_turf(checked_atom), 'sound/magic/charge.ogg', 50, TRUE) + playsound(get_turf(checked_atom), 'sound/effects/supermatter.ogg', 50, TRUE) + qdel(checked_atom) + else if(isitem(checked_atom)) + playsound(get_turf(checked_atom), 'sound/effects/supermatter.ogg', 50, TRUE) + qdel(checked_atom) new /obj/crystal_mass(next_turf, get_dir(next_turf, src)) @@ -78,9 +89,13 @@ qdel(rip_u) return COMPONENT_CANCEL_ATTACK_CHAIN +/obj/crystal_mass/Destroy() + sm_comp = null + return ..() + /obj/cascade_portal name = "Bluespace Rift" - desc = "Your mind begins to bubble and ooze as it tries to comprehend what it sees." + desc = "Your mind begins to spin as it tries to comprehend what it sees." icon = 'icons/effects/224x224.dmi' icon_state = "reality" anchored = TRUE @@ -96,18 +111,9 @@ resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF | FREEZE_PROOF /obj/cascade_portal/Bumped(atom/movable/hit_object) - if(isliving(hit_object)) - hit_object.visible_message(span_danger("\The [hit_object] slams into \the [src] inducing a resonance... [hit_object.p_their()] body starts to glow and burst into flames before flashing into dust!"), - span_userdanger("You slam into \the [src] as your ears are filled with unearthly ringing. Your last thought is \"Oh, fuck.\""), - span_hear("You hear an unearthly noise as a wave of heat washes over you.")) - else if(isobj(hit_object) && !iseffect(hit_object)) - hit_object.visible_message(span_danger("\The [hit_object] smacks into \the [src] and rapidly flashes to ash."), null, - span_hear("You hear a loud crack as you are washed with a wave of heat.")) - else - return - - playsound(get_turf(src), 'sound/effects/supermatter.ogg', 50, TRUE) consume(hit_object) + new /obj/effect/particle_effect/sparks(loc) + playsound(loc, SFX_SPARKS, 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) /** * Proc to consume the objects colliding with the portal @@ -116,17 +122,30 @@ */ /obj/cascade_portal/proc/consume(atom/movable/consumed_object) if(isliving(consumed_object)) + consumed_object.visible_message(span_danger("\The [consumed_object] walks into \the [src]... \ + A blinding light covers [consumed_object.p_their()] body before disappearing completely!"), + span_userdanger("You walk into \the [src] as your body is washed with a powerful blue light. \ + You contemplate about this decision before landing face first onto the cold, hard floor."), + span_hear("You hear a loud crack as a distortion passes through you.")) + var/list/arrival_turfs = get_area_turfs(/area/centcom/central_command_areas/evacuation) - var/turf/arrival_turf = pick(arrival_turfs) + var/turf/arrival_turf + do + arrival_turf = pick_n_take(arrival_turfs) + while(!is_safe_turf(arrival_turf)) + var/mob/living/consumed_mob = consumed_object - if(consumed_mob.status_flags & GODMODE) - return message_admins("[key_name_admin(consumed_mob)] has entered [src] [ADMIN_JMP(src)].") investigate_log("was entered by [key_name(consumed_mob)].", INVESTIGATE_ENGINE) consumed_mob.forceMove(arrival_turf) consumed_mob.Paralyze(100) consumed_mob.adjustBruteLoss(30) - else if(consumed_object.flags_1 & SUPERMATTER_IGNORES_1) - return - else if(isobj(consumed_object)) + consumed_mob.flash_act(1, TRUE, TRUE) + + new /obj/effect/particle_effect/sparks(consumed_object) + playsound(consumed_object, SFX_SPARKS, 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) + else if(isitem(consumed_object)) + consumed_object.visible_message(span_danger("\The [consumed_object] smacks into \the [src] and disappears out of sight."), null, + span_hear("You hear a loud crack as a small distortion passes through you.")) + qdel(consumed_object) diff --git a/code/modules/power/supermatter/supermatter_delamination.dm b/code/modules/power/supermatter/supermatter_delamination.dm index 42d418d0d91..961d9b7defc 100644 --- a/code/modules/power/supermatter/supermatter_delamination.dm +++ b/code/modules/power/supermatter/supermatter_delamination.dm @@ -1,3 +1,6 @@ +///Minimum distance that a crystal mass must have from the rift +#define MIN_RIFT_SAFE_DIST 30 + /datum/supermatter_delamination ///Power amount of the SM at the moment of death var/supermatter_power = 0 @@ -170,76 +173,168 @@ * Setup for the cascade delamination */ /datum/supermatter_delamination/proc/start_supermatter_cascade() - SSshuttle.registerHostileEnvironment(src) + // buncha shuttle manipulation incoming + + // set timer to infinity, so shuttle never arrives + SSshuttle.emergency.setTimer(INFINITY) + // disallow shuttle recalls, so people cannot cheese the timer + SSshuttle.emergency_no_recall = TRUE + // set supermatter cascade to true, to prevent auto evacuation due to no way of calling the shuttle SSshuttle.supermatter_cascade = TRUE + // This logic is to keep uncalled shuttles uncalled + // In SSshuttle, there is not much of a way to prevent shuttle calls, unless we mess with admin panel vars + // SHUTTLE_STRANDED is different here, because it *can* block the shuttle from being called, however if we don't register a hostile + // environment, it gets unset immediately. Internally, it checks if the count of HEs is zero + // and that the shuttle is in stranded mode, then frees it with an announcement. + // This is a botched solution to a problem that could be solved with a small change in shuttle code, however- + if(SSshuttle.emergency.mode == SHUTTLE_IDLE) + SSshuttle.emergency.mode = SHUTTLE_STRANDED + SSshuttle.registerHostileEnvironment(src) + // set hijack completion timer to infinity, so that you cant prematurely end the round with a hijack + for(var/obj/machinery/computer/emergency_shuttle/console in GLOB.machines) + console.hijack_completion_flight_time_set = INFINITY + + for(var/mob/player as anything in GLOB.player_list) + if(!isdead(player)) + to_chat(player, span_boldannounce("Everything around you is resonating with a powerful energy. This can't be good.")) + SEND_SIGNAL(player, COMSIG_ADD_MOOD_EVENT, "cascade", /datum/mood_event/cascade) + SEND_SOUND(player, 'sound/magic/charge.ogg') + call_explosion() create_cascade_ambience() - pick_rift_location() warn_crew() + + var/rift_loc = pick_rift_location() new /obj/crystal_mass(supermatter_turf) + + var/list/mass_loc_candidates = GLOB.generic_event_spawns.Copy() + mass_loc_candidates.Remove(rift_loc) // this should now actually get rid of stalemates for(var/i in 1 to rand(4,6)) - new /obj/crystal_mass(get_turf(pick(GLOB.generic_event_spawns))) + var/list/loc_list = mass_loc_candidates.Copy() + var/mass_loc + do + mass_loc = pick_n_take(loc_list) + while(get_dist(mass_loc, rift_loc) < MIN_RIFT_SAFE_DIST) + new /obj/crystal_mass(get_turf(mass_loc)) + + SSsupermatter_cascade.cascade_initiated = TRUE /** * Adds a bit of spiciness to the cascade by breaking lights and turning emergency maint access on */ /datum/supermatter_delamination/proc/create_cascade_ambience() - break_lights_on_station() + if(SSsecurity_level.current_level != SEC_LEVEL_DELTA) + SSsecurity_level.set_level(SEC_LEVEL_DELTA) // skip the announcement and shuttle timer adjustment in set_security_level() make_maint_all_access() + break_lights_on_station() /** * Picks a random location for the rift + * Returns: ref to rift location */ /datum/supermatter_delamination/proc/pick_rift_location() - var/turf/rift_location = get_turf(pick(GLOB.generic_event_spawns)) - cascade_rift = new /obj/cascade_portal(rift_location) + var/rift_spawn = pick(GLOB.generic_event_spawns) + var/turf/rift_turf = get_turf(rift_spawn) + cascade_rift = new /obj/cascade_portal(rift_turf) + message_admins("Exit rift created at [get_area_name(rift_turf)]. [ADMIN_JMP(cascade_rift)]") + log_game("Bluespace Exit Rift was created at [get_area_name(rift_turf)].") + cascade_rift.investigate_log("created at [get_area_name(rift_turf)].", INVESTIGATE_ENGINE) RegisterSignal(cascade_rift, COMSIG_PARENT_QDELETING, .proc/deleted_portal) + return rift_spawn /** * Warns the crew about the cascade start and the rift location */ /datum/supermatter_delamination/proc/warn_crew() - for(var/mob/player as anything in GLOB.alive_player_list) - to_chat(player, span_boldannounce("You feel a strange presence in the air around you. You feel unsafe.")) + priority_announce("A Type-C resonance shift event has occurred in your sector. Scans indicate local oscillation flux affecting spatial and gravitational substructure. \ + Multiple resonance hotspots have formed. Please standby.", "Nanotrasen Star Observation Association", ANNOUNCER_SPANOMALIES) - priority_announce("Unknown harmonance affecting local spatial substructure, all nearby matter is starting to crystallize.", "Central Command Higher Dimensional Affairs", 'sound/misc/bloblarm.ogg') - priority_announce("There's been a sector-wide electromagnetic pulse. All of our systems are heavily damaged, including those required for emergency shuttle navigation. \ - We can only reasonably conclude that a supermatter cascade has been initiated on or near your station. \ - Evacuation is no longer possible by conventional means; however, we managed to open a rift near the [get_area_name(cascade_rift)]. \ - All personnel are hereby advised to enter the rift using all means available. Retrieval of survivors will be conducted upon recovery of necessary facilities. \ - Good l\[\[###!!!-") + if(SSshuttle.emergency.mode != SHUTTLE_STRANDED) + addtimer(CALLBACK(src, .proc/announce_shuttle_gone), 2 SECONDS) + addtimer(CALLBACK(src, .proc/announce_beginning), 5 SECONDS) - addtimer(CALLBACK(src, .proc/delta), 10 SECONDS) - +/** + * Logs the deletion of the bluespace rift, and starts countdown to the end of the round. + */ /datum/supermatter_delamination/proc/deleted_portal() SIGNAL_HANDLER + message_admins("[cascade_rift] deleted at [get_area_name(cascade_rift.loc)]. [ADMIN_JMP(cascade_rift.loc)]") + log_game("[cascade_rift] was deleted.") + cascade_rift.investigate_log("was deleted.", INVESTIGATE_ENGINE) - priority_announce("The rift has been destroyed, we can no longer help you...", "Warning", 'sound/misc/bloblarm.ogg') + priority_announce("[Gibberish("The rift has been destroyed, we can no longer help you.", FALSE, 5)]") + addtimer(CALLBACK(src, .proc/announce_gravitation_shift), 25 SECONDS) addtimer(CALLBACK(src, .proc/last_message), 50 SECONDS) - - addtimer(CALLBACK(src, .proc/the_end), 1 MINUTES) + if(SSshuttle.emergency.mode != SHUTTLE_ESCAPE) // if the shuttle is enroute to centcom, we let the shuttle end the round + addtimer(CALLBACK(src, .proc/the_end), 1 MINUTES) /** - * Increases the security level to the highest level + * Announces the halfway point to the end. */ -/datum/supermatter_delamination/proc/delta() - set_security_level("delta") - sound_to_playing_players('sound/misc/notice1.ogg') +/datum/supermatter_delamination/proc/announce_gravitation_shift() + priority_announce("Reports indicate formation of crystalline seeds following resonance shift event. \ + Rapid expansion of crystal mass proportional to rising gravitational force. \ + Matter collapse due to gravitational pull foreseeable.", + "Nanotrasen Star Observation Association") /** - * Announces the last message to the station + * This proc manipulates the shuttle if it's enroute to centcom, to remain in hyperspace. Otherwise, it just plays an announcement if + * the shuttle was in any other state except stranded (idle) + */ +/datum/supermatter_delamination/proc/announce_shuttle_gone() + // say goodbye to that shuttle of yours + if(SSshuttle.emergency.mode != SHUTTLE_ESCAPE) + priority_announce("Fatal error occurred in emergency shuttle uplink during transit. Unable to reestablish connection.", + "Emergency Shuttle Uplink Alert", 'sound/misc/announce_dig.ogg') + else + // except if you are on it already, then you are safe c: + minor_announce("ERROR: Corruption detected in navigation protocols. Connection with Transponder #XCC-P5831-ES13 lost. \ + Backup exit route protocol decrypted. Calibrating route...", + "Emergency Shuttle", TRUE) // wait out until the rift on the station gets destroyed and the final message plays + var/list/mobs = mobs_in_area_type(list(/area/shuttle/escape)) + for(var/mob/living/mob as anything in mobs) // emulate mob/living/lateShuttleMove() behaviour + if(mob.buckled) + continue + if(mob.client) + shake_camera(mob, 3 SECONDS * 0.25, 1) + mob.Paralyze(3 SECONDS, TRUE) + +/** + * Announces the last message to the station, frees the shuttle from purgatory if applicable */ /datum/supermatter_delamination/proc/last_message() - priority_announce("To the remaining survivors of [station_name()], We're sorry.", " ", 'sound/misc/bloop.ogg') + priority_announce("[Gibberish("All attempts at evacuation have now ceased, and all assets have been retrieved from your sector.\n \ + To the remaining survivors of [station_name()], farewell.", FALSE, 5)]") + + if(SSshuttle.emergency.mode == SHUTTLE_ESCAPE) + // special message for hijacks + var/shuttle_msg = "Navigation protocol set to [SSshuttle.emergency.is_hijacked() ? "\[ERROR\]" : "backup route"]. \ + Reorienting bluespace vessel to exit vector. ETA 15 seconds." + // garble the special message + if(SSshuttle.emergency.is_hijacked()) + shuttle_msg = Gibberish(shuttle_msg, TRUE, 15) + minor_announce(shuttle_msg, "Emergency Shuttle", TRUE) + SSshuttle.emergency.setTimer(15 SECONDS) + +/** + * Announce detail about the event, as well as rift location + */ +/datum/supermatter_delamination/proc/announce_beginning() + priority_announce("We have been hit by a sector-wide electromagnetic pulse. All of our systems are heavily damaged, including those \ + required for shuttle navigation. We can only reasonably conclude that a supermatter cascade is occurring on or near your station.\n\n\ + Evacuation is no longer possible by conventional means; however, we managed to open a rift near the [get_area_name(cascade_rift)]. \ + All personnel are hereby required to enter the rift by any means available.\n\n\ + [Gibberish("Retrieval of survivors will be conducted upon recovery of necessary facilities.", FALSE, 5)] \ + [Gibberish("Good luck--", FALSE, 25)]") /** * Ends the round */ /datum/supermatter_delamination/proc/the_end() SSticker.news_report = SUPERMATTER_CASCADE - SSticker.force_ending = 1 + SSticker.force_ending = TRUE /** * Break the lights on the station, have 35% of them be set to emergency @@ -247,7 +342,8 @@ /datum/supermatter_delamination/proc/break_lights_on_station() for(var/obj/machinery/light/light_to_break in GLOB.machines) if(prob(35)) - light_to_break.emergency_mode = TRUE - light_to_break.update_appearance() + light_to_break.set_major_emergency_light() continue light_to_break.break_light_tube() + +#undef MIN_RIFT_SAFE_DIST diff --git a/code/modules/power/supermatter/supermatter_hit_procs.dm b/code/modules/power/supermatter/supermatter_hit_procs.dm index 4ec79dcd555..4824e69168b 100644 --- a/code/modules/power/supermatter/supermatter_hit_procs.dm +++ b/code/modules/power/supermatter/supermatter_hit_procs.dm @@ -89,20 +89,24 @@ var/obj/item/destabilizing_crystal/destabilizing_crystal = item if(!anomaly_event) - to_chat(user, span_warning("You can't use \the [destabilizing_crystal] on a Shard.")) + to_chat(user, span_warning("You can't use \the [destabilizing_crystal] on \a [name].")) return if(get_integrity_percent() < SUPERMATTER_CASCADE_PERCENT) - to_chat(user, span_warning("You can only apply \the [destabilizing_crystal] to a Supermatter src that is at least [SUPERMATTER_CASCADE_PERCENT]% intact.")) + to_chat(user, span_warning("You can only apply \the [destabilizing_crystal] to \a [name] that is at least [SUPERMATTER_CASCADE_PERCENT]% intact.")) return - to_chat(user, span_notice("You begin to attach \the [destabilizing_crystal] to \the [src]...")) + to_chat(user, span_warning("You begin to attach \the [destabilizing_crystal] to \the [src]...")) if(do_after(user, 3 SECONDS, src)) - to_chat(user, span_notice("You attach \the [destabilizing_crystal] to \the [src].")) + message_admins("[ADMIN_LOOKUPFLW(user)] attached [destabilizing_crystal] to the supermatter at [ADMIN_VERBOSEJMP(src)]") + log_game("[key_name(user)] attached [destabilizing_crystal] to the supermatter at [AREACOORD(src)]") + investigate_log("[key_name(user)] attached [destabilizing_crystal] to a supermatter crystal.", INVESTIGATE_ENGINE) + to_chat(user, span_danger("\The [destabilizing_crystal] snaps onto \the [src].")) has_destabilizing_crystal = TRUE cascade_initiated = TRUE damage += 100 matter_power += 500 + addtimer(CALLBACK(src, .proc/announce_incoming_cascade), 2 MINUTES) qdel(destabilizing_crystal) return diff --git a/code/modules/power/supermatter/supermatter_process.dm b/code/modules/power/supermatter/supermatter_process.dm index bf20147c9de..1633fcee76d 100644 --- a/code/modules/power/supermatter/supermatter_process.dm +++ b/code/modules/power/supermatter/supermatter_process.dm @@ -69,19 +69,28 @@ //handles temperature increase and gases made by the crystal temperature_gas_production(env, removed) - if(check_cascade_requirements(anomaly_event)) + var/cascading = check_cascade_requirements() + if(cascading) + if(!cascade_initiated) + addtimer(CALLBACK(src, .proc/announce_incoming_cascade), 2 MINUTES, TIMER_UNIQUE | TIMER_OVERRIDE) + log_game("[src] has begun a cascade.") + message_admins("[src] has begun a cascade, reasons: [cascading]. [ADMIN_JMP(src)]") + investigate_log("has begun a cascade, reasons: [cascading].", INVESTIGATE_ENGINE) cascade_initiated = TRUE if(!warp) warp = new(src) vis_contents += warp animate(warp, time = 1, transform = matrix().Scale(0.5,0.5)) animate(time = 9, transform = matrix()) - else if(warp) vis_contents -= warp - warp = null - cascade_initiated = FALSE + QDEL_NULL(warp) + if(cascade_initiated) + log_game("[src] has stopped its cascade.") + message_admins("[src] has stopped its cascade. [ADMIN_JMP(src)]") + investigate_log("has stopped its cascade.", INVESTIGATE_ENGINE) + cascade_initiated = FALSE //handles hallucinations and the presence of a psychiatrist psychological_examination() @@ -432,19 +441,25 @@ if(combined_gas > MOLE_PENALTY_THRESHOLD) radio.talk_into(src, "Warning: Critical coolant mass reached.", engineering_channel) - if(check_cascade_requirements(anomaly_event)) + if(check_cascade_requirements()) var/channel_to_talk_to = damage > emergency_point ? common_channel : engineering_channel - radio.talk_into(src, "DANGER: RESONANCE CASCADE INITIATED.", channel_to_talk_to) + radio.talk_into(src, "DANGER: HYPERSTRUCTURE OSCILLATION FREQUENCY OUT OF BOUNDS.", channel_to_talk_to) for(var/mob/victim as anything in GLOB.player_list) var/list/messages = list( - "You feel a strange presence in the air coming from engineering.", - "Something is wrong, there are weird sounds coming from engineering.", - "You don't like the smell of the SM.", - "The SM is emitting strange noises.", - "Crystals sounds are echoing through the station.", + "Space seems to be shifting around you...", + "You hear a high-pitched ringing sound.", + "You feel tingling going down your back.", + "Something feels very off.", + "A drowning sense of dread washes over you." ) - to_chat(victim, span_boldannounce(pick(messages))) + to_chat(victim, span_danger(pick(messages))) //Boom (Mind blown) if(damage > explosion_point) countdown() + +/obj/machinery/power/supermatter_crystal/proc/announce_incoming_cascade() + if(check_cascade_requirements()) + priority_announce("Attention: Long range anomaly scans indicate abnormal quantities of harmonic flux originating from \ + a subject within [station_name()], a resonance collapse may occur.", + "Nanotrasen Star Observation Association") diff --git a/code/modules/shuttle/emergency.dm b/code/modules/shuttle/emergency.dm index 526b4bfd953..833637f052a 100644 --- a/code/modules/shuttle/emergency.dm +++ b/code/modules/shuttle/emergency.dm @@ -246,7 +246,9 @@ if(HIJACKED) msg = "SYSTEM OVERRIDE - Resetting course to \[[scramble_message_replace_chars("###########", 100)]\] \ ([scramble_message_replace_chars("#######", 100)]/[scramble_message_replace_chars("#######", 100)]/[scramble_message_replace_chars("#######", 100)]) \ - {AUTH - ROOT (uid: 0)}.[SSshuttle.emergency.mode == SHUTTLE_ESCAPE ? "Diverting from existing route - Bluespace exit in [hijack_completion_flight_time_set/10] seconds." : ""]" + {AUTH - ROOT (uid: 0)}.\ + [SSshuttle.emergency.mode == SHUTTLE_ESCAPE ? "Diverting from existing route - Bluespace exit in \ + [hijack_completion_flight_time_set >= INFINITY ? "[scramble_message_replace_chars("\[ERROR\]")]" : hijack_completion_flight_time_set/10] seconds." : ""]" minor_announce(scramble_message_replace_chars(msg, replaceprob = 10), "Emergency Shuttle", TRUE) /obj/machinery/computer/emergency_shuttle/emag_act(mob/user)