Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
28 changes: 28 additions & 0 deletions Celeste.Mod.mm/Mod/Entities/CustomWipeAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System;

namespace Celeste.Mod.Entities;

/// <summary>
/// Mark this renderer as a custom <see cref="ScreenWipe"/> with an identifier.
/// <br/>
/// This Screen Wipe will be applied if the map's Wipe metadata has a matching value.
/// <br/>
/// If there is no match, then the full type name of the Screen Wipe is checked for.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public class CustomWipeAttribute : Attribute {

/// <summary>
/// A list of unique identifiers for this Screen Wipe.
/// </summary>
public string[] IDs;

/// <summary>
/// Mark this renderer as a custom <see cref="ScreenWipe"/> with an identifier.<br/>
/// If there is no match, then the full type name of the Screen Wipe is checked for.
/// </summary>
/// <param name="ids">A list of unique identifiers for this Screen Wipe.</param>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: might be good to note that this follows the ID[=MethodName] pattern!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we do the same for the other attributes too?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, i guess i neglected to check those :p
yea if you can add wording to their docs too that'd be good!

public CustomWipeAttribute(params string[] ids) {
IDs = ids;
}
}
16 changes: 16 additions & 0 deletions Celeste.Mod.mm/Mod/Everest/Everest.Events.cs
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,22 @@ public static class SubHudRenderer {
internal static void BeforeRender(_SubHudRenderer renderer, Scene scene)
=> OnBeforeRender?.Invoke(renderer, scene);
}

public static class MapMeta {
public delegate Action<Scene, bool, Action> ParseWipeHandler(string wipe);
public static event ParseWipeHandler OnParseWipe;
internal static Action<Scene, bool, Action> ParseWipe(string wipe) {
if (OnParseWipe is null)
return null;

foreach (ParseWipeHandler handler in OnParseWipe.GetInvocationList()) {
if (handler(wipe) is { } wipeLoader)
return wipeLoader;
}

return null;
Comment thread
SilverDorian46 marked this conversation as resolved.
Outdated
}
}
}
}
}
47 changes: 47 additions & 0 deletions Celeste.Mod.mm/Mod/Everest/Everest.Loader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -822,6 +822,53 @@ internal static void ProcessAssembly(EverestModuleMetadata meta, Assembly asm, T
}
}

// Search for all Screen Wipes marked with the CustomWipeAttribute.
foreach (CustomWipeAttribute attrib in type.GetCustomAttributes<CustomWipeAttribute>()) {
foreach (string idFull in attrib.IDs) {
string id;
string genName;
string[] split = idFull.Split('=');

if (split.Length == 1) {
id = split[0];
genName = "Load";
} else if (split.Length == 2) {
id = split[0];
genName = split[1];
} else {
Logger.Warn("core", $"Invalid number of custom wipe ID elements: {idFull} ({type.FullName})");
continue;
}

id = id.Trim();
genName = genName.Trim();

Action<Scene, bool, Action> loader = null;

ConstructorInfo ctor;
MethodInfo gen;

gen = type.GetMethod(genName, new Type[] { typeof(Scene), typeof(bool), typeof(Action) });
if (gen != null && gen.IsStatic && gen.ReturnType.IsCompatible(typeof(ScreenWipe))) {
loader = (scene, wipeIn, onComplete) => gen.Invoke(null, new object[] { scene, wipeIn, onComplete });
goto RegisterWipeLoader;
}

ctor = type.GetConstructor(new Type[] { typeof(Scene), typeof(bool), typeof(Action) });
if (ctor != null) {
loader = (scene, wipeIn, onComplete) => ctor.Invoke(new object[] { scene, wipeIn, onComplete });
goto RegisterWipeLoader;
}

RegisterWipeLoader:
if (loader == null) {
Logger.Warn("core", $"Found custom wipe without suitable constructor / {genName}(Scene, bool, Action): {id} ({type.FullName})");
continue;
}
patch_AreaData.WipeLoaders[id] = loader;
}
}

// 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}");
Expand Down
14 changes: 10 additions & 4 deletions Celeste.Mod.mm/Mod/Meta/MapMeta.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,10 +161,16 @@ public void ApplyTo(patch_AreaData area) {
area.ColorGrade = ColorGrade;

if (!string.IsNullOrEmpty(Wipe)) {
Type type = Assembly.GetEntryAssembly().GetType(Wipe);
ConstructorInfo ctor = type?.GetConstructor(new Type[] { typeof(Scene), typeof(bool), typeof(Action) });
if (type != null && ctor != null) {
area.Wipe = (scene, wipeIn, onComplete) => ctor.Invoke(new object[] { scene, wipeIn, onComplete });
string wipeStr = Wipe;
if (Everest.Events.MapMeta.ParseWipe(wipeStr) is { } wipeLoader
|| (patch_AreaData.WipeLoaders.TryGetValue(wipeStr, out wipeLoader) && wipeLoader is not null))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: can this be split into an if/else if? it feels a bit cramped as a single condition

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, will do

area.Wipe = wipeLoader;
else {
Type type = Assembly.GetEntryAssembly().GetType(wipeStr);
ConstructorInfo ctor = type?.GetConstructor(new Type[] { typeof(Scene), typeof(bool), typeof(Action) });
if (type != null && ctor != null) {
area.Wipe = (scene, wipeIn, onComplete) => ctor.Invoke(new object[] { scene, wipeIn, onComplete });
}
}
}

Expand Down
2 changes: 2 additions & 0 deletions Celeste.Mod.mm/Patches/AreaData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
namespace Celeste {
public class patch_AreaData : AreaData {

public static readonly Dictionary<string, Action<Scene, bool, Action>> WipeLoaders = new();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: can the Action<Scene, bool, Action> be a named delegate type instead?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

may just be my experience, but getting an Action and a delegate to work together like this is kind of tricky.
so I thought just using an Action would be easier


#pragma warning disable CS0108 // Hides inherited member

// Required to reference this class in other files
Expand Down