private static void CmdPrintCounts() { if (SaveData.Instance == null) { Engine.Commands.Log("No save loaded!"); return; } LevelSetStats stats = SaveData.Instance.GetLevelSetStats(); Engine.Commands.Log($"** Level Set Stats: {stats.Name} **"); Engine.Commands.Log($"Max strawberry count = {stats.MaxStrawberries}"); Engine.Commands.Log($"Max golden strawberry count = {stats.MaxGoldenStrawberries}"); Engine.Commands.Log($"Max strawberry count including untracked = {stats.MaxStrawberriesIncludingUntracked}"); Engine.Commands.Log($"Max cassettes = {stats.MaxCassettes}"); Engine.Commands.Log($"Max crystal hearts = {stats.MaxHeartGems}"); Engine.Commands.Log($"Max crystal hearts excluding C-sides = {stats.MaxHeartGemsExcludingCSides}"); Engine.Commands.Log($"Chapter count = {stats.MaxCompletions}"); Engine.Commands.Log("===="); Engine.Commands.Log($"Owned strawberries = {stats.TotalStrawberries}"); Engine.Commands.Log($"Owned golden strawberries = {stats.TotalGoldenStrawberries}"); Engine.Commands.Log($"Owned cassettes = {stats.TotalCassettes}"); Engine.Commands.Log($"Owned crystal hearts = {stats.TotalHeartGems}"); Engine.Commands.Log($"Completed chapters = {stats.TotalCompletions}"); Engine.Commands.Log("===="); Engine.Commands.Log($"Completion percent = {stats.CompletionPercent}"); }
private IEnumerator EnterFirstAreaRoutine() { // Replace ID 0 with SaveData.Instance.LastArea.ID Overworld overworld = fileSelect.Overworld; AreaData area = AreaData.Areas[SaveData.Instance.LastArea.ID]; if (area.GetLevelSet() != "Celeste") { // Pretend that we've beaten Prologue. LevelSetStats stats = SaveData.Instance.GetLevelSetStatsFor("Celeste"); stats.UnlockedAreas = 1; stats.AreasIncludingCeleste[0].Modes[0].Completed = true; } yield return(fileSelect.Leave(null)); yield return(overworld.Mountain.EaseCamera(0, area.MountainIdle)); yield return(0.3f); overworld.Mountain.EaseCamera(0, area.MountainZoom, 1f); yield return(0.4f); area.Wipe(overworld, false, null); overworld.RendererList.UpdateLists(); overworld.RendererList.MoveToFront(overworld.Snow); yield return(0.5f); LevelEnter.Go(new Session(SaveData.Instance.LastArea), false); }
public void ctor(int index, OuiFileSelect fileSelect, SaveData data) { // Temporarily set the current save data to the file slot's save data. // This enables filtering the areas by the save data's current levelset. SaveData prev = SaveData.Instance; SaveData.Instance = data; orig_ctor(index, fileSelect, data); LevelSetStats stats = data?.GetLevelSetStats(); if (stats != null) { StrawberriesCounter strawbs = Strawberries; strawbs.Amount = stats.TotalStrawberries; strawbs.OutOf = stats.MaxStrawberries; strawbs.ShowOutOf = stats.Name != "Celeste" || strawbs.OutOf <= 0; } SaveData.Instance = prev; }
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; } } } } }
public new void AfterInitialize() { // Vanilla / new saves don't have the LevelSets list. if (LevelSets == null) { LevelSets = new List <LevelSetStats>(); } // 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; } int count = AreaData.Areas.Count(other => other.GetLevelSet() == set.Name); while (areas.Count < count) { areas.Add(new AreaStats(offset + areas.Count)); } while (areas.Count > count) { areas.RemoveAt(areas.Count - 1); } for (int i = 0; i < count; i++) { areas[i].ID = offset + i; areas[i].SetSID(AreaData.Get(offset + i).GetSID()); } 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); } } if (!AssistMode) { Assists = default(Assists); } // Disable the GameSpeed clamping - allow mods to "break" this. /* * if (Assists.GameSpeed < 5 || Assists.GameSpeed > 10) { * Assists.GameSpeed = 10; * } */ Everest.Invoke("LoadSaveData", FileSlot); }
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; } } } } }
public new void Show() { // Temporarily set the current save data to the file slot's save data. // This enables filtering the areas by the save data's current levelset. SaveData prev = SaveData.Instance; SaveData.Instance = SaveData; LevelSetStats stats = SaveData?.GetLevelSetStats(); if (stats != null) { StrawberriesCounter strawbs = Strawberries; strawbs.Amount = stats.TotalStrawberries; strawbs.OutOf = stats.MaxStrawberries; strawbs.ShowOutOf = stats.Name != "Celeste" || strawbs.OutOf <= 0; strawbs.CanWiggle = false; if (stats.Name == "Celeste") { // never mess with vanilla. maxStrawberryCount = 175; maxGoldenStrawberryCount = 25; // vanilla is wrong (there are 26 including dashless), but don't mess with vanilla. maxStrawberryCountIncludingUntracked = 202; maxCassettes = 8; maxCrystalHeartsExcludingCSides = 16; maxCrystalHearts = 24; summitStamp = SaveData.Areas[7].Modes[0].Completed; farewellStamp = SaveData.Areas[10].Modes[0].Completed; } else { // compute the counts for the current level set. maxStrawberryCount = stats.MaxStrawberries; maxGoldenStrawberryCount = stats.MaxGoldenStrawberries; maxStrawberryCountIncludingUntracked = stats.MaxStrawberriesIncludingUntracked; maxCassettes = stats.MaxCassettes; maxCrystalHearts = stats.MaxHeartGems; maxCrystalHeartsExcludingCSides = stats.MaxHeartGemsExcludingCSides; // summit stamp is displayed if we finished all areas that are not interludes. (TotalCompletions filters interludes out.) summitStamp = stats.TotalCompletions >= stats.MaxCompletions; farewellStamp = false; // what is supposed to be Farewell in mod campaigns anyway?? } // save the values from the current level set. They will be patched in instead of SaveData.TotalXX. totalGoldenStrawberries = stats.TotalGoldenStrawberries; // The value saved on the file is global for all level sets. totalHeartGems = stats.TotalHeartGems; // this counts from all level sets. totalCassettes = stats.TotalCassettes; // this relies on SaveData.Instance. // redo what is done on the constructor. This keeps the area name and stats up-to-date with the latest area. FurthestArea = SaveData.UnlockedAreas; Cassettes.Clear(); HeartGems.Clear(); foreach (AreaStats areaStats in SaveData.Areas) { if (areaStats.ID > SaveData.UnlockedAreas) { break; } if (!AreaData.Areas[areaStats.ID].Interlude && AreaData.Areas[areaStats.ID].CanFullClear) { bool[] hearts = new bool[3]; for (int i = 0; i < hearts.Length; i++) { hearts[i] = areaStats.Modes[i].HeartGem; } Cassettes.Add(areaStats.Cassette); HeartGems.Add(hearts); } } } SaveData.Instance = prev; orig_Show(); }