Ported Map Effects (#10295)

This commit is contained in:
Geeves
2020-10-22 17:22:18 +02:00
committed by GitHub
parent 0ad062dcb4
commit 5c8f46a2d9
12 changed files with 856 additions and 2 deletions
@@ -0,0 +1,176 @@
var/global/list/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 = -1 // 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()
LAZYADD(all_beam_points, src)
if(make_beams_on_init)
create_beams()
if(use_timer)
addtimer(CALLBACK(src, .proc/handle_beam_timer), initial_delay)
return ..()
/obj/effect/map_effect/beam_point/Destroy()
destroy_all_beams()
use_timer = FALSE
LAZYREMOVE(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(length(my_beams) >= max_beams)
return
var/beams_to_fill = max_beams - length(my_beams)
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 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(length(my_beams)) // 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
// Turns on and off on a timer.
/obj/effect/map_effect/beam_point/timer
use_timer = TRUE
// 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
@@ -0,0 +1,54 @@
// 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, src.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
// Makes sparks.
/obj/effect/map_effect/interval/effect_emitter/sparks
name = "spark emitter"
icon_state = "spark_emitter"
effect_system_type = /datum/effect_system/sparks
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
@@ -0,0 +1,69 @@
// 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 = 5 SECONDS // How long until we check for players again.
var/next_attempt = 0 // Next time we're going to do ACTUAL WORK
/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.
/obj/effect/map_effect/interval/Initialize()
. = ..()
START_PROCESSING(SSprocessing, src)
/obj/effect/map_effect/interval/Destroy()
STOP_PROCESSING(SSprocessing, src)
return ..()
// Override this for the specific thing to do.
/obj/effect/map_effect/interval/proc/trigger()
return
// Handles the delay and making sure it doesn't run when it would be bad.
/obj/effect/map_effect/interval/process()
//Not yet!
if(world.time < next_attempt)
return
// Check to see if we're useful first.
if(!always_run && !check_for_player_proximity(src, proximity_needed, ignore_ghosts, ignore_afk))
next_attempt = world.time + retry_delay
// Hey there's someone nearby.
else
next_attempt = world.time + rand(interval_lower_bound, interval_upper_bound)
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
@@ -0,0 +1,28 @@
// 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"
/obj/effect/map_effect/perma_light/brighter
name = "permanent light (bright)"
icon_state = "permalight"
light_range = 5
light_power = 3
light_color = "#FFFFFF"
/obj/effect/map_effect/perma_light/concentrated
name = "permanent light (concentrated)"
light_range = 2
light_power = 5
/obj/effect/map_effect/perma_light/concentrated/halogen
name = "permanent light (concentrated halogen)"
light_color = LIGHT_COLOR_HALOGEN
+345
View File
@@ -0,0 +1,345 @@
var/global/list/all_portal_masters
/*
Portal map effects allow a mapper to join two distant places together, while looking somewhat seamlessly connected.
This can allow for very strange PoIs that twist and turn in what appear to be physically impossible ways.
Portals do have some specific requirements when mapping them in;
- There must by one, and only one `/obj/effect/map_effect/portal/master` for each side of a portal.
- Both sides need to have matching `portal_id`s in order to link to each other.
- Each side must face opposite directions, e.g. if side A faces SOUTH, side B must face NORTH.
- Each side must have the same orientation, e.g. horizontal on both sides, or vertical on both sides.
- Portals can be made to be longer than 1x1 with `/obj/effect/map_effect/portal/line`s,
but both sides must have the same length.
- If portal lines are added, they must form a straight line and be next to a portal master or another portal line.
- If portal lines are used, both portal masters should be in the same relative position among the lines.
E.g. both being on the left most side on a horizontal row.
Portals also have some limitations to be aware of when mapping. Some of these are not an issue if you're trying to make an 'obvious' portal;
- The objects seen through portals are purely visual, which has many implications,
such as simple_mob AIs being blind to mobs on the other side of portals.
- Objects on the other side of a portal can be interacted with if the interaction has no range limitation,
or the distance between the two portal sides happens to be less than the interaction max range. Examine will probably work,
while picking up an item that appears to be next to you will fail.
- Sounds currently are not carried across portals.
- Mismatched lighting between each portal end can make the portal look obvious.
- Portals look weird when observing as a ghost, or otherwise when able to see through walls. Meson vision will also spoil the illusion.
- Walls that change icons based on neightboring walls can give away that a portal is nearby if both sides don't have a similar transition.
- Projectiles that pass through portals will generally work as intended, however aiming and firing upon someone on the other side of a portal
will likely be weird due to the click targeting the real position of the thing clicked instead of the apparent position.
Thrown objects suffer a similar fate.
- The tiles that are visually shown across a portal are determined based on visibility at the time of portal initialization,
and currently don't update, meaning that opacity changes are not reflected, e.g. a wall is deconstructed, or an airlock is opened.
- There is currently a small but somewhat noticable pause in mob movement when moving across a portal,
as a result of the mob's glide animation being inturrupted by a teleport.
- Gas is not transferred through portals, and ZAS is oblivious to them.
A lot of those limitations can potentially be solved with some more work. Otherwise, portals work best in static environments like Points of Interest,
when portals are shortly lived, or when portals are made to be obvious with special effects.
*/
/obj/effect/map_effect/portal
name = "portal subtype"
invisibility = 0
opacity = TRUE
layer = ON_TURF_LAYER
appearance_flags = PIXEL_SCALE|KEEP_TOGETHER // Removed TILE_BOUND so things not visible on the other side stay hidden from the viewer.
var/obj/effect/map_effect/portal/counterpart = null // The portal line or master that this is connected to, on the 'other side'.
// Information used to apply `pixel_[x|y]` offsets so that the visuals line up.
// Set automatically by `calculate_dimensions()`.
var/total_height = 0 // Measured in tiles.
var/total_width = 0
var/portal_distance_x = 0 // How far the portal is from the left edge, in tiles.
var/portal_distance_y = 0 // How far the portal is from the top edge.
/obj/effect/map_effect/portal/Destroy()
vis_contents = null
if(counterpart)
counterpart.counterpart = null // Disconnect our counterpart from us
counterpart = null // Now disconnect us from them.
return ..()
// Called when something touches the portal, and usually teleports them to the other side.
/obj/effect/map_effect/portal/Crossed(atom/movable/AM)
..()
if(!AM)
return
if(!counterpart)
return
go_through_portal(AM)
/obj/effect/map_effect/portal/proc/go_through_portal(atom/movable/AM)
// TODO: Find a way to fake the glide or something.
if(isliving(AM))
var/mob/living/L = AM
if(L.pulling)
var/atom/movable/pulled = L.pulling
L.stop_pulling()
// For some reason, trying to put the pulled object behind the person makes the drag stop and it doesn't even move to the other side.
// pulled.forceMove(get_turf(counterpart))
pulled.forceMove(counterpart.get_focused_turf())
L.forceMove(counterpart.get_focused_turf())
L.start_pulling(pulled)
else
L.forceMove(counterpart.get_focused_turf())
else
AM.forceMove(counterpart.get_focused_turf())
// 'Focused turf' is the turf directly in front of a portal,
// and it is used both as the destination when crossing, as well as the PoV for visuals.
/obj/effect/map_effect/portal/proc/get_focused_turf()
return get_step(get_turf(src), dir)
// Determines the size of the block of turfs inside `vis_contents`, and where the portal is in relation to that.
/obj/effect/map_effect/portal/proc/calculate_dimensions()
var/highest_x = 0
var/lowest_x = 0
var/highest_y = 0
var/lowest_y = 0
// First pass is for finding the top right corner.
for(var/thing in vis_contents)
var/turf/T = thing
if(T.x > highest_x)
highest_x = T.x
if(T.y > highest_y)
highest_y = T.y
lowest_x = highest_x
lowest_y = highest_y
// Second one is for the bottom left corner.
for(var/thing in vis_contents)
var/turf/T = thing
if(T.x < lowest_x)
lowest_x = T.x
if(T.y < lowest_y)
lowest_y = T.y
// Now calculate the dimensions.
total_width = (highest_x - lowest_x) + 1
total_height = (highest_y - lowest_y) + 1
// Find how far the portal is from the edges.
var/turf/focused_T = counterpart.get_focused_turf()
portal_distance_x = lowest_x - focused_T.x
portal_distance_y = lowest_y - focused_T.y
// Portal masters manage everything else involving portals.
// This is the base type. Use `/side_a` or `/side_b` with matching IDs for actual portals.
/obj/effect/map_effect/portal/master
name = "portal master"
var/portal_id = "test" // For a portal to be made, both the A and B sides need to share the same ID value.
var/list/portal_lines = list()
/obj/effect/map_effect/portal/master/Initialize()
LAZYADD(all_portal_masters, src)
LAZYADD(listening_objects, src)
find_lines()
..()
return INITIALIZE_HINT_LATELOAD
/obj/effect/map_effect/portal/master/LateInitialize()
find_counterparts()
make_visuals()
apply_offset()
/obj/effect/map_effect/portal/master/Destroy()
LAZYREMOVE(all_portal_masters, src)
LAZYREMOVE(listening_objects, src)
for(var/thing in portal_lines)
qdel(thing)
return ..()
/obj/effect/map_effect/portal/master/proc/find_lines()
var/list/dirs_to_search = list( turn(dir, 90), turn(dir, -90) )
for(var/dir_to_search in dirs_to_search)
var/turf/current_T = get_turf(src)
while(current_T)
current_T = get_step(current_T, dir_to_search)
var/obj/effect/map_effect/portal/line/line = locate() in current_T
if(line)
portal_lines += line
line.my_master = src
else
break
// Connects both sides of a portal together.
/obj/effect/map_effect/portal/master/proc/find_counterparts()
for(var/thing in all_portal_masters)
var/obj/effect/map_effect/portal/master/M = thing
if(M == src)
continue
if(M.counterpart)
continue
if(M.portal_id == src.portal_id)
counterpart = M
M.counterpart = src
if(portal_lines.len)
for(var/i = 1 to portal_lines.len)
var/obj/effect/map_effect/portal/line/our_line = portal_lines[i]
var/obj/effect/map_effect/portal/line/their_line = M.portal_lines[i]
our_line.counterpart = their_line
their_line.counterpart = our_line
break
if(!counterpart)
crash_with("Portal master [type] ([x],[y],[z]) could not find another portal master with a matching portal_id ([portal_id]).")
/obj/effect/map_effect/portal/master/proc/make_visuals()
var/list/observed_turfs = list()
for(var/thing in portal_lines + src)
var/obj/effect/map_effect/portal/P = thing
P.name = null
P.icon_state = null
if(!P.counterpart)
return
var/turf/T = P.counterpart.get_focused_turf()
P.vis_contents += T
var/list/things = list()
DVIEW(things, world.view, T, INVISIBILITY_LIGHTING)
for(var/turf/turf in things)
if(get_dir(turf, T) & P.dir)
if(turf in observed_turfs) // Avoid showing the same turf twice or more for improved performance.
continue
P.vis_contents += turf
observed_turfs += turf
P.calculate_dimensions()
// Shifts the portal's pixels in order to line up properly, as BYOND offsets the sprite when it holds multiple turfs inside `vis_contents`.
// This undos the shift that BYOND did.
/obj/effect/map_effect/portal/master/proc/apply_offset()
for(var/thing in portal_lines + src)
var/obj/effect/map_effect/portal/P = thing
P.pixel_x = WORLD_ICON_SIZE * P.portal_distance_x
P.pixel_y = WORLD_ICON_SIZE * P.portal_distance_y
// Allows portals to transfer emotes.
// Only portal masters do this to avoid flooding the other side with duplicate messages.
/obj/effect/map_effect/portal/master/see_emote(mob/M, text)
if(!counterpart)
return
var/turf/T = counterpart.get_focused_turf()
var/list/mobs_to_relay = list()
var/list/objs = list()
get_mobs_and_objs_in_view_fast(T, world.view, mobs_to_relay, objs)
for(var/thing in mobs_to_relay)
var/mob/mob = thing
var/rendered = "<span class='message'>[text]</span>"
mob.show_message(rendered)
..()
// Allows portals to transfer visible messages.
/obj/effect/map_effect/portal/master/show_message(msg, type, alt, alt_type)
if(!counterpart)
return
var/rendered = "<span class='message'>[msg]</span>"
var/turf/T = counterpart.get_focused_turf()
var/list/mobs_to_relay = list()
var/list/objs = list()
get_mobs_and_objs_in_view_fast(T, world.view, mobs_to_relay, objs)
for(var/thing in mobs_to_relay)
var/mob/mob = thing
mob.show_message(rendered)
..()
// Allows portals to transfer speech.
/obj/effect/map_effect/portal/master/hear_talk(mob/M, text, verb, datum/language/speaking)
if(!counterpart)
return
var/turf/T = counterpart.get_focused_turf()
var/list/mobs_to_relay = list()
var/list/objs = list()
get_mobs_and_objs_in_view_fast(T, world.view, mobs_to_relay, objs)
for(var/thing in mobs_to_relay)
var/mob/mob = thing
var/accent_icon = M.get_accent_icon(speaking, src)
var/name_used = M.GetVoice()
var/rendered = null
if(speaking)
rendered = "[accent_icon ? accent_icon + " " : ""]<span class='game say'><span class='name'>[name_used]</span> [speaking.format_message(text, verb)]</span>"
else
rendered = "<span class='game say'><span class='name'>[name_used]</span> [verb], <span class='message'><span class='body'>\"[text]\"</span></span></span>"
mob.show_message(rendered, 2)
..()
// Returns the position that an atom that's hopefully on the other side of the portal would be if it were really there.
// Z levels not taken into account.
/obj/effect/map_effect/portal/master/proc/get_apparent_position(atom/A)
if(!counterpart)
return null
var/turf/true_turf = get_turf(A)
var/obj/effect/map_effect/portal/master/other_master = counterpart
var/in_vis_contents = FALSE
for(var/thing in other_master.portal_lines + other_master)
var/obj/effect/map_effect/portal/P = thing
if(P in true_turf.vis_locs)
in_vis_contents = TRUE
break
if(!in_vis_contents)
return null // Not in vision of the other portal.
var/turf/their_focus = counterpart.get_focused_turf()
var/turf/our_focus = get_focused_turf()
var/relative_x = (true_turf.x - our_focus.x)
relative_x += SIGN(relative_x)
var/relative_y = (true_turf.y - our_focus.y)
relative_y += SIGN(relative_y)
return new /datum/position(their_focus.x + relative_x, their_focus.y + relative_y, our_focus.z)
/obj/effect/map_effect/portal/master/side_a
name = "portal master A"
icon_state = "portal_side_a"
// color = "#00FF00"
/obj/effect/map_effect/portal/master/side_b
name = "portal master B"
icon_state = "portal_side_b"
// color = "#FF0000"
// Portal lines extend out from the sides of portal masters,
// They let portals be longer than 1x1.
// Both sides MUST be the same length, meaning if side A is 1x3, side B must also be 1x3.
/obj/effect/map_effect/portal/line
name = "portal line"
var/obj/effect/map_effect/portal/master/my_master = null
/obj/effect/map_effect/portal/line/Destroy()
if(my_master)
my_master.portal_lines -= src
my_master = null
return ..()
/obj/effect/map_effect/portal/line/side_a
name = "portal line A"
icon_state = "portal_line_side_a"
/obj/effect/map_effect/portal/line/side_b
name = "portal line B"
icon_state = "portal_line_side_b"
@@ -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)
..()
@@ -0,0 +1,85 @@
// 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/punching
sounds_to_play = list(/decl/sound_category/punch_sound)
interval_lower_bound = 5
interval_upper_bound = 1 SECOND
/obj/effect/map_effect/interval/sound_emitter/explosions
sounds_to_play = list(/decl/sound_category/explosion_sound)
interval_lower_bound = 5 SECONDS
interval_upper_bound = 10 SECONDS
/obj/effect/map_effect/interval/sound_emitter/explosions/distant
sounds_to_play = list('sound/effects/explosionfar.ogg')
/obj/effect/map_effect/interval/sound_emitter/gunfight
sounds_to_play = list(/decl/sound_category/gunshots)
interval_lower_bound = 5
interval_upper_bound = 2 SECONDS
/obj/effect/map_effect/interval/sound_emitter/gunfight/ballistic
sounds_to_play = list(/decl/sound_category/gunshots/ballistic)
/obj/effect/map_effect/interval/sound_emitter/gunfight/energy
sounds_to_play = list(/decl/sound_category/gunshots/energy)
// I'm not sorry.
/obj/effect/map_effect/interval/sound_emitter/clownsteps
sounds_to_play = list(/decl/sound_category/clown_sound)
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
+2 -2
View File
@@ -322,5 +322,5 @@ proc/get_radio_key_from_channel(var/channel)
/obj/effect/speech_bubble
var/mob/parent
/mob/living/proc/GetVoice()
return name
/mob/proc/GetVoice()
return name