From 763a10d1cc44c91720101d422d8709ad1aa0644d Mon Sep 17 00:00:00 2001 From: distributivgesetz Date: Thu, 16 Jun 2022 17:22:22 +0200 Subject: [PATCH] Resonance cascade polishening, bugfixes and better logging (#67488) This PR rewrites almost all messages related to cascade events. Some messages felt kinda clunky to read or could have been written better. Overall, the new messages add to the experience as a cascade being a terrifying event in a way that I felt the old ones missed, and they make the event feel overall a lot sharper. While looking at the resonance cascade code, I noticed that there a lot of stuff about cascades in the air which was not touched on. So, as I do, this PR evolved into a polish and roundup PR for cascades. There was a lot of stuff still hanging out relating to the event, and although the big backend of it sits, there was still a bit left to be completed. Therefore this PR deserves more the title of the "Resonance cascade POLISHENING" instead of the "REFLAVAHRING". But yeah, you ever go on a massive tangent before? --- code/__HELPERS/areas.dm | 13 ++ .../processing/supermatter_cascade.dm | 3 + code/controllers/subsystem/ticker.dm | 2 +- .../mood_events/generic_negative_events.dm | 5 + code/game/gamemodes/game_mode.dm | 2 + .../items/devices/destabilizing_crystal.dm | 3 +- .../traitor/equipment/Malf_Modules.dm | 2 +- .../final_objective/supermatter_cascade.dm | 6 +- code/modules/power/apc/apc_main.dm | 4 +- code/modules/power/lighting/light.dm | 67 +++++--- .../power/lighting/light_mapping_helpers.dm | 4 +- code/modules/power/supermatter/supermatter.dm | 73 ++++++--- .../supermatter_cascade_components.dm | 67 +++++--- .../supermatter/supermatter_delamination.dm | 152 ++++++++++++++---- .../supermatter/supermatter_hit_procs.dm | 12 +- .../power/supermatter/supermatter_process.dm | 39 +++-- code/modules/shuttle/emergency.dm | 4 +- 17 files changed, 332 insertions(+), 126 deletions(-) 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 f9c77b67d7f..55594cab8d5 100755 --- a/code/controllers/subsystem/ticker.dm +++ b/code/controllers/subsystem/ticker.dm @@ -598,7 +598,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]." if(news_message) send2otherserver(news_source, news_message,"News_Report") diff --git a/code/datums/mood_events/generic_negative_events.dm b/code/datums/mood_events/generic_negative_events.dm index f871351e1e9..150e94336d0 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 7e177775f82..0e17257274f 100644 --- a/code/modules/antagonists/traitor/equipment/Malf_Modules.dm +++ b/code/modules/antagonists/traitor/equipment/Malf_Modules.dm @@ -757,7 +757,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 122867b4718..4d08c8ac496 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) @@ -110,7 +116,7 @@ switch(status) // set icon_states if(LIGHT_OK) var/area/local_area = get_area(src) - if(emergency_mode || (local_area?.fire)) + if(low_power_mode || major_emergency || (local_area?.fire)) icon_state = "[base_state]_emergency" else icon_state = "[base_state]" @@ -128,7 +134,7 @@ return var/area/local_area = get_area(src) - if(emergency_mode || (local_area?.fire)) + if(low_power_mode || major_emergency || (local_area?.fire)) . += mutable_appearance(overlay_icon, "[base_state]_emergency") return if(nightshift_enabled) @@ -163,7 +169,7 @@ switch(status) if(LIGHT_BROKEN,LIGHT_BURNED,LIGHT_EMPTY) on = FALSE - emergency_mode = FALSE + low_power_mode = FALSE if(on) var/brightness_set = brightness var/power_set = bulb_power @@ -172,12 +178,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++ @@ -196,7 +205,7 @@ ) 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 @@ -239,7 +248,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() @@ -407,7 +416,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 @@ -422,9 +431,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 @@ -449,8 +458,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 @@ -523,6 +532,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 5eef6bb898f..18605d0e533 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) @@ -431,7 +437,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() @@ -442,11 +448,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 @@ -454,18 +471,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) @@ -476,29 +496,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) @@ -728,6 +753,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 dd605c1df0a..4e9b21a2e5b 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() @@ -429,19 +438,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 4ee761e74dd..84c3c955e14 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)