diff --git a/code/game/gamemodes/meteor/meteors.dm b/code/game/gamemodes/meteor/meteors.dm index 9d18216aa53..06c66190213 100644 --- a/code/game/gamemodes/meteor/meteors.dm +++ b/code/game/gamemodes/meteor/meteors.dm @@ -5,6 +5,9 @@ /var/const/meteors_in_small_wave = 10 /proc/meteor_wave(var/number = meteors_in_wave) + if (number == 0) + return + if(!ticker || wavesecret) return @@ -34,23 +37,23 @@ do switch(pick(1,2,3,4)) if(1) //NORTH - starty = world.maxy-(TRANSITIONEDGE+1) - startx = rand((TRANSITIONEDGE+1), world.maxx-(TRANSITIONEDGE+1)) + starty = world.maxy-(TRANSITIONEDGE+2) + startx = rand((TRANSITIONEDGE+2), world.maxx-(TRANSITIONEDGE+2)) endy = TRANSITIONEDGE endx = rand(TRANSITIONEDGE, world.maxx-TRANSITIONEDGE) if(2) //EAST - starty = rand((TRANSITIONEDGE+1),world.maxy-(TRANSITIONEDGE+1)) - startx = world.maxx-(TRANSITIONEDGE+1) + starty = rand((TRANSITIONEDGE+2),world.maxy-(TRANSITIONEDGE+1)) + startx = world.maxx-(TRANSITIONEDGE+2) endy = rand(TRANSITIONEDGE, world.maxy-TRANSITIONEDGE) endx = TRANSITIONEDGE if(3) //SOUTH - starty = (TRANSITIONEDGE+1) - startx = rand((TRANSITIONEDGE+1), world.maxx-(TRANSITIONEDGE+1)) + starty = (TRANSITIONEDGE+2) + startx = rand((TRANSITIONEDGE+2), world.maxx-(TRANSITIONEDGE+2)) endy = world.maxy-TRANSITIONEDGE endx = rand(TRANSITIONEDGE, world.maxx-TRANSITIONEDGE) if(4) //WEST - starty = rand((TRANSITIONEDGE+1), world.maxy-(TRANSITIONEDGE+1)) - startx = (TRANSITIONEDGE+1) + starty = rand((TRANSITIONEDGE+2), world.maxy-(TRANSITIONEDGE+2)) + startx = (TRANSITIONEDGE+2) endy = rand(TRANSITIONEDGE,world.maxy-TRANSITIONEDGE) endx = world.maxx-TRANSITIONEDGE @@ -73,7 +76,7 @@ M = new /obj/effect/meteor/small( pickedstart ) M.dest = pickedgoal - spawn(0) + spawn(1) walk_towards(M, M.dest, 1) return @@ -84,39 +87,65 @@ icon_state = "flaming" density = 1 anchored = 1.0 - var/hits = 1 - var/detonation_chance = 15 - var/power = 4 - var/power_step = 1 + var/hits = 3 + var/detonation_chance = 50 + var/power = 2 + var/power_step = 0.75 var/dest + var/shieldsoundrange = 200 // The maximum number of tiles away the sound can be heard, falls off over distance, so it will be quiet near the limit pass_flags = PASSTABLE + var/done = 0//This is set to 1 when the meteor is done colliding, and is used to ignore additional bumps while waiting for deletion + /obj/effect/meteor/small name = "small meteor" icon_state = "smallf" pass_flags = PASSTABLE | PASSGRILLE - power = 2 + power = 1 + power_step = 0.5 + hits = 2 + detonation_chance = 30 + shieldsoundrange = 100 + /obj/effect/meteor/Destroy() walk(src,0) //this cancels the walk_towards() proc ..() /obj/effect/meteor/Bump(atom/A) - spawn(0) + if (!done) + spawn(0) + + if (A) + A.meteorhit(src) + playsound(src.loc, 'sound/effects/meteorimpact.ogg', 40, 1) + + if (istype(A, /obj/effect/energy_field))//If a normal/small meteor impacts an energy field, then it makes a widely audible impact sound and qdels + done = 1 + hits = 0 + power *= 0.5 + power_step *= 0.5 + var/turf/T = src.loc + if (!T) + T = A.loc + + if (T)//We have a double safety check on T to prevent runtime errors + meteor_shield_impact_sound(T, shieldsoundrange) + spawn()//Delaying the Qdel a frame provides a little more safety + qdel(src) + + if (--src.hits == 0 && !done) + //Prevent meteors from blowing up the singularity's containment. + //Changing emitter and generator ex_act would result in them being bomb and C4 proof. + done = 1 + if(!istype(A,/obj/machinery/power/emitter) && \ + !istype(A,/obj/machinery/field_generator) && \ + prob(detonation_chance)) + explosion(loc, power, power + power_step, power + power_step * 2, power + power_step * 3, 0) + spawn() + qdel(src) - if (A) - A.meteorhit(src) - playsound(src.loc, 'sound/effects/meteorimpact.ogg', 40, 1) - if (--src.hits <= 0) - //Prevent meteors from blowing up the singularity's containment. - //Changing emitter and generator ex_act would result in them being bomb and C4 proof. - if(!istype(A,/obj/machinery/power/emitter) && \ - !istype(A,/obj/machinery/field_generator) && \ - prob(detonation_chance)) - explosion(loc, power, power + power_step, power + power_step * 2, power + power_step * 3, 0) - qdel(src) - return /obj/effect/meteor/ex_act(severity) @@ -128,37 +157,78 @@ /obj/effect/meteor/big name = "big meteor" hits = 5 - power = 1 + power = 4 + power_step = 1 + detonation_chance = 60 + shieldsoundrange = 300//This can be set larger than the dimensions of the map, to allow it to remain louder at extreme distance ex_act(severity) return Bump(atom/A) - spawn(0) - //Prevent meteors from blowing up the singularity's containment. - //Changing emitter and generator ex_act would result in them being bomb and C4 proof - if(!istype(A,/obj/machinery/power/emitter) && \ - !istype(A,/obj/machinery/field_generator)) - if(--src.hits <= 0) - qdel(src) //Dont blow up singularity containment if we get stuck there. + if (!done) + spawn(0) + //Prevent meteors from blowing up the singularity's containment. + //Changing emitter and generator ex_act would result in them being bomb and C4 proof + if(!istype(A,/obj/machinery/power/emitter) && \ + !istype(A,/obj/machinery/field_generator)) + if(--src.hits <= 0) + qdel(src) //Dont blow up singularity containment if we get stuck there. - if (A) - for(var/mob/M in player_list) - var/turf/T = get_turf(M) - if(!T || T.z != src.z) - continue - shake_camera(M, 3, get_dist(M.loc, src.loc) > 20 ? 1 : 3) - playsound(src.loc, 'sound/effects/meteorimpact.ogg', 40, 1) - explosion(src.loc, 0, 1, 2, 3, 0) + if (istype(A, /obj/effect/energy_field))//If a big meteor impacts an energy field, then it detonates immediately with reduced power + done = 1 + hits = 0 + power *= 0.5 + power_step *= 0.5 + for(var/mob/M in player_list) + var/turf/T = get_turf(M) + if(!T || T.z != src.z) + continue + shake_camera(M, 3, get_dist(M.loc, src.loc) > 20 ? 1 : 3) + var/turf/T = src.loc + if (!T) + T = A.loc - if (--src.hits <= 0) - if(prob(detonation_chance) && !istype(A, /obj/structure/grille)) + if (T) + meteor_shield_impact_sound(T, shieldsoundrange) explosion(loc, power, power + power_step, power + power_step * 2, power + power_step * 3, 0) - qdel(src) - return + spawn()//Have to delay the qdel a little, or the playsound will throw a runtime + qdel(src) + + else if (A) + for(var/mob/M in player_list) + var/turf/T = get_turf(M) + if(!T || T.z != src.z) + continue + shake_camera(M, 3, get_dist(M.loc, src.loc) > 20 ? 1 : 3) + playsound(src.loc, 'sound/effects/meteorimpact.ogg', 40, 1) + explosion(src.loc, 0, 1, 2, 3, 0) + + if (--src.hits == 0 && !done) + done = 1 + if(prob(detonation_chance) && !istype(A, /obj/structure/grille)) + explosion(loc, power, power + power_step, power + power_step * 2, power + power_step * 3, 0) + qdel(src) + /obj/effect/meteor/attackby(obj/item/weapon/W as obj, mob/user as mob) if(istype(W, /obj/item/weapon/pickaxe)) qdel(src) return ..() + +//This function takes a turf to prevent race conditions, as the object calling it will probably be deleted in the same frame +/proc/meteor_shield_impact_sound(var/turf/T, var/range) + //The supplied volume is reduced by an amount = distance - viewrange * 2, viewrange is 7 i think + + //Calculate the supplied volume so it will be heard with slightly > 0 volume at the maximum range. + //The +1 gives it that tiny amount + range = ((range - world.view) * 2)+1 + + + for(var/mob/M in world) + if(M.client && M.z == T.z) + if(M.ear_deaf <= 0 || !M.ear_deaf) + M.playsound_local(T, 'sound/effects/meteorimpact.ogg', range, 1, usepressure = 0) + + diff --git a/code/game/sound.dm b/code/game/sound.dm index 310ad7e8030..58d8a7ce4e9 100644 --- a/code/game/sound.dm +++ b/code/game/sound.dm @@ -47,7 +47,7 @@ var/list/footstepfx = list("defaultstep","concretestep","grassstep","dirtstep"," var/const/FALLOFF_SOUNDS = 0.5 -/mob/proc/playsound_local(var/turf/turf_source, soundin, vol as num, vary, frequency, falloff, is_global) +/mob/proc/playsound_local(var/turf/turf_source, soundin, vol as num, vary, frequency, falloff, is_global, var/usepressure = 1) if(!src.client || ear_deaf > 0) return if(soundin in footstepfx) @@ -59,7 +59,7 @@ var/const/FALLOFF_SOUNDS = 0.5 var/sound/S = sound(soundin) S.wait = 0 //No queue S.channel = 0 //Any channel - S.volume = vol + S.environment = -1 if (vary) if(frequency) @@ -74,30 +74,36 @@ var/const/FALLOFF_SOUNDS = 0.5 //sound volume falloff with distance var/distance = get_dist(T, turf_source) - S.volume -= max(distance - world.view, 0) * 2 //multiplicative falloff to add on top of natural audio falloff. + vol -= max(distance - world.view, 0) * 2 //multiplicative falloff to add on top of natural audio falloff. + //Not sure if author understood what they were doing, but this is not multiplicative, its linear, and its implementation breaks longdistance sounds. + //This extra falloff should probably be rewritten or removed, but for now ive implemented a quick fix by only setting S.volume to vol after calculations are done + //This fix allows feeding in high volume values (>100) to make longrange sounds audible + // -Nanako - //sound volume falloff with pressure - var/pressure_factor = 1.0 + if (usepressure) + //sound volume falloff with pressure. Pass usepressure = 0 to disable these calculations + var/pressure_factor = 1.0 - var/datum/gas_mixture/hearer_env = T.return_air() - var/datum/gas_mixture/source_env = turf_source.return_air() + var/datum/gas_mixture/hearer_env = T.return_air() + var/datum/gas_mixture/source_env = turf_source.return_air() - if (hearer_env && source_env) - var/pressure = min(hearer_env.return_pressure(), source_env.return_pressure()) + if (hearer_env && source_env) + var/pressure = min(hearer_env.return_pressure(), source_env.return_pressure()) - if (pressure < ONE_ATMOSPHERE) - pressure_factor = max((pressure - SOUND_MINIMUM_PRESSURE)/(ONE_ATMOSPHERE - SOUND_MINIMUM_PRESSURE), 0) - else //in space - pressure_factor = 0 + if (pressure < ONE_ATMOSPHERE) + pressure_factor = max((pressure - SOUND_MINIMUM_PRESSURE)/(ONE_ATMOSPHERE - SOUND_MINIMUM_PRESSURE), 0) + else //in space + pressure_factor = 0 - if (distance <= 1) - pressure_factor = max(pressure_factor, 0.15) //hearing through contact + if (distance <= 1) + pressure_factor = max(pressure_factor, 0.15) //hearing through contact - S.volume *= pressure_factor + vol *= pressure_factor - if (S.volume <= 0) + if (vol <= 0) return //no volume means no sound + S.volume = vol var/dx = turf_source.x - T.x // Hearing from the right/left S.x = dx var/dz = turf_source.y - T.y // Hearing from infront/behind @@ -112,6 +118,8 @@ var/const/FALLOFF_SOUNDS = 0.5 src << S + + /client/proc/playtitlemusic() if(!ticker || !ticker.login_music) return if(prefs.toggles & SOUND_LOBBY) diff --git a/code/modules/events/meteors.dm b/code/modules/events/meteors.dm index 45d59ca8cd0..523f79b59b8 100644 --- a/code/modules/events/meteors.dm +++ b/code/modules/events/meteors.dm @@ -1,44 +1,96 @@ //meteor storms are much heavier /datum/event/meteor_wave - startWhen = 6 - endWhen = 33 + startWhen = 86 + endWhen = 9999//safety value, will be set during ticks + + var/wave_delay = 13//Note, wave delay is in procs. actual time is equal to wave_delay * 2.1 + var/min_waves = 11 + var/max_waves = 16 + var/min_meteors = 1 + var/max_meteors = 2 + var/duration = 420//Total duration in seconds that the storm will last after it starts + + + var/waves = 8 + var/next_wave = 86 /datum/event/meteor_wave/setup() - endWhen = rand(15,30) * 3 + startWhen += rand(-15,15)//slightly randomised start time + waves = rand(min_waves,max_waves) + next_wave = startWhen + wave_delay = round(((duration - 10)/waves)/2.1, 1) /datum/event/meteor_wave/announce() - command_announcement.Announce("Meteors have been detected on collision course with the station.", "Meteor Alert", new_sound = 'sound/AI/meteors.ogg') + command_announcement.Announce("A heavy meteor storm has been detected on collision course with the station. Estimated three minutes until impact, please activate station shields, and seek shelter in the central ring.", "Meteor Alert", new_sound = 'sound/AI/meteors.ogg') + +/datum/event/meteor_wave/start() + command_announcement.Announce("Contact with meteor wave imminent, all hands brace for impact.", "Meteor Alert") /datum/event/meteor_wave/tick() - if(IsMultiple(activeFor, 3)) - meteor_wave(rand(2,5)) + if(activeFor >= next_wave) + var/amount = rand(min_meteors,max_meteors) -/datum/event/meteor_wave/end() - command_announcement.Announce("The station has cleared the meteor storm.", "Meteor Alert") - -// -/datum/event/meteor_shower - startWhen = 5 - endWhen = 7 - var/next_meteor = 6 - var/waves = 1 - -/datum/event/meteor_shower/setup() - waves = rand(2,5) - -/datum/event/meteor_shower/announce() - command_announcement.Announce("The station is now in a meteor shower.", "Meteor Alert") - -//meteor showers are lighter and more common, -/datum/event/meteor_shower/tick() - if(activeFor >= next_meteor) - meteor_wave(rand(1,4)) - next_meteor += rand(20,100) + event_meteor_wave(amount) + next_wave += wave_delay waves-- if(waves <= 0) endWhen = activeFor + 1 else - endWhen = next_meteor + 1 + endWhen = next_wave + wave_delay + +/datum/event/meteor_wave/end() + spawn(100)//We give 10 seconds before announcing, for the last wave of meteors to hit the station + command_announcement.Announce("The station has survived the meteor storm, it is now safe to commence repairs.", "Meteor Alert") + +// +/datum/event/meteor_shower + startWhen = 86 + endWhen = 9999 + + var/wave_delay = 6 + var/min_waves = 7 + var/max_waves = 9 + var/min_meteors = 0 + var/max_meteors = 1 + var/duration = 180//Total duration in seconds that the storm will last after it starts + + var/waves = 4//this is randomised + var/next_wave = 86 + +/datum/event/meteor_shower/setup() + startWhen += rand(-15,15)//slightly randomised start time + waves = rand(min_waves,max_waves) + next_wave = startWhen + + wave_delay = round(((duration - 10)/waves)/2.1, 1) + +/datum/event/meteor_shower/announce() + command_announcement.Announce("A meteor shower is approaching the station, estimated contact in three minutes. Crew are recommended to stay away from the outer areas of the station.", "Meteor Alert") + +//meteor showers are lighter and more common, +/datum/event/meteor_shower/tick() + if(activeFor >= next_wave) + var/amount = rand(min_meteors,max_meteors) + + event_meteor_wave(amount) + next_wave += wave_delay + waves-- + if(waves <= 0) + endWhen = activeFor + 1 + else + endWhen = next_wave + wave_delay + +/datum/event/meteor_wave/start() + command_announcement.Announce("Meteors have reached the station. Please stay away from outer areas until the shower has passed.", "Meteor Alert") + /datum/event/meteor_shower/end() - command_announcement.Announce("The station has cleared the meteor shower", "Meteor Alert") + spawn(100) + command_announcement.Announce("The station has cleared the meteor shower, please return to your stations.", "Meteor Alert") + + +//An event specific version of the meteor wave proc, to bypass the delays +/proc/event_meteor_wave(var/number = meteors_in_wave) + for(var/i = 0 to number) + spawn(rand(10,80)) + spawn_meteor() \ No newline at end of file diff --git a/html/changelogs/Nanako-Meteors.yml b/html/changelogs/Nanako-Meteors.yml new file mode 100644 index 00000000000..b6f1fe95425 --- /dev/null +++ b/html/changelogs/Nanako-Meteors.yml @@ -0,0 +1,42 @@ +################################ +# Example Changelog File +# +# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb. +# +# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.) +# When it is, any changes listed below will disappear. +# +# Valid Prefixes: +# bugfix +# wip (For works in progress) +# tweak +# soundadd +# sounddel +# rscadd (general adding of nice things) +# rscdel (general deleting of nice things) +# imageadd +# imagedel +# maptweak +# spellcheck (typo fixes) +# experiment +################################# + +# Your name. +author: Nanako + +# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again. +delete-after: True + +# Any changes you've made. See valid prefix list above. +# INDENT WITH TWO SPACES. NOT TABS. SPACES. +# SCREW THIS UP AND IT WON'T WORK. +# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries. +# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog. +changes: + - bugfix: "Fixed a bug where almost half of all meteors spawned would instantly delete without hitting anything" + - bugfix: "Meteors that impact energy shields will no longer bug out and spin forever in space" + - rscadd: "Meteor showers and storms now last a lot longer, and are far more punishing if the station isn't shielded" + - rscadd: "Meteors are now far more likely to make an audible explosion on impact. Explosion power reduced a bit though" + - rscadd: "Meteor events now give a three minute advance warning, allowing time to turn on station shield generators" + - rscadd: "All meteors that impact a shield now make a special sound effect." + - tweak: "Small and normal sized meteors are now vaporised harmlessly on contact with a shield. Large meteors will explode, but with reduced power"