// Automatically override intro cutscenes private static void SlugcatSelectMenu_StartGame(On.Menu.SlugcatSelectMenu.orig_StartGame orig, SlugcatSelectMenu self, int storyGameCharacter) { orig(self, storyGameCharacter); if (!self.restartChecked && self.manager.rainWorld.progression.IsThereASavedGame(storyGameCharacter)) { return; } // Only continue to the slideshow if this character has an intro slideshow SlugBaseCharacter ply = PlayerManager.GetCustomPlayer(storyGameCharacter); if (ply == null) { return; } if (ply.HasSlideshow("Intro") && !Input.GetKey("s")) { OverrideNextSlideshow(ply, "Intro"); self.manager.upcomingProcess = null; self.manager.RequestMainProcessSwitch(ProcessManager.ProcessID.SlideShow); } else { self.manager.upcomingProcess = null; self.manager.RequestMainProcessSwitch(ProcessManager.ProcessID.Game); } }
/// <summary> /// Creates a scene from a JSON object. /// </summary> public CustomScene(SlugBaseCharacter owner, string name, JsonObj data) : this(owner, name) { foreach (var pair in data) { LoadValue(pair.Key, pair.Value); } }
/// <summary> /// Creates an empty slideshow. /// </summary> /// <param name="owner"></param> /// <param name="name"></param> public CustomSlideshow(SlugBaseCharacter owner, string name) { NextProcess = null; Owner = owner; Name = name; Slides = new List <SlideshowSlide>(); }
// The select menu relies on a manifest of save information // In vanilla, this is either pulled from the current game or mined from the progression file // If the indicated slugcat is added by slugbase, instead mine from the custom save file private static SlugcatSelectMenu.SaveGameData SlugcatSelectMenu_MineForSaveData(On.Menu.SlugcatSelectMenu.orig_MineForSaveData orig, ProcessManager manager, int slugcat) { SlugBaseCharacter ply = PlayerManager.GetCustomPlayer(slugcat); if (ply != null) { SaveState save = manager.rainWorld.progression.currentSaveState; if (save != null && save.saveStateNumber == slugcat) { return(new SlugcatSelectMenu.SaveGameData { karmaCap = save.deathPersistentSaveData.karmaCap, karma = save.deathPersistentSaveData.karma, karmaReinforced = save.deathPersistentSaveData.reinforcedKarma, shelterName = save.denPosition, cycle = save.cycleNumber, hasGlow = save.theGlow, hasMark = save.deathPersistentSaveData.theMark, redsExtraCycles = save.redExtraCycles, food = save.food, redsDeath = save.deathPersistentSaveData.redsDeath, ascended = save.deathPersistentSaveData.ascended }); } int slot = manager.rainWorld.options.saveSlot; return(SaveManager.GetCustomSaveData(manager.rainWorld, ply.Name, slot)); } return(orig(manager, slugcat)); }
/// <summary> /// Creates a representation of a SlugBase character. /// </summary> /// <param name="customPlayer">The character to represent.</param> public PlayerDescriptor(SlugBaseCharacter customPlayer) { type = Type.SlugBase; name = customPlayer.DisplayName; player = customPlayer; index = -1; }
internal static CustomScene OverrideNextScene(SlugBaseCharacter ply, string customSceneName, SceneImageFilter filter = null) { sceneOverride = ply.BuildScene(customSceneName); if (filter != null) { sceneOverride.ApplyFilter(filter); } return(sceneOverride); }
private static bool PlayerProgression_IsThereASavedGame(On.PlayerProgression.orig_IsThereASavedGame orig, PlayerProgression self, int saveStateNumber) { SlugBaseCharacter ply = PlayerManager.GetCustomPlayer(saveStateNumber); if (ply != null) { return(HasCustomSaveData(ply.Name, self.rainWorld.options.saveSlot)); } return(orig(self, saveStateNumber)); }
private static void PlayerProgression_WipeSaveState(On.PlayerProgression.orig_WipeSaveState orig, PlayerProgression self, int saveStateNumber) { orig(self, saveStateNumber); SlugBaseCharacter ply = PlayerManager.GetCustomPlayer(saveStateNumber); if (ply != null) { File.Delete(GetSaveFilePath(ply.Name, self.rainWorld.options.saveSlot)); } }
// Change some data associated with custom slugcat pages private static void SlugcatPage_ctor(On.Menu.SlugcatSelectMenu.SlugcatPage.orig_ctor orig, SlugcatSelectMenu.SlugcatPage self, Menu.Menu menu, MenuObject owner, int pageIndex, int slugcatNumber) { orig(self, menu, owner, pageIndex, slugcatNumber); SlugBaseCharacter ply = PlayerManager.GetCustomPlayer(pageIndex); if (ply != null) { self.colorName = ply.Name; self.effectColor = ply.SlugcatColor() ?? Color.white; } }
// Change select screen name and description private static void SlugcatPageNewGame_ctor(On.Menu.SlugcatSelectMenu.SlugcatPageNewGame.orig_ctor orig, SlugcatSelectMenu.SlugcatPageNewGame self, Menu.Menu menu, MenuObject owner, int pageIndex, int slugcatNumber) { orig(self, menu, owner, pageIndex, slugcatNumber); SlugBaseCharacter ply = PlayerManager.GetCustomPlayer(slugcatNumber); if (ply != null) { self.difficultyLabel.text = ply.DisplayName.ToUpper(); self.infoLabel.text = ply.Description.Replace("<LINE>", Environment.NewLine); } }
/// <summary> /// Creates a new representation of a SlugBase character's save state. /// </summary> /// <param name="progression">The <see cref="PlayerProgression"/> instance to attach this save state to.</param> /// <param name="character">The SlugBase character that owns this save state.</param> /// <exception cref="ArgumentException">Thrown when <paramref name="character"/> is null.</exception> public CustomSaveState(PlayerProgression progression, SlugBaseCharacter character) : base(character.slugcatIndex, progression) { if (character == null) { throw new ArgumentException("Character may not be null.", nameof(character)); } Character = character; if (!appliedHooks) { appliedHooks = true; ApplyHooks(); } }
private static Texture2D LoadTextureFromResources(string fileName) { string[] args = fileName.Split('\\'); if (args.Length < 2) { return(null); } SlugBaseCharacter ply = PlayerManager.GetCustomPlayer(args[0]); if (ply == null) { return(null); } string[] resourcePath = new string[args.Length - 1]; for (int i = 0; i < resourcePath.Length; i++) { resourcePath[i] = args[i + 1]; } if (resourcePath.Length > 0) { resourcePath[resourcePath.Length - 1] = Path.ChangeExtension(resourcePath[resourcePath.Length - 1], "png"); } // Load the image resource from disk Texture2D tex = new Texture2D(1, 1); using (Stream imageData = ply.GetResource(resourcePath)) { if (imageData == null) { Debug.LogException(new FileNotFoundException($"Could not find image for SlugBase character: \"{ply.Name}:{string.Join("\\", resourcePath)}\".")); return(null); } if (imageData.Length > int.MaxValue) { throw new FormatException($"Image resource may not be more than {int.MaxValue} bytes!"); } BinaryReader br = new BinaryReader(imageData); byte[] buffer = br.ReadBytes((int)imageData.Length); tex.LoadImage(buffer); } return(tex); }
// Automatically override outro cutscenes private static void RainWorldGame_ExitToVoidSeaSlideShow(On.RainWorldGame.orig_ExitToVoidSeaSlideShow orig, RainWorldGame self) { orig(self); // Override the outro if this character has the corresponding slideshow SlugBaseCharacter ply = PlayerManager.GetCustomPlayer(self.StoryCharacter); if (ply == null) { return; } if (ply.HasSlideshow("Outro")) { OverrideNextSlideshow(ply, "Outro"); } }
// Read the shelter name from a separate file private static string PlayerProgression_ShelterOfSaveGame(On.PlayerProgression.orig_ShelterOfSaveGame orig, PlayerProgression self, int saveStateNumber) { SlugBaseCharacter ply = PlayerManager.GetCustomPlayer(saveStateNumber); if (ply == null) { return(orig(self, saveStateNumber)); } if (self.currentSaveState != null && self.currentSaveState.saveStateNumber == saveStateNumber) { return(self.currentSaveState.denPosition); } string startRoom = ply.StartRoom; int slot = self.rainWorld.options.saveSlot; if (!HasCustomSaveData(ply.Name, slot) && startRoom != null) { return(startRoom); } string saveText = File.ReadAllText(GetSaveFilePath(ply.Name, slot)); List <SaveStateMiner.Target> targets = new List <SaveStateMiner.Target>(); targets.Add(new SaveStateMiner.Target(">DENPOS", "<svB>", "<svA>", 20)); List <SaveStateMiner.Result> results = SaveStateMiner.Mine(self.rainWorld, saveText, targets); if (results.Count > 0 && results[0].data != null) { return(results[0].data); } return(startRoom ?? "SU_S01"); }
private static void MenuScene_AddIllustration(On.Menu.MenuScene.orig_AddIllustration orig, MenuScene self, MenuIllustration newIllu) { SlugBaseCharacter chara = PlayerManager.GetCustomPlayer(self.menu.manager.rainWorld.progression.miscProgressionData.currentlySelectedSinglePlayerSlugcat); if (newIllu.fileName == "Sleep - 2 - Red" && chara != null && !chara.HasScene("SleepScreen") && ((self.menu as SleepAndDeathScreen)?.IsSleepScreen ?? false) && newIllu is MenuDepthIllustration mdi) { string folder = string.Concat(new object[] { "Scenes", Path.DirectorySeparatorChar, "Sleep Screen - White", }); newIllu.RemoveSprites(); newIllu = new MenuDepthIllustration(newIllu.menu, newIllu.owner, folder, "Sleep - 2 - White", new Vector2(677f, 63f), mdi.depth, mdi.shader); moveImages.Add(new KeyValuePair <MenuDepthIllustration, Vector2>((MenuDepthIllustration)newIllu, new Vector2(677f, 63f))); } orig(self, newIllu); }
/// <summary> /// /// </summary> /// <param name="input"></param> /// <returns></returns> public static PlayerDescriptor FromString(string input) { try { // Find type int typeSplit = input.IndexOf('-'); if (typeSplit == -1) { return(new PlayerDescriptor(0)); } Type t = Custom.ParseEnum <Type>(input.Substring(0, typeSplit)); // Fill data switch (t) { case Type.Vanilla: return(new PlayerDescriptor((int)Custom.ParseEnum <SlugcatStats.Name>(input.Substring(typeSplit + 1)))); case Type.SlugBase: { SlugBaseCharacter ply = PlayerManager.GetCustomPlayer(input.Substring(typeSplit + 1)); if (ply == null) { return(new PlayerDescriptor(0)); } return(new PlayerDescriptor(ply)); } default: return(new PlayerDescriptor(0)); } } catch (Exception e) { throw new ArgumentException("Failed to parse character descriptor string.", nameof(input), e); } }
private static void MenuScene_GrafUpdate(On.Menu.MenuScene.orig_GrafUpdate orig, MenuScene self, float timeStacker) { orig(self, timeStacker); if (editor.TryGet(self, out SceneEditor se)) { se.Update(self); if (Input.GetKeyDown(KeyCode.RightBracket)) { se.Remove(); editor.Unset(self); } } else { if (self.sceneFolder == resourceFolderName && Input.GetKeyDown(KeyCode.RightBracket)) { SlugBaseCharacter ply = null; foreach (MenuObject subObj in self.subObjects) { if (subObj is MenuIllustration illust) { ply = customRep[illust]?.Owner.Owner; if (ply != null) { break; } } } if (ply.DevMode) { editor[self] = new SceneEditor(self); } } } }
/// <summary> /// Creates an empty scene. /// </summary> public CustomScene(SlugBaseCharacter owner, string name) { Images = new List <SceneImage>(); Owner = owner; Name = name; }
// Override select scenes for SlugBase characters private static void SlugcatPage_AddImage(On.Menu.SlugcatSelectMenu.SlugcatPage.orig_AddImage orig, SlugcatSelectMenu.SlugcatPage self, bool ascended) { SlugBaseCharacter ply = PlayerManager.GetCustomPlayer(self.slugcatNumber); // Do not modify scenes for any non-SlugBase slugcats if (ply == null) { orig(self, ascended); return; } // Use Survivor's default scenes on the select menu string sceneName = ascended ? "SelectMenuAscended" : "SelectMenu"; if (!ply.HasScene(sceneName)) { orig(self, ascended); // Fix the scene position being off if (self.sceneOffset == default(Vector2)) { self.sceneOffset = new Vector2(-10f, 100f); } // Fix the wrong scene loading in when ascended if (ascended && self.slugcatImage.sceneID == MenuScene.SceneID.Slugcat_White) { self.slugcatImage.RemoveSprites(); self.RemoveSubObject(self.slugcatImage); self.slugcatImage = new InteractiveMenuScene(self.menu, self, MenuScene.SceneID.Ghost_White); self.subObjects.Add(self.slugcatImage); } return; } // Make sure it doesn't crash if the mark or glow is missing self.markSquare = new FSprite("pixel") { isVisible = false }; self.markGlow = new FSprite("pixel") { isVisible = false }; self.glowSpriteA = new FSprite("pixel") { isVisible = false }; self.glowSpriteB = new FSprite("pixel") { isVisible = false }; // This function intentionally does not call the original // If this mod has claimed a slot, it seems best to not let other mods try to change this screen // Taken from SlugcatPage.AddImage self.imagePos = new Vector2(683f, 484f); self.sceneOffset = new Vector2(0f, 0f); // Load a custom character's select screen from resources CustomScene scene = OverrideNextScene(ply, sceneName, img => { if (img.HasTag("MARK") && !self.HasMark) { return(false); } if (img.HasTag("GLOW") && !self.HasGlow) { return(false); } return(true); }); // Parse selectmenux and selectmenuy self.sceneOffset.x = scene.GetProperty <float?>("selectmenux") ?? 0f; self.sceneOffset.y = scene.GetProperty <float?>("selectmenuy") ?? 0f; Debug.Log($"Scene offset for {ply.Name}: {self.sceneOffset}"); // Slugcat depth, used for positioning the glow and mark self.slugcatDepth = scene.GetProperty <float?>("slugcatdepth") ?? 3f; // Add mark MarkImage mark = new MarkImage(scene, self.slugcatDepth + 0.1f); scene.InsertImage(mark); // Add glow GlowImage glow = new GlowImage(scene, self.slugcatDepth + 0.1f); scene.InsertImage(glow); try { self.slugcatImage = new InteractiveMenuScene(self.menu, self, MenuScene.SceneID.Slugcat_White); // This scene will be immediately overwritten } finally { ClearSceneOverride(); } self.subObjects.Add(self.slugcatImage); // Find the relative mark and glow positions self.markOffset = mark.Pos - new Vector2(self.MidXpos, self.imagePos.y + 150f) + self.sceneOffset; self.glowOffset = glow.Pos - new Vector2(self.MidXpos, self.imagePos.y) + self.sceneOffset; }
// Same as below, but for slideshows private static void SlideShow_ctor(On.Menu.SlideShow.orig_ctor orig, SlideShow self, ProcessManager manager, SlideShow.SlideShowID slideShowID) { // Automatically override slideshows if the current character has a slideshow by the same name SlugBaseCharacter currentPlayer; if (PlayerManager.UsingCustomCharacter) { currentPlayer = PlayerManager.CurrentCharacter; } else { int index; if (manager.currentMainLoop is RainWorldGame rwg) { index = rwg.StoryCharacter; } else { index = manager.rainWorld.progression.PlayingAsSlugcat; } currentPlayer = PlayerManager.GetCustomPlayer(index); } if (currentPlayer != null) { string slideshowName = self.slideShowID.ToString(); if (slideshowOverride == null && currentPlayer.HasSlideshow(slideshowName)) { OverrideNextSlideshow(currentPlayer, slideshowName); } } if (slideshowOverride == null) { orig(self, manager, slideShowID); return; } try { // Call the original constructor, save a reference to the loading label // This will always be empty, due to the ID of -1 FLabel loadingLabel = manager.loadingLabel; orig(self, manager, (SlideShow.SlideShowID)(-1)); // Undo RemoveLoadingLabel and NextScene manager.loadingLabel = loadingLabel; Futile.stage.AddChild(loadingLabel); self.current = -1; // Load a custom scene SlugBaseCharacter owner = slideshowOverride.Owner; List <SlideshowSlide> slides = slideshowOverride.Slides; // Chose a destination process if (slideshowOverride.NextProcess == null) { switch (slideShowID) { case SlideShow.SlideShowID.WhiteIntro: case SlideShow.SlideShowID.YellowIntro: self.nextProcess = ProcessManager.ProcessID.Game; break; case SlideShow.SlideShowID.WhiteOutro: case SlideShow.SlideShowID.YellowOutro: case SlideShow.SlideShowID.RedOutro: self.nextProcess = ProcessManager.ProcessID.Credits; break; default: // Take a best guess // Accidentally going to the game is better than accidentally going to the credits self.nextProcess = ProcessManager.ProcessID.Game; break; } } else { self.nextProcess = slideshowOverride.NextProcess.Value; } // Custom music if (manager.musicPlayer != null) { self.waitForMusic = slideshowOverride.Music; self.stall = true; manager.musicPlayer.MenuRequestsSong(self.waitForMusic, 1.5f, 40f); } // Custom playlist float time = 0f; float endTime; self.playList.Clear(); foreach (SlideshowSlide slide in slides) { if (!slide.Enabled) { continue; } endTime = time + slide.Duration; self.playList.Add(new SlideShow.Scene(MenuScene.SceneID.Empty, time, time + slide.FadeIn, endTime - slide.FadeOut)); time = endTime; } // Preload the scenes self.preloadedScenes = new SlideShowMenuScene[self.playList.Count]; try { for (int i = 0; i < self.preloadedScenes.Length; i++) { MenuScene.SceneID id = MenuScene.SceneID.Empty; if (slideshowOverride.Owner.HasScene(slides[i].SceneName)) { // Prioritize this character's scenes OverrideNextScene(slideshowOverride.Owner, slideshowOverride.Slides[i].SceneName); } else { ClearSceneOverride(); try { // ... then try existing scenes id = Custom.ParseEnum <MenuScene.SceneID>(slides[i].SceneName); } catch (Exception) { // ... and default to Empty id = MenuScene.SceneID.Empty; } } self.preloadedScenes[i] = new SlideShowMenuScene(self, self.pages[0], id); self.preloadedScenes[i].Hide(); List <Vector3> camPath = self.preloadedScenes[i].cameraMovementPoints; camPath.Clear(); camPath.AddRange(slides[i].CameraPath); } } finally { ClearSceneOverride(); } } finally { ClearSlideshowOverride(); } manager.RemoveLoadingLabel(); self.NextScene(); }
internal static CustomSlideshow OverrideNextSlideshow(SlugBaseCharacter ply, string customSlideshowName) { slideshowOverride = ply.BuildSlideshow(customSlideshowName); return(slideshowOverride); }
/// <summary> /// Creates a scene from a JSON string. /// </summary> public CustomScene(SlugBaseCharacter owner, string name, string json) : this(owner, name, json.dictionaryFromJson()) { }
private static SaveState PlayerProgression_GetOrInitiateSaveState(On.PlayerProgression.orig_GetOrInitiateSaveState orig, PlayerProgression self, int saveStateNumber, RainWorldGame game, ProcessManager.MenuSetup setup, bool saveAsDeathOrQuit) { int slot = self.rainWorld.options.saveSlot; SlugBaseCharacter ply = PlayerManager.GetCustomPlayer(saveStateNumber); if (ply == null) { return(orig(self, saveStateNumber, game, setup, saveAsDeathOrQuit)); } // Copied from PlayerProgression.GetOrInitiateSaveState if (self.currentSaveState == null && self.starvedSaveState != null) { Debug.Log("LOADING STARVED STATE"); self.currentSaveState = self.starvedSaveState; self.currentSaveState.deathPersistentSaveData.winState.ResetLastShownValues(); self.starvedSaveState = null; } if (self.currentSaveState != null && self.currentSaveState.saveStateNumber == saveStateNumber) { if (saveAsDeathOrQuit) { self.SaveDeathPersistentDataOfCurrentState(true, true); } return(self.currentSaveState); } // Create a CustomSaveState instance instead self.currentSaveState = ply.CreateNewSave(self); if (!File.Exists(self.saveFilePath) || !setup.LoadInitCondition) { self.currentSaveState.LoadGame(string.Empty, game); } else { // Read the save state from a separate file instead of from prog lines CustomSaveState css = self.currentSaveState as CustomSaveState; if (css != null && HasCustomSaveData(ply.Name, slot)) { string inSave = File.ReadAllText(GetSaveFilePath(ply.Name, slot)); self.currentSaveState.LoadGame(inSave, game); if (saveAsDeathOrQuit) { self.SaveDeathPersistentDataOfCurrentState(true, true); } return(self.currentSaveState); } // By default, load an empty string self.currentSaveState.LoadGame(string.Empty, game); } if (saveAsDeathOrQuit) { self.SaveDeathPersistentDataOfCurrentState(true, true); } return(self.currentSaveState); // End copied section }
// Update a file on the disk with the current save-persistent data private static void PlayerProgression_SaveDeathPersistentDataOfCurrentState(On.PlayerProgression.orig_SaveDeathPersistentDataOfCurrentState orig, PlayerProgression self, bool saveAsIfPlayerDied, bool saveAsIfPlayerQuit) { int slot = self.rainWorld.options.saveSlot; SlugBaseCharacter ply = null; if (self.currentSaveState != null) { ply = PlayerManager.GetCustomPlayer(self.currentSaveState.saveStateNumber); } if (ply == null || !(self.currentSaveState is CustomSaveState css)) { orig(self, saveAsIfPlayerDied, saveAsIfPlayerQuit); return; } // Copied from PlayerProgression.SaveDeathPersistentDataOfCurrentState Debug.Log(string.Concat(new object[] { "save slugbase deathPersistent data ", self.currentSaveState.deathPersistentSaveData.karma, " sub karma: ", saveAsIfPlayerDied, " (quit:", saveAsIfPlayerQuit, ")" })); string savePath = GetSaveFilePath(ply.Name, slot); string vanillaDPSD = self.currentSaveState.deathPersistentSaveData.SaveToString(saveAsIfPlayerDied, saveAsIfPlayerQuit); string customDPSD = css.SaveCustomPermanentToString(saveAsIfPlayerDied, saveAsIfPlayerQuit); string inSave; try { inSave = File.ReadAllText(savePath); } catch (Exception) { // Consider changing to handle FileNotFound only return; } StringBuilder outSave = new StringBuilder(); string[] array2 = Regex.Split(inSave, "<svA>"); for (int j = 0; j < array2.Length; j++) { string[] pair = Regex.Split(array2[j], "<svB>"); // Save vanilla DPSD if (pair[0] == "DEATHPERSISTENTSAVEDATA" && !string.IsNullOrEmpty(vanillaDPSD)) { outSave.Append("DEATHPERSISTENTSAVEDATA<svB>" + vanillaDPSD + "<svA>"); } // Save custom DPSD else if (pair[0] == "SLUGBASEPERSISTENT" && !string.IsNullOrEmpty(customDPSD)) { outSave.Append("SLUGBASEPERSISTENT<svB>" + customDPSD + "<svA>"); } // Echo any other data else { outSave.Append(array2[j] + "<svA>"); } } File.WriteAllText(savePath, outSave.ToString()); }