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);
+ }
+ }
+}