Esempio n. 1
0
        /// <summary>
        /// Create the mod menu subsection including the section header in the given menu.
        /// The default implementation uses reflection to attempt creating a menu.
        /// </summary>
        /// <param name="menu">Menu to add the section to.</param>
        /// <param name="inGame">Whether we're in-game (paused) or in the main menu.</param>
        /// <param name="snapshot">The Level.PauseSnapshot</param>
        public virtual void CreateModMenuSection(TextMenu menu, bool inGame, EventInstance snapshot)
        {
            Type type = SettingsType;
            EverestModuleSettings settings = _Settings;

            if (type == null || settings == null)
            {
                return;
            }

            // The default name prefix.
            string typeName = type.Name.ToLowerInvariant();

            if (typeName.EndsWith("settings"))
            {
                typeName = typeName.Substring(0, typeName.Length - 8);
            }
            string nameDefaultPrefix = $"modoptions_{typeName}_";

            // Any attributes we may want to get and read from later.
            SettingInGameAttribute      attribInGame;
            SettingRangeAttribute       attribRange;
            SettingNumberInputAttribute attribNumber;

            // If the settings type has got the InGame attrib, only show it in the matching situation.
            if ((attribInGame = type.GetCustomAttribute <SettingInGameAttribute>()) != null &&
                attribInGame.InGame != inGame)
            {
                return;
            }

            bool headerCreated = false;

            if (GetType().GetMethod("CreateModMenuSection").DeclaringType != typeof(EverestModule))
            {
                CreateModMenuSectionHeader(menu, inGame, snapshot);
                headerCreated = true;
            }

            PropertyInfo[] props;
            if (type == _PrevSettingsType)
            {
                props = _PrevSettingsProps;
            }
            else
            {
                _PrevSettingsProps = props = type.GetProperties();
                _PrevSettingsType  = type;
            }

            foreach (PropertyInfo prop in props)
            {
                MethodInfo creator = type.GetMethod(
                    $"Create{prop.Name}Entry",
                    BindingFlags.Public | BindingFlags.Instance,
                    null,
                    new Type[] { typeof(TextMenu), typeof(bool) },
                    new ParameterModifier[0]
                    );

                if (creator != null)
                {
                    if (!headerCreated)
                    {
                        CreateModMenuSectionHeader(menu, inGame, snapshot);
                        headerCreated = true;
                    }

                    creator.GetFastDelegate()(settings, menu, inGame);
                    continue;
                }

                if ((attribInGame = prop.GetCustomAttribute <SettingInGameAttribute>()) != null &&
                    attribInGame.InGame != inGame)
                {
                    continue;
                }

                if (prop.GetCustomAttribute <SettingIgnoreAttribute>() != null)
                {
                    continue;
                }

                if (!prop.CanRead || !prop.CanWrite)
                {
                    continue;
                }

                string name = prop.GetCustomAttribute <SettingNameAttribute>()?.Name ?? $"{nameDefaultPrefix}{prop.Name.ToLowerInvariant()}";
                name = name.DialogCleanOrNull() ?? prop.Name.SpacedPascalCase();

                bool needsRelaunch = prop.GetCustomAttribute <SettingNeedsRelaunchAttribute>() != null;

                string description = prop.GetCustomAttribute <SettingSubTextAttribute>()?.Description;

                TextMenu.Item item     = null;
                Type          propType = prop.PropertyType;
                object        value    = prop.GetValue(settings);

                // Create the matching item based off of the type and attributes.

                if (propType == typeof(bool))
                {
                    item =
                        new TextMenu.OnOff(name, (bool)value)
                        .Change(v => prop.SetValue(settings, v))
                    ;
                }
                else if (
                    propType == typeof(int) &&
                    (attribRange = prop.GetCustomAttribute <SettingRangeAttribute>()) != null
                    )
                {
                    if (attribRange.LargeRange)
                    {
                        item =
                            new TextMenuExt.IntSlider(name, attribRange.Min, attribRange.Max, (int)value)
                            .Change(v => prop.SetValue(settings, v))
                        ;
                    }
                    else
                    {
                        item =
                            new TextMenu.Slider(name, i => i.ToString(), attribRange.Min, attribRange.Max, (int)value)
                            .Change(v => prop.SetValue(settings, v))
                        ;
                    }
                }
                else if ((propType == typeof(int) || propType == typeof(float)) &&
                         (attribNumber = prop.GetCustomAttribute <SettingNumberInputAttribute>()) != null)
                {
                    float          currentValue;
                    Action <float> valueSetter;
                    if (propType == typeof(int))
                    {
                        currentValue = (int)value;
                        valueSetter  = v => prop.SetValue(settings, (int)v);
                    }
                    else
                    {
                        currentValue = (float)value;
                        valueSetter  = v => prop.SetValue(settings, v);
                    }
                    int  maxLength      = attribNumber.MaxLength;
                    bool allowNegatives = attribNumber.AllowNegatives;

                    item =
                        new TextMenu.Button(name + ": " + currentValue.ToString($"F{maxLength}").TrimEnd('0').TrimEnd('.'))
                        .Pressed(() => {
                        Audio.Play(SFX.ui_main_savefile_rename_start);
                        menu.SceneAs <Overworld>().Goto <OuiNumberEntry>().Init <OuiModOptions>(
                            currentValue,
                            valueSetter,
                            maxLength,
                            propType == typeof(float),
                            allowNegatives
                            );
                    })
                    ;
                }
                else if (propType.IsEnum)
                {
                    Array enumValues = Enum.GetValues(propType);
                    Array.Sort((int[])enumValues);
                    string enumNamePrefix = $"{nameDefaultPrefix}{prop.Name.ToLowerInvariant()}_";
                    item =
                        new TextMenu.Slider(name, (i) => {
                        string enumName = enumValues.GetValue(i).ToString();
                        return
                        ($"{enumNamePrefix}{enumName.ToLowerInvariant()}".DialogCleanOrNull() ??
                         $"modoptions_{propType.Name.ToLowerInvariant()}_{enumName.ToLowerInvariant()}".DialogCleanOrNull() ??
                         enumName);
                    }, 0, enumValues.Length - 1, (int)value)
                        .Change(v => prop.SetValue(settings, v))
                    ;
                }
                else if (!inGame && propType == typeof(string))
                {
                    int maxValueLength = prop.GetCustomAttribute <SettingMaxLengthAttribute>()?.Max ?? 12;
                    int minValueLength = prop.GetCustomAttribute <SettingMinLengthAttribute>()?.Min ?? 1;

                    item =
                        new TextMenu.Button(name + ": " + value)
                        .Pressed(() => {
                        Audio.Play(SFX.ui_main_savefile_rename_start);
                        menu.SceneAs <Overworld>().Goto <OuiModOptionString>().Init <OuiModOptions>(
                            (string)value,
                            v => prop.SetValue(settings, v),
                            maxValueLength,
                            minValueLength
                            );
                    })
                    ;
                }

                if (item == null)
                {
                    continue;
                }

                if (!headerCreated)
                {
                    CreateModMenuSectionHeader(menu, inGame, snapshot);
                    headerCreated = true;
                }

                menu.Add(item);

                if (needsRelaunch)
                {
                    item = item.NeedsRelaunch(menu);
                }

                if (description != null)
                {
                    item = item.AddDescription(menu, description.DialogCleanOrNull() ?? description);
                }
            }

            foreach (PropertyInfo prop in type.GetProperties())
            {
                if ((attribInGame = prop.GetCustomAttribute <SettingInGameAttribute>()) != null &&
                    attribInGame.InGame != inGame)
                {
                    continue;
                }

                if (prop.GetCustomAttribute <SettingIgnoreAttribute>() != null)
                {
                    continue;
                }

                if (!prop.CanRead || !prop.CanWrite)
                {
                    continue;
                }

                if (!typeof(ButtonBinding).IsAssignableFrom(prop.PropertyType))
                {
                    continue;
                }

                if (!headerCreated)
                {
                    CreateModMenuSectionHeader(menu, inGame, snapshot);
                    headerCreated = true;
                }

                CreateModMenuSectionKeyBindings(menu, inGame, snapshot);
                break;
            }
        }
        private void ReloadMenu()
        {
            menu = new DisablableTextMenu {
                new TextMenu.Header(Dialog.Clean("MODOPTIONS_RANDOMIZER_HEADER"))
            };

            var hashtext = new TextMenuExt.EaseInSubHeaderExt("{hash}", true, menu)
            {
                HeightExtra = -10f,
                Offset      = new Vector2(30, -5),
            };

            void updateHashText()
            {
                hashtext.Title = "v" + RandoModule.Instance.VersionString;
                if (Settings.SeedType == SeedType.Custom)
                {
                    hashtext.Title += " #" + Settings.Hash.ToString();
                }
            }

            updateHashText();

            var errortext = new TextMenuExt.EaseInSubHeaderExt("{error}", false, menu)
            {
                HeightExtra = -10f,
                Offset      = new Vector2(30, -5),
            };

            var seedbutton = new TextMenu.Button(Dialog.Clean("MODOPTIONS_RANDOMIZER_SEED") + ": " + Settings.Seed);

            seedbutton.Pressed(() => {
                Audio.Play(SFX.ui_main_savefile_rename_start);
                menu.SceneAs <Overworld>().Goto <UI.OuiTextEntry>().Init <OuiRandoSettings>(
                    Settings.Seed,
                    (v) => Settings.Seed = v,
                    RandoModule.MAX_SEED_CHARS
                    );
            });
            seedbutton.Visible = Settings.SeedType == SeedType.Custom;

            var endlesslivespicker = new TextMenuExt.IntSlider(Dialog.Clean("MODOPTIONS_RANDOMIZER_LIVES"), 0, 50, Settings.EndlessLives);

            endlesslivespicker.OnValueChange = i => {
                Settings.EndlessLives = i;
            };
            endlesslivespicker.Visible = Settings.Algorithm == LogicType.Endless;

            var seedtypetoggle = new TextMenu.Slider(Dialog.Clean("MODOPTIONS_RANDOMIZER_SEEDTYPE"), (i) => {
                return(Dialog.Clean("MODOPTIONS_RANDOMIZER_SEEDTYPE_" + Enum.GetNames(typeof(SeedType))[i].ToUpperInvariant()));
            }, 0, (int)SeedType.Last - 1, (int)Settings.SeedType).Change((i) => {
                Settings.SeedType  = (SeedType)i;
                seedbutton.Visible = Settings.SeedType == SeedType.Custom;
                // just in case...
                seedbutton.Label = Dialog.Clean("MODOPTIONS_RANDOMIZER_SEED") + ": " + Settings.Seed;
                updateHashText();
            });

            var mapbutton = new TextMenu.Button(Dialog.Clean("MODOPTIONS_RANDOMIZER_MAPPICKER")).Pressed(() => {
                Audio.Play(SFX.ui_main_button_select);
                menu.SceneAs <Overworld>().Goto <OuiMapPicker>();
            });

            var mapcountlbl = new TextMenuExt.SubHeaderExt(Settings.LevelCount.ToString() + " " + Dialog.Clean("MODOPTIONS_RANDOMIZER_MAPPICKER_LEVELS"))
            {
                HeightExtra = -10f,
                Offset      = new Vector2(30, -5),
            };

            var logictoggle = new TextMenu.Slider(Dialog.Clean("MODOPTIONS_RANDOMIZER_LOGIC"), (i) => {
                return(Dialog.Clean("MODOPTIONS_RANDOMIZER_LOGIC_" + Enum.GetNames(typeof(LogicType))[i].ToUpperInvariant()));
            }, 0, (int)LogicType.Last - 1, (int)Settings.Algorithm).Change((i) => {
                Settings.Algorithm         = (LogicType)i;
                endlesslivespicker.Visible = Settings.Algorithm == LogicType.Endless;
                updateHashText();
            });

            var lengthtoggle = new TextMenu.Slider(Dialog.Clean("MODOPTIONS_RANDOMIZER_LENGTH"), (i) => {
                return(Dialog.Clean("MODOPTIONS_RANDOMIZER_LENGTH_" + Enum.GetNames(typeof(MapLength))[i].ToUpperInvariant()));
            }, 0, (int)MapLength.Last - 1, (int)Settings.Length).Change((i) => {
                Settings.Length = (MapLength)i;
                updateHashText();
            });

            var numdashestoggle = new TextMenu.Slider(Dialog.Clean("MODOPTIONS_RANDOMIZER_NUMDASHES"), (i) => {
                return(Dialog.Clean("MODOPTIONS_RANDOMIZER_NUMDASHES_" + Enum.GetNames(typeof(NumDashes))[i].ToUpperInvariant()));
            }, 0, (int)NumDashes.Last - 1, (int)Settings.Dashes).Change((i) => {
                Settings.Dashes = (NumDashes)i;
                updateHashText();
            });

            var difficultytext = new TextMenuExt.EaseInSubHeaderExt(Dialog.Clean("MODOPTIONS_RANDOMIZER_DIFFICULTY_EXPLAIN_" + Settings.Difficulty.ToString().ToUpperInvariant()), false, menu)
            {
                HeightExtra = 17f,
                Offset      = new Vector2(30, -5),
            };

            var difficultytoggle = new TextMenu.Slider(Dialog.Clean("MODOPTIONS_RANDOMIZER_DIFFICULTY"), (i) => {
                return(Dialog.Clean("MODOPTIONS_RANDOMIZER_DIFFICULTY_" + Enum.GetNames(typeof(Difficulty))[i].ToUpperInvariant()));
            }, 0, (int)Difficulty.Last - 1, (int)Settings.Difficulty).Change((i) => {
                Settings.Difficulty = (Difficulty)i;
                updateHashText();
                difficultytext.Title = Dialog.Clean("MODOPTIONS_RANDOMIZER_DIFFICULTY_EXPLAIN_" + Settings.Difficulty.ToString().ToUpperInvariant());
            });

            difficultytoggle.OnEnter += () => { difficultytext.FadeVisible = true; };
            difficultytoggle.OnLeave += () => { difficultytext.FadeVisible = false; };

            var repeatroomstoggle = new TextMenu.OnOff(Dialog.Clean("MODOPTIONS_RANDOMIZER_REPEATROOMS"), Settings.RepeatRooms).Change((val) => {
                Settings.RepeatRooms = val;
                updateHashText();
            });

            var enterunknowntext = new TextMenuExt.EaseInSubHeaderExt(Dialog.Clean("MODOPTIONS_RANDOMIZER_ENTERUNKNOWN_EXPLAIN"), false, menu)
            {
                HeightExtra = 17f,
                Offset      = new Vector2(30, -5),
            };

            var enterunknowntoggle = new TextMenu.OnOff(Dialog.Clean("MODOPTIONS_RANDOMIZER_ENTERUNKNOWN"), Settings.EnterUnknown).Change((val) => {
                Settings.EnterUnknown = val;
                updateHashText();
            });

            enterunknowntoggle.OnEnter += () => { enterunknowntext.FadeVisible = true; };
            enterunknowntoggle.OnLeave += () => { enterunknowntext.FadeVisible = false; };

            var shinetoggle = new TextMenu.Slider(Dialog.Clean("MODOPTIONS_RANDOMIZER_SHINE"), (i) => {
                return(Dialog.Clean("MODOPTIONS_RANDOMIZER_SHINE_" + Enum.GetNames(typeof(ShineLights))[i].ToUpperInvariant()));
            }, 0, (int)ShineLights.Last - 1, (int)Settings.Lights).Change((i) => {
                Settings.Lights = (ShineLights)i;
                updateHashText();
            });

            var darktoggle = new TextMenu.Slider(Dialog.Clean("MODOPTIONS_RANDOMIZER_DARK"), (i) => {
                return(Dialog.Clean("MODOPTIONS_RANDOMIZER_DARK_" + Enum.GetNames(typeof(Darkness))[i].ToUpperInvariant()));
            }, 0, (int)Darkness.Last - 1, (int)Settings.Darkness).Change((i) => {
                Settings.Darkness = (Darkness)i;
                updateHashText();
            });

            var goldentoggle = new TextMenu.OnOff(Dialog.Clean("MODOPTIONS_RANDOMIZER_GOLDENBERRY"), Settings.SpawnGolden).Change((val) => {
                Settings.SpawnGolden = val;
            });

            var variantstoggle = new TextMenu.OnOff(Dialog.Clean("MODOPTIONS_RANDOMIZER_VARIANTS"), Settings.Variants).Change((val) => {
                Settings.Variants = val;
            });

            var decorationstoggle = new TextMenu.OnOff(Dialog.Clean("MODOPTIONS_RANDOMIZER_DECORATIONS"), Settings.RandomDecorations).Change((val) => {
                Settings.RandomDecorations = val;
            });

            var moreoptions = false;

            repeatroomstoggle.Visible  = false;
            enterunknowntoggle.Visible = false;
            goldentoggle.Visible       = false;
            shinetoggle.Visible        = false;
            darktoggle.Visible         = false;
            variantstoggle.Visible     = false;
            decorationstoggle.Visible  = false;

            var moreoptionsbtn = new TextMenu.Button(Dialog.Clean("MODOPTIONS_RANDOMIZER_MOREOPTIONS"));

            moreoptionsbtn.Pressed(() => {
                moreoptions          = !moreoptions;
                moreoptionsbtn.Label = moreoptions ? Dialog.Clean("MODOPTIONS_RANDOMIZER_FEWEROPTIONS") : Dialog.Clean("MODOPTIONS_RANDOMIZER_MOREOPTIONS");

                repeatroomstoggle.Visible  = moreoptions;
                enterunknowntoggle.Visible = moreoptions;
                goldentoggle.Visible       = moreoptions;
                shinetoggle.Visible        = moreoptions;
                darktoggle.Visible         = moreoptions;
                variantstoggle.Visible     = moreoptions;
                decorationstoggle.Visible  = moreoptions;
            });

            void syncModel()
            {
                repeatroomstoggle.Index    = Settings.RepeatRooms ? 1 : 0;
                enterunknowntoggle.Index   = Settings.EnterUnknown ? 1 : 0;
                variantstoggle.Index       = Settings.Variants ? 1 : 0;
                logictoggle.Index          = (int)Settings.Algorithm;
                lengthtoggle.Index         = (int)Settings.Length;
                numdashestoggle.Index      = (int)Settings.Dashes;
                difficultytoggle.Index     = (int)Settings.Difficulty;
                shinetoggle.Index          = (int)Settings.Lights;
                darktoggle.Index           = (int)Settings.Darkness;
                mapcountlbl.Title          = Settings.LevelCount.ToString() + " " + Dialog.Clean("MODOPTIONS_RANDOMIZER_MAPPICKER_LEVELS");
                endlesslivespicker.Index   = Settings.EndlessLives;
                endlesslivespicker.Visible = Settings.Algorithm == LogicType.Endless;

                var locked = Settings.Rules != Ruleset.Custom;

                mapbutton.Disabled          = locked;
                repeatroomstoggle.Disabled  = locked;
                enterunknowntoggle.Disabled = locked;
                logictoggle.Disabled        = locked;
                lengthtoggle.Disabled       = locked;
                numdashestoggle.Disabled    = locked;
                difficultytoggle.Disabled   = locked;
                shinetoggle.Disabled        = locked;
                darktoggle.Disabled         = locked;
                variantstoggle.Disabled     = locked;
                endlesslivespicker.Disabled = locked;
            }

            syncModel();

            var rulestoggle = new TextMenu.Slider(Dialog.Clean("MODOPTIONS_RANDOMIZER_RULES"), (i) => {
                return(Dialog.Clean("MODOPTIONS_RANDOMIZER_RULES_" + Enum.GetNames(typeof(Ruleset))[i].ToUpperInvariant()));
            }, 0, (int)Ruleset.Last - 1, (int)Settings.Rules).Change((i) => {
                Settings.Rules = (Ruleset)i;
                Settings.Enforce();
                syncModel();
                updateHashText();
            });

            var startbutton = new TextMenu.Button(Dialog.Clean("MODOPTIONS_RANDOMIZER_START"));

            startbutton.Pressed(() => {
                if (this.entering)
                {
                    return;
                }

                void reenableMenu()
                {
                    this.builderThread = null;

                    startbutton.Label = Dialog.Clean("MODOPTIONS_RANDOMIZER_START");
                    updateHashText();
                    menu.DisableMovement = false;
                }

                if (this.builderThread == null)
                {
                    errortext.FadeVisible = false;
                    startbutton.Label     = Dialog.Clean("MODOPTIONS_RANDOMIZER_CANCEL");
                    hashtext.Title       += " " + Dialog.Clean("MODOPTIONS_RANDOMIZER_GENERATING");
                    menu.DisableMovement  = true;

                    this.builderThread = new Thread(() => {
                        Settings.Enforce();
                        AreaKey newArea;
                        try {
                            newArea = RandoLogic.GenerateMap(Settings);
                        } catch (ThreadAbortException) {
                            return;
                        } catch (GenerationError e) {
                            errortext.Title       = e.Message;
                            errortext.FadeVisible = true;
                            reenableMenu();
                            return;
                        } catch (Exception e) {
                            errortext.Title = "Encountered an error - Check log.txt for details";
                            Logger.LogDetailed(e, "randomizer");
                            errortext.FadeVisible = true;
                            reenableMenu();
                            return;
                        }
                        // save settings
                        RandoModule.Instance.SavedData.SavedSettings = Settings.Copy();
                        RandoModule.Instance.SaveSettings();

                        this.entering       = true;
                        RandoModule.StartMe = newArea;
                        while (RandoModule.StartMe != null)
                        {
                            Thread.Sleep(10);
                        }
                        this.builderThread = null;
                        this.entering      = false;
                    });
                    this.builderThread.Start();
                }
                else
                {
                    this.builderThread.Abort();
                    reenableMenu();
                }
            });

            menu.Add(seedtypetoggle);
            menu.Add(seedbutton);
            menu.Add(rulestoggle);
            menu.Add(mapbutton);
            menu.Add(mapcountlbl);
            menu.Add(logictoggle);
            menu.Add(endlesslivespicker);
            menu.Add(lengthtoggle);
            menu.Add(numdashestoggle);
            menu.Add(difficultytoggle);
            menu.Add(difficultytext);
            menu.Add(moreoptionsbtn);
            menu.Add(repeatroomstoggle);
            menu.Add(enterunknowntoggle);
            menu.Add(enterunknowntext);
            menu.Add(shinetoggle);
            menu.Add(darktoggle);
            menu.Add(goldentoggle);
            menu.Add(variantstoggle);
            menu.Add(decorationstoggle);
            menu.Add(startbutton);
            menu.Add(hashtext);
            menu.Add(errortext);

            Scene.Add(menu);
        }