Files
Bubberstation/code/datums/components/reflection.dm
SmArtKar acad706c1f Wallening-style directional mirrors (#85818)
<!-- Write **BELOW** The Headers and **ABOVE** The comments else it may
not be viewable. -->
<!-- You can view Contributing.MD for a detailed description of the pull
request process. -->

## About The Pull Request
Updates mirrors to fit wallening's style, sadly this does mean making
them smaller and only noticably reflective when facing south.

![dreamseeker_54OsLqrkU1](https://github.com/user-attachments/assets/5d34f87d-36d3-41db-bd06-4ae543ddfccf)

![dreamseeker_gUTS0N9Eah](https://github.com/user-attachments/assets/916edbc8-daa4-4ed3-81aa-9db8a24ad340)

Closes #85836
Closes #77476

<!-- Describe The Pull Request. Please be sure every change is
documented or this can delay review and even discourage maintainers from
merging your PR! -->

## Why It's Good For The Game

Currently they look like floating plates near walls instead of being
actual mirrors

## Changelog

<!-- If your PR modifies aspects of the game that can be concretely
observed by players or admins you should add a changelog. If your change
does NOT meet this description, remove this section. Be sure to properly
mark your PRs to prevent unnecessary GBP loss. You can read up on GBP
and its effects on PRs in the tgstation guides for contributors. Please
note that maintainers freely reserve the right to remove and add tags
should they deem it appropriate. You can attempt to finagle the system
all you want, but it's best to shoot for clear communication right off
the bat. -->

🆑
image: Mirrors have been successfully wallened
/🆑

<!-- Both 🆑's are required for the changelog to work! You can put
your name to the right of the first 🆑 if you want to overwrite your
GitHub username as author ingame. -->
<!-- You can use multiple of the same prefix (they're only used for the
icon ingame) and delete the unneeded ones. Despite some of the tags,
changelogs should generally represent how a player might be affected by
the changes rather than a summary of the PR's contents. -->
2024-08-15 19:40:02 +02:00

131 lines
5.7 KiB
Plaintext

/**
* A simple-ish component that reflects the icons of movables on the parent like a mirror.
* Sadly, there's no easy way to make the SOUTH dir reflection flip the visual so that you can see
* the back NORTH dir of a target while it's facing SOUTH beside adding the VIS_INHERIT_DIR flag
* to the target movable, which I'm not doing to spare eventual issues with other vis overlays in the future.
*/
/datum/component/reflection
/**
* The direction from which the component gets its visual overlays.
* The visuals are also flipped horizontally or vertically based on it.
*/
var/reflected_dir
/// the movable which the reflected movables are attached to, in turn added to the vis contents of the parent.
var/obj/effect/abstract/reflection_holder
/// A lazy assoc list that keeps track of which movables are being reflected and the associated reflections.
var/list/reflected_movables
/// A callback used check to know which movables should be reflected and which not.
var/datum/callback/can_reflect
///the base matrix used by reflections
var/matrix/reflection_matrix
///the filter data added to reflection holder.
var/list/reflection_filter
///the transparency channel value of the reflection holder.
var/alpha
///A list of signals that when sent to the parent, will force the comp to recalculate the reflected movables.
var/list/update_signals
/datum/component/reflection/Initialize(reflected_dir = NORTH, list/reflection_filter, matrix/reflection_matrix, datum/callback/can_reflect, alpha = 150, list/update_signals)
if(!ismovable(parent))
return COMPONENT_INCOMPATIBLE
var/static/list/connections = list(
COMSIG_ATOM_ENTERED = PROC_REF(on_movable_entered_or_initialized),
COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON = PROC_REF(on_movable_entered_or_initialized),
COMSIG_ATOM_EXITED = PROC_REF(on_movable_exited)
)
AddComponent(/datum/component/connect_range, parent, connections, 1, works_in_containers = FALSE)
src.reflected_dir = reflected_dir
src.reflection_matrix = reflection_matrix
src.reflection_filter = reflection_filter
src.can_reflect = can_reflect
reflection_holder = new(parent)
reflection_holder.alpha = alpha
reflection_holder.appearance_flags = KEEP_TOGETHER
reflection_holder.vis_flags = VIS_INHERIT_ID
reflection_holder.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
if(reflection_filter)
reflection_holder.add_filter("reflection", 1, reflection_filter)
var/atom/movable/mov_parent = parent
mov_parent.vis_contents += reflection_holder
set_reflection(new_dir = mov_parent.dir)
RegisterSignal(parent, COMSIG_ATOM_DIR_CHANGE, PROC_REF(on_dir_change))
var/list/reflect_update_signals = list(COMSIG_MOVABLE_MOVED) + update_signals
RegisterSignals(parent, reflect_update_signals, PROC_REF(get_reflection_targets))
/datum/component/reflection/Destroy(force)
QDEL_LIST_ASSOC_VAL(reflected_movables)
QDEL_NULL(reflection_holder)
can_reflect = null
return ..()
///Called when the parent changes its direction.
/datum/component/reflection/proc/on_dir_change(atom/movable/source, old_dir, new_dir)
SIGNAL_HANDLER
set_reflection(old_dir, new_dir)
///Turns the allowed reflected direction alongside the parent's dir. then calls get_reflection_targets.
/datum/component/reflection/proc/set_reflection(old_dir = SOUTH, new_dir = SOUTH)
if(old_dir == new_dir)
return
reflected_dir = turn(reflected_dir, dir2angle(new_dir) - dir2angle(old_dir))
get_reflection_targets()
///Unsets the old reflected movables and sets it with new ones.
/datum/component/reflection/proc/get_reflection_targets(atom/movable/source)
SIGNAL_HANDLER
QDEL_LIST_ASSOC_VAL(reflected_movables)
for(var/atom/movable/target in view(1, source))
if(check_can_reflect(target, FALSE))
set_reflected(target)
///Checks if the target movable can be reflected or not.
/datum/component/reflection/proc/check_can_reflect(atom/movable/target, check_view = TRUE)
if(target == parent || (check_view && !(target in view(1, parent))))
return FALSE
var/atom/movable/mov_parent = parent
if(target.loc != mov_parent.loc && get_dir(mov_parent, target) != reflected_dir)
return FALSE
if(can_reflect && !can_reflect.Invoke(target))
return FALSE
return TRUE
///Called when a movable enters a turf within the connected range
/datum/component/reflection/proc/on_movable_entered_or_initialized(atom/movable/source, atom/movable/arrived)
SIGNAL_HANDLER
if(LAZYACCESS(reflected_movables, arrived) || !check_can_reflect(arrived))
return
set_reflected(arrived)
///Called when a movable exits a turf within the connected range
/datum/component/reflection/proc/on_movable_exited(atom/movable/source, atom/movable/gone)
SIGNAL_HANDLER
var/atom/movable/reflection = LAZYACCESS(reflected_movables, gone)
if(!reflection || check_can_reflect(gone))
return
qdel(reflection)
LAZYREMOVE(reflected_movables, gone)
///Sets up a visual overlay of the target movables, which is added to the parent's vis contents.
/datum/component/reflection/proc/set_reflected(atom/movable/target)
SIGNAL_HANDLER
/**
* If the loc is null, only a black (or grey depending on alpha) silhouette of the target will be rendered
* Just putting this information here in case you want something like that in the future.
*/
var/obj/effect/abstract/reflection = new(parent)
reflection.vis_flags = VIS_INHERIT_ID
if(!target.render_target)
target.render_target = REF(target)
reflection.render_source = target.render_target
///The filter is added to the reflection holder; the matrix is not, otherwise that'd go affecting the filter.
if(reflection_matrix)
reflection.transform = reflection_matrix
if(reflected_dir != NORTH && reflected_dir != SOUTH)
reflection.transform = reflection.transform.Scale(-1, 1)
LAZYSET(reflected_movables, target, reflection)
reflection_holder.vis_contents += reflection