diff --git a/Celeste.Mod.mm/Mod/Everest/Everest.Loader.cs b/Celeste.Mod.mm/Mod/Everest/Everest.Loader.cs index 72a1f7eff..0e181ade8 100644 --- a/Celeste.Mod.mm/Mod/Everest/Everest.Loader.cs +++ b/Celeste.Mod.mm/Mod/Everest/Everest.Loader.cs @@ -777,11 +777,7 @@ internal static void ProcessAssembly(EverestModuleMetadata meta, Assembly asm, T // we already are in the overworld. Register new Ouis real quick! if (Engine.Instance != null && Engine.Scene is Overworld overworld && typeof(Oui).IsAssignableFrom(type) && !type.IsAbstract) { Logger.Verbose("core", $"Instantiating UI from {meta}: {type.FullName}"); - - Oui oui = (Oui) Activator.CreateInstance(type); - oui.Visible = false; - overworld.Add(oui); - overworld.UIs.Add(oui); + ((patch_Overworld) overworld).RegisterOui(type); } } // We should run the map data processors again if new berry types are registered, so that CoreMapDataProcessor assigns them checkpoint IDs and orders. diff --git a/Celeste.Mod.mm/Mod/UI/OuiPropertiesAttribute.cs b/Celeste.Mod.mm/Mod/UI/OuiPropertiesAttribute.cs new file mode 100644 index 000000000..3a4d60682 --- /dev/null +++ b/Celeste.Mod.mm/Mod/UI/OuiPropertiesAttribute.cs @@ -0,0 +1,19 @@ +using System; + +namespace Celeste.Mod.UI { + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] + public class OuiPropertiesAttribute : Attribute { + /// + /// Whether the mountain music for the current map should play in this menu. + /// + public bool PlayCustomMusic { get; } + + /// + /// Configures extra properties for this Oui. + /// + /// A list of unique identifiers for this Backdrop. + public OuiPropertiesAttribute(bool playCustomMusic = false) { + PlayCustomMusic = playCustomMusic; + } + } +} diff --git a/Celeste.Mod.mm/Patches/Overworld.cs b/Celeste.Mod.mm/Patches/Overworld.cs index 140a145e5..30f86a904 100644 --- a/Celeste.Mod.mm/Patches/Overworld.cs +++ b/Celeste.Mod.mm/Patches/Overworld.cs @@ -1,10 +1,18 @@ #pragma warning disable CS0626 // Method, operator, or accessor is marked external and has no attributes on it +using Celeste; using Celeste.Mod; using Celeste.Mod.Meta; using Celeste.Mod.UI; +using Mono.Cecil; using Monocle; +using MonoMod; +using MonoMod.Cil; +using MonoMod.InlineRT; +using MonoMod.Utils; +using System; using System.Collections.Generic; +using System.Reflection; namespace Celeste { class patch_Overworld : Overworld { @@ -14,6 +22,8 @@ class patch_Overworld : Overworld { private Snow3D Snow3D; #pragma warning restore CS0649 + public Dictionary UIProperties { get; set; } + public patch_Overworld(OverworldLoader loader) : base(loader) { // no-op. MonoMod ignores this - we only need this to make the compiler shut up. @@ -54,9 +64,7 @@ public override void Update() { return; } - if (SaveData.Instance != null && (IsCurrent() || IsCurrent() - || IsCurrent() || IsCurrent() || IsCurrent())) { - + if (SaveData.Instance != null && IsCurrent(o => UIProperties?.GetValueOrDefault(o.GetType())?.PlayCustomMusic ?? false)) { string backgroundMusic = mountainMetadata?.BackgroundMusic; string backgroundAmbience = mountainMetadata?.BackgroundAmbience; if (backgroundMusic != null || backgroundAmbience != null) { @@ -79,6 +87,35 @@ public override void Update() { } } + public bool IsCurrent(Func predicate) { + if (Current != null) { + return predicate(Current); + } + return predicate(Last); + } + + public Oui RegisterOui(Type type) { + Oui oui = (Oui) Activator.CreateInstance(type); + oui.Visible = false; + Add(oui); + UIs.Add(oui); + UIProperties ??= new() { + { typeof(OuiChapterSelect), new(true) }, + { typeof(OuiChapterPanel), new(true) }, + { typeof(OuiMapList), new(true) }, + { typeof(OuiMapSearch), new(true) }, + { typeof(OuiJournal), new(true) } + }; + foreach (OuiPropertiesAttribute attrib in type.GetCustomAttributes()) { + UIProperties[type] = attrib; + } + return oui; + } + + [MonoModIgnore] + [PatchOverworldRegisterOui] + public new extern void ReloadMenus(StartMode startMode = StartMode.Titlescreen); + public extern void orig_ReloadMountainStuff(); public new void ReloadMountainStuff() { orig_ReloadMountainStuff(); @@ -108,3 +145,38 @@ private void restoreNormalMusicIfCustomized() { } } } + +namespace MonoMod { + /// + /// Adjust the Overworld.ReloadMenus method to use the RegisterOui function, rather than registering manually + /// + [MonoModCustomMethodAttribute(nameof(MonoModRules.PatchOverworldRegisterOuiFunction))] + class PatchOverworldRegisterOuiAttribute : Attribute { } + + static partial class MonoModRules { + public static void PatchOverworldRegisterOuiFunction(ILContext il, CustomAttribute attrib) { + MethodDefinition registerOuiMethod = MonoModRule.Modder.Module.GetType("Celeste.Overworld").FindMethod(nameof(patch_Overworld.RegisterOui)); + //MethodInfo registerOuiMethod = typeof(patch_Overworld).GetMethod(nameof(patch_Overworld.RegisterOui)); + + ILCursor c = new(il); + c.GotoNext(MoveType.Before, + instr => instr.MatchLdloc(4), + instr => instr.MatchCall("System.Activator", nameof(Activator.CreateInstance))); + // We have Oui oui = (Oui)Activator.CreateInstance(type); + // Replace the right side of the equals with our register function + + // this. + c.EmitLdarg0(); + // Skip past `type` argument + c.GotoNext().GotoNext(); + c.Remove(); + c.Remove(); + // RegisterOui() + c.EmitCall(registerOuiMethod); + // Skip past the "Oui oui =" + c.GotoNext().GotoNext(); + // The next 10 instructions are already present in RegisterOui and can be removed + c.RemoveRange(10); + } + } +}