From eba43f278c559826af3f99bf690a1cd030d9c404 Mon Sep 17 00:00:00 2001 From: Will <7099514+Willburd@users.noreply.github.com> Date: Fri, 15 Aug 2025 16:55:19 -0400 Subject: [PATCH] Explosion subsystem (#17499) * Initial port * pause air * decoupled input from processing * explosion condition tweak * closer to original recursive code * accurate explosions * better defer calls * glob fix * fix * DON'T DO THAT * initial deferral code * small explosions ignored * lower thresholds * better thresholds again * forbid powernet defer during init, explosions too * don't block your own network regen * use procs * better thresholds, always defer at least * admin notice * subsytem updated * Allow removal from networks * defer till rebuild * dir * Update breaker_box.dm * no init means no init * then flag it... --------- Co-authored-by: Selis <12716288+ItsSelis@users.noreply.github.com> Co-authored-by: C.L. Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com> --- code/__defines/machinery.dm | 2 - code/__defines/subsystems.dm | 1 + .../configuration/entries/general.dm | 3 - code/controllers/subsystems/explosions.dm | 339 ++++++++++++++++++ code/controllers/subsystems/machines.dm | 28 +- code/game/gamemodes/meteor/meteor.dm | 4 - code/game/objects/explosion.dm | 112 ------ code/game/objects/explosion_recursive.dm | 82 +---- code/modules/admin/admin_verbs.dm | 6 +- code/modules/multiz/movement.dm | 2 +- code/modules/power/breaker_box.dm | 2 +- code/modules/power/cable.dm | 11 +- code/modules/power/pacman2.dm | 3 +- code/modules/power/power.dm | 2 + .../machinery/dispenser/reagent_tank.dm | 2 +- code/modules/vore/resizing/grav_pull_vr.dm | 7 - vorestation.dme | 2 +- 17 files changed, 388 insertions(+), 220 deletions(-) create mode 100644 code/controllers/subsystems/explosions.dm delete mode 100644 code/game/objects/explosion.dm diff --git a/code/__defines/machinery.dm b/code/__defines/machinery.dm index cca73b2209..85ed2e2f07 100644 --- a/code/__defines/machinery.dm +++ b/code/__defines/machinery.dm @@ -1,5 +1,3 @@ -var/global/defer_powernet_rebuild = 0 // True if net rebuild will be called manually after an event. - #define CELLRATE 0.002 // Multiplier for watts per tick <> cell storage (e.g., 0.02 means if there is a load of 1000 watts, 20 units will be taken from a cell per second) // It's a conversion constant. power_used*CELLRATE = charge_provided, or charge_used/CELLRATE = power_provided #define SMESRATE 0.03333 // Same for SMESes. A different number for some reason. diff --git a/code/__defines/subsystems.dm b/code/__defines/subsystems.dm index 63a1c91bdc..b018d1cccd 100644 --- a/code/__defines/subsystems.dm +++ b/code/__defines/subsystems.dm @@ -136,6 +136,7 @@ #define FIRE_PRIORITY_DEFAULT 50 #define FIRE_PRIORITY_TICKER 60 #define FIRE_PRIORITY_PLANETS 75 +#define FIRE_PRIORITY_EXPLOSIONS 90 #define FIRE_PRIORITY_MACHINES 100 #define FIRE_PRIORITY_MOBS 100 #define FIRE_PRIORITY_TGUI 110 diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm index 7b9ae13596..6a638117b4 100644 --- a/code/controllers/configuration/entries/general.dm +++ b/code/controllers/configuration/entries/general.dm @@ -472,9 +472,6 @@ ///datum/config_entry/number/simultaneous_pm_warning_timeout // default = 100 -/// Defines whether the server uses recursive or circular explosions. -/datum/config_entry/flag/use_recursive_explosions - /// Multiplier for how much weaker explosions are on neighboring z levels. /datum/config_entry/number/multi_z_explosion_scalar default = 0.5 diff --git a/code/controllers/subsystems/explosions.dm b/code/controllers/subsystems/explosions.dm new file mode 100644 index 0000000000..34b432cbc0 --- /dev/null +++ b/code/controllers/subsystems/explosions.dm @@ -0,0 +1,339 @@ +SUBSYSTEM_DEF(explosions) + name = "Explosions" + priority = FIRE_PRIORITY_EXPLOSIONS + runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME + wait = 0.5 SECONDS + dependencies = list( + /datum/controller/subsystem/machines + ) + flags = SS_NO_INIT + + VAR_PRIVATE/resolve_explosions = FALSE + VAR_PRIVATE/list/currentrun = null + VAR_PRIVATE/list/currentsignals = null + VAR_PRIVATE/list/pending_explosions = list() + VAR_PRIVATE/list/resolving_explosions = list() + VAR_PRIVATE/list/explosion_signals = list() + +/datum/controller/subsystem/explosions/stat_entry(msg) + var/meme = "" + switch(resolving_explosions.len) + if(0 to 10000) meme = "" + if(10000 to 15000) meme = "- HEAVY LOAD" + if(15000 to 20000) meme = "- EXTREME LOAD" + if(20000 to 25000) meme = "- I STILL FUNCTION" + if(25000 to 30000) meme = "- WANNA BET?" + if(30000 to INFINITY) meme = "- CALL /abort() TO FORCE END" + msg = "E: [explosion_signals.len] | P: [pending_explosions.len] | R: [resolving_explosions.len] | CR : [currentrun.len] | CS : [currentsignals.len] - [resolve_explosions ? "RESOLVING" : currentrun.len ? "PREPARING" : "IDLING"] [meme]" + return ..() + +/datum/controller/subsystem/explosions/proc/gotosleep() + can_fire = FALSE + +/datum/controller/subsystem/explosions/proc/wakeup() + can_fire = TRUE + next_fire = world.time + +/datum/controller/subsystem/explosions/fire(resumed) + // Build both queues. The first one gets the explosion power in each turf + // The second queue applies that explosion power to all turfs and objects in them + if(!resumed) + if(resolve_explosions && !currentrun.len) + end_resolve() + if(!resolve_explosions) + // Setup the explosion buffer! + currentrun = pending_explosions.Copy() + currentsignals = explosion_signals.Copy() + pending_explosions.Cut() + explosion_signals.Cut() + if(currentrun.len == 0 && !resolve_explosions) // Wait till we're useful if we have nothing to do! + gotosleep() + return + + // The heavy lifting part... + while(currentrun.len) + // Lets handle list management here instead of in each proc + // get the first key of the current run, use the key to get the + // data, use the data, than discard it from the list using the key! + var/key = currentrun[1] // Yes this is how you get the first KEY... byond why... + if(resolve_explosions) + fire_resolve_explosions(currentrun[key]) + else + fire_prepare_explosions(currentrun[key]) + currentrun.Remove(key) + + // Check if we move on to final resolution + if(currentrun.len == 0) + if(!resolve_explosions) + start_resolve() + currentrun = resolving_explosions.Copy() + resolving_explosions.Cut() + return + break // In resolution mode, break into final res ahead + + if(MC_TICK_CHECK) + return + + // !!!Final resolution!!! + // Toggles between setup, and explosion modes. Sends signals to dopplers. Rebuilds powernets + // We've handled the actual explosions, it's time to wrap up everything else. + // send signals to all machines scanning for them + for(var/list/time_dat in currentsignals) + var/x0 = time_dat[1] + var/y0 = time_dat[2] + var/z0 = time_dat[3] + var/devastation_range = time_dat[4] + var/heavy_impact_range = time_dat[5] + var/light_impact_range = time_dat[6] + var/tim = time_dat[7] + for(var/i,i<=GLOB.doppler_arrays.len,i++) + var/obj/machinery/doppler_array/Array = GLOB.doppler_arrays[i] + if(Array) + Array.sense_explosion(x0,y0,z0,devastation_range,heavy_impact_range,light_impact_range, tim - world.time) + currentsignals.Cut() + + // return to setup mode... Unless... + end_resolve() + if(!pending_explosions.len) + suspend_and_invoke_deferred_subsystems() + +/datum/controller/subsystem/explosions/proc/fire_prepare_explosions(var/list/data) + var/pwr = data[4] + var/direction = data[5] + var/starting_power = data[6] + if(pwr <= 0) + return + //This step handles the gathering of turfs which will be ex_act() -ed in the next step. It also ensures each turf gets the maximum possible amount of power dealt to it. + var/turf/epicenter = locate(data[1],data[2],data[3]) + if(!epicenter) + return + var/list/res_explo = resolving_explosions["[epicenter.x].[epicenter.y].[epicenter.z]"] // check if this has already resolved + if(res_explo && res_explo[4] >= pwr) + return + if(direction) + //This is the amount of power that will be spread to the tile in the direction of the blast, subtracted from everything blocking it in the turf! + var/spread_power = pwr - epicenter.explosion_resistance + for(var/obj/O in epicenter) + if(O.explosion_resistance) + spread_power -= O.explosion_resistance + if(spread_power > 0) + // Fan outward from the original explosion + var/turf/T = get_step(epicenter, direction) + if(T) + append_currentrun(T.x,T.y,T.z,spread_power,direction,starting_power) + T = get_step(epicenter, turn(direction,90)) + if(T) + append_currentrun(T.x,T.y,T.z,spread_power,direction,starting_power) + T = get_step(src, turn(direction,-90)) + if(T) + append_currentrun(T.x,T.y,T.z,spread_power,direction,starting_power) + // Make these feel a little more flashy + if(spread_power > 3 && spread_power < GLOB.max_explosion_range && prob(6)) // bombs above maxcap are probably badmins, lets not make 10000 effects + if(prob(30)) + var/datum/effect/effect/system/smoke_spread/S = new/datum/effect/effect/system/smoke_spread() + S.set_up(2,0,epicenter,direction) + S.start() + else + var/datum/effect/system/expl_particles/P = new/datum/effect/system/expl_particles() + P.set_up(2,epicenter,direction) + P.start() + // Build the final explosion list, will be processed when we get to final resolution + finalize_explosion(data[1],data[2],data[3],pwr,starting_power) + +/datum/controller/subsystem/explosions/proc/fire_resolve_explosions(var/list/data) + var/pwr = data[4] + var/starting_power = data[5] + if(pwr <= 0) + return + var/turf/T = locate(data[1],data[2],data[3]) + if(!T) + return + //Wow severity looks confusing to calculate... Fret not, I didn't leave you with any additional instructions or help. (just kidding, see the line under the calculation) + var/severity = 4 - round(max(min( 3, ((pwr - T.explosion_resistance) / (max(3,(starting_power/3)))) ) ,1), 1) //sanity effective power on tile divided by either 3 or one third the total explosion power + // One third because there are three power levels and I + // want each one to take up a third of the crater + T.ex_act(severity) + for(var/atom_movable in T.contents) + var/atom/movable/AM = atom_movable + if(AM && AM.simulated) + AM.ex_act(severity) + +/datum/controller/subsystem/explosions/proc/start_resolve() + SHOULD_NOT_OVERRIDE(TRUE) + PRIVATE_PROC(TRUE) + resolve_explosions = TRUE + +/datum/controller/subsystem/explosions/proc/end_resolve() + SHOULD_NOT_OVERRIDE(TRUE) + PRIVATE_PROC(TRUE) + resolve_explosions = FALSE + +/datum/controller/subsystem/explosions/proc/wake_and_defer_subsystem_updates(var/defer = FALSE) + if(defer) // Save these for AFTER the explosion has resolved + SSmachines.defer_powernet_rebuild(); + // waking from sleep, we are absolutely not resuming, and INSTANT feedback to players is required here. + if(can_fire) // already awake + return + wakeup() + +/datum/controller/subsystem/explosions/proc/suspend_and_invoke_deferred_subsystems() + // Resolve all the stuff we put off for after the explosion resolved + // Awaiting the rust powernet rebuild so this can be called normally... + INVOKE_ASYNC(SSmachines, TYPE_PROC_REF(/datum/controller/subsystem/machines,release_powernet_defer)) + // we've finished. Pause because was have no more work to do. + if(!can_fire) // already asleep + return + gotosleep() + +/datum/controller/subsystem/explosions/proc/abort() + if(!currentrun.len) + return + // Removes all entries except the top most, so we enter resolution phase properly, need at least one entry to do so... + var/key = currentrun[1] + var/data = currentrun[key] + currentrun.Cut() + currentrun[key] = data + +// INTERNAL explosion proc, meant for GROWING a currently processing blast. +/datum/controller/subsystem/explosions/proc/append_currentrun(var/x0,var/y0,var/z0,var/pwr,var/direction,var/starting_power) + SHOULD_NOT_OVERRIDE(TRUE) + PRIVATE_PROC(TRUE) + if(pwr <= 0) + return + // check if there is already an explosion calculated by our current run... + var/final_data = resolving_explosions["[x0].[y0].[z0]"] + var/final_power = 0 + if(final_data) + final_power = final_data[4] + // If there is already a stronger explosion calculated there, then we don't need to bother + if(pwr <= final_power) + return + // Update data at position for next run. Floodfill until the current_run is empty of new explosions! + var/max_starting = starting_power + var/list/dat = currentrun["[x0].[y0].[z0]"] + if(!isnull(dat) && dat[6] > max_starting) + max_starting = dat[6] + if(isnull(dat) || pwr >= dat[4]) + currentrun["[x0].[y0].[z0]"] = list(x0,y0,z0,pwr,direction,max_starting) + +// Queue explosion event, call this from explosion() ONLY +/datum/controller/subsystem/explosions/proc/append_explosion(var/turf/epicenter,var/pwr,var/devastation_range,var/heavy_impact_range,var/light_impact_range,var/flash_range) + SHOULD_NOT_OVERRIDE(TRUE) + if(pwr <= 0) + return + var/x0 = epicenter.x + var/y0 = epicenter.y + var/z0 = epicenter.z + // actual explosion. Do not allow multiple, just take the highest power explosion hitting that turf + var/max_starting = pwr + var/list/dat = pending_explosions["[x0].[y0].[z0]"] + if(!isnull(dat) && dat[6] > max_starting) + max_starting = dat[6] + if(isnull(dat) || pwr >= dat[4]) + // primary explosion + pending_explosions["[x0].[y0].[z0]"] = list(x0,y0,z0,pwr,0,max_starting) + // outward radiating explosions + var/rad_power = pwr - epicenter.explosion_resistance + for(var/direction in GLOB.cardinal) + var/turf/T = get_step(epicenter, direction) + if(T) + dat = pending_explosions["[T.x].[T.y].[T.z]"] + max_starting = pwr + if(!isnull(dat) && dat[6] > max_starting) + max_starting = dat[6] + if(isnull(dat) || rad_power >= dat[4]) + pending_explosions["[T.x].[T.y].[T.z]"] = list(T.x,T.y,T.z,rad_power,direction,max_starting) + + // send signals to dopplers + explosion_signals.Add(list( list(x0,y0,z0,devastation_range,heavy_impact_range,light_impact_range,world.time) )) // append a list in a list. Needed so that the data list doesn't get merged into the list of datalists + + // BOINK! Time to wake up sleeping beauty! + wake_and_defer_subsystem_updates(devastation_range >= 2 || heavy_impact_range >= 4 || light_impact_range >= 8) + +// Collect prepared explosions for BLAST PROCESSING +/datum/controller/subsystem/explosions/proc/finalize_explosion(var/x0,var/y0,var/z0,var/pwr,var/max_starting) + SHOULD_NOT_OVERRIDE(TRUE) + PRIVATE_PROC(TRUE) + if(pwr <= 0) + return + var/list/dat = pending_explosions["[x0].[y0].[z0]"] + if(isnull(dat) || pwr >= dat[4]) + resolving_explosions["[x0].[y0].[z0]"] = list(x0,y0,z0,pwr,max_starting) + +/proc/explosion(turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range, adminlog = 1, z_transfer = UP|DOWN, shaped) + // Rarely objects might explode during init... Don't. + if(!SSticker.HasRoundStarted()) + return + + // Lets assume recursive prey has happened... + var/limit_escape = 10 + while(isbelly(epicenter)) + if(limit_escape-- <= 0) + break + var/obj/belly/B = epicenter + epicenter = B.owner.loc + epicenter = get_turf(epicenter) + if(!epicenter) + return + + // Handles recursive propagation of explosions. + var/multi_z_scalar = CONFIG_GET(number/multi_z_explosion_scalar) + if(z_transfer && multi_z_scalar) + var/adj_dev = max(0, (multi_z_scalar * devastation_range) - (shaped ? 2 : 0) ) + var/adj_heavy = max(0, (multi_z_scalar * heavy_impact_range) - (shaped ? 2 : 0) ) + var/adj_light = max(0, (multi_z_scalar * light_impact_range) - (shaped ? 2 : 0) ) + var/adj_flash = max(0, (multi_z_scalar * flash_range) - (shaped ? 2 : 0) ) + if(adj_dev > 0 || adj_heavy > 0) + if(HasAbove(epicenter.z) && z_transfer & UP) + explosion(GetAbove(epicenter), round(adj_dev), round(adj_heavy), round(adj_light), round(adj_flash), 0, UP, shaped) + if(HasBelow(epicenter.z) && z_transfer & DOWN) + explosion(GetBelow(epicenter), round(adj_dev), round(adj_heavy), round(adj_light), round(adj_flash), 0, DOWN, shaped) + var/max_range = max(devastation_range, heavy_impact_range, light_impact_range, flash_range) + + // Play sounds; we want sounds to be different depending on distance so we will manually do it ourselves. + // Stereo users will also hear the direction of the explosion! + // Calculate far explosion sound range. Only allow the sound effect for heavy/devastating explosions. + // 3/7/14 will calculate to 80 + 35 + var/far_dist = 0 + far_dist += heavy_impact_range * 5 + far_dist += devastation_range * 20 + var/frequency = get_rand_frequency() + for(var/mob/M in GLOB.player_list) + if(M.z == epicenter.z) + var/turf/M_turf = get_turf(M) + var/dist = get_dist(M_turf, epicenter) + // If inside the blast radius + world.view - 2 + if(dist <= round(max_range + world.view - 2, 1)) + M.playsound_local(epicenter, get_sfx("explosion"), 100, 1, frequency, falloff = 5) // get_sfx() is so that everyone gets the same sound + /* Downstream - Ear Ringing/Deafness + //var/mob/living/mL = M + //if(isliving(mL)) + // mL.deaf_loop.start() + */ + else if(dist <= far_dist) + var/far_volume = CLAMP(far_dist, 30, 50) // Volume is based on explosion size and dist + far_volume += (dist <= far_dist * 0.5 ? 50 : 0) // add 50 volume if the mob is pretty close to the explosion + M.playsound_local(epicenter, 'sound/effects/explosionfar.ogg', far_volume, 1, frequency, falloff = 5) + + var/close = range(world.view+round(devastation_range,1), epicenter) + // to all distanced mobs play a different sound + for(var/mob/M in GLOB.player_list) + if(M.z == epicenter.z) + if(!(M in close)) + // check if the mob can hear + if(M.ear_deaf <= 0 || !M.ear_deaf) + if(!istype(M.loc,/turf/space)) + M << 'sound/effects/explosionfar.ogg' + + if(adminlog) + message_admins("Explosion with [shaped ? "shaped" : "non-shaped"] size ([devastation_range], [heavy_impact_range], [light_impact_range]) in area [epicenter.loc.name] ([epicenter.x],[epicenter.y],[epicenter.z]) (JMP)") + log_game("Explosion with [shaped ? "shaped" : "non-shaped"] size ([devastation_range], [heavy_impact_range], [light_impact_range]) in area [epicenter.loc.name] ") + + if(heavy_impact_range > 1) + var/datum/effect/system/explosion/E = new/datum/effect/system/explosion() + E.set_up(epicenter) + E.start() + + // Queue explosion event + var/power = devastation_range * 2 + heavy_impact_range + light_impact_range //The ranges add up, ie light 14 includes both heavy 7 and devestation 3. So this calculation means devestation counts for 4, heavy for 2 and light for 1 power, giving us a cap of 27 power. + SSexplosions.append_explosion(epicenter,power,devastation_range,heavy_impact_range,light_impact_range,flash_range) diff --git a/code/controllers/subsystems/machines.dm b/code/controllers/subsystems/machines.dm index f20d38d175..991fac53f1 100644 --- a/code/controllers/subsystems/machines.dm +++ b/code/controllers/subsystems/machines.dm @@ -33,6 +33,9 @@ SUBSYSTEM_DEF(machines) var/list/powernets = list() var/list/powerobjs = list() + // Wait to rebuild powernets + VAR_PRIVATE/defering_powernets = FALSE + /datum/controller/subsystem/machines/Initialize() makepowernets() admin_notice(span_danger("Initializing atmos machinery."), R_DEBUG) @@ -48,8 +51,27 @@ SUBSYSTEM_DEF(machines) INTERNAL_PROCESS_STEP(SSMACHINES_MACHINERY,FALSE,process_machinery,cost_machinery,SSMACHINES_POWERNETS) INTERNAL_PROCESS_STEP(SSMACHINES_POWERNETS,FALSE,process_powernets,cost_powernets,SSMACHINES_POWER_OBJECTS) -// rebuild all power networks from scratch - only called at world creation or by the admin verb -// The above is a lie. Turbolifts also call this proc. +// Call when you need the network rebuilt, but we should wait until we have a good time to do it +/datum/controller/subsystem/machines/proc/defer_powernet_rebuild() + if(!SSticker.HasRoundStarted()) + return + // Use with responsibility... Must regen the entire power network after deferal is finished. + if(!defering_powernets) + defering_powernets = TRUE + message_admins("Powernet generation deferred...") + + +// This MUST be called if request_powernet_rebuild is called with defer = TRUE once the network is free to regen +/datum/controller/subsystem/machines/proc/release_powernet_defer() + if(defering_powernets) + defering_powernets = FALSE + message_admins("Powernet generation resumed. Rebuilding network...") + makepowernets() + +/datum/controller/subsystem/machines/proc/powernet_is_defered() + return defering_powernets + +// rebuild all power networks from scratch - Called when major network changes happen, like shuttles/turbolifts with wires moving, or huge explosions, where doing it per-wire does not make sense. /datum/controller/subsystem/machines/proc/makepowernets() // TODO - check to not run while in the middle of a tick! for(var/datum/powernet/PN as anything in powernets) @@ -94,7 +116,7 @@ SUBSYSTEM_DEF(machines) msg += "} " msg += "PI:[SSmachines.networks.len]|" msg += "MC:[SSmachines.processing_machines.len]|" - msg += "PN:[SSmachines.powernets.len]|" + msg += "PN:[SSmachines.powernets.len][defering_powernets ? " - !!DEFER!!" : ""]|" msg += "PO:[SSmachines.powerobjs.len]|" msg += "HV:[SSmachines.hibernating_vents.len]|" msg += "MC/MS:[round((cost ? SSmachines.processing_machines.len/cost_machinery : 0),0.1)]" diff --git a/code/game/gamemodes/meteor/meteor.dm b/code/game/gamemodes/meteor/meteor.dm index 5932704f4f..a17781bd16 100644 --- a/code/game/gamemodes/meteor/meteor.dm +++ b/code/game/gamemodes/meteor/meteor.dm @@ -10,10 +10,6 @@ deny_respawn = 0 var/next_wave = METEOR_DELAY -/datum/game_mode/meteor/post_setup() - defer_powernet_rebuild = 2//Might help with the lag - ..() - /datum/game_mode/meteor/process() if(world.time >= next_wave) next_wave = world.time + GLOB.meteor_wave_delay diff --git a/code/game/objects/explosion.dm b/code/game/objects/explosion.dm deleted file mode 100644 index f559ef57aa..0000000000 --- a/code/game/objects/explosion.dm +++ /dev/null @@ -1,112 +0,0 @@ -//TODO: Flash range does nothing currently - -/proc/explosion(turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range, adminlog = 1, z_transfer = UP|DOWN, shaped) - var/multi_z_scalar = CONFIG_GET(number/multi_z_explosion_scalar) - spawn(0) - var/start = world.timeofday - epicenter = get_turf(epicenter) - if(!epicenter) return - - // Handles recursive propagation of explosions. - if(z_transfer && multi_z_scalar) - var/adj_dev = max(0, (multi_z_scalar * devastation_range) - (shaped ? 2 : 0) ) - var/adj_heavy = max(0, (multi_z_scalar * heavy_impact_range) - (shaped ? 2 : 0) ) - var/adj_light = max(0, (multi_z_scalar * light_impact_range) - (shaped ? 2 : 0) ) - var/adj_flash = max(0, (multi_z_scalar * flash_range) - (shaped ? 2 : 0) ) - - - if(adj_dev > 0 || adj_heavy > 0) - if(HasAbove(epicenter.z) && z_transfer & UP) - explosion(GetAbove(epicenter), round(adj_dev), round(adj_heavy), round(adj_light), round(adj_flash), 0, UP, shaped) - if(HasBelow(epicenter.z) && z_transfer & DOWN) - explosion(GetBelow(epicenter), round(adj_dev), round(adj_heavy), round(adj_light), round(adj_flash), 0, DOWN, shaped) - - var/max_range = max(devastation_range, heavy_impact_range, light_impact_range, flash_range) - - // Play sounds; we want sounds to be different depending on distance so we will manually do it ourselves. - // Stereo users will also hear the direction of the explosion! - // Calculate far explosion sound range. Only allow the sound effect for heavy/devastating explosions. - // 3/7/14 will calculate to 80 + 35 - var/far_dist = 0 - far_dist += heavy_impact_range * 5 - far_dist += devastation_range * 20 - var/frequency = get_rand_frequency() - for(var/mob/M in GLOB.player_list) - if(M.z == epicenter.z) - var/turf/M_turf = get_turf(M) - var/dist = get_dist(M_turf, epicenter) - // If inside the blast radius + world.view - 2 - if(dist <= round(max_range + world.view - 2, 1)) - M.playsound_local(epicenter, get_sfx("explosion"), 100, 1, frequency, falloff = 5) // get_sfx() is so that everyone gets the same sound - else if(dist <= far_dist) - var/far_volume = CLAMP(far_dist, 30, 50) // Volume is based on explosion size and dist - far_volume += (dist <= far_dist * 0.5 ? 50 : 0) // add 50 volume if the mob is pretty close to the explosion - M.playsound_local(epicenter, 'sound/effects/explosionfar.ogg', far_volume, 1, frequency, falloff = 5) - - var/close = range(world.view+round(devastation_range,1), epicenter) - // to all distanced mobs play a different sound - for(var/mob/M in GLOB.player_list) - if(M.z == epicenter.z) - if(!(M in close)) - // check if the mob can hear - if(M.ear_deaf <= 0 || !M.ear_deaf) - if(!istype(M.loc,/turf/space)) - M << 'sound/effects/explosionfar.ogg' - - if(adminlog) - message_admins("Explosion with [shaped ? "shaped" : "non-shaped"] size ([devastation_range], [heavy_impact_range], [light_impact_range]) in area [epicenter.loc.name] ([epicenter.x],[epicenter.y],[epicenter.z]) (JMP)") - log_game("Explosion with [shaped ? "shaped" : "non-shaped"] size ([devastation_range], [heavy_impact_range], [light_impact_range]) in area [epicenter.loc.name] ") - - var/approximate_intensity = (devastation_range * 3) + (heavy_impact_range * 2) + light_impact_range - var/powernet_rebuild_was_deferred_already = defer_powernet_rebuild - // Large enough explosion. For performance reasons, powernets will be rebuilt manually - if(!defer_powernet_rebuild && (approximate_intensity > 25)) - defer_powernet_rebuild = 1 - - if(heavy_impact_range > 1) - var/datum/effect/system/explosion/E = new/datum/effect/system/explosion() - E.set_up(epicenter) - E.start() - - var/x0 = epicenter.x - var/y0 = epicenter.y - var/z0 = epicenter.z - if(CONFIG_GET(flag/use_recursive_explosions)) - var/power = devastation_range * 2 + heavy_impact_range + light_impact_range //The ranges add up, ie light 14 includes both heavy 7 and devestation 3. So this calculation means devestation counts for 4, heavy for 2 and light for 1 power, giving us a cap of 27 power. - explosion_rec(epicenter, power, shaped) - else - for(var/turf/T in trange(max_range, epicenter)) - var/dist = sqrt((T.x - x0)**2 + (T.y - y0)**2) - - if(dist < devastation_range) dist = 1 - else if(dist < heavy_impact_range) dist = 2 - else if(dist < light_impact_range) dist = 3 - else continue - - if(!T) - T = locate(x0,y0,z0) - for(var/atom_movable in T.contents) //bypass type checking since only atom/movable can be contained by turfs anyway - var/atom/movable/AM = atom_movable - if(AM && AM.simulated) AM.ex_act(dist) - - T.ex_act(dist) - - var/took = (world.timeofday-start)/10 - //You need to press the DebugGame verb to see these now....they were getting annoying and we've collected a fair bit of data. Just -test- changes to explosion code using this please so we can compare - if(GLOB.Debug2) to_world_log("## DEBUG: Explosion([x0],[y0],[z0])(d[devastation_range],h[heavy_impact_range],l[light_impact_range]): Took [took] seconds.") - - //Machines which report explosions. - for(var/i, i <= GLOB.doppler_arrays.len, i++) - var/obj/machinery/doppler_array/Array = GLOB.doppler_arrays[i] - if(Array) - Array.sense_explosion(x0,y0,z0,devastation_range,heavy_impact_range,light_impact_range,took) - sleep(8) - - if(!powernet_rebuild_was_deferred_already && defer_powernet_rebuild) - SSmachines.makepowernets() - defer_powernet_rebuild = 0 - return 1 - -/proc/secondaryexplosion(turf/epicenter, range) - for(var/turf/tile in range(range, epicenter)) - tile.ex_act(2) diff --git a/code/game/objects/explosion_recursive.dm b/code/game/objects/explosion_recursive.dm index 9a144e7e83..3b9d2ebc31 100644 --- a/code/game/objects/explosion_recursive.dm +++ b/code/game/objects/explosion_recursive.dm @@ -1,63 +1,11 @@ /client/proc/kaboom() var/power = tgui_input_number(src, "power?", "power?") var/turf/T = get_turf(src.mob) - explosion_rec(T, power) + explosion(T, power) /obj var/explosion_resistance -/proc/explosion_rec(turf/epicenter, power) - var/list/explosion_turfs = list() - var/explosion_in_progress = 0 - - var/loopbreak = 0 - while(explosion_in_progress) - if(loopbreak >= 15) return - spawn(10) - loopbreak++ - - if(power <= 0) return - epicenter = get_turf(epicenter) - if(!epicenter) return - - message_admins("Explosion with size ([power]) in area [epicenter.loc.name] ([epicenter.x],[epicenter.y],[epicenter.z])") - log_game("Explosion with size ([power]) in area [epicenter.loc.name] ") - - playsound(epicenter, 'sound/effects/explosionfar.ogg', 100, 1, round(power*2,1) ) - playsound(epicenter, "explosion", 100, 1, round(power,1) ) - - explosion_in_progress = 1 - explosion_turfs = list() - - explosion_turfs[epicenter] = power - - //This steap handles the gathering of turfs which will be ex_act() -ed in the next step. It also ensures each turf gets the maximum possible amount of power dealt to it. - for(var/direction in GLOB.cardinal) - var/turf/T = get_step(epicenter, direction) - T.explosion_spread(power - epicenter.explosion_resistance, direction, explosion_turfs) - - //This step applies the ex_act effects for the explosion, as planned in the previous step. - for(var/turf/T in explosion_turfs) - if(explosion_turfs[T] <= 0) continue - if(!T) continue - - //Wow severity looks confusing to calculate... Fret not, I didn't leave you with any additional instructions or help. (just kidding, see the line under the calculation) - var/severity = 4 - round(max(min( 3, ((explosion_turfs[T] - T.explosion_resistance) / (max(3,(power/3)))) ) ,1), 1) //sanity effective power on tile divided by either 3 or one third the total explosion power - // One third because there are three power levels and I - // want each one to take up a third of the crater - var/x = T.x - var/y = T.y - var/z = T.z - T.ex_act(severity) - if(!T) - T = locate(x,y,z) - for(var/atom_movable in T.contents) - var/atom/movable/AM = atom_movable - if(AM && AM.simulated) - AM.ex_act(severity) - - explosion_in_progress = 0 - /turf var/explosion_resistance @@ -87,31 +35,3 @@ /turf/simulated/wall explosion_resistance = 10 - -//Code-wise, a safe value for power is something up to ~25 or ~30.. This does quite a bit of damage to the station. -//direction is the direction that the spread took to come to this tile. So it is pointing in the main blast direction - meaning where this tile should spread most of it's force. -/turf/proc/explosion_spread(power, direction, var/list/explosion_turfs) - if(power <= 0) - return - if(src in explosion_turfs) - if(explosion_turfs[src] >= power) - return //The turf already sustained and spread a power greated than what we are dealing with. No point spreading again. - explosion_turfs[src] = power - - var/spread_power = power - src.explosion_resistance //This is the amount of power that will be spread to the tile in the direction of the blast - for(var/obj/O in src) - if(O.explosion_resistance) - spread_power -= O.explosion_resistance - - var/turf/T = get_step(src, direction) - if(T) - T.explosion_spread(spread_power, direction, explosion_turfs) - T = get_step(src, turn(direction,90)) - if(T) - T.explosion_spread(spread_power, turn(direction,90), explosion_turfs) - T = get_step(src, turn(direction,-90)) - if(T) - T.explosion_spread(spread_power, turn(direction,-90), explosion_turfs) - -/turf/unsimulated/explosion_spread(power) - return //So it doesn't get to the parent proc, which simulates explosions diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index dab54e532a..d0b20dbfc2 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -276,7 +276,7 @@ ADMIN_VERB(stealth, R_STEALTH, "Stealth Mode", "Toggle stealth.", "Admin.Game") set desc = "Cause an explosion of varying strength at your location." var/turf/epicenter = mob.loc - var/list/choices = list("Small Bomb", "Medium Bomb", "Big Bomb", "Custom Bomb", "Cancel") + var/list/choices = list("Small Bomb", "Medium Bomb", "Big Bomb", "Maxcap Bomb", "SM Blast", "Custom Bomb", "Cancel") var/choice = tgui_input_list(usr, "What size explosion would you like to produce?", "Explosion Choice", choices) switch(choice) if(null) @@ -289,6 +289,10 @@ ADMIN_VERB(stealth, R_STEALTH, "Stealth Mode", "Toggle stealth.", "Admin.Game") explosion(epicenter, 2, 3, 4, 4) if("Big Bomb") explosion(epicenter, 3, 5, 7, 5) + if("Maxcap Bomb") // Being able to test what players can legally make themselves sounds good, no?~ + explosion(epicenter, BOMBCAP_DVSTN_RADIUS, BOMBCAP_HEAVY_RADIUS, BOMBCAP_LIGHT_RADIUS, BOMBCAP_FLASH_RADIUS) + if("SM Blast") + explosion(epicenter, 8, 16, 24, 32) if("Custom Bomb") var/devastation_range = tgui_input_number(usr, "Devastation range (in tiles):") var/heavy_impact_range = tgui_input_number(usr, "Heavy impact range (in tiles):") diff --git a/code/modules/multiz/movement.dm b/code/modules/multiz/movement.dm index cfaf314985..06c6d5b8af 100644 --- a/code/modules/multiz/movement.dm +++ b/code/modules/multiz/movement.dm @@ -445,7 +445,7 @@ var/turf/oldloc = loc // Now lets move there! - if(!Move(landing)) + if(!Move(landing, direct = dir)) //infinite fall fix return 1 // Detect if we made a silent landing. diff --git a/code/modules/power/breaker_box.dm b/code/modules/power/breaker_box.dm index e861c0a452..f82f925b22 100644 --- a/code/modules/power/breaker_box.dm +++ b/code/modules/power/breaker_box.dm @@ -26,7 +26,7 @@ for(var/obj/structure/cable/C in src.loc) qdel(C) . = ..() - for(var/datum/tgui_module/rcon/R in world) + for(var/datum/tgui_module/rcon/R in SStgui.all_uis) R.FindDevices() /obj/machinery/power/breakerbox/Initialize(mapload) diff --git a/code/modules/power/cable.dm b/code/modules/power/cable.dm index 23215deced..145f00d5f1 100644 --- a/code/modules/power/cable.dm +++ b/code/modules/power/cable.dm @@ -281,6 +281,7 @@ GLOBAL_LIST_INIT(possible_cable_coil_colours, list( //handles merging diagonally matching cables //for info : direction^3 is flipping horizontally, direction^12 is flipping vertically /obj/structure/cable/proc/mergeDiagonalsNetworks(var/direction) + if(SSmachines.powernet_is_defered()) return; //search for and merge diagonally matching cables from the first direction component (north/south) var/turf/T = get_step(src, direction&3)//go north/south @@ -325,6 +326,7 @@ GLOBAL_LIST_INIT(possible_cable_coil_colours, list( // merge with the powernets of power objects in the given direction /obj/structure/cable/proc/mergeConnectedNetworks(var/direction) + if(SSmachines.powernet_is_defered()) return; var/fdir = direction ? GLOB.reverse_dir[direction] : 0 //flip the direction, to match with the source position on its turf @@ -353,6 +355,8 @@ GLOBAL_LIST_INIT(possible_cable_coil_colours, list( // merge with the powernets of power objects in the source turf /obj/structure/cable/proc/mergeConnectedNetworksOnTurf() + if(SSmachines.powernet_is_defered()) return; + var/list/to_connect = list() if(!powernet) //if we somehow have no powernet, make one (should not happen for cables) @@ -441,6 +445,8 @@ GLOBAL_LIST_INIT(possible_cable_coil_colours, list( //should be called after placing a cable which extends another cable, creating a "smooth" cable that no longer terminates in the centre of a turf. //needed as this can, unlike other placements, disconnect cables /obj/structure/cable/proc/denode() + if(SSmachines.powernet_is_defered()) return; + var/turf/T1 = loc if(!T1) return @@ -476,8 +482,9 @@ GLOBAL_LIST_INIT(possible_cable_coil_colours, list( loc = null powernet.remove_cable(src) //remove the cut cable from its powernet - var/datum/powernet/newPN = new()// creates a new powernet... - propagate_network(P_list[1], newPN)//... and propagates it to the other side of the cable + if(!SSmachines.powernet_is_defered()) // Deferring until rebuild + var/datum/powernet/newPN = new()// creates a new powernet... + propagate_network(P_list[1], newPN)//... and propagates it to the other side of the cable // Disconnect machines connected to nodes if(d1 == 0) // if we cut a node (O-X) cable diff --git a/code/modules/power/pacman2.dm b/code/modules/power/pacman2.dm index e0c09c5cf7..74d1862bb2 100644 --- a/code/modules/power/pacman2.dm +++ b/code/modules/power/pacman2.dm @@ -74,10 +74,11 @@ anchored = !anchored playsound(src, 'sound/items/Deconstruct.ogg', 50, 1) if(anchored) + connect_to_network() to_chat(user, span_blue("You secure the generator to the floor.")) else + disconnect_from_network() to_chat(user, span_blue("You unsecure the generator from the floor.")) - SSmachines.makepowernets() else if(O.has_tool_quality(TOOL_SCREWDRIVER)) open = !open playsound(src, O.usesound, 50, 1) diff --git a/code/modules/power/power.dm b/code/modules/power/power.dm index a84a94b766..03d0ad3039 100644 --- a/code/modules/power/power.dm +++ b/code/modules/power/power.dm @@ -66,6 +66,8 @@ // connect the machine to a powernet if a node cable is present on the turf /obj/machinery/power/proc/connect_to_network() + if(SSmachines.powernet_is_defered()) return 0; + var/turf/T = src.loc if(!T || !istype(T)) return 0 diff --git a/code/modules/reagents/machinery/dispenser/reagent_tank.dm b/code/modules/reagents/machinery/dispenser/reagent_tank.dm index da97b503e2..97fc37a3e2 100644 --- a/code/modules/reagents/machinery/dispenser/reagent_tank.dm +++ b/code/modules/reagents/machinery/dispenser/reagent_tank.dm @@ -276,7 +276,7 @@ explode() return ..() -/obj/structure/reagent_dispensers/fueltank/Move() +/obj/structure/reagent_dispensers/fueltank/Move(atom/newloc, direct, movetime) if (..() && modded) leak_fuel(amount_per_transfer_from_this/10.0) diff --git a/code/modules/vore/resizing/grav_pull_vr.dm b/code/modules/vore/resizing/grav_pull_vr.dm index b73fafca8f..1edc4b03d5 100644 --- a/code/modules/vore/resizing/grav_pull_vr.dm +++ b/code/modules/vore/resizing/grav_pull_vr.dm @@ -24,10 +24,6 @@ sleep(25) /datum/effect/effect/system/grav_pull/proc/do_pull() - //following is adapted from supermatter and singulo code - if(defer_powernet_rebuild != 2) - defer_powernet_rebuild = 1 - // Let's just make this one loop. for(var/X in orange(pull_radius, location)) // Movable atoms only @@ -58,6 +54,3 @@ step_towards(X, location) // Step twice step_towards(X, location) - - if(defer_powernet_rebuild != 2) - defer_powernet_rebuild = 0 diff --git a/vorestation.dme b/vorestation.dme index c94b8c0fcf..f7824d1fae 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -423,6 +423,7 @@ #include "code\controllers\subsystems\dbcore.dm" #include "code\controllers\subsystems\dcs.dm" #include "code\controllers\subsystems\events.dm" +#include "code\controllers\subsystems\explosions.dm" #include "code\controllers\subsystems\garbage.dm" #include "code\controllers\subsystems\holomaps.dm" #include "code\controllers\subsystems\inactivity.dm" @@ -1351,7 +1352,6 @@ #include "code\game\objects\banners_vr.dm" #include "code\game\objects\buckling.dm" #include "code\game\objects\empulse.dm" -#include "code\game\objects\explosion.dm" #include "code\game\objects\explosion_recursive.dm" #include "code\game\objects\items.dm" #include "code\game\objects\items_vr.dm"