mirror of
https://github.com/Aurorastation/Aurora.3.git
synced 2026-07-19 20:06:28 +01:00
Ported Map Effects (#10295)
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user