Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow modded Ouis to specify if they play custom mountain music #863

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions Celeste.Mod.mm/Mod/Everest/Everest.Loader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
19 changes: 19 additions & 0 deletions Celeste.Mod.mm/Mod/UI/OuiPropertiesAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;

namespace Celeste.Mod.UI {
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public class OuiPropertiesAttribute : Attribute {
/// <summary>
/// Whether the mountain music for the current map should play in this menu.
/// </summary>
public bool PlayCustomMusic { get; }

/// <summary>
/// Configures extra properties for this Oui.
/// </summary>
/// <param name="playCustomMusic">A list of unique identifiers for this Backdrop.</param>
public OuiPropertiesAttribute(bool playCustomMusic = false) {
PlayCustomMusic = playCustomMusic;
}
}
}
78 changes: 75 additions & 3 deletions Celeste.Mod.mm/Patches/Overworld.cs
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -14,6 +22,8 @@ class patch_Overworld : Overworld {
private Snow3D Snow3D;
#pragma warning restore CS0649

public Dictionary<Type, OuiPropertiesAttribute> 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.
Expand Down Expand Up @@ -54,9 +64,7 @@ public override void Update() {
return;
}

if (SaveData.Instance != null && (IsCurrent<OuiChapterSelect>() || IsCurrent<OuiChapterPanel>()
|| IsCurrent<OuiMapList>() || IsCurrent<OuiMapSearch>() || IsCurrent<OuiJournal>())) {

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) {
Expand All @@ -79,6 +87,35 @@ public override void Update() {
}
}

public bool IsCurrent(Func<Oui, bool> 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<OuiPropertiesAttribute>()) {
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();
Expand Down Expand Up @@ -108,3 +145,38 @@ private void restoreNormalMusicIfCustomized() {
}
}
}

namespace MonoMod {
/// <summary>
/// Adjust the Overworld.ReloadMenus method to use the RegisterOui function, rather than registering manually
/// </summary>
[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);
}
}
}