From 219bb26cfce61ba7f46b9659a4839d498e3fa9f2 Mon Sep 17 00:00:00 2001 From: Letter N <24603524+LetterN@users.noreply.github.com> Date: Tue, 5 Nov 2024 22:15:04 +0800 Subject: [PATCH] update filter (#6836) ## About The Pull Request filter is now in datum level so you can do it on /images as well ## Why It's Good For The Game ## Changelog --- citadel.dme | 1 + code/__HELPERS/matrices/transform_matrix.dm | 27 ---- code/__HELPERS/sorts/TimSort.dm | 54 +++++--- code/__HELPERS/visual_effects.dm | 63 ++++++++++ code/datums/datum.dm | 129 ++++++++++++++++++++ code/game/atoms/_atom.dm | 69 ----------- 6 files changed, 230 insertions(+), 113 deletions(-) create mode 100644 code/__HELPERS/visual_effects.dm diff --git a/citadel.dme b/citadel.dme index d3fee73206d..06d6fce576d 100644 --- a/citadel.dme +++ b/citadel.dme @@ -399,6 +399,7 @@ #include "code\__HELPERS\unsorted.dm" #include "code\__HELPERS\vector.dm" #include "code\__HELPERS\verbs.dm" +#include "code\__HELPERS\visual_effects.dm" #include "code\__HELPERS\animations\attack.dm" #include "code\__HELPERS\datastructs\bodytypes.dm" #include "code\__HELPERS\datastructs\filters.dm" diff --git a/code/__HELPERS/matrices/transform_matrix.dm b/code/__HELPERS/matrices/transform_matrix.dm index 51288a51b7d..668e28360b5 100644 --- a/code/__HELPERS/matrices/transform_matrix.dm +++ b/code/__HELPERS/matrices/transform_matrix.dm @@ -45,33 +45,6 @@ /matrix/proc/Shear(x, y) return Multiply(matrix(1, x, 0, y, 1, 0)) -/atom/proc/SpinAnimation(speed = 1 SECONDS, loops = -1, clockwise = 1, segments = 3, parallel = TRUE) - if(!segments) - return - var/segment = 360/segments - if(!clockwise) - segment = -segment - var/list/matrices = list() - for(var/i in 1 to segments-1) - var/matrix/M = matrix(transform) - M.Turn(segment*i) - matrices += M - var/matrix/last = matrix(transform) - matrices += last - - speed /= segments - - if(parallel) - animate(src, transform = matrices[1], time = speed, loops , flags = ANIMATION_PARALLEL) - else - animate(src, transform = matrices[1], time = speed, 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 - - //Dumps the matrix data in format a-f /matrix/proc/tolist() . = list() diff --git a/code/__HELPERS/sorts/TimSort.dm b/code/__HELPERS/sorts/TimSort.dm index d16a9832a8a..aafbaf00204 100644 --- a/code/__HELPERS/sorts/TimSort.dm +++ b/code/__HELPERS/sorts/TimSort.dm @@ -1,22 +1,42 @@ + /** - * TimSort + * ## Tim Sort + * Hybrid sorting algorithm derived from merge sort and insertion sort. + * + * **Sorts in place**. + * You might not need to get the return value. + * + * @see + * https://en.wikipedia.org/wiki/Timsort + * + * @param {list} to_sort - The list to sort. + * + * @param {proc} cmp - The comparison proc to use. Default: Numeric ascending. + * + * @param {boolean} associative - Whether the list is associative. Default: FALSE. + * + * @param {int} fromIndex - The index to start sorting from. Default: 1. + * + * @param {int} toIndex - The index to stop sorting at. Default: 0. */ -/proc/tim_sort(list/L, cmp= GLOBAL_PROC_REF(cmp_numeric_asc), associative, fromIndex=1, toIndex=0) - if(L && L.len >= 2) - fromIndex = fromIndex % L.len - toIndex = toIndex % (L.len+1) - if(fromIndex <= 0) - fromIndex += L.len - if(toIndex <= 0) - toIndex += L.len + 1 +/proc/tim_sort(list/to_sort, cmp = GLOBAL_PROC_REF(cmp_numeric_asc), associative = FALSE, fromIndex = 1, toIndex = 0) as /list + if(length(to_sort) < 2) + return to_sort - var/datum/sort_instance/SI = GLOB.sort_instance - if(!SI) - SI = new + fromIndex = fromIndex % length(to_sort) + toIndex = toIndex % (length(to_sort) + 1) + if(fromIndex <= 0) + fromIndex += length(to_sort) + if(toIndex <= 0) + toIndex += length(to_sort) + 1 - SI.L = L - SI.cmp = cmp - SI.associative = associative + var/datum/sort_instance/sorter = GLOB.sort_instance + if(isnull(sorter)) + sorter = new - SI.tim_sort(fromIndex, toIndex) - return L + sorter.L = to_sort + sorter.cmp = cmp + sorter.associative = associative + + sorter.tim_sort(fromIndex, toIndex) + return to_sort diff --git a/code/__HELPERS/visual_effects.dm b/code/__HELPERS/visual_effects.dm new file mode 100644 index 00000000000..8e30698ba3b --- /dev/null +++ b/code/__HELPERS/visual_effects.dm @@ -0,0 +1,63 @@ + +///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) + 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) + 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 + +/** + * 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. + */ +/atom/proc/SpinAnimation(speed = 1 SECONDS, loops = -1, clockwise = TRUE, segments = 3, parallel = TRUE) + if(!segments) + return + var/segment = 360/segments + if(!clockwise) + segment = -segment + do_spin_animation(speed, loops, segments, segment, parallel) + +/// 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) diff --git a/code/datums/datum.dm b/code/datums/datum.dm index 25d5061d6e4..e6d8793f5fa 100644 --- a/code/datums/datum.dm +++ b/code/datums/datum.dm @@ -51,6 +51,9 @@ */ var/list/cooldowns + /// List for handling persistent filters. + var/list/filter_data + #ifdef REFERENCE_TRACKING var/running_find_references var/last_find_references = 0 @@ -162,6 +165,132 @@ SEND_SIGNAL(source, COMSIG_CD_RESET(index), S_TIMER_COOLDOWN_TIMELEFT(source, index)) TIMER_COOLDOWN_END(source, index) + +//? Filters + +/** Add a filter to the datum. + * This is on datum level, despite being most commonly / primarily used on atoms, so that filters can be applied to images / mutable appearances. + * Can also be used to assert a filter's existence. I.E. update a filter regardless if it exists or not. + * + * Arguments: + * * name - Filter name + * * priority - Priority used when sorting the filter. + * * params - Parameters of the filter. + */ +/datum/proc/add_filter(name, priority, list/params) + LAZYINITLIST(filter_data) + var/list/copied_parameters = params.Copy() + copied_parameters["priority"] = priority + filter_data[name] = copied_parameters + update_filters() + +///A version of add_filter that takes a list of filters to add rather than being individual, to limit calls to update_filters(). +/datum/proc/add_filters(list/list/filters) + LAZYINITLIST(filter_data) + for(var/list/individual_filter as anything in filters) + var/list/params = individual_filter["params"] + var/list/copied_parameters = params.Copy() + copied_parameters["priority"] = individual_filter["priority"] + filter_data[individual_filter["name"]] = copied_parameters + update_filters() + +/// Reapplies all the filters. +/datum/proc/update_filters() + ASSERT(isatom(src) || isimage(src)) + var/atom/atom_cast = src // filters only work with images or atoms. + atom_cast.filters = null + tim_sort(filter_data, GLOBAL_PROC_REF(cmp_filter_data_priority), TRUE) + for(var/filter_raw in filter_data) + var/list/data = filter_data[filter_raw] + var/list/arguments = data.Copy() + arguments -= "priority" + atom_cast.filters += filter(arglist(arguments)) + UNSETEMPTY(filter_data) + +/obj/item/update_filters() + . = ..() + update_action_buttons() + +/** Update a filter's parameter to the new one. If the filter doesn't exist we won't do anything. + * + * Arguments: + * * name - Filter name + * * new_params - New parameters of the filter + * * overwrite - TRUE means we replace the parameter list completely. FALSE means we only replace the things on new_params. + */ +/datum/proc/modify_filter(name, list/new_params, overwrite = FALSE) + var/filter = get_filter(name) + if(!filter) + return + if(overwrite) + filter_data[name] = new_params + else + for(var/thing in new_params) + filter_data[name][thing] = new_params[thing] + update_filters() + +/** Update a filter's parameter and animate this change. If the filter doesn't exist we won't do anything. + * Basically a [datum/proc/modify_filter] call but with animations. Unmodified filter parameters are kept. + * + * Arguments: + * * name - Filter name + * * new_params - New parameters of the filter + * * time - time arg of the BYOND animate() proc. + * * easing - easing arg of the BYOND animate() proc. + * * loop - loop arg of the BYOND animate() proc. + */ +/datum/proc/transition_filter(name, list/new_params, time, easing, loop) + var/filter = get_filter(name) + if(!filter) + return + // This can get injected by the filter procs, we want to support them so bye byeeeee + new_params -= "type" + animate(filter, new_params, time = time, easing = easing, loop = loop) + modify_filter(name, new_params) + +/// Updates the priority of the passed filter key +/datum/proc/change_filter_priority(name, new_priority) + if(!filter_data || !filter_data[name]) + return + + filter_data[name]["priority"] = new_priority + update_filters() + +/// Returns the filter associated with the passed key +/datum/proc/get_filter(name) + ASSERT(isatom(src) || isimage(src)) + if(filter_data && filter_data[name]) + var/atom/atom_cast = src // filters only work with images or atoms. + return atom_cast.filters[filter_data.Find(name)] + +/// Returns the indice in filters of the given filter name. +/// If it is not found, returns null. +/datum/proc/get_filter_index(name) + return filter_data?.Find(name) + +/// Removes the passed filter, or multiple filters, if supplied with a list. +/datum/proc/remove_filter(name_or_names) + if(!filter_data) + return + + var/list/names = islist(name_or_names) ? name_or_names : list(name_or_names) + + . = FALSE + for(var/name in names) + if(filter_data[name]) + filter_data -= name + . = TRUE + + if(.) + update_filters() + return . + +/datum/proc/clear_filters() + ASSERT(isatom(src) || isimage(src)) + var/atom/atom_cast = src // filters only work with images or atoms. + filter_data = null + atom_cast.filters = null + //* Duplication *// /** diff --git a/code/game/atoms/_atom.dm b/code/game/atoms/_atom.dm index 0bdec7cb8c7..271960c63f5 100644 --- a/code/game/atoms/_atom.dm +++ b/code/game/atoms/_atom.dm @@ -189,10 +189,6 @@ /// expected icon height; centering offsets will be calculated from this and our base pixel y. var/icon_y_dimension = 32 - //? Filters - /// For handling persistent filters - var/list/filter_data - //? Misc /// What mobs are interacting with us right now, associated directly to concurrent interactions. (use defines) var/list/interacting_mobs @@ -877,71 +873,6 @@ // /atom/proc/handle_contents_del(atom/movable/deleting) // return -//? Filters - -/atom/proc/add_filter(name, priority, list/params, update = TRUE) - LAZYINITLIST(filter_data) - var/list/copied_parameters = params.Copy() - copied_parameters["priority"] = priority - filter_data[name] = copied_parameters - if(update) - update_filters() - -/atom/proc/update_filters() - filters = null - filter_data = tim_sort(filter_data, GLOBAL_PROC_REF(cmp_filter_data_priority), TRUE) - for(var/f in filter_data) - var/list/data = filter_data[f] - var/list/arguments = data.Copy() - arguments -= "priority" - filters += filter(arglist(arguments)) - UNSETEMPTY(filter_data) - -/atom/proc/transition_filter(name, time, list/new_params, easing, loop) - var/filter = get_filter(name) - if(!filter) - return - - var/list/old_filter_data = filter_data[name] - - var/list/params = old_filter_data.Copy() - for(var/thing in new_params) - params[thing] = new_params[thing] - - animate(filter, new_params, time = time, easing = easing, loop = loop) - for(var/param in params) - filter_data[name][param] = params[param] - -/atom/proc/change_filter_priority(name, new_priority) - if(!filter_data || !filter_data[name]) - return - - filter_data[name]["priority"] = new_priority - update_filters() - -/atom/proc/get_filter(name) - if(filter_data && filter_data[name]) - return filters[filter_data.Find(name)] - -/atom/proc/remove_filter(name_or_names, update = TRUE) - if(!filter_data) - return - - var/list/names = islist(name_or_names) ? name_or_names : list(name_or_names) - - for(var/name in names) - if(filter_data[name]) - filter_data -= name - if(update) - update_filters() - -/atom/proc/has_filter(name) - return !isnull(filter_data?[name]) - -/atom/proc/clear_filters() - filter_data = null - filters = null - //* Inventory *// /atom/proc/on_contents_weight_class_change(obj/item/item, old_weight_class, new_weight_class)