diff --git a/code/datums/beam.dm b/code/datums/beam.dm index 51b58f462e..07407a3ddd 100644 --- a/code/datums/beam.dm +++ b/code/datums/beam.dm @@ -59,6 +59,10 @@ return ..() /datum/beam/proc/Draw() + if(QDELETED(target) || !QDELETED(origin)) + qdel(src) + return + var/Angle = round(Get_Angle(origin,target)) var/matrix/rot_matrix = matrix() @@ -113,6 +117,8 @@ X.pixel_x = Pixel_x X.pixel_y = Pixel_y + X.on_drawn() + /obj/effect/ebeam mouse_opacity = 0 anchored = TRUE @@ -127,10 +133,53 @@ /obj/effect/ebeam/singularity_act() return +// Called when the beam datum finishes drawing and the ebeam object is placed correctly. +/obj/effect/ebeam/proc/on_drawn() + return + /obj/effect/ebeam/deadly/Crossed(atom/A) ..() A.ex_act(1) +// 'Reactive' beam parts do something when touched or stood in. +/obj/effect/ebeam/reactive + +/obj/effect/ebeam/reactive/initialize() + processing_objects += src + return ..() + +/obj/effect/ebeam/reactive/Destroy() + processing_objects -= src + return ..() + +/obj/effect/ebeam/reactive/on_drawn() + for(var/A in loc) + on_contact(A) + +/obj/effect/ebeam/reactive/Crossed(atom/A) + ..() + on_contact(A) + +/obj/effect/ebeam/reactive/process() + for(var/A in loc) + on_contact(A) + +// Override for things to do when someone touches the beam. +/obj/effect/ebeam/reactive/proc/on_contact(atom/movable/AM) + return + + +// Shocks things that touch it. +/obj/effect/ebeam/reactive/electric + var/shock_amount = 25 // Be aware that high numbers may stun and result in dying due to not being able to get out of the beam. + +/obj/effect/ebeam/reactive/electric/on_contact(atom/movable/AM) + if(isliving(AM)) + var/mob/living/L = AM + L.inflict_shock_damage(shock_amount) + + + /atom/proc/Beam(atom/BeamTarget,icon_state="b_beam",icon='icons/effects/beam.dmi',time=50, maxdistance=10,beam_type=/obj/effect/ebeam,beam_sleep_time=3) var/datum/beam/newbeam = new(src,BeamTarget,icon,icon_state,time,maxdistance,beam_type,beam_sleep_time) spawn(0) diff --git a/code/game/objects/effects/effect_system.dm b/code/game/objects/effects/effect_system.dm index e92268f4da..6d9bb74faa 100644 --- a/code/game/objects/effects/effect_system.dm +++ b/code/game/objects/effects/effect_system.dm @@ -225,15 +225,16 @@ steam.start() -- spawns the effect /obj/effect/effect/smoke/bad/Move() ..() - for(var/mob/living/carbon/M in get_turf(src)) - affect(M) + for(var/mob/living/L in get_turf(src)) + affect(L) -/obj/effect/effect/smoke/bad/affect(var/mob/living/carbon/M) +/obj/effect/effect/smoke/bad/affect(var/mob/living/L) if (!..()) return 0 - M.adjustOxyLoss(1) - if(prob(25)) - M.emote("cough") + if(L.needs_to_breathe()) + L.adjustOxyLoss(1) + if(prob(25)) + L.emote("cough") /* Not feasile until a later date /obj/effect/effect/smoke/bad/Crossed(atom/movable/M as mob|obj) @@ -251,6 +252,72 @@ steam.start() -- spawns the effect projectiles -= proj */ +///////////////////////////////////////////// +// 'Elemental' smoke +///////////////////////////////////////////// + +/obj/effect/effect/smoke/elemental + name = "cloud" + desc = "A cloud of some kind that seems really generic and boring." + opacity = FALSE + var/strength = 5 // How much damage to do inside each affect() + +/obj/effect/effect/smoke/elemental/initialize() + processing_objects += src + return ..() + +/obj/effect/effect/smoke/elemental/Destroy() + processing_objects -= src + return ..() + +/obj/effect/effect/smoke/elemental/Move() + ..() + for(var/mob/living/L in range(1, src)) + affect(L) + +/obj/effect/effect/smoke/elemental/process() + for(var/mob/living/L in range(1, src)) + affect(L) + + +/obj/effect/effect/smoke/elemental/fire + name = "burning cloud" + desc = "A cloud of something that is on fire." + color = "#FF9933" + light_color = "#FF0000" + light_range = 2 + light_power = 5 + +/obj/effect/effect/smoke/elemental/fire/affect(mob/living/L) + L.inflict_heat_damage(strength) + L.add_modifier(/datum/modifier/fire, 6 SECONDS) // Around 15 damage per stack. + +/obj/effect/effect/smoke/elemental/frost + name = "freezing cloud" + desc = "A cloud filled with brutally cold mist." + color = "#00CCFF" + +/obj/effect/effect/smoke/elemental/frost/affect(mob/living/L) + L.inflict_cold_damage(strength) + +/obj/effect/effect/smoke/elemental/shock + name = "charged cloud" + desc = "A cloud charged with electricity." + color = "#4D4D4D" + +/obj/effect/effect/smoke/elemental/shock/affect(mob/living/L) + L.inflict_shock_damage(strength) + +/obj/effect/effect/smoke/elemental/mist + name = "misty cloud" + desc = "A cloud filled with water vapor." + color = "#CCFFFF" + alpha = 128 + strength = 1 + +/obj/effect/effect/smoke/elemental/mist/affect(mob/living/L) + L.water_act(strength) + ///////////////////////////////////////////// // Smoke spread ///////////////////////////////////////////// @@ -282,7 +349,8 @@ steam.start() -- spawns the effect src.location = get_turf(holder) var/obj/effect/effect/smoke/smoke = new smoke_type(src.location) src.total_smoke++ - smoke.color = I + if(I) + smoke.color = I var/direction = src.direction if(!direction) if(src.cardinals) @@ -296,10 +364,21 @@ steam.start() -- spawns the effect if (smoke) qdel(smoke) src.total_smoke-- - /datum/effect/effect/system/smoke_spread/bad smoke_type = /obj/effect/effect/smoke/bad +/datum/effect/effect/system/smoke_spread/fire + smoke_type = /obj/effect/effect/smoke/elemental/fire + +/datum/effect/effect/system/smoke_spread/frost + smoke_type = /obj/effect/effect/smoke/elemental/frost + +/datum/effect/effect/system/smoke_spread/shock + smoke_type = /obj/effect/effect/smoke/elemental/shock + +/datum/effect/effect/system/smoke_spread/mist + smoke_type = /obj/effect/effect/smoke/elemental/mist + ///////////////////////////////////////////// //////// Attach an Ion trail to any object, that spawns when it moves (like for the jetpack) /// just pass in the object to attach it to in set_up diff --git a/code/game/objects/effects/map_effects/beam_point.dm b/code/game/objects/effects/map_effects/beam_point.dm new file mode 100644 index 0000000000..de6e2d58a5 --- /dev/null +++ b/code/game/objects/effects/map_effects/beam_point.dm @@ -0,0 +1,192 @@ +GLOBAL_LIST_EMPTY(all_beam_points) + +// Creates and manages a beam attached to itself and another beam_point. +// You can do cool things with these such as moving the beam_point to move the beam, turning them on and off on a timer, triggered by external input, and more. +/obj/effect/map_effect/beam_point + name = "beam point" + icon_state = "beam_point" + + // General variables. + var/list/my_beams = list() // Instances of beams. Deleting one will kill the beam. + var/id = "A" // Two beam_points must share the same ID to be connected to each other. + var/max_beams = 10 // How many concurrent beams to seperate beam_points to have at once. Set to zero to only act as targets for other beam_points. + var/seek_range = 7 // How far to look for an end beam_point when not having a beam. Defaults to screen height/width. Make sure this is below beam_max_distance. + + // Controls how and when the beam is created. + var/make_beams_on_init = FALSE + var/use_timer = FALSE // Sadly not the /tg/ timers. + var/list/on_duration = list(2 SECONDS, 2 SECONDS, 2 SECONDS) // How long the beam should stay on for, if use_timer is true. Alternates between each duration in the list. + var/list/off_duration = list(3 SECONDS, 0.5 SECOND, 0.5 SECOND) // How long it should stay off for. List length is not needed to be the same as on_duration. + var/timer_on_index = 1 // Index to use for on_duration list. + var/timer_off_index = 1// Ditto, for off_duration list. + var/initial_delay = 0 // How long to wait before first turning on the beam, to sync beam times or create a specific pattern. + var/beam_creation_sound = null // Optional sound played when one or more beams are created. + var/beam_destruction_sound = null // Optional sound played when a beam is destroyed. + + // Beam datum arguments. + var/beam_icon = 'icons/effects/beam.dmi' // Icon file to use for beam visuals. + var/beam_icon_state = "b_beam" // Icon state to use for visuals. + var/beam_time = INFINITY // How long the beam lasts. By default it will last forever until destroyed. + var/beam_max_distance = 10 // If the beam is farther than this, it will be destroyed. Make sure it's higher than seek_range. + var/beam_type = /obj/effect/ebeam // The type of beam. Default has no special properties. Some others may do things like hurt things touching it. + var/beam_sleep_time = 3 // How often the beam updates visually. Suggested to leave this alone, 3 is already fast. + +/obj/effect/map_effect/beam_point/initialize() + GLOB.all_beam_points += src + if(make_beams_on_init) + create_beams() + if(use_timer) + spawn(initial_delay) + handle_beam_timer() + return ..() + +/obj/effect/map_effect/beam_point/Destroy() + destroy_all_beams() + use_timer = FALSE + GLOB.all_beam_points -= src + return ..() + +// This is the top level proc to make the magic happen. +/obj/effect/map_effect/beam_point/proc/create_beams() + if(my_beams.len >= max_beams) + return + var/beams_to_fill = max_beams - my_beams.len + for(var/i = 1 to beams_to_fill) + var/obj/effect/map_effect/beam_point/point = seek_beam_point() + if(!point) + break // No more points could be found, no point checking repeatively. + build_beam(point) + +// Finds a suitable beam point. +/obj/effect/map_effect/beam_point/proc/seek_beam_point() + for(var/obj/effect/map_effect/beam_point/point in GLOB.all_beam_points) + if(id != point.id) + continue // Not linked together by ID. + if(has_active_beam(point)) + continue // Already got one. + if(point.z != src.z) + continue // Not on same z-level. get_dist() ignores z-levels by design according to docs. + if(get_dist(src, point) > seek_range) + continue // Too far. + return point + +// Checks if the two points have an active beam between them. +// Used to make sure two points don't have more than one beam. +/obj/effect/map_effect/beam_point/proc/has_active_beam(var/obj/effect/map_effect/beam_point/them) + // First, check our beams. + for(var/datum/beam/B in my_beams) + if(B.target == them) + return TRUE + if(B.origin == them) // This shouldn't be needed unless the beam gets built backwards but why not. + return TRUE + + // Now check theirs, to see if they have a beam on us. + for(var/datum/beam/B in them.my_beams) + if(B.target == src) + return TRUE + if(B.origin == src) // Same story as above. + return TRUE + + return FALSE + +/obj/effect/map_effect/beam_point/proc/build_beam(var/atom/beam_target) + if(!beam_target) + log_debug("[src] ([src.type] \[[x],[y],[z]\]) failed to build its beam due to not having a target.") + return FALSE + + var/datum/beam/new_beam = Beam(beam_target, beam_icon_state, beam_icon, beam_time, beam_max_distance, beam_type, beam_sleep_time) + my_beams += new_beam + if(beam_creation_sound) + playsound(src, beam_creation_sound, 70, 1) + + return TRUE + +/obj/effect/map_effect/beam_point/proc/destroy_beam(var/datum/beam/B) + if(!B) + log_debug("[src] ([src.type] \[[x],[y],[z]\]) was asked to destroy a beam that does not exist.") + return FALSE + + if(!(B in my_beams)) + log_debug("[src] ([src.type] \[[x],[y],[z]\]) was asked to destroy a beam it did not own.") + return FALSE + + my_beams -= B + qdel(B) + if(beam_destruction_sound) + playsound(src, beam_destruction_sound, 70, 1) + + return TRUE + +/obj/effect/map_effect/beam_point/proc/destroy_all_beams() + for(var/datum/beam/B in my_beams) + destroy_beam(B) + return TRUE + +// This code makes me sad. +/obj/effect/map_effect/beam_point/proc/handle_beam_timer() + if(!use_timer || QDELETED(src)) + return + + if(my_beams.len) // Currently on. + destroy_all_beams() + color = "#FF0000" + + timer_off_index++ + if(timer_off_index > off_duration.len) + timer_off_index = 1 + + spawn(off_duration[timer_off_index]) + .() + + else // Currently off. + // If nobody's around, keep the beams off to avoid wasteful beam process(), if they have one. + if(!always_run && !check_for_player_proximity(src, proximity_needed, ignore_ghosts, ignore_afk)) + spawn(retry_delay) + .() + return + + create_beams() + color = "#00FF00" + + timer_on_index++ + if(timer_on_index > on_duration.len) + timer_on_index = 1 + + spawn(on_duration[timer_on_index]) + .() + + + +// Subtypes to use in maps and adminbuse. +// Remember, beam_points ONLY connect to other beam_points with the same id variable. + +// Creates the beam when instantiated and stays on until told otherwise. +/obj/effect/map_effect/beam_point/instant + make_beams_on_init = TRUE + +/obj/effect/map_effect/beam_point/instant/electric + beam_icon_state = "nzcrentrs_power" + beam_type = /obj/effect/ebeam/reactive/electric + beam_creation_sound = 'sound/effects/lightningshock.ogg' + beam_destruction_sound = "sparks" + +// Turns on and off on a timer. +/obj/effect/map_effect/beam_point/timer + use_timer = TRUE + +// Shocks people who touch the beam while it's on. Flicks on and off on a specific pattern. +/obj/effect/map_effect/beam_point/timer/electric + beam_icon_state = "nzcrentrs_power" + beam_type = /obj/effect/ebeam/reactive/electric + beam_creation_sound = 'sound/effects/lightningshock.ogg' + beam_destruction_sound = "sparks" + seek_range = 3 + +// Is only a target for other beams to connect to. +/obj/effect/map_effect/beam_point/end + max_beams = 0 + +// Can only have one beam. +/obj/effect/map_effect/beam_point/mono + make_beams_on_init = TRUE + max_beams = 1 diff --git a/code/game/objects/effects/map_effects/effect_emitter.dm b/code/game/objects/effects/map_effects/effect_emitter.dm new file mode 100644 index 0000000000..6a7b599ccc --- /dev/null +++ b/code/game/objects/effects/map_effects/effect_emitter.dm @@ -0,0 +1,78 @@ +// Creates effects like smoke clouds every so often. +/obj/effect/map_effect/interval/effect_emitter + var/datum/effect/effect/system/effect_system = null + var/effect_system_type = null // Which effect system to attach. + + var/effect_amount = 10 // How many effect objects to create on each interval. Note that there's a hard cap on certain effect_systems. + var/effect_cardinals_only = FALSE // If true, effects only move in cardinal directions. + var/effect_forced_dir = null // If set, effects emitted will always move in this direction. + +/obj/effect/map_effect/interval/effect_emitter/initialize() + effect_system = new effect_system_type() + effect_system.attach(src) + configure_effects() + return ..() + +/obj/effect/map_effect/interval/effect_emitter/interval/Destroy() + QDEL_NULL(effect_system) + return ..() + +/obj/effect/map_effect/interval/effect_emitter/proc/configure_effects() + effect_system.set_up(effect_amount, effect_cardinals_only, usr.loc, effect_forced_dir) + +/obj/effect/map_effect/interval/effect_emitter/trigger() + configure_effects() // We do this every interval in case it changes. + effect_system.start() + ..() + + +// Creates smoke clouds every so often. +/obj/effect/map_effect/interval/effect_emitter/smoke + name = "smoke emitter" + icon_state = "smoke_emitter" + effect_system_type = /datum/effect/effect/system/smoke_spread + + interval_lower_bound = 1 SECOND + interval_upper_bound = 1 SECOND + effect_amount = 2 + +/obj/effect/map_effect/interval/effect_emitter/smoke/bad + name = "bad smoke emitter" + effect_system_type = /datum/effect/effect/system/smoke_spread/bad + +/obj/effect/map_effect/interval/effect_emitter/smoke/fire + name = "fire smoke emitter" + effect_system_type = /datum/effect/effect/system/smoke_spread/fire + +/obj/effect/map_effect/interval/effect_emitter/smoke/frost + name = "frost smoke emitter" + effect_system_type = /datum/effect/effect/system/smoke_spread/frost + +/obj/effect/map_effect/interval/effect_emitter/smoke/shock + name = "shock smoke emitter" + effect_system_type = /datum/effect/effect/system/smoke_spread/shock + +/obj/effect/map_effect/interval/effect_emitter/smoke/mist + name = "mist smoke emitter" + effect_system_type = /datum/effect/effect/system/smoke_spread/mist + + +// Makes sparks. +/obj/effect/map_effect/interval/effect_emitter/sparks + name = "spark emitter" + icon_state = "spark_emitter" + effect_system_type = /datum/effect/effect/system/spark_spread + + interval_lower_bound = 3 SECONDS + interval_upper_bound = 7 SECONDS + +/obj/effect/map_effect/interval/effect_emitter/sparks/frequent + effect_amount = 4 // Otherwise it caps out fast. + interval_lower_bound = 1 + interval_upper_bound = 3 SECONDS + +// Makes ""steam"" that looks like fire extinguisher water except it does nothing. +/obj/effect/map_effect/interval/effect_emitter/steam + name = "steam emitter" + icon_state = "smoke_emitter" + effect_system_type = /datum/effect/effect/system/steam_spread diff --git a/code/game/objects/effects/map_effects/map_effects.dm b/code/game/objects/effects/map_effects/map_effects.dm new file mode 100644 index 0000000000..994e418f59 --- /dev/null +++ b/code/game/objects/effects/map_effects/map_effects.dm @@ -0,0 +1,71 @@ +// These are objects you can use inside special maps (like PoIs), or for adminbuse. +// Players cannot see or interact with these. +/obj/effect/map_effect + anchored = TRUE + invisibility = 99 // So a badmin can go view these by changing their see_invisible. + icon = 'icons/effects/map_effects.dmi' + + // Below vars concern check_for_player_proximity() and is used to not waste effort if nobody is around to appreciate the effects. + var/always_run = FALSE // If true, the game will not try to suppress this from firing if nobody is around to see it. + var/proximity_needed = 12 // How many tiles a mob with a client must be for this to run. + var/ignore_ghosts = FALSE // If true, ghosts won't satisfy the above requirement. + var/ignore_afk = TRUE // If true, AFK people (5 minutes) won't satisfy it as well. + var/retry_delay = 3 SECONDS // How long until we check for players again. + +/obj/effect/map_effect/ex_act() + return + +/obj/effect/map_effect/singularity_pull() + return + +/obj/effect/map_effect/singularity_act() + return + +// Base type for effects that run on variable intervals. +/obj/effect/map_effect/interval + var/interval_lower_bound = 5 SECONDS // Lower number for how often the map_effect will trigger. + var/interval_upper_bound = 5 SECONDS // Higher number for above. + var/halt = FALSE // Set to true to stop the loop when it reaches the next iteration. + +/obj/effect/map_effect/interval/initialize() + handle_interval_delay() + return ..() + +/obj/effect/map_effect/interval/Destroy() + halt = TRUE // Shouldn't need it to GC but just in case. + return ..() + +// Override this for the specific thing to do. Be sure to call parent to keep looping. +/obj/effect/map_effect/interval/proc/trigger() + handle_interval_delay() + +// Handles the delay and making sure it doesn't run when it would be bad. +/obj/effect/map_effect/interval/proc/handle_interval_delay() + // Check to see if we're useful first. + if(halt) + return // Do not pass .(), do not recursively collect 200 thaler. + + if(!always_run && !check_for_player_proximity(src, proximity_needed, ignore_ghosts, ignore_afk)) + spawn(retry_delay) // Maybe someday we'll have fancy TG timers/schedulers. + if(!QDELETED(src)) + .() + + var/next_interval = rand(interval_lower_bound, interval_upper_bound) + spawn(next_interval) + if(!QDELETED(src)) + trigger() + +// Helper proc to optimize the use of effects by making sure they do not run if nobody is around to perceive it. +/proc/check_for_player_proximity(var/atom/proximity_to, var/radius = 12, var/ignore_ghosts = FALSE, var/ignore_afk = TRUE) + if(!proximity_to) + return FALSE + + for(var/thing in player_list) + var/mob/M = thing // Avoiding typechecks for more speed, player_list will only contain mobs anyways. + if(ignore_ghosts && isobserver(M)) + continue + if(ignore_afk && M.client && M.client.is_afk(5 MINUTES)) + continue + if(M.z == proximity_to.z && get_dist(M, proximity_to) <= radius) + return TRUE + return FALSE \ No newline at end of file diff --git a/code/game/objects/effects/map_effects/perma_light.dm b/code/game/objects/effects/map_effects/perma_light.dm new file mode 100644 index 0000000000..3a82dc7e69 --- /dev/null +++ b/code/game/objects/effects/map_effects/perma_light.dm @@ -0,0 +1,9 @@ +// Emits light forever with magic. Useful for mood lighting in Points of Interest. +// Be sure to check how it looks ingame, and fiddle with the settings until it looks right. +/obj/effect/map_effect/perma_light + name = "permanent light" + icon_state = "permalight" + + light_range = 3 + light_power = 1 + light_color = "#FFFFFF" \ No newline at end of file diff --git a/code/game/objects/effects/map_effects/radiation_emitter.dm b/code/game/objects/effects/map_effects/radiation_emitter.dm new file mode 100644 index 0000000000..7af2a1af32 --- /dev/null +++ b/code/game/objects/effects/map_effects/radiation_emitter.dm @@ -0,0 +1,19 @@ +// Constantly emites radiation from the tile it's placed on. +/obj/effect/map_effect/radiation_emitter + name = "radiation emitter" + icon_state = "radiation_emitter" + var/radiation_power = 30 // Bigger numbers means more radiation. + +/obj/effect/map_effect/radiation_emitter/initialize() + processing_objects += src + return ..() + +/obj/effect/map_effect/radiation_emitter/Destroy() + processing_objects -= src + return ..() + +/obj/effect/map_effect/radiation_emitter/process() + radiation_repository.radiate(src, radiation_power) + +/obj/effect/map_effect/radiation_emitter/strong + radiation_power = 100 \ No newline at end of file diff --git a/code/game/objects/effects/map_effects/screen_shaker.dm b/code/game/objects/effects/map_effects/screen_shaker.dm new file mode 100644 index 0000000000..29568242e5 --- /dev/null +++ b/code/game/objects/effects/map_effects/screen_shaker.dm @@ -0,0 +1,18 @@ +// Makes the screen shake for nearby players every so often. +/obj/effect/map_effect/interval/screen_shaker + name = "screen shaker" + icon_state = "screen_shaker" + + interval_lower_bound = 1 SECOND + interval_upper_bound = 2 SECONDS + + var/shake_radius = 7 // How far the shaking effect extends to. By default it is one screen length. + var/shake_duration = 2 // How long the shaking lasts. + var/shake_strength = 1 // How much it shakes. + +/obj/effect/map_effect/interval/screen_shaker/trigger() + for(var/A in player_list) + var/mob/M = A + if(M.z == src.z && get_dist(src, M) <= shake_radius) + shake_camera(M, shake_duration, shake_strength) + ..() \ No newline at end of file diff --git a/code/game/objects/effects/map_effects/sound_emitter.dm b/code/game/objects/effects/map_effects/sound_emitter.dm new file mode 100644 index 0000000000..4f30a3fdc6 --- /dev/null +++ b/code/game/objects/effects/map_effects/sound_emitter.dm @@ -0,0 +1,117 @@ +// Plays a sound at its location every so often. +/obj/effect/map_effect/interval/sound_emitter + name = "sound emitter" + icon_state = "sound_emitter" + var/list/sounds_to_play = list(null) // List containing sound files or strings of sound groups. + // A sound or string is picked randomly each run. + + var/sound_volume = 50 // How loud the sound is. 0 is silent, and 100 is loudest. Please be reasonable with the volume. + // Note that things like vacuum may affect the volume heard by other mobs. + + var/sound_frequency_variance = TRUE // If the sound will sound somewhat different each time. + // If a specific frequency is desired, sound_frequency must also be set. + + var/sound_extra_range = 0 // Set to make sounds heard from farther away than normal. + + var/sound_fallout = 0 // Within the 'fallout distance', the sound stays at the same volume, otherwise it attenuates. + // Higher numbers make the sound fade out more slowly with distance. + + var/sound_global = FALSE // If true, sounds will not be distorted due to the current area's 'sound environment'. + // It DOES NOT make the sound have a constant volume or z-level wide range, despite the misleading name. + + var/sound_frequency = null // Sets a specific custom frequency. sound_frequency_variance must be true as well. + // If sound_frequency is null, but sound_frequency_variance is true, a semi-random frequency will be chosen to the sound each time. + + var/sound_channel = 0 // BYOND allows a sound to play in 1 through 1024 sound channels. + // 0 will have BYOND give it the lowest available channel, it is not recommended to change this without a good reason. + + var/sound_pressure_affected = TRUE // If false, people in low pressure or vacuum will hear the sound. + + var/sound_ignore_walls = TRUE // If false, walls will completely muffle the sound. + + var/sound_preference = null // Player preference to check before playing this sound to them, if any. + +/obj/effect/map_effect/interval/sound_emitter/trigger() + playsound( + src, + pick(sounds_to_play), + sound_volume, + sound_frequency_variance, + sound_extra_range, + sound_fallout, + sound_global, + sound_frequency, + sound_channel, + sound_pressure_affected, + sound_ignore_walls, + sound_preference + ) + ..() + +/obj/effect/map_effect/interval/sound_emitter/thunder + sounds_to_play = list("thunder") + interval_lower_bound = 10 SECONDS + interval_upper_bound = 15 SECONDS + +/obj/effect/map_effect/interval/sound_emitter/geiger + sounds_to_play = list("geiger") + interval_lower_bound = 2 SECONDS + interval_upper_bound = 2 SECONDS + +/obj/effect/map_effect/interval/sound_emitter/geiger_weak + sounds_to_play = list("geiger_weak") + interval_lower_bound = 1 SECOND + interval_upper_bound = 1 SECOND + +/obj/effect/map_effect/interval/sound_emitter/punching + sounds_to_play = list("punch") + interval_lower_bound = 5 + interval_upper_bound = 1 SECOND + +/obj/effect/map_effect/interval/sound_emitter/explosions + sounds_to_play = list("explosion") + interval_lower_bound = 5 SECONDS + interval_upper_bound = 10 SECONDS + +/obj/effect/map_effect/interval/sound_emitter/distant_explosions + sounds_to_play = list('sound/effects/explosionfar.ogg') + interval_lower_bound = 5 SECONDS + interval_upper_bound = 10 SECONDS + +/obj/effect/map_effect/interval/sound_emitter/ballistic_gunfight + sounds_to_play = list( + 'sound/weapons/gunshot.ogg', + 'sound/weapons/deagle.ogg', + 'sound/weapons/rifleshot.ogg', + 'sound/weapons/sniper.ogg', + 'sound/weapons/shotgun.ogg', + 'sound/weapons/gunshot3.ogg', + 'sound/weapons/machinegun.ogg' + ) + interval_lower_bound = 5 + interval_upper_bound = 2 SECONDS + +/obj/effect/map_effect/interval/sound_emitter/energy_gunfight + sounds_to_play = list( + 'sound/weapons/Taser.ogg', + 'sound/weapons/laser.ogg', + 'sound/weapons/eLuger.ogg', + 'sound/weapons/laser3.ogg', + 'sound/weapons/pulse.ogg', + 'sound/weapons/gauss_shoot.ogg', + 'sound/weapons/emitter.ogg' + ) + interval_lower_bound = 5 + interval_upper_bound = 2 SECONDS + + +// I'm not sorry. +/obj/effect/map_effect/interval/sound_emitter/clownsteps + sounds_to_play = list("clownstep") + interval_lower_bound = 5 + interval_upper_bound = 1 SECOND + +/obj/effect/map_effect/interval/sound_emitter/bikehorns + sounds_to_play = list('sound/items/bikehorn.ogg') + interval_lower_bound = 5 + interval_upper_bound = 1 SECOND diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 969da63807..a3f563183f 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -387,3 +387,8 @@ if(isSynthetic()) return 0 return !(species.flags & NO_PAIN) + +/mob/living/carbon/needs_to_breathe() + if(does_not_breathe) + return FALSE + return ..() diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 00fccaa413..1658644171 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -529,7 +529,7 @@ default behaviour is: // and one for electricity because why not /mob/living/proc/inflict_shock_damage(amount) - electrocute_act(amount, null, 1 - get_shock_protection()) + electrocute_act(amount, null, 1 - get_shock_protection(), pick(BP_HEAD, BP_TORSO, BP_GROIN)) // also one for water (most things resist it entirely, except for slimes) /mob/living/proc/inflict_water_damage(amount) @@ -1270,4 +1270,7 @@ default behaviour is: /mob/living/proc/dirties_floor() // If we ever decide to add fancy conditionals for making dirty floors (floating, etc), here's the proc. - return makes_dirt \ No newline at end of file + return makes_dirt + +/mob/living/proc/needs_to_breathe() + return !isSynthetic() \ No newline at end of file diff --git a/code/modules/power/lighting.dm b/code/modules/power/lighting.dm index 6417889dcd..7521ddcf39 100644 --- a/code/modules/power/lighting.dm +++ b/code/modules/power/lighting.dm @@ -176,6 +176,11 @@ var/rigged = 0 // true if rigged to explode + var/auto_flicker = FALSE // If true, will constantly flicker, so long as someone is around to see it (otherwise its a waste of CPU). + +/obj/machinery/light/flicker + auto_flicker = TRUE + // the smaller bulb light fixture /obj/machinery/light/small @@ -187,6 +192,9 @@ desc = "A small lighting fixture." light_type = /obj/item/weapon/light/bulb +/obj/machinery/light/small/flicker + auto_flicker = TRUE + /obj/machinery/light/flamp icon_state = "flamp1" base_state = "flamp" @@ -199,10 +207,18 @@ light_type = /obj/item/weapon/light/bulb var/lamp_shade = 1 +/obj/machinery/light/flamp/flicker + auto_flicker = TRUE + + /obj/machinery/light/small/emergency brightness_range = 4 brightness_color = "#da0205" +/obj/machinery/light/small/emergency/flicker + auto_flicker = TRUE + + /obj/machinery/light/spot name = "spotlight" fitting = "large tube" @@ -210,6 +226,10 @@ brightness_range = 12 brightness_power = 0.9 +/obj/machinery/light/spot/flicker + auto_flicker = TRUE + + /obj/machinery/light/built/New() status = LIGHT_EMPTY update(0) @@ -292,7 +312,8 @@ update_icon() if(on) if(light_range != brightness_range || light_power != brightness_power || light_color != brightness_color) - switchcount++ + if(!auto_flicker) + switchcount++ if(rigged) if(status == LIGHT_OK && trigger) @@ -675,6 +696,13 @@ if(on) use_power(light_range * LIGHTING_POWER_FACTOR, LIGHT) + if(auto_flicker && !flickering) + if(check_for_player_proximity(src, radius = 12, ignore_ghosts = FALSE, ignore_afk = TRUE)) + seton(TRUE) // Lights must be on to flicker. + flicker(5) + else + seton(FALSE) // Otherwise keep it dark and spooky for when someone shows up. + // called when area power state changes /obj/machinery/light/power_change() diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi index e45ddcbb0d..e2286cb6f3 100644 Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ diff --git a/icons/effects/map_effects.dmi b/icons/effects/map_effects.dmi new file mode 100644 index 0000000000..4f5bff755d Binary files /dev/null and b/icons/effects/map_effects.dmi differ diff --git a/polaris.dme b/polaris.dme index 2d0a1cd2d6..4124ce6f02 100644 --- a/polaris.dme +++ b/polaris.dme @@ -833,6 +833,13 @@ #include "code\game\objects\effects\decals\Cleanable\tracks.dm" #include "code\game\objects\effects\decals\posters\bs12.dm" #include "code\game\objects\effects\decals\posters\polarisposters.dm" +#include "code\game\objects\effects\map_effects\beam_point.dm" +#include "code\game\objects\effects\map_effects\effect_emitter.dm" +#include "code\game\objects\effects\map_effects\map_effects.dm" +#include "code\game\objects\effects\map_effects\perma_light.dm" +#include "code\game\objects\effects\map_effects\radiation_emitter.dm" +#include "code\game\objects\effects\map_effects\screen_shaker.dm" +#include "code\game\objects\effects\map_effects\sound_emitter.dm" #include "code\game\objects\effects\spawners\bombspawner.dm" #include "code\game\objects\effects\spawners\gibspawner.dm" #include "code\game\objects\effects\temporary_visuals\miscellaneous.dm"