diff --git a/code/__DEFINES/achievements.dm b/code/__DEFINES/achievements.dm index 10121577466..d533e0feab2 100644 --- a/code/__DEFINES/achievements.dm +++ b/code/__DEFINES/achievements.dm @@ -46,6 +46,7 @@ #define MEDAL_SPRINGLOCK "The Man Inside the Modsuit" #define MEDAL_HEALTHY "Heart Healthy" #define MEDAL_GODS_WRATH "God's Wrath" +#define MEDAL_EARTHQUAKE_VICTIM "Earthquake Victim" //Skill medal hub IDs #define MEDAL_LEGENDARY_MINER "Legendary Miner" diff --git a/code/__DEFINES/turfs.dm b/code/__DEFINES/turfs.dm index 659e1e8b646..b0a9e52b486 100644 --- a/code/__DEFINES/turfs.dm +++ b/code/__DEFINES/turfs.dm @@ -105,3 +105,8 @@ #define PATTERN_RANDOM "random" /// Generate rainbow color turf decals #define PATTERN_RAINBOW "rainbow" + +/** + * Finds the midpoint of two given turfs. + */ +#define TURF_MIDPOINT(a, b) (locate(((a.x + b.x) * 0.5), (a.y + b.y) * 0.5, (a.z + b.z) * 0.5)) diff --git a/code/datums/achievements/misc_achievements.dm b/code/datums/achievements/misc_achievements.dm index 55b5d70cdf9..fc8e7b15337 100644 --- a/code/datums/achievements/misc_achievements.dm +++ b/code/datums/achievements/misc_achievements.dm @@ -223,3 +223,9 @@ desc = "Did you think you could get away with defiling the word of God?" database_id = MEDAL_GODS_WRATH icon = "godswrath" + +/datum/award/achievement/misc/earthquake_victim + name = "A Nasty Fall" + desc = "...And the earth opened its mouth and swallowed them and their station- all the HOP's men and all their possessions." + database_id = MEDAL_EARTHQUAKE_VICTIM + icon = "earthquake" diff --git a/code/game/gamemodes/dynamic/dynamic_unfavorable_situation.dm b/code/game/gamemodes/dynamic/dynamic_unfavorable_situation.dm index 584d80c260e..1be6511433c 100644 --- a/code/game/gamemodes/dynamic/dynamic_unfavorable_situation.dm +++ b/code/game/gamemodes/dynamic/dynamic_unfavorable_situation.dm @@ -60,6 +60,7 @@ /// Filter the below list by which events can actually run on this map /datum/game_mode/dynamic/proc/generate_unfavourable_events() var/static/list/unfavorable_random_events = list( + /datum/round_event_control/earthquake, /datum/round_event_control/immovable_rod, /datum/round_event_control/meteor_wave, /datum/round_event_control/portal_storm_syndicate, diff --git a/code/modules/events/earthquake.dm b/code/modules/events/earthquake.dm new file mode 100644 index 00000000000..11ade10f10e --- /dev/null +++ b/code/modules/events/earthquake.dm @@ -0,0 +1,176 @@ +///Earthquake random event. +///Draws a curve of turfs between a high and low point. These turfs will shake and eventually "collapse", forming a cut in the station that drops to the z-level below. +///Much of the actual structural damage is done through the explosions subsystem. Objects, machines, and especially people +///that aren't moved out of the epicenter area (indicated by the wobbly tiles) will not just be thrown down a z-level, but also be destroyed/maimed in the process. +/datum/round_event_control/earthquake + name = "Chasmic Earthquake" + description = "Causes an earthquake, demolishing anything caught in the fault." + typepath = /datum/round_event/earthquake + category = EVENT_CATEGORY_ENGINEERING + min_players = 20 + max_occurrences = 3 + earliest_start = 35 MINUTES + weight = 7 + min_wizard_trigger_potency = 3 + max_wizard_trigger_potency = 7 + map_flags = EVENT_PLANETARY_ONLY + +/datum/round_event_control/earthquake/can_spawn_event(players_amt, allow_magic) + . = ..() + if(!.) + return . + + if(!length(GLOB.generic_event_spawns)) + return FALSE + +/datum/round_event/earthquake + start_when = 1 + announce_when = 3 + end_when = 25 + announce_chance = 25 + ///The chosen location and center of our earthquake. + var/turf/epicenter + ///A list of turfs that will be damaged by this event. + var/list/turfs_to_shred + ///A list of turfs directly under turfs_to_shred, for creating a proper chasm to the floor below. + var/list/underbelly = list() + ///The edges of our fault line, to recieve light damage. + var/list/edges = list() + +/datum/round_event/earthquake/setup() + epicenter = get_turf(pick(GLOB.generic_event_spawns)) + if(!epicenter) + message_admins("Earthquake event failed to find a turf! generic_event_spawn landmarks may be absent or bugged. Aborting...") + return + + // Give a bit of variance so our epicenter isn't always on the landmark. + epicenter = locate(epicenter.x + rand(-10, 10), epicenter.y + rand(-10, 10), epicenter.z) + + message_admins("An earthquake event is about to strike the [get_area_name(epicenter)][ADMIN_JMP(epicenter)].") + + // Picks two points generally opposite from each other + var/turf/fracture_point_high = locate(epicenter.x + rand(4, 10), epicenter.y + rand(4, 8), epicenter.z) + var/turf/fracture_point_low = locate(epicenter.x - rand(4, 10), epicenter.y - rand(4, 8), epicenter.z) + + turfs_to_shred = block(fracture_point_high, fracture_point_low) + + // Now, we filter out some of the points that are a certain distance away from a rough approximation of the fault line. + // This should create a pattern more akin to a fracture in the ground, rather than a rectangle-shaped crater of destroyed ground. + var/turf/high_midpoint = TURF_MIDPOINT(fracture_point_high, epicenter) + var/turf/low_midpoint = TURF_MIDPOINT(fracture_point_low, epicenter) + + // We populate a list with the midpoints and midpoints of midpoints to create a rough line of turfs to compare distances against. + var/list/turfs_to_compare = list( + fracture_point_high, + fracture_point_low, + high_midpoint, + low_midpoint, + TURF_MIDPOINT(fracture_point_high, high_midpoint), + TURF_MIDPOINT(fracture_point_low, low_midpoint), + TURF_MIDPOINT(high_midpoint, epicenter), + TURF_MIDPOINT(low_midpoint, epicenter), + ) + + // Find the shortest distance between each turf in the list and the rough fault line we've just established + for(var/turf/turf_to_check in turfs_to_shred) + var/nearest_distance = get_dist(turf_to_check, epicenter) + + for(var/turf/turf_to_compare in turfs_to_compare) + nearest_distance = min(get_dist(turf_to_check, turf_to_compare), nearest_distance) + + // If the turf is too far from any point on our fault line estimate, we remove it. If it's on the edge, we lightly damage it + if(nearest_distance > 2) + if(nearest_distance == 3) + edges += turf_to_check + turfs_to_shred -= turf_to_check + + // Grab a list of turfs below the ones we're going to destroy. + // If we're at the bottom layer, it will just tear up the flooring a bunch (exposing it to LAVA). + for(var/turf/turf_to_quake in turfs_to_shred) + underbelly += SSmapping.get_turf_below(turf_to_quake) + +/datum/round_event/earthquake/announce(fake) + priority_announce("Planetary monitoring systems indicate a devastating seismic event in the near future.", "Seismic Report") + +/datum/round_event/earthquake/start() + notify_ghosts("The earthquake's epicenter has been located: [get_area_name(epicenter)]!", source = epicenter, header = "Rumble Rumble Rumble!") + +/datum/round_event/earthquake/tick() + if(ISMULTIPLE(activeFor, 5)) + for(var/turf/turf_to_quake in turfs_to_shred) + turf_to_quake.Shake(pixelshiftx = 0.1, pixelshifty = 0.1, duration = 1 SECONDS) + + if(ISMULTIPLE(activeFor, 10)) + for(var/mob/earthquake_witness as anything in GLOB.player_list) + if(!is_station_level(earthquake_witness.z)) + continue + shake_camera(earthquake_witness, 1 SECONDS, 1 + (activeFor % 10)) + earthquake_witness.playsound_local( + earthquake_witness, + pick( + 'sound/misc/earth_rumble_distant1.ogg', + 'sound/misc/earth_rumble_distant2.ogg', + 'sound/misc/earth_rumble_distant3.ogg', + 'sound/misc/earth_rumble_distant4.ogg', + ), + 75, + ) + + for(var/turf/turf_to_quake in underbelly) + turf_to_quake.Shake(pixelshiftx = 0.1, pixelshifty = 0.1, duration = 1 SECONDS) + + if(activeFor == end_when - 2) + for(var/turf/turf_to_quake in turfs_to_shred) + turf_to_quake.Shake(pixelshiftx = 0.5, pixelshifty = 0.5, duration = 1 SECONDS) + for(var/mob/living/quake_victim in turf_to_quake) + quake_victim.Knockdown(7 SECONDS) + quake_victim.Paralyze(5 SECONDS) + to_chat(quake_victim, span_warning("The ground quakes violently beneath you, throwing you off your feet!")) + + for(var/turf/turf_to_quake in underbelly) + turf_to_quake.Shake(pixelshiftx = 0.5, pixelshifty = 0.5, duration = 1 SECONDS) + for(var/mob/living/carbon/quake_victim in turf_to_quake) + to_chat(quake_victim, span_warning("Damn, I wonder what that rumbling noise is?")) ///You're about to find out + + // Step one of the destruction, which clears natural tiles out from the underbelly and does a bit of initial damage to the topside. + if(activeFor == end_when - 1) + for(var/turf/turf_to_shred in turfs_to_shred) + if(prob(90)) + SSexplosions.lowturf += turf_to_shred + for(var/turf/turf_to_clear in underbelly) + if(ismineralturf(turf_to_clear)) + var/turf/closed/mineral/rock_to_clear = turf_to_clear + rock_to_clear.gets_drilled(give_exp = FALSE) + for(var/turf/turf_to_quake in edges) + turf_to_quake.Shake(pixelshiftx = 0.5, pixelshifty = 0.5, duration = 1 SECONDS) + playsound(epicenter, 'sound/misc/metal_creak.ogg', 125, TRUE) + +/datum/round_event/earthquake/end() + playsound(epicenter, 'sound/misc/earth_rumble.ogg', 125) + for(var/mob/earthquake_witness as anything in GLOB.player_list) + if(!is_station_level(earthquake_witness.z) || !is_mining_level(earthquake_witness.z)) + continue + shake_camera(earthquake_witness, 2 SECONDS, 4) + earthquake_witness.playsound_local(earthquake_witness, 'sound/effects/explosionfar.ogg', 75) + + // Step two of the destruction, which detonates the turfs in the earthquake zone. There is no actual explosion, meaning stuff around the earthquake zone is perfectly safe. + // All turfs, and everything else that IS in the earthquake zone, however, will behave as if it were bombed. + // If you are caught in the earthquake zone, you will not only die but probably be torn apart in the process. + for(var/turf/turf_to_shred in turfs_to_shred) + if(prob(10)) + SSexplosions.medturf += turf_to_shred + else + SSexplosions.highturf += turf_to_shred + + if(isasteroidturf(turf_to_shred) && prob(95)) + turf_to_shred.ScrapeAway(flags = CHANGETURF_INHERIT_AIR) + + for(var/mob/living/carbon/quake_victim in turf_to_shred) + if(quake_victim.client) + quake_victim.client.give_award(/datum/award/achievement/misc/earthquake_victim, quake_victim) + + for(var/turf/edge_to_damage in edges) + if(prob(25)) + SSexplosions.medturf += edge_to_damage + else + SSexplosions.lowturf += edge_to_damage diff --git a/icons/ui_icons/achievements/achievements.dmi b/icons/ui_icons/achievements/achievements.dmi index a20e0fbecff..ba4ee706431 100644 Binary files a/icons/ui_icons/achievements/achievements.dmi and b/icons/ui_icons/achievements/achievements.dmi differ diff --git a/sound/attributions.txt b/sound/attributions.txt index 49a808da159..b273b7d9278 100644 --- a/sound/attributions.txt +++ b/sound/attributions.txt @@ -60,7 +60,14 @@ https://freesound.org/people/ScottFerguson1/sounds/132846/ parry.ogg is taken from loudernoises "Sword Clash" which is licensed under CC Attributions 0, which is mixed with a genhit.ogg https://freesound.org/people/loudernoises/sounds/334169/ +earth_rumble.ogg is adapted from uagadugu's "Cracking Earthquake (cracking soil, cracking stone)" (CC 0) +https://freesound.org/people/uagadugu/sounds/222521/ +earth_rumble_distant1, earth_rumble_distant2, earth_rumble_distant3, and earth_rumble_distant4 are adapted from metrostock99's "EARTHQUAKE OR DISTANT SPACE SHUTTLE RUMBLE.WAV" (CC 0) +https://freesound.org/people/metrostock99/sounds/203281/ + +metal_creak.ogg is adapted from BloodPixelHero's "Metal door creak" under CC Attribution 4.0 +https://freesound.org/people/BloodPixelHero/sounds/585323/ revolutionary_tide.ogg is adapted from several audio samples: diff --git a/sound/misc/earth_rumble.ogg b/sound/misc/earth_rumble.ogg new file mode 100644 index 00000000000..df1241d6896 Binary files /dev/null and b/sound/misc/earth_rumble.ogg differ diff --git a/sound/misc/earth_rumble_distant1.ogg b/sound/misc/earth_rumble_distant1.ogg new file mode 100644 index 00000000000..c6f37b27f3c Binary files /dev/null and b/sound/misc/earth_rumble_distant1.ogg differ diff --git a/sound/misc/earth_rumble_distant2.ogg b/sound/misc/earth_rumble_distant2.ogg new file mode 100644 index 00000000000..1a0777d8c7c Binary files /dev/null and b/sound/misc/earth_rumble_distant2.ogg differ diff --git a/sound/misc/earth_rumble_distant3.ogg b/sound/misc/earth_rumble_distant3.ogg new file mode 100644 index 00000000000..aad0d0645a3 Binary files /dev/null and b/sound/misc/earth_rumble_distant3.ogg differ diff --git a/sound/misc/earth_rumble_distant4.ogg b/sound/misc/earth_rumble_distant4.ogg new file mode 100644 index 00000000000..23b4ae86c45 Binary files /dev/null and b/sound/misc/earth_rumble_distant4.ogg differ diff --git a/sound/misc/metal_creak.ogg b/sound/misc/metal_creak.ogg new file mode 100644 index 00000000000..669594a0bfe Binary files /dev/null and b/sound/misc/metal_creak.ogg differ diff --git a/tgstation.dme b/tgstation.dme index 955eacee667..a9a47df4d6b 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -3364,6 +3364,7 @@ #include "code\modules\events\creep_awakening.dm" #include "code\modules\events\disease_outbreak.dm" #include "code\modules\events\dust.dm" +#include "code\modules\events\earthquake.dm" #include "code\modules\events\electrical_storm.dm" #include "code\modules\events\fake_virus.dm" #include "code\modules\events\false_alarm.dm"