Пример #1
0
        public override void OnApplicationStart()
        {
            /* Register settings */
            Settings.RegisterConfig();

            /* Load audio settings */
            WorldAudio.LoadConfig();

            /* Load avatar parameters */
            Parameters.LoadConfig();

            /* Load our custom UI elements */
            UiExpansion.LoadUiObjects();

            /* TODO: Consider switching to operator+ when everyone had to update the assembly unhollower */
            /*       The current solution might be prefereable so we are always first */
            VRCAvatarManager.field_Private_Static_Action_3_Player_GameObject_VRC_AvatarDescriptor_0 += (Il2CppSystem.Action <Player, GameObject, VRC.SDKBase.VRC_AvatarDescriptor>)OnAvatarInstantiate;

            /* Register async, awaiting network manager */
            MelonCoroutines.Start(RegisterJoinLeaveNotifier());

            ExpansionKitApi.GetExpandedMenu(ExpandedMenu.QuickMenu).AddSimpleButton("Player List", PlayerList);
            ExpansionKitApi.GetExpandedMenu(ExpandedMenu.QuickMenu).AddSimpleButton("WorldCleanup", MainMenu);
            ExpansionKitApi.GetExpandedMenu(ExpandedMenu.UserQuickMenu).AddSimpleButton("Avatar Toggles", OnUserQuickMenu);

            /* Hook into setter for parameter properties */
            unsafe
            {
                var param_prop_bool_set = (IntPtr)typeof(AvatarParameter).GetField("NativeMethodInfoPtr_Method_Public_Virtual_Final_New_set_Void_Boolean_0", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null);
                MelonUtils.NativeHookAttach(param_prop_bool_set, new Action <IntPtr, bool>(Parameters.BoolPropertySetter).Method.MethodHandle.GetFunctionPointer());
                Parameters._boolPropertySetterDelegate = Marshal.GetDelegateForFunctionPointer <Parameters.BoolPropertySetterDelegate>(*(IntPtr *)(void *)param_prop_bool_set);

                var param_prop_int_set = (IntPtr)typeof(AvatarParameter).GetField("NativeMethodInfoPtr_Method_Public_Virtual_Final_New_set_Void_Int32_0", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null);
                MelonUtils.NativeHookAttach(param_prop_int_set, new Action <IntPtr, int>(Parameters.IntPropertySetter).Method.MethodHandle.GetFunctionPointer());
                Parameters._intPropertySetterDelegate = Marshal.GetDelegateForFunctionPointer <Parameters.IntPropertySetterDelegate>(*(IntPtr *)(void *)param_prop_int_set);

                var param_prop_float_set = (IntPtr)typeof(AvatarParameter).GetField("NativeMethodInfoPtr_Method_Public_Virtual_Final_New_set_Void_Single_0", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null);
                MelonUtils.NativeHookAttach(param_prop_float_set, new Action <IntPtr, float>(Parameters.FloatPropertySetter).Method.MethodHandle.GetFunctionPointer());
                Parameters._floatPropertySetterDelegate = Marshal.GetDelegateForFunctionPointer <Parameters.FloatPropertySetterDelegate>(*(IntPtr *)(void *)param_prop_float_set);
            }

            AMUtils.AddToModsFolder("Player Toggles", () =>
            {
                /* Filter inactive avatar objects */
                s_PlayerList = s_PlayerList.Where(o => o.Value).ToDictionary(o => o.Key, o => o.Value);

                /* Order by physical distance to camera */
                var query = from player in s_PlayerList
                            orderby Vector3.Distance(player.Value.transform.position, Camera.main.transform.position)
                            select player;

                /* Only allow a max of 10 players there at once */
                /* TODO: Consider adding multiple pages */
                var remaining_count = 10;

                foreach (var entry in query)
                {
                    var manager = entry.Value.GetComponentInParent <VRCAvatarManager>();

                    /* Ignore SDK2 & avatars w/o custom expressions */
                    if (!manager.HasCustomExpressions())
                    {
                        continue;
                    }

                    var avatar_id = entry.Value.GetComponent <VRC.Core.PipelineManager>().blueprintId;
                    var user_icon = s_Portraits[avatar_id].Get();

                    /* Source default expression icon */
                    var menu_icons         = ActionMenuDriver.prop_ActionMenuDriver_0.field_Public_MenuIcons_0;
                    var default_expression = menu_icons.defaultExpression;

                    CustomSubMenu.AddSubMenu(entry.Key, () =>
                    {
                        if (entry.Value == null || !entry.Value.active)
                        {
                            return;
                        }

                        var parameters        = manager.GetAllAvatarParameters();
                        var filtered          = Parameters.FilterDefaultParameters(parameters);
                        var avatar_descriptor = manager.prop_VRCAvatarDescriptor_0;

                        CustomSubMenu.AddToggle("Lock", filtered.Any(Parameters.IsLocked), (state) => { filtered.ForEach(state ? Parameters.Lock : Parameters.Unlock); }, icon: UiExpansion.LockClosedIcon);
                        CustomSubMenu.AddButton("Save", () => Parameters.StoreParameters(manager), icon: UiExpansion.SaveIcon);

                        AvatarParameter FindParameter(string name)
                        {
                            foreach (var parameter in parameters)
                            {
                                if (parameter.field_Private_String_0 == name)
                                {
                                    return(parameter);
                                }
                            }
                            return(null);
                        }

                        void ExpressionSubmenu(VRCExpressionsMenu expressions_menu)
                        {
                            if (entry.Value == null || !entry.Value.active)
                            {
                                return;
                            }

                            void FourAxisControl(VRCExpressionsMenu.Control control, Action <Vector2> callback)
                            {
                                CustomSubMenu.AddFourAxisPuppet(
                                    control.TruncatedName(),
                                    callback,
                                    icon: control.icon ?? default_expression,
                                    topButtonText: control.labels[0]?.TruncatedName() ?? "Up",
                                    rightButtonText: control.labels[1]?.TruncatedName() ?? "Right",
                                    downButtonText: control.labels[2]?.TruncatedName() ?? "Down",
                                    leftButtonText: control.labels[3]?.TruncatedName() ?? "Left");
                            }

                            foreach (var control in expressions_menu.controls)
                            {
                                try
                                {
                                    switch (control.type)
                                    {
                                    case VRCExpressionsMenu.Control.ControlType.Button:
                                    /* Note: Action Menu "Buttons" are actually Toggles */
                                    /*       that set on press and revert on release.   */
                                    /* TODO: Add proper implementation.                 */
                                    case VRCExpressionsMenu.Control.ControlType.Toggle:
                                        {
                                            var param         = FindParameter(control.parameter.name);
                                            var current_value = param.GetValue();
                                            var default_value = avatar_descriptor.expressionParameters.FindParameter(control.parameter.name)?.defaultValue ?? 0f;
                                            var target_value  = control.value;
                                            void SetIntFloat(bool state) => param.SetValue(state ? target_value : default_value);
                                            void SetBool(bool state) => param.SetValue(state ? 1f : 0f);

                                            CustomSubMenu.AddToggle(
                                                control.TruncatedName(),
                                                current_value == target_value,
                                                param.prop_ParameterType_0 == AvatarParameter.ParameterType.Bool ? SetBool : SetIntFloat,
                                                icon: control.icon ?? default_expression);
                                            break;
                                        }

                                    case VRCExpressionsMenu.Control.ControlType.SubMenu:
                                        {
                                            CustomSubMenu.AddSubMenu(control.TruncatedName(), () => ExpressionSubmenu(control.subMenu), icon: control.icon ?? default_expression);
                                            break;
                                        }

                                    case VRCExpressionsMenu.Control.ControlType.TwoAxisPuppet:
                                        {
                                            var horizontal = FindParameter(control.subParameters[0]?.name);
                                            var vertical   = FindParameter(control.subParameters[1]?.name);
                                            FourAxisControl(control, (value) =>
                                            {
                                                horizontal.SetFloatProperty(value.x);
                                                vertical.SetFloatProperty(value.y);
                                            });
                                            break;
                                        }

                                    case VRCExpressionsMenu.Control.ControlType.FourAxisPuppet:
                                        {
                                            var up    = FindParameter(control.subParameters[0]?.name);
                                            var down  = FindParameter(control.subParameters[1]?.name);
                                            var left  = FindParameter(control.subParameters[2]?.name);
                                            var right = FindParameter(control.subParameters[3]?.name);
                                            FourAxisControl(control, (value) =>
                                            {
                                                up.SetFloatProperty(Math.Max(0, value.y));
                                                down.SetFloatProperty(-Math.Min(0, value.y));
                                                left.SetFloatProperty(Math.Max(0, value.x));
                                                right.SetFloatProperty(-Math.Min(0, value.x));
                                            });
                                            break;
                                        }

                                    case VRCExpressionsMenu.Control.ControlType.RadialPuppet:
                                        {
                                            var param = FindParameter(control.subParameters[0]?.name);
                                            CustomSubMenu.AddRestrictedRadialPuppet(control.TruncatedName(), param.SetValue, startingValue: param.GetValue(), icon: control.icon ?? default_expression);
                                            break;
                                        }
                                    }
                                }
                                catch (Exception e)
                                {
                                    MelonLogger.Error(e.StackTrace);
                                }
                            }
                        }

                        ExpressionSubmenu(avatar_descriptor.expressionsMenu);
                    }, icon: user_icon);

                    if (--remaining_count == 0)
                    {
                        break;
                    }
                }
            });

            MelonLogger.Msg(ConsoleColor.Green, "WorldCleanup ready!");
        }