Merge pull request #5791 from Neerti/looping_sounds

Adds Various Sounds, Looping Sound System
This commit is contained in:
Anewbe
2018-12-31 09:01:31 -06:00
committed by GitHub
133 changed files with 768 additions and 80 deletions
@@ -74,6 +74,18 @@ var/list/_client_preferences_by_type
preference_mob << sound(null, repeat = 0, wait = 0, volume = 0, channel = 1)
preference_mob << sound(null, repeat = 0, wait = 0, volume = 0, channel = 2)
/datum/client_preference/weather_sounds
description ="Weather sounds"
key = "SOUND_WEATHER"
enabled_description = "Audible"
disabled_description = "Silent"
/datum/client_preference/supermatter_hum
description ="Supermatter hum"
key = "SOUND_SUPERMATTER"
enabled_description = "Audible"
disabled_description = "Silent"
/datum/client_preference/ghost_ears
description ="Ghost ears"
key = "CHAT_GHOSTEARS"
@@ -143,7 +143,7 @@
toggle_preference(pref_path)
to_chat(src,"You will [ (is_preference_enabled(pref_path)) ? "now" : " no longer"] hear MIDIs from admins.")
to_chat(src,"You will [ (is_preference_enabled(pref_path)) ? "now" : "no longer"] hear MIDIs from admins.")
prefs.save_preferences()
@@ -158,11 +158,41 @@
toggle_preference(pref_path)
to_chat(src,"You will [ (is_preference_enabled(pref_path)) ? "now" : " no longer"] hear ambient noise.")
to_chat(src,"You will [ (is_preference_enabled(pref_path)) ? "now" : "no longer"] hear ambient noise.")
prefs.save_preferences()
feedback_add_details("admin_verb","TBeSpecial") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
feedback_add_details("admin_verb","TAmbience") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/verb/toggle_weather_sounds()
set name = "Toggle Weather Sounds"
set category = "Preferences"
set desc = "Toggles the ability to hear weather sounds while on a planet."
var/pref_path = /datum/client_preference/weather_sounds
toggle_preference(pref_path)
to_chat(src,"You will [ (is_preference_enabled(pref_path)) ? "now" : "no longer"] hear weather sounds.")
prefs.save_preferences()
feedback_add_details("admin_verb","TWeatherSounds") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/verb/toggle_supermatter_hum()
set name = "Toggle SM Hum" // Avoiding using the full 'Supermatter' name to not conflict with the Setup-Supermatter adminverb.
set category = "Preferences"
set desc = "Toggles the ability to hear supermatter hums."
var/pref_path = /datum/client_preference/supermatter_hum
toggle_preference(pref_path)
to_chat(src,"You will [ (is_preference_enabled(pref_path)) ? "now" : "no longer"] hear a hum from the supermatter.")
prefs.save_preferences()
feedback_add_details("admin_verb","TSupermatterHum") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/verb/toggle_be_special(role in be_special_flags)
set name = "Toggle SpecialRole Candidacy"
@@ -35,6 +35,10 @@
cooking_obj = null
return ..()
/obj/machinery/cooker/proc/set_cooking(new_setting)
cooking = new_setting
icon_state = new_setting ? on_icon : off_icon
/obj/machinery/cooker/examine()
..()
if(cooking_obj && Adjacent(usr))
@@ -113,7 +117,7 @@
user.visible_message("<span class='notice'>\The [user] puts \the [I] into \the [src].</span>")
cooking_obj = I
cooking_obj.forceMove(src)
cooking = 1
set_cooking(TRUE)
icon_state = on_icon
// Doop de doo. Jeopardy theme goes here.
@@ -123,7 +127,7 @@
if(!cooking_obj || cooking_obj.loc != src)
cooking_obj = null
icon_state = off_icon
cooking = 0
set_cooking(FALSE)
return
// RIP slow-moving held mobs.
@@ -166,7 +170,7 @@
if(!can_burn_food)
icon_state = off_icon
cooking = 0
set_cooking(FALSE)
result.forceMove(get_turf(src))
cooking_obj = null
else
@@ -192,7 +196,7 @@
failed = 1
if(failed)
cooking = 0
set_cooking(FALSE)
icon_state = off_icon
break
@@ -201,7 +205,7 @@
if(cooking_obj && user.Adjacent(src)) //Fixes borgs being able to teleport food in these machines to themselves.
user << "<span class='notice'>You grab \the [cooking_obj] from \the [src].</span>"
user.put_in_hands(cooking_obj)
cooking = 0
set_cooking(FALSE)
cooking_obj = null
icon_state = off_icon
return
@@ -8,6 +8,22 @@
off_icon = "fryer_off"
food_color = "#FFAD33"
cooked_sound = 'sound/machines/ding.ogg'
var/datum/looping_sound/deep_fryer/fry_loop
/obj/machinery/cooker/fryer/Initialize()
fry_loop = new(list(src), FALSE)
return ..()
/obj/machinery/cooker/fryer/Destroy()
QDEL_NULL(fry_loop)
return ..()
/obj/machinery/cooker/fryer/set_cooking(new_setting)
..()
if(new_setting)
fry_loop.start()
else
fry_loop.stop()
/obj/machinery/cooker/fryer/cook_mob(var/mob/living/victim, var/mob/user)
@@ -17,16 +33,19 @@
user.visible_message("<span class='danger'>\The [user] starts pushing \the [victim] into \the [src]!</span>")
icon_state = on_icon
cooking = 1
fry_loop.start()
if(!do_mob(user, victim, 20))
cooking = 0
icon_state = off_icon
fry_loop.stop()
return
if(!victim || !victim.Adjacent(user))
user << "<span class='danger'>Your victim slipped free!</span>"
cooking = 0
icon_state = off_icon
fry_loop.stop()
return
var/obj/item/organ/external/E
@@ -62,4 +81,5 @@
icon_state = off_icon
cooking = 0
fry_loop.stop()
return
+15 -10
View File
@@ -16,6 +16,7 @@
var/global/list/acceptable_items // List of the items you can put in
var/global/list/acceptable_reagents // List of the reagents you can put in
var/global/max_n_of_items = 0
var/datum/looping_sound/microwave/soundloop
// see code/modules/food/recipes_microwave.dm for recipes
@@ -24,8 +25,7 @@
* Initialising
********************/
/obj/machinery/microwave/New()
..()
/obj/machinery/microwave/Initialize()
reagents = new/datum/reagents(100)
reagents.my_atom = src
@@ -54,6 +54,12 @@
acceptable_items |= /obj/item/weapon/reagent_containers/food/snacks/grown
RefreshParts()
soundloop = new(list(src), FALSE)
return ..()
/obj/machinery/microwave/Destroy()
QDEL_NULL(soundloop)
return ..()
/*******************
* Item Adding
@@ -311,20 +317,19 @@
/obj/machinery/microwave/proc/start()
src.visible_message("<span class='notice'>The microwave turns on.</span>", "<span class='notice'>You hear a microwave.</span>")
src.operating = 1
soundloop.start()
src.operating = TRUE
src.icon_state = "mw1"
src.updateUsrDialog()
/obj/machinery/microwave/proc/abort()
src.operating = 0 // Turn it off again aferwards
src.icon_state = "mw"
src.updateUsrDialog()
operating = FALSE // Turn it off again aferwards
icon_state = "mw"
updateUsrDialog()
soundloop.stop()
/obj/machinery/microwave/proc/stop()
playsound(src.loc, 'sound/machines/ding.ogg', 50, 1)
src.operating = 0 // Turn it off again aferwards
src.icon_state = "mw"
src.updateUsrDialog()
abort()
/obj/machinery/microwave/proc/dispose()
for (var/obj/O in ((contents-component_parts)-circuit))
+12 -12
View File
@@ -32,22 +32,22 @@
"Beach" = new/datum/holodeck_program(/area/holodeck/source_beach),
"Desert" = new/datum/holodeck_program(/area/holodeck/source_desert,
list(
'sound/effects/wind/wind_2_1.ogg',
'sound/effects/wind/wind_2_2.ogg',
'sound/effects/wind/wind_3_1.ogg',
'sound/effects/wind/wind_4_1.ogg',
'sound/effects/wind/wind_4_2.ogg',
'sound/effects/wind/wind_5_1.ogg'
'sound/effects/weather/wind/wind_2_1.ogg',
'sound/effects/weather/wind/wind_2_2.ogg',
'sound/effects/weather/wind/wind_3_1.ogg',
'sound/effects/weather/wind/wind_4_1.ogg',
'sound/effects/weather/wind/wind_4_2.ogg',
'sound/effects/weather/wind/wind_5_1.ogg'
)
),
"Snowfield" = new/datum/holodeck_program(/area/holodeck/source_snowfield,
list(
'sound/effects/wind/wind_2_1.ogg',
'sound/effects/wind/wind_2_2.ogg',
'sound/effects/wind/wind_3_1.ogg',
'sound/effects/wind/wind_4_1.ogg',
'sound/effects/wind/wind_4_2.ogg',
'sound/effects/wind/wind_5_1.ogg'
'sound/effects/weather/wind/wind_2_1.ogg',
'sound/effects/weather/wind/wind_2_2.ogg',
'sound/effects/weather/wind/wind_3_1.ogg',
'sound/effects/weather/wind/wind_4_1.ogg',
'sound/effects/weather/wind/wind_4_2.ogg',
'sound/effects/weather/wind/wind_5_1.ogg'
)
),
"Space" = new/datum/holodeck_program(/area/holodeck/source_space,
+2 -1
View File
@@ -104,7 +104,8 @@ var/global/use_preloader = FALSE
if(cropMap)
continue
else
world.maxz = zcrd //create a new z_level if needed
while(world.maxz < zcrd)
world.increment_max_z() // create a new z_level if needed.
if(!no_changeturf)
WARNING("Z-level expansion occurred without no_changeturf set, this may cause problems")
+4 -1
View File
@@ -70,4 +70,7 @@
if(!client.tooltips)
client.tooltips = new(client)
var/turf/T = get_turf(src)
if(isturf(T))
update_client_z(T.z)
+1
View File
@@ -1,6 +1,7 @@
/mob/Logout()
GLOB.nanomanager.user_logout(src) // this is used to clean up (remove) this user's Nano UIs
player_list -= src
update_client_z(null)
log_access_out(src)
if(admin_datums[src.ckey])
if (ticker && ticker.current_state == GAME_STATE_PLAYING) //Only report this stuff if we are currently playing.
+16
View File
@@ -1170,3 +1170,19 @@ mob/proc/yank_out_object()
closeToolTip(usr) //No reason not to, really
..()
// Manages a global list of mobs with clients attached, indexed by z-level.
/mob/proc/update_client_z(new_z) // +1 to register, null to unregister.
if(registered_z != new_z)
if(registered_z)
GLOB.players_by_zlevel[registered_z] -= src
if(client)
if(new_z)
GLOB.players_by_zlevel[new_z] += src
registered_z = new_z
else
registered_z = null
/mob/on_z_change(old_z, new_z)
..()
update_client_z(new_z)
+2
View File
@@ -231,3 +231,5 @@
var/attack_icon //Icon to use when attacking w/o anything in-hand
var/attack_icon_state //State for above
var/registered_z
+2
View File
@@ -170,6 +170,8 @@
src.move_speed = world.time - src.l_move_time
src.l_move_time = world.time
src.m_flag = 1
if(A && A.z != src.z) // If we changed z-levels, tell the AM that.
on_z_change(A.z, src.z)
if ((A != src.loc && A && A.z == src.z))
src.last_move = get_dir(A, src.loc)
if(.)
+1 -1
View File
@@ -14,7 +14,7 @@
var/sun_position = 0 // 0 means midnight, 1 means noon.
var/list/sun = list("range","brightness","color","lum_r","lum_g","lum_b")
var/list/datum/lighting_corner/sunlit_corners = list()
var/expected_z_levels = list()
var/list/expected_z_levels = list()
var/turf/unsimulated/wall/planetary/planetary_wall_type = /turf/unsimulated/wall/planetary
+119 -2
View File
@@ -121,7 +121,10 @@ var/datum/planet/sif/planet_sif = null
WEATHER_RAIN = new /datum/weather/sif/rain(),
WEATHER_STORM = new /datum/weather/sif/storm(),
WEATHER_HAIL = new /datum/weather/sif/hail(),
WEATHER_BLOOD_MOON = new /datum/weather/sif/blood_moon()
WEATHER_BLOOD_MOON = new /datum/weather/sif/blood_moon(),
WEATHER_EMBERFALL = new /datum/weather/sif/emberfall(),
WEATHER_ASH_STORM = new /datum/weather/sif/ash_storm(),
WEATHER_FALLOUT = new /datum/weather/sif/fallout()
)
roundstart_weather_chances = list(
WEATHER_CLEAR = 30,
@@ -208,6 +211,8 @@ var/datum/planet/sif/planet_sif = null
"It's starting to snow.",
"The air feels much colder as snowflakes fall from above."
)
outdoor_sounds_type = /datum/looping_sound/weather/outside_snow
indoor_sounds_type = /datum/looping_sound/weather/inside_snow
/datum/weather/sif/snow/process_effects()
..()
@@ -237,6 +242,8 @@ var/datum/planet/sif/planet_sif = null
"Strong winds howl around you as a blizzard appears.",
"It starts snowing heavily, and it feels extremly cold now."
)
outdoor_sounds_type = /datum/looping_sound/weather/outside_blizzard
indoor_sounds_type = /datum/looping_sound/weather/inside_blizzard
/datum/weather/sif/blizzard/process_effects()
..()
@@ -265,6 +272,8 @@ var/datum/planet/sif/planet_sif = null
transition_messages = list(
"The sky is dark, and rain falls down upon you."
)
// outdoor_sounds_type = /datum/looping_sound/weather/rain
// indoor_sounds_type = /datum/looping_sound/weather/rain/indoors
/datum/weather/sif/rain/process_effects()
..()
@@ -310,6 +319,8 @@ var/datum/planet/sif/planet_sif = null
"Loud thunder is heard in the distance.",
"A bright flash heralds the approach of a storm."
)
// outdoor_sounds_type = /datum/looping_sound/weather/rain
// indoor_sounds_type = /datum/looping_sound/weather/rain/indoors
transition_chances = list(
@@ -417,7 +428,7 @@ var/datum/planet/sif/planet_sif = null
if(show_message)
to_chat(H, "<span class='notice'>Hail patters onto your umbrella.</span>")
continue
var/target_zone = pick(BP_ALL)
var/amount_blocked = H.run_armor_check(target_zone, "melee")
var/amount_soaked = H.get_armor_soak(target_zone, "melee")
@@ -435,6 +446,10 @@ var/datum/planet/sif/planet_sif = null
if(show_message)
to_chat(H, effect_message)
// These never happen naturally, and are for adminbuse.
// A culty weather.
/datum/weather/sif/blood_moon
name = "blood moon"
light_modifier = 0.5
@@ -447,3 +462,105 @@ var/datum/planet/sif/planet_sif = null
transition_messages = list(
"The sky turns blood red!"
)
outdoor_sounds_type = /datum/looping_sound/weather/wind
indoor_sounds_type = /datum/looping_sound/weather/wind/indoors
// Ash and embers fall forever, such as from a volcano or something.
/datum/weather/sif/emberfall
name = "emberfall"
icon_state = "ashfall_light"
light_modifier = 0.7
light_color = "#880000"
temp_high = 293.15 // 20c
temp_low = 283.15 // 10c
flight_failure_modifier = 20
transition_chances = list(
WEATHER_EMBERFALL = 100
)
observed_message = "Soot, ash, and embers float down from above."
transition_messages = list(
"Gentle embers waft down around you like grotesque snow."
)
outdoor_sounds_type = /datum/looping_sound/weather/wind
indoor_sounds_type = /datum/looping_sound/weather/wind/indoors
// Like the above but a lot more harmful.
/datum/weather/sif/ash_storm
name = "ash storm"
icon_state = "ashfall_heavy"
light_modifier = 0.1
light_color = "#FF0000"
temp_high = 323.15 // 50c
temp_low = 313.15 // 40c
flight_failure_modifier = 50
transition_chances = list(
WEATHER_ASH_STORM = 100
)
observed_message = "All that can be seen is black smoldering ash."
transition_messages = list(
"Smoldering clouds of scorching ash billow down around you!"
)
// Lets recycle.
outdoor_sounds_type = /datum/looping_sound/weather/outside_blizzard
indoor_sounds_type = /datum/looping_sound/weather/inside_blizzard
/datum/weather/sif/ash_storm/process_effects()
..()
for(var/thing in living_mob_list)
var/mob/living/L = thing
if(L.z in holder.our_planet.expected_z_levels)
var/turf/T = get_turf(L)
if(!T.outdoors)
continue // They're indoors, so no need to burn them with ash.
L.inflict_heat_damage(rand(1, 3))
// Totally radical.
/datum/weather/sif/fallout
name = "fallout"
icon_state = "fallout"
light_modifier = 0.7
light_color = "#CCFFCC"
flight_failure_modifier = 30
transition_chances = list(
WEATHER_FALLOUT = 100
)
observed_message = "Radioactive soot and ash rains down from the heavens."
transition_messages = list(
"Radioactive soot and ash start to float down around you, contaminating whatever they touch."
)
outdoor_sounds_type = /datum/looping_sound/weather/wind
indoor_sounds_type = /datum/looping_sound/weather/wind/indoors
// How much radiation a mob gets while on an outside tile.
var/direct_rad_low = RAD_LEVEL_LOW
var/direct_rad_high = RAD_LEVEL_MODERATE
// How much radiation is bursted onto a random tile near a mob.
var/fallout_rad_low = RAD_LEVEL_HIGH
var/fallout_rad_high = RAD_LEVEL_VERY_HIGH
/datum/weather/sif/fallout/process_effects()
..()
for(var/thing in living_mob_list)
var/mob/living/L = thing
if(L.z in holder.our_planet.expected_z_levels)
irradiate_nearby_turf(L)
var/turf/T = get_turf(L)
if(!T.outdoors)
continue // They're indoors, so no need to irradiate them with fallout.
L.rad_act(rand(direct_rad_low, direct_rad_high))
// This makes random tiles near people radioactive for awhile.
// Tiles far away from people are left alone, for performance.
/datum/weather/sif/fallout/proc/irradiate_nearby_turf(mob/living/L)
if(!istype(L))
return
var/list/turfs = RANGE_TURFS(world.view, L)
var/turf/T = pick(turfs) // We get one try per tick.
if(!istype(T))
return
if(T.outdoors)
radiation_repository.radiate(T, rand(fallout_rad_low, fallout_rad_high))
+85 -2
View File
@@ -25,13 +25,19 @@
/datum/weather_holder/proc/change_weather(var/new_weather)
var/old_light_modifier = null
var/old_weather = null
var/datum/weather/old_weather = null
if(current_weather)
old_light_modifier = current_weather.light_modifier // We store the old one, so we can determine if recalculating the sun is needed.
old_weather = current_weather
current_weather = allowed_weather_types[new_weather]
next_weather_shift = world.time + rand(current_weather.timer_low_bound, current_weather.timer_high_bound) MINUTES
if(new_weather != old_weather)
if(istype(old_weather)) // At roundstart this is null.
old_weather.process_sounds() // Ensure that people who should hear the ending sound will hear it.
old_weather.stop_sounds()
current_weather.process_sounds() // Same story, make sure the starting sound is heard.
current_weather.start_sounds()
show_transition_message()
update_icon_effects()
@@ -48,7 +54,7 @@
advance_forecast()
else
current_weather.process_effects()
current_weather.process_sounds()
// Should only have to be called once.
@@ -140,6 +146,18 @@
var/list/transition_messages = list()// List of messages shown to all outdoor mobs when this weather is transitioned to, for flavor. Not shown if already this weather.
var/observed_message = null // What is shown to a player 'examining' the weather.
// Looping sound datums for weather sounds, both inside and outside.
var/datum/looping_sound/outdoor_sounds = null
var/datum/looping_sound/indoor_sounds = null
var/outdoor_sounds_type = null
var/indoor_sounds_type = null
/datum/weather/New()
if(outdoor_sounds_type)
outdoor_sounds = new outdoor_sounds_type(list(), FALSE, TRUE)
if(indoor_sounds_type)
indoor_sounds = new indoor_sounds_type(list(), FALSE, TRUE)
/datum/weather/proc/process_effects()
show_message = FALSE // Need to reset the show_message var, just in case
if(effect_message) // Only bother with the code below if we actually need to display something
@@ -148,6 +166,71 @@
show_message = TRUE // Tell the rest of the process that we need to make a message
return
/datum/weather/proc/process_sounds()
if(!outdoor_sounds && !indoor_sounds) // No point bothering if we have no sounds.
return
for(var/z_level in 1 to world.maxz)
for(var/a in GLOB.players_by_zlevel[z_level])
var/mob/M = a
// Check if the mob left the z-levels we control. If so, make the sounds stop for them.
if(!(z_level in holder.our_planet.expected_z_levels))
hear_indoor_sounds(M, FALSE)
hear_outdoor_sounds(M, FALSE)
continue
// Otherwise they should hear some sounds, depending on if they're inside or not.
var/turf/T = get_turf(M)
if(istype(T))
if(T.outdoors) // Mob is currently outdoors.
hear_outdoor_sounds(M, TRUE)
hear_indoor_sounds(M, FALSE)
else // Mob is currently indoors.
hear_outdoor_sounds(M, FALSE)
hear_indoor_sounds(M, TRUE)
else
hear_indoor_sounds(M, FALSE)
hear_outdoor_sounds(M, FALSE)
/datum/weather/proc/start_sounds()
if(outdoor_sounds)
outdoor_sounds.start()
if(indoor_sounds)
indoor_sounds.start()
/datum/weather/proc/stop_sounds()
if(outdoor_sounds)
outdoor_sounds.stop()
if(indoor_sounds)
indoor_sounds.stop()
// Stop everything just in case.
for(var/z_level in 1 to world.maxz)
for(var/a in GLOB.players_by_zlevel[z_level])
hear_indoor_sounds(a, FALSE)
hear_outdoor_sounds(a, FALSE)
// Adds or removes someone from the outdoor list.
/datum/weather/proc/hear_outdoor_sounds(mob/M, adding)
if(!outdoor_sounds)
return
if(adding)
outdoor_sounds.output_atoms |= M
return
outdoor_sounds.output_atoms -= M
// Ditto, for indoors.
/datum/weather/proc/hear_indoor_sounds(mob/M, adding)
if(!indoor_sounds)
return
if(adding)
indoor_sounds.output_atoms |= M
return
indoor_sounds.output_atoms -= M
// All this does is hold the weather icon.
/atom/movable/weather_visuals
icon = 'icons/effects/weather.dmi'
+15 -2
View File
@@ -22,12 +22,18 @@
var/lastgen2 = 0
var/effective_gen = 0
var/lastgenlev = 0
var/datum/looping_sound/generator/soundloop
/obj/machinery/power/generator/New()
..()
/obj/machinery/power/generator/Initialize()
soundloop = new(list(src), FALSE)
desc = initial(desc) + " Rated for [round(max_power/1000)] kW."
spawn(1)
reconnect()
return ..()
/obj/machinery/power/generator/Destroy()
QDEL_NULL(soundloop)
return ..()
//generators connect in dir and reverse_dir(dir) directions
//mnemonic to determine circulator/generator directions: the cirulators orbit clockwise around the generator
@@ -124,6 +130,13 @@
stored_energy -= lastgen1
effective_gen = (lastgen1 + lastgen2) / 2
// Sounds.
if(effective_gen > (max_power * 0.05)) // More than 5% and sounds start.
soundloop.start()
soundloop.volume = LERP(1, 40, effective_gen / max_power)
else
soundloop.stop()
// update icon overlays and power usage only if displayed level has changed
var/genlev = max(0, min( round(11*effective_gen / max_power), 11))
if(effective_gen > 100 && genlev == 0)
+12 -1
View File
@@ -88,9 +88,15 @@
var/debug = 0
var/datum/looping_sound/supermatter/soundloop
/obj/machinery/power/supermatter/Initialize()
soundloop = new(list(src), TRUE)
return ..()
/obj/machinery/power/supermatter/Destroy()
. = ..()
QDEL_NULL(soundloop)
return ..()
/obj/machinery/power/supermatter/proc/explode()
message_admins("Supermatter exploded at ([x],[y],[z] - <A HREF='?_src_=holder;adminplayerobservecoodjump=1;X=[x];Y=[y];Z=[z]'>JMP</a>)",0,1)
@@ -196,6 +202,11 @@
if(grav_pulling)
supermatter_pull(src)
if(power)
// Volume will be 1 at no power, ~12.5 at ENERGY_NITROGEN, and 20+ at ENERGY_PHORON.
// Capped to 20 volume since higher volumes get annoying and it sounds worse.
soundloop.volume = min(round(power/10)+1, 20)
//Ok, get the air from the turf
var/datum/gas_mixture/removed = null
var/datum/gas_mixture/env = null