/// <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); }