diff --git a/Celeste.Mod.mm/Mod/Backdrops/CustomBackdropAttribute.cs b/Celeste.Mod.mm/Mod/Backdrops/CustomBackdropAttribute.cs
index 79be0fbdb..5e7af635e 100644
--- a/Celeste.Mod.mm/Mod/Backdrops/CustomBackdropAttribute.cs
+++ b/Celeste.Mod.mm/Mod/Backdrops/CustomBackdropAttribute.cs
@@ -9,14 +9,18 @@ namespace Celeste.Mod.Backdrops {
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public class CustomBackdropAttribute : Attribute {
///
- /// A list of unique identifiers for this Backdrop.
+ /// A list of unique identifiers for this Backdrop.
+ /// Follows the pattern "ID [= LoadMethodName]".
///
public string[] IDs { get; }
///
/// Marks this backdrop as a Custom .
///
- /// A list of unique identifiers for this Backdrop.
+ ///
+ /// A list of unique identifiers for this Backdrop.
+ /// Follows the pattern "ID [= LoadMethodName]".
+ ///
public CustomBackdropAttribute(params string[] ids) {
IDs = ids;
}
diff --git a/Celeste.Mod.mm/Mod/Entities/CustomEntityAttribute.cs b/Celeste.Mod.mm/Mod/Entities/CustomEntityAttribute.cs
index af29475e5..bed6419e2 100644
--- a/Celeste.Mod.mm/Mod/Entities/CustomEntityAttribute.cs
+++ b/Celeste.Mod.mm/Mod/Entities/CustomEntityAttribute.cs
@@ -13,14 +13,18 @@ namespace Celeste.Mod.Entities {
public class CustomEntityAttribute : Attribute {
///
- /// A list of unique identifiers for this Entity.
+ /// A list of unique identifiers for this Entity.
+ /// Follows the pattern "ID [= LoadMethodName]".
///
public string[] IDs;
///
/// Mark this entity as a Custom or .
///
- /// A list of unique identifiers for this Entity.
+ ///
+ /// A list of unique identifiers for this Entity.
+ /// Follows the pattern "ID [= LoadMethodName]".
+ ///
public CustomEntityAttribute(params string[] ids) {
IDs = ids;
}
diff --git a/Celeste.Mod.mm/Mod/Entities/CustomEventAttribute.cs b/Celeste.Mod.mm/Mod/Entities/CustomEventAttribute.cs
index 925743673..80b6d7b54 100644
--- a/Celeste.Mod.mm/Mod/Entities/CustomEventAttribute.cs
+++ b/Celeste.Mod.mm/Mod/Entities/CustomEventAttribute.cs
@@ -13,14 +13,18 @@ namespace Celeste.Mod.Entities {
public class CustomEventAttribute : Attribute {
///
- /// A list of unique identifiers for this Event.
+ /// A list of unique identifiers for this Event.
+ /// Follows the pattern "ID [= LoadMethodName]".
///
public string[] IDs;
///
/// Mark this entity as a Custom or other Event .
///
- /// A list of unique identifiers for this Event.
+ ///
+ /// A list of unique identifiers for this Event.
+ /// Follows the pattern "ID [= LoadMethodName]".
+ ///
public CustomEventAttribute(params string[] ids) {
IDs = ids;
}
diff --git a/Celeste.Mod.mm/Mod/Entities/CustomWipeAttribute.cs b/Celeste.Mod.mm/Mod/Entities/CustomWipeAttribute.cs
new file mode 100644
index 000000000..e77ebad3c
--- /dev/null
+++ b/Celeste.Mod.mm/Mod/Entities/CustomWipeAttribute.cs
@@ -0,0 +1,32 @@
+using System;
+
+namespace Celeste.Mod.Entities;
+
+///
+/// Mark this renderer as a custom with an identifier.
+///
+/// This Screen Wipe will be applied if the map's Wipe metadata has a matching value.
+///
+/// If there is no match, then the full type name of the Screen Wipe is checked for.
+///
+[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
+public class CustomWipeAttribute : Attribute {
+
+ ///
+ /// A list of unique identifiers for this Screen Wipe.
+ /// Follows the pattern "ID [= LoadMethodName]".
+ ///
+ public string[] IDs;
+
+ ///
+ /// Mark this renderer as a custom with an identifier.
+ /// If there is no match, then the full type name of the Screen Wipe is checked for.
+ ///
+ ///
+ /// A list of unique identifiers for this Screen Wipe.
+ /// Follows the pattern "ID [= LoadMethodName]".
+ ///
+ public CustomWipeAttribute(params string[] ids) {
+ IDs = ids;
+ }
+}
diff --git a/Celeste.Mod.mm/Mod/Everest/Everest.Events.cs b/Celeste.Mod.mm/Mod/Everest/Everest.Events.cs
index adc5dc5ab..2ebc645e6 100644
--- a/Celeste.Mod.mm/Mod/Everest/Everest.Events.cs
+++ b/Celeste.Mod.mm/Mod/Everest/Everest.Events.cs
@@ -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 ParseWipeHandler(string wipe);
+ public static event ParseWipeHandler OnParseWipe;
+ internal static 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;
+ }
+ }
}
}
}
diff --git a/Celeste.Mod.mm/Mod/Everest/Everest.Loader.cs b/Celeste.Mod.mm/Mod/Everest/Everest.Loader.cs
index 0271ed89c..880789c2d 100644
--- a/Celeste.Mod.mm/Mod/Everest/Everest.Loader.cs
+++ b/Celeste.Mod.mm/Mod/Everest/Everest.Loader.cs
@@ -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()) {
+ 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 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}");
diff --git a/Celeste.Mod.mm/Mod/Meta/MapMeta.cs b/Celeste.Mod.mm/Mod/Meta/MapMeta.cs
index 6614d80ee..46f63aa1f 100644
--- a/Celeste.Mod.mm/Mod/Meta/MapMeta.cs
+++ b/Celeste.Mod.mm/Mod/Meta/MapMeta.cs
@@ -161,10 +161,17 @@ 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)
+ area.Wipe = wipeLoader;
+ else if (patch_AreaData.WipeLoaders.TryGetValue(wipeStr, out wipeLoader) && wipeLoader is not null)
+ 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 });
+ }
}
}
diff --git a/Celeste.Mod.mm/Patches/AreaData.cs b/Celeste.Mod.mm/Patches/AreaData.cs
index df8c3ff0e..b5c948e49 100644
--- a/Celeste.Mod.mm/Patches/AreaData.cs
+++ b/Celeste.Mod.mm/Patches/AreaData.cs
@@ -14,6 +14,8 @@
namespace Celeste {
public class patch_AreaData : AreaData {
+ public static readonly Dictionary> WipeLoaders = new();
+
#pragma warning disable CS0108 // Hides inherited member
// Required to reference this class in other files