From 78ea4187fccd4c8ce9de2846b8f224484fc19d33 Mon Sep 17 00:00:00 2001 From: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Date: Thu, 13 Nov 2025 08:55:15 +0100 Subject: [PATCH] Implements color matrix and transform modification support for Filterrific (#93867) ## About The Pull Request Allows users to edit color matrix filters and layered filters' transforms through Filterrific, which were the two last unsupported filter fields. The menus are pretty minimal, but you shouldn't be touching them if you don't know what you're doing anyways. https://github.com/user-attachments/assets/e87447f3-e5d6-4f6c-a8e4-bf50a1d5fa79 ## Why It's Good For The Game Makes our tooling actually work ## Changelog :cl: admin: Implemented color matrix and transform modification support for Filterrific /:cl: --- code/modules/admin/holder2.dm | 2 +- .../admin/view_variables/filterrific.dm | 23 ++- .../nobody_wants_to_learn_matrix_math.dm | 2 +- code/modules/client/client_procs.dm | 4 +- .../{Filteriffic.jsx => Filterrific.jsx} | 164 +++++++++++++++++- 5 files changed, 184 insertions(+), 11 deletions(-) rename tgui/packages/tgui/interfaces/{Filteriffic.jsx => Filterrific.jsx} (65%) diff --git a/code/modules/admin/holder2.dm b/code/modules/admin/holder2.dm index 6be76b4df87..b87878e7453 100644 --- a/code/modules/admin/holder2.dm +++ b/code/modules/admin/holder2.dm @@ -32,7 +32,7 @@ GLOBAL_PROTECT(href_token) var/deadmined - var/datum/filter_editor/filteriffic + var/datum/filter_editor/filterrific var/datum/particle_editor/particle_test var/datum/colorblind_tester/color_test var/datum/plane_master_debug/plane_debug diff --git a/code/modules/admin/view_variables/filterrific.dm b/code/modules/admin/view_variables/filterrific.dm index f8dbd7fee43..89172c46e12 100644 --- a/code/modules/admin/view_variables/filterrific.dm +++ b/code/modules/admin/view_variables/filterrific.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) @@ -21,7 +21,14 @@ /datum/filter_editor/ui_data() 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) @@ -59,7 +66,7 @@ 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 @@ -68,6 +75,16 @@ if(new_icon) 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_for(usr.client, R_FUN)) to_chat(usr, span_userdanger("Stay in your lane, jannie.")) diff --git a/code/modules/admin/view_variables/nobody_wants_to_learn_matrix_math.dm b/code/modules/admin/view_variables/nobody_wants_to_learn_matrix_math.dm index 4d057383284..db887d8053e 100644 --- a/code/modules/admin/view_variables/nobody_wants_to_learn_matrix_math.dm +++ b/code/modules/admin/view_variables/nobody_wants_to_learn_matrix_math.dm @@ -4,7 +4,7 @@ * * More than just a completely true statement, this datum is created as a tgui interface * allowing you to modify each vector until you know what you're doing. - * Much like filteriffic, 'nobody wants to learn matrix math' is meant for developers like you and I + * Much like filterrific, 'nobody wants to learn matrix math' is meant for developers like you and I * to implement interesting matrix transformations without the hassle if needing to know... algebra? Damn, i'm stupid. */ /datum/nobody_wants_to_learn_matrix_math diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index d9c1389c0cf..19bfebc0fe2 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -1126,8 +1126,8 @@ GLOBAL_LIST_INIT(unrecommended_builds, list( /client/proc/open_filter_editor(atom/in_atom) if(holder) - holder.filteriffic = new /datum/filter_editor(in_atom) - holder.filteriffic.ui_interact(mob) + holder.filterrific = new /datum/filter_editor(in_atom) + holder.filterrific.ui_interact(mob) ///opens the particle editor UI for the in_atom object for this client /client/proc/open_particle_editor(atom/movable/in_atom) diff --git a/tgui/packages/tgui/interfaces/Filteriffic.jsx b/tgui/packages/tgui/interfaces/Filterrific.jsx similarity index 65% rename from tgui/packages/tgui/interfaces/Filteriffic.jsx rename to tgui/packages/tgui/interfaces/Filterrific.jsx index a82fecf8638..b1b28373853 100644 --- a/tgui/packages/tgui/interfaces/Filteriffic.jsx +++ b/tgui/packages/tgui/interfaces/Filterrific.jsx @@ -11,6 +11,7 @@ import { NoticeBox, NumberInput, Section, + Stack, } from 'tgui-core/components'; import { numberOfDecimalDigits, toFixed } from 'tgui-core/math'; @@ -193,6 +194,159 @@ const FilterOptionsEntry = (props) => { ); }; +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(' ')) + ? null + : act('modify_filter_value', { + name: filterName, + new_data: { + [name]: resize_matrix(matrix, parseInt(option.split(' '))), + }, + }) + } + /> + + {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, filterType } = props; @@ -204,6 +358,8 @@ const FilterDataEntry = (props) => { icon: , flags: , options: , + transform: , + matrix: , plug: 'Not Implemented', }; @@ -214,7 +370,7 @@ const FilterDataEntry = (props) => { render_source: 'string', flags: 'flags', size: 'float', - color: { default: 'color', color: 'plug' }, + color: { default: 'color', color: 'matrix' }, offset: 'float', radius: 'int', falloff: 'float', @@ -225,7 +381,7 @@ const FilterDataEntry = (props) => { repeat: 'int', space: 'options', blend_mode: 'options', - transform: 'plug', + transform: 'transform', }; let filterInputType = filterEntryMap[name]; @@ -315,7 +471,7 @@ 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 || {}; @@ -325,7 +481,7 @@ export const Filteriffic = (props) => { const [hiddenSecret, setHiddenSecret] = useState(false); return ( - + DO NOT MESS WITH EXISTING FILTERS IF YOU DO NOT KNOW THE CONSEQUENCES.