diff --git a/code/__DEFINES/colors.dm b/code/__DEFINES/colors.dm index 4278c69a177..cb6328110c3 100644 --- a/code/__DEFINES/colors.dm +++ b/code/__DEFINES/colors.dm @@ -14,6 +14,7 @@ #define COLOR_FLOORTILE_GRAY "#8D8B8B" #define COLOR_ALMOST_BLACK "#333333" #define COLOR_BLACK "#000000" +#define COLOR_HALF_TRANSPARENT_BLACK "#0000007A" #define COLOR_RED "#FF0000" #define COLOR_MOSTLY_PURE_RED "#FF3300" diff --git a/code/__DEFINES/vv.dm b/code/__DEFINES/vv.dm index dce63618ce6..c624b3ff6b7 100644 --- a/code/__DEFINES/vv.dm +++ b/code/__DEFINES/vv.dm @@ -84,6 +84,7 @@ #define VV_HK_TRIGGER_EXPLOSION "explode" #define VV_HK_AUTO_RENAME "auto_rename" #define VV_HK_RADIATE "radiate" +#define VV_HK_EDIT_FILTERS "edit_filters" #define VV_HK_ADD_AI "add_ai" // /obj diff --git a/code/__HELPERS/filters.dm b/code/__HELPERS/filters.dm new file mode 100644 index 00000000000..7be7ca5d732 --- /dev/null +++ b/code/__HELPERS/filters.dm @@ -0,0 +1,319 @@ +#define ICON_NOT_SET "Not Set" + +//This is stored as a nested list instead of datums or whatever because it json encodes nicely for usage in tgui +GLOBAL_LIST_INIT(master_filter_info, list( + "alpha" = list( + "defaults" = list( + "x" = 0, + "y" = 0, + "icon" = ICON_NOT_SET, + "render_source" = "", + "flags" = 0 + ), + "flags" = list( + "MASK_INVERSE" = MASK_INVERSE, + "MASK_SWAP" = MASK_SWAP + ) + ), + "angular_blur" = list( + "defaults" = list( + "x" = 0, + "y" = 0, + "size" = 1 + ) + ), + /* Not supported because making a proper matrix editor on the frontend would be a huge dick pain. + Uncomment if you ever implement it + "color" = list( + "defaults" = list( + "color" = matrix(), + "space" = FILTER_COLOR_RGB + ) + ), + */ + "displace" = list( + "defaults" = list( + "x" = 0, + "y" = 0, + "size" = null, + "icon" = ICON_NOT_SET, + "render_source" = "" + ) + ), + "drop_shadow" = list( + "defaults" = list( + "x" = 1, + "y" = -1, + "size" = 1, + "offset" = 0, + "color" = COLOR_HALF_TRANSPARENT_BLACK + ) + ), + "blur" = list( + "defaults" = list( + "size" = 1 + ) + ), + "layer" = list( + "defaults" = list( + "x" = 0, + "y" = 0, + "icon" = ICON_NOT_SET, + "render_source" = "", + "flags" = FILTER_OVERLAY, + "color" = "", + "transform" = null, + "blend_mode" = BLEND_DEFAULT + ) + ), + "motion_blur" = list( + "defaults" = list( + "x" = 0, + "y" = 0 + ) + ), + "outline" = list( + "defaults" = list( + "size" = 0, + "color" = COLOR_BLACK, + "flags" = NONE + ), + "flags" = list( + "OUTLINE_SHARP" = OUTLINE_SHARP, + "OUTLINE_SQUARE" = OUTLINE_SQUARE + ) + ), + "radial_blur" = list( + "defaults" = list( + "x" = 0, + "y" = 0, + "size" = 0.01 + ) + ), + "rays" = list( + "defaults" = list( + "x" = 0, + "y" = 0, + "size" = 16, + "color" = COLOR_WHITE, + "offset" = 0, + "density" = 10, + "threshold" = 0.5, + "factor" = 0, + "flags" = FILTER_OVERLAY | FILTER_UNDERLAY + ), + "flags" = list( + "FILTER_OVERLAY" = FILTER_OVERLAY, + "FILTER_UNDERLAY" = FILTER_UNDERLAY + ) + ), + "ripple" = list( + "defaults" = list( + "x" = 0, + "y" = 0, + "size" = 1, + "repeat" = 2, + "radius" = 0, + "falloff" = 1, + "flags" = NONE + ), + "flags" = list( + "WAVE_BOUNDED" = WAVE_BOUNDED + ) + ), + "wave" = list( + "defaults" = list( + "x" = 0, + "y" = 0, + "size" = 1, + "offset" = 0, + "flags" = NONE + ), + "flags" = list( + "WAVE_SIDEWAYS" = WAVE_SIDEWAYS, + "WAVE_BOUNDED" = WAVE_BOUNDED + ) + ) +)) + +#undef ICON_NOT_SET + +//Helpers to generate lists for filter helpers +//This is the only practical way of writing these that actually produces sane lists +/proc/alpha_mask_filter(x, y, icon/icon, render_source, flags) + . = list("type" = "alpha") + if(!isnull(x)) + .["x"] = x + if(!isnull(y)) + .["y"] = y + if(!isnull(icon)) + .["icon"] = icon + if(!isnull(render_source)) + .["render_source"] = render_source + if(!isnull(flags)) + .["flags"] = flags + +/proc/angular_blur_filter(x, y, size) + . = list("type" = "angular_blur") + if(!isnull(x)) + .["x"] = x + if(!isnull(y)) + .["y"] = y + if(!isnull(size)) + .["size"] = size + +/proc/color_matrix_filter(matrix/in_matrix, space) + . = list("type" = "color") + .["color"] = in_matrix + if(!isnull(space)) + .["space"] = space + +/proc/displacement_map_filter(icon, render_source, x, y, size = 32) + . = list("type" = "displace") + if(!isnull(icon)) + .["icon"] = icon + if(!isnull(render_source)) + .["render_source"] = render_source + if(!isnull(x)) + .["x"] = x + if(!isnull(y)) + .["y"] = y + if(!isnull(size)) + .["size"] = size + +/proc/drop_shadow_filter(x, y, size, offset, color) + . = list("type" = "drop_shadow") + if(!isnull(x)) + .["x"] = x + if(!isnull(y)) + .["y"] = y + if(!isnull(size)) + .["size"] = size + if(!isnull(offset)) + .["offset"] = offset + if(!isnull(color)) + .["color"] = color + +/proc/gauss_blur_filter(size) + . = list("type" = "blur") + if(!isnull(size)) + .["size"] = size + +/proc/layering_filter(icon, render_source, x, y, flags, color, transform, blend_mode) + . = list("type" = "layer") + if(!isnull(icon)) + .["icon"] = icon + if(!isnull(render_source)) + .["render_source"] = render_source + if(!isnull(x)) + .["x"] = x + if(!isnull(y)) + .["y"] = y + if(!isnull(color)) + .["color"] = color + if(!isnull(flags)) + .["flags"] = flags + if(!isnull(transform)) + .["transform"] = transform + if(!isnull(blend_mode)) + .["blend_mode"] = blend_mode + +/proc/motion_blur_filter(x, y) + . = list("type" = "motion_blur") + if(!isnull(x)) + .["x"] = x + if(!isnull(y)) + .["y"] = y + +/proc/outline_filter(size, color, flags) + . = list("type" = "outline") + if(!isnull(size)) + .["size"] = size + if(!isnull(color)) + .["color"] = color + if(!isnull(flags)) + .["flags"] = flags + +/proc/radial_blur_filter(size, x, y) + . = list("type" = "radial_blur") + if(!isnull(size)) + .["size"] = size + if(!isnull(x)) + .["x"] = x + if(!isnull(y)) + .["y"] = y + +/proc/rays_filter(size, color, offset, density, threshold, factor, x, y, flags) + . = list("type" = "rays") + if(!isnull(size)) + .["size"] = size + if(!isnull(color)) + .["color"] = color + if(!isnull(offset)) + .["offset"] = offset + if(!isnull(density)) + .["density"] = density + if(!isnull(threshold)) + .["threshold"] = threshold + if(!isnull(factor)) + .["factor"] = factor + if(!isnull(x)) + .["x"] = x + if(!isnull(y)) + .["y"] = y + if(!isnull(flags)) + .["flags"] = flags + +/proc/ripple_filter(radius, size, falloff, repeat, x, y, flags) + . = list("type" = "ripple") + if(!isnull(radius)) + .["radius"] = radius + if(!isnull(size)) + .["size"] = size + if(!isnull(falloff)) + .["falloff"] = falloff + if(!isnull(repeat)) + .["repeat"] = repeat + if(!isnull(flags)) + .["flags"] = flags + if(!isnull(x)) + .["x"] = x + if(!isnull(y)) + .["y"] = y + +/proc/wave_filter(x, y, size, offset, flags) + . = list("type" = "wave") + if(!isnull(size)) + .["size"] = size + if(!isnull(x)) + .["x"] = x + if(!isnull(y)) + .["y"] = y + if(!isnull(offset)) + .["offset"] = offset + if(!isnull(flags)) + .["flags"] = flags + +/proc/apply_wibbly_filters(atom/in_atom, length) + for(var/i in 1 to 7) + //This is a very baffling and strange way of doing this but I am just preserving old functionality + var/X + var/Y + var/rsq + do + X = 60*rand() - 30 + Y = 60*rand() - 30 + rsq = X*X + Y*Y + while(rsq<100 || rsq>900) // Yeah let's just loop infinitely due to bad luck what's the worst that could happen? + var/random_roll = rand() + in_atom.add_filter("wibbly-[i]", 5, wave_filter(x = X, y = Y, size = rand() * 2.5 + 0.5, offset = random_roll)) + var/filter = in_atom.get_filter("wibbly-[i]") + 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) + var/filter + for(var/i in 1 to 7) + filter = in_atom.get_filter("wibbly-[i]") + animate(filter) + in_atom.remove_filter("wibbly-[i]") diff --git a/code/_onclick/hud/plane_master.dm b/code/_onclick/hud/plane_master.dm index 35e8df2d89e..b7d66406164 100644 --- a/code/_onclick/hud/plane_master.dm +++ b/code/_onclick/hud/plane_master.dm @@ -26,18 +26,9 @@ /atom/movable/screen/plane_master/openspace/Initialize() . = ..() - filters += filter(type = "drop_shadow", color = "#04080FAA", size = -10) - filters += filter(type = "drop_shadow", color = "#04080FAA", size = -15) - filters += filter(type = "drop_shadow", color = "#04080FAA", size = -20) - -/atom/movable/screen/plane_master/proc/outline(_size, _color) - filters += filter(type = "outline", size = _size, color = _color) - -/atom/movable/screen/plane_master/proc/shadow(_size, _border, _offset = 0, _x = 0, _y = 0, _color = "#04080FAA") - filters += filter(type = "drop_shadow", x = _x, y = _y, color = _color, size = _size, offset = _offset) - -/atom/movable/screen/plane_master/proc/clear_filters() - filters = list() + add_filter("first_stage_openspace", 1, drop_shadow_filter(color = "#04080FAA", size = -10)) + add_filter("second_stage_openspace", 2, drop_shadow_filter(color = "#04080FAA", size = -15)) + add_filter("third_stage_openspace", 2, drop_shadow_filter(color = "#04080FAA", size = -20)) ///Contains just the floor /atom/movable/screen/plane_master/floor @@ -47,9 +38,9 @@ blend_mode = BLEND_OVERLAY /atom/movable/screen/plane_master/floor/backdrop(mob/mymob) - filters = list() + clear_filters() if(istype(mymob) && mymob.eye_blurry) - filters += GAUSSIAN_BLUR(clamp(mymob.eye_blurry*0.1,0.6,3)) + add_filter("eye_blur", 1, gauss_blur_filter(clamp(mymob.eye_blurry * 0.1, 0.6, 3))) ///Contains most things in the game world /atom/movable/screen/plane_master/game_world @@ -59,11 +50,11 @@ blend_mode = BLEND_OVERLAY /atom/movable/screen/plane_master/game_world/backdrop(mob/mymob) - filters = list() + clear_filters() if(istype(mymob) && mymob.client && mymob.client.prefs && mymob.client.prefs.ambientocclusion) - filters += AMBIENT_OCCLUSION + add_filter("AO", 1, drop_shadow_filter(x = 0, y = -2, size = 4, color = "#04080FAA")) if(istype(mymob) && mymob.eye_blurry) - filters += GAUSSIAN_BLUR(clamp(mymob.eye_blurry*0.1,0.6,3)) + add_filter("eye_blur", 1, gauss_blur_filter(clamp(mymob.eye_blurry * 0.1, 0.6, 3))) ///Contains all lighting objects @@ -79,9 +70,9 @@ /atom/movable/screen/plane_master/lighting/Initialize() . = ..() - filters += filter(type="alpha", render_source = EMISSIVE_RENDER_TARGET, flags = MASK_INVERSE) - filters += filter(type="alpha", render_source = EMISSIVE_UNBLOCKABLE_RENDER_TARGET, flags = MASK_INVERSE) - filters += filter(type="alpha", render_source = O_LIGHTING_VISUAL_RENDER_TARGET, flags = MASK_INVERSE) + add_filter("emissives", 1, alpha_mask_filter(render_source = EMISSIVE_RENDER_TARGET, flags = MASK_INVERSE)) + add_filter("unblockable_emissives", 2, alpha_mask_filter(render_source = EMISSIVE_UNBLOCKABLE_RENDER_TARGET, flags = MASK_INVERSE)) + add_filter("object_lighting", 3, alpha_mask_filter(render_source = O_LIGHTING_VISUAL_RENDER_TARGET, flags = MASK_INVERSE)) /** * Things placed on this mask the lighting plane. Doesn't render directly. @@ -97,7 +88,7 @@ /atom/movable/screen/plane_master/emissive/Initialize() . = ..() - filters += filter(type="alpha", render_source=EMISSIVE_BLOCKER_RENDER_TARGET, flags=MASK_INVERSE) + add_filter("emissive_block", 1, alpha_mask_filter(render_source = EMISSIVE_BLOCKER_RENDER_TARGET, flags = MASK_INVERSE)) /** * Things placed on this always mask the lighting plane. Doesn't render directly. @@ -163,4 +154,4 @@ /atom/movable/screen/plane_master/runechat/backdrop(mob/mymob) filters = list() if(istype(mymob) && mymob.client?.prefs?.ambientocclusion) - filters += AMBIENT_OCCLUSION + add_filter("AO", 1, drop_shadow_filter(x = 0, y = -2, size = 4, color = "#04080FAA")) diff --git a/code/game/atoms.dm b/code/game/atoms.dm index f9619a6abb8..b18ae480a0a 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -1102,6 +1102,7 @@ VV_DROPDOWN_OPTION(VV_HK_TRIGGER_EMP, "EMP Pulse") VV_DROPDOWN_OPTION(VV_HK_TRIGGER_EXPLOSION, "Explosion") VV_DROPDOWN_OPTION(VV_HK_RADIATE, "Radiate") + VV_DROPDOWN_OPTION(VV_HK_EDIT_FILTERS, "Edit Filters") VV_DROPDOWN_OPTION(VV_HK_ADD_AI, "Add AI controller") /atom/vv_do_topic(list/href_list) @@ -1181,6 +1182,10 @@ if(newname && !(CHAT_FILTER_CHECK(newname) && alert(usr, "Your selected name contains words restricted by IC chat filters. Confirm this new name?", "IC Chat Filter Conflict", "Confirm", "Cancel") != "Confirm")) vv_auto_rename(newname) + if(href_list[VV_HK_EDIT_FILTERS] && check_rights(R_VAREDIT)) + var/client/C = usr.client + C?.open_filter_editor(src) + /atom/vv_get_header() . = ..() var/refid = REF(src) @@ -1488,14 +1493,14 @@ victim.log_message(message, LOG_ATTACK, color="blue") -/atom/movable/proc/add_filter(name,priority,list/params) +/atom/proc/add_filter(name,priority,list/params) LAZYINITLIST(filter_data) var/list/p = params.Copy() p["priority"] = priority filter_data[name] = p update_filters() -/atom/movable/proc/update_filters() +/atom/proc/update_filters() filters = null filter_data = sortTim(filter_data, /proc/cmp_filter_data_priority, TRUE) for(var/f in filter_data) @@ -1503,6 +1508,29 @@ 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() /obj/item/update_filters() . = ..() @@ -1510,14 +1538,24 @@ var/datum/action/A = X A.UpdateButtonIcon() -/atom/movable/proc/get_filter(name) +/atom/proc/get_filter(name) if(filter_data && filter_data[name]) return filters[filter_data.Find(name)] -/atom/movable/proc/remove_filter(name) - if(filter_data && filter_data[name]) - filter_data -= name - update_filters() +/atom/proc/remove_filter(name_or_names) + 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 + update_filters() + +/atom/proc/clear_filters() + filter_data = null + filters = null /atom/proc/intercept_zImpact(atom/movable/AM, levels = 1) . |= SEND_SIGNAL(src, COMSIG_ATOM_INTERCEPT_Z_FALL, AM, levels) diff --git a/code/game/objects/structures/statues.dm b/code/game/objects/structures/statues.dm index 3131192153e..275b18cdd94 100644 --- a/code/game/objects/structures/statues.dm +++ b/code/game/objects/structures/statues.dm @@ -426,8 +426,6 @@ Moving interrupts var/completion = 0 /// Greyscaled target with cutout filter var/mutable_appearance/target_appearance_with_filters - /// Cutout filter for main block sprite - var/partial_uncover_filter /// HSV color filters parameters var/static/list/greyscale_with_value_bump = list(0,0,0, 0,0,0, 0,0,1, 0,0,-0.05) @@ -490,20 +488,19 @@ Moving interrupts if(!target_appearance_with_filters) target_appearance_with_filters = new(current_target) target_appearance_with_filters.appearance_flags |= KEEP_TOGETHER + //Doesn't use filter helpers because MAs aren't atoms target_appearance_with_filters.filters = filter(type="color",color=greyscale_with_value_bump,space=FILTER_COLOR_HSV) completion = value var/static/icon/white = icon('icons/effects/alphacolors.dmi', "white") switch(value) if(0) //delete uncovered and reset filters - filters -= partial_uncover_filter + remove_filter("partial_uncover") target_appearance_with_filters = null else var/mask_offset = min(world.icon_size,round(completion * world.icon_size)) - if(partial_uncover_filter) - filters -= partial_uncover_filter - partial_uncover_filter = filter(type="alpha",icon=white,y=-mask_offset) - filters += partial_uncover_filter + remove_filter("partial_uncover") + add_filter("partial_uncover", 1, alpha_mask_filter(icon = white, y = -mask_offset)) target_appearance_with_filters.filters = filter(type="alpha",icon=white,y=-mask_offset,flags=MASK_INVERSE) update_icon() diff --git a/code/modules/admin/holder2.dm b/code/modules/admin/holder2.dm index 257f38dda48..882a43c9f48 100644 --- a/code/modules/admin/holder2.dm +++ b/code/modules/admin/holder2.dm @@ -28,6 +28,8 @@ GLOBAL_PROTECT(href_token) var/deadmined + var/datum/filter_editor/filteriffic + /datum/admins/New(datum/admin_rank/R, ckey, force_active = FALSE, protected) if(IsAdminAdvancedProcCall()) var/msg = " has tried to elevate permissions!" diff --git a/code/modules/admin/view_variables/filterrific.dm b/code/modules/admin/view_variables/filterrific.dm new file mode 100644 index 00000000000..e651028cbe6 --- /dev/null +++ b/code/modules/admin/view_variables/filterrific.dm @@ -0,0 +1,99 @@ +/datum/filter_editor + var/atom/target + +/datum/filter_editor/New(atom/target) + src.target = target + +/datum/filter_editor/ui_state(mob/user) + return GLOB.admin_state + +/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.open() + +/datum/filter_editor/ui_static_data(mob/user) + var/list/data = list() + data["filter_info"] = GLOB.master_filter_info + return data + +/datum/filter_editor/ui_data() + var/list/data = list() + data["target_name"] = target.name + data["target_filter_data"] = target.filter_data + return data + +/datum/filter_editor/ui_act(action, list/params) + . = ..() + if(.) + return + + switch(action) + if("add_filter") + var/target_name = params["name"] + while(target.filter_data && target.filter_data[target_name]) + target_name = "[target_name]-dupe" + target.add_filter(target_name, params["priority"], list("type" = params["type"])) + . = TRUE + if("remove_filter") + target.remove_filter(params["name"]) + . = TRUE + if("rename_filter") + var/list/filter_data = target.filter_data[params["name"]] + target.remove_filter(params["name"]) + target.add_filter(params["new_name"], filter_data["priority"], filter_data) + . = TRUE + if("edit_filter") + target.remove_filter(params["name"]) + target.add_filter(params["name"], params["priority"], params["new_filter"]) + . = TRUE + if("change_priority") + var/new_priority = params["new_priority"] + target.change_filter_priority(params["name"], new_priority) + . = TRUE + if("transition_filter_value") + target.transition_filter(params["name"], 4, params["new_data"]) + . = 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) + . = TRUE + if("modify_color_value") + var/new_color = input(usr, "Pick new filter color", "Filteriffic Colors!") as color|null + if(new_color) + target.transition_filter(params["name"], 4, list("color" = new_color)) + . = TRUE + if("modify_icon_value") + 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() + . = TRUE + if("mass_apply") + if(!check_rights_for(usr.client, R_FUN)) + to_chat(usr, "You activate \the [src].") playsound(src, 'sound/effects/seedling_chargeup.ogg', 100, TRUE, -6) - var/start = user.filters.len - var/X,Y,rsq,i,f - for(i=1, i<=7, ++i) - do - X = 60*rand() - 30 - Y = 60*rand() - 30 - rsq = X*X + Y*Y - while(rsq<100 || rsq>900) - user.filters += filter(type="wave", x=X, y=Y, size=rand()*2.5+0.5, offset=rand()) - for(i=1, i<=7, ++i) - f = user.filters[start+i] - animate(f, offset=f:offset, time=0, loop=3, flags=ANIMATION_PARALLEL) - animate(offset=f:offset-1, time=rand()*20+10) + apply_wibbly_filters(user) if (do_after(user, 50, target=user) && user.cell.use(activationCost)) playsound(src, 'sound/effects/bamf.ogg', 100, TRUE, -6) to_chat(user, "You are now disguised as the Nanotrasen engineering borg \"[friendlyName]\".") @@ -84,10 +72,7 @@ else to_chat(user, "The chameleon field fizzles.") do_sparks(3, FALSE, user) - for(i=1, i<=min(7, user.filters.len), ++i) // removing filters that are animating does nothing, we gotta stop the animations first - f = user.filters[start+i] - animate(f) - user.filters = null + remove_wibbly_filters(user) animation_playing = FALSE /obj/item/borg_chameleon/process() diff --git a/code/modules/cargo/supplypod.dm b/code/modules/cargo/supplypod.dm index 144614357ed..af1411d6bc8 100644 --- a/code/modules/cargo/supplypod.dm +++ b/code/modules/cargo/supplypod.dm @@ -617,7 +617,7 @@ smoke_effects[i] = smoke_part smoke_part.pixel_x = sin(rotation)*32 * i smoke_part.pixel_y = abs(cos(rotation))*32 * i - smoke_part.filters += filter(type = "blur", size = 4) + smoke_part.add_filter("smoke_blur", 1, gauss_blur_filter(size = 4)) var/time = (pod.delays[POD_FALLING] / length(smoke_effects))*(length(smoke_effects)-i) addtimer(CALLBACK(smoke_part, /obj/effect/supplypod_smoke/.proc/drawSelf, i), time, TIMER_CLIENT_TIME) //Go onto the last step after a very short falling animation QDEL_IN(smoke_part, pod.delays[POD_FALLING] + 35) @@ -627,7 +627,7 @@ return for (var/obj/effect/supplypod_smoke/smoke_part in smoke_effects) animate(smoke_part, alpha = 0, time = 20, flags = ANIMATION_PARALLEL) - animate(smoke_part.filters[1], size = 6, time = 15, easing = CUBIC_EASING|EASE_OUT, flags = ANIMATION_PARALLEL) + animate(smoke_part.get_filter("smoke_blur"), size = 6, time = 15, easing = CUBIC_EASING|EASE_OUT, flags = ANIMATION_PARALLEL) /obj/effect/pod_landingzone/proc/endLaunch() pod.tryMakeRubble(drop_location()) diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index e03667d6e64..1890c591436 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -1080,3 +1080,8 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( var/datum/verbs/menu/menuitem = GLOB.menulist[thing] if (menuitem) menuitem.Load_checked(src) + +/client/proc/open_filter_editor(atom/in_atom) + if(holder) + holder.filteriffic = new /datum/filter_editor(in_atom) + holder.filteriffic.ui_interact(mob) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 9c0cd93e38a..4619a8e26e8 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -610,8 +610,8 @@ if(mob_mask.Height() > world.icon_size || mob_mask.Width() > world.icon_size) var/health_doll_icon_state = health_doll_icon ? health_doll_icon : "megasprite" mob_mask = icon('icons/hud/screen_gen.dmi', health_doll_icon_state) //swap to something generic if they have no special doll - livingdoll.filters += filter(type="alpha", icon = mob_mask) - livingdoll.filters += filter(type="drop_shadow", size = -1) + livingdoll.add_filter("mob_shape_mask", 1, alpha_mask_filter(icon = mob_mask)) + livingdoll.add_filter("inset_drop_shadow", 2, drop_shadow_filter(size = -1)) if(severity > 0) overlay_fullscreen("brute", /atom/movable/screen/fullscreen/brute, severity) else diff --git a/tgstation.dme b/tgstation.dme index aa3f35fb3eb..a5a08dbcbb7 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -148,6 +148,7 @@ #include "code\__HELPERS\dates.dm" #include "code\__HELPERS\dna.dm" #include "code\__HELPERS\files.dm" +#include "code\__HELPERS\filters.dm" #include "code\__HELPERS\game.dm" #include "code\__HELPERS\global_lists.dm" #include "code\__HELPERS\heap.dm" @@ -1450,6 +1451,7 @@ #include "code\modules\admin\verbs\SDQL2\SDQL_2_wrappers.dm" #include "code\modules\admin\view_variables\admin_delete.dm" #include "code\modules\admin\view_variables\debug_variables.dm" +#include "code\modules\admin\view_variables\filterrific.dm" #include "code\modules\admin\view_variables\get_variables.dm" #include "code\modules\admin\view_variables\mark_datum.dm" #include "code\modules\admin\view_variables\mass_edit_variables.dm" diff --git a/tgui/docs/component-reference.md b/tgui/docs/component-reference.md index 503c012c001..19a703bbd13 100644 --- a/tgui/docs/component-reference.md +++ b/tgui/docs/component-reference.md @@ -361,12 +361,16 @@ and displays selected entry. **Props:** - See inherited props: [Box](#box) +- See inherited props: [Icon](#icon) - `options: string[]` - An array of strings which will be displayed in the dropdown when open - `selected: string` - Currently selected entry - `width: number` - Width of dropdown button and resulting menu -- `over: boolean` - dropdown renders over instead of below -- `color: string` - color of dropdown button +- `over: boolean` - Dropdown renders over instead of below +- `color: string` - Color of dropdown button +- `nochevron: boolean` - Whether or not the arrow on the right hand side of the dropdown button is visible +- `noscroll: boolean` - Whether or not the dropdown menu should have a scroll bar +- `displayText: string` - Text to always display in place of the selected text - `onClick: (e) => void` - Called when dropdown button is clicked - `onSelected: (value) => void` - Called when a value is picked from the list, `value` is the value that was picked diff --git a/tgui/packages/common/math.js b/tgui/packages/common/math.js index c3013b5a32a..df7e395ea79 100644 --- a/tgui/packages/common/math.js +++ b/tgui/packages/common/math.js @@ -88,3 +88,13 @@ export const keyOfMatchingRange = (value, ranges) => { } } }; + +/** + * Get number of digits following the decimal point in a number + */ +export const numberOfDecimalDigits = value => { + if (Math.floor(value) !== value) { + return value.toString().split('.')[1].length || 0; + } + return 0; +}; diff --git a/tgui/packages/tgui/components/Dropdown.js b/tgui/packages/tgui/components/Dropdown.js index adbb13de5b2..7486807e3f3 100644 --- a/tgui/packages/tgui/components/Dropdown.js +++ b/tgui/packages/tgui/components/Dropdown.js @@ -64,6 +64,9 @@ export class Dropdown extends Component { render() { const { props } = this; const { + icon, + iconRotation, + iconSpin, color = 'default', over, noscroll, @@ -72,6 +75,7 @@ export class Dropdown extends Component { onClick, selected, disabled, + displayText, ...boxProps } = props; const { @@ -114,8 +118,15 @@ export class Dropdown extends Component { } this.setOpen(!this.state.open); }}> + {icon && ( + + )} - {this.state.selected} + {displayText ? displayText : this.state.selected} {!!nochevron || ( diff --git a/tgui/packages/tgui/interfaces/Filteriffic.js b/tgui/packages/tgui/interfaces/Filteriffic.js new file mode 100644 index 00000000000..39695ea54c8 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Filteriffic.js @@ -0,0 +1,307 @@ +import { useBackend, useLocalState } from "../backend"; +import { Fragment } from 'inferno'; +import { Box, Button, Collapsible, ColorBox, Dropdown, Input, LabeledList, NoticeBox, NumberInput, Section } from '../components'; +import { Window } from '../layouts'; +import { map } from 'common/collections'; +import { toFixed } from 'common/math'; +import { numberOfDecimalDigits } from "../../common/math"; + +const FilterIntegerEntry = (props, context) => { + const { value, name, filterName } = props; + const { act } = useBackend(context); + return ( + act('modify_filter_value', { + name: filterName, + new_data: { + [name]: value, + }, + })} /> + ); +}; + +const FilterFloatEntry = (props, context) => { + const { value, name, filterName } = props; + const { act } = useBackend(context); + const [step, setStep] = useLocalState(context, `${filterName}-${name}`, 0.01); + return ( + + toFixed(value, numberOfDecimalDigits(step))} + width="80px" + onDrag={(e, value) => act('transition_filter_value', { + name: filterName, + new_data: { + [name]: value, + }, + })} /> + + Step: + + toFixed(value, 4)} + width="70px" + onChange={(e, value) => setStep(value)} /> + + ); +}; + +const FilterTextEntry = (props, context) => { + const { value, name, filterName } = props; + const { act } = useBackend(context); + + return ( + act('modify_filter_value', { + name: filterName, + new_data: { + [name]: value, + }, + })} /> + ); +}; + +const FilterColorEntry = (props, context) => { + const { value, filterName, name } = props; + const { act } = useBackend(context); + return ( + +