diff --git a/.github/guides/VISUALS.md b/.github/guides/VISUALS.md index 938f70eab2f..0bd46b920da 100644 --- a/.github/guides/VISUALS.md +++ b/.github/guides/VISUALS.md @@ -554,6 +554,9 @@ At base BYOND only allows you to attach one particle emitter to any one `/atom`. The type is `/obj/effect/abstract/particle_holder`. Interacting with it's real simple, you just pass in the location to mirror, and the type to use. It'll do the rest. +> We have a debug tool for particles. You can access it in-game by vving an atom, going to the dropdown, and hitting `Edit Particles` +It'll let you add and tweak the particles attached to that atom. + ## Pixel Offsets - [Table of Contents](#table-of-contents) - [Reference Entry](https://www.byond.com/docs/ref/#/atom/var/pixel_x) diff --git a/code/__DEFINES/generators.dm b/code/__DEFINES/generators.dm new file mode 100644 index 00000000000..e9d373c9a65 --- /dev/null +++ b/code/__DEFINES/generators.dm @@ -0,0 +1,15 @@ +//generator types +#define GEN_NUM "num" +#define GEN_VECTOR "vector" +#define GEN_BOX "box" +#define GEN_COLOR "color" +#define GEN_CIRCLE "circle" +#define GEN_SPHERE "sphere" +#define GEN_SQUARE "square" +#define GEN_CUBE "cube" + +///particle editor var modifiers +#define P_DATA_GENERATOR "generator" +#define P_DATA_ICON_ADD "icon_add" +#define P_DATA_ICON_REMOVE "icon_remove" +#define P_DATA_ICON_WEIGHT "icon_edit" diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm index f2417fb0295..81cf2a50678 100644 --- a/code/__DEFINES/is_helpers.dm +++ b/code/__DEFINES/is_helpers.dm @@ -11,6 +11,8 @@ #define isweakref(D) (istype(D, /datum/weakref)) +#define isgenerator(A) (istype(A, /generator)) + //Turfs //#define isturf(A) (istype(A, /turf)) This is actually a byond built-in. Added here for completeness sake. diff --git a/code/__DEFINES/vv.dm b/code/__DEFINES/vv.dm index 0ae13df5e7e..c46ce8cacc3 100644 --- a/code/__DEFINES/vv.dm +++ b/code/__DEFINES/vv.dm @@ -94,6 +94,7 @@ #define VV_HK_TRIGGER_EXPLOSION "explode" #define VV_HK_AUTO_RENAME "auto_rename" #define VV_HK_EDIT_FILTERS "edit_filters" +#define VV_HK_EDIT_PARTICLES "edit_particles" #define VV_HK_EDIT_COLOR_MATRIX "edit_color_matrix" #define VV_HK_ADD_AI "add_ai" diff --git a/code/__HELPERS/generators.dm b/code/__HELPERS/generators.dm new file mode 100644 index 00000000000..d50df7deba1 --- /dev/null +++ b/code/__HELPERS/generators.dm @@ -0,0 +1,11 @@ +/** + * returns the arguments given to a generator and manually extracts them from the internal byond object + * returns: + * * flat list of strings for args given to the generator. + * * Note: this means things like "list(1,2,3)" will need to be processed + */ +/proc/return_generator_args(generator/target) + var/string_repr = "[target]" //the name of the generator is the string representation of it's _binobj, which also contains it's args + string_repr = copytext(string_repr, 11, length(string_repr)) // strips extraneous data + string_repr = replacetext(string_repr, "\"", "") // removes the " around the type + return splittext(string_repr, ", ") diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index d01a08a8958..0e65c70f1a1 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -1468,6 +1468,7 @@ /atom/movable/vv_get_dropdown() . = ..() + VV_DROPDOWN_OPTION(VV_HK_EDIT_PARTICLES, "Edit Particles") VV_DROPDOWN_OPTION(VV_HK_DEADCHAT_PLAYS, "Start/Stop Deadchat Plays") VV_DROPDOWN_OPTION(VV_HK_ADD_FANTASY_AFFIX, "Add Fantasy Affix") @@ -1477,6 +1478,10 @@ if(!.) return + if(href_list[VV_HK_EDIT_PARTICLES] && check_rights(R_VAREDIT)) + var/client/C = usr.client + C?.open_particle_editor(src) + if(href_list[VV_HK_DEADCHAT_PLAYS] && check_rights(R_FUN)) if(tgui_alert(usr, "Allow deadchat to control [src] via chat commands?", "Deadchat Plays [src]", list("Allow", "Cancel")) != "Allow") return diff --git a/code/game/objects/effects/particles/smoke.dm b/code/game/objects/effects/particles/smoke.dm index 85ad0e30ba1..eb9959229d5 100644 --- a/code/game/objects/effects/particles/smoke.dm +++ b/code/game/objects/effects/particles/smoke.dm @@ -9,7 +9,7 @@ fade = 1 SECONDS velocity = list(0, 0.4, 0) position = list(6, 0, 0) - drift = generator("sphere", 0, 2, NORMAL_RAND) + drift = generator(GEN_SPHERE, 0, 2, NORMAL_RAND) friction = 0.2 gravity = list(0, 0.95) grow = 0.05 diff --git a/code/game/objects/structures/bonfire.dm b/code/game/objects/structures/bonfire.dm index 3713466a5a6..a507ef4ef05 100644 --- a/code/game/objects/structures/bonfire.dm +++ b/code/game/objects/structures/bonfire.dm @@ -187,9 +187,9 @@ fade = 1 SECONDS grow = -0.01 velocity = list(0, 0) - position = generator("circle", 0, 16, NORMAL_RAND) - drift = generator("vector", list(0, -0.2), list(0, 0.2)) + position = generator(GEN_CIRCLE, 0, 16, NORMAL_RAND) + drift = generator(GEN_VECTOR, list(0, -0.2), list(0, 0.2)) gravity = list(0, 0.95) - scale = generator("vector", list(0.3, 0.3), list(1,1), NORMAL_RAND) + scale = generator(GEN_VECTOR, list(0.3, 0.3), list(1,1), NORMAL_RAND) rotation = 30 - spin = generator("num", -20, 20) + spin = generator(GEN_NUM, -20, 20) diff --git a/code/modules/admin/holder2.dm b/code/modules/admin/holder2.dm index 89ec19d7158..952b9b263b2 100644 --- a/code/modules/admin/holder2.dm +++ b/code/modules/admin/holder2.dm @@ -32,6 +32,7 @@ GLOBAL_PROTECT(href_token) var/deadmined var/datum/filter_editor/filteriffic + var/datum/particle_editor/particle_test var/datum/colorblind_tester/color_test = new var/datum/plane_master_debug/plane_debug diff --git a/code/modules/admin/view_variables/particle_editor.dm b/code/modules/admin/view_variables/particle_editor.dm new file mode 100644 index 00000000000..dcda7a6e0df --- /dev/null +++ b/code/modules/admin/view_variables/particle_editor.dm @@ -0,0 +1,199 @@ +/datum/particle_editor + /// movable whose particles we want to be editing + var/atom/movable/target + +/datum/particle_editor/New(atom/target) + src.target = target + +/datum/particle_editor/ui_state(mob/user) + return GLOB.admin_state + +/datum/particle_editor/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "ParticleEdit") + ui.open() + +/datum/particle_editor/ui_assets(mob/user) + . = ..() + . += get_asset_datum(/datum/asset/simple/particle_editor) + +///returns ui_data values for the particle editor +/particles/proc/return_ui_representation(mob/user) + var/data = list() + //affect entire set: no generators + data["width"] = width //float + data["height"] = height //float + data["count"] = count //float + data["spawning"] = spawning //float + data["bound1"] = islist(bound1) ? bound1 : list(bound1,bound1,bound1) //float OR list(x, y, z) + data["bound2"] = islist(bound2) ? bound2 : list(bound2,bound2,bound2) //float OR list(x, y, z) + data["gravity"] = gravity //list(x, y, z) + data["gradient"] = gradient //gradient array list(number, string, number, string, "loop", "space"=COLORSPACE_RGB) + data["transform"] = transform //list(a, b, c, d, e, f) OR list(xx,xy,xz, yx,yy,yz, zx,zy,zz) OR list(xx,xy,xz, yx,yy,yz, zx,zy,zz, cx,cy,cz) OR list(xx,xy,xz,xw, yx,yy,yz,yw, zx,zy,zz,zw, wx,wy,wz,ww) + + //applied on spawn + if(islist(icon)) + var/list/icon_data = list() + for(var/file in icon) + icon_data["[file]"] = icon[file] + data["icon"] = icon_data + else + data["icon"] = "[icon]" //list(icon = weight) OR file reference + data["icon_state"] = icon_state // list(icon_state = weight) OR string + if(isgenerator(lifespan)) + data["lifespan"] = return_generator_args(lifespan) + else + data["lifespan"] = lifespan //float + if(isgenerator(fade)) + data["fade"] = return_generator_args(fade) + else + data["fade"] = fade //float + if(isgenerator(fadein)) + data["fadein"] = return_generator_args(fadein) + else + data["fadein"] = fadein //float + if(isgenerator(color)) + data["color"] = return_generator_args(color) + else + data["color"] = color //float OR string + if(isgenerator(color_change)) + data["color_change"] = return_generator_args(color_change) + else + data["color_change"] = color_change //float + if(isgenerator(position)) + data["position"] = return_generator_args(position) + else + data["position"] = position //list(x,y) OR list(x,y,z) + if(isgenerator(velocity)) + data["velocity"] = return_generator_args(velocity) + else + data["velocity"] = velocity //list(x,y) OR list(x,y,z) + if(isgenerator(scale)) + data["scale"] = return_generator_args(scale) + else + data["scale"] = scale + if(isgenerator(grow)) + data["grow"] = return_generator_args(grow) + else + data["grow"] = grow //float OR list(x,y) + if(isgenerator(rotation)) + data["rotation"] = return_generator_args(rotation) + else + data["rotation"] = rotation + if(isgenerator(spin)) + data["spin"] = return_generator_args(spin) + else + data["spin"] = spin //float + if(isgenerator(friction)) + data["friction"] = return_generator_args(friction) + else + data["friction"] = friction //float: 0-1 + + //evaluated every tick + if(isgenerator(drift)) + data["drift"] = return_generator_args(drift) + else + data["drift"] = drift + return data + +/datum/particle_editor/ui_data(mob/user) + var/list/data = list() + data["target_name"] = target.name + if(!target.particles) + target.particles = new /particles + data["particle_data"] = target.particles.return_ui_representation(user) + return data + +/datum/particle_editor/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if(.) + return + + switch(action) + if("delete_and_close") + ui.close() + target.particles = null + target = null + . = FALSE + if("new_type") + var/new_type = pick_closest_path(/particles, make_types_fancy(typesof(/particles))) + if(!new_type) + return FALSE + target.particles = new new_type + target.particles.datum_flags |= DF_VAR_EDITED + . = TRUE + if("transform_size") + var/list/values = list("Simple Matrix" = 6, "Complex Matrix" = 12, "Projection Matrix" = 16) + var/new_size = values[params["new_value"]] + if(!new_size) + return FALSE + . = TRUE + target.particles.datum_flags |= DF_VAR_EDITED + if(!target.particles.transform) + target.particles.transform = new /list(new_size) + return + var/size = length(target.particles.transform) + if(size < new_size) + target.particles.transform += new /list(new_size-size) + return + //transform is not cast as a list + var/list/holder = target.particles.transform + holder.Cut(new_size) + if("edit") + var/particles/owner = target.particles + var/param_var_name = params["var"] + if(!(param_var_name in owner.vars)) + return FALSE + var/var_value = params["new_value"] + var/var_mod = params["var_mod"] + // we can only return arrays from tgui so lets convert it to something usable if needed + switch(var_mod) + if(P_DATA_GENERATOR) + //these MUST be vectors and the others MUST be floats + if(var_value[1] in list(GEN_VECTOR, GEN_BOX)) + if(!islist(var_value[2])) + var_value[2] = list(var_value[2],var_value[2],var_value[2]) + if(!islist(var_value[3])) + var_value[3] = list(var_value[3],var_value[3],var_value[3]) + //this means we just switched off a vector-requiring generator type + else if(islist(var_value[2]) && islist(var_value[3])) + var_value[2] = var_value[1] + var_value[3] = var_value[1] + var_value = generator(arglist(var_value)) + if(P_DATA_ICON_ADD) + var_value = input("Pick icon:", "Icon") as null|icon + if(!var_value) + return FALSE + var/list/new_values = list() + new_values += var_value + new_values[var_value] = 1 + if(isicon(owner.icon)) + new_values[owner.icon] = 1 + owner.icon = new_values + else if(islist(owner.icon)) + owner.icon[var_value] = 1 + else + owner.icon = new_values + target.particles.datum_flags |= DF_VAR_EDITED + return TRUE + if(P_DATA_ICON_REMOVE) + for(var/file in owner.icon) + if("[file]" == var_value) + owner.icon -= file + UNSETEMPTY(owner.icon) + target.particles.datum_flags |= DF_VAR_EDITED + return TRUE + if(P_DATA_ICON_WEIGHT) + // [filename, new_weight] + var/list/mod_data = var_value + for(var/file in owner.icon) + if("[file]" == mod_data[1]) + owner.icon[file] = mod_data[2] + target.particles.datum_flags |= DF_VAR_EDITED + return TRUE + + owner.vars[param_var_name] = var_value + target.particles.datum_flags |= DF_VAR_EDITED + return TRUE + diff --git a/code/modules/asset_cache/assets/particle_editor.dm b/code/modules/asset_cache/assets/particle_editor.dm new file mode 100644 index 00000000000..f5f7bb00987 --- /dev/null +++ b/code/modules/asset_cache/assets/particle_editor.dm @@ -0,0 +1,17 @@ +/datum/asset/simple/particle_editor + assets = list( + "motion" = 'icons/ui_icons/particle_editor/motion.png', + + "uniform" = 'icons/ui_icons/particle_editor/uniform_rand.png', + "normal" ='icons/ui_icons/particle_editor/normal_rand.png', + "linear" = 'icons/ui_icons/particle_editor/linear_rand.png', + "square_rand" = 'icons/ui_icons/particle_editor/square_rand.png', + + "num" = 'icons/ui_icons/particle_editor/num_gen.png', + "vector" = 'icons/ui_icons/particle_editor/vector_gen.png', + "box" = 'icons/ui_icons/particle_editor/box_gen.png', + "circle" = 'icons/ui_icons/particle_editor/circle_gen.png', + "sphere" = 'icons/ui_icons/particle_editor/sphere_gen.png', + "square" = 'icons/ui_icons/particle_editor/square_gen.png', + "cube" = 'icons/ui_icons/particle_editor/cube_gen.png', + ) diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index 7162279f626..9a84de8b73f 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -1181,6 +1181,12 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( holder.filteriffic = new /datum/filter_editor(in_atom) holder.filteriffic.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) + if(holder) + holder.particle_test = new /datum/particle_editor(in_atom) + holder.particle_test.ui_interact(mob) + /client/proc/set_right_click_menu_mode(shift_only) if(shift_only) winset(src, "mapwindow.map", "right-click=true") diff --git a/code/modules/food_and_drinks/plate.dm b/code/modules/food_and_drinks/plate.dm index d0bfc8be72b..5a1af43a15c 100644 --- a/code/modules/food_and_drinks/plate.dm +++ b/code/modules/food_and_drinks/plate.dm @@ -67,7 +67,7 @@ /obj/item/plate/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) if(.) return - var/generator/scatter_gen = generator("circle", 0, 48, NORMAL_RAND) + var/generator/scatter_gen = generator(GEN_CIRCLE, 0, 48, NORMAL_RAND) var/scatter_turf = get_turf(hit_atom) for(var/obj/item/scattered_item as anything in contents) diff --git a/code/modules/religion/festival/note_particles.dm b/code/modules/religion/festival/note_particles.dm index cf8072b8854..a959bf4537a 100644 --- a/code/modules/religion/festival/note_particles.dm +++ b/code/modules/religion/festival/note_particles.dm @@ -19,8 +19,8 @@ fade = 1 SECONDS grow = -0.01 velocity = list(0, 0) - position = generator("circle", 0, 16, NORMAL_RAND) - drift = generator("vector", list(0, -0.2), list(0, 0.2)) + position = generator(GEN_CIRCLE, 0, 16, NORMAL_RAND) + drift = generator(GEN_VECTOR, list(0, -0.2), list(0, 0.2)) gravity = list(0, 0.95) /particles/musical_notes/holy diff --git a/icons/ui_icons/particle_editor/box_gen.png b/icons/ui_icons/particle_editor/box_gen.png new file mode 100644 index 00000000000..2a6fcd0c7e6 Binary files /dev/null and b/icons/ui_icons/particle_editor/box_gen.png differ diff --git a/icons/ui_icons/particle_editor/circle_gen.png b/icons/ui_icons/particle_editor/circle_gen.png new file mode 100644 index 00000000000..27aed9a0f26 Binary files /dev/null and b/icons/ui_icons/particle_editor/circle_gen.png differ diff --git a/icons/ui_icons/particle_editor/cube_gen.png b/icons/ui_icons/particle_editor/cube_gen.png new file mode 100644 index 00000000000..8f2f743c026 Binary files /dev/null and b/icons/ui_icons/particle_editor/cube_gen.png differ diff --git a/icons/ui_icons/particle_editor/linear_rand.png b/icons/ui_icons/particle_editor/linear_rand.png new file mode 100644 index 00000000000..72c07fcb58e Binary files /dev/null and b/icons/ui_icons/particle_editor/linear_rand.png differ diff --git a/icons/ui_icons/particle_editor/motion.png b/icons/ui_icons/particle_editor/motion.png new file mode 100644 index 00000000000..6cd82778272 Binary files /dev/null and b/icons/ui_icons/particle_editor/motion.png differ diff --git a/icons/ui_icons/particle_editor/normal_rand.png b/icons/ui_icons/particle_editor/normal_rand.png new file mode 100644 index 00000000000..b32e8b67732 Binary files /dev/null and b/icons/ui_icons/particle_editor/normal_rand.png differ diff --git a/icons/ui_icons/particle_editor/num_gen.png b/icons/ui_icons/particle_editor/num_gen.png new file mode 100644 index 00000000000..3771de85219 Binary files /dev/null and b/icons/ui_icons/particle_editor/num_gen.png differ diff --git a/icons/ui_icons/particle_editor/sphere_gen.png b/icons/ui_icons/particle_editor/sphere_gen.png new file mode 100644 index 00000000000..801b0db2e6a Binary files /dev/null and b/icons/ui_icons/particle_editor/sphere_gen.png differ diff --git a/icons/ui_icons/particle_editor/square_gen.png b/icons/ui_icons/particle_editor/square_gen.png new file mode 100644 index 00000000000..ee71f16c0b3 Binary files /dev/null and b/icons/ui_icons/particle_editor/square_gen.png differ diff --git a/icons/ui_icons/particle_editor/square_rand.png b/icons/ui_icons/particle_editor/square_rand.png new file mode 100644 index 00000000000..718f8038b58 Binary files /dev/null and b/icons/ui_icons/particle_editor/square_rand.png differ diff --git a/icons/ui_icons/particle_editor/uniform_rand.png b/icons/ui_icons/particle_editor/uniform_rand.png new file mode 100644 index 00000000000..09841b8a2d4 Binary files /dev/null and b/icons/ui_icons/particle_editor/uniform_rand.png differ diff --git a/icons/ui_icons/particle_editor/vector_gen.png b/icons/ui_icons/particle_editor/vector_gen.png new file mode 100644 index 00000000000..b3fe3df4e04 Binary files /dev/null and b/icons/ui_icons/particle_editor/vector_gen.png differ diff --git a/tgstation.dme b/tgstation.dme index d52584019b4..0a1c2cf4200 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -87,6 +87,7 @@ #include "code\__DEFINES\food.dm" #include "code\__DEFINES\footsteps.dm" #include "code\__DEFINES\fov.dm" +#include "code\__DEFINES\generators.dm" #include "code\__DEFINES\ghost.dm" #include "code\__DEFINES\gravity.dm" #include "code\__DEFINES\hud.dm" @@ -320,6 +321,7 @@ #include "code\__HELPERS\filters.dm" #include "code\__HELPERS\forensics.dm" #include "code\__HELPERS\game.dm" +#include "code\__HELPERS\generators.dm" #include "code\__HELPERS\global_lists.dm" #include "code\__HELPERS\guid.dm" #include "code\__HELPERS\hallucinations.dm" @@ -2181,6 +2183,7 @@ #include "code\modules\admin\view_variables\mark_datum.dm" #include "code\modules\admin\view_variables\mass_edit_variables.dm" #include "code\modules\admin\view_variables\modify_variables.dm" +#include "code\modules\admin\view_variables\particle_editor.dm" #include "code\modules\admin\view_variables\reference_tracking.dm" #include "code\modules\admin\view_variables\tag_datum.dm" #include "code\modules\admin\view_variables\topic.dm" @@ -2498,6 +2501,7 @@ #include "code\modules\asset_cache\assets\notes.dm" #include "code\modules\asset_cache\assets\orbit.dm" #include "code\modules\asset_cache\assets\paper.dm" +#include "code\modules\asset_cache\assets\particle_editor.dm" #include "code\modules\asset_cache\assets\patches.dm" #include "code\modules\asset_cache\assets\pda.dm" #include "code\modules\asset_cache\assets\permissions.dm" diff --git a/tgui/packages/tgui/interfaces/ParticleEdit/EntriesBasic.tsx b/tgui/packages/tgui/interfaces/ParticleEdit/EntriesBasic.tsx new file mode 100644 index 00000000000..85a344ae5d0 --- /dev/null +++ b/tgui/packages/tgui/interfaces/ParticleEdit/EntriesBasic.tsx @@ -0,0 +1,397 @@ +import { useBackend, useLocalState } from '../../backend'; +import { Box, Button, LabeledList, NumberInput, ColorBox, Input, Dropdown, Stack } from '../../components'; +import { EntryCoordProps, EntryFloatProps, EntryGradientProps, EntryIconStateProps, EntryTransformProps, MatrixTypes, ParticleUIData, P_DATA_ICON_ADD, P_DATA_ICON_REMOVE, P_DATA_ICON_WEIGHT, SpaceToNum, SpaceTypes } from './data'; +import { editKeyOf, editWeightOf, setGradientSpace } from './helpers'; + +export const EntryFloat = (props: EntryFloatProps, context) => { + const { act, data } = useBackend(context); + const [desc, setdesc] = useLocalState(context, 'desc', ''); + const { name, var_name, float } = props; + return ( + +