mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-10 09:42:29 +00:00
## About The Pull Request Fixes #88607 Soft-requires #89358 since I used a proc I added in that in this PR Mirror reflections from all directions now work as you would... maybe expect them to work Becoming a Vampire while in front of a mirror makes you immediately disappear https://github.com/user-attachments/assets/d9426da4-1cdc-446f-b501-fdab3ba1a32c ## Changelog 🆑 Melbert fix: Mirror reflections should be more accurate now fix: Becoming a Vampire while standing in front of a mirror correctly vanishes you (rather than waiting to exit and enter the mirror's view) /🆑 --------- Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com>
224 lines
9.5 KiB
Plaintext
224 lines
9.5 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_PRIVATE/obj/effect/abstract/reflection_holder
|
|
/**
|
|
* A lazy assoc list that keeps track of all movables in range that either could be reflected or are reflected.
|
|
*
|
|
* The key is the movable, and the value is the reflection object (or null - the reflection object is also lazy loaded).
|
|
*/
|
|
VAR_PRIVATE/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
|
|
///List of signals registered on reflected atoms to update their reflections.
|
|
var/list/check_reflect_signals
|
|
|
|
/**
|
|
* Init Args
|
|
* * set_reflected_dir: Optional: What dir to reflect. If not provided, uses (and updates to) the parent's dir.
|
|
* * reflection_filter: Optional: A list of filters to apply to the reflection.
|
|
* * reflection_matrix: Optional: A matrix to apply as the transform of the reflection.
|
|
* * can_reflect: Optional: A callback to check if a movable should be reflected.
|
|
* * alpha: The transparency of the reflection holder.
|
|
* * update_signals: Optional: Additional signals to provide to update_signals (to check for when to recalculate all reflections).
|
|
* * check_reflect_signals: Optional: Additional signals to provide to check_reflect_signals (to check for when to update a single reflection).
|
|
*/
|
|
/datum/component/reflection/Initialize(set_reflected_dir, list/reflection_filter, matrix/reflection_matrix, datum/callback/can_reflect, alpha = 150, list/update_signals, list/check_reflect_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)
|
|
// Always supplied to check_reflect_signals
|
|
var/list/default_can_reflect_signals = list(
|
|
COMSIG_ATOM_POST_DIR_CHANGE,
|
|
COMSIG_ATOM_UPDATED_ICON,
|
|
COMSIG_CARBON_APPLY_OVERLAY,
|
|
COMSIG_CARBON_REMOVE_OVERLAY,
|
|
COMSIG_LIVING_POST_UPDATE_TRANSFORM,
|
|
)
|
|
// Always supplied to update_signals
|
|
var/list/default_update_signals = list(
|
|
COMSIG_MOVABLE_MOVED,
|
|
)
|
|
|
|
src.reflection_matrix = reflection_matrix
|
|
src.reflection_filter = reflection_filter
|
|
src.can_reflect = can_reflect
|
|
src.check_reflect_signals = (check_reflect_signals || list()) + default_can_reflect_signals
|
|
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(set_reflected_dir || REVERSE_DIR(mov_parent.dir))
|
|
|
|
if(!set_reflected_dir)
|
|
RegisterSignal(parent, COMSIG_ATOM_DIR_CHANGE, PROC_REF(on_dir_change))
|
|
RegisterSignals(parent, (update_signals || list()) + default_update_signals, PROC_REF(get_reflection_targets))
|
|
|
|
/datum/component/reflection/Destroy(force)
|
|
for(var/atom/movable/tracked in reflected_movables)
|
|
nuke_reflection(tracked)
|
|
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(REVERSE_DIR(new_dir))
|
|
|
|
///Turns the allowed reflected direction alongside the parent's dir. then calls get_reflection_targets.
|
|
/datum/component/reflection/proc/set_reflection(new_dir = SOUTH)
|
|
if(reflected_dir == new_dir)
|
|
return
|
|
|
|
reflected_dir = new_dir
|
|
get_reflection_targets(parent)
|
|
|
|
///Unsets the old reflected movables and sets it with new ones.
|
|
/datum/component/reflection/proc/get_reflection_targets(atom/movable/source)
|
|
SIGNAL_HANDLER
|
|
// clean slate
|
|
for(var/atom/movable/tracked in reflected_movables)
|
|
nuke_reflection(tracked)
|
|
// find anything adjacent, if we can't see it that's fine (we check view later)
|
|
for(var/atom/movable/target in range(1, source))
|
|
track_reflection(target)
|
|
|
|
///Checks if the target movable can be reflected or not.
|
|
/datum/component/reflection/proc/check_can_reflect(atom/movable/target)
|
|
if(target == parent || !(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
|
|
track_reflection(arrived)
|
|
|
|
/datum/component/reflection/proc/track_reflection(atom/movable/target, check_view = TRUE)
|
|
// this stuff really shouldn't be tracked
|
|
if(QDELETED(target) || target == parent || target.loc == parent)
|
|
return
|
|
// i don't really want to do this but there's a bunch of abstract effects we should ignore...
|
|
// we can revisit this later when we actually have an object that wants to reflect this stuff
|
|
if(iseffect(target))
|
|
return
|
|
if(!LAZYFIND(reflected_movables, target)) // not lazyaccess - value may be null
|
|
LAZYSET(reflected_movables, target, null)
|
|
RegisterSignals(target, check_reflect_signals, PROC_REF(update_reflection))
|
|
RegisterSignals(target, COMSIG_QDELETING, PROC_REF(nuke_reflection))
|
|
update_reflection(target)
|
|
|
|
/datum/component/reflection/proc/nuke_reflection(atom/movable/target)
|
|
SIGNAL_HANDLER
|
|
|
|
var/atom/movable/reflection = LAZYACCESS(reflected_movables, target)
|
|
if(reflection)
|
|
qdel(reflection)
|
|
LAZYREMOVE(reflected_movables, target)
|
|
UnregisterSignal(target, check_reflect_signals)
|
|
UnregisterSignal(target, COMSIG_QDELETING)
|
|
|
|
///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
|
|
|
|
if(!LAZYFIND(reflected_movables, gone)) // not lazyaccess - value may be null
|
|
return
|
|
if(check_can_reflect(gone))
|
|
return
|
|
nuke_reflection(gone)
|
|
|
|
/// Handles updating the appearance of the reflection to match the target movable.
|
|
/datum/component/reflection/proc/copy_appearance_to_reflection(obj/effect/abstract/reflection, atom/movable/target)
|
|
reflection.appearance = copy_appearance_filter_overlays(target.appearance)
|
|
reflection.vis_flags = VIS_INHERIT_ID
|
|
reflection.transform = reflection_matrix || matrix()
|
|
// updating the dir so facing towards / away from it correctly faces the reflection away / towards it,
|
|
// while facing left / right will correctly face the reflection left / right
|
|
// there's probably a more intelligent way to tackle this but this is more readable, i guess
|
|
if(reflected_dir & EAST)
|
|
if(target.dir & NORTH)
|
|
reflection.dir = WEST
|
|
else if(target.dir & SOUTH)
|
|
reflection.dir = EAST
|
|
else
|
|
reflection.dir = REVERSE_DIR(target.dir)
|
|
|
|
else if(reflected_dir & WEST)
|
|
if(target.dir & NORTH)
|
|
reflection.dir = EAST
|
|
else if(target.dir & SOUTH)
|
|
reflection.dir = WEST
|
|
else
|
|
reflection.dir = REVERSE_DIR(target.dir)
|
|
|
|
else if(reflected_dir & SOUTH)
|
|
// east/west is the same, makes it easy on us
|
|
reflection.dir = (target.dir & (EAST|WEST)) ? target.dir : REVERSE_DIR(target.dir)
|
|
|
|
else if(reflected_dir & NORTH)
|
|
reflection.dir = (target.dir & (NORTH|SOUTH)) ? target.dir : REVERSE_DIR(target.dir)
|
|
// north needs snowflake handling to make a more... "understandable" reflection
|
|
reflection.transform = reflection.transform.Turn(180)
|
|
reflection.pixel_y += 5
|
|
|
|
// purely for vv
|
|
reflection.name = "[target.name]'s reflection"
|
|
|
|
///Called when the target movable changes its appearance or dir.
|
|
/datum/component/reflection/proc/update_reflection(atom/movable/source)
|
|
SIGNAL_HANDLER
|
|
|
|
var/obj/effect/abstract/reflection = LAZYACCESS(reflected_movables, source)
|
|
if(!check_can_reflect(source))
|
|
// temporarily hide any reflection
|
|
reflection?.vis_flags |= VIS_HIDE
|
|
return
|
|
// Lazy init the reflection
|
|
if(!reflection)
|
|
// 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.
|
|
reflection = new(parent)
|
|
reflection_holder.vis_contents += reflection
|
|
LAZYSET(reflected_movables, source, reflection)
|
|
|
|
// technically redundant (...because copying appearance copies vis flags), but good to be explicit
|
|
reflection.vis_flags &= ~VIS_HIDE
|
|
copy_appearance_to_reflection(reflection, source)
|