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(area.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); }
private static int AreaComparison(AreaData a, AreaData b) { string aSet = a.GetLevelSet(); string aSID = a.GetSID(); string bSet = b.GetLevelSet(); string bSID = b.GetSID(); // Celeste appears before everything else. if (aSet == "Celeste" && bSet != "Celeste") { return(-1); } if (aSet != "Celeste" && bSet == "Celeste") { return(1); } // Uncategorized appears after everything else. if (string.IsNullOrEmpty(aSet) && !string.IsNullOrEmpty(bSet)) { return(1); } if (!string.IsNullOrEmpty(aSet) && string.IsNullOrEmpty(bSet)) { return(-1); } // Compare level sets alphabetically. if (aSet != bSet) { return(string.Compare(aSet, bSet)); } int? aOrder; AreaMode aSide; string aName; ParseName(aSID, out aOrder, out aSide, out aName); int? bOrder; AreaMode bSide; string bName; ParseName(bSID, out bOrder, out bSide, out bName); if (aOrder != null && bOrder != null && aOrder.Value != bOrder.Value) { return(aOrder.Value - bOrder.Value); } if (aSide != bSide) { return(aSide - bSide); } return(string.Compare(aName, bName)); }
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; }))); }
public static RandoConfigFile Load(AreaData area) { String fullPath = "Config/" + area.GetSID() + ".rando"; Logger.Log("randomizer", $"Loading config from {fullPath}"); if (!Everest.Content.TryGet(fullPath, out ModAsset asset)) { Logger.Log("randomizer", "...not found"); return(null); } else { using (StreamReader reader = new StreamReader(asset.Stream)) { try { return(YamlHelper.Deserializer.Deserialize <RandoConfigFile>(reader)); } catch (YamlException e) { throw new Exception($"Error parsing {area.GetSID()}.rando: {e.Message}"); } } } }
private static int AreaComparison(AreaData a, AreaData b) { if (string.IsNullOrEmpty(a.GetLevelSet()) && !string.IsNullOrEmpty(b.GetLevelSet())) { return(1); } if (!string.IsNullOrEmpty(a.GetLevelSet()) && string.IsNullOrEmpty(b.GetLevelSet())) { return(-1); } return(string.Compare(a.GetSID(), b.GetSID())); }
public static RandoConfigFile Load(AreaData area) { String fullpath = "Config/" + area.GetSID() + ".rando"; if (!Everest.Content.TryGet(fullpath, out ModAsset asset)) { return(null); } else { try { using (StreamReader reader = new StreamReader(asset.Stream)) { return(YamlHelper.Deserializer.Deserialize <RandoConfigFile>(reader)); } } catch (Exception e) { Logger.Log("randomizer", $"Failed loading config for area {area}: {e.Message}"); return(null); } } }
private void ProcessLevels(BinaryPacker.Element levels) { AreaData area = AreaData.Get(Area); ModeProperties mode = area.Mode[(int)Area.Mode]; // Mod levels are... different. // levels.Children.Sort((a, b) => a.Attr("name").Replace("lvl_", "").CompareTo(b.Attr("name").Replace("lvl_", ""))); int checkpoint = 0; List <CheckpointData> checkpointsAuto = null; if (mode.Checkpoints == null) { checkpointsAuto = new List <CheckpointData>(); } int strawberry = 0; int strawberryInCheckpoint = 0; if (levels.Children != null) { foreach (BinaryPacker.Element level in levels.Children) { string[] levelTags = level.Attr("name").Split(':'); string levelName = levelTags[0]; if (levelName.StartsWith("lvl_")) { levelName = levelName.Substring(4); } level.SetAttr("name", "lvl_" + levelName); // lvl_ was optional before Celeste 1.2.5.0 made it mandatory. BinaryPacker.Element entities = level.Children.FirstOrDefault(el => el.Name == "entities"); BinaryPacker.Element triggers = level.Children.FirstOrDefault(el => el.Name == "triggers"); // Celeste 1.2.5.0 optimizes BinaryPacker, which causes some issues. // Let's "unoptimize" entities and triggers. if (entities == null) { level.Children.Add(entities = new BinaryPacker.Element { Name = "entities" }); } if (entities.Children == null) { entities.Children = new List <BinaryPacker.Element>(); } if (triggers == null) { level.Children.Add(triggers = new BinaryPacker.Element { Name = "triggers" }); } if (triggers.Children == null) { triggers.Children = new List <BinaryPacker.Element>(); } if (levelTags.Contains("checkpoint") || levelTags.Contains("cp")) { entities.Children.Add(new BinaryPacker.Element { Name = "checkpoint", Attributes = new Dictionary <string, object>() { { "x", "0" }, { "y", "0" } } }); } if (level.AttrBool("space")) { if (level.AttrBool("spaceSkipWrap") || levelTags.Contains("nospacewrap") || levelTags.Contains("nsw")) { entities.Children.Add(new BinaryPacker.Element { Name = "everest/spaceControllerBlocker" }); } if (level.AttrBool("spaceSkipGravity") || levelTags.Contains("nospacegravity") || levelTags.Contains("nsg")) { entities.Children.Add(new BinaryPacker.Element { Name = "everest/spaceController" }); level.SetAttr("space", false); } if (!levelTags.Contains("nospacefix") && !levelTags.Contains("nsf") && !triggers.Children.Any(el => el.Name == "cameraTargetTrigger") && !entities.Children.Any(el => el.Name == "everest/spaceControllerBlocker")) { // Camera centers tile-perfectly on uneven heights. int heightForCenter = level.AttrInt("height"); heightForCenter /= 8; if (heightForCenter % 2 == 0) { heightForCenter--; } heightForCenter *= 8; triggers.Children.Add(new BinaryPacker.Element { Name = "cameraTargetTrigger", Attributes = new Dictionary <string, object>() { { "x", 0f }, { "y", 0f }, { "width", level.AttrInt("width") }, { "height", level.AttrInt("height") }, { "yOnly", true }, { "lerpStrength", 1f } }, Children = new List <BinaryPacker.Element>() { new BinaryPacker.Element { Attributes = new Dictionary <string, object>() { { "x", 160f }, { "y", heightForCenter / 2f } } } } }); } } foreach (BinaryPacker.Element levelChild in level.Children) { switch (levelChild.Name) { case "entities": foreach (BinaryPacker.Element entity in levelChild.Children) { switch (entity.Name) { case "checkpoint": if (checkpointsAuto != null) { CheckpointData c = new CheckpointData( levelName, (area.GetSID() + "_" + levelName).DialogKeyify(), MapMeta.GetInventory(entity.Attr("inventory")), entity.Attr("dreaming") == "" ? area.Dreaming : entity.AttrBool("dreaming"), null ); int id = entity.AttrInt("checkpointID", -1); if (id == -1) { checkpointsAuto.Add(c); } else { while (checkpointsAuto.Count <= id) { checkpointsAuto.Add(null); } checkpointsAuto[id] = c; } } checkpoint++; strawberryInCheckpoint = 0; break; case "cassette": if (area.CassetteCheckpointIndex == 0) { area.CassetteCheckpointIndex = checkpoint; } break; case "strawberry": if (entity.AttrInt("checkpointID", -1) == -1) { entity.SetAttr("checkpointID", checkpoint); } if (entity.AttrInt("order", -1) == -1) { entity.SetAttr("order", strawberryInCheckpoint); } strawberry++; strawberryInCheckpoint++; break; } } break; } } } } if (mode.Checkpoints == null) { mode.Checkpoints = checkpointsAuto.Where(c => c != null).ToArray(); } }
/// <summary> /// Get an AreaKey for this area. /// </summary> public static AreaKey ToKey(this AreaData self, AreaMode mode = AreaMode.Normal) => new AreaKey(self.ID, mode).SetSID(self.GetSID());
private static int AreaComparison(AreaData a, AreaData b) { string aSet = a.GetLevelSet(); string aSID = a.GetSID(); MapMeta aMeta = a.GetMeta(); string bSet = b.GetLevelSet(); string bSID = b.GetSID(); MapMeta bMeta = b.GetMeta(); // Celeste appears before everything else. if (aSet == "Celeste" && bSet != "Celeste") { return(-1); } if (aSet != "Celeste" && bSet == "Celeste") { return(1); } // Uncategorized appears after everything else. if (string.IsNullOrEmpty(aSet) && !string.IsNullOrEmpty(bSet)) { return(1); } if (!string.IsNullOrEmpty(aSet) && string.IsNullOrEmpty(bSet)) { return(-1); } // Compare level sets alphabetically. if (aSet != bSet) { return(string.Compare(aSet, bSet)); } // Put "parented" levels at the end. if (!string.IsNullOrEmpty(aMeta?.Parent) && string.IsNullOrEmpty(bMeta?.Parent)) { return(1); } if (string.IsNullOrEmpty(aMeta?.Parent) && !string.IsNullOrEmpty(bMeta?.Parent)) { return(-1); } int? aOrder; AreaMode aSide; string aName; ParseName(aSID, out aOrder, out aSide, out aName); int? bOrder; AreaMode bSide; string bName; ParseName(bSID, out bOrder, out bSide, out bName); // put the "unordered" levels at the end. (Farewell is one of them.) if (aOrder != null && bOrder == null) { return(-1); } if (aOrder == null && bOrder != null) { return(1); } // order the rest by order, then by name, then by side if (aOrder != null && bOrder != null && aOrder.Value != bOrder.Value) { return(aOrder.Value - bOrder.Value); } if (aName != bName) { return(string.Compare(aName, bName)); } if (aSide != bSide) { return(aSide - bSide); } // everything is the same: this is the same level return(0); }
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(); }
public static new void Load() { orig_Load(); foreach (AreaData area in Areas) { area.SetSID("Celeste/" + area.Mode[0].Path); } // Separate array as we sort it afterwards. List <AreaData> modAreas = new List <AreaData>(); foreach (ModAsset asset in Everest.Content.ListMaps) { string path = asset.PathMapped.Substring(5); MapMeta meta = asset.GetMeta <MapMeta>(); 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.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 { Path = asset.PathMapped.Substring(5), Inventory = PlayerInventory.Default, AudioState = new AudioState(Sfxs.music_city, Sfxs.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 = Sfxs.cas_01_forsaken_city; // Custom values. if (meta != null) { if (!string.IsNullOrEmpty(meta.Name)) { area.Name = meta.Name; } if (!string.IsNullOrEmpty(meta.SID)) { area.SetSID(meta.SID); } if (!string.IsNullOrEmpty(meta.Icon) && GFX.Gui.Has(meta.Icon)) { area.Icon = meta.Icon; } area.Interlude = meta.Interlude; if (!string.IsNullOrEmpty(meta.CompleteScreenName)) { area.CompleteScreenName = meta.CompleteScreenName; } area.CassetteCheckpointIndex = meta.CassetteCheckpointIndex; if (!string.IsNullOrEmpty(meta.TitleBaseColor)) { area.TitleBaseColor = Calc.HexToColor(meta.TitleBaseColor); } if (!string.IsNullOrEmpty(meta.TitleAccentColor)) { area.TitleAccentColor = Calc.HexToColor(meta.TitleAccentColor); } if (!string.IsNullOrEmpty(meta.TitleTextColor)) { area.TitleTextColor = Calc.HexToColor(meta.TitleTextColor); } area.IntroType = meta.IntroType; area.Dreaming = meta.Dreaming; if (!string.IsNullOrEmpty(meta.ColorGrade)) { area.ColorGrade = meta.ColorGrade; } area.Mode = MapMeta.Convert(meta.Modes) ?? area.Mode; if (!string.IsNullOrEmpty(meta.Wipe)) { Type type = Assembly.GetEntryAssembly().GetType(meta.Wipe); ConstructorInfo ctor = type?.GetConstructor(new Type[] { typeof(Scene), typeof(bool), typeof(Action) }); if (type != null && ctor != null) { area.Wipe = (scene, wipeIn, onComplete) => ctor.Invoke(new object[] { scene, wipeIn, onComplete }); } } area.DarknessAlpha = meta.DarknessAlpha; area.BloomBase = meta.BloomBase; area.BloomStrength = meta.BloomStrength; if (!string.IsNullOrEmpty(meta.Jumpthru)) { area.Jumpthru = meta.Jumpthru; } if (!string.IsNullOrEmpty(meta.CassetteNoteColor)) { area.CassseteNoteColor = Calc.HexToColor(meta.CassetteNoteColor); } if (!string.IsNullOrEmpty(meta.CassetteSong)) { area.CassetteSong = meta.CassetteSong; } area.MountainIdle = meta.Mountain?.Idle?.Convert() ?? area.MountainIdle; area.MountainSelect = meta.Mountain?.Select?.Convert() ?? area.MountainSelect; area.MountainZoom = meta.Mountain?.Zoom?.Convert() ?? area.MountainZoom; area.MountainCursor = meta.Mountain?.Cursor?.ToVector3() ?? area.MountainCursor; area.MountainState = meta.Mountain?.State ?? area.MountainState; area.SetCompleteScreenMeta(meta.CompleteScreen); } // Some of the game's code checks for [1] / [2] explicitly. // Let's just provide null modes to fill any gaps. 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); } } // Sort and merge modAreas into Areas. Makes for easier levelset handling. Areas.Sort(AreaComparison); modAreas.Sort(AreaComparison); Areas.AddRange(modAreas); // Find duplicates and remove the earlier copy. 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--; } } // Remove AreaDatas which are now a mode of another AreaData. for (int i = 0; i < Areas.Count; i++) { AreaData area = Areas[i]; int otherIndex = Areas.FindIndex(other => other.Mode.Any(otherMode => otherMode?.Path == area.Mode[0].Path)); if (otherIndex != -1 && otherIndex != i) { Areas.RemoveAt(i); i--; } } // Update old MapData areas and load any new areas. for (int i = 0; i < Areas.Count; i++) { AreaData area = Areas[i]; area.ID = i; if (area.Mode[0].MapData != null) { area.Mode[0].MapData.Area = area.ToKey(); } else { area.Mode[0].MapData = new MapData(area.ToKey()); } if (area.Interlude) { continue; } 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)); } } } }
public static List <OuiJournalCollabProgressInLobby> GeneratePages(OuiJournal journal, string levelSet) { List <OuiJournalCollabProgressInLobby> pages = new List <OuiJournalCollabProgressInLobby>(); int rowCount = 0; OuiJournalCollabProgressInLobby currentPage = new OuiJournalCollabProgressInLobby(journal, levelSet); pages.Add(currentPage); int totalStrawberries = 0; int totalDeaths = 0; int sumOfBestDeaths = 0; long totalTime = 0; long sumOfBestTimes = 0; bool allMapsDone = true; bool allLevelsDone = true; bool allSpeedBerriesDone = true; string heartTexture = MTN.Journal.Has("CollabUtils2Hearts/" + levelSet) ? "CollabUtils2Hearts/" + levelSet : "heartgem0"; foreach (AreaStats item in SaveData.Instance.Areas_Safe) { AreaData areaData = AreaData.Get(item.ID_Safe); if (!areaData.Interlude_Safe) { if (LobbyHelper.IsHeartSide(areaData.GetSID())) { if (allMapsDone || item.TotalTimePlayed > 0) { // add a separator, like the one between regular maps and Farewell currentPage.table.AddRow(); } else { // all maps weren't complete yet, and the heart side was never accessed: hide the heart side for now. continue; } } string strawberryText = null; if (areaData.Mode[0].TotalStrawberries > 0 || item.TotalStrawberries > 0) { strawberryText = item.TotalStrawberries.ToString(); if (item.Modes[0].Completed) { strawberryText = strawberryText + "/" + areaData.Mode[0].TotalStrawberries; } } else { strawberryText = "-"; } Row row = currentPage.table.AddRow() .Add(new TextCell(Dialog.Clean(areaData.Name), new Vector2(1f, 0.5f), 0.6f, currentPage.TextColor)) .Add(null) .Add(new IconCell(item.Modes[0].HeartGem ? heartTexture : "dot")) .Add(new TextCell(strawberryText, currentPage.TextJustify, 0.5f, currentPage.TextColor)); if (item.TotalTimePlayed > 0) { row.Add(new TextCell(Dialog.Deaths(item.Modes[0].Deaths), currentPage.TextJustify, 0.5f, currentPage.TextColor)); } else { row.Add(new IconCell("dot")); } AreaStats stats = SaveData.Instance.GetAreaStatsFor(areaData.ToKey()); if (CollabMapDataProcessor.SilverBerries.TryGetValue(areaData.GetLevelSet(), out Dictionary <string, EntityID> levelSetBerries) && levelSetBerries.TryGetValue(areaData.GetSID(), out EntityID berryID) && stats.Modes[0].Strawberries.Contains(berryID)) { // silver berry was obtained! row.Add(new IconCell("CollabUtils2/silver_strawberry")); } else if (stats.Modes[0].Strawberries.Any(berry => areaData.Mode[0].MapData.Goldenberries.Any(golden => golden.ID == berry.ID && golden.Level.Name == berry.Level))) { // golden berry was obtained! row.Add(new IconCell("CollabUtils2/golden_strawberry")); } else if (item.Modes[0].SingleRunCompleted) { row.Add(new TextCell(Dialog.Deaths(item.Modes[0].BestDeaths), currentPage.TextJustify, 0.5f, currentPage.TextColor)); sumOfBestDeaths += item.Modes[0].BestDeaths; } else { // the player didn't ever do a single run. row.Add(new IconCell("dot")); allLevelsDone = false; } if (item.TotalTimePlayed > 0) { row.Add(new TextCell(Dialog.Time(item.TotalTimePlayed), currentPage.TextJustify, 0.5f, currentPage.TextColor)); } else { row.Add(new IconCell("dot")); } if (CollabModule.Instance.Settings.BestTimeToDisplayInJournal == CollabSettings.BestTimeInJournal.SpeedBerry) { if (CollabMapDataProcessor.SpeedBerries.TryGetValue(item.GetSID(), out CollabMapDataProcessor.SpeedBerryInfo speedBerryInfo) && CollabModule.Instance.SaveData.SpeedBerryPBs.TryGetValue(item.GetSID(), out long speedBerryPB)) { row.Add(new TextCell(Dialog.Time(speedBerryPB), currentPage.TextJustify, 0.5f, getRankColor(speedBerryInfo, speedBerryPB))); row.Add(new IconCell(getRankIcon(speedBerryInfo, speedBerryPB))); sumOfBestTimes += speedBerryPB; } else { row.Add(new IconCell("dot")).Add(null); allSpeedBerriesDone = false; } } else { if (item.Modes[0].BestTime > 0f) { row.Add(new TextCell(Dialog.Time(item.Modes[0].BestTime), currentPage.TextJustify, 0.5f, currentPage.TextColor)).Add(null); sumOfBestTimes += item.Modes[0].BestTime; } else { row.Add(new IconCell("dot")).Add(null); allSpeedBerriesDone = false; } } totalStrawberries += item.TotalStrawberries; totalDeaths += item.Modes[0].Deaths; totalTime += item.TotalTimePlayed; if (!item.Modes[0].HeartGem) { allMapsDone = false; } rowCount++; if (rowCount > 11) { // split the next zones into another page. rowCount = 0; currentPage = new OuiJournalCollabProgressInLobby(journal, levelSet); pages.Add(currentPage); } } } if (currentPage.table.Rows > 1) { currentPage.table.AddRow(); Row totalsRow = currentPage.table.AddRow() .Add(new TextCell(Dialog.Clean("journal_totals"), new Vector2(1f, 0.5f), 0.7f, currentPage.TextColor)).Add(null) .Add(null) .Add(new TextCell(totalStrawberries.ToString(), currentPage.TextJustify, 0.6f, currentPage.TextColor)) .Add(new TextCell(Dialog.Deaths(totalDeaths), currentPage.TextJustify, 0.6f, currentPage.TextColor)) .Add(new TextCell(allLevelsDone ? Dialog.Deaths(sumOfBestDeaths) : "-", currentPage.TextJustify, 0.6f, currentPage.TextColor)) .Add(new TextCell(Dialog.Time(totalTime), currentPage.TextJustify, 0.6f, currentPage.TextColor)) .Add(new TextCell(allSpeedBerriesDone ? Dialog.Time(sumOfBestTimes) : "-", currentPage.TextJustify, 0.6f, currentPage.TextColor)).Add(null); for (int l = 1; l < SaveData.Instance.UnlockedModes; l++) { totalsRow.Add(null); } totalsRow.Add(new TextCell(Dialog.Time(SaveData.Instance.Time), currentPage.TextJustify, 0.6f, currentPage.TextColor)); currentPage.table.AddRow(); } return(pages); }
public static AreaKey GenerateMap(RandoSettings settings) { var lastarea = AreaData.Areas[AreaData.Areas.Count - 1]; var newID = AreaData.Areas.Count; bool secondVerseSameAsTheFirst = lastarea.GetSID().StartsWith("randomizer/"); if (secondVerseSameAsTheFirst) { newID--; } var newArea = new AreaData { IntroType = Player.IntroTypes.WakeUp, Interlude = false, Dreaming = false, ID = newID, Name = $"{settings.Seed}_{settings.Hash}", Mode = new ModeProperties[3] { new ModeProperties { Inventory = settings.Dashes == NumDashes.Zero ? new PlayerInventory(0, true, true, false) : settings.Dashes == NumDashes.One ? PlayerInventory.Default : PlayerInventory.CH6End, }, null, null }, Icon = AreaData.Areas[0].Icon, MountainIdle = AreaData.Areas[0].MountainIdle, MountainZoom = AreaData.Areas[0].MountainZoom, MountainState = AreaData.Areas[0].MountainState, MountainCursor = new Vector3(0, 100000, 0), // ??? MountainSelect = AreaData.Areas[0].MountainSelect, MountainCursorScale = AreaData.Areas[0].MountainCursorScale, }; newArea.SetMeta(new Meta.MapMeta { Modes = new Meta.MapMetaModeProperties[] { new Meta.MapMetaModeProperties { HeartIsEnd = true, //SeekerSlowdown = true, // this doesn't do anything }, null, null } }); newArea.OnLevelBegin = (level) => { level.Add(new SeekerEffectsController()); }; var dyn = new DynData <AreaData>(newArea); dyn.Set <RandoSettings>("RandoSettings", settings.Copy()); newArea.SetSID($"randomizer/{newArea.Name}"); if (secondVerseSameAsTheFirst) { AreaData.Areas[AreaData.Areas.Count - 1] = newArea; // invalidate the MapEditor area key cache, as it will erroniously see a cache hit typeof(Editor.MapEditor).GetField("area", BindingFlags.NonPublic | BindingFlags.Static).SetValue(null, AreaKey.None); } else { // avert race condition RandoModule.AreaHandoff = newArea; while (RandoModule.AreaHandoff != null) { Thread.Sleep(10); } } var key = new AreaKey(newArea.ID); var r = new RandoLogic(settings, key); newArea.Mode[0].MapData = r.MakeMap(); newArea.Wipe = r.PickWipe(); newArea.CompleteScreenName = r.PickCompleteScreen(); newArea.CassetteSong = r.PickCassetteAudio(); newArea.Mode[0].AudioState = new AudioState(r.PickMusicAudio(), r.PickAmbienceAudio()); r.RandomizeDialog(); Logger.Log("randomizer", $"new area {newArea.GetSID()}"); return(key); }
public OuiJournalCollabProgressInOverworld(OuiJournal journal) : base(journal) { bool displaySpeedBerryColumn = shouldDisplaySpeedBerryColumn(); PageTexture = "page"; table = new Table() .AddColumn(new TextCell(Dialog.Clean("journal_progress"), new Vector2(0f, 0.5f), 1f, Color.Black * 0.7f, 420f)) .AddColumn(new EmptyCell(20f)) .AddColumn(new EmptyCell(64f)) .AddColumn(new IconCell("strawberry", 150f)) .AddColumn(new IconCell("skullblue", 100f)) .AddColumn(new IconCell("CollabUtils2MinDeaths/SpringCollab2020/1-Beginner", 100f)); if (OuiJournalCollabProgressDashCountMod.IsDashCountEnabled()) { table.AddColumn(new IconCell("max480/DashCountMod/dashes", 80f)); } table .AddColumn(new IconCell("time", 220f)) .AddColumn(new IconCell("CollabUtils2/speed_berry_pbs_heading", 220f)) .AddColumn(new EmptyCell(30f)); int totalStrawberries = 0; int totalDeaths = 0; int sumOfBestDeaths = 0; int sumOfBestDashes = 0; long totalTime = 0; long sumOfBestTimes = 0; bool allLevelsDone = true; bool allSpeedBerriesDone = true; bool allMapsCompletedInSingleRun = true; foreach (AreaStats item in SaveData.Instance.Areas_Safe) { AreaData areaData = AreaData.Get(item.ID_Safe); if (areaData.GetLevelSet() == SaveData.Instance.LevelSet) { string lobbyMapLevelSetName = LobbyHelper.GetLobbyLevelSet(areaData.GetSID()); LevelSetStats lobbyMapLevelSet = null; if (lobbyMapLevelSetName != null) { lobbyMapLevelSet = SaveData.Instance.GetLevelSetStatsFor(lobbyMapLevelSetName); } int lobbyStrawberries = item.TotalStrawberries; int lobbyTotalStrawberries = areaData.Mode[0].TotalStrawberries; int lobbyDeaths = item.Modes[0].Deaths; int lobbySumOfBestDeaths = 0; int lobbySumOfBestDashes = OuiJournalCollabProgressDashCountMod.DisplaysTotalDashes() ? OuiJournalCollabProgressDashCountMod.GetLevelDashesForJournalProgress(item) : 0; long lobbyTotalTime = item.TotalTimePlayed; long lobbySumOfBestTimes = 0; bool lobbyLevelsDone = true; int lobbySpeedBerryLevel = 1; bool lobbySilverBerriesObtained = true; bool lobbyAllMapsCompletedInSingleRun = true; foreach (AreaStats lobbyMap in lobbyMapLevelSet?.Areas ?? new List <AreaStats>()) { AreaData lobbyAreaData = AreaData.Get(lobbyMap.ID_Safe); lobbyStrawberries += lobbyMap.TotalStrawberries; lobbyTotalStrawberries += lobbyAreaData.Mode[0].TotalStrawberries; lobbyDeaths += lobbyMap.Modes[0].Deaths; lobbyTotalTime += lobbyMap.TotalTimePlayed; lobbyAllMapsCompletedInSingleRun &= lobbyMap.Modes[0].SingleRunCompleted; if (displaySpeedBerryColumn) { if (CollabMapDataProcessor.SpeedBerries.TryGetValue(lobbyMap.GetSID(), out CollabMapDataProcessor.SpeedBerryInfo mapSpeedBerryInfo) && CollabModule.Instance.SaveData.SpeedBerryPBs.TryGetValue(lobbyMap.GetSID(), out long mapSpeedBerryPB)) { lobbySpeedBerryLevel = Math.Max(getRankLevel(mapSpeedBerryInfo, mapSpeedBerryPB), lobbySpeedBerryLevel); lobbySumOfBestTimes += mapSpeedBerryPB; } else { lobbySpeedBerryLevel = 4; } } else { if (lobbyMap.Modes[0].BestTime > 0f) { lobbySumOfBestTimes += lobbyMap.Modes[0].BestTime; } else { lobbySpeedBerryLevel = 4; } } bool goldenBerryNotObtained = !lobbyMap.Modes[0].Strawberries.Any(berry => lobbyAreaData.Mode[0].MapData.Goldenberries.Any(golden => golden.ID == berry.ID && golden.Level.Name == berry.Level)); bool silverBerryNotObtained = !CollabMapDataProcessor.SilverBerries.TryGetValue(lobbyMap.GetLevelSet(), out Dictionary <string, EntityID> levelSetBerries) || !levelSetBerries.TryGetValue(lobbyMap.GetSID(), out EntityID berryID) || !lobbyMap.Modes[0].Strawberries.Contains(berryID); if (goldenBerryNotObtained && silverBerryNotObtained) { lobbySilverBerriesObtained = false; lobbySumOfBestDeaths += lobbyMap.Modes[0].BestDeaths; } lobbySumOfBestDashes += OuiJournalCollabProgressDashCountMod.GetLevelDashesForJournalProgress(lobbyMap); if (!lobbyMap.Modes[0].HeartGem) { lobbyLevelsDone = false; } } string strawberryText = null; if (lobbyStrawberries > 0 || lobbyTotalStrawberries > 0) { strawberryText = lobbyStrawberries.ToString(); if (lobbyLevelsDone) { strawberryText = strawberryText + "/" + lobbyTotalStrawberries; } } else { strawberryText = "-"; } string heartTexturePath = lobbyMapLevelSetName ?? areaData.GetSID(); string heartTexture = MTN.Journal.Has("CollabUtils2Hearts/" + heartTexturePath) ? "CollabUtils2Hearts/" + heartTexturePath : "heartgem0"; string areaName = Dialog.Clean(areaData.Name); if (Dialog.Has(areaData.Name + "_journal")) { areaName = Dialog.Clean(areaData.Name + "_journal"); } Row row = table.AddRow() .Add(new TextCell(areaName, new Vector2(1f, 0.5f), 0.6f, TextColor)) .Add(null) .Add(new IconCell(item.Modes[0].HeartGem ? heartTexture : "dot")) .Add(new TextCell(strawberryText, TextJustify, 0.5f, TextColor)); if (lobbyTotalTime > 0) { row.Add(new TextCell(Dialog.Deaths(lobbyDeaths), TextJustify, 0.5f, TextColor)); } else { row.Add(new IconCell("dot")); } if (lobbyLevelsDone) { AreaStats stats = SaveData.Instance.GetAreaStatsFor(areaData.ToKey()); if (lobbyMapLevelSet == null) { row.Add(new TextCell(Dialog.Deaths(item.Modes[0].BestDeaths), TextJustify, 0.5f, TextColor)); sumOfBestDeaths += item.Modes[0].BestDeaths; } else if (lobbySilverBerriesObtained) { row.Add(new IconCell("CollabUtils2/golden_strawberry")); } else if (lobbyAllMapsCompletedInSingleRun) { row.Add(new TextCell(Dialog.Deaths(lobbySumOfBestDeaths), TextJustify, 0.5f, TextColor)); } else { row.Add(new IconCell("dot")); } } else { row.Add(new IconCell("dot")); allLevelsDone = false; } if (OuiJournalCollabProgressDashCountMod.IsDashCountEnabled()) { if (lobbyMapLevelSet == null) { if ((OuiJournalCollabProgressDashCountMod.DisplaysTotalDashes() && item.TotalTimePlayed > 0) || item.Modes[0].SingleRunCompleted) { row.Add(new TextCell(Dialog.Deaths(OuiJournalCollabProgressDashCountMod.GetLevelDashesForJournalProgress(item)), TextJustify, 0.5f, TextColor)); sumOfBestDashes += OuiJournalCollabProgressDashCountMod.GetLevelDashesForJournalProgress(item); } else { row.Add(new IconCell("dot")); } } else if ((OuiJournalCollabProgressDashCountMod.DisplaysTotalDashes() && item.TotalTimePlayed > 0) || lobbyAllMapsCompletedInSingleRun) { row.Add(new TextCell(Dialog.Deaths(lobbySumOfBestDashes), TextJustify, 0.5f, TextColor)); } else { row.Add(new IconCell("dot")); } } if (lobbyTotalTime > 0) { row.Add(new TextCell(Dialog.Time(lobbyTotalTime), TextJustify, 0.5f, TextColor)); } else { row.Add(new IconCell("dot")); } if (lobbyMapLevelSet == null) { row.Add(new TextCell("-", TextJustify, 0.5f, TextColor)).Add(null); } else if (lobbySpeedBerryLevel < 4) { if (displaySpeedBerryColumn) { row.Add(new TextCell(Dialog.Time(lobbySumOfBestTimes), TextJustify, 0.5f, getRankColor(lobbySpeedBerryLevel))); row.Add(new IconCell(getRankIcon(lobbySpeedBerryLevel))); sumOfBestTimes += lobbySumOfBestTimes; } else { row.Add(new TextCell(Dialog.Time(lobbySumOfBestTimes), TextJustify, 0.5f, TextColor)).Add(null); sumOfBestTimes += lobbySumOfBestTimes; } } else { row.Add(new IconCell("dot")).Add(null); allSpeedBerriesDone = false; } totalStrawberries += lobbyStrawberries; totalDeaths += lobbyDeaths; sumOfBestDeaths += lobbySumOfBestDeaths; sumOfBestDashes += lobbySumOfBestDashes; totalTime += lobbyTotalTime; allMapsCompletedInSingleRun &= lobbyAllMapsCompletedInSingleRun; if (!lobbyLevelsDone) { allLevelsDone = false; } } } table.AddRow(); Row totalsRow = table.AddRow() .Add(new TextCell(Dialog.Clean("journal_totals"), new Vector2(1f, 0.5f), 0.7f, TextColor)).Add(null) .Add(null) .Add(new TextCell(totalStrawberries.ToString(), TextJustify, 0.6f, TextColor)) .Add(new TextCell(Dialog.Deaths(totalDeaths), TextJustify, 0.6f, TextColor)) .Add(new TextCell(allLevelsDone && allMapsCompletedInSingleRun ? Dialog.Deaths(sumOfBestDeaths) : "-", TextJustify, 0.6f, TextColor)); if (OuiJournalCollabProgressDashCountMod.IsDashCountEnabled()) { totalsRow.Add(new TextCell(OuiJournalCollabProgressDashCountMod.DisplaysTotalDashes() || (allLevelsDone && allMapsCompletedInSingleRun) ? Dialog.Deaths(sumOfBestDashes) : "-", TextJustify, 0.6f, TextColor)); } totalsRow .Add(new TextCell(Dialog.Time(totalTime), TextJustify, 0.6f, TextColor)) .Add(new TextCell(allSpeedBerriesDone ? Dialog.Time(sumOfBestTimes) : "-", TextJustify, 0.6f, TextColor)).Add(null); for (int l = 1; l < SaveData.Instance.UnlockedModes; l++) { totalsRow.Add(null); } totalsRow.Add(new TextCell(Dialog.Time(SaveData.Instance.Time), TextJustify, 0.6f, TextColor)); table.AddRow(); }
public static AreaKey GenerateMap(RandoSettings settings) { var lastarea = AreaData.Areas[AreaData.Areas.Count - 1]; var newID = AreaData.Areas.Count; if (lastarea.GetLevelSet() == "randomizer") { newID--; } var newArea = new AreaData { IntroType = Player.IntroTypes.WakeUp, Interlude = false, Dreaming = false, ID = newID, Name = $"{settings.Seed}_{settings.Hash}", Mode = new ModeProperties[3] { new ModeProperties { Inventory = settings.Dashes == NumDashes.Zero ? new PlayerInventory(0, true, true, false) : settings.Dashes == NumDashes.One ? PlayerInventory.Default : PlayerInventory.CH6End, }, null, null }, Icon = AreaData.Areas[0].Icon, MountainIdle = AreaData.Areas[0].MountainIdle, MountainZoom = AreaData.Areas[0].MountainZoom, MountainState = AreaData.Areas[0].MountainState, MountainCursor = new Vector3(0, 100000, 0), // ??? MountainSelect = AreaData.Areas[0].MountainSelect, MountainCursorScale = AreaData.Areas[0].MountainCursorScale, }; newArea.SetMeta(new Meta.MapMeta { Modes = new Meta.MapMetaModeProperties[] { new Meta.MapMetaModeProperties { HeartIsEnd = true, //SeekerSlowdown = true, // this doesn't do anything }, null, null } }); newArea.OnLevelBegin = (level) => { level.Add(new SeekerEffectsController()); }; newArea.SetSID($"randomizer/{newArea.Name}"); if (lastarea.GetSID().StartsWith("randomizer/")) { AreaData.Areas[AreaData.Areas.Count - 1] = newArea; } else { // avert race condition RandoModule.AreaHandoff = newArea; while (RandoModule.AreaHandoff != null) { Thread.Sleep(10); } } var key = new AreaKey(newArea.ID); var r = new RandoLogic(settings, key); newArea.Wipe = r.PickWipe(); newArea.CompleteScreenName = r.PickCompleteScreen(); newArea.CassetteSong = r.PickCassetteAudio(); newArea.Mode[0].AudioState = new AudioState(r.PickMusicAudio(), r.PickAmbienceAudio()); newArea.Mode[0].MapData = r.MakeMap(); r.RandomizeDialog(); Logger.Log("randomizer", $"new area {newArea.GetSID()}"); return(key); }
public override Dictionary <string, Action <BinaryPacker.Element> > Init() => new Dictionary <string, Action <BinaryPacker.Element> >() { { "root", root => { foreach (BinaryPacker.Element el in root.Children) { Context.Run(el.Name, el); } } }, { "Style", style => { // Celeste 1.2.5.0 optimizes BinaryPacker, which causes some issues. // Let's "unoptimize" Style and its Backgrounds and Foregrounds. if (style.Children == null) { style.Children = new List <BinaryPacker.Element>(); } foreach (BinaryPacker.Element el in style.Children) { if ((el.Name == "Backgrounds" || el.Name == "Foregrounds") && el.Children == null) { el.Children = new List <BinaryPacker.Element>(); } } } }, { "levels", levels => { if (levels.Children != null) { foreach (BinaryPacker.Element level in levels.Children) { Context.Run("level", level); if (level.Children != null) { foreach (BinaryPacker.Element levelChild in level.Children) { Context.Run(levelChild.Name, levelChild); } } } } } }, { "level", level => { // lvl_ was optional before Celeste 1.2.5.0 made it mandatory. // Certain level "tags" are supported as very early mods used them. LevelTags = level.Attr("name").Split(':'); LevelName = LevelTags[0]; if (LevelName.StartsWith("lvl_")) { LevelName = LevelName.Substring(4); } level.SetAttr("name", "lvl_" + LevelName); BinaryPacker.Element entities = level.Children.FirstOrDefault(el => el.Name == "entities"); BinaryPacker.Element triggers = level.Children.FirstOrDefault(el => el.Name == "triggers"); // Celeste 1.2.5.0 optimizes BinaryPacker (null instead of empty lists), // which causes some issues where the game still expects an empty list. // Let's "unoptimize" entities and triggers. if (entities == null) { level.Children.Add(entities = new BinaryPacker.Element { Name = "entities" }); } if (entities.Children == null) { entities.Children = new List <BinaryPacker.Element>(); } if (triggers == null) { level.Children.Add(triggers = new BinaryPacker.Element { Name = "triggers" }); } if (triggers.Children == null) { triggers.Children = new List <BinaryPacker.Element>(); } if (LevelTags.Contains("checkpoint") || LevelTags.Contains("cp")) { entities.Children.Add(new BinaryPacker.Element { Name = "checkpoint", Attributes = new Dictionary <string, object>() { { "x", "0" }, { "y", "0" } } }); } if (level.AttrBool("space")) { if (level.AttrBool("spaceSkipWrap") || LevelTags.Contains("nospacewrap") || LevelTags.Contains("nsw")) { entities.Children.Add(new BinaryPacker.Element { Name = "everest/spaceControllerBlocker" }); } if (level.AttrBool("spaceSkipGravity") || LevelTags.Contains("nospacegravity") || LevelTags.Contains("nsg")) { entities.Children.Add(new BinaryPacker.Element { Name = "everest/spaceController" }); level.SetAttr("space", false); } if (!LevelTags.Contains("nospacefix") && !LevelTags.Contains("nsf") && !triggers.Children.Any(el => el.Name == "cameraTargetTrigger") && !entities.Children.Any(el => el.Name == "everest/spaceControllerBlocker")) { // Camera centers tile-perfectly on uneven heights. int heightForCenter = level.AttrInt("height"); heightForCenter /= 8; if (heightForCenter % 2 == 0) { heightForCenter--; } heightForCenter *= 8; triggers.Children.Add(new BinaryPacker.Element { Name = "cameraTargetTrigger", Attributes = new Dictionary <string, object>() { { "x", 0f }, { "y", 0f }, { "width", level.AttrInt("width") }, { "height", level.AttrInt("height") }, { "yOnly", true }, { "lerpStrength", 1f } }, Children = new List <BinaryPacker.Element>() { new BinaryPacker.Element { Attributes = new Dictionary <string, object>() { { "x", 160f }, { "y", heightForCenter / 2f } } } } }); } } } }, { "entities", levelChild => { // check if the room has a checkpoint first. foreach (BinaryPacker.Element entity in levelChild.Children) { if (entity.Name == "checkpoint") { if (CheckpointsAuto != null) { MapMeta modeMeta = AreaData.GetModeMeta(AreaKey.Mode); CheckpointData c = new CheckpointData( LevelName, (AreaData.GetSID() + "_" + LevelName).DialogKeyify(), MapMeta.GetInventory(entity.Attr("inventory")), entity.Attr("dreaming") == "" ? modeMeta.Dreaming ?? AreaData.Dreaming : entity.AttrBool("dreaming"), null ); if (entity.Attr("coreMode") == "") { c.CoreMode = modeMeta.CoreMode ?? AreaData.CoreMode; } else { entity.AttrIf("coreMode", v => c.CoreMode = (Session.CoreModes)Enum.Parse(typeof(Session.CoreModes), v, true)); } int id = entity.AttrInt("checkpointID", -1); if (id == -1) { CheckpointsAuto.Add(c); } else { while (CheckpointsAuto.Count <= id) { CheckpointsAuto.Add(null); } CheckpointsAuto[id] = c; } } Checkpoint++; StrawberryInCheckpoint = 0; } } // then, auto-assign strawberries and cassettes to checkpoints. foreach (BinaryPacker.Element entity in levelChild.Children) { Context.Run("entity:" + entity.Name, entity); } } }, { "entity:cassette", entity => { if (AreaData.CassetteCheckpointIndex < 0) { AreaData.CassetteCheckpointIndex = Checkpoint; } } }, { "entity:strawberry", entity => { if (!entity.AttrBool("moon", false)) { if (entity.AttrInt("checkpointID", -1) == -1) { entity.SetAttr("checkpointID", Checkpoint); } if (entity.AttrInt("order", -1) == -1) { entity.SetAttr("order", StrawberryInCheckpoint); } Strawberry++; StrawberryInCheckpoint++; } } } };
public static List <OuiJournalCollabProgressInLobby> GeneratePages(OuiJournal journal, string levelSet, bool showOnlyDiscovered) { bool displaySpeedBerryColumn = shouldDisplaySpeedBerryColumn(levelSet); List <OuiJournalCollabProgressInLobby> pages = new List <OuiJournalCollabProgressInLobby>(); int rowCount = 0; int totalStrawberries = 0; int totalDeaths = 0; int sumOfBestDeaths = 0; int sumOfBestDashes = 0; long totalTime = 0; long sumOfBestTimes = 0; bool allMapsDone = true; bool allLevelsDone = true; bool allSpeedBerriesDone = true; string heartTexture = MTN.Journal.Has("CollabUtils2Hearts/" + levelSet) ? "CollabUtils2Hearts/" + levelSet : "heartgem0"; int mapsPerPage = 12; int mapAmount = SaveData.Instance.Areas_Safe.Where(item => !AreaData.Get(item.ID_Safe).Interlude_Safe && (!showOnlyDiscovered || item.TotalTimePlayed > 0)).Count(); // we want to display the map icons if they're not actually all the same. ^^' bool displayIcons = AreaData.Areas .Where(area => !area.Interlude_Safe) .Select(area => area.Icon) .Distinct() .Count() > 1; OuiJournalCollabProgressInLobby currentPage = new OuiJournalCollabProgressInLobby(journal, levelSet, displayIcons); pages.Add(currentPage); if (mapAmount >= mapsPerPage) { // we want the last page to contain at least 2 maps. while (mapAmount % mapsPerPage < 2) { mapsPerPage--; } } List <AreaStats> sortedMaps = new List <AreaStats>(SaveData.Instance.Areas_Safe) .Where(map => !AreaData.Get(map).Interlude_Safe) .ToList(); // sort maps by icon name if all of their icons start with [number]-... // because then we know the ordering is intentional (and not accidental like easy > hard > medium) Regex startsWithNumber = new Regex(".*/[0-9]+-.*"); if (sortedMaps.Select(map => AreaData.Get(map).Icon ?? "").All(icon => startsWithNumber.IsMatch(icon))) { sortedMaps.Sort((a, b) => { AreaData adata = AreaData.Get(a); AreaData bdata = AreaData.Get(b); bool aHeartSide = LobbyHelper.IsHeartSide(a.GetSID()); bool bHeartSide = LobbyHelper.IsHeartSide(b.GetSID()); // heart sides should appear last. if (aHeartSide && !bHeartSide) { return(1); } if (!aHeartSide && bHeartSide) { return(-1); } // sort by icon name, then by map bin name. return(adata.Icon == bdata.Icon ? adata.Name.CompareTo(bdata.Name) : adata.Icon.CompareTo(bdata.Icon)); }); } foreach (AreaStats item in sortedMaps) { AreaData areaData = AreaData.Get(item.ID_Safe); if (LobbyHelper.IsHeartSide(areaData.GetSID())) { if (allMapsDone || item.TotalTimePlayed > 0) { // add a separator, like the one between regular maps and Farewell currentPage.table.AddRow(); } else { // all maps weren't complete yet, and the heart side was never accessed: hide the heart side for now. continue; } } if (showOnlyDiscovered && item.TotalTimePlayed <= 0) { // skip the map, because it was not discovered yet. // since it wasn't discovered, we can already say all maps weren't done though. allMapsDone = false; allLevelsDone = false; allSpeedBerriesDone = false; continue; } string strawberryText = null; if (areaData.Mode[0].TotalStrawberries > 0 || item.TotalStrawberries > 0) { strawberryText = item.TotalStrawberries.ToString(); if (item.Modes[0].Completed) { strawberryText = strawberryText + "/" + areaData.Mode[0].TotalStrawberries; } } else { strawberryText = "-"; } Row row = currentPage.table.AddRow() .Add(new TextCell(Dialog.Clean(areaData.Name), new Vector2(1f, 0.5f), 0.6f, currentPage.TextColor)); if (displayIcons) { row.Add(null).Add(new IconCellFromGui(GFX.Gui.Has(areaData.Icon) ? areaData.Icon : "areas/null", 60f, 50f)); } row.Add(null) .Add(new IconCell(item.Modes[0].HeartGem ? heartTexture : "dot")) .Add(new TextCell(strawberryText, currentPage.TextJustify, 0.5f, currentPage.TextColor)); if (item.TotalTimePlayed > 0) { row.Add(new TextCell(Dialog.Deaths(item.Modes[0].Deaths), currentPage.TextJustify, 0.5f, currentPage.TextColor)); } else { row.Add(new IconCell("dot")); } AreaStats stats = SaveData.Instance.GetAreaStatsFor(areaData.ToKey()); if (CollabMapDataProcessor.SilverBerries.TryGetValue(areaData.GetLevelSet(), out Dictionary <string, EntityID> levelSetBerries) && levelSetBerries.TryGetValue(areaData.GetSID(), out EntityID berryID) && stats.Modes[0].Strawberries.Contains(berryID)) { // silver berry was obtained! row.Add(new IconCell("CollabUtils2/silver_strawberry")); } else if (stats.Modes[0].Strawberries.Any(berry => areaData.Mode[0].MapData.Goldenberries.Any(golden => golden.ID == berry.ID && golden.Level.Name == berry.Level))) { // golden berry was obtained! row.Add(new IconCell("CollabUtils2/golden_strawberry")); } else if (item.Modes[0].SingleRunCompleted) { row.Add(new TextCell(Dialog.Deaths(item.Modes[0].BestDeaths), currentPage.TextJustify, 0.5f, currentPage.TextColor)); sumOfBestDeaths += item.Modes[0].BestDeaths; } else { // the player didn't ever do a single run. row.Add(new IconCell("dot")); allLevelsDone = false; } if (OuiJournalCollabProgressDashCountMod.IsDashCountEnabled()) { if ((OuiJournalCollabProgressDashCountMod.DisplaysTotalDashes() && item.TotalTimePlayed > 0) || item.Modes[0].SingleRunCompleted) { row.Add(new TextCell(Dialog.Deaths(OuiJournalCollabProgressDashCountMod.GetLevelDashesForJournalProgress(item)), currentPage.TextJustify, 0.5f, currentPage.TextColor)); sumOfBestDashes += OuiJournalCollabProgressDashCountMod.GetLevelDashesForJournalProgress(item); } else { row.Add(new IconCell("dot")); } } if (item.TotalTimePlayed > 0) { row.Add(new TextCell(Dialog.Time(item.TotalTimePlayed), currentPage.TextJustify, 0.5f, currentPage.TextColor)); } else { row.Add(new IconCell("dot")); } if (displaySpeedBerryColumn) { if (CollabMapDataProcessor.SpeedBerries.TryGetValue(item.GetSID(), out CollabMapDataProcessor.SpeedBerryInfo speedBerryInfo) && CollabModule.Instance.SaveData.SpeedBerryPBs.TryGetValue(item.GetSID(), out long speedBerryPB)) { row.Add(new TextCell(Dialog.Time(speedBerryPB), currentPage.TextJustify, 0.5f, getRankColor(speedBerryInfo, speedBerryPB))); row.Add(new IconCell(getRankIcon(speedBerryInfo, speedBerryPB))); sumOfBestTimes += speedBerryPB; } else { row.Add(new IconCell("dot")).Add(null); allSpeedBerriesDone = false; } } else { if (item.Modes[0].BestTime > 0f) { row.Add(new TextCell(Dialog.Time(item.Modes[0].BestTime), currentPage.TextJustify, 0.5f, currentPage.TextColor)).Add(null); sumOfBestTimes += item.Modes[0].BestTime; } else { row.Add(new IconCell("dot")).Add(null); allSpeedBerriesDone = false; } } totalStrawberries += item.TotalStrawberries; totalDeaths += item.Modes[0].Deaths; totalTime += item.TotalTimePlayed; if (!item.Modes[0].HeartGem) { allMapsDone = false; } rowCount++; if (rowCount >= mapsPerPage) { // split the next zones into another page. rowCount = 0; currentPage = new OuiJournalCollabProgressInLobby(journal, levelSet, displayIcons); pages.Add(currentPage); } } if (currentPage.table.Rows > 1) { currentPage.table.AddRow(); Row totalsRow = currentPage.table.AddRow() .Add(new TextCell(Dialog.Clean("journal_totals"), new Vector2(1f, 0.5f), 0.7f, currentPage.TextColor)).Add(null); if (displayIcons) { totalsRow.Add(null).Add(null); } totalsRow.Add(null) .Add(new TextCell(totalStrawberries.ToString(), currentPage.TextJustify, 0.6f, currentPage.TextColor)) .Add(new TextCell(Dialog.Deaths(totalDeaths), currentPage.TextJustify, 0.6f, currentPage.TextColor)) .Add(new TextCell(allLevelsDone ? Dialog.Deaths(sumOfBestDeaths) : "-", currentPage.TextJustify, 0.6f, currentPage.TextColor)); if (OuiJournalCollabProgressDashCountMod.IsDashCountEnabled()) { totalsRow.Add(new TextCell(OuiJournalCollabProgressDashCountMod.DisplaysTotalDashes() || allLevelsDone ? Dialog.Deaths(sumOfBestDashes) : "-", currentPage.TextJustify, 0.6f, currentPage.TextColor)); } totalsRow .Add(new TextCell(Dialog.Time(totalTime), currentPage.TextJustify, 0.6f, currentPage.TextColor)) .Add(new TextCell(allSpeedBerriesDone ? Dialog.Time(sumOfBestTimes) : "-", currentPage.TextJustify, 0.6f, currentPage.TextColor)).Add(null); for (int l = 1; l < SaveData.Instance.UnlockedModes; l++) { totalsRow.Add(null); } totalsRow.Add(new TextCell(Dialog.Time(SaveData.Instance.Time), currentPage.TextJustify, 0.6f, currentPage.TextColor)); currentPage.table.AddRow(); } return(pages); }
public static AreaKey GenerateMap(RandoSettings settings) { StringWriter builder = new StringWriter(); builder.Write("Generating map with settings:\n"); YamlHelper.Serializer.Serialize(builder, settings); Logger.Log("randomizer", builder.ToString()); var newID = AreaData.Areas.Count; if (AreaData.Areas.Last().GetSID().StartsWith("randomizer/")) { newID--; } var newArea = new AreaData { IntroType = Player.IntroTypes.WakeUp, Interlude = false, Dreaming = false, ID = newID, Name = $"{settings.Seed}_{settings.EndlessLevel}_{settings.Hash}", Mode = new ModeProperties[3] { new ModeProperties { Inventory = settings.Dashes == NumDashes.Zero ? new PlayerInventory(0, true, false, false) : settings.Dashes == NumDashes.One ? new PlayerInventory(1, true, false, false) : new PlayerInventory(2, true, false, false), }, null, null }, Icon = AreaData.Areas[0].Icon, MountainIdle = AreaData.Areas[0].MountainIdle, MountainZoom = AreaData.Areas[0].MountainZoom, MountainState = AreaData.Areas[0].MountainState, MountainCursor = new Vector3(0, 100000, 0), // ??? MountainSelect = AreaData.Areas[0].MountainSelect, MountainCursorScale = AreaData.Areas[0].MountainCursorScale, }; newArea.SetMeta(new Meta.MapMeta { Modes = new Meta.MapMetaModeProperties[] { new Meta.MapMetaModeProperties { HeartIsEnd = true, //SeekerSlowdown = true, // this doesn't do anything }, null, null } }); newArea.OnLevelBegin = (level) => { level.Add(new SeekerEffectsController()); }; var dyn = new DynData <AreaData>(newArea); dyn.Set <RandoSettings>("RandoSettings", settings.Copy()); newArea.SetSID($"randomizer/{newArea.Name}"); // avert race condition RandoModule.AreaHandoff = newArea; while (RandoModule.AreaHandoff != null) { Thread.Sleep(10); } // invalidate the MapEditor area key cache, as it will erroniously see a cache hit typeof(Editor.MapEditor).GetField("area", BindingFlags.NonPublic | BindingFlags.Static).SetValue(null, AreaKey.None); var key = new AreaKey(newArea.ID); int tryNum = 0; while (true) { try { var r = new RandoLogic(settings, key, tryNum); newArea.Mode[0].MapData = r.MakeMap(); newArea.Wipe = r.PickWipe(); newArea.CompleteScreenName = r.PickCompleteScreen(); newArea.CassetteSong = r.PickCassetteAudio(); newArea.Mode[0].AudioState = new AudioState(r.PickMusicAudio(), r.PickAmbienceAudio()); if (settings.RandomColors) { newArea.BloomBase = (float)Math.Pow(r.Random.NextFloat(), 5) * r.Random.NextFloat(); newArea.DarknessAlpha = r.Random.NextFloat() * (float)Math.Pow(r.Random.NextFloat(), 0.5) * (float)Math.Pow(r.Random.NextFloat(), 2) * 0.35f; newArea.ColorGrade = r.PickColorGrade(); } r.RandomizeDialog(); break; } catch (RetryException) { tryNum++; if (tryNum >= 500) { throw new GenerationError("Cannot create map with these settings"); } Logger.Log("randomizer", $"Too many retries ({tryNum}), starting again"); } } Logger.Log("randomizer", $"new area {newArea.GetSID()}"); return(key); }