mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-11 10:11:09 +00:00
## About The Pull Request Tin, the proc that's managing references should remove the thing from the list before calling qdel on it. Also, it should not be calling qdel on something that's already being qdeleted (which could happen before if `nuke_reflection()` was reached via the `COMSIG_QDELING` signal). <img width="1675" height="480" alt="firefox_xIIRCIbgdU" src="https://github.com/user-attachments/assets/246be9de-6ff0-46a5-bf5e-1d0895fd9f55" /> ## Why It's Good For The Game Less hard deletes ## Changelog Not player facing/should get the hard deletes tag
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)
|
|
LAZYREMOVE(reflected_movables, target)
|
|
if(!QDELETED(reflection))
|
|
qdel(reflection)
|
|
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)
|