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