using System.Collections.Generic; using System.Text.RegularExpressions; using gay.lilyy.Common; using gay.lilyy.MenuStyling; using nadena.dev.ndmf; using nadena.dev.ndmf.vrchat; using UnityEngine; using VRC.SDK3.Avatars.ScriptableObjects; using static VRC.SDK3.Avatars.ScriptableObjects.VRCExpressionsMenu; [assembly: ExportsPlugin(typeof(MenuStylingPlugin))] namespace gay.lilyy.MenuStyling { public class MenuStylingPlugin : Plugin { public override string DisplayName => "MenuStyling"; public override string QualifiedName => "gay.lilyy.MenuStyling"; protected override void Configure() { InPhase(BuildPhase.Optimizing).AfterPlugin("gay.lilyy.MenuIconRemover").Run("SetMenuStyling", ctx => { var obj = ComponentHelper.GetComponentInChildrenWithError(ctx.AvatarRootObject); if (obj != null) { var descriptor = ctx.VRChatAvatarDescriptor(); var processedMenus = new HashSet(); descriptor.expressionsMenu = DuplicateMenu(descriptor.expressionsMenu, processedMenus); var walkProcessedMenus = new HashSet(); WalkMenu(obj, descriptor.expressionsMenu, walkProcessedMenus); Object.DestroyImmediate(obj); } }); } private VRCExpressionsMenu DuplicateMenu(VRCExpressionsMenu menu, HashSet processedMenus) { if (menu == null) return null; // Prevent circular references - if we've already processed this menu, return null to break the cycle if (processedMenus.Contains(menu)) { return null; } processedMenus.Add(menu); var newMenu = ScriptableObject.CreateInstance(); newMenu.controls = new List(); foreach (var control in menu.controls) { var newControl = new Control { name = control.name, type = control.type, icon = control.icon, parameter = control.parameter, subMenu = DuplicateMenu(control.subMenu, processedMenus), value = control.value, style = control.style, labels = control.labels, subParameters = control.subParameters }; newMenu.controls.Add(newControl); } return newMenu; } private string StripTextMeshProTags(string text) { if (string.IsNullOrEmpty(text)) return text; // Remove all TextMeshPro/rich text tags // Pattern matches: , , , , text = Regex.Replace(text, @"<[^>]+>", ""); return text; } private void WalkMenu(MenuStylingConfig config, VRCExpressionsMenu menu, HashSet processedMenus) { if (menu == null) return; // Prevent circular references if (processedMenus.Contains(menu)) { return; } processedMenus.Add(menu); foreach (var child in menu.controls) { if (child.name != " " && child.name != "" && child.name != null) { // Strip all TextMeshPro styling tags first if enabled if (config.RemovePreexistingStyling) { child.name = StripTextMeshProTags(child.name); } if (!child.name.StartsWith(config.Prefix)) { child.name = config.Prefix + child.name; } if (!child.name.EndsWith(config.Suffix)) { child.name += config.Suffix; } } if (child.type == Control.ControlType.SubMenu && child.subMenu != null) { WalkMenu(config, child.subMenu, processedMenus); } } } } }