private static void onCreatePauseMenuButtons(Level level, TextMenu menu, bool minimal) { if (CollabModule.Instance.Session.LobbySID != null) { // find the position just under "Return to Map". int returnToMapIndex = menu.GetItems().FindIndex(item => item.GetType() == typeof(TextMenu.Button) && ((TextMenu.Button)item).Label == Dialog.Clean("MENU_PAUSE_RETURN")); if (returnToMapIndex == -1) { // fall back to the bottom of the menu. returnToMapIndex = menu.GetItems().Count - 1; } // add the "Return to Lobby" button TextMenu.Button returnToLobbyButton = new TextMenu.Button(Dialog.Clean("collabutils2_returntolobby")); returnToLobbyButton.Pressed(() => { level.PauseMainMenuOpen = false; menu.RemoveSelf(); openReturnToLobbyConfirmMenu(level, menu.Selection); }); returnToLobbyButton.ConfirmSfx = "event:/ui/main/message_confirm"; menu.Insert(returnToMapIndex + 1, returnToLobbyButton); } }
public override void CreateModMenuSection(TextMenu menu, bool inGame, EventInstance snapshot) { base.CreateModMenuSection(menu, inGame, snapshot); // this could maybe be refactored into a helper function with some generics magic foreach (var item in menu.Items) { if (!(item is TextMenu.Button btn) || !btn.Label.StartsWith(Dialog.Clean("modoptions_bingoclient_playername"))) { continue; } var messageHeader = new TextMenuExt.EaseInSubHeaderExt(Dialog.Clean("modoptions_bingoclient_playername_about"), false, menu) { HeightExtra = 17f, Offset = new Vector2(30, -5), }; menu.Insert(menu.Items.IndexOf(item) + 1, messageHeader); btn.OnEnter = () => messageHeader.FadeVisible = true; btn.OnLeave = () => messageHeader.FadeVisible = false; break; } foreach (var item in menu.Items) { if (!(item is TextMenu.OnOff btn) || !btn.Label.StartsWith(Dialog.Clean("modoptions_bingoclient_claimassist"))) { continue; } var messageHeader = new TextMenuExt.EaseInSubHeaderExt(Dialog.Clean("modoptions_bingoclient_claimassist_about"), false, menu) { HeightExtra = 17f, Offset = new Vector2(30, -5), }; menu.Insert(menu.Items.IndexOf(item) + 1, messageHeader); btn.OnEnter = () => messageHeader.FadeVisible = true; btn.OnLeave = () => messageHeader.FadeVisible = false; break; } if (this.Password != null && !this.Connected) { var retryBtn = new TextMenu.Button(Dialog.Clean("modoptions_bingoclient_reconnect")); retryBtn.OnPressed = () => { this.Connect(); }; menu.Add(retryBtn); } if (this.Connected) { var disconnectBtn = new TextMenu.Button(Dialog.Clean("modoptions_bingoclient_disconnect")); disconnectBtn.OnPressed = () => { this.Disconnect(); }; menu.Add(disconnectBtn); } }
public override IEnumerator Enter(Oui from) { Everest.Loader.AutoLoadNewMods = false; menu = new TextMenu(); // display the title and a dummy "Fetching" button menu.Add(new TextMenu.Header(Dialog.Clean("MODUPDATECHECKER_MENU_TITLE"))); menu.Add(subHeader = new TextMenuExt.SubHeaderExt(Dialog.Clean("MODUPDATECHECKER_MENU_HEADER"))); fetchingButton = new TextMenu.Button(Dialog.Clean("MODUPDATECHECKER_FETCHING")); fetchingButton.Disabled = true; menu.Add(fetchingButton); Scene.Add(menu); currentCheckForUpdates = new CheckForUpdates(); task = new Task(() => currentCheckForUpdates.Fetch()); task.Start(); menu.Visible = Visible = true; menu.Focused = false; for (float p = 0f; p < 1f; p += Engine.DeltaTime * 4f) { menu.X = offScreenX + -1920f * Ease.CubeOut(p); alpha = Ease.CubeOut(p); yield return(null); } menu.Focused = true; menuOnScreen = true; }
// heavily copy-pasted from Everest.Updater! private static void downloadMod(ModUpdateInfo update, TextMenu.Button button, string zipPath) { Logger.Log("UpdateChecker", $"Downloading {update.URL} to {zipPath}"); DateTime timeStart = DateTime.Now; if (File.Exists(zipPath)) { File.Delete(zipPath); } // Manual buffered copy from web input to file output. // Allows us to measure speed and progress. using (WebClient wc = new WebClient()) using (Stream input = wc.OpenRead(update.URL)) using (FileStream output = File.OpenWrite(zipPath)) { long length; if (input.CanSeek) { length = input.Length; } else { length = _ContentLength(update.URL); } byte[] buffer = new byte[4096]; DateTime timeLastSpeed = timeStart; int read = 1; int readForSpeed = 0; int pos = 0; int speed = 0; int count = 0; TimeSpan td; while (read > 0) { count = length > 0 ? (int)Math.Min(buffer.Length, length - pos) : buffer.Length; read = input.Read(buffer, 0, count); output.Write(buffer, 0, read); pos += read; readForSpeed += read; td = DateTime.Now - timeLastSpeed; if (td.TotalMilliseconds > 100) { speed = (int)((readForSpeed / 1024D) / td.TotalSeconds); readForSpeed = 0; timeLastSpeed = DateTime.Now; } if (length > 0) { button.Label = $"{update.Name} ({((int)Math.Floor(100D * (pos / (double)length)))}% @ {speed} KiB/s)"; } else { button.Label = $"{update.Name} ({((int)Math.Floor(pos / 1000D))}KiB @ {speed} KiB/s)"; } } } }
/// <summary> /// Downloads a mod update. /// </summary> /// <param name="update">The update info coming from the update server</param> /// <param name="button">The button for that mod shown on the interface</param> /// <param name="zipPath">The path to the zip the update will be downloaded to</param> private static void downloadMod(ModUpdateInfo update, TextMenu.Button button, string zipPath) { Logger.Log("OuiModUpdateList", $"Downloading {update.URL} to {zipPath}"); Func <int, long, int, bool> progressCallback = (position, length, speed) => { if (ongoingUpdateCancelled) { return(false); } if (length > 0) { button.Label = $"{ModUpdaterHelper.FormatModName(update.Name)} ({((int) Math.Floor(100D * (position / (double) length)))}% @ {speed} KiB/s)"; } else { button.Label = $"{ModUpdaterHelper.FormatModName(update.Name)} ({((int) Math.Floor(position / 1000D))}KiB @ {speed} KiB/s)"; } return(true); }; try { Everest.Updater.DownloadFileWithProgress(update.URL, zipPath, progressCallback); } catch (WebException e) { Logger.Log(LogLevel.Warn, "OuiModUpdateList", $"Download failed, trying mirror {update.MirrorURL}"); Logger.LogDetailed(e); Everest.Updater.DownloadFileWithProgress(update.MirrorURL, zipPath, progressCallback); } }
public void CreatePauseMenuButtons(Level level, TextMenu menu, bool minimal) { if (Everest.Flags.IsDisabled || !Settings.ShowModOptionsInGame) { return; } List <TextMenu.Item> items = menu.GetItems(); int index; // Find the options button and place our button below it. string cleanedOptions = Dialog.Clean("menu_pause_options"); index = items.FindIndex(_ => { TextMenu.Button other = (_ as TextMenu.Button); if (other == null) { return(false); } return(other.Label == cleanedOptions); }); if (index != -1) { index++; } // Otherwise, place it below the last button. else { index = items.Count; } TextMenu.Item itemModOptions = null; menu.Insert(index, itemModOptions = new TextMenu.Button(Dialog.Clean("menu_pause_modoptions")).Pressed(() => { int returnIndex = menu.IndexOf(itemModOptions); menu.RemoveSelf(); level.PauseMainMenuOpen = false; level.Paused = true; TextMenu options = OuiModOptions.CreateMenu(true, LevelExt.PauseSnapshot); options.OnESC = options.OnCancel = () => { Audio.Play(Sfxs.ui_main_button_back); options.CloseAndRun(Everest.SaveSettings(), () => level.Pause(returnIndex, minimal, false)); }; options.OnPause = () => { Audio.Play(Sfxs.ui_main_button_back); options.CloseAndRun(Everest.SaveSettings(), () => { level.Paused = false; Engine.FreezeTimer = 0.15f; }); }; level.Add(options); })); }
private void focusOn(TextMenu.Button button) { int index = menu.GetItems().IndexOf(button); if (index != -1) { menu.Selection = index; } }
public void CreateOpenBackupFolderEntry(TextMenu textMenu, bool inGame) { TextMenu.Item item = new TextMenu.Button(DialogId.Options.OpenBackupFolder.DialogClean()) .Pressed(() => { try { string backupPath = Modules.InfiniteBackups.BackupPath; if (!Directory.Exists(backupPath)) { Directory.CreateDirectory(backupPath); } Process.Start(backupPath); } catch (Exception err) { LogUtil.Log("Open backup folder failed!", LogLevel.Warn); err.LogDetailed(); TextMenu.Item openBackupFolder = menuItems[DialogId.Options.OpenBackupFolder]; TextMenu.Item openFailed = menuItems[DialogId.Subtext.OpenBackupFolderFailed]; if (!textMenu.Items.Contains(openFailed)) { textMenu.Insert(textMenu.IndexOf(openBackupFolder) + 1, openFailed); } } }); textMenu.Add(item); menuItems.Add(DialogId.Options.OpenBackupFolder, item); // split the string into multiple lines to prevent off-screen menus caused by long path string[] descriptionLines = string.Format(DialogId.Subtext.BackupLocation.DialogGet(), Path.GetFullPath(Modules.InfiniteBackups.BackupPath)).SplitIntoFixedLength(50); TextMenuExt.EaseInSubHeaderExt backupLocationSubtext = new TextMenuExt.EaseInSubHeaderExt(string.Join("\n", descriptionLines), false, textMenu) { TextColor = Color.Gray }; // increase the height and make it vertical align backupLocationSubtext.HeightExtra = (backupLocationSubtext.Title.Split('\n').Length - 1) * ActiveFont.LineHeight * 0.6f; backupLocationSubtext.Offset = new Vector2(0f, -Math.Max(0f, -16f + backupLocationSubtext.HeightExtra) + textMenu.ItemSpacing); textMenu.Add(backupLocationSubtext); TextMenuExt.EaseInSubHeaderExt openFailedSubtext = new TextMenuExt.EaseInSubHeaderExt(DialogId.Subtext.OpenBackupFolderFailed.DialogClean(), false, textMenu) { TextColor = Color.OrangeRed, HeightExtra = 0f }; item.OnEnter += delegate { openFailedSubtext.FadeVisible = true; backupLocationSubtext.FadeVisible = true; }; item.OnLeave += delegate { openFailedSubtext.FadeVisible = false; backupLocationSubtext.FadeVisible = false; }; menuItems.Add(DialogId.Subtext.BackupLocation, backupLocationSubtext); menuItems.Add(DialogId.Subtext.OpenBackupFolderFailed, openFailedSubtext); }
public static TextMenu.Item CreateTriggerHitboxColorButton(TextMenu textMenu, bool inGame) { TextMenu.Item item = new TextMenu.Button("Trigger Hitbox Color".ToDialogText() + $": {ColorToHex(Settings.TriggerHitboxColor)}").Pressed( () => { Audio.Play("event:/ui/main/savefile_rename_start"); textMenu.SceneAs <Overworld>().Goto <OuiModOptionString>() .Init <OuiModOptions>(ColorToHex(Settings.TriggerHitboxColor), value => Settings.TriggerHitboxColor = HexToColor(value, Settings.TriggerHitboxColor), 9); }); item.Disabled = inGame; return(item); }
/// <summary> /// Downloads a mod update. /// </summary> /// <param name="update">The update info coming from the update server</param> /// <param name="button">The button for that mod shown on the interface</param> /// <param name="zipPath">The path to the zip the update will be downloaded to</param> private static void downloadMod(ModUpdateInfo update, TextMenu.Button button, string zipPath) { Logger.Log("OuiModUpdateList", $"Downloading {update.URL} to {zipPath}"); Everest.Updater.DownloadFileWithProgress(update.URL, zipPath, (position, length, speed) => { if (length > 0) { button.Label = $"{update.Name.SpacedPascalCase()} ({((int)Math.Floor(100D * (position / (double)length)))}% @ {speed} KiB/s)"; } else { button.Label = $"{update.Name.SpacedPascalCase()} ({((int)Math.Floor(position / 1000D))}KiB @ {speed} KiB/s)"; } }); }
public void CreateServerEntry(TextMenu menu, bool inGame) { menu.Add( (ServerEntry = new TextMenu.Button(("modoptions_ghostnetmodule_server".DialogCleanOrNull() ?? "Server") + ": " + Server)) .Pressed(() => { Audio.Play("event:/ui/main/savefile_rename_start"); menu.SceneAs <Overworld>().Goto <OuiModOptionString>().Init <OuiModOptions>( Server, v => Server = v, maxValueLength: 30 ); }) ); ServerEntry.Disabled = inGame || Connection; }
public void CreatePresetMenu(TextMenu menu) { if (Hyperline.Instance.presetManager.presets.Count != 0) { List <KeyValuePair <uint, string> > enumerableList = new List <KeyValuePair <uint, string> >(); for (uint i = 0; i < Hyperline.Instance.presetManager.presets.Count; i++) { enumerableList.Add(new KeyValuePair <uint, string>(i, Hyperline.Instance.presetManager.presets[(int)i].Key)); } TextMenuExt.EnumerableSlider <uint> slider = new TextMenuExt.EnumerableSlider <uint>("Preset:", enumerableList, 0); slider.Change(v => { currentPreset = (int)v; }); menu.Add(slider); TextMenu.Button applyButton = new TextMenu.Button("Apply Preset"); applyButton.Pressed(CopyPreset); menu.Add(applyButton); } }
private static void onCreatePauseMenuButtons(Level level, TextMenu menu, bool minimal) { // create the Restart Speed Berry option at the bottom of the menu SpeedBerry berry; if ((berry = level.Tracker.GetEntity <SpeedBerry>()) != null && berry.Follower.HasLeader && !minimal) { TextMenu.Button item = new TextMenu.Button(Dialog.Clean("collabutils2_restartspeedberry")) { OnPressed = () => { level.Paused = false; level.PauseMainMenuOpen = false; menu.RemoveSelf(); berry.TimeRanOut = true; } }; menu.Add(item); } }
public override IEnumerator Enter(Oui from) { Everest.Loader.AutoLoadNewMods = false; menu = new TextMenu(); // display the title and a dummy "Fetching" button menu.Add(new TextMenu.Header(Dialog.Clean("MODUPDATECHECKER_MENU_TITLE"))); menu.Add(subHeader = new TextMenuExt.SubHeaderExt(Dialog.Clean("MODUPDATECHECKER_MENU_HEADER"))); fetchingButton = new TextMenu.Button(Dialog.Clean("MODUPDATECHECKER_FETCHING")); fetchingButton.Disabled = true; menu.Add(fetchingButton); Scene.Add(menu); menu.Visible = Visible = true; menu.Focused = false; for (float p = 0f; p < 1f; p += Engine.DeltaTime * 4f) { menu.X = offScreenX + -1920f * Ease.CubeOut(p); alpha = Ease.CubeOut(p); yield return(null); } menu.Focused = true; menuOnScreen = true; task = new Task(() => { // 1. Download the mod updates database updateCatalog = ModUpdaterHelper.DownloadModUpdateList(); // 2. Find out what actually has been updated if (updateCatalog != null) { availableUpdatesCatalog = ModUpdaterHelper.ListAvailableUpdates(updateCatalog); } }); task.Start(); }
/// <summary> /// Downloads a mod update. /// </summary> /// <param name="update">The update info coming from the update server</param> /// <param name="button">The button for that mod shown on the interface</param> /// <param name="zipPath">The path to the zip the update will be downloaded to</param> private static void downloadMod(ModUpdateInfo update, TextMenu.Button button, string zipPath) { Logger.Log("OuiModUpdateList", $"Downloading {update.URL} to {zipPath}"); Everest.Updater.DownloadFileWithProgress(update.URL, zipPath, (position, length, speed) => { if (ongoingUpdateCancelled) { return(false); } if (length > 0) { button.Label = $"{ModUpdaterHelper.FormatModName(update.Name)} ({((int) Math.Floor(100D * (position / (double) length)))}% @ {speed} KiB/s)"; } else { button.Label = $"{ModUpdaterHelper.FormatModName(update.Name)} ({((int) Math.Floor(position / 1000D))}KiB @ {speed} KiB/s)"; } return(true); }); }
public void CreatePlayerNameEntry(TextMenu menu, bool inGame) { if (inGame) { return; } var item = new TextMenu.Button(Dialog.Clean("MODOPTIONS_BINGOCLIENT_PLAYERNAME") + ": " + this.PlayerName) .Pressed(() => { Audio.Play(SFX.ui_main_savefile_rename_start); menu.SceneAs <Overworld>().Goto <OuiModOptionString>().Init <OuiModOptions>( (string)this.PlayerName, v => { this.PlayerName = v; BingoClient.Instance.Username = v; BingoClient.Instance.NameChanged = true; }, 20, 0 ); }); menu.Add(item); }
protected override void addOptionsToMenu(TextMenu menu) { // for now, display a "loading" message. TextMenu.Button loading = new TextMenu.Button(Dialog.Clean("MODOPTIONS_MODTOGGLE_LOADING")) { Disabled = true }; menu.Add(loading); modLoadingTask = new Task(() => { // load all the mod yamls (that can take some time), update the progress every 500ms so that the text doesn't go crazy since it is centered. Stopwatch updateTimer = Stopwatch.StartNew(); modYamls = LoadAllModYamls(progress => { if (updateTimer.ElapsedMilliseconds > 500) { updateTimer.Restart(); loading.Label = $"{Dialog.Clean("MODOPTIONS_MODTOGGLE_LOADING")} ({(int) (progress * 100)}%)"; } }); updateTimer.Stop(); MainThreadHelper.Do(() => { modToggles = new Dictionary <string, TextMenu.OnOff>(); // remove the "loading..." message menu.Remove(loading); // if there is a whitelist, warn the user that it will break those settings. if (Everest.Loader.Whitelist != null) { menu.Add(restartMessage1 = new TextMenuExt.SubHeaderExt(Dialog.Clean("MODOPTIONS_MODTOGGLE_WHITELISTWARN")) { TextColor = Color.OrangeRed }); } // display the warning about blacklist.txt + restarting menu.Add(restartMessage1 = new TextMenuExt.SubHeaderExt(Dialog.Clean("MODOPTIONS_MODTOGGLE_MESSAGE_1"))); menu.Add(restartMessage2 = new TextMenuExt.SubHeaderExt(Dialog.Clean("MODOPTIONS_MODTOGGLE_MESSAGE_2")) { HeightExtra = 0f }); menu.Add(new TextMenuExt.SubHeaderExt(Dialog.Clean("MODOPTIONS_MODTOGGLE_MESSAGE_3")) { HeightExtra = 20f, TextColor = Color.Goldenrod }); // reduce spacing between the whitelist warning and the blacklist overwrite warning if (Everest.Loader.Whitelist != null) { restartMessage1.HeightExtra = 30f; } // "enable all" and "disable all" buttons menu.Add(new TextMenu.Button(Dialog.Clean("MODOPTIONS_MODTOGGLE_ENABLEALL")).Pressed(() => { foreach (TextMenu.OnOff toggle in modToggles.Values) { toggle.Index = 1; } blacklistedMods.Clear(); updateHighlightedMods(); })); menu.Add(new TextMenu.Button(Dialog.Clean("MODOPTIONS_MODTOGGLE_DISABLEALL")).Pressed(() => { blacklistedMods.Clear(); foreach (KeyValuePair <string, TextMenu.OnOff> toggle in modToggles) { toggle.Value.Index = 0; blacklistedMods.Add(toggle.Key); } updateHighlightedMods(); })); // "toggle dependencies automatically" button TextMenu.Item toggleDependenciesButton; menu.Add(toggleDependenciesButton = new TextMenu.OnOff(Dialog.Clean("MODOPTIONS_MODTOGGLE_TOGGLEDEPS"), true) .Change(value => toggleDependencies = value)); toggleDependenciesButton.AddDescription(menu, Dialog.Clean("MODOPTIONS_MODTOGGLE_TOGGLEDEPS_MESSAGE2")); toggleDependenciesButton.AddDescription(menu, Dialog.Clean("MODOPTIONS_MODTOGGLE_TOGGLEDEPS_MESSAGE1")); // "cancel" button to leave the screen without saving menu.Add(new TextMenu.Button(Dialog.Clean("MODOPTIONS_MODTOGGLE_CANCEL")).Pressed(() => { blacklistedMods = blacklistedModsOriginal; onBackPressed(Overworld); })); // reset the mods list allMods = new List <string>(); blacklistedMods = new HashSet <string>(); string[] files; bool headerInserted; // crawl directories files = Directory.GetDirectories(Everest.Loader.PathMods); Array.Sort(files, (a, b) => a.ToLowerInvariant().CompareTo(b.ToLowerInvariant())); headerInserted = false; for (int i = 0; i < files.Length; i++) { string file = Path.GetFileName(files[i]); if (file != "Cache") { if (!headerInserted) { menu.Add(new patch_TextMenu.patch_SubHeader(Dialog.Clean("MODOPTIONS_MODTOGGLE_DIRECTORIES"))); headerInserted = true; } addFileToMenu(menu, file); } } // crawl zips files = Directory.GetFiles(Everest.Loader.PathMods); Array.Sort(files, (a, b) => a.ToLowerInvariant().CompareTo(b.ToLowerInvariant())); headerInserted = false; for (int i = 0; i < files.Length; i++) { string file = Path.GetFileName(files[i]); if (file.EndsWith(".zip")) { if (!headerInserted) { menu.Add(new patch_TextMenu.patch_SubHeader(Dialog.Clean("MODOPTIONS_MODTOGGLE_ZIPS"))); headerInserted = true; } addFileToMenu(menu, file); } } // crawl map bins files = Directory.GetFiles(Everest.Loader.PathMods); Array.Sort(files, (a, b) => a.ToLowerInvariant().CompareTo(b.ToLowerInvariant())); headerInserted = false; for (int i = 0; i < files.Length; i++) { string file = Path.GetFileName(files[i]); if (file.EndsWith(".bin")) { if (!headerInserted) { menu.Add(new patch_TextMenu.patch_SubHeader(Dialog.Clean("MODOPTIONS_MODTOGGLE_BINS"))); headerInserted = true; } addFileToMenu(menu, file); } } // sort the mods list alphabetically, for output in the blacklist.txt file later. allMods.Sort((a, b) => a.ToLowerInvariant().CompareTo(b.ToLowerInvariant())); // adjust the mods' color if they are required dependencies for other mods foreach (KeyValuePair <string, TextMenu.OnOff> toggle in modToggles) { if (modHasDependencies(toggle.Key)) { ((patch_TextMenu.patch_Option <bool>)(object) toggle.Value).UnselectedColor = Color.Goldenrod; } } // snap the menu so that it doesn't show a scroll up. menu.Y = menu.ScrollTargetY; // clone the list to be able to check if the list changed when leaving the menu. blacklistedModsOriginal = new HashSet <string>(blacklistedMods); // loading is done! modLoadingTask = null; }); }); modLoadingTask.Start(); }
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.Metadata.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 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; 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 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(); }); 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 moreoptions = false; repeatroomstoggle.Visible = false; enterunknowntoggle.Visible = false; goldentoggle.Visible = false; shinetoggle.Visible = false; darktoggle.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; }); void syncModel() { repeatroomstoggle.Index = Settings.RepeatRooms ? 1 : 0; enterunknowntoggle.Index = Settings.EnterUnknown ? 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"); 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; } 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 (Exception e) { if (e.Message == "Could not generate map") { errortext.Title = e.Message; } else { errortext.Title = "Encountered an error - Check log.txt for details"; Logger.LogDetailed(e, "randomizer"); } errortext.FadeVisible = true; reenableMenu(); return; } this.entering = true; Audio.SetMusic((string)null, true, true); Audio.SetAmbience((string)null, true); Audio.Play("event:/ui/main/savefile_begin"); // use the debug file SaveData.InitializeDebugMode(); // turn on variants mode SaveData.Instance.VariantMode = true; SaveData.Instance.AssistMode = false; // clear summit gems, just in case! SaveData.Instance.SummitGems = new bool[6]; // mark as completed to spawn golden berry SaveData.Instance.Areas[newArea.ID].Modes[0].Completed = true; var fade = new FadeWipe(this.Scene, false, () => { // assign to variable to suppress compiler warning var session = new Session(newArea, null, null); //session.FirstLevel = false; // setting this value here prevents the wakeup animation. set it in a hook. LevelEnter.Go(session, true); this.builderThread = null; this.entering = false; }); /*foreach (AreaData area in AreaData.Areas) { * Logger.Log("randomizer", $"Skeleton for {area.GetSID()}"); * RandoConfigFile.YamlSkeleton(area); * * }*/ }); 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(lengthtoggle); menu.Add(numdashestoggle); menu.Add(difficultytoggle); menu.Add(moreoptionsbtn); menu.Add(repeatroomstoggle); menu.Add(enterunknowntoggle); menu.Add(enterunknowntext); menu.Add(shinetoggle); menu.Add(darktoggle); menu.Add(goldentoggle); menu.Add(startbutton); menu.Add(hashtext); menu.Add(errortext); Scene.Add(menu); }
/// <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; // 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; } // The settings subheader. string name; // We lazily reuse this field for the props later on. name = type.GetCustomAttribute <SettingNameAttribute>()?.Name ?? $"{nameDefaultPrefix}title"; name = name.DialogCleanOrNull() ?? Metadata.Name.SpacedPascalCase(); menu.Add(new TextMenu.SubHeader(name + " | v." + Metadata.VersionString)); 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) { 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; } name = prop.GetCustomAttribute <SettingNameAttribute>()?.Name ?? $"{nameDefaultPrefix}{prop.Name.ToLowerInvariant()}"; name = name.DialogCleanOrNull() ?? prop.Name.SpacedPascalCase(); bool needsRelaunch = prop.GetCustomAttribute <SettingNeedsRelaunchAttribute>() != null; 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 ) { item = new TextMenu.Slider(name, i => i.ToString(), attribRange.Min, attribRange.Max, (int)value) .Change(v => prop.SetValue(settings, v)) ; } 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(); string fullName = $"{enumNamePrefix}{enumName.ToLowerInvariant()}"; return(fullName.DialogCleanOrNull() ?? enumName); }, 0, enumValues.Length - 1, (int)value) .Change(v => prop.SetValue(settings, v)) ; } else if (!inGame && propType == typeof(string)) { item = new TextMenu.Button(name + ": " + value) .Pressed(() => { Audio.Play(Sfxs.ui_main_savefile_rename_start); menu.SceneAs <Overworld>().Goto <OuiModOptionString>().Init <OuiModOptions>( (string)value, v => prop.SetValue(settings, v) ); }) ; } if (item == null) { continue; } if (needsRelaunch) { item = item.NeedsRelaunch(); } menu.Add(item); } }
public override void Update() { if (menu != null && task != null && task.IsCompleted) { // there is no download or install task in progress if (fetchingButton != null) { // This means fetching the updates just finished. We have to remove the "Checking for updates" button // and put the actual update list instead. Logger.Log("OuiModUpdateList", "Rendering updates"); menu.Remove(fetchingButton); fetchingButton = null; if (updateCatalog == null) { // display an error message TextMenu.Button button = new TextMenu.Button(Dialog.Clean("MODUPDATECHECKER_ERROR")); button.Disabled = true; menu.Add(button); } else if (availableUpdatesCatalog.Count == 0) { // display a dummy "no update available" button TextMenu.Button button = new TextMenu.Button(Dialog.Clean("MODUPDATECHECKER_NOUPDATE")); button.Disabled = true; menu.Add(button); } else { // display one button per update foreach (ModUpdateInfo update in availableUpdatesCatalog.Keys) { EverestModuleMetadata metadata = availableUpdatesCatalog[update]; string versionUpdate = metadata.VersionString; if (metadata.VersionString != update.Version) { versionUpdate = $"{metadata.VersionString} > {update.Version}"; } TextMenu.Button button = new TextMenu.Button($"{metadata.Name.SpacedPascalCase()} | v. {versionUpdate} ({new DateTime(1970, 1, 1, 0, 0, 0, 0).AddSeconds(update.LastUpdate):yyyy-MM-dd})"); button.Pressed(() => { // make the menu non-interactive menu.Focused = false; button.Disabled = true; // trigger the update download downloadModUpdate(update, metadata, button); }); // if there is more than one hash, it means there is multiple downloads for this mod. Thus, we can't update it manually. if (update.xxHash.Count > 1) { button.Disabled = true; } menu.Add(button); } } } if (menu.Focused && Selected && Input.MenuCancel.Pressed) { if (shouldRestart) { Everest.QuickFullRestart(); } else { // go back to mod options instead Audio.Play(SFX.ui_main_button_back); Overworld.Goto <OuiModOptions>(); } } } base.Update(); }
/// <summary> /// Downloads and installs a mod update. /// </summary> /// <param name="update">The update info coming from the update server</param> /// <param name="mod">The mod metadata from Everest for the installed mod</param> /// <param name="button">The button for that mod shown on the interface</param> private void downloadModUpdate(ModUpdateInfo update, EverestModuleMetadata mod, TextMenu.Button button) { task = new Task(() => { // we will download the mod to Celeste_Directory/mod-update.zip at first. string zipPath = Path.Combine(Everest.PathGame, "mod-update.zip"); try { // download it... button.Label = $"{update.Name.SpacedPascalCase()} ({Dialog.Clean("MODUPDATECHECKER_DOWNLOADING")})"; downloadMod(update, button, zipPath); // verify its checksum ModUpdaterHelper.VerifyChecksum(update, zipPath); // mark restarting as required, as we will do weird stuff like closing zips afterwards. if (!shouldRestart) { shouldRestart = true; subHeader.TextColor = Color.OrangeRed; subHeader.Title = $"{Dialog.Clean("MODUPDATECHECKER_MENU_HEADER")} ({Dialog.Clean("MODUPDATECHECKER_WILLRESTART")})"; } // install it button.Label = $"{update.Name.SpacedPascalCase()} ({Dialog.Clean("MODUPDATECHECKER_INSTALLING")})"; ModUpdaterHelper.InstallModUpdate(update, mod, zipPath); // done! button.Label = $"{update.Name.SpacedPascalCase()} ({Dialog.Clean("MODUPDATECHECKER_UPDATED")})"; // select another enabled option: the next one, or the last one if there is no next one. if (menu.Selection + 1 > menu.LastPossibleSelection) { menu.Selection = menu.LastPossibleSelection; } else { menu.MoveSelection(1); } } catch (Exception e) { // update failed button.Label = $"{update.Name.SpacedPascalCase()} ({Dialog.Clean("MODUPDATECHECKER_FAILED")})"; Logger.Log("OuiModUpdateList", $"Updating {update.Name} failed"); Logger.LogDetailed(e); button.Disabled = false; // try to delete mod-update.zip if it still exists. ModUpdaterHelper.TryDelete(zipPath); } // give the menu control back to the player menu.Focused = true; }); task.Start(); }
protected override void addOptionsToMenu(TextMenu menu, bool inGame, object[] parameters) { OptionItems items = new OptionItems(); // Add the general settings menu.Add(new TextMenu.SubHeader(Dialog.Clean("MODOPTIONS_EXTENDEDVARIANTS_RANDOMIZER_GENERALSETTINGS"))); menu.Add(new TextMenu.Slider(Dialog.Clean("MODOPTIONS_EXTENDEDVARIANTS_CHANGEVARIANTSINTERVAL"), i => { if (i == 0) { return(Dialog.Clean("MODOPTIONS_EXTENDEDVARIANTS_ONSCREENTRANSITION")); } return($"{Dialog.Clean("MODOPTIONS_EXTENDEDVARIANTS_EVERY")} {changeVariantsIntervalScale[i]}s"); }, 0, changeVariantsIntervalScale.Length - 1, indexFromChangeVariantsInterval(ExtendedVariantsModule.Settings.ChangeVariantsInterval)) .Change(i => { ExtendedVariantsModule.Settings.ChangeVariantsInterval = changeVariantsIntervalScale[i]; refreshOptionMenuEnabledStatus(items); ExtendedVariantsModule.Instance.Randomizer.UpdateCountersFromSettings(); })); menu.Add(new TextMenu.Slider(Dialog.Clean("MODOPTIONS_EXTENDEDVARIANTS_RANDOMIZER_VARIANTSET"), i => Dialog.Clean("MODOPTIONS_EXTENDEDVARIANTS_" + new string[] { "OFF", "VANILLA", "EXTENDED", "BOTH" }[i]), 1, 3, ExtendedVariantsModule.Settings.VariantSet) .Change(i => { ExtendedVariantsModule.Settings.VariantSet = i; refreshOptionMenuEnabledStatus(items); })); TextMenu.Option <int> maxEnabledVariants = new TextMenu.Slider( Dialog.Clean("MODOPTIONS_EXTENDEDVARIANTS_RANDOMIZER_MAXENABLEDVARIANTS" + (ExtendedVariantsModule.Settings.RerollMode ? "_REROLL" : "")), i => i.ToString(), 0, ExtendedVariantsModule.Instance.VariantHandlers.Count + 13, ExtendedVariantsModule.Settings.MaxEnabledVariants) .Change(newValue => ExtendedVariantsModule.Settings.MaxEnabledVariants = newValue); menu.Add(new TextMenu.OnOff(Dialog.Clean("MODOPTIONS_EXTENDEDVARIANTS_RANDOMIZER_REROLLMODE"), ExtendedVariantsModule.Settings.RerollMode) .Change(newValue => { ExtendedVariantsModule.Settings.RerollMode = newValue; maxEnabledVariants.Label = Dialog.Clean("MODOPTIONS_EXTENDEDVARIANTS_RANDOMIZER_MAXENABLEDVARIANTS" + (newValue ? "_REROLL" : "")); })); menu.Add(maxEnabledVariants); menu.Add(items.VanillafyOption = new TextMenu.Slider(Dialog.Clean("MODOPTIONS_EXTENDEDVARIANTS_RANDOMIZER_VANILLAFY"), i => { if (i == 0) { return(Dialog.Clean("MODOPTIONS_EXTENDEDVARIANTS_DISABLED")); } i = vanillafyScale[i]; if (i < 60) { return($"{i.ToString()}s"); } return($"{(i / 60).ToString()} min"); }, 0, vanillafyScale.Length - 1, indexFromVanillafyScale(ExtendedVariantsModule.Settings.Vanillafy)) .Change(newValue => { ExtendedVariantsModule.Settings.Vanillafy = vanillafyScale[newValue]; ExtendedVariantsModule.Instance.Randomizer.UpdateCountersFromSettings(); })); menu.Add(new TextMenu.OnOff(Dialog.Clean("MODOPTIONS_EXTENDEDVARIANTS_RANDOMIZER_DISPLAYONSCREEN"), ExtendedVariantsModule.Settings.DisplayEnabledVariantsToScreen) .Change(newValue => ExtendedVariantsModule.Settings.DisplayEnabledVariantsToScreen = newValue)); if (!inGame) { TextMenu.Button seedButton = new TextMenu.Button(Dialog.Clean("MODOPTIONS_EXTENDEDVARIANTS_RANDOMIZER_SEEDINPUT") + " " + ExtendedVariantsModule.Settings.RandoSetSeed); seedButton.Pressed(() => { returnIndex = menu.Selection; Audio.Play(SFX.ui_main_savefile_rename_start); menu.SceneAs <Overworld>().Goto <OuiModOptionString>().Init <OuiRandomizerOptions>( ExtendedVariantsModule.Settings.RandoSetSeed, v => ExtendedVariantsModule.Settings.RandoSetSeed = v, 25 ); }); TextMenu.Option <bool> toggle = new TextMenu.OnOff(Dialog.Clean("MODOPTIONS_EXTENDEDVARIANTS_RANDOMIZER_SETSEED"), ExtendedVariantsModule.Settings.RandoSetSeed != null) .Change(newValue => { ExtendedVariantsModule.Settings.RandoSetSeed = (newValue ? "seed" : null); seedButton.Visible = newValue; seedButton.Label = Dialog.Clean("MODOPTIONS_EXTENDEDVARIANTS_RANDOMIZER_SEEDINPUT") + " seed"; }); seedButton.Visible = ExtendedVariantsModule.Settings.RandoSetSeed != null; menu.Add(toggle); toggle.AddDescription(menu, Dialog.Clean("MODOPTIONS_EXTENDEDVARIANTS_RANDOMIZER_SEEDDESCRIPTION2")); toggle.AddDescription(menu, Dialog.Clean("MODOPTIONS_EXTENDEDVARIANTS_RANDOMIZER_SEEDDESCRIPTION1")); menu.Add(seedButton); } // build the toggles to individually enable or disable all vanilla variants menu.Add(new TextMenu.SubHeader(Dialog.Clean("MODOPTIONS_EXTENDEDVARIANTS_RANDOMIZER_ENABLED_VANILLA"))); items.VanillaVariantOptions.Add(addToggleOptionToMenu(menu, VanillaVariant.GameSpeed)); items.VanillaVariantOptions.Add(addToggleOptionToMenu(menu, VanillaVariant.MirrorMode)); items.VanillaVariantOptions.Add(addToggleOptionToMenu(menu, VanillaVariant.ThreeSixtyDashing)); items.VanillaVariantOptions.Add(addToggleOptionToMenu(menu, VanillaVariant.InvisibleMotion)); items.VanillaVariantOptions.Add(addToggleOptionToMenu(menu, VanillaVariant.NoGrabbing)); items.VanillaVariantOptions.Add(addToggleOptionToMenu(menu, VanillaVariant.LowFriction)); items.VanillaVariantOptions.Add(addToggleOptionToMenu(menu, VanillaVariant.SuperDashing)); items.VanillaVariantOptions.Add(addToggleOptionToMenu(menu, VanillaVariant.Hiccups)); items.VanillaVariantOptions.Add(addToggleOptionToMenu(menu, VanillaVariant.PlayAsBadeline)); menu.Add(new TextMenu.SubHeader(Dialog.Clean("MODOPTIONS_EXTENDEDVARIANTS_RANDOMIZER_ENABLED_VANILLA_ASSISTS"))); items.VanillaVariantOptions.Add(addToggleOptionToMenu(menu, VanillaVariant.InfiniteStamina)); items.VanillaVariantOptions.Add(addToggleOptionToMenu(menu, VanillaVariant.DashMode)); items.VanillaVariantOptions.Add(addToggleOptionToMenu(menu, VanillaVariant.Invincible)); items.VanillaVariantOptions.Add(addToggleOptionToMenu(menu, VanillaVariant.DashAssist)); // and do the same with extended ones menu.Add(new TextMenu.SubHeader(Dialog.Clean("MODOPTIONS_EXTENDEDVARIANTS_RANDOMIZER_ENABLED_EXTENDED"))); foreach (ExtendedVariantsModule.Variant variant in ExtendedVariantsModule.Instance.VariantHandlers.Keys) { items.ExtendedVariantOptions.Add(addToggleOptionToMenu(menu, variant)); } refreshOptionMenuEnabledStatus(items); if (returnIndex >= 0) { menu.Selection = returnIndex; returnIndex = -1; } }
public override IEnumerator Enter(Oui from) { menu = new TextMenu(); // display the title and a dummy "Fetching" button menu.Add(new TextMenu.Header(Dialog.Clean("UPDATECHECKER_MENU_TITLE"))); menu.Add(subHeader = new TextMenuExt.SubHeaderExt(Dialog.Clean("UPDATECHECKER_MENU_HEADER"))); fetchingButton = new TextMenu.Button(Dialog.Clean("UPDATECHECKER_FETCHING")); fetchingButton.Disabled = true; menu.Add(fetchingButton); Scene.Add(menu); menu.Visible = Visible = true; menu.Focused = false; for (float p = 0f; p < 1f; p += Engine.DeltaTime * 4f) { menu.X = offScreenX + -1920f * Ease.CubeOut(p); alpha = Ease.CubeOut(p); yield return(null); } menu.Focused = true; task = new Task(() => { // 1. Download the updates list Logger.Log("UpdateChecker", "Downloading last versions list"); try { using (WebClient wc = new WebClient()) { string yamlData = wc.DownloadString("https://max480-random-stuff.appspot.com/celeste/everest_update.yaml"); updateCatalog = new Deserializer().Deserialize <Dictionary <string, ModUpdateInfo> >(yamlData); foreach (string name in updateCatalog.Keys) { updateCatalog[name].Name = name; } Logger.Log("UpdateChecker", $"Downloaded {updateCatalog.Count} item(s)"); } } catch (Exception e) { Logger.Log("UpdateChecker", $"Download failed! {e.ToString()}"); } // 2. Find out what actually has been updated availableUpdatesCatalog.Clear(); if (updateCatalog != null) { Logger.Log("UpdateChecker", "Checking for updates"); foreach (EverestModule module in Everest.Modules) { EverestModuleMetadata metadata = module.Metadata; if (metadata.PathArchive != null && updateCatalog.ContainsKey(metadata.Name)) { string xxHashStringInstalled = BitConverter.ToString(metadata.Hash).Replace("-", "").ToLowerInvariant(); Logger.Log("UpdateChecker", $"Mod {metadata.Name}: installed hash {xxHashStringInstalled}, latest hash(es) {string.Join(", ", updateCatalog[metadata.Name].xxHash)}"); if (!updateCatalog[metadata.Name].xxHash.Contains(xxHashStringInstalled)) { availableUpdatesCatalog.Add(updateCatalog[metadata.Name], metadata); } } } Logger.Log("UpdateChecker", $"{availableUpdatesCatalog.Count} update(s) available"); } }); task.Start(); }
/// <summary> /// Downloads and installs a mod update. /// </summary> /// <param name="update">The update info coming from the update server</param> /// <param name="mod">The mod metadata from Everest for the installed mod</param> /// <param name="button">The button for that mod shown on the interface</param> private void downloadModUpdate(ModUpdateInfo update, EverestModuleMetadata mod, TextMenu.Button button) { task = new Task(() => { bool updateSuccess = doDownloadModUpdate(update, mod, button); if (updateSuccess) { // select another enabled option: the next one, or the last one if there is no next one. if (menu.Selection + 1 > menu.LastPossibleSelection) { menu.Selection = menu.LastPossibleSelection; } else { menu.MoveSelection(1); } // remove this mod from the updatable mods list (it won't be updated by the "update all mods" button) updatableMods.Remove(new ModUpdateHolder() { update = update, metadata = mod, button = button }); } else { // re-enable the button to allow the user to try again. button.Disabled = false; } // give the menu control back to the player menu.Focused = true; }); task.Start(); }
private void downloadModUpdate(ModUpdateInfo update, EverestModuleMetadata mod, TextMenu.Button button) { task = new Task(() => { // we will download the mod to Celeste_Directory/mod-update.zip at first. string zipPath = Path.Combine(Everest.PathGame, "mod-update.zip"); try { // download it... button.Label = $"{update.Name} ({Dialog.Clean("UPDATECHECKER_DOWNLOADING")})"; downloadMod(update, button, zipPath); // verify its checksum string actualHash = BitConverter.ToString(Everest.GetChecksum("mod-update.zip")).Replace("-", "").ToLowerInvariant(); string expectedHash = update.xxHash[0]; Logger.Log("UpdateChecker", $"Verifying checksum: actual hash is {actualHash}, expected hash is {expectedHash}"); if (expectedHash != actualHash) { throw new IOException($"Checksum error: expected {expectedHash}, got {actualHash}"); } // mark restarting as required, as we will do weird stuff like closing zips afterwards. if (!shouldRestart) { shouldRestart = true; subHeader.TextColor = Color.OrangeRed; subHeader.Title = $"{Dialog.Clean("UPDATECHECKER_MENU_HEADER")} ({Dialog.Clean("UPDATECHECKER_WILLRESTART")})"; } // install it button.Label = $"{update.Name} ({Dialog.Clean("UPDATECHECKER_INSTALLING")})"; installMod(update, mod, zipPath); // done! button.Label = $"{update.Name} ({Dialog.Clean("UPDATECHECKER_UPDATED")})"; // select another enabled option: the next one, or the last one if there is no next one. if (menu.Selection + 1 > menu.LastPossibleSelection) { menu.Selection = menu.LastPossibleSelection; } else { menu.Selection++; } } catch (Exception e) { // update failed button.Label = $"{update.Name} ({Dialog.Clean("UPDATECHECKER_FAILED")})"; Logger.Log("UpdateChecker", $"Updating {update.Name} failed"); Logger.LogDetailed(e); button.Disabled = false; // try to delete mod-update.zip if it still exists. if (File.Exists(zipPath)) { try { Logger.Log("UpdateChecker", $"Deleting temp file {zipPath}"); File.Delete(zipPath); } catch (Exception) { Logger.Log("UpdateChecker", $"Removing {zipPath} failed"); } } } // give the menu control back to the player menu.Focused = true; }); task.Start(); }
/// <summary> /// Does the actual downloading of the mod. This is it's own function, to avoid double code /// </summary> /// <param name="update">The update info coming from the update server</param> /// <param name="mod">The mod metadata from Everest for the installed mod</param> /// <param name="button">The button for that mod shown on the interface</param> /// <returns>Bool wether the update failed or not</returns> private bool doDownloadModUpdate(ModUpdateInfo update, EverestModuleMetadata mod, TextMenu.Button button) { // we will download the mod to Celeste_Directory/[update.GetHashCode()].zip at first. string zipPath = Path.Combine(Everest.PathGame, $"modupdate-{update.GetHashCode()}.zip"); try { // download it... button.Label = $"{ModUpdaterHelper.FormatModName(update.Name)} ({Dialog.Clean("MODUPDATECHECKER_DOWNLOADING")})"; downloadMod(update, button, zipPath); // verify its checksum ModUpdaterHelper.VerifyChecksum(update, zipPath); // mark restarting as required, as we will do weird stuff like closing zips afterwards. shouldRestart = true; // install it button.Label = $"{ModUpdaterHelper.FormatModName(update.Name)} ({Dialog.Clean("MODUPDATECHECKER_INSTALLING")})"; ModUpdaterHelper.InstallModUpdate(update, mod, zipPath); // done! button.Label = $"{ModUpdaterHelper.FormatModName(update.Name)} ({Dialog.Clean("MODUPDATECHECKER_UPDATED")})"; return(true); } catch (Exception e) { // update failed button.Label = $"{ModUpdaterHelper.FormatModName(update.Name)} ({Dialog.Clean("MODUPDATECHECKER_FAILED")})"; Logger.Log("OuiModUpdateList", $"Updating {update.Name} failed"); Logger.LogDetailed(e); // try to delete mod-update.zip if it still exists. ModUpdaterHelper.TryDelete(zipPath); return(false); } }
public override void Update() { // check if the "press Back to restart" message has to be toggled if (menu != null && subHeader != null && (shouldRestart && menu.Focused) != willRestartMessageShown) { willRestartMessageShown = !willRestartMessageShown; if (willRestartMessageShown) { subHeader.TextColor = Color.OrangeRed; subHeader.Title = $"{Dialog.Clean("MODUPDATECHECKER_MENU_HEADER")} ({Dialog.Clean("MODUPDATECHECKER_WILLRESTART")})"; } else { subHeader.TextColor = Color.Gray; subHeader.Title = Dialog.Clean("MODUPDATECHECKER_MENU_HEADER"); } } if (menu != null && task != null && task.IsCompleted) { // there is no download or install task in progress if (fetchingButton != null) { // This means fetching the updates just finished. We have to remove the "Checking for updates" button // and put the actual update list instead. Logger.Log("OuiModUpdateList", "Rendering updates"); menu.Remove(fetchingButton); fetchingButton = null; if (updateCatalog == null) { // display an error message TextMenu.Button button = new TextMenu.Button(Dialog.Clean("MODUPDATECHECKER_ERROR")); button.Disabled = true; menu.Add(button); } else if (availableUpdatesCatalog.Count == 0) { // display a dummy "no update available" button TextMenu.Button button = new TextMenu.Button(Dialog.Clean("MODUPDATECHECKER_NOUPDATE")); button.Disabled = true; menu.Add(button); } else { // if there are multiple updates... if (availableUpdatesCatalog.Count > 1) { // display an "update all" button at the top of the list updateAllButton = new TextMenu.Button(Dialog.Clean("MODUPDATECHECKER_UPDATE_ALL")); updateAllButton.Pressed(() => downloadAllMods()); menu.Add(updateAllButton); } // then, display one button per update foreach (ModUpdateInfo update in availableUpdatesCatalog.Keys) { EverestModuleMetadata metadata = availableUpdatesCatalog[update]; string versionUpdate = metadata.VersionString; if (metadata.VersionString != update.Version) { versionUpdate = $"{metadata.VersionString} > {update.Version}"; } TextMenu.Button button = new TextMenu.Button($"{metadata.Name.SpacedPascalCase()} | v. {versionUpdate} ({new DateTime(1970, 1, 1, 0, 0, 0, 0).AddSeconds(update.LastUpdate):yyyy-MM-dd})"); button.Pressed(() => { // make the menu non-interactive menu.Focused = false; button.Disabled = true; // trigger the update download downloadModUpdate(update, metadata, button); }); // if there is more than one hash, it means there is multiple downloads for this mod. Thus, we can't update it manually. // if there isnt, add it to the list of mods that can be updated via "update all" if (update.xxHash.Count > 1) { button.Disabled = true; } else { updatableMods.Add(new ModUpdateHolder() { update = update, metadata = metadata, button = button }); } menu.Add(button); } } } if (menu.Focused && Selected && Input.MenuCancel.Pressed) { if (shouldRestart) { Everest.QuickFullRestart(); } else { // go back to mod options instead Audio.Play(SFX.ui_main_button_back); Overworld.Goto <OuiModOptions>(); } } } base.Update(); }
/// <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; } }
public void CreatePauseMenuButtons(Level level, TextMenu menu, bool minimal) { if (Everest.Flags.IsDisabled || !Settings.ShowModOptionsInGame) { return; } List <TextMenu.Item> items = menu.GetItems(); int index; // Find the options button and place our button below it. string cleanedOptions = Dialog.Clean("menu_pause_options"); index = items.FindIndex(_ => { TextMenu.Button other = (_ as TextMenu.Button); if (other == null) { return(false); } return(other.Label == cleanedOptions); }); if (index != -1) { index++; } // Otherwise, place it below the last button. else { index = items.Count; } TextMenu.Item itemModOptions = null; menu.Insert(index, itemModOptions = new TextMenu.Button(Dialog.Clean("menu_pause_modoptions")).Pressed(() => { int returnIndex = menu.IndexOf(itemModOptions); menu.RemoveSelf(); level.PauseMainMenuOpen = false; level.Paused = true; TextMenu options = OuiModOptions.CreateMenu(true, LevelExt.PauseSnapshot); options.OnESC = options.OnCancel = () => { Audio.Play(SFX.ui_main_button_back); options.CloseAndRun(Everest.SaveSettings(), () => { level.Pause(returnIndex, minimal, false); // adjust the Mod Options menu position, in case it moved (pause menu entries added/removed after changing mod options). TextMenu textMenu = level.Entities.GetToAdd().FirstOrDefault((Entity e) => e is TextMenu) as TextMenu; TextMenu.Button modOptionsButton = textMenu?.GetItems().OfType <TextMenu.Button>() .FirstOrDefault(button => button.Label == Dialog.Clean("menu_pause_modoptions")); if (modOptionsButton != null) { textMenu.Selection = textMenu.IndexOf(modOptionsButton); } }); }; options.OnPause = () => { Audio.Play(SFX.ui_main_button_back); options.CloseAndRun(Everest.SaveSettings(), () => { level.Paused = false; Engine.FreezeTimer = 0.15f; }); }; level.Add(options); })); }
public void CreateCommandSettingsEntry(TextMenu textMenu, bool inGame) { TextMenu.Button button = AbstractSubmenu.BuildOpenMenuButton <OuiCrowControlSubmenu>(textMenu, inGame, () => OuiModOptions.Instance.Overworld.Goto <OuiModOptions>(), new object[] { DialogIds.CommandSettings }); textMenu.Add(button); }