private bool FixReturnFromAltSide(On.Celeste.OuiChapterPanel.orig_IsStart orig, OuiChapterPanel self, Overworld overworld, Overworld.StartMode start) { AreaData newArea = null; AreaData old; if (start == Overworld.StartMode.AreaComplete || start == Overworld.StartMode.AreaQuit) { AreaData area = AreaData.Get(SaveData.Instance.LastArea.ID); old = area; var meta = GetMetaForAreaData(area); if (meta?.AltSideData.IsAltSide ?? false) { area = AreaData.Get(meta.AltSideData.For) ?? area; if (area != null) { newArea = area; SaveData.Instance.LastArea.ID = area.ID; int returningSide = 0; //last unlocked mode if (!area.Interlude_Safe && area.HasMode(AreaMode.BSide) && (SaveData.Instance.Areas_Safe[area.ID].Cassette || SaveData.Instance.DebugMode || SaveData.Instance.CheatMode)) { returningSide++; } if (!area.Interlude_Safe && area.HasMode(AreaMode.CSide) && SaveData.Instance.UnlockedModes >= 3) { returningSide++; } var asideAltSideMeta = GetMetaForAreaData(area); foreach (var mode in asideAltSideMeta.Sides) { if (!mode.OverrideVanillaSideData) { returningSide++; if (mode.Map.Equals(old.GetSID())) { break; } } } returningAltSide = returningSide; SaveData.Instance.LastArea_Safe.ID = old.ID; } } } var ret = orig(self, overworld, start); if (newArea != null) { //self.Data = newArea; SaveData.Instance.LastArea_Safe.ID = newArea.ID; shouldResetStats = false; resetMethod.Invoke(self, new object[] { }); shouldResetStats = true; } returningAltSide = -1; return(ret); }
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); } } } } }
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); } } } } }
public static new void Load() { orig_Load(); // assign SIDs and CheckpointData.Area for vanilla maps. foreach (AreaData area in Areas) { area.SetSID("Celeste/" + area.Mode[0].Path); for (int modeId = 0; modeId < area.Mode.Length; modeId++) { ModeProperties mode = area.Mode[modeId]; if (mode?.Checkpoints == null) { continue; } foreach (CheckpointData checkpoint in mode.Checkpoints) { checkpoint.SetArea(area.ToKey((AreaMode)modeId)); } } } // Separate array as we sort it afterwards. List <AreaData> modAreas = new List <AreaData>(); lock (Everest.Content.Map) { foreach (ModAsset asset in Everest.Content.Map.Values.Where(asset => asset.Type == typeof(AssetTypeMap))) { string path = asset.PathVirtual.Substring(5); AreaData area = new AreaData(); // Default values. area.SetSID(path); area.Name = path; area.Icon = "areas/" + path.ToLowerInvariant(); if (!GFX.Gui.Has(area.Icon)) { area.Icon = "areas/null"; } area.Interlude = false; area.CanFullClear = true; area.TitleBaseColor = Calc.HexToColor("6c7c81"); area.TitleAccentColor = Calc.HexToColor("2f344b"); area.TitleTextColor = Color.White; area.IntroType = Player.IntroTypes.WakeUp; area.Dreaming = false; area.ColorGrade = null; area.Mode = new ModeProperties[] { new ModeProperties { Inventory = PlayerInventory.Default, AudioState = new AudioState(SFX.music_city, SFX.env_amb_00_main) } }; area.Wipe = (Scene scene, bool wipeIn, Action onComplete) => new AngledWipe(scene, wipeIn, onComplete); area.DarknessAlpha = 0.05f; area.BloomBase = 0f; area.BloomStrength = 1f; area.Jumpthru = "wood"; area.CassseteNoteColor = Calc.HexToColor("33a9ee"); area.CassetteSong = SFX.cas_01_forsaken_city; // Custom values can be set via the MapMeta. MapMeta meta = new MapMeta(); meta.ApplyTo(area); MapMeta metaLoaded = asset.GetMeta <MapMeta>(); if (metaLoaded != null) { area.SetMeta(null); metaLoaded.ApplyTo(area); meta = metaLoaded; } if (string.IsNullOrEmpty(area.Mode[0].Path)) { area.Mode[0].Path = asset.PathVirtual.Substring(5); } // Some of the game's code checks for [1] / [2] explicitly. // Let's just provide null modes to fill any gaps. meta.Modes = meta.Modes ?? new MapMetaModeProperties[3]; if (meta.Modes.Length < 3) { MapMetaModeProperties[] larger = new MapMetaModeProperties[3]; for (int i = 0; i < meta.Modes.Length; i++) { larger[i] = meta.Modes[i]; } meta.Modes = larger; } if (area.Mode.Length < 3) { ModeProperties[] larger = new ModeProperties[3]; for (int i = 0; i < area.Mode.Length; i++) { larger[i] = area.Mode[i]; } area.Mode = larger; } // Celeste levelset always appears first. if (area.GetLevelSet() == "Celeste") { Areas.Add(area); } else { modAreas.Add(area); } // Some special handling. area.OnLevelBegin = (level) => { MapMeta levelMeta = AreaData.Get(level.Session).GetMeta(); MapMetaModeProperties levelMetaMode = level.Session.MapData.GetMeta(); if (levelMetaMode?.SeekerSlowdown ?? false) { level.Add(new SeekerEffectsController()); } }; } } // Merge modAreas into Areas. Areas.AddRange(modAreas); // Find duplicates and remove any earlier copies. for (int i = 0; i < Areas.Count; i++) { AreaData area = Areas[i]; int otherIndex = Areas.FindIndex(other => other.GetSID() == area.GetSID()); if (otherIndex < i) { Areas[otherIndex] = area; Areas.RemoveAt(i); i--; } } // Sort areas. Areas.Sort(AreaComparison); // Remove AreaDatas which are now a mode of another AreaData. // This can happen late as the map data (.bin) can contain additional metadata. for (int i = 0; i < Areas.Count; i++) { AreaData area = Areas[i]; string path = area.Mode[0].Path; int otherIndex = Areas.FindIndex(other => other.Mode.Any(otherMode => otherMode?.Path == path)); if (otherIndex != -1 && otherIndex != i) { Areas.RemoveAt(i); i--; continue; } int? order; AreaMode side; string name; ParseName(path, out order, out side, out name); // Also check for .bins possibly belonging to A side .bins by their path and lack of existing modes. for (int ii = 0; ii < Areas.Count; ii++) { AreaData other = Areas[ii]; int? otherOrder; AreaMode otherSide; string otherName; ParseName(other.Mode[0].Path, out otherOrder, out otherSide, out otherName); if (area.GetLevelSet() == other.GetLevelSet() && order == otherOrder && name == otherName && side != otherSide && !other.HasMode(side)) { if (other.Mode[(int)side] == null) { other.Mode[(int)side] = new ModeProperties { Inventory = PlayerInventory.Default, AudioState = new AudioState(SFX.music_city, SFX.env_amb_00_main) } } ; other.Mode[(int)side].Path = path; Areas.RemoveAt(i); i--; break; } } } for (int i = 0; i < Areas.Count; i++) { AreaData area = Areas[i]; area.ID = i; // Clean up non-existing modes. int modei = 0; for (; modei < area.Mode.Length; modei++) { ModeProperties mode = area.Mode[modei]; if (mode == null || string.IsNullOrEmpty(mode.Path)) { break; } } Array.Resize(ref area.Mode, modei); Logger.Log("AreaData", $"{i}: {area.GetSID()} - {area.Mode.Length} sides"); // Update old MapData areas and load any new areas. // Add the A side MapData or update its area key. if (area.Mode[0].MapData != null) { area.Mode[0].MapData.Area = area.ToKey(); } else { area.Mode[0].MapData = new MapData(area.ToKey()); } if (area.IsInterludeUnsafe()) { continue; } // A and (some) B sides have PoemIDs. Can be overridden via empty PoemID. if (area.Mode[0].PoemID == null) { area.Mode[0].PoemID = area.GetSID().DialogKeyify() + "_A"; } if (area.Mode.Length > 1 && area.Mode[1] != null && area.Mode[1].PoemID == null) { area.Mode[1].PoemID = area.GetSID().DialogKeyify() + "_B"; } // Update all other existing mode's area keys. for (int mode = 1; mode < area.Mode.Length; mode++) { if (area.Mode[mode] == null) { continue; } if (area.Mode[mode].MapData != null) { area.Mode[mode].MapData.Area = area.ToKey((AreaMode)mode); } else { area.Mode[mode].MapData = new MapData(area.ToKey((AreaMode)mode)); } } } // Load custom mountains // This needs to be done after areas are loaded because it depends on the MapMeta MTNExt.LoadMod(); MTNExt.LoadModData(); }
private void ReloadItems() { foreach (TextMenu.Item item in items) { menu.Remove(item); } items.Clear(); string filterSet = null; if (type == 0) { filterSet = "Celeste"; } else if (type >= 2) { filterSet = sets[type - 2]; } string lastLevelSet = null; LevelSetStats levelSetStats = null; int levelSetAreaOffset = 0; int levelSetUnlockedAreas = int.MaxValue; int levelSetUnlockedModes = int.MaxValue; string name; List <AreaStats> areaStatsAll = SaveData.Instance.Areas; for (int i = 0; i < AreaData.Areas.Count; i++) { AreaData area = AreaData.Areas[i]; if (!area.HasMode((AreaMode)side)) { continue; } string levelSet = area.GetLevelSet(); if ((filterSet == null && levelSet == "Celeste") || (filterSet != null && filterSet != levelSet)) { continue; } if (lastLevelSet != levelSet) { lastLevelSet = levelSet; levelSetStats = SaveData.Instance.GetLevelSetStatsFor(levelSet); levelSetAreaOffset = levelSetStats.AreaOffset; levelSetUnlockedAreas = levelSetStats.UnlockedAreas; levelSetUnlockedModes = levelSetStats.UnlockedModes; if (levelSet != "Celeste") { name = DialogExt.CleanLevelSet(levelSet); TextMenuExt.SubHeaderExt levelSetHeader = new TextMenuExt.SubHeaderExt(name); levelSetHeader.Alpha = 0f; menu.Add(levelSetHeader); items.Add(levelSetHeader); } } name = area.Name; name = name.DialogCleanOrNull() ?? name.SpacedPascalCase(); TextMenuExt.ButtonExt button = new TextMenuExt.ButtonExt(name); button.Alpha = 0f; if (area.Icon != "areas/null") { button.Icon = area.Icon; } button.IconWidth = 64f; if (levelSet == "Celeste" && i > levelSetAreaOffset + levelSetUnlockedAreas) { button.Disabled = true; } if (side == 1 && !areaStatsAll[i].Cassette) { button.Disabled = true; } if (side >= 2 && levelSetUnlockedModes < (side + 1)) { button.Disabled = true; } menu.Add(button.Pressed(() => { Inspect(area, (AreaMode)side); })); items.Add(button); } // Do this afterwards as the menu has now properly updated its size. for (int i = 0; i < items.Count; i++) { Add(new Coroutine(FadeIn(i, items[i]))); } if (menu.Height > menu.ScrollableMinSize) { menu.Position.Y = menu.ScrollTargetY; } }
private void ReloadItems() { foreach (TextMenu.Item item in items) { menu.Remove(item); } items.Clear(); string filterSet = null; if (type == 0) { filterSet = "Celeste"; } else if (type >= 3) { filterSet = sets[type - 3]; } string lastLevelSet = null; LevelSetStats levelSetStats = null; int levelSetAreaOffset = 0; int levelSetUnlockedAreas = int.MaxValue; int levelSetUnlockedModes = int.MaxValue; string name; SaveData save = SaveData.Instance; List <AreaStats> areaStatsAll = save.Areas; for (int i = 0; i < AreaData.Areas.Count; i++) { AreaData area = AreaData.Get(i); if (area == null || !area.HasMode((AreaMode)side)) { continue; } // TODO: Make subchapters hidden by default in the map list, even in debug mode. if (!save.DebugMode && !string.IsNullOrEmpty(area.GetMeta()?.Parent)) { continue; } string levelSet = area.GetLevelSet(); if (type != 1 && ((filterSet == null && levelSet == "Celeste") || (filterSet != null && filterSet != levelSet))) { continue; } name = area.Name; name = name.DialogCleanOrNull() ?? name.SpacedPascalCase(); if (lastLevelSet != levelSet) { lastLevelSet = levelSet; levelSetStats = SaveData.Instance.GetLevelSetStatsFor(levelSet); levelSetAreaOffset = levelSetStats.AreaOffset; levelSetUnlockedAreas = levelSetStats.UnlockedAreas; levelSetUnlockedModes = levelSetStats.UnlockedModes; string setname = DialogExt.CleanLevelSet(levelSet); TextMenuExt.SubHeaderExt levelSetHeader = new TextMenuExt.SubHeaderExt(setname); levelSetHeader.Alpha = 0f; menu.Add(levelSetHeader); items.Add(levelSetHeader); } TextMenuExt.ButtonExt button = new TextMenuExt.ButtonExt(name); button.Alpha = 0f; if (area.Icon != "areas/null") { button.Icon = area.Icon; } button.IconWidth = 64f; if (levelSet == "Celeste" && i > levelSetAreaOffset + levelSetUnlockedAreas) { button.Disabled = true; } if (side == 1 && !areaStatsAll[i].Cassette) { button.Disabled = true; } if (side >= 2 && levelSetUnlockedModes < (side + 1)) { button.Disabled = true; } menu.Add(button.Pressed(() => { Inspect(area, (AreaMode)side); })); items.Add(button); } // compute a delay so that options don't take more than a second to show up if many mods are installed. float delayBetweenOptions = 0.03f; if (items.Count > 0) { delayBetweenOptions = Math.Min(0.03f, 1f / items.Count); } // Do this afterwards as the menu has now properly updated its size. for (int i = 0; i < items.Count; i++) { Add(new Coroutine(FadeIn(i, delayBetweenOptions, items[i]))); } if (menu.Height > menu.ScrollableMinSize) { menu.Position.Y = menu.ScrollTargetY; } }