public override void End() { if (Mode.Checkpoints == null) { Mode.Checkpoints = CheckpointsAuto.Where(c => c != null).ToArray(); } if (Mode != ParentMode) { if (ParentMode.Checkpoints == null) { ParentMode.Checkpoints = CheckpointsAuto.Where(c => c != null).ToArray(); } else { ParentMode.Checkpoints = ParentMode.Checkpoints.Concat(CheckpointsAuto.Where(c => c != null)).ToArray(); } } MapData.SetDetectedStrawberriesIncludingUntracked(TotalStrawberriesIncludingUntracked); if (MapData != ParentMapData) { ParentMapData.SetDetectedStrawberriesIncludingUntracked(ParentMapData.GetDetectedStrawberriesIncludingUntracked() + TotalStrawberriesIncludingUntracked); } }
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 ); c.SetArea(AreaKey); 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; } if (ParentAreaData.CassetteCheckpointIndex < 0) { ParentAreaData.CassetteCheckpointIndex = Checkpoint + (ParentMode.Checkpoints?.Length ?? 0); } MapData.SetDetectedCassette(); ParentMapData.SetDetectedCassette(); } }, { "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); } entity.SetAttr("checkpointIDParented", Checkpoint + (ParentMode.Checkpoints?.Length ?? 0)); StrawberryInCheckpoint++; } } } };