Files
fulpstation/code/__HELPERS/visual_effects.dm
John Willard 7199947c08 [MDB IGNORE] [IDB IGNORE] WIP TGU (#1427)
Several months worth of updates.

---------

Co-authored-by: A miscellaneous Fern <80640114+FernandoJ8@users.noreply.github.com>
Co-authored-by: Pepsilawn <reisenrui@gmail.com>
Co-authored-by: Ray <64306407+OneAsianTortoise@users.noreply.github.com>
Co-authored-by: Cure221 <106662180+Cure221@users.noreply.github.com>
2025-11-06 08:20:20 -05:00

131 lines
6.0 KiB
Plaintext

/**
* Causes the passed atom / image to appear floating,
* playing a simple animation where they move up and down by 2 pixels (looping)
*
* In most cases you should NOT call this manually, instead use [/datum/element/movetype_handler]!
* This is just so you can apply the animation to things which can be animated but are not movables (like images)
*/
#define DO_FLOATING_ANIM(target) \
animate(target, pixel_z = 2, time = 1 SECONDS, loop = -1, flags = ANIMATION_RELATIVE); \
animate(pixel_z = -2, time = 1 SECONDS, flags = ANIMATION_RELATIVE)
/**
* Stops the passed atom / image from appearing floating
* (Living mobs also have a 'body_position_pixel_y_offset' variable that has to be taken into account here)
*
* In most cases you should NOT call this manually, instead use [/datum/element/movetype_handler]!
* This is just so you can apply the animation to things which can be animated but are not movables (like images)
*/
#define STOP_FLOATING_ANIM(target) \
var/__final_pixel_z = 0; \
if(ismovable(target)) { \
var/atom/movable/__movable_target = target; \
__final_pixel_z += __movable_target.base_pixel_z; \
}; \
if(isliving(target)) { \
var/mob/living/__living_target = target; \
__final_pixel_z += __living_target.has_offset(pixel = PIXEL_Z_OFFSET); \
}; \
animate(target, pixel_z = __final_pixel_z, time = 1 SECONDS)
/// The duration of the animate call in mob/living/update_transform
#define UPDATE_TRANSFORM_ANIMATION_TIME (0.2 SECONDS)
///Animates source spinning around itself. For docmentation on the args, check atom/proc/SpinAnimation()
/atom/proc/do_spin_animation(speed = 1 SECONDS, loops = -1, segments = 3, angle = 120, parallel = TRUE, tag = null)
var/list/matrices = list()
for(var/i in 1 to segments-1)
var/matrix/segment_matrix = matrix(transform)
segment_matrix.Turn(angle*i)
matrices += segment_matrix
var/matrix/last = matrix(transform)
matrices += last
speed /= segments
if(parallel)
animate(src, transform = matrices[1], time = speed, loop = loops, flags = ANIMATION_PARALLEL, tag = tag)
else
animate(src, transform = matrices[1], time = speed, loop = loops)
for(var/i in 2 to segments) //2 because 1 is covered above
animate(transform = matrices[i], time = speed)
//doesn't have an object argument because this is "Stacking" with the animate call above
//3 billion% intentional
/// Similar to shake but more spasm-y and jerk-y
/atom/proc/spasm_animation(loops = -1)
var/list/transforms = list(
matrix(transform).Translate(-1, 0),
matrix(transform).Translate(0, 1),
matrix(transform).Translate(1, 0),
matrix(transform).Translate(0, -1),
matrix(transform),
)
animate(src, transform = transforms[1], time = 0.1, loop = loops)
animate(transform = transforms[2], time = 0.1)
animate(transform = transforms[3], time = 0.2)
animate(transform = transforms[4], time = 0.3)
animate(transform = transforms[5], time = 0.1)
/**
* Proc called when you want the atom to spin around the center of its icon (or where it would be if its transform var is translated)
* By default, it makes the atom spin forever and ever at a speed of 60 rpm.
*
* Arguments:
* * speed: how much it takes for the atom to complete one 360° rotation
* * loops: how many times do we want the atom to rotate
* * clockwise: whether the atom ought to spin clockwise or counter-clockwise
* * segments: in how many animate calls the rotation is split. Probably unnecessary, but you shouldn't set it lower than 3 anyway.
* * parallel: whether the animation calls have the ANIMATION_PARALLEL flag, necessary for it to run alongside concurrent animations.
* * tag: animation tag to use, for parralel animations only
*/
/atom/proc/SpinAnimation(speed = 1 SECONDS, loops = -1, clockwise = TRUE, segments = 3, parallel = TRUE, tag = null)
if(!segments)
return
var/segment = 360/segments
if(!clockwise)
segment = -segment
SEND_SIGNAL(src, COMSIG_ATOM_SPIN_ANIMATION, speed, loops, segments, segment)
do_spin_animation(speed, loops, segments, segment, parallel, tag)
/// Makes this atom look like a "hologram"
/// So transparent, blue, with a scanline and an emissive glow
/// This is acomplished using a combination of filters and render steps/overlays
/// The degree of the opacity is optional, based off the opacity arg (0 -> 1)
/atom/proc/makeHologram(opacity = 0.5)
// First, we'll make things blue (roughly) and sorta transparent
add_filter("HOLO: Color and Transparent", 1, color_matrix_filter(rgb(125,180,225, opacity * 255)))
// Now we're gonna do a scanline effect
// Gonna take this atom and give it a render target, then use it as a source for a filter
// (We use an atom because it seems as if setting render_target on an MA is just invalid. I hate this engine)
var/atom/movable/scanline = new(null)
scanline.icon = 'icons/effects/effects.dmi'
scanline.icon_state = "scanline"
scanline.appearance_flags |= RESET_TRANSFORM
// * so it doesn't render
var/static/uid_scan = 0
scanline.render_target = "*HoloScanline [uid_scan]"
uid_scan++
// Now we add it as a filter, and overlay the appearance so the render source is always around
add_filter("HOLO: Scanline", 2, alpha_mask_filter(render_source = scanline.render_target))
add_overlay(scanline)
qdel(scanline)
// Annd let's make the sucker emissive, so it glows in the dark
if(!render_target)
var/static/uid = 0
render_target = "HOLOGRAM [uid]"
uid++
// I'm using static here to reduce the overhead, it does mean we need to do plane stuff manually tho
var/static/atom/movable/render_step/emissive/glow
if(!glow)
glow = new(null)
glow.render_source = render_target
SET_PLANE_EXPLICIT(glow, initial(glow.plane), src)
// We're creating a render step that copies ourselves, and draws it to the emissive plane
// Then we overlay it, and release "ownership" back to this proc, since we get to keep the appearance it generates
// We can't just use an MA from the start cause render_source setting starts going fuckey REALLY quick
var/mutable_appearance/glow_appearance = new(glow)
add_overlay(glow_appearance)
LAZYADD(update_overlays_on_z, glow_appearance)