From ece35e83db68156067da9aa57081746837c988f9 Mon Sep 17 00:00:00 2001 From: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Date: Mon, 4 May 2026 11:19:35 +0200 Subject: [PATCH] Implements OverFlayer, VV dev tool for debugging appearances and images (#95852) --- code/__DEFINES/vv.dm | 1 + code/_globalvars/layers.dm | 190 +++++ code/datums/datumvars.dm | 3 + code/game/atom/atom_vv.dm | 1 + code/modules/admin/holder2.dm | 3 + .../appearance_debugger.dm | 243 +++++++ .../debug_variable_appearance.dm | 5 + tgstation.dme | 2 + .../AppearanceDebug/AppearanceBox.tsx | 134 ++++ .../AppearanceDebug/AppearanceInfo.tsx | 185 +++++ .../tgui/interfaces/AppearanceDebug/index.tsx | 670 ++++++++++++++++++ .../tgui/interfaces/AppearanceDebug/types.ts | 135 ++++ .../AppearanceDebug/useAppearanceDebug.ts | 30 + .../PlaneMasterDebug/PlaneEditor.tsx | 2 +- .../interfaces/PlaneMasterDebug/index.tsx | 1 + 15 files changed, 1604 insertions(+), 1 deletion(-) create mode 100644 code/_globalvars/layers.dm create mode 100644 code/modules/admin/verbs/appearance_debugger/appearance_debugger.dm create mode 100644 tgui/packages/tgui/interfaces/AppearanceDebug/AppearanceBox.tsx create mode 100644 tgui/packages/tgui/interfaces/AppearanceDebug/AppearanceInfo.tsx create mode 100644 tgui/packages/tgui/interfaces/AppearanceDebug/index.tsx create mode 100644 tgui/packages/tgui/interfaces/AppearanceDebug/types.ts create mode 100644 tgui/packages/tgui/interfaces/AppearanceDebug/useAppearanceDebug.ts diff --git a/code/__DEFINES/vv.dm b/code/__DEFINES/vv.dm index eae9769003f..1cbb45a235a 100644 --- a/code/__DEFINES/vv.dm +++ b/code/__DEFINES/vv.dm @@ -84,6 +84,7 @@ #define VV_HK_REMOVECOMPONENT "removecomponent" #define VV_HK_MASS_REMOVECOMPONENT "massremovecomponent" #define VV_HK_MODIFY_TRAITS "modtraits" +#define VV_HK_DEBUG_APPEARANCE "debugappearance" // /atom #define VV_HK_MODIFY_TRANSFORM "atom_transform" diff --git a/code/_globalvars/layers.dm b/code/_globalvars/layers.dm new file mode 100644 index 00000000000..e90c38756d5 --- /dev/null +++ b/code/_globalvars/layers.dm @@ -0,0 +1,190 @@ +GLOBAL_LIST_INIT(admin_readable_planes, list( + "FLOAT_PLANE" = FLOAT_PLANE, + "FIELD_OF_VISION_BLOCKER_PLANE" = FIELD_OF_VISION_BLOCKER_PLANE, + "CLICKCATCHER_PLANE" = CLICKCATCHER_PLANE, + "PLANE_SPACE" = PLANE_SPACE, + "PLANE_SPACE_PARALLAX" = PLANE_SPACE_PARALLAX, + "DISPLACEMENT_PLANE" = DISPLACEMENT_PLANE, + "RENDER_PLANE_TRANSPARENT" = RENDER_PLANE_TRANSPARENT, + "TRANSPARENT_FLOOR_PLANE" = TRANSPARENT_FLOOR_PLANE, + "FLOOR_PLANE" = FLOOR_PLANE, + "WALL_PLANE" = WALL_PLANE, + "GAME_PLANE" = GAME_PLANE, + "ABOVE_GAME_PLANE" = ABOVE_GAME_PLANE, + "SEETHROUGH_PLANE" = SEETHROUGH_PLANE, + "RENDER_PLANE_GAME_WORLD" = RENDER_PLANE_GAME_WORLD, + "DEFAULT_PLANE" = DEFAULT_PLANE, + "WEATHER_PLANE" = WEATHER_PLANE, + "AREA_PLANE" = AREA_PLANE, + "MASSIVE_OBJ_PLANE" = MASSIVE_OBJ_PLANE, + "GHOST_PLANE" = GHOST_PLANE, + "POINT_PLANE" = POINT_PLANE, + "LIGHTING_PLANE" = LIGHTING_PLANE, + "O_LIGHTING_VISUAL_PLANE" = O_LIGHTING_VISUAL_PLANE, + "RENDER_PLANE_TURF_LIGHTING" = RENDER_PLANE_TURF_LIGHTING, + "EMISSIVE_PLANE" = EMISSIVE_PLANE, + "RENDER_PLANE_EMISSIVE" = RENDER_PLANE_EMISSIVE, + "RENDER_PLANE_EMISSIVE_BLOOM_MASK" = RENDER_PLANE_EMISSIVE_BLOOM_MASK, + "RENDER_PLANE_EMISSIVE_BLOOM" = RENDER_PLANE_EMISSIVE_BLOOM, + "RENDER_PLANE_SPECULAR_MASK" = RENDER_PLANE_SPECULAR_MASK, + "RENDER_PLANE_UNLIT_GAME" = RENDER_PLANE_UNLIT_GAME, + "RENDER_PLANE_LIGHTING" = RENDER_PLANE_LIGHTING, + "RENDER_PLANE_LIGHT_MASK" = RENDER_PLANE_LIGHT_MASK, + "RENDER_PLANE_SPECULAR" = RENDER_PLANE_SPECULAR, + "ABOVE_LIGHTING_PLANE" = ABOVE_LIGHTING_PLANE, + "WEATHER_GLOW_PLANE" = WEATHER_GLOW_PLANE, + "PIPECRAWL_IMAGES_PLANE" = PIPECRAWL_IMAGES_PLANE, + "CAMERA_STATIC_PLANE" = CAMERA_STATIC_PLANE, + "HIGH_GAME_PLANE" = HIGH_GAME_PLANE, + "FULLSCREEN_PLANE" = FULLSCREEN_PLANE, + "RUNECHAT_PLANE" = RUNECHAT_PLANE, + "BALLOON_CHAT_PLANE" = BALLOON_CHAT_PLANE, + "HUD_PLANE" = HUD_PLANE, + "ABOVE_HUD_PLANE" = ABOVE_HUD_PLANE, + "SPLASHSCREEN_PLANE" = SPLASHSCREEN_PLANE, + "RENDER_PLANE_GAME" = RENDER_PLANE_GAME, + "RENDER_PLANE_GAME_MASKED" = RENDER_PLANE_GAME_MASKED, + "RENDER_PLANE_GAME_UNMASKED" = RENDER_PLANE_GAME_UNMASKED, + "RENDER_PLANE_NON_GAME" = RENDER_PLANE_NON_GAME, + "ESCAPE_MENU_PLANE" = ESCAPE_MENU_PLANE, + "RENDER_PLANE_MASTER" = RENDER_PLANE_MASTER +)) + +GLOBAL_LIST_INIT(admin_readable_layers, list( + "FLOAT_LAYER" = FLOAT_LAYER, + "SPACE_LAYER" = SPACE_LAYER, + // Topdown layers + "TOPDOWN_LAYER" = TOPDOWN_LAYER, + "LOWER_FLOOR_LAYER (Topdown)" = LOWER_FLOOR_LAYER, + "LOW_FLOOR_LAYER (Topdown)" = LOW_FLOOR_LAYER, + "TURF_PLATING_DECAL_LAYER (Topdown)" = TURF_PLATING_DECAL_LAYER, + "TURF_DECAL_LAYER (Topdown)" = TURF_DECAL_LAYER, + "CULT_OVERLAY_LAYER (Topdown)" = CULT_OVERLAY_LAYER, + "MID_TURF_LAYER (Topdown)" = MID_TURF_LAYER, + "HIGH_TURF_LAYER (Topdown)" = HIGH_TURF_LAYER, + "LATTICE_LAYER (Topdown)" = LATTICE_LAYER, + "DISPOSAL_PIPE_LAYER (Topdown)" = DISPOSAL_PIPE_LAYER, + "WIRE_LAYER (Topdown)" = WIRE_LAYER, + "BELOW_CATWALK_LAYER (Topdown)" = BELOW_CATWALK_LAYER, + "GLASS_FLOOR_LAYER (Topdown)" = GLASS_FLOOR_LAYER, + "CATWALK_LAYER (Topdown)" = CATWALK_LAYER, + "TRAM_RAIL_LAYER (Topdown)" = TRAM_RAIL_LAYER, + "ABOVE_OPEN_TURF_LAYER (Topdown)" = ABOVE_OPEN_TURF_LAYER, + "LOWER_RUNE_LAYER (Topdown)" = LOWER_RUNE_LAYER, + "RUNE_LAYER (Topdown)" = RUNE_LAYER, + "CLEANABLE_FLOOR_OBJECT_LAYER (Topdown)" = CLEANABLE_FLOOR_OBJECT_LAYER, + "TOPDOWN_WATER_LEVEL_LAYER (Topdown)" = TOPDOWN_WATER_LEVEL_LAYER, + "TOPDOWN_ABOVE_WATER_LAYER (Topdown)" = TOPDOWN_ABOVE_WATER_LAYER, + // Game layers + "BELOW_CLOSED_TURF_LAYER" = BELOW_CLOSED_TURF_LAYER, + "CLOSED_TURF_LAYER" = CLOSED_TURF_LAYER, + "BULLET_HOLE_LAYER" = BULLET_HOLE_LAYER, + "ABOVE_NORMAL_TURF_LAYER" = ABOVE_NORMAL_TURF_LAYER, + "FLOOR_EMISSIVE_START_LAYER" = FLOOR_EMISSIVE_START_LAYER, + "FLOOR_EMISSIVE_END_LAYER" = FLOOR_EMISSIVE_END_LAYER, + "GAS_PIPE_HIDDEN_LAYER" = GAS_PIPE_HIDDEN_LAYER, + "WIRE_BRIDGE_LAYER" = WIRE_BRIDGE_LAYER, + "WIRE_TERMINAL_LAYER" = WIRE_TERMINAL_LAYER, + "GAS_SCRUBBER_LAYER" = GAS_SCRUBBER_LAYER, + "GAS_PIPE_VISIBLE_LAYER" = GAS_PIPE_VISIBLE_LAYER, + "GAS_FILTER_LAYER" = GAS_FILTER_LAYER, + "GAS_PUMP_LAYER" = GAS_PUMP_LAYER, + "PLUMBING_PIPE_VISIBILE_LAYER" = PLUMBING_PIPE_VISIBILE_LAYER, + "BOT_PATH_LAYER" = BOT_PATH_LAYER, + "LOW_OBJ_LAYER" = LOW_OBJ_LAYER, + "HIGH_PIPE_LAYER" = HIGH_PIPE_LAYER, + "CLEANABLE_OBJECT_LAYER" = CLEANABLE_OBJECT_LAYER, + "TRAM_STRUCTURE_LAYER" = TRAM_STRUCTURE_LAYER, + "TRAM_FLOOR_LAYER" = TRAM_FLOOR_LAYER, + "TRAM_WALL_LAYER" = TRAM_WALL_LAYER, + "BELOW_OPEN_DOOR_LAYER" = BELOW_OPEN_DOOR_LAYER, + "WATER_LEVEL_LAYER" = WATER_LEVEL_LAYER, + "BLASTDOOR_LAYER" = BLASTDOOR_LAYER, + "OPEN_DOOR_LAYER" = OPEN_DOOR_LAYER, + "DOOR_ACCESS_HELPER_LAYER" = DOOR_ACCESS_HELPER_LAYER, + "DOOR_HELPER_LAYER" = DOOR_HELPER_LAYER, + "PROJECTILE_HIT_THRESHHOLD_LAYER" = PROJECTILE_HIT_THRESHHOLD_LAYER, + "TABLE_LAYER" = TABLE_LAYER, + "GIB_LAYER" = GIB_LAYER, + "BELOW_OBJ_LAYER" = BELOW_OBJ_LAYER, + "LOW_ITEM_LAYER" = LOW_ITEM_LAYER, + "OBJ_LAYER" = OBJ_LAYER, + "CLOSED_DOOR_LAYER" = CLOSED_DOOR_LAYER, + "CLOSED_FIREDOOR_LAYER" = CLOSED_FIREDOOR_LAYER, + "ABOVE_OBJ_LAYER" = ABOVE_OBJ_LAYER, + "CLOSED_BLASTDOOR_LAYER" = CLOSED_BLASTDOOR_LAYER, + "SHUTTER_LAYER" = SHUTTER_LAYER, + "ABOVE_WINDOW_LAYER" = ABOVE_WINDOW_LAYER, + "SIGN_LAYER" = SIGN_LAYER, + "CORGI_ASS_PIN_LAYER" = CORGI_ASS_PIN_LAYER, + "NOT_HIGH_OBJ_LAYER" = NOT_HIGH_OBJ_LAYER, + "HIGH_OBJ_LAYER" = HIGH_OBJ_LAYER, + "BELOW_MOB_LAYER" = BELOW_MOB_LAYER, + "LOW_MOB_LAYER" = LOW_MOB_LAYER, + "LYING_MOB_LAYER" = LYING_MOB_LAYER, + "ABOVE_LYING_MOB_LAYER" = ABOVE_LYING_MOB_LAYER, + "VEHICLE_LAYER" = VEHICLE_LAYER, + "MOB_BELOW_PIGGYBACK_LAYER" = MOB_BELOW_PIGGYBACK_LAYER, + "MOB_LAYER" = MOB_LAYER, + "MOB_SHIELD_LAYER" = MOB_SHIELD_LAYER, + "MOB_ABOVE_PIGGYBACK_LAYER" = MOB_ABOVE_PIGGYBACK_LAYER, + "MOB_UPPER_LAYER" = MOB_UPPER_LAYER, + "HITSCAN_PROJECTILE_LAYER" = HITSCAN_PROJECTILE_LAYER, + "ABOVE_MOB_LAYER" = ABOVE_MOB_LAYER, + "WALL_OBJ_LAYER" = WALL_OBJ_LAYER, + "TRAM_SIGNAL_LAYER" = TRAM_SIGNAL_LAYER, + "EDGED_TURF_LAYER" = EDGED_TURF_LAYER, + "ON_EDGED_TURF_LAYER" = ON_EDGED_TURF_LAYER, + "SPACEVINE_LAYER" = SPACEVINE_LAYER, + "LARGE_MOB_LAYER" = LARGE_MOB_LAYER, + "SPACEVINE_MOB_LAYER" = SPACEVINE_MOB_LAYER, + "ABOVE_ALL_MOB_LAYER" = ABOVE_ALL_MOB_LAYER, + "NAVIGATION_EYE_LAYER" = NAVIGATION_EYE_LAYER, + "FLY_LAYER" = FLY_LAYER, + "ABOVE_TREE_LAYER" = ABOVE_TREE_LAYER, + "GASFIRE_LAYER" = GASFIRE_LAYER, + "RIPPLE_LAYER" = RIPPLE_LAYER, + // LIGHTING_PLANE layers + "LIGHTING_MASK_LAYER" = LIGHTING_MASK_LAYER, + "LIGHTING_PRIMARY_LAYER" = LIGHTING_PRIMARY_LAYER, + "LIGHTING_ABOVE_ALL" = LIGHTING_ABOVE_ALL, + "EMISSIVE_LAYER_UNBLOCKABLE" = EMISSIVE_LAYER_UNBLOCKABLE, + // Mob layers + "MUTATIONS_LAYER" = -MUTATIONS_LAYER, + "BODY_BEHIND_LAYER" = -BODY_BEHIND_LAYER, + "BODYPARTS_LOW_LAYER" = -BODYPARTS_LOW_LAYER, + "BODYPARTS_LAYER" = -BODYPARTS_LAYER, + "BODY_ADJ_LAYER" = -BODY_ADJ_LAYER, + "BODY_LAYER" = -BODY_LAYER, + "EYES_LAYER" = -EYES_LAYER, + "FRONT_MUTATIONS_LAYER" = -FRONT_MUTATIONS_LAYER, + "DAMAGE_LAYER" = -DAMAGE_LAYER, + "UNIFORM_LAYER" = -UNIFORM_LAYER, + "ID_LAYER" = -ID_LAYER, + "ID_CARD_LAYER" = -ID_CARD_LAYER, + "BODYPARTS_HIGH_LAYER" = -BODYPARTS_HIGH_LAYER, + "GLOVES_LAYER" = -GLOVES_LAYER, + "SHOES_LAYER" = -SHOES_LAYER, + "LOW_FACEMASK_LAYER" = -LOW_FACEMASK_LAYER, + "EARS_LAYER" = -EARS_LAYER, + "LOW_NECK_LAYER" = -LOW_NECK_LAYER, + "SUIT_LAYER" = -SUIT_LAYER, + "GLASSES_LAYER" = -GLASSES_LAYER, + "BELT_LAYER" = -BELT_LAYER, + "SUIT_STORE_LAYER" = -SUIT_STORE_LAYER, + "NECK_LAYER" = -NECK_LAYER, + "BACK_LAYER" = -BACK_LAYER, + "BENEATH_HAIR_LAYER" = -BENEATH_HAIR_LAYER, + "HAIR_LAYER" = -HAIR_LAYER, + "FACEMASK_LAYER" = -FACEMASK_LAYER, + "HEAD_LAYER" = -HEAD_LAYER, + "OUTER_HAIR_LAYER" = -OUTER_HAIR_LAYER, + "HANDCUFF_LAYER" = -HANDCUFF_LAYER, + "LEGCUFF_LAYER" = -LEGCUFF_LAYER, + "HANDS_LAYER" = -HANDS_LAYER, + "BODY_FRONT_LAYER" = -BODY_FRONT_LAYER, + "ABOVE_BODY_FRONT_GLASSES_LAYER" = -ABOVE_BODY_FRONT_GLASSES_LAYER, + "ABOVE_BODY_FRONT_HEAD_LAYER" = -ABOVE_BODY_FRONT_HEAD_LAYER, + "WOUND_LAYER" = -WOUND_LAYER, + "HALO_LAYER" = -HALO_LAYER, +)) diff --git a/code/datums/datumvars.dm b/code/datums/datumvars.dm index 55d8e28b197..3d7fff25706 100644 --- a/code/datums/datumvars.dm +++ b/code/datums/datumvars.dm @@ -60,6 +60,9 @@ GLOBAL_LIST_INIT(vv_var_blacklist, list( return FALSE if(href_list[VV_HK_MODIFY_TRAITS]) usr.client.holder.modify_traits(src) + if(href_list[VV_HK_DEBUG_APPEARANCE]) // On base datum as it is shared by atoms and mutable_appearance/image VVs + usr.client.holder.appearance_debug.set_target(src) + usr.client.holder.appearance_debug.ui_interact(usr) return TRUE /datum/proc/vv_get_header() diff --git a/code/game/atom/atom_vv.dm b/code/game/atom/atom_vv.dm index 85d7ed011c7..d5a4b5ccd9d 100644 --- a/code/game/atom/atom_vv.dm +++ b/code/game/atom/atom_vv.dm @@ -11,6 +11,7 @@ if(curturf) . += "Jump To" VV_DROPDOWN_OPTION(VV_HK_MODIFY_TRANSFORM, "Modify Transform") + VV_DROPDOWN_OPTION(VV_HK_DEBUG_APPEARANCE, "Debug Appearance") VV_DROPDOWN_OPTION(VV_HK_SPIN_ANIMATION, "SpinAnimation") VV_DROPDOWN_OPTION(VV_HK_STOP_ALL_ANIMATIONS, "Stop All Animations") VV_DROPDOWN_OPTION(VV_HK_SHOW_HIDDENPRINTS, "Show Hiddenprint log") diff --git a/code/modules/admin/holder2.dm b/code/modules/admin/holder2.dm index 18b96318f06..9db5efee0db 100644 --- a/code/modules/admin/holder2.dm +++ b/code/modules/admin/holder2.dm @@ -36,6 +36,7 @@ GLOBAL_PROTECT(href_token) var/datum/particle_editor/particle_test var/datum/colorblind_tester/color_test var/datum/plane_master_debug/plane_debug + var/datum/appearance_debugger/appearance_debug var/obj/machinery/computer/libraryconsole/admin_only_do_not_map_in_you_fucker/library_manager var/datum/pathfind_debug/path_debug var/datum/spawn_menu/spawn_menu @@ -98,6 +99,7 @@ GLOBAL_PROTECT(href_token) GLOB.admin_datums[target] = src deadmined = FALSE plane_debug = new(src) + appearance_debug = new(src) if (GLOB.directory[target]) associate(GLOB.directory[target]) //find the client for a ckey if they are connected and associate them with us @@ -109,6 +111,7 @@ GLOBAL_PROTECT(href_token) GLOB.deadmins[target] = src GLOB.admin_datums -= target QDEL_NULL(plane_debug) + QDEL_NULL(appearance_debug) QDEL_NULL(path_debug) deadmined = TRUE diff --git a/code/modules/admin/verbs/appearance_debugger/appearance_debugger.dm b/code/modules/admin/verbs/appearance_debugger/appearance_debugger.dm new file mode 100644 index 00000000000..32c29a9e540 --- /dev/null +++ b/code/modules/admin/verbs/appearance_debugger/appearance_debugger.dm @@ -0,0 +1,243 @@ +/// Allows developers to see a breakdown of an atom or a specific target and edit some of the values +/datum/appearance_debugger + var/datum/admins/owner + /// Currently debugged atom or mutable appearance, hence /datum + var/datum/debug_target + /// A list of copies of the currently debugged appearance and its children for access from the UI + var/list/mutable_appearance/appearance_copies + /// Assoc list of ref -> appearance as to prevent refreshing of dynamic appearances + var/list/mutable_appearance/appearance_cache + /// Mapview used to display the hovered over appearance + var/atom/movable/screen/map_view/proxy_view_hover + /// Mapview used to display the selected appearance + var/atom/movable/screen/map_view/proxy_view_selected + /// Assoc list of ref -> list of atoms to be displayed in said ref atom's vis_contents + var/list/atom/movable/fake_vis + /// Should we display a popup that the appearance in-game has updated? + var/update_warning = FALSE + +/datum/appearance_debugger/New(datum/admins/owner) + src.owner = owner + proxy_view_hover = new() + proxy_view_hover.generate_view("appearance_debugger_[REF(owner)]_proxy_hover") + proxy_view_selected = new() + proxy_view_selected.generate_view("appearance_debugger_[REF(owner)]_proxy_selected") + +/datum/appearance_debugger/Destroy() + if (owner) + owner.appearance_debug = null + owner = null + if (fake_vis) + for (var/ref_id in fake_vis) + var/list/all_fakes = fake_vis[ref_id] + QDEL_LIST(all_fakes) + fake_vis.Cut() + QDEL_NULL(proxy_view_hover) + QDEL_NULL(proxy_view_selected) + return ..() + +/datum/appearance_debugger/proc/get_appearance_data(atom/appearance_owner) + var/mutable_appearance/target = appearance_owner + if (isatom(appearance_owner)) + target = appearance_cache["[REF(appearance_owner)]"] || new /mutable_appearance(appearance_owner.appearance) + appearance_cache["[REF(appearance_owner)]"] = target + if (!fake_vis["[REF(appearance_owner)]"] && (ismovable(appearance_owner) || isturf(appearance_owner))) + var/atom/movable/as_movable = appearance_owner + var/list/false_vis_contents = list() + fake_vis["[REF(appearance_owner)]"] = false_vis_contents + for (var/atom/something in (isturf(as_movable) ? (as_movable.vis_contents + as_movable.contents) : as_movable.vis_contents)) + var/atom/movable/mimic = new() + mimic.appearance = new /mutable_appearance(something.appearance) + false_vis_contents += mimic + + var/list/data = list( + "type" = isatom(appearance_owner) ? "atom" : (isimage(appearance_owner) ? "image" : "appearance"), + "alpha" = target.alpha, + "flags" = target.appearance_flags, + "blend_mode" = target.blend_mode, + "color" = target.color, + "dir" = target.dir, + "icon" = length("[target.icon]") ? "[target.icon]" : null, + "icon_state" = target.icon_state, + "invisibility" = target.invisibility, + "layer" = target.layer, + "name" = target.name, + "maptext" = target.maptext, + "maptext_width" = target.maptext_width, + "maptext_height" = target.maptext_height, + "maptext_x" = target.maptext_x, + "maptext_y" = target.maptext_y, + "mouse_opacity" = target.mouse_opacity, + "pixel_x" = target.pixel_x, + "pixel_y" = target.pixel_y, + "pixel_w" = target.pixel_w, + "pixel_z" = target.pixel_z, + "plane" = target.plane, + "plane_true" = PLANE_TO_TRUE(target.plane), + "render_source" = target.render_source, + "render_target" = target.render_target, + "screen_loc" = target.screen_loc, + ) + + if (!(target in appearance_copies)) + appearance_copies += target + data["id"] = length(appearance_copies) + else + data["id"] = appearance_copies.Find(target) + + var/list/filter_data = list() + for (var/list/our_filter as anything in target.filter_data) + filter_data += list(our_filter) + data["filters"] = filter_data + + var/list/underlay_data = list() + for (var/mutable_appearance/underlay as anything in target.underlays) + underlay_data += list(get_appearance_data(underlay)) + data["underlays"] = underlay_data + + var/list/overlay_data = list() + for (var/mutable_appearance/overlay as anything in target.overlays) + overlay_data += list(get_appearance_data(overlay)) + data["overlays"] = overlay_data + + // Display previews if it is either an instance icon or a file and we have icon_state set + if ((isicon(target.icon) && !isfile(target.icon)) || (target.icon && target.icon_state)) + var/icon/used_icon = icon(target.icon, target.icon_state, (isimage(target) && target.dir) ? target.dir : SOUTH, frame = 1) + if (istext(target.color)) + used_icon.Blend(target.color, ICON_MULTIPLY) + data["embed_icon"] = icon2base64(used_icon) + + data["transform"] = list(target.transform.a, target.transform.b, target.transform.c, target.transform.d, target.transform.e, target.transform.f) + // Turfs aren't movables but they still can have vis_flags/contents so lets just count them as such + if (ismovable(appearance_owner) || isturf(appearance_owner)) + var/atom/movable/as_movable = appearance_owner + data["vis_flags"] = as_movable.vis_flags + // Maybe should be cached but I'm too lazy and don't think this'll matter enough + var/list/vis_data = list() + // Turfs also inherit their contents as vis_contents + for (var/atom/vis_thing as anything in isturf(as_movable) ? (as_movable.vis_contents + as_movable.contents) : as_movable.vis_contents) + vis_data += list(get_appearance_data(vis_thing)) + data["vis_contents"] = vis_data + + // Handle all dynamically modified layers + if (target.layer > FLOOR_EMISSIVE_START_LAYER && target.layer < FLOOR_EMISSIVE_END_LAYER) + data["layer_text_override"] = "FLOOR_EMISSIVE_LAYER (+[target.layer - FLOOR_EMISSIVE_START_LAYER])" + else if (target.layer > GAS_PIPE_HIDDEN_LAYER && target.layer < GAS_PIPE_HIDDEN_LAYER + 0.006) + data["layer_text_override"] = "GAS_PIPE_HIDDEN_LAYER (+[target.layer - GAS_PIPE_HIDDEN_LAYER])" + else if (target.layer > GAS_PIPE_VISIBLE_LAYER && target.layer < GAS_PIPE_VISIBLE_LAYER + 0.006) + data["layer_text_override"] = "GAS_PIPE_VISIBLE_LAYER (+[target.layer - GAS_PIPE_VISIBLE_LAYER])" + else if (target.layer > PLUMBING_PIPE_VISIBILE_LAYER && target.layer < PLUMBING_PIPE_VISIBILE_LAYER + (FIFTH_DUCT_LAYER * 2 / 3333)) + data["layer_text_override"] = "PLUMBING_PIPE_VISIBILE_LAYER (+[target.layer - PLUMBING_PIPE_VISIBILE_LAYER])" + + return data + +/datum/appearance_debugger/ui_data(mob/user) + return list( + "updateWarning" = update_warning, + ) + +/datum/appearance_debugger/ui_static_data(mob/user) + return list( + "mainAppearance" = get_appearance_data(debug_target), + "planeToText" = GLOB.admin_readable_planes, + "layerToText" = GLOB.admin_readable_layers, + "flagsToText" = get_valid_bitflags("appearance_flags"), + "visToText" = get_valid_bitflags("vis_flags"), + "blendToText" = GLOB.blend_names, + "mapRefHover" = proxy_view_hover.assigned_map, + "mapRefSelected" = proxy_view_selected.assigned_map, + ) + +/datum/appearance_debugger/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if(.) + return + switch(action) + if("swapMapViewHover") + var/appearance_id = text2num(params["id"]) + proxy_view_hover.appearance = appearance_copies[appearance_id] + + for (var/atom/movable/something as anything in proxy_view_hover.vis_contents) + proxy_view_hover.vis_contents -= something + for (var/ref_id in appearance_cache) + if (appearance_copies[appearance_id] != appearance_cache[ref_id]) + continue + if (fake_vis[ref_id]) + proxy_view_hover.vis_contents |= fake_vis[ref_id] + + // Needs screenloc to be set since we're setting the appearance and carrying over target's screenloc + proxy_view_hover.set_position(1, 1) + + if("swapMapViewSelected") + var/appearance_id = text2num(params["id"]) + proxy_view_selected.appearance = appearance_copies[appearance_id] + + for (var/atom/movable/something as anything in proxy_view_selected.vis_contents) + proxy_view_selected.vis_contents -= something + for (var/ref_id in appearance_cache) + if (appearance_copies[appearance_id] != appearance_cache[ref_id]) + continue + if (fake_vis[ref_id]) + proxy_view_selected.vis_contents |= fake_vis[ref_id] + + proxy_view_selected.set_position(1, 1) + + if("refreshAppearance") + update_warning = FALSE + appearance_copies = list() + appearance_cache = list() + for (var/ref_id in fake_vis) + var/list/all_fakes = fake_vis[ref_id] + QDEL_LIST(all_fakes) + fake_vis = list() + update_static_data_for_all_viewers() + + if("vvAppearance") + if (!check_rights(R_VAREDIT)) + return + var/appearance_id = text2num(params["id"]) + usr.client.debug_variables(appearance_copies[appearance_id]) + +/datum/appearance_debugger/ui_assets(mob/user) + return list(get_asset_datum(/datum/asset/simple/plane_background)) + +/datum/appearance_debugger/ui_state(mob/user) + return ADMIN_STATE(R_DEBUG) + +/datum/appearance_debugger/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "AppearanceDebug") + ui.open() + proxy_view_hover.display_to(user, ui.window) + proxy_view_selected.display_to(user, ui.window) + +/datum/appearance_debugger/ui_close(mob/user) + . = ..() + // Reset appearances when the UI is closed + proxy_view_hover.appearance = new /mutable_appearance() + proxy_view_selected.appearance = new /mutable_appearance() + proxy_view_hover.vis_contents.Cut() + proxy_view_selected.vis_contents.Cut() + if (isatom(debug_target)) + UnregisterSignal(debug_target, COMSIG_ATOM_UPDATE_APPEARANCE) + +/datum/appearance_debugger/proc/set_target(mutable_appearance/new_target) + if (isatom(debug_target)) + UnregisterSignal(debug_target, COMSIG_ATOM_UPDATE_APPEARANCE) + update_warning = FALSE + // Can be an atom! + debug_target = new_target + if (isatom(debug_target)) + RegisterSignal(debug_target, COMSIG_ATOM_UPDATE_APPEARANCE, PROC_REF(warn_update)) + appearance_copies = list() + appearance_cache = list() + for (var/ref_id in fake_vis) + var/list/all_fakes = fake_vis[ref_id] + QDEL_LIST(all_fakes) + fake_vis = list() + update_static_data_for_all_viewers() + +/datum/appearance_debugger/proc/warn_update() + SIGNAL_HANDLER + update_warning = TRUE diff --git a/code/modules/admin/view_variables/debug_variable_appearance.dm b/code/modules/admin/view_variables/debug_variable_appearance.dm index d92efcb7b2a..9f801066998 100644 --- a/code/modules/admin/view_variables/debug_variable_appearance.dm +++ b/code/modules/admin/view_variables/debug_variable_appearance.dm @@ -64,6 +64,10 @@ var/value = vars[var_name] return "
  • (READ ONLY) [var_name] = [_debug_variable_value(var_name, value, 0, src, sanitize = TRUE, display_flags = NONE)]
  • " +/mutable_appearance/vv_get_dropdown() + . = ..() + VV_DROPDOWN_OPTION(VV_HK_DEBUG_APPEARANCE, "Debug Appearance") + /mutable_appearance/appearance_mirror/vv_get_dropdown() SHOULD_CALL_PARENT(FALSE) @@ -74,6 +78,7 @@ VV_DROPDOWN_OPTION(VV_HK_TAG, "Tag Datum") VV_DROPDOWN_OPTION(VV_HK_DELETE, "Delete") VV_DROPDOWN_OPTION(VV_HK_EXPOSE, "Show VV To Player") + VV_DROPDOWN_OPTION(VV_HK_DEBUG_APPEARANCE, "Debug Appearance") /proc/get_vv_appearance(mutable_appearance/appearance) // actually appearance yadeeyada return new /mutable_appearance/appearance_mirror(appearance) diff --git a/tgstation.dme b/tgstation.dme index 496de2a3b74..9d6ca244378 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -554,6 +554,7 @@ #include "code\_globalvars\configuration.dm" #include "code\_globalvars\game_modes.dm" #include "code\_globalvars\genetics.dm" +#include "code\_globalvars\layers.dm" #include "code\_globalvars\lighting.dm" #include "code\_globalvars\logging.dm" #include "code\_globalvars\phobias.dm" @@ -3277,6 +3278,7 @@ #include "code\modules\admin\verbs\spawnobjasmob.dm" #include "code\modules\admin\verbs\special_verbs.dm" #include "code\modules\admin\verbs\sprite_auditor.dm" +#include "code\modules\admin\verbs\appearance_debugger\appearance_debugger.dm" #include "code\modules\admin\verbs\lua\_wrappers.dm" #include "code\modules\admin\verbs\lua\helpers.dm" #include "code\modules\admin\verbs\lua\lua_editor.dm" diff --git a/tgui/packages/tgui/interfaces/AppearanceDebug/AppearanceBox.tsx b/tgui/packages/tgui/interfaces/AppearanceDebug/AppearanceBox.tsx new file mode 100644 index 00000000000..3264c9df34e --- /dev/null +++ b/tgui/packages/tgui/interfaces/AppearanceDebug/AppearanceBox.tsx @@ -0,0 +1,134 @@ +import { Box, Button, Image, Stack, Tooltip } from 'tgui-core/components'; +import { classes } from 'tgui-core/react'; +import type { Coordinates } from '../common/Connections'; +import { + getReadableLayer, + getReadablePlane, + isEmissive, + isEmissiveBlocker, +} from '.'; +import { + APPEARANCE_FLAGS, + type Appearance, + AppearanceType, + HiddenState, +} from './types'; +import { useAppearanceDebugContext } from './useAppearanceDebug'; + +export type AppearanceProps = { + appearance: Appearance; + position: Coordinates; + onClick: React.MouseEventHandler; +}; + +export function AppearanceBox(props: AppearanceProps) { + const { appearance, position, onClick } = props; + const { planeToText, layerToText, act } = useAppearanceDebugContext(); + + return ( + <> + {!!( + appearance.data.flags & + (APPEARANCE_FLAGS.KEEP_APART | APPEARANCE_FLAGS.KEEP_TOGETHER) + ) && ( + + act('swapMapViewHover', { id: appearance.data.id }) + } + style={{ + zIndex: -(999 - appearance.depth), + border: `3px solid ${appearance.data.flags & APPEARANCE_FLAGS.KEEP_APART ? (appearance.data.flags & APPEARANCE_FLAGS.KEEP_TOGETHER ? '#2a7dc6' : '#107e2e') : '#e9cb0c'}`, + borderRadius: '5px', + padding: '5px', + backgroundColor: `${appearance.data.flags & APPEARANCE_FLAGS.KEEP_APART ? (appearance.data.flags & APPEARANCE_FLAGS.KEEP_TOGETHER ? '#223c54' : '#13381c') : '#544b15'}`, + }} + > + {!!(appearance.data.flags & APPEARANCE_FLAGS.KEEP_APART) && + 'KEEP_APART'} + {!!( + appearance.data.flags & APPEARANCE_FLAGS.KEEP_APART && + appearance.data.flags & APPEARANCE_FLAGS.KEEP_TOGETHER + ) && ' | '} + {!!(appearance.data.flags & APPEARANCE_FLAGS.KEEP_TOGETHER) && + 'KEEP_TOGETHER'} + + )} + act('swapMapViewHover', { id: appearance.data.id })} + onClick={onClick} + style={{ zIndex: 1 }} + opacity={appearance.hidden === HiddenState.VisibleChild ? 0.7 : 1} + > + + + + {appearance.data.name || appearance.data.icon_state} + {isEmissive(appearance) + ? ' (Emissive)' + : isEmissiveBlocker(appearance) + ? ' (Emissive Blocker)' + : ''} + + +