Filterrific! (#55246)

Filter refactor + In Game Filter Editor
Accessed via VV in the dropdown of atoms. "Edit Filters.
Makes filters actually usable.

Co-authored-by: ghgh <hghgh>
This commit is contained in:
Rob Bailey
2020-12-18 10:05:20 -08:00
committed by GitHub
parent aea44cd545
commit cb01640043
20 changed files with 835 additions and 63 deletions
+1
View File
@@ -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"
+1
View File
@@ -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
+319
View File
@@ -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]")
+13 -22
View File
@@ -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"))
+45 -7
View File
@@ -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)
+4 -7
View File
@@ -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()
+2
View File
@@ -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!"
@@ -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, "<span class='userdanger>Stay in your lane, jannie.</span>'")
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/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
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"]]!")
@@ -64,19 +64,7 @@
animation_playing = TRUE
to_chat(user, "<span class='notice'>You activate \the [src].</span>")
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, "<span class='notice'>You are now disguised as the Nanotrasen engineering borg \"[friendlyName]\".</span>")
@@ -84,10 +72,7 @@
else
to_chat(user, "<span class='warning'>The chameleon field fizzles.</span>")
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()
+2 -2
View File
@@ -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())
+5
View File
@@ -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)
+2 -2
View File
@@ -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
+2
View File
@@ -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"
+6 -2
View File
@@ -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
+10
View File
@@ -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;
};
+12 -1
View File
@@ -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 && (
<Icon
name={icon}
rotation={iconRotation}
spin={iconSpin}
mr={1} />
)}
<span className="Dropdown__selected-text">
{this.state.selected}
{displayText ? displayText : this.state.selected}
</span>
{!!nochevron || (
<span className="Dropdown__arrow-button">
@@ -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 (
<NumberInput
value={value}
minValue={-500}
maxValue={500}
stepPixelSize={5}
width="39px"
onDrag={(e, value) => 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 (
<Fragment>
<NumberInput
value={value}
minValue={-500}
maxValue={500}
stepPixelSize={4}
step={step}
format={value => toFixed(value, numberOfDecimalDigits(step))}
width="80px"
onDrag={(e, value) => act('transition_filter_value', {
name: filterName,
new_data: {
[name]: value,
},
})} />
<Box
inline
ml={2}
mr={1}>
Step:
</Box>
<NumberInput
value={step}
step={0.001}
format={value => toFixed(value, 4)}
width="70px"
onChange={(e, value) => setStep(value)} />
</Fragment>
);
};
const FilterTextEntry = (props, context) => {
const { value, name, filterName } = props;
const { act } = useBackend(context);
return (
<Input
value={value}
width="250px"
onInput={(e, value) => act('modify_filter_value', {
name: filterName,
new_data: {
[name]: value,
},
})} />
);
};
const FilterColorEntry = (props, context) => {
const { value, filterName, name } = props;
const { act } = useBackend(context);
return (
<Fragment>
<Button
icon="pencil-alt"
onClick={() => act('modify_color_value', {
name: filterName,
})} />
<ColorBox
color={value}
mr={0.5} />
<Input
value={value}
width="90px"
onInput={(e, value) => act('transition_filter_value', {
name: filterName,
new_data: {
[name]: value,
},
})} />
</Fragment>
);
};
const FilterIconEntry = (props, context) => {
const { value, filterName } = props;
const { act } = useBackend(context);
return (
<Fragment>
<Button
icon="pencil-alt"
onClick={() => act('modify_icon_value', {
name: filterName,
})} />
<Box inline ml={1}>
{value}
</Box>
</Fragment>
);
};
const FilterFlagsEntry = (props, context) => {
const { name, value, filterName, filterType } = props;
const { act, data } = useBackend(context);
const filterInfo = data.filter_info;
const flags = filterInfo[filterType]['flags'];
return (
map((bitField, flagName) => (
<Button.Checkbox
checked={value & bitField}
content={flagName}
onClick={() => act('modify_filter_value', {
name: filterName,
new_data: {
[name]: value ^ bitField,
},
})} />
))(flags)
);
};
const FilterDataEntry = (props, context) => {
const { name, value, hasValue, filterName } = props;
const filterEntryTypes = {
int: <FilterIntegerEntry {...props} />,
float: <FilterFloatEntry {...props} />,
string: <FilterTextEntry {...props} />,
color: <FilterColorEntry {...props} />,
icon: <FilterIconEntry {...props} />,
flags: <FilterFlagsEntry {...props} />,
};
const filterEntryMap = {
x: 'float',
y: 'float',
icon: 'icon',
render_source: 'string',
flags: 'flags',
size: 'float',
color: 'color',
offset: 'float',
radius: 'float',
falloff: 'float',
density: 'int',
threshold: 'float',
factor: 'float',
repeat: 'int',
};
return (
<LabeledList.Item label={name}>
{filterEntryTypes[filterEntryMap[name]] || "Not Found (This is an error)"}
{' '}
{!hasValue && <Box inline color="average">(Default)</Box>}
</LabeledList.Item>
);
};
const FilterEntry = (props, context) => {
const { act, data } = useBackend(context);
const { name, filterDataEntry } = props;
const { type, priority, ...restOfProps } = filterDataEntry;
const filterDefaults = data["filter_info"];
const targetFilterPossibleKeys = Object.keys(filterDefaults[type]['defaults']);
return (
<Collapsible
title={name + " (" + type + ")"}
buttons={(
<Fragment>
<NumberInput
value={priority}
stepPixelSize={10}
width="60px"
onChange={(e, value) => act('change_priority', {
name: name,
new_priority: value,
})}
/>
<Button.Input
content="Rename"
placeholder={name}
onCommit={(e, new_name) => act('rename_filter', {
name: name,
new_name: new_name,
})}
width="90px" />
<Button.Confirm
icon="minus"
onClick={() => act("remove_filter", { name: name })} />
</Fragment>
)}>
<Section level={2}>
<LabeledList>
{targetFilterPossibleKeys.map(entryName => {
const defaults = filterDefaults[type]['defaults'];
const value = restOfProps[entryName] || defaults[entryName];
const hasValue = value !== defaults[entryName];
return (
<FilterDataEntry
key={entryName}
filterName={name}
filterType={type}
name={entryName}
value={value}
hasValue={hasValue} />
);
})}
</LabeledList>
</Section>
</Collapsible>
);
};
export const Filteriffic = (props, context) => {
const { act, data } = useBackend(context);
const name = data.target_name || "Unknown Object";
const filters = data.target_filter_data || {};
const hasFilters = filters !== {};
const filterDefaults = data["filter_info"];
const [massApplyPath, setMassApplyPath] = useLocalState(context, 'massApplyPath', '');
const [hiddenSecret, setHiddenSecret] = useLocalState(context, 'hidden', false);
return (
<Window
width={500}
height={500}
title="Filteriffic"
resizable>
<Window.Content scrollable>
<NoticeBox danger>
DO NOT MESS WITH EXISTING FILTERS IF YOU DO NOT KNOW THE CONSEQUENCES.
YOU HAVE BEEN WARNED.
</NoticeBox>
<Section
title={hiddenSecret ? (
<Fragment>
<Box mr={0.5} inline>
MASS EDIT:
</Box>
<Input
value={massApplyPath}
width="100px"
onInput={(e, value) => setMassApplyPath(value)} />
<Button.Confirm
content="Apply"
confirmContent="ARE YOU SURE?"
onClick={() => act('mass_apply', { path: massApplyPath })} />
</Fragment>
) : (
<Box
inline
onDblClick={() => setHiddenSecret(true)}>
{name}
</Box>
)}
buttons={(
<Dropdown
icon="plus"
displayText="Add Filter"
nochevron
options={Object.keys(filterDefaults)}
onSelected={value => act('add_filter', {
name: 'default',
priority: 10,
type: value,
})} />
)} >
{!hasFilters ? (
<Box>
No filters
</Box>
) : (
map((entry, key) => (
<FilterEntry filterDataEntry={entry} name={key} key={key} />
))(filters)
)}
</Section>
</Window.Content>
</Window>
);
};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long