Exemple #1
0
        public new void CleanCheckpoints()
        {
            AreaData area = AreaData.Get(ID);

            for (int i = 0; i < Modes.Length; i++)
            {
                AreaMode       areaMode       = (AreaMode)i;
                AreaModeStats  areaModeStats  = Modes[i];
                ModeProperties modeProperties = null;
                if (area.HasMode(areaMode))
                {
                    modeProperties = area.Mode[i];
                }

                HashSet <string> checkpoints = new HashSet <string>(areaModeStats.Checkpoints);
                areaModeStats.Checkpoints.Clear();

                if (modeProperties != null && modeProperties.Checkpoints != null)
                {
                    foreach (CheckpointData checkpointData in modeProperties.Checkpoints)
                    {
                        if (checkpoints.Contains(checkpointData.Level))
                        {
                            areaModeStats.Checkpoints.Add(checkpointData.Level);
                        }
                    }
                }
            }
        }
Exemple #2
0
        private static void OnChapterPanelUpdateStats(On.Celeste.OuiChapterPanel.orig_UpdateStats orig, OuiChapterPanel self, bool wiggle,
                                                      bool?overrideStrawberryWiggle, bool?overrideDeathWiggle, bool?overrideHeartWiggle)
        {
            orig(self, wiggle, overrideStrawberryWiggle, overrideDeathWiggle, overrideHeartWiggle);

            if (Engine.Scene == overworldWrapper?.Scene)
            {
                AreaModeStats areaModeStats = self.DisplayedStats.Modes[(int)self.Area.Mode];
                DeathsCounter deathsCounter = new DynData <OuiChapterPanel>(self).Get <DeathsCounter>("deaths");
                deathsCounter.Visible = areaModeStats.Deaths > 0 && !AreaData.Get(self.Area).Interlude_Safe;

                // mod the death icon
                string pathToSkull = "CollabUtils2/skulls/" + self.Area.GetLevelSet();
                if (GFX.Gui.Has(pathToSkull))
                {
                    new DynData <DeathsCounter>(deathsCounter)["icon"] = GFX.Gui[pathToSkull];
                }
            }


            if (isPanelShowingLobby(self) || Engine.Scene == overworldWrapper?.Scene)
            {
                // turn strawberry counter into golden if there is no berry in the map
                if (AreaData.Get(self.Area).Mode[0].TotalStrawberries == 0)
                {
                    StrawberriesCounter strawberriesCounter = new DynData <OuiChapterPanel>(self).Get <StrawberriesCounter>("strawberries");
                    strawberriesCounter.Golden    = true;
                    strawberriesCounter.ShowOutOf = false;
                }
            }
        }
Exemple #3
0
        public new void CleanCheckpoints()
        {
            if (string.IsNullOrEmpty(SID) && (ID_Unsafe < 0 || AreaData.Areas.Count <= ID_Unsafe))
            {
                throw new Exception($"SaveData contains invalid AreaStats with no SID and out-of-range ID of {ID_Unsafe} / {AreaData.Areas.Count}");
            }

            AreaData area = AreaData.Get(ID);

            for (int i = 0; i < Modes.Length; i++)
            {
                AreaMode       areaMode       = (AreaMode)i;
                AreaModeStats  areaModeStats  = Modes[i];
                ModeProperties modeProperties = null;
                if (area.HasMode(areaMode))
                {
                    modeProperties = area.Mode[i];
                }

                HashSet <string> checkpoints = new HashSet <string>(areaModeStats.Checkpoints);
                areaModeStats.Checkpoints.Clear();

                if (modeProperties != null && modeProperties.Checkpoints != null)
                {
                    foreach (CheckpointData checkpointData in modeProperties.Checkpoints)
                    {
                        if (checkpoints.Contains(checkpointData.Level))
                        {
                            areaModeStats.Checkpoints.Add(checkpointData.Level);
                        }
                    }
                }
            }
        }
Exemple #4
0
        private static HashSet <string> _GetCheckpoints(SaveData save, AreaKey area)
        {
            // TODO: Maybe switch back to using SaveData.GetCheckpoints in the future?

            if (Celeste.PlayMode == Celeste.PlayModes.Event)
            {
                return(new HashSet <string>());
            }

            HashSet <string> set;

            AreaData       areaData = AreaData.Areas[area.ID];
            ModeProperties mode     = areaData.Mode[(int)area.Mode];

            if (save.DebugMode || save.CheatMode)
            {
                set = new HashSet <string>();
                if (mode.Checkpoints != null)
                {
                    foreach (CheckpointData cp in mode.Checkpoints)
                    {
                        set.Add($"{(AreaData.Get(cp.GetArea()) ?? areaData).GetSID()}|{cp.Level}");
                    }
                }
                return(set);
            }

            AreaModeStats areaModeStats = save.Areas[area.ID].Modes[(int)area.Mode];

            set = areaModeStats.Checkpoints;

            // Perform the same "cleanup" as SaveData.GetCheckpoints, but copy the set when adding area SIDs.
            if (mode == null)
            {
                set.Clear();
                return(set);
            }

            set.RemoveWhere((string a) => !mode.Checkpoints.Any((CheckpointData b) => b.Level == a));
            AreaData[] subs = AreaData.Areas.Where(other =>
                                                   other.GetMeta()?.Parent == areaData.GetSID() &&
                                                   other.HasMode(area.Mode)
                                                   ).ToArray();
            return(new HashSet <string>(set.Select(s => {
                foreach (AreaData sub in subs)
                {
                    foreach (CheckpointData cp in sub.Mode[(int)area.Mode].Checkpoints)
                    {
                        if (cp.Level == s)
                        {
                            return $"{sub.GetSID()}|{s}";
                        }
                    }
                }
                return s;
            })));
        }
Exemple #5
0
        private static int OnChapterPanelGetModeHeight(On.Celeste.OuiChapterPanel.orig_GetModeHeight orig, OuiChapterPanel self)
        {
            // force the chapter panel to be bigger if deaths > 0 (we force deaths to display even if the player didn't beat the map) or if there is a speed berry PB,
            // because in these cases we have stuff to display in the chapter panel, and vanilla wouldn't display anything.
            AreaModeStats areaModeStats = self.RealStats.Modes[(int)self.Area.Mode];

            if (Engine.Scene == overworldWrapper?.Scene && !AreaData.Get(self.Area).Interlude_Safe &&
                (areaModeStats.Deaths > 0 || CollabModule.Instance.SaveData.SpeedBerryPBs.ContainsKey(self.Area.GetSID())))
            {
                return(540);
            }

            return(orig(self));
        }
        private static void onRegisterCompletion(On.Celeste.SaveData.orig_RegisterCompletion orig, SaveData self, Session session)
        {
            orig(self, session);

            AreaKey currentArea = session.Area;

            if (IsHeartSide(currentArea.GetSID()))
            {
                string lobby = GetLobbyForLevelSet(currentArea.GetLevelSet());
                if (lobby != null)
                {
                    // completing the heart side should also complete the lobby.
                    AreaModeStats areaModeStats = SaveData.Instance.Areas_Safe[AreaData.Get(lobby).ID].Modes[0];
                    areaModeStats.Completed = true;
                }
            }
        }
Exemple #7
0
        private static void OnChapterPanelUpdateStats(On.Celeste.OuiChapterPanel.orig_UpdateStats orig, OuiChapterPanel self, bool wiggle,
                                                      bool?overrideStrawberryWiggle, bool?overrideDeathWiggle, bool?overrideHeartWiggle)
        {
            orig(self, wiggle, overrideStrawberryWiggle, overrideDeathWiggle, overrideHeartWiggle);

            DeathsCounter deathsCounter = new DynData <OuiChapterPanel>(self).Get <DeathsCounter>("deaths");

            if (Engine.Scene == overworldWrapper?.Scene)
            {
                // within lobbies, death counts always show up, even if you didn't beat the map yet.
                AreaModeStats areaModeStats = self.DisplayedStats.Modes[(int)self.Area.Mode];
                deathsCounter.Visible = areaModeStats.Deaths > 0 && !AreaData.Get(self.Area).Interlude_Safe;
            }

            // mod the death icon: for the path, use the current level set, or for lobbies, the lobby's matching level set.
            string pathToSkull = "CollabUtils2/skulls/" + self.Area.GetLevelSet();

            if (LobbyHelper.GetLobbyLevelSet(self.Area.GetSID()) != null)
            {
                pathToSkull = "CollabUtils2/skulls/" + LobbyHelper.GetLobbyLevelSet(self.Area.GetSID());
            }
            if (GFX.Gui.Has(pathToSkull))
            {
                new DynData <DeathsCounter>(deathsCounter)["icon"] = GFX.Gui[pathToSkull];
            }
            new DynData <DeathsCounter>(deathsCounter)["modifiedByCollabUtils"] = GFX.Gui.Has(pathToSkull);


            if (isPanelShowingLobby(self) || Engine.Scene == overworldWrapper?.Scene)
            {
                // turn strawberry counter into golden if there only are golden berries in the map
                MapData mapData = AreaData.Get(self.Area).Mode[0].MapData;
                if (mapData.GetDetectedStrawberriesIncludingUntracked() == mapData.Goldenberries.Count)
                {
                    StrawberriesCounter strawberriesCounter = new DynData <OuiChapterPanel>(self).Get <StrawberriesCounter>("strawberries");
                    strawberriesCounter.Golden    = true;
                    strawberriesCounter.ShowOutOf = false;
                }
            }
        }
Exemple #8
0
        private static void CmdHearts(int amount = int.MaxValue, string levelSet = null)
        {
            patch_SaveData saveData = SaveData.Instance as patch_SaveData;

            if (saveData == null)
            {
                return;
            }

            if (string.IsNullOrEmpty(levelSet))
            {
                levelSet = saveData.GetLevelSet();
            }

            int num = 0;

            foreach (patch_AreaStats areaStats in saveData.Areas_Safe.Cast <patch_AreaStats>().Where(stats => stats.LevelSet == levelSet))
            {
                for (int i = 0; i < areaStats.Modes.Length; i++)
                {
                    if (AreaData.Get(areaStats.ID).Mode is not {
                    } mode || mode.Length <= i || mode[i]?.MapData == null)
                    {
                        continue;
                    }

                    AreaModeStats areaModeStats = areaStats.Modes[i];
                    if (num < amount)
                    {
                        areaModeStats.HeartGem = true;
                        num++;
                    }
                    else
                    {
                        areaModeStats.HeartGem = false;
                    }
                }
            }
        }
Exemple #9
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;
                        }
                    }
                }
            }
        }
Exemple #10
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;
                        }
                    }
                }
            }
        }