diff --git a/Celeste.Mod.mm/Mod/Meta/MapMeta.cs b/Celeste.Mod.mm/Mod/Meta/MapMeta.cs index 3c67a9063..806e0b33b 100644 --- a/Celeste.Mod.mm/Mod/Meta/MapMeta.cs +++ b/Celeste.Mod.mm/Mod/Meta/MapMeta.cs @@ -63,17 +63,7 @@ public MapMeta(BinaryPacker.Element meta) { public bool? OverrideASideMeta { get; set; } - private MapMetaModeProperties[] _modes = new MapMetaModeProperties[3]; - public MapMetaModeProperties[] Modes { - get => _modes; - set { - _modes = value; - // Some of the game's code checks for [1] / [2] explicitly. - // Let's just provide null modes to fill any gaps. - Array.Resize(ref _modes, 3); - } - } - public MapMetaModeProperties Mode { get; set; } // This property will be null outside of loading + public MapMetaModeProperties[] Modes { get; set; } public MapMetaMountain Mountain { get; set; } @@ -131,7 +121,8 @@ public void Parse(BinaryPacker.Element meta) { child = meta.Children?.FirstOrDefault(el => el.Name == "cassettemodifier"); if (child != null) CassetteModifier = new MapMetaCassetteModifier(child); - + + Modes = new MapMetaModeProperties[3]; child = meta.Children?.FirstOrDefault(el => el.Name == "modes"); if (child != null && child.Children != null) { for (int i = 0; i < child.Children.Count; i++) { @@ -141,72 +132,6 @@ public void Parse(BinaryPacker.Element meta) { Modes[i] = null; } } - BinaryPacker.Element modeMeta = meta.Children?.FirstOrDefault(el => el.Name == "mode"); - if (modeMeta != null) { - Mode = new MapMetaModeProperties(modeMeta); - } - } - - public void AddTo(MapMeta other) { - if (!string.IsNullOrEmpty(Parent)) { other.Parent = Parent; } - if (!string.IsNullOrEmpty(Icon)) { other.Icon = Icon; } - if (Interlude != null) { other.Interlude = Interlude; } - if (CassetteCheckpointIndex != null) { other.CassetteCheckpointIndex = CassetteCheckpointIndex; } - if (!string.IsNullOrEmpty(TitleBaseColor)) { other.TitleBaseColor = TitleBaseColor; } - if (!string.IsNullOrEmpty(TitleAccentColor)) { other.TitleAccentColor = TitleAccentColor; } - if (!string.IsNullOrEmpty(TitleTextColor)) { other.TitleTextColor = TitleTextColor; } - if (IntroType != null) { other.IntroType = IntroType; } - if (Dreaming != null) { other.Dreaming = Dreaming; } - if (!string.IsNullOrEmpty(ColorGrade)) { other.ColorGrade = ColorGrade; } - if (!string.IsNullOrEmpty(Wipe)) { other.Wipe = Wipe; } - if (DarknessAlpha != null) { other.DarknessAlpha = DarknessAlpha; } - if (BloomBase != null) { other.BloomBase = BloomBase; } - if (BloomStrength != null) { other.BloomStrength = BloomStrength; } - if (!string.IsNullOrEmpty(Jumpthru)) { other.Jumpthru = Jumpthru; } - if (CoreMode != null) { other.CoreMode = CoreMode; } - if (!string.IsNullOrEmpty(CassetteNoteColor)) { other.CassetteNoteColor = CassetteNoteColor; } - if (!string.IsNullOrEmpty(CassetteSong)) { other.CassetteSong = CassetteSong; } - if (!string.IsNullOrEmpty(PostcardSoundID)) { other.PostcardSoundID = PostcardSoundID; } - if (!string.IsNullOrEmpty(ForegroundTiles)) { other.ForegroundTiles = ForegroundTiles; } - if (!string.IsNullOrEmpty(BackgroundTiles)) { other.BackgroundTiles = BackgroundTiles; } - if (!string.IsNullOrEmpty(AnimatedTiles)) { other.AnimatedTiles = AnimatedTiles; } - if (!string.IsNullOrEmpty(Sprites)) { other.Sprites = Sprites; } - if (!string.IsNullOrEmpty(Portraits)) { other.Portraits = Portraits; } - if (OverrideASideMeta != null) { other.OverrideASideMeta = OverrideASideMeta; } - if (Modes != null) { - if (other.Modes == null) { - other.Modes = Modes; - } else { - for (int i = 0; i < Modes.Length && i < other.Modes.Length; i++) { - if (other.Modes[i] == null) { - other.Modes[i] = Modes[i]; - } else if (Modes[i] != null) { - Modes[i].AddTo(other.Modes[i]); - } - } - } - } - if (Mountain != null) { other.Mountain = Mountain; } - if (CompleteScreen != null) { other.CompleteScreen = CompleteScreen; } - if (LoadingVignetteScreen != null) { other.LoadingVignetteScreen = LoadingVignetteScreen; } - if (LoadingVignetteText != null) { other.LoadingVignetteText = LoadingVignetteText; } - if (CassetteModifier != null) { other.CassetteModifier = CassetteModifier; } - } - - public static MapMeta Add(MapMeta self, MapMeta other) { - switch ((self, other)) { - case (null, null): - return new MapMeta(); - case (null, _): - return other; - case (_, null): - return self; - default: - MapMeta result = new MapMeta(); - other.AddTo(result); - self.AddTo(result); - return result; - } } public void ApplyTo(patch_AreaData area) { @@ -267,7 +192,59 @@ public void ApplyTo(patch_AreaData area) { area.MountainCursor = Mountain?.Cursor?.ToVector3() ?? area.MountainCursor; area.MountainState = Mountain?.State ?? area.MountainState; - area.Meta = this; + patch_ModeProperties[] modes = area.Mode; + area.Mode = Convert(Modes) ?? modes; + if (modes != null) + for (int i = 0; i < area.Mode.Length && i < modes.Length; i++) + if (area.Mode[i] == null) + area.Mode[i] = modes[i]; + + MapMeta meta = area.Meta; + if (meta == null) { + area.Meta = this; + } else { + if (!string.IsNullOrEmpty(Parent)) + meta.Parent = Parent; + + if (!string.IsNullOrEmpty(PostcardSoundID)) + meta.PostcardSoundID = PostcardSoundID; + + if (!string.IsNullOrEmpty(ForegroundTiles)) + meta.ForegroundTiles = ForegroundTiles; + + if (!string.IsNullOrEmpty(BackgroundTiles)) + meta.BackgroundTiles = BackgroundTiles; + + if (!string.IsNullOrEmpty(AnimatedTiles)) + meta.AnimatedTiles = AnimatedTiles; + + if (!string.IsNullOrEmpty(Sprites)) + meta.Sprites = Sprites; + + if (!string.IsNullOrEmpty(Portraits)) + meta.Portraits = Portraits; + + if (OverrideASideMeta != null) + meta.OverrideASideMeta = OverrideASideMeta; + + if ((Modes?.Length ?? 0) != 0 && Modes.Any(mode => mode != null)) + meta.Modes = Modes; + + if (Mountain != null) + meta.Mountain = Mountain; + + if (CompleteScreen != null) + meta.CompleteScreen = CompleteScreen; + + if (LoadingVignetteScreen != null) + meta.LoadingVignetteScreen = LoadingVignetteScreen; + + if (LoadingVignetteText != null) + meta.LoadingVignetteText = LoadingVignetteText; + + if (CassetteModifier != null) + meta.CassetteModifier = CassetteModifier; + } } public void ApplyToForOverride(AreaData area) { @@ -405,41 +382,6 @@ public void ApplyTo(patch_AreaData area, AreaMode mode) { } area.Mode[(int) mode] = props; } - - public void AddTo(MapMetaModeProperties other) { - if (AudioState != null) { - if (other.AudioState == null) { - other.AudioState = AudioState; - } else { - AudioState.AddTo(other.AudioState); - } - } - if (Checkpoints != null) { other.Checkpoints = Checkpoints; } - if (IgnoreLevelAudioLayerData != null) { other.IgnoreLevelAudioLayerData = IgnoreLevelAudioLayerData; } - if (!string.IsNullOrEmpty(Inventory)) { other.Inventory = Inventory; } - if (!string.IsNullOrEmpty(Path)) { other.Path = Path; } - if (!string.IsNullOrEmpty(PoemID)) { other.PoemID = PoemID; } - if (!string.IsNullOrEmpty(StartLevel)) { other.StartLevel = StartLevel; } - if (HeartIsEnd != null) { other.HeartIsEnd = HeartIsEnd; } - if (SeekerSlowdown != null) { other.SeekerSlowdown = SeekerSlowdown; } - if (TheoInBubble != null) { other.TheoInBubble = TheoInBubble; } - } - - public static MapMetaModeProperties Add(MapMetaModeProperties self, MapMetaModeProperties other) { - switch ((self, other)) { - case (null, null): - return new MapMetaModeProperties(); - case (null, _): - return other; - case (_, null): - return self; - default: - MapMetaModeProperties result = new MapMetaModeProperties(); - other.AddTo(result); - self.AddTo(result); - return result; - } - } } public class MapMetaAudioState { public MapMetaAudioState() { @@ -458,11 +400,6 @@ public void Parse(BinaryPacker.Element meta) { meta.AttrIf("Music", v => Music = v); meta.AttrIf("Ambience", v => Ambience = v); } - - public void AddTo(MapMetaAudioState other) { - if (!string.IsNullOrEmpty(Music)) { other.Music = Music; } - if (!string.IsNullOrEmpty(Ambience)) { other.Ambience = Ambience; } - } } public class MapMetaCheckpointData { public MapMetaCheckpointData() { @@ -474,14 +411,14 @@ public MapMetaCheckpointData(BinaryPacker.Element meta) { public string Level { get; set; } public string Name { get; set; } - public bool? Dreaming { get; set; } + public bool Dreaming { get; set; } public string Inventory { get; set; } public MapMetaAudioState AudioState { get; set; } public string[] Flags { get; set; } public Session.CoreModes? CoreMode { get; set; } public CheckpointData Convert() - => new CheckpointData(Level, Name, MapMeta.GetInventory(Inventory), Dreaming ?? false, AudioState?.Convert()) { - Flags = new HashSet(Flags ?? Array.Empty()), + => new CheckpointData(Level, Name, MapMeta.GetInventory(Inventory), Dreaming, AudioState?.Convert()) { + Flags = new HashSet(Flags ?? new string[0]), CoreMode = CoreMode }; @@ -506,16 +443,6 @@ public void Parse(BinaryPacker.Element meta) { } } } - - public void AddTo(MapMetaCheckpointData other) { - if (!string.IsNullOrEmpty(Level)) { other.Level = Level; } - if (!string.IsNullOrEmpty(Name)) { other.Name = Name; } - if (Dreaming != null) { other.Dreaming = Dreaming; } - if (!string.IsNullOrEmpty(Inventory)) { other.Inventory = Inventory; } - if (AudioState != null) { other.AudioState = AudioState; } - if (Flags != null) { other.Flags = Flags; } - if (CoreMode != null) { other.CoreMode = CoreMode; } - } } public class MapMetaMountain { public string MountainModelDirectory { get; set; } = null; diff --git a/Celeste.Mod.mm/Patches/AreaData.cs b/Celeste.Mod.mm/Patches/AreaData.cs index 6c744d108..aa90e3ed8 100644 --- a/Celeste.Mod.mm/Patches/AreaData.cs +++ b/Celeste.Mod.mm/Patches/AreaData.cs @@ -254,11 +254,28 @@ public static patch_AreaData Get(string sid) { 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(); + if (metaLoaded != null) { + area.Meta = 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) { patch_ModeProperties[] larger = new patch_ModeProperties[3]; for (int i = 0; i < area.Mode.Length; i++) @@ -274,6 +291,7 @@ public static patch_AreaData Get(string sid) { // Some special handling. area.OnLevelBegin = (level) => { + MapMeta levelMeta = patch_AreaData.Get(level.Session).Meta; MapMetaModeProperties levelMetaMode = ((patch_MapData) level.Session.MapData).Meta; if (levelMetaMode?.SeekerSlowdown ?? false) @@ -297,12 +315,24 @@ public static patch_AreaData Get(string sid) { } } + // 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++) { - // Check for .bins possibly belonging to A side .bins by their path and lack of existing modes. patch_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; + } + ParseName(path, out int? order, out AreaMode side, out string 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++) { patch_AreaData other = Areas[ii]; ParseName(other.Mode[0].Path, out int? otherOrder, out AreaMode otherSide, out string otherName); @@ -326,12 +356,6 @@ public static patch_AreaData Get(string sid) { patch_AreaData area = Areas[i]; area.ID = i; - // 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 patch_MapData(area.ToKey()); - // Clean up non-existing modes. int modei = 0; for (; modei < area.Mode.Length; modei++) { @@ -343,6 +367,14 @@ public static patch_AreaData Get(string sid) { Logger.Verbose("AreaData", string.Format("{0}: {1} - {2} sides", i, area.SID, area.Mode.Length)); + // 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 patch_MapData(area.ToKey()); + if (area.IsInterludeUnsafe()) continue; @@ -365,25 +397,6 @@ public static patch_AreaData Get(string sid) { area.Mode[mode].MapData = new patch_MapData(area.ToKey((AreaMode) mode)); } } - - // Sort areas. - Areas.Sort(AreaComparison); - - for (int i = 0; i < Areas.Count; i++) { - // Remove AreaDatas which are now a mode of another AreaData. - // This can happen late as the map data (.bin) can contain additional metadata. - patch_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) { - Logger.Verbose("AreaData", $"Removing area {i} since it has the same path {path} as one of area {otherIndex}'s modes"); - Areas.RemoveAt(i); - i--; - } - } - for (int i = 0; i < Areas.Count; i++) { - Areas[i].ID = i; - } // Load custom mountains // This needs to be done after areas are loaded because it depends on the MapMeta diff --git a/Celeste.Mod.mm/Patches/MapData.cs b/Celeste.Mod.mm/Patches/MapData.cs index e48f92a67..1cb21e116 100644 --- a/Celeste.Mod.mm/Patches/MapData.cs +++ b/Celeste.Mod.mm/Patches/MapData.cs @@ -30,8 +30,8 @@ public MapMetaModeProperties Meta { MapMeta metaAll = patch_AreaData.Get(Area).Meta; return (metaAll?.Modes?.Length ?? 0) > (int) Area.Mode ? - metaAll.Modes[(int) Area.Mode] : - null; + metaAll.Modes[(int) Area.Mode] : + null; } } @@ -170,17 +170,12 @@ private static BinaryPacker.Element _Process(BinaryPacker.Element root, MapData } private BinaryPacker.Element Process(BinaryPacker.Element root) { - if (root.Children == null) { - ProcessMeta(null); + if (root.Children == null) return root; - } // make sure parse meta first, because checkpoint entity needs to read meta - if (root.Children.Find(element => element.Name == "meta") is BinaryPacker.Element meta) { + if (root.Children.Find(element => element.Name == "meta") is BinaryPacker.Element meta) ProcessMeta(meta); - } else { - ProcessMeta(null); - } new MapDataFixup(this).Process(root); @@ -191,38 +186,8 @@ private void ProcessMeta(BinaryPacker.Element meta) { patch_AreaData area = patch_AreaData.Get(Area); AreaMode mode = Area.Mode; - MapMeta metaParsedFromFile = null; - MapMeta metaParsed = null; - - // load metadata from .meta.yaml file - string path = $"Maps/{area.Mode[(int) mode].Path}"; - if (Everest.Content.TryGet(path, out ModAsset asset)) { - metaParsedFromFile = asset.GetMeta(); - if (metaParsedFromFile != null) { - metaParsedFromFile.Modes[(int) mode] = MapMetaModeProperties.Add(metaParsedFromFile.Mode, metaParsedFromFile.Modes[(int) mode]); - metaParsedFromFile.Mode = null; - } - } - - // load metadata from .bin file - if (meta != null) { - metaParsed = new MapMeta(meta); - metaParsed.Modes[(int) mode] = MapMetaModeProperties.Add(metaParsed.Mode, metaParsed.Modes[(int) mode]); - metaParsed.Mode = null; - } - - // merge metadata, with .meta.yaml taking priority - metaParsed = MapMeta.Add(metaParsedFromFile, metaParsed); - - // merge metadata with the existing meta, with the previously merged metadata taking priority - MapMeta combinedMeta = MapMeta.Add(metaParsed, area.Meta); - - // apply metadata to AreaData if (mode == AreaMode.Normal) { - combinedMeta.ApplyTo(area); - for (int i = 0; i < combinedMeta.Modes.Length; i++) { - combinedMeta.Modes[i]?.ApplyTo(area, (AreaMode) i); - } + new MapMeta(meta).ApplyTo(area); Area = area.ToKey(); // Backup A-Side's Metadata. Only back up useful data. @@ -235,9 +200,20 @@ private void ProcessMeta(BinaryPacker.Element meta) { CoreMode = area.CoreMode, Dreaming = area.Dreaming }; - } else { - area.Mode[(int) mode].MapMeta = combinedMeta; - combinedMeta.Modes[(int) mode]?.ApplyTo(area, mode); + } + + BinaryPacker.Element modeMeta = meta.Children?.FirstOrDefault(el => el.Name == "mode"); + if (modeMeta == null) + return; + + new MapMetaModeProperties(modeMeta).ApplyTo(area, mode); + + // Metadata for B-Side and C-Side are parsed and stored. + if (mode != AreaMode.Normal) { + MapMeta mapMeta = new MapMeta(meta) { + Modes = area.Meta.Modes + }; + area.Mode[(int) mode].MapMeta = mapMeta; } } @@ -292,7 +268,6 @@ public static void ParseTags(BinaryPacker.Element child, Backdrop backdrop) { } } } - public static class MapDataExt { // Mods can't access patch_ classes directly.