Esempio n. 1
0
        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"}");
        }
Esempio n. 2
0
        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);
        }
Esempio n. 3
0
        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));
        }
Esempio n. 4
0
        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);
        }
Esempio n. 5
0
        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;
                        }
                    }
                }
            }
        }
Esempio n. 6
0
        [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();
            }
        }
Esempio n. 7
0
        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;
                        }
                    }
                }
            }
        }