diff --git a/citadel.dme b/citadel.dme index 192d28e52ae..c49e37ee036 100644 --- a/citadel.dme +++ b/citadel.dme @@ -210,7 +210,6 @@ #include "code\__DEFINES\dcs\signals\signals_atom\signals_atom-reachability.dm" #include "code\__DEFINES\dcs\signals\signals_atom\signals_atom-throwing.dm" #include "code\__DEFINES\dcs\signals\signals_atom\signals_atom-tool_system.dm" -#include "code\__DEFINES\dcs\signals\signals_atom\signals_atom-update_icon.dm" #include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_appearance.dm" #include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_attack.dm" #include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_main.dm" diff --git a/code/__DEFINES/_lists.dm b/code/__DEFINES/_lists.dm index af8676ec459..1d0d18589cf 100644 --- a/code/__DEFINES/_lists.dm +++ b/code/__DEFINES/_lists.dm @@ -159,6 +159,45 @@ };\ } while(FALSE) +#define SORT_FIRST_INDEX(list) (list[1]) +#define SORT_PRIORITY_INDEX(list) (list["priority"]) +#define SORT_COMPARE_DIRECTLY(thing) (thing) +#define SORT_VAR_NO_TYPE(varname) var/varname +/**** + * Even more custom binary search sorted insert, using defines instead of vars + * INPUT: Item to be inserted + * LIST: List to insert INPUT into + * TYPECONT: A define setting the var to the typepath of the contents of the list + * COMPARE: The item to compare against, usualy the same as INPUT + * COMPARISON: A define that takes an item to compare as input, and returns their comparable value + * COMPTYPE: How should the list be compared? Either COMPARE_KEY or COMPARE_VALUE. + */ +#define BINARY_INSERT_DEFINE(INPUT, LIST, TYPECONT, COMPARE, COMPARISON, COMPTYPE) \ + do {\ + var/list/__BIN_LIST = LIST;\ + var/__BIN_CTTL = length(__BIN_LIST);\ + if(!__BIN_CTTL) {\ + __BIN_LIST += INPUT;\ + } else {\ + var/__BIN_LEFT = 1;\ + var/__BIN_RIGHT = __BIN_CTTL;\ + var/__BIN_MID = (__BIN_LEFT + __BIN_RIGHT) >> 1;\ + ##TYPECONT(__BIN_ITEM);\ + while(__BIN_LEFT < __BIN_RIGHT) {\ + __BIN_ITEM = COMPTYPE;\ + if(##COMPARISON(__BIN_ITEM) <= ##COMPARISON(COMPARE)) {\ + __BIN_LEFT = __BIN_MID + 1;\ + } else {\ + __BIN_RIGHT = __BIN_MID;\ + };\ + __BIN_MID = (__BIN_LEFT + __BIN_RIGHT) >> 1;\ + };\ + __BIN_ITEM = COMPTYPE;\ + __BIN_MID = ##COMPARISON(__BIN_ITEM) > ##COMPARISON(COMPARE) ? __BIN_MID : __BIN_MID + 1;\ + __BIN_LIST.Insert(__BIN_MID, INPUT);\ + };\ + } while(FALSE) + #define isalist(L) istype(L, /alist) #define VARSET_FROM_LIST(L, V) if(L && L[#V]) V = L[#V] diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom-update_icon.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom-update_icon.dm deleted file mode 100644 index a11c7904ee9..00000000000 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom-update_icon.dm +++ /dev/null @@ -1,8 +0,0 @@ -//* This file is explicitly licensed under the MIT license. *// -//* Copyright (c) 2024 Citadel Station Developers *// - -/// Called at the base of update_filters() -/// * Signature: () -/// * Hook this to re-apply managed filters. -// TODO: 516 added assoc filters; touch-up the datum filter procs and stop doing this. -#define COMSIG_ATOM_RELOAD_FILTERS "atom-reload_filters" diff --git a/code/__HELPERS/datastructs/filters.dm b/code/__HELPERS/datastructs/filters.dm index 72577d8c7b1..a2f3411134b 100644 --- a/code/__HELPERS/datastructs/filters.dm +++ b/code/__HELPERS/datastructs/filters.dm @@ -8,7 +8,7 @@ GLOBAL_LIST_INIT(master_filter_info, list( "y" = 0, "icon" = ICON_NOT_SET, "render_source" = "", - "flags" = 0 + "flags" = NONE ), "flags" = list( "MASK_INVERSE" = MASK_INVERSE, @@ -24,16 +24,26 @@ GLOBAL_LIST_INIT(master_filter_info, list( ), "bloom" = list( "defaults" = list( - "threshold" = "#ffffff", - "size" = 0, + "threshold" = COLOR_BLACK, + "size" = 1, "offset" = 0, - "alpha" = 255, + "alpha" = 255 ) ), + // Not fully implemented, but if this isn't uncommented some windows will just error + // Needs either a proper matrix editor, or just a hook to our existing one "color" = list( "defaults" = list( "color" = matrix(), "space" = FILTER_COLOR_RGB + ), + "options" = list( + "space" = list( + "FILTER_COLOR_RGB" = FILTER_COLOR_RGB, + "FILTER_COLOR_HSV" = FILTER_COLOR_HSV, + "FILTER_COLOR_HSL" = FILTER_COLOR_HSL, + "FILTER_COLOR_HCY" = FILTER_COLOR_HCY + ) ) ), "displace" = list( @@ -42,7 +52,11 @@ GLOBAL_LIST_INIT(master_filter_info, list( "y" = 0, "size" = null, "icon" = ICON_NOT_SET, - "render_source" = "" + "render_source" = "", + "flags" = NONE + ), + "flags" = list( + "FILTER_OVERLAY" = FILTER_OVERLAY ) ), "drop_shadow" = list( @@ -66,10 +80,24 @@ GLOBAL_LIST_INIT(master_filter_info, list( "icon" = ICON_NOT_SET, "render_source" = "", "flags" = FILTER_OVERLAY, - "color" = "", + "color" = COLOR_WHITE, "transform" = null, "blend_mode" = BLEND_DEFAULT + ), + "flags" = list( + "FILTER_OVERLAY" = FILTER_OVERLAY, + ), + "options" = list( + "blend_mode" = list( + "BLEND_DEFAULT" = BLEND_DEFAULT, + "BLEND_OVERLAY" = BLEND_OVERLAY, + "BLEND_ADD" = BLEND_ADD, + "BLEND_SUBTRACT" = BLEND_SUBTRACT, + "BLEND_MULTIPLY" = BLEND_MULTIPLY, + "BLEND_INSET_OVERLAY" = BLEND_INSET_OVERLAY + ) ) + ), "motion_blur" = list( "defaults" = list( @@ -169,17 +197,6 @@ GLOBAL_LIST_INIT(master_filter_info, list( if(!isnull(size)) .["size"] = size -/proc/bloom_filter(threshold, size, offset, alpha) - . = list("type" = "bloom") - if(!isnull(threshold)) - .["threshold"] = threshold - if(!isnull(size)) - .["size"] = size - if(!isnull(offset)) - .["offset"] = offset - if(!isnull(alpha)) - .["alpha"] = alpha - /proc/color_matrix_filter(matrix/in_matrix, space) . = list("type" = "color") .["color"] = in_matrix @@ -217,6 +234,17 @@ GLOBAL_LIST_INIT(master_filter_info, list( if(!isnull(size)) .["size"] = size +/proc/bloom_filter(threshold, size, offset, alpha) + . = list("type" = "bloom") + if(!isnull(threshold)) + .["threshold"] = threshold + if(!isnull(size)) + .["size"] = size + if(!isnull(offset)) + .["offset"] = offset + if(!isnull(alpha)) + .["alpha"] = alpha + /proc/layering_filter(icon, render_source, x, y, flags, color, transform, blend_mode) . = list("type" = "layer") if(!isnull(icon)) @@ -330,9 +358,20 @@ GLOBAL_LIST_INIT(master_filter_info, list( animate(filter, offset = random_roll, time = 0, loop = -1, flags = ANIMATION_PARALLEL) animate(offset = random_roll - 1, time = rand() * 20 + 10) -/proc/remove_wibbly_filters(atom/in_atom) +/proc/remove_wibbly_filters(atom/in_atom, remove_duration = 0) + if(QDELETED(in_atom)) + return var/filter for(var/i in 1 to 7) filter = in_atom.get_filter("wibbly-[i]") - animate(filter) - in_atom.remove_filter("wibbly-[i]") + if(remove_duration == 0) + animate(filter) + in_atom.remove_filter("wibbly-[i]") + continue + animate(filter, x = 0, y = 0, size = 0, offset = 0, time = remove_duration) + addtimer(CALLBACK(in_atom, TYPE_PROC_REF(/datum, remove_filter), "wibbly-[i]"), remove_duration) + +/proc/convert_list_to_filter(list/list_filter) + var/list/arguments = list_filter.Copy() + arguments -= "priority" + return filter(arglist(arguments)) diff --git a/code/__HELPERS/visual_effects.dm b/code/__HELPERS/visual_effects.dm index 8e30698ba3b..2ba45852876 100644 --- a/code/__HELPERS/visual_effects.dm +++ b/code/__HELPERS/visual_effects.dm @@ -1,6 +1,6 @@ ///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) +/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) @@ -12,7 +12,7 @@ speed /= segments if(parallel) - animate(src, transform = matrices[1], time = speed, loop = loops, flags = ANIMATION_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 @@ -30,14 +30,16 @@ * * 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) +/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 - do_spin_animation(speed, loops, segments, segment, parallel) + // 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 diff --git a/code/datums/components/atoms/radioactive.dm b/code/datums/components/atoms/radioactive.dm index 014c51b9af0..a2a51b23471 100644 --- a/code/datums/components/atoms/radioactive.dm +++ b/code/datums/components/atoms/radioactive.dm @@ -23,8 +23,6 @@ if(istype(parent, /atom)) RegisterSignal(parent, COMSIG_PARENT_EXAMINE, PROC_REF(rad_examine)) - // TODO: 516 added assoc filters; touch-up the datum filter procs and stop doing this. - RegisterSignal(parent, COMSIG_ATOM_RELOAD_FILTERS, PROC_REF(reload_filters)) else . = COMPONENT_INCOMPATIBLE CRASH("Something that wasn't an atom was given /datum/component/radioactive") @@ -32,17 +30,16 @@ if(strength > RAD_MINIMUM_CONTAMINATION) SSradiation.warn(src) - //Let's make er glow - //This relies on parent not being a turf or something. IF YOU CHANGE THAT, CHANGE THIS - var/atom/movable/master = parent - master.add_filter("rad_glow", 2, list("type" = "outline", "color" = "#14fff714", "size" = 2)) - addtimer(CALLBACK(src, PROC_REF(glow_loop), master), rand(1,19))//Things should look uneven + create_glow() + SSradiation.sources += src /datum/component/radioactive/Destroy() + var/atom/movable/parent_movable = parent + if (istype(parent_movable)) + parent_movable.remove_filter("rad_glow") + SSradiation.sources -= src - var/atom/movable/master = parent - master.remove_filter("rad_glow") return ..() /datum/component/radioactive/proc/emit(ds) @@ -66,15 +63,21 @@ if(!(datum_flags & DF_ISPROCESSING)) // keep going SSradiation.sources += src -/datum/component/radioactive/proc/reload_filters(datum/source) - SIGNAL_HANDLER - glow_loop(source) +/datum/component/radioactive/proc/create_glow() + var/atom/movable/parent_movable = parent + if (!istype(parent_movable)) + return -/datum/component/radioactive/proc/glow_loop(atom/movable/master) - var/filter = master.get_filter("rad_glow") - if(filter) - animate(filter, alpha = 75, time = 15, loop = -1) - animate(alpha = 25, time = 25) + parent_movable.add_filter("rad_glow", 2, list("type" = "outline", "color" = "#14fff714", "size" = 2)) + addtimer(CALLBACK(src, PROC_REF(start_glow_loop), parent_movable), rand(0.1 SECONDS, 1.9 SECONDS)) // Things should look uneven + +/datum/component/radioactive/proc/start_glow_loop(atom/movable/parent_movable) + var/filter = parent_movable.get_filter("rad_glow") + if (!filter) + return + + animate(filter, alpha = 110, time = 1.5 SECONDS, loop = -1) + animate(alpha = 40, time = 2.5 SECONDS) /datum/component/radioactive/InheritComponent(datum/component/C, i_am_original, _strength, _source, _half_life, _can_contaminate) if(!i_am_original) diff --git a/code/datums/datum-filters.dm b/code/datums/datum-filters.dm index d6df77b1070..5b59aa876c4 100644 --- a/code/datums/datum-filters.dm +++ b/code/datums/datum-filters.dm @@ -4,41 +4,71 @@ * * Arguments: * * name - Filter name - * * priority - Priority used when sorting the filter. Lower is applied first. + * * priority - Priority used when sorting the filter. * * params - Parameters of the filter. + * * update - If we should update our actual filters list, or wait until something updates it later */ -/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() +/datum/proc/add_filter(name, priority, list/params, update = TRUE) 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) - SEND_SIGNAL(src, COMSIG_ATOM_RELOAD_FILTERS) + LAZYINITLIST(filter_data) + LAZYINITLIST(filter_cache) + var/list/copied_parameters = params.Copy() + copied_parameters["name"] = name + copied_parameters["priority"] = priority + for (var/index in 1 to length(filter_data)) + var/list/filter_info = filter_data[index] + if (filter_info["name"] != name) + continue + filter_data -= list(filter_info) + filter_cache -= filter_cache[index] + break -/obj/item/update_filters() + BINARY_INSERT_DEFINE(list(copied_parameters), filter_data, SORT_VAR_NO_TYPE, copied_parameters, SORT_PRIORITY_INDEX, COMPARE_KEY) + + for (var/index in 1 to length(filter_data)) + var/list/filter_info = filter_data[index] + if (filter_info["name"] != name) + continue + var/list/arguments = filter_info.Copy() + arguments -= "priority" + filter_cache.Insert(index, filter(arglist(arguments))) + break + + if (update) + atom_cast.filters = filter_cache + +/// A version of add_filter that takes a list of filters to add rather than being individual, to limit appearance updates +/datum/proc/add_filters(list/list/filters, update = TRUE) + ASSERT(isatom(src) || isimage(src)) + var/atom/atom_cast = src // filters only work with images or atoms. + for (var/list/individual_filter as anything in filters) + add_filter(individual_filter["name"], individual_filter["priority"], individual_filter["params"], update = FALSE) + if (update) + atom_cast.filters = filter_cache + +/// Reapplies all the filters. If start_index is passed, only a portion of all filters are reapplied starting from said index +/datum/proc/update_filters(start_index = null) + ASSERT(isatom(src) || isimage(src)) + var/atom/atom_cast = src // filters only work with images or atoms. + if (start_index) + filter_cache.Cut(start_index) + else + atom_cast.filters = null + filter_cache.Cut() + + for (var/index in start_index || 1 to length(filter_data)) + var/list/filter_info = filter_data[index] + var/list/arguments = filter_info.Copy() + arguments -= "priority" + if (start_index) // See https://www.byond.com/forum/post/2980598 as to why we cannot just override the existing filter + atom_cast.filters -= filter_info["name"] // We're trapped in the belly of this horrible machine + filter_cache += filter(arglist(arguments)) // And the machine is bleeding to death + + atom_cast.filters = filter_cache + UNSETEMPTY(filter_data) + +/obj/item/update_filters(start_index = null) . = ..() update_action_buttons() @@ -48,17 +78,29 @@ * * 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. + * * update - If we should apply our filter cache to our actual filters */ -/datum/proc/modify_filter(name, list/new_params, overwrite = FALSE) - var/filter = get_filter(name) - if(!filter) +/datum/proc/modify_filter(name, list/new_params, overwrite = FALSE, update = TRUE) + ASSERT(isatom(src) || isimage(src)) + var/atom/atom_cast = src // filters only work with images or atoms. + for (var/index in 1 to length(filter_data)) + var/list/filter_info = filter_data[index] + if (filter_info["name"] != name) + continue + + if (overwrite) + filter_data[index] = new_params + else + for (var/thing in new_params) + filter_info[thing] = new_params[thing] + + var/list/arguments = filter_info.Copy() + arguments -= "priority" + filter_cache[index] = filter(arglist(arguments)) + + if (update) + atom_cast.filters = filter_cache 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. @@ -79,45 +121,85 @@ animate(filter, new_params, time = time, easing = easing, loop = loop) modify_filter(name, new_params) +/** Keeps the steps in the correct order. +* Arguments: +* * params - the parameters you want this step to animate to +* * duration - the time it takes to animate this step +* * easing - the type of easing this step has +*/ +/proc/filter_chain_step(params, duration, easing, flags) + params -= "type" + return list("params" = params, "duration" = duration, "easing" = easing, "flags" = flags) + +/** Similar to transition_filter(), except it creates an animation chain that moves between a list of states. + * Arguments: + * * name - Filter name + * * num_loops - Amount of times the chain loops. INDEFINITE = Infinite + * * ... - a list of each link in the animation chain. Use filter_chain_step(params, duration, easing) for each link + * Example use: + * * add_filter("blue_pulse", 1, color_matrix_filter(COLOR_WHITE)) + * * transition_filter_chain(src, "blue_pulse", INDEFINITE,\ + * * filter_chain_step(color_matrix_filter(COLOR_BLUE), 10 SECONDS, CUBIC_EASING),\ + * * filter_chain_step(color_matrix_filter(COLOR_WHITE), 10 SECONDS, CUBIC_EASING)) + * The above code would edit a color_matrix_filter() to slowly turn blue over 10 seconds before returning back to white 10 seconds after, repeating this chain forever. + */ +/datum/proc/transition_filter_chain(name, num_loops, ...) + var/list/transition_steps = args.Copy(3) + var/filter = get_filter(name) + if (!filter) + return + var/list/first_step = transition_steps[1] + animate(filter, first_step["params"], time = first_step["duration"], easing = first_step["easing"], flags = first_step["flags"], loop = num_loops) + for (var/transition_step in 2 to length(transition_steps)) + var/list/this_step = transition_steps[transition_step] + animate(this_step["params"], time = this_step["duration"], easing = this_step["easing"], flags = this_step["flags"]) + /// Updates the priority of the passed filter key /datum/proc/change_filter_priority(name, new_priority) - if(!filter_data || !filter_data[name]) - return + for (var/list/filter_info as anything in filter_data) + if (filter_info["name"] != name) + continue - filter_data[name]["priority"] = new_priority - update_filters() + remove_filter(name, update = FALSE) + add_filter(name, new_priority, filter_info) + return /// 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)] + var/atom/atom_cast = src // filters only work with images or atoms. + return atom_cast.filters[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) +/// Returns filter data associated with the passed key +/datum/proc/get_filter_data(name) + for (var/list/filter_info as anything in filter_data) + if (filter_info["name"] == name) + return filter_info.Copy() /// Removes the passed filter, or multiple filters, if supplied with a list. -/datum/proc/remove_filter(name_or_names) +/datum/proc/remove_filter(name_or_names, update = TRUE) + ASSERT(isatom(src) || isimage(src)) if(!filter_data) return - + var/atom/atom_cast = src // filters only work with images or atoms. 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() + var/list/new_data = list() + var/list/new_cache = list() + for (var/index in 1 to length(filter_data)) + var/list/filter_info = filter_data[index] + if (!(filter_info["name"] in names)) + new_data += list(filter_info) + new_cache += filter_cache[index] + filter_data = new_data + filter_cache = new_cache + if (update) + atom_cast.filters = filter_cache 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 + filter_cache = null atom_cast.filters = null diff --git a/code/datums/datum.dm b/code/datums/datum.dm index d815ee1e6c4..5a3ff400c51 100644 --- a/code/datums/datum.dm +++ b/code/datums/datum.dm @@ -62,9 +62,11 @@ //* misc - filters *// /// List for handling persistent filters. - /// - /// * This is on /datum so it can be used on /image. This is pretty horrible. How do we fix this? var/list/filter_data + /// An accursed beast of a list that contains our filters. Why? Because var/list/filters on atoms/images isn't actually a list + /// but a snowflaked skinwalker pretending to be one, which doesn't support half the list procs/operations and the other half behaves weirdly + /// so we cut down on filter creation and appearance update costs by editing *this* list, and then assigning ours to it + var/list/filter_cache //* misc - reftracking *// #ifdef REFERENCE_TRACKING diff --git a/code/datums/status_effects/grouped/proto_kinetic_mark.dm b/code/datums/status_effects/grouped/proto_kinetic_mark.dm index 23879dee212..e8970f955e3 100644 --- a/code/datums/status_effects/grouped/proto_kinetic_mark.dm +++ b/code/datums/status_effects/grouped/proto_kinetic_mark.dm @@ -11,15 +11,13 @@ /datum/status_effect/grouped/proto_kinetic_mark/on_apply() RegisterSignal(owner, COMSIG_MOVABLE_PROTO_KINETIC_SCAN, PROC_REF(signal_scan)) RegisterSignal(owner, COMSIG_MOVABLE_PROTO_KINETIC_DETONATION, PROC_REF(signal_detonation)) - RegisterSignal(owner, COMSIG_ATOM_RELOAD_FILTERS, PROC_REF(inject_visual_filter)) inject_visual_filter() return ..() /datum/status_effect/grouped/proto_kinetic_mark/on_remove() - UnregisterSignal(owner, COMSIG_MOVABLE_PROTO_KINETIC_SCAN, PROC_REF(signal_scan)) - UnregisterSignal(owner, COMSIG_MOVABLE_PROTO_KINETIC_DETONATION, PROC_REF(signal_detonation)) - UnregisterSignal(owner, COMSIG_ATOM_RELOAD_FILTERS, PROC_REF(signal_detonation)) - owner.update_filters() + UnregisterSignal(owner, COMSIG_MOVABLE_PROTO_KINETIC_SCAN) + UnregisterSignal(owner, COMSIG_MOVABLE_PROTO_KINETIC_DETONATION) + owner.remove_filter(list("pkm-blur", "pkm-shadow"), TRUE) return ..() /datum/status_effect/grouped/proto_kinetic_mark/proc/signal_scan(datum/source, list/detonation_data) @@ -33,71 +31,24 @@ /datum/status_effect/grouped/proto_kinetic_mark/proc/inject_visual_filter(datum/source) SIGNAL_HANDLER - // TODO: 516 added assoc filters; touch-up the datum filter procs and stop doing this. - // the reason we do this is because update_filters() tramples 'manual filters' - // so we have to redo everything - var/list/creating_filter_args - var/list/animating_filter_args - var/target_filter + owner.add_filters(list( + list( + "name" = "pkm-blur", + "priority" = 1, + "params" = motion_blur_filter(0, 0), + ), + list( + "name" = "pkm-shadow", + "priority" = 1, + "params" = drop_shadow_filter(0.5, 0.5, 0, 0, "#1c1c1c77"), + ), + )) - creating_filter_args = list( - type = "motion_blur", - x = 0, - y = 0, - ) - owner.filters += filter(arglist(creating_filter_args)) - target_filter = owner.filters[length(owner.filters)] + var/pkm_blur = owner.get_filter("pkm-blur") + animate(pkm_blur, appearance = list(x = 1.5), time = 0.33 SECONDS, loop = -1, easing = QUAD_EASING) + animate(pkm_blur, appearance = list(x = 0), time = 0.33 SECONDS, loop = -1, easing = LINEAR_EASING, flags = ANIMATION_CONTINUE) - animating_filter_args = list( - x = 1.5, - ) - animate( - target_filter, - appearance = animating_filter_args, - easing = QUAD_EASING, - time = 0.33 SECONDS, - loop = -1, - ) - animating_filter_args = list( - x = 0, - ) - animate( - target_filter, - appearance = animating_filter_args, - easing = LINEAR_EASING, - time = 0.33 SECONDS, - flags = ANIMATION_CONTINUE, - ) + var/pkm_shadow = owner.get_filter("pkm-shadow") + animate(pkm_shadow, appearance = list(size = 1.65), time = 0.33 SECONDS, loop = -1, easing = QUAD_EASING, flags = ANIMATION_PARALLEL) + animate(pkm_shadow, appearance = list(size = 0), time = 0.33 SECONDS, loop = -1, easing = LINEAR_EASING, flags = ANIMATION_CONTINUE) - creating_filter_args = list( - type = "drop_shadow", - x = 0.5, - y = 0.5, - size = 0, - offset = 0, - color = "#1c1c1c77" - ) - owner.filters += filter(arglist(creating_filter_args)) - target_filter = owner.filters[length(owner.filters)] - - animating_filter_args = list( - size = 1.65, - ) - animate( - target_filter, - appearance = animating_filter_args, - easing = QUAD_EASING, - time = 0.33 SECONDS, - loop = -1, - flags = ANIMATION_PARALLEL - ) - animating_filter_args = list( - size = 0, - ) - animate( - target_filter, - appearance = animating_filter_args, - easing = LINEAR_EASING, - time = 0.33 SECONDS, - flags = ANIMATION_CONTINUE, - ) diff --git a/code/modules/admin/view_variables/filteriffic.dm b/code/modules/admin/view_variables/filteriffic.dm index 12e6c96c862..5b1be0a85ae 100644 --- a/code/modules/admin/view_variables/filteriffic.dm +++ b/code/modules/admin/view_variables/filteriffic.dm @@ -10,7 +10,7 @@ /datum/filter_editor/ui_interact(mob/user, datum/tgui/ui) ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, "Filteriffic") + ui = new(user, src, "Filterrific") ui.open() /datum/filter_editor/ui_static_data(mob/user, datum/tgui/ui) @@ -21,7 +21,14 @@ /datum/filter_editor/ui_data(mob/user, datum/tgui/ui) var/list/data = list() data["target_name"] = target.name - data["target_filter_data"] = target.filter_data + var/list/target_filter_data = list() + for (var/list/filter_info as anything in target.filter_data) + filter_info = deep_copy_list(filter_info) + if (filter_info["transform"]) + var/matrix/filter_transform = filter_info["transform"] + filter_info["transform"] = list("a" = filter_transform.a, "b" = filter_transform.b, "c" = filter_transform.c, "d" = filter_transform.d, "e" = filter_transform.e, "f" = filter_transform.f) + target_filter_data += list(filter_info) + data["target_filter_data"] = target_filter_data return data /datum/filter_editor/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state, datum/event_args/actor/actor) @@ -32,7 +39,7 @@ switch(action) if("add_filter") var/target_name = params["name"] - while(target.filter_data && target.filter_data[target_name]) + while(target.get_filter(target_name)) target_name = "[target_name]-dupe" target.add_filter(target_name, params["priority"], list("type" = params["type"])) . = TRUE @@ -40,9 +47,9 @@ target.remove_filter(params["name"]) . = TRUE if("rename_filter") - var/list/filter_data = target.filter_data[params["name"]] + var/list/filter_info = target.get_filter_data(params["name"]) target.remove_filter(params["name"]) - target.add_filter(params["new_name"], filter_data["priority"], filter_data) + target.add_filter(params["new_name"], filter_data["priority"], filter_info) . = TRUE if("edit_filter") target.remove_filter(params["name"]) @@ -56,43 +63,43 @@ target.transition_filter(params["name"], params["new_data"], 4) . = TRUE if("modify_filter_value") - var/list/old_filter_data = target.filter_data[params["name"]] - var/list/new_filter_data = old_filter_data.Copy() - for(var/entry in params["new_data"]) - new_filter_data[entry] = params["new_data"][entry] - for(var/entry in new_filter_data) - if(entry == GLOB.master_filter_info[old_filter_data["type"]]["defaults"][entry]) - new_filter_data.Remove(entry) - target.remove_filter(params["name"]) - target.add_filter(params["name"], old_filter_data["priority"], new_filter_data) + target.modify_filter(params["name"], params["new_data"]) . = TRUE if("modify_color_value") - var/new_color = input(usr, "Pick new filter color", "Filteriffic Colors!") as color|null + var/new_color = input(usr, "Pick new filter color", "Filterrific Colors!") as color|null if(new_color) target.transition_filter(params["name"], list("color" = new_color), 4) . = TRUE if("modify_icon_value") - var/icon/new_icon = usr.client.prompt_for_icon_or_null("Pick icon", "Icon") + var/icon/new_icon = input("Pick icon:", "Icon") as null|icon if(new_icon) - target.filter_data[params["name"]]["icon"] = new_icon - target.update_filters() + target.modify_filter(params["name"], list("icon" = new_icon)) . = TRUE + if("modify_transform_value") + var/list/filter_info = target.get_filter_data(params["name"]) + if (!filter_info) + return + var/matrix/new_transform = matrix(filter_info[params["field_name"]]) + if (!(params["transform_key"] in list("a", "b", "c", "d", "e", "f"))) + return + new_transform.vars[params["transform_key"]] = text2num(params["transform_value"]) + target.modify_filter(params["name"], list(params["field_name"] = new_transform)) + . = TRUE if("mass_apply") - if(!check_rights(R_FUN)) - to_chat(usr, SPAN_DANGER("Stay in your lane, jannie.")) + if(!check_rights_for(usr.client, R_FUN)) + to_chat(usr, SPAN_USERDANGER("Stay in your lane, jannie.")) return var/target_path = text2path(params["path"]) if(!target_path) return - var/filters_to_copy = target.filters - var/filter_data_to_copy = target.filter_data + var/list/filter_data_to_copy = target.filter_data var/count = 0 - for(var/thing in world.contents) - if(istype(thing, target_path)) - var/atom/thing_at = thing - thing_at.filters = filters_to_copy - thing_at.filter_data = filter_data_to_copy - count += 1 + for(var/atom/thing_at as anything in world.contents) + if(!istype(thing_at, target_path)) + continue + thing_at.filter_data = filter_data_to_copy.Copy() + thing_at.update_filters() + count += 1 message_admins("LOCAL CLOWN [usr.ckey] JUST MASS FILTER EDITED [count] WITH PATH OF [params["path"]]!") log_admin("LOCAL CLOWN [usr.ckey] JUST MASS FILTER EDITED [count] WITH PATH OF [params["path"]]!") diff --git a/tgui/packages/tgui/interfaces/Filteriffic.jsx b/tgui/packages/tgui/interfaces/Filterrific.jsx similarity index 52% rename from tgui/packages/tgui/interfaces/Filteriffic.jsx rename to tgui/packages/tgui/interfaces/Filterrific.jsx index 8e1188e3010..9117e6349fd 100644 --- a/tgui/packages/tgui/interfaces/Filteriffic.jsx +++ b/tgui/packages/tgui/interfaces/Filterrific.jsx @@ -11,9 +11,9 @@ import { NoticeBox, NumberInput, Section, + Stack, } from 'tgui-core/components'; -import { toFixed } from 'tgui-core/math'; -import { numberOfDecimalDigits } from 'tgui-core/math'; +import { numberOfDecimalDigits, toFixed } from 'tgui-core/math'; import { useBackend } from '../backend'; import { Window } from '../layouts'; @@ -29,7 +29,7 @@ const FilterIntegerEntry = (props) => { step={1} stepPixelSize={5} width="39px" - onDrag={(value) => + onChange={(value) => act('modify_filter_value', { name: filterName, new_data: { @@ -56,7 +56,7 @@ const FilterFloatEntry = (props) => { step={step} format={(value) => toFixed(value, numberOfDecimalDigits(step))} width="80px" - onDrag={(value) => + onChange={(value) => act('transition_filter_value', { name: filterName, new_data: { @@ -154,7 +154,7 @@ const FilterFlagsEntry = (props) => { const { act, data } = useBackend(); const filterInfo = data.filter_info; - const flags = filterInfo[filterType]['flags']; + const flags = filterInfo[filterType].flags; return map(flags, (bitField, flagName) => ( { )); }; +const FilterOptionsEntry = (props) => { + const { name, value, filterName, filterType } = props; + const { act, data } = useBackend(); + const filterInfo = data.filter_info; + const options = filterInfo[filterType].options[name]; + return ( + value === options[x])} + options={Object.keys(options)} + onSelected={(value) => + act('modify_filter_value', { + name: filterName, + new_data: { + [name]: options[value], + }, + }) + } + /> + ); +}; + +const FilterTransformEntry = (props) => { + const { value, name, filterName } = props; + const { act } = useBackend(); + + return ( + <> + + {['a', 'b', 'c'].map((letter_key) => ( + + + {letter_key}: + + + act('modify_transform_value', { + name: filterName, + field_name: name, + transform_key: letter_key, + transform_value: value, + }) + } + /> + + ))} + + + {['d', 'e', 'f'].map((letter_key) => ( + + + {letter_key}: + + + act('modify_transform_value', { + name: filterName, + field_name: name, + transform_key: letter_key, + transform_value: value, + }) + } + /> + + ))} + + + ); +}; + +const FilterMatrixEntry = (props) => { + const { name, value, filterName, filterType } = props; + const { act } = useBackend(); + const matrix_sizes = [9, 12, 16, 20]; + const resize_matrix = (matrix, size) => { + let identity = [1, 0, 0, 0, 1, 0, 0, 0, 1]; + switch (size) { + case 12: + identity = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0]; + break; + case 16: + identity = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; + break; + case 20: + identity = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0]; + break; + } + if (matrix === null || matrix === undefined) return identity; + + for (let i = 0; i < Math.min(size, matrix.length); i++) { + if (matrix.length === 9) { identity[i + Math.floor(i / 3)] = matrix[i]; } // Account for skipped constants + else identity[i] = matrix[i]; + } + return identity; + }; + + let matrix = value; + + if (value === null || value === undefined) { + matrix = resize_matrix(value, 9); + } + + const processed_matrix = []; + const row_width = matrix.length > 9 ? 4 : 3; + for (let i = 0; i < (matrix.length > 9 ? matrix.length / 4 : 3); i++) { + const new_row = []; + for (let j = 0; j < row_width; j++) { + new_row.push(matrix[i * row_width + j]); + } + processed_matrix.push(new_row); + } + + return ( + + `${size} elements`)} + onSelected={(option) => + matrix.length === parseInt(option.split(' '), 10) + ? null + : act('modify_filter_value', { + name: filterName, + new_data: { + [name]: resize_matrix( + matrix, + parseInt(option.split(' '), 10), + ), + }, + }) + } + /> + + {processed_matrix.map((matrix_row, row_index) => ( + + + {matrix_row.map((matrix_elem, elem_index) => ( + + { + matrix[row_index * row_width + elem_index] = value; + act('transition_filter_value', { + name: filterName, + new_data: { + [name]: matrix, + }, + }); + }} + /> + + ))} + + + ))} + + + ); +}; + const FilterDataEntry = (props) => { - const { name, value, hasValue, filterName } = props; + const { name, value, hasValue, filterName, filterType } = props; const filterEntryTypes = { int: , @@ -183,6 +359,10 @@ const FilterDataEntry = (props) => { color: , icon: , flags: , + options: , + transform: , + matrix: , + plug: 'Not Implemented', }; const filterEntryMap = { @@ -192,19 +372,32 @@ const FilterDataEntry = (props) => { render_source: 'string', flags: 'flags', size: 'float', - color: 'color', + color: { default: 'color', color: 'matrix' }, offset: 'float', - radius: 'float', + radius: 'int', falloff: 'float', density: 'int', - threshold: 'float', + alpha: 'int', + threshold: { rays: 'float', bloom: 'color' }, factor: 'float', repeat: 'int', + space: 'options', + blend_mode: 'options', + transform: 'transform', }; + let filterInputType = filterEntryMap[name]; + // i hate javascript, this checks if its a dict + if (filterInputType !== undefined && filterInputType.constructor === Object) { + filterInputType = filterInputType[filterType] || filterInputType.default; + } + return ( - {filterEntryTypes[filterEntryMap[name]] || 'Not Found (This is an error)'}{' '} + + {filterEntryTypes[filterInputType] || + 'Not Found (This is an error)'}{' '} + {!hasValue && ( (Default) @@ -219,15 +412,13 @@ const FilterEntry = (props) => { const { name, filterDataEntry } = props; const { type, priority, ...restOfProps } = filterDataEntry; - const filterDefaults = data['filter_info']; + const filterDefaults = data.filter_info; - const targetFilterPossibleKeys = Object.keys( - filterDefaults[type]['defaults'], - ); + const targetFilterPossibleKeys = Object.keys(filterDefaults[type].defaults); return ( { } > -
+
{targetFilterPossibleKeys.map((entryName) => { - const defaults = filterDefaults[type]['defaults']; + const defaults = filterDefaults[type].defaults; const value = restOfProps[entryName] || defaults[entryName]; const hasValue = value !== defaults[entryName]; return ( @@ -282,17 +473,17 @@ const FilterEntry = (props) => { ); }; -export const Filteriffic = (props) => { +export const Filterrific = (props) => { const { act, data } = useBackend(); const name = data.target_name || 'Unknown Object'; const filters = data.target_filter_data || {}; const hasFilters = Object.keys(filters).length !== 0; - const filterDefaults = data['filter_info']; + const filterDefaults = data.filter_info; const [massApplyPath, setMassApplyPath] = useState(''); const [hiddenSecret, setHiddenSecret] = useState(false); return ( - + DO NOT MESS WITH EXISTING FILTERS IF YOU DO NOT KNOW THE CONSEQUENCES. @@ -342,7 +533,11 @@ export const Filteriffic = (props) => { No filters ) : ( map(filters, (entry, key) => ( - + )) )}