internal static string _GetCheckpointPreviewName(AreaKey area, string level) { int split = level?.IndexOf('|') ?? -1; if (split >= 0) { area = AreaDataExt.Get(level.Substring(0, split))?.ToKey(area.Mode) ?? area; level = level.Substring(split + 1); } string result = area.ToString(); if (area.GetLevelSet() != "Celeste") { result = area.GetSID(); } if (level != null) { result = result + "_" + level; } if (MTN.Checkpoints.Has(result)) { return(result); } return($"{area.GetSID()}/{(char) ('A' + (int) area.Mode)}/{level ?? "start"}"); }
private IEnumerator StartRoutine(string checkpoint = null) { int checkpointAreaSplit = checkpoint?.IndexOf('|') ?? -1; if (checkpointAreaSplit >= 0) { Area = AreaDataExt.Get(checkpoint.Substring(0, checkpointAreaSplit))?.ToKey(Area.Mode) ?? Area; checkpoint = checkpoint.Substring(checkpointAreaSplit + 1); } EnteringChapter = true; Overworld.Maddy.Hide(false); Overworld.Mountain.EaseCamera(Area.ID, Data.MountainZoom, 1f); Add(new Coroutine(EaseOut(false))); yield return(0.2f); ScreenWipe.WipeColor = Color.Black; AreaData.Get(Area).Wipe(Overworld, false, null); Audio.SetMusic(null); Audio.SetAmbience(null); // TODO: Determine if the area should keep the overworld snow. if ((Area.ID == 0 || Area.ID == 9) && checkpoint == null && Area.Mode == AreaMode.Normal) { Overworld.RendererList.UpdateLists(); Overworld.RendererList.MoveToFront(Overworld.Snow); } yield return(0.5f); LevelEnter.Go(new Session(Area, checkpoint), false); }
public override bool IsStart(Overworld overworld, Overworld.StartMode start) { if (start == Overworld.StartMode.AreaComplete || start == Overworld.StartMode.AreaQuit) { AreaData area = AreaData.Get(SaveData.Instance.LastArea.ID); area = AreaDataExt.Get(area?.GetMeta()?.Parent) ?? area; if (area != null) { SaveData.Instance.LastArea.ID = area.ID; } } return(orig_IsStart(overworld, start)); }
public override bool IsStart(Overworld overworld, Overworld.StartMode start) { if (SaveData.Instance != null && SaveData.Instance.LastArea.ID == AreaKey.None.ID) { SaveData.Instance.LastArea = AreaKey.Default; instantClose = true; } if (start == Overworld.StartMode.AreaComplete || start == Overworld.StartMode.AreaQuit) { AreaData area = AreaData.Get(SaveData.Instance.LastArea.ID); area = AreaDataExt.Get(area?.GetMeta()?.Parent) ?? area; if (area != null) { SaveData.Instance.LastArea.ID = area.ID; } } bool isStart = orig_IsStart(overworld, start); if (isStart && option >= options.Count && options.Count == 1) { // we are coming back from a B/C-side and we didn't unlock B-sides. Force-add it. AddRemixButton(); } if (isStart && option >= options.Count && options.Count == 2) { // we are coming back from a C-side we did not unlock. Force-add it. options.Add(new Option { Label = Dialog.Clean("overworld_remix2"), Icon = GFX.Gui["menu/rmx2"], ID = "C" }); } return(isStart); }
public new void AfterInitialize() { // Vanilla / new saves don't have the LevelSets list. if (LevelSets == null) { LevelSets = new List <LevelSetStats>(); } if (LevelSetRecycleBin == null) { LevelSetRecycleBin = new List <LevelSetStats>(); } if (LevelSets.Count <= 1 && LevelSetRecycleBin.Count == 0 && !HasModdedSaveData) { // the save file doesn't have any mod save data (just created, overwritten by vanilla, or Everest just updated). // we want to carry mod save data that was backed up in the mod save file, if any. ModSaveData modSaveData = UserIO.Load <ModSaveData>(GetFilename(FileSlot) + "-modsavedata"); if (modSaveData != null) { modSaveData.CopyToCelesteSaveData(this); Logger.Log(LogLevel.Warn, "SaveData", $"{LevelSets.Count} level set(s) were restored from mod backup for save slot {FileSlot}"); } } HasModdedSaveData = true; if (Areas_Unsafe == null) { Areas_Unsafe = new List <AreaStats>(); } // Add missing LevelSetStats. foreach (AreaData area in AreaData.Areas) { string set = area.GetLevelSet(); if (!LevelSets.Exists(other => other.Name == set)) { LevelSetStats recycleBinLevelSet = LevelSetRecycleBin.FirstOrDefault(other => other.Name == set); if (recycleBinLevelSet != null) { // the level set is actually in the recycle bin - restore it. LevelSets.Add(recycleBinLevelSet); LevelSetRecycleBin.Remove(recycleBinLevelSet); } else { // create a new LevelSetStats entry. LevelSets.Add(new LevelSetStats { Name = set, UnlockedAreas = set == "Celeste" ? UnlockedAreas_Unsafe : 0 }); } } } // Fill each LevelSetStats with its areas. for (int lsi = 0; lsi < LevelSets.Count; lsi++) { LevelSetStats set = LevelSets[lsi]; set.SaveData = this; List <AreaStats> areas = set.Areas; if (set.Name == "Celeste") { areas = Areas_Unsafe; } int offset = set.AreaOffset; if (offset == -1) { // LevelSet gone - let's move it to the recycle bin. LevelSetStats levelSetAlreadyInRecycleBin = LevelSetRecycleBin.FirstOrDefault(other => other.Name == set.Name); if (levelSetAlreadyInRecycleBin != null) { // a level set with the same name already exists in the recycle bin - replace it. LevelSetRecycleBin.Remove(levelSetAlreadyInRecycleBin); } LevelSetRecycleBin.Add(set); // now, remove it to prevent any unwanted access. LevelSets.RemoveAt(lsi); lsi--; continue; } // Refresh all stat IDs based on their SIDs, sort, fill and remove leftovers. // Temporarily use ID_Unsafe; later ID_Safe to ID_Unsafe to resync the SIDs. // This keeps the stats bound to their SIDs, not their indices, while removing non-existent areas. int countRoots = AreaData.Areas.Count(other => other.GetLevelSet() == set.Name && string.IsNullOrEmpty(other?.GetMeta()?.Parent)); int countAll = AreaData.Areas.Count(other => other.GetLevelSet() == set.Name); // Fix IDs for (int i = 0; i < areas.Count; i++) { AreaData area = AreaDataExt.Get(areas[i]); if (!string.IsNullOrEmpty(area?.GetMeta()?.Parent)) { area = null; } ((patch_AreaStats)areas[i]).ID_Unsafe = area?.ID ?? int.MaxValue; } // Sort areas.Sort((a, b) => ((patch_AreaStats)a).ID_Unsafe - ((patch_AreaStats)b).ID_Unsafe); // Remove leftovers while (areas.Count > 0 && ((patch_AreaStats)areas[areas.Count - 1]).ID_Unsafe == int.MaxValue) { areas.RemoveAt(areas.Count - 1); } // Fill gaps for (int i = 0; i < countRoots; i++) { if (i >= areas.Count || ((patch_AreaStats)areas[i]).ID_Unsafe != offset + i) { areas.Insert(i, new AreaStats(offset + i)); } } // Duplicate parent stat refs into their respective children slots. for (int i = countRoots; i < countAll; i++) { if (i >= areas.Count) { areas.Insert(i, areas[AreaDataExt.Get(AreaData.Get(offset + i).GetMeta().Parent).ID - offset]); } } // Resync SIDs for (int i = 0; i < areas.Count; i++) { ((patch_AreaStats)areas[i]).ID_Safe = ((patch_AreaStats)areas[i]).ID_Unsafe; } int lastCompleted = -1; for (int i = 0; i < countRoots; i++) { if (areas[i].Modes[0].Completed) { lastCompleted = i; } } if (set.Name == "Celeste") { if (UnlockedAreas_Unsafe < lastCompleted + 1 && set.MaxArea >= lastCompleted + 1) { UnlockedAreas_Unsafe = lastCompleted + 1; } if (DebugMode) { UnlockedAreas_Unsafe = set.MaxArea; } } else { if (set.UnlockedAreas < lastCompleted + 1 && set.MaxArea >= lastCompleted + 1) { set.UnlockedAreas = lastCompleted + 1; } if (DebugMode) { set.UnlockedAreas = set.MaxArea; } } foreach (AreaStats area in areas) { area.CleanCheckpoints(); } } // Assign SaveData for the level sets in the recycle bin to prevent crashes. foreach (LevelSetStats set in LevelSetRecycleBin) { set.SaveData = this; } // Order the levelsets to appear just as their areas appear in AreaData.Areas LevelSets.Sort((set1, set2) => set1.AreaOffset.CompareTo(set2.AreaOffset)); // If there is no mod progress, carry over any progress from vanilla saves. if (LastArea_Safe.ID == 0) { LastArea_Safe = LastArea_Unsafe; } if (CurrentSession_Safe == null) { CurrentSession_Safe = CurrentSession_Unsafe; } // Trick unmodded instances of Celeste to thinking that we last selected prologue / played no level. LastArea_Unsafe = AreaKey.Default; CurrentSession_Unsafe = null; // Fix areas with missing SID (f.e. deleted or renamed maps). if (AreaData.Get(LastArea) == null) { LastArea = AreaKey.Default; } // Fix out of bounds areas. if (LastArea.ID < 0 || LastArea.ID >= AreaData.Areas.Count) { LastArea = AreaKey.Default; } if (string.IsNullOrEmpty(TheoSisterName)) { TheoSisterName = Dialog.Clean("THEO_SISTER_NAME", null); if (Name.IndexOf(TheoSisterName, StringComparison.InvariantCultureIgnoreCase) >= 0) { TheoSisterName = Dialog.Clean("THEO_SISTER_ALT_NAME", null); } } AssistModeChecks(); if (Version != null) { Version v = new Version(Version); if (v < new Version(1, 2, 1, 1)) { for (int id = 0; id < Areas_Unsafe.Count; id++) { AreaStats area = Areas_Unsafe[id]; if (area == null) { continue; } for (int modei = 0; modei < area.Modes.Length; modei++) { AreaModeStats mode = area.Modes[modei]; if (mode == null) { continue; } if (mode.BestTime > 0L) { mode.SingleRunCompleted = true; } mode.BestTime = 0L; mode.BestFullClearTime = 0L; } } } } }
[PatchMapDataLoader] // Manually manipulate the method via MonoModRules private void Load() { // reset those fields to prevent them from stacking up when reloading the map. DetectedStrawberries = 0; DetectedHeartGem = false; DetectedRemixNotes = false; Goldenberries = new List <EntityData>(); DashlessGoldenberries = new List <EntityData>(); DetectedCassette = false; DetectedStrawberriesIncludingUntracked = 0; try { orig_Load(); foreach (LevelData level in Levels) { foreach (EntityData entity in level.Entities) { if (entity.Name == "memorialTextController") // aka "dashless golden" { DashlessGoldenberries.Add(entity); } } } AreaData area = AreaData.Get(Area); AreaData parentArea = AreaDataExt.Get(area.GetMeta()?.Parent); ModeProperties parentMode = parentArea?.Mode?.ElementAtOrDefault((int)Area.Mode); if (parentMode != null) { MapData parentMapData = parentMode.MapData; if (parentMapData == null) { Mod.Logger.Log(LogLevel.Warn, "misc", $"Failed auto-assigning data from {Area} to its unloaded parent"); return; } parentMapData.Strawberries.AddRange(Strawberries); // Recount everything berry-related for the parent map data, just like in orig_Load. parentMode.TotalStrawberries = 0; parentMode.StartStrawberries = 0; parentMode.StrawberriesByCheckpoint = new EntityData[10, 25]; for (int i = 0; parentMode.Checkpoints != null && i < parentMode.Checkpoints.Length; i++) { if (parentMode.Checkpoints[i] != null) { parentMode.Checkpoints[i].Strawberries = 0; } } foreach (EntityData entity in parentMapData.Strawberries) { if (!entity.Bool("moon")) { int checkpointID = entity.Int("checkpointIDParented", entity.Int("checkpointID")); int order = entity.Int("order"); if (_GrowAndGet(ref parentMode.StrawberriesByCheckpoint, checkpointID, order) == null) { parentMode.StrawberriesByCheckpoint[checkpointID, order] = entity; } if (checkpointID == 0) { parentMode.StartStrawberries++; } else if (parentMode.Checkpoints != null) { parentMode.Checkpoints[checkpointID - 1].Strawberries++; } parentMode.TotalStrawberries++; } } } } catch (Exception e) { Mod.Logger.Log(LogLevel.Warn, "misc", $"Failed loading MapData {Area}"); e.LogDetailed(); } }
public new void AfterInitialize() { // Vanilla / new saves don't have the LevelSets list. if (LevelSets == null) { LevelSets = new List <LevelSetStats>(); } if (Areas_Unsafe == null) { Areas_Unsafe = new List <AreaStats>(); } // Add missing LevelSetStats. foreach (AreaData area in AreaData.Areas) { string set = area.GetLevelSet(); if (!LevelSets.Exists(other => other.Name == set)) { LevelSets.Add(new LevelSetStats { Name = set, UnlockedAreas = set == "Celeste" ? UnlockedAreas_Unsafe : 0 }); } } // Fill each LevelSetStats with its areas. for (int lsi = 0; lsi < LevelSets.Count; lsi++) { LevelSetStats set = LevelSets[lsi]; set.SaveData = this; List <AreaStats> areas = set.Areas; if (set.Name == "Celeste") { areas = Areas_Unsafe; } int offset = set.AreaOffset; if (offset == -1) { // LevelSet gone - let's remove it to prevent any unwanted accesses. // We previously kept the LevelSetStats around in case the levelset resurfaces later on, but as it turns out, this breaks some stuff. LevelSets.RemoveAt(lsi); lsi--; continue; } // Refresh all stat IDs based on their SIDs, sort, fill and remove leftovers. // Temporarily use ID_Unsafe; later ID_Safe to ID_Unsafe to resync the SIDs. // This keeps the stats bound to their SIDs, not their indices, while removing non-existent areas. int count = AreaData.Areas.Count(other => other.GetLevelSet() == set.Name); // Fix IDs for (int i = 0; i < areas.Count; i++) { ((patch_AreaStats)areas[i]).ID_Unsafe = AreaDataExt.Get(areas[i])?.ID ?? int.MaxValue; } // Sort areas.Sort((a, b) => ((patch_AreaStats)a).ID_Unsafe - ((patch_AreaStats)b).ID_Unsafe); // Remove leftovers while (areas.Count > 0 && ((patch_AreaStats)areas[areas.Count - 1]).ID_Unsafe == int.MaxValue) { areas.RemoveAt(areas.Count - 1); } // Fill gaps for (int i = 0; i < count; i++) { if (i >= areas.Count || ((patch_AreaStats)areas[i]).ID_Unsafe != offset + i) { areas.Insert(i, new AreaStats(offset + i)); } } // Resync SIDs for (int i = 0; i < areas.Count; i++) { ((patch_AreaStats)areas[i]).ID_Safe = ((patch_AreaStats)areas[i]).ID_Unsafe; } int lastCompleted = -1; for (int i = 0; i < count; i++) { if (areas[i].Modes[0].Completed) { lastCompleted = i; } } if (set.Name == "Celeste") { if (UnlockedAreas_Unsafe < lastCompleted + 1 && set.MaxArea >= lastCompleted + 1) { UnlockedAreas_Unsafe = lastCompleted + 1; } if (DebugMode) { UnlockedAreas_Unsafe = set.MaxArea; } } else { if (set.UnlockedAreas < lastCompleted + 1 && set.MaxArea >= lastCompleted + 1) { set.UnlockedAreas = lastCompleted + 1; } if (DebugMode) { set.UnlockedAreas = set.MaxArea; } } foreach (AreaStats area in areas) { area.CleanCheckpoints(); } } // Order the levelsets to appear just as their areas appear in AreaData.Areas LevelSets.OrderBy(set => set.AreaOffset); // Carry over any progress from vanilla saves. if (LastArea_Unsafe.ID != 0) { LastArea_Safe = LastArea_Unsafe; } if (CurrentSession_Unsafe != null) { CurrentSession_Safe = CurrentSession_Unsafe; } // Trick unmodded instances of Celeste to thinking that we last selected prologue / played no level. LastArea_Unsafe = AreaKey.Default; CurrentSession_Unsafe = null; // Fix out of bounds areas. if (LastArea.ID < 0 || LastArea.ID >= AreaData.Areas.Count) { LastArea = AreaKey.Default; } // Debug mode shouldn't auto-enter into a level. if (DebugMode) { CurrentSession = null; } if (string.IsNullOrEmpty(TheoSisterName)) { TheoSisterName = Dialog.Clean("THEO_SISTER_NAME", null); if (Name.IndexOf(TheoSisterName, StringComparison.InvariantCultureIgnoreCase) >= 0) { TheoSisterName = Dialog.Clean("THEO_SISTER_ALT_NAME", null); } } AssistModeChecks(); if (Version != null) { Version v = new Version(Version); if (v < new Version(1, 2, 1, 1)) { for (int id = 0; id < Areas_Unsafe.Count; id++) { AreaStats area = Areas_Unsafe[id]; if (area == null) { continue; } for (int modei = 0; modei < area.Modes.Length; modei++) { AreaModeStats mode = area.Modes[modei]; if (mode == null) { continue; } if (mode.BestTime > 0L) { mode.SingleRunCompleted = true; } mode.BestTime = 0L; mode.BestFullClearTime = 0L; } } } } }